@claude-flow/cli 3.32.2 → 3.32.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/helpers/helpers.manifest.json +3 -3
- package/.claude/helpers/statusline.cjs +38 -38
- package/catalog-manifest.json +2 -2
- package/package.json +2 -2
- package/plugins/ruflo-metaharness/scripts/smoke.sh +11 -11
- package/dist/src/agenticow/speculative-exploration.d.ts +0 -148
- package/dist/src/agenticow/speculative-exploration.js +0 -218
- package/dist/src/appliance/gguf-engine.d.ts +0 -91
- package/dist/src/appliance/gguf-engine.js +0 -425
- package/dist/src/appliance/ruvllm-bridge.d.ts +0 -102
- package/dist/src/appliance/ruvllm-bridge.js +0 -292
- package/dist/src/appliance/rvfa-builder.d.ts +0 -44
- package/dist/src/appliance/rvfa-builder.js +0 -329
- package/dist/src/appliance/rvfa-distribution.d.ts +0 -97
- package/dist/src/appliance/rvfa-distribution.js +0 -370
- package/dist/src/appliance/rvfa-format.d.ts +0 -111
- package/dist/src/appliance/rvfa-format.js +0 -393
- package/dist/src/appliance/rvfa-runner.d.ts +0 -69
- package/dist/src/appliance/rvfa-runner.js +0 -237
- package/dist/src/appliance/rvfa-signing.d.ts +0 -123
- package/dist/src/appliance/rvfa-signing.js +0 -347
- package/dist/src/autopilot-state.d.ts +0 -77
- package/dist/src/autopilot-state.js +0 -271
- package/dist/src/benchmarks/gaia-agent-planning.smoke.d.ts +0 -18
- package/dist/src/benchmarks/gaia-agent-planning.smoke.js +0 -253
- package/dist/src/benchmarks/gaia-agent.d.ts +0 -198
- package/dist/src/benchmarks/gaia-agent.js +0 -651
- package/dist/src/benchmarks/gaia-causal-memory.d.ts +0 -133
- package/dist/src/benchmarks/gaia-causal-memory.js +0 -281
- package/dist/src/benchmarks/gaia-causal-memory.smoke.d.ts +0 -22
- package/dist/src/benchmarks/gaia-causal-memory.smoke.js +0 -300
- package/dist/src/benchmarks/gaia-convergence.d.ts +0 -138
- package/dist/src/benchmarks/gaia-convergence.js +0 -260
- package/dist/src/benchmarks/gaia-convergence.smoke.d.ts +0 -19
- package/dist/src/benchmarks/gaia-convergence.smoke.js +0 -246
- package/dist/src/benchmarks/gaia-critic.d.ts +0 -123
- package/dist/src/benchmarks/gaia-critic.js +0 -312
- package/dist/src/benchmarks/gaia-critic.smoke.d.ts +0 -21
- package/dist/src/benchmarks/gaia-critic.smoke.js +0 -327
- package/dist/src/benchmarks/gaia-decomposer.d.ts +0 -125
- package/dist/src/benchmarks/gaia-decomposer.js +0 -350
- package/dist/src/benchmarks/gaia-decomposer.smoke.d.ts +0 -21
- package/dist/src/benchmarks/gaia-decomposer.smoke.js +0 -228
- package/dist/src/benchmarks/gaia-e2e-smoke.d.ts +0 -27
- package/dist/src/benchmarks/gaia-e2e-smoke.js +0 -136
- package/dist/src/benchmarks/gaia-extract.smoke.d.ts +0 -45
- package/dist/src/benchmarks/gaia-extract.smoke.js +0 -242
- package/dist/src/benchmarks/gaia-hardness/features.d.ts +0 -46
- package/dist/src/benchmarks/gaia-hardness/features.js +0 -170
- package/dist/src/benchmarks/gaia-hardness/predictor.d.ts +0 -105
- package/dist/src/benchmarks/gaia-hardness/predictor.js +0 -260
- package/dist/src/benchmarks/gaia-hardness/predictor.smoke.d.ts +0 -20
- package/dist/src/benchmarks/gaia-hardness/predictor.smoke.js +0 -235
- package/dist/src/benchmarks/gaia-hardness/train-data-loader.d.ts +0 -51
- package/dist/src/benchmarks/gaia-hardness/train-data-loader.js +0 -179
- package/dist/src/benchmarks/gaia-judge.d.ts +0 -88
- package/dist/src/benchmarks/gaia-judge.js +0 -437
- package/dist/src/benchmarks/gaia-loader.d.ts +0 -87
- package/dist/src/benchmarks/gaia-loader.js +0 -326
- package/dist/src/benchmarks/gaia-tools/file_read.d.ts +0 -35
- package/dist/src/benchmarks/gaia-tools/file_read.js +0 -403
- package/dist/src/benchmarks/gaia-tools/grounded_query.d.ts +0 -126
- package/dist/src/benchmarks/gaia-tools/grounded_query.js +0 -225
- package/dist/src/benchmarks/gaia-tools/index.d.ts +0 -32
- package/dist/src/benchmarks/gaia-tools/index.js +0 -36
- package/dist/src/benchmarks/gaia-tools/types.d.ts +0 -62
- package/dist/src/benchmarks/gaia-tools/types.js +0 -12
- package/dist/src/benchmarks/gaia-tools/web_search.d.ts +0 -30
- package/dist/src/benchmarks/gaia-tools/web_search.js +0 -210
- package/dist/src/benchmarks/gaia-voting.d.ts +0 -88
- package/dist/src/benchmarks/gaia-voting.js +0 -297
- package/dist/src/benchmarks/gaia-voting.smoke.d.ts +0 -20
- package/dist/src/benchmarks/gaia-voting.smoke.js +0 -332
- package/dist/src/benchmarks/pretrain/index.d.ts +0 -58
- package/dist/src/benchmarks/pretrain/index.js +0 -404
- package/dist/src/business-pods/bbs-budget-tracker.d.ts +0 -139
- package/dist/src/business-pods/bbs-budget-tracker.js +0 -358
- package/dist/src/business-pods/domain-affinity-policy.d.ts +0 -47
- package/dist/src/business-pods/domain-affinity-policy.js +0 -65
- package/dist/src/business-pods/pod-schema.d.ts +0 -96
- package/dist/src/business-pods/pod-schema.js +0 -225
- package/dist/src/commands/advisor.d.ts +0 -15
- package/dist/src/commands/advisor.js +0 -94
- package/dist/src/commands/agent-wasm.d.ts +0 -14
- package/dist/src/commands/agent-wasm.js +0 -333
- package/dist/src/commands/agent.d.ts +0 -8
- package/dist/src/commands/agent.js +0 -927
- package/dist/src/commands/analyze.d.ts +0 -19
- package/dist/src/commands/analyze.js +0 -2047
- package/dist/src/commands/announcements.d.ts +0 -17
- package/dist/src/commands/announcements.js +0 -0
- package/dist/src/commands/appliance-advanced.d.ts +0 -9
- package/dist/src/commands/appliance-advanced.js +0 -215
- package/dist/src/commands/appliance.d.ts +0 -8
- package/dist/src/commands/appliance.js +0 -404
- package/dist/src/commands/autopilot.d.ts +0 -15
- package/dist/src/commands/autopilot.js +0 -407
- package/dist/src/commands/benchmark.d.ts +0 -10
- package/dist/src/commands/benchmark.js +0 -460
- package/dist/src/commands/claims.d.ts +0 -10
- package/dist/src/commands/claims.js +0 -620
- package/dist/src/commands/cleanup.d.ts +0 -13
- package/dist/src/commands/cleanup.js +0 -250
- package/dist/src/commands/completions.d.ts +0 -10
- package/dist/src/commands/completions.js +0 -539
- package/dist/src/commands/config.d.ts +0 -8
- package/dist/src/commands/config.js +0 -428
- package/dist/src/commands/daemon.d.ts +0 -59
- package/dist/src/commands/daemon.js +0 -1733
- package/dist/src/commands/deployment.d.ts +0 -10
- package/dist/src/commands/deployment.js +0 -672
- package/dist/src/commands/doctor.d.ts +0 -10
- package/dist/src/commands/doctor.js +0 -1695
- package/dist/src/commands/eject.d.ts +0 -33
- package/dist/src/commands/eject.js +0 -195
- package/dist/src/commands/embeddings.d.ts +0 -18
- package/dist/src/commands/embeddings.js +0 -1623
- package/dist/src/commands/funnel.d.ts +0 -18
- package/dist/src/commands/funnel.js +0 -302
- package/dist/src/commands/gaia-bench.d.ts +0 -40
- package/dist/src/commands/gaia-bench.js +0 -597
- package/dist/src/commands/guidance.d.ts +0 -8
- package/dist/src/commands/guidance.js +0 -556
- package/dist/src/commands/hive-mind.d.ts +0 -11
- package/dist/src/commands/hive-mind.js +0 -1319
- package/dist/src/commands/hooks.d.ts +0 -8
- package/dist/src/commands/hooks.js +0 -4606
- package/dist/src/commands/index.d.ts +0 -118
- package/dist/src/commands/index.js +0 -364
- package/dist/src/commands/init.d.ts +0 -13
- package/dist/src/commands/init.js +0 -1297
- package/dist/src/commands/issues.d.ts +0 -21
- package/dist/src/commands/issues.js +0 -567
- package/dist/src/commands/mcp.d.ts +0 -11
- package/dist/src/commands/mcp.js +0 -732
- package/dist/src/commands/memory-backup.d.ts +0 -11
- package/dist/src/commands/memory-backup.js +0 -46
- package/dist/src/commands/memory-distill.d.ts +0 -27
- package/dist/src/commands/memory-distill.js +0 -374
- package/dist/src/commands/memory.d.ts +0 -8
- package/dist/src/commands/memory.js +0 -1596
- package/dist/src/commands/metaharness.d.ts +0 -39
- package/dist/src/commands/metaharness.js +0 -215
- package/dist/src/commands/migrate.d.ts +0 -8
- package/dist/src/commands/migrate.js +0 -742
- package/dist/src/commands/neural.d.ts +0 -10
- package/dist/src/commands/neural.js +0 -4331
- package/dist/src/commands/performance.d.ts +0 -10
- package/dist/src/commands/performance.js +0 -583
- package/dist/src/commands/plugins.d.ts +0 -11
- package/dist/src/commands/plugins.js +0 -826
- package/dist/src/commands/process.d.ts +0 -10
- package/dist/src/commands/process.js +0 -694
- package/dist/src/commands/progress.d.ts +0 -11
- package/dist/src/commands/progress.js +0 -259
- package/dist/src/commands/providers.d.ts +0 -10
- package/dist/src/commands/providers.js +0 -502
- package/dist/src/commands/proxy.d.ts +0 -21
- package/dist/src/commands/proxy.js +0 -310
- package/dist/src/commands/route.d.ts +0 -16
- package/dist/src/commands/route.js +0 -822
- package/dist/src/commands/ruvector/backup.d.ts +0 -11
- package/dist/src/commands/ruvector/backup.js +0 -747
- package/dist/src/commands/ruvector/benchmark.d.ts +0 -11
- package/dist/src/commands/ruvector/benchmark.js +0 -490
- package/dist/src/commands/ruvector/import.d.ts +0 -18
- package/dist/src/commands/ruvector/import.js +0 -373
- package/dist/src/commands/ruvector/index.d.ts +0 -29
- package/dist/src/commands/ruvector/index.js +0 -129
- package/dist/src/commands/ruvector/init.d.ts +0 -11
- package/dist/src/commands/ruvector/init.js +0 -467
- package/dist/src/commands/ruvector/migrate.d.ts +0 -11
- package/dist/src/commands/ruvector/migrate.js +0 -498
- package/dist/src/commands/ruvector/optimize.d.ts +0 -11
- package/dist/src/commands/ruvector/optimize.js +0 -505
- package/dist/src/commands/ruvector/pg-utils.d.ts +0 -14
- package/dist/src/commands/ruvector/pg-utils.js +0 -41
- package/dist/src/commands/ruvector/setup.d.ts +0 -18
- package/dist/src/commands/ruvector/setup.js +0 -765
- package/dist/src/commands/ruvector/status.d.ts +0 -11
- package/dist/src/commands/ruvector/status.js +0 -479
- package/dist/src/commands/security.d.ts +0 -10
- package/dist/src/commands/security.js +0 -1012
- package/dist/src/commands/session.d.ts +0 -8
- package/dist/src/commands/session.js +0 -757
- package/dist/src/commands/settings.d.ts +0 -19
- package/dist/src/commands/settings.js +0 -180
- package/dist/src/commands/spinner.d.ts +0 -16
- package/dist/src/commands/spinner.js +0 -329
- package/dist/src/commands/start.d.ts +0 -8
- package/dist/src/commands/start.js +0 -418
- package/dist/src/commands/status.d.ts +0 -8
- package/dist/src/commands/status.js +0 -608
- package/dist/src/commands/swarm.d.ts +0 -8
- package/dist/src/commands/swarm.js +0 -891
- package/dist/src/commands/task.d.ts +0 -8
- package/dist/src/commands/task.js +0 -675
- package/dist/src/commands/transfer-store.d.ts +0 -13
- package/dist/src/commands/transfer-store.js +0 -428
- package/dist/src/commands/update.d.ts +0 -8
- package/dist/src/commands/update.js +0 -276
- package/dist/src/commands/verify.d.ts +0 -19
- package/dist/src/commands/verify.js +0 -261
- package/dist/src/commands/version.d.ts +0 -42
- package/dist/src/commands/version.js +0 -106
- package/dist/src/commands/workflow.d.ts +0 -8
- package/dist/src/commands/workflow.js +0 -617
- package/dist/src/config/harness-feedback-applier.d.ts +0 -50
- package/dist/src/config/harness-feedback-applier.js +0 -122
- package/dist/src/config/proven-config-refresh.d.ts +0 -39
- package/dist/src/config/proven-config-refresh.js +0 -154
- package/dist/src/config/proven-config-rvfa.d.ts +0 -23
- package/dist/src/config/proven-config-rvfa.js +0 -73
- package/dist/src/config/proven-config.d.ts +0 -86
- package/dist/src/config/proven-config.js +0 -176
- package/dist/src/config-adapter.d.ts +0 -15
- package/dist/src/config-adapter.js +0 -186
- package/dist/src/encryption/vault.d.ts +0 -94
- package/dist/src/encryption/vault.js +0 -172
- package/dist/src/fs-secure.d.ts +0 -86
- package/dist/src/fs-secure.js +0 -133
- package/dist/src/funnel/advisor-tip.d.ts +0 -58
- package/dist/src/funnel/advisor-tip.js +0 -92
- package/dist/src/funnel/attribution.d.ts +0 -37
- package/dist/src/funnel/attribution.js +0 -101
- package/dist/src/funnel/consent.d.ts +0 -22
- package/dist/src/funnel/consent.js +0 -58
- package/dist/src/funnel/credit-errors.d.ts +0 -31
- package/dist/src/funnel/credit-errors.js +0 -88
- package/dist/src/funnel/credit-notifier.d.ts +0 -44
- package/dist/src/funnel/credit-notifier.js +0 -74
- package/dist/src/funnel/disclosure.d.ts +0 -47
- package/dist/src/funnel/disclosure.js +0 -109
- package/dist/src/funnel/enrollment.d.ts +0 -36
- package/dist/src/funnel/enrollment.js +0 -64
- package/dist/src/funnel/environment.d.ts +0 -17
- package/dist/src/funnel/environment.js +0 -39
- package/dist/src/funnel/event-transport.d.ts +0 -51
- package/dist/src/funnel/event-transport.js +0 -199
- package/dist/src/funnel/events.d.ts +0 -42
- package/dist/src/funnel/events.js +0 -150
- package/dist/src/funnel/index.d.ts +0 -21
- package/dist/src/funnel/index.js +0 -21
- package/dist/src/funnel/insights.d.ts +0 -50
- package/dist/src/funnel/insights.js +0 -120
- package/dist/src/funnel/local-signals.d.ts +0 -20
- package/dist/src/funnel/local-signals.js +0 -77
- package/dist/src/funnel/message-transport.d.ts +0 -51
- package/dist/src/funnel/message-transport.js +0 -149
- package/dist/src/funnel/messages.d.ts +0 -55
- package/dist/src/funnel/messages.js +0 -160
- package/dist/src/funnel/payout.d.ts +0 -40
- package/dist/src/funnel/payout.js +0 -60
- package/dist/src/funnel/power-saver-notifier.d.ts +0 -44
- package/dist/src/funnel/power-saver-notifier.js +0 -92
- package/dist/src/funnel/precedence.d.ts +0 -16
- package/dist/src/funnel/precedence.js +0 -85
- package/dist/src/funnel/promo.d.ts +0 -41
- package/dist/src/funnel/promo.js +0 -144
- package/dist/src/funnel/rate-limit-notifier.d.ts +0 -55
- package/dist/src/funnel/rate-limit-notifier.js +0 -102
- package/dist/src/funnel/rotation.d.ts +0 -19
- package/dist/src/funnel/rotation.js +0 -70
- package/dist/src/funnel/state.d.ts +0 -13
- package/dist/src/funnel/state.js +0 -52
- package/dist/src/funnel/toggle-cooldown.d.ts +0 -17
- package/dist/src/funnel/toggle-cooldown.js +0 -32
- package/dist/src/funnel/types.d.ts +0 -98
- package/dist/src/funnel/types.js +0 -26
- package/dist/src/index.d.ts +0 -81
- package/dist/src/index.js +0 -609
- package/dist/src/infrastructure/in-memory-repositories.d.ts +0 -68
- package/dist/src/infrastructure/in-memory-repositories.js +0 -264
- package/dist/src/init/claudemd-generator.d.ts +0 -16
- package/dist/src/init/claudemd-generator.js +0 -368
- package/dist/src/init/executor.d.ts +0 -41
- package/dist/src/init/executor.js +0 -2142
- package/dist/src/init/helper-refresh.d.ts +0 -79
- package/dist/src/init/helper-refresh.js +0 -347
- package/dist/src/init/helper-signing.d.ts +0 -37
- package/dist/src/init/helper-signing.js +0 -67
- package/dist/src/init/helpers-generator.d.ts +0 -87
- package/dist/src/init/helpers-generator.js +0 -1456
- package/dist/src/init/index.d.ts +0 -13
- package/dist/src/init/index.js +0 -15
- package/dist/src/init/mcp-generator.d.ts +0 -26
- package/dist/src/init/mcp-generator.js +0 -126
- package/dist/src/init/memory-package-resolver.d.ts +0 -53
- package/dist/src/init/memory-package-resolver.js +0 -118
- package/dist/src/init/settings-generator.d.ts +0 -14
- package/dist/src/init/settings-generator.js +0 -462
- package/dist/src/init/statusline-generator.d.ts +0 -28
- package/dist/src/init/statusline-generator.js +0 -177
- package/dist/src/init/types.d.ts +0 -331
- package/dist/src/init/types.js +0 -276
- package/dist/src/log-filters.d.ts +0 -46
- package/dist/src/log-filters.js +0 -107
- package/dist/src/mcp-client.d.ts +0 -92
- package/dist/src/mcp-client.js +0 -397
- package/dist/src/mcp-server.d.ts +0 -163
- package/dist/src/mcp-server.js +0 -751
- package/dist/src/mcp-tools/agent-execute-core.d.ts +0 -115
- package/dist/src/mcp-tools/agent-execute-core.js +0 -587
- package/dist/src/mcp-tools/agent-tools.d.ts +0 -9
- package/dist/src/mcp-tools/agent-tools.js +0 -837
- package/dist/src/mcp-tools/agentbbs-tools.d.ts +0 -28
- package/dist/src/mcp-tools/agentbbs-tools.js +0 -394
- package/dist/src/mcp-tools/agentdb-tools.d.ts +0 -35
- package/dist/src/mcp-tools/agentdb-tools.js +0 -1473
- package/dist/src/mcp-tools/agenticow-loader.d.ts +0 -59
- package/dist/src/mcp-tools/agenticow-loader.js +0 -105
- package/dist/src/mcp-tools/agenticow-speculate-tools.d.ts +0 -24
- package/dist/src/mcp-tools/agenticow-speculate-tools.js +0 -209
- package/dist/src/mcp-tools/agenticow-tools.d.ts +0 -36
- package/dist/src/mcp-tools/agenticow-tools.js +0 -360
- package/dist/src/mcp-tools/analyze-tools.d.ts +0 -38
- package/dist/src/mcp-tools/analyze-tools.js +0 -346
- package/dist/src/mcp-tools/auto-install.d.ts +0 -83
- package/dist/src/mcp-tools/auto-install.js +0 -131
- package/dist/src/mcp-tools/autopilot-tools.d.ts +0 -12
- package/dist/src/mcp-tools/autopilot-tools.js +0 -231
- package/dist/src/mcp-tools/browser-intent-tools.d.ts +0 -162
- package/dist/src/mcp-tools/browser-intent-tools.js +0 -548
- package/dist/src/mcp-tools/browser-session-tools.d.ts +0 -27
- package/dist/src/mcp-tools/browser-session-tools.js +0 -398
- package/dist/src/mcp-tools/browser-tools.d.ts +0 -21
- package/dist/src/mcp-tools/browser-tools.js +0 -760
- package/dist/src/mcp-tools/business-pod-tools.d.ts +0 -20
- package/dist/src/mcp-tools/business-pod-tools.js +0 -169
- package/dist/src/mcp-tools/claims-tools.d.ts +0 -12
- package/dist/src/mcp-tools/claims-tools.js +0 -863
- package/dist/src/mcp-tools/config-tools.d.ts +0 -8
- package/dist/src/mcp-tools/config-tools.js +0 -411
- package/dist/src/mcp-tools/coordination-tools.d.ts +0 -13
- package/dist/src/mcp-tools/coordination-tools.js +0 -729
- package/dist/src/mcp-tools/daa-tools.d.ts +0 -13
- package/dist/src/mcp-tools/daa-tools.js +0 -534
- package/dist/src/mcp-tools/embeddings-tools.d.ts +0 -9
- package/dist/src/mcp-tools/embeddings-tools.js +0 -904
- package/dist/src/mcp-tools/github-tools.d.ts +0 -9
- package/dist/src/mcp-tools/github-tools.js +0 -659
- package/dist/src/mcp-tools/guidance-tools.d.ts +0 -15
- package/dist/src/mcp-tools/guidance-tools.js +0 -639
- package/dist/src/mcp-tools/hive-mind-tools.d.ts +0 -8
- package/dist/src/mcp-tools/hive-mind-tools.js +0 -953
- package/dist/src/mcp-tools/hooks-tools.d.ts +0 -65
- package/dist/src/mcp-tools/hooks-tools.js +0 -4836
- package/dist/src/mcp-tools/http-fetch-tools.d.ts +0 -55
- package/dist/src/mcp-tools/http-fetch-tools.js +0 -329
- package/dist/src/mcp-tools/index.d.ts +0 -34
- package/dist/src/mcp-tools/index.js +0 -40
- package/dist/src/mcp-tools/managed-agent-tools.d.ts +0 -22
- package/dist/src/mcp-tools/managed-agent-tools.js +0 -357
- package/dist/src/mcp-tools/memory-tools.d.ts +0 -14
- package/dist/src/mcp-tools/memory-tools.js +0 -1330
- package/dist/src/mcp-tools/metaharness-tools.d.ts +0 -51
- package/dist/src/mcp-tools/metaharness-tools.js +0 -684
- package/dist/src/mcp-tools/neural-tools.d.ts +0 -54
- package/dist/src/mcp-tools/neural-tools.js +0 -1168
- package/dist/src/mcp-tools/performance-tools.d.ts +0 -16
- package/dist/src/mcp-tools/performance-tools.js +0 -675
- package/dist/src/mcp-tools/progress-tools.d.ts +0 -14
- package/dist/src/mcp-tools/progress-tools.js +0 -348
- package/dist/src/mcp-tools/request-tracker.d.ts +0 -17
- package/dist/src/mcp-tools/request-tracker.js +0 -27
- package/dist/src/mcp-tools/ruvllm-tools.d.ts +0 -9
- package/dist/src/mcp-tools/ruvllm-tools.js +0 -355
- package/dist/src/mcp-tools/security-tools.d.ts +0 -18
- package/dist/src/mcp-tools/security-tools.js +0 -556
- package/dist/src/mcp-tools/session-tools.d.ts +0 -8
- package/dist/src/mcp-tools/session-tools.js +0 -517
- package/dist/src/mcp-tools/swarm-tools.d.ts +0 -37
- package/dist/src/mcp-tools/swarm-tools.js +0 -390
- package/dist/src/mcp-tools/system-tools.d.ts +0 -13
- package/dist/src/mcp-tools/system-tools.js +0 -688
- package/dist/src/mcp-tools/task-tools.d.ts +0 -8
- package/dist/src/mcp-tools/task-tools.js +0 -487
- package/dist/src/mcp-tools/terminal-tools.d.ts +0 -8
- package/dist/src/mcp-tools/terminal-tools.js +0 -306
- package/dist/src/mcp-tools/testgen-tools.d.ts +0 -26
- package/dist/src/mcp-tools/testgen-tools.js +0 -168
- package/dist/src/mcp-tools/tool-loop-guardrail.d.ts +0 -31
- package/dist/src/mcp-tools/tool-loop-guardrail.js +0 -71
- package/dist/src/mcp-tools/transfer-tools.d.ts +0 -14
- package/dist/src/mcp-tools/transfer-tools.js +0 -447
- package/dist/src/mcp-tools/types.d.ts +0 -8
- package/dist/src/mcp-tools/types.js +0 -8
- package/dist/src/mcp-tools/validate-input.d.ts +0 -9
- package/dist/src/mcp-tools/validate-input.js +0 -9
- package/dist/src/mcp-tools/wasm-agent-tools.d.ts +0 -13
- package/dist/src/mcp-tools/wasm-agent-tools.js +0 -840
- package/dist/src/mcp-tools/workflow-tools.d.ts +0 -8
- package/dist/src/mcp-tools/workflow-tools.js +0 -884
- package/dist/src/memory/bge-embedder.d.ts +0 -25
- package/dist/src/memory/bge-embedder.js +0 -121
- package/dist/src/memory/cross-encoder-rerank.d.ts +0 -33
- package/dist/src/memory/cross-encoder-rerank.js +0 -123
- package/dist/src/memory/embedding-policy.d.ts +0 -21
- package/dist/src/memory/embedding-policy.js +0 -30
- package/dist/src/memory/embedding-quantization.d.ts +0 -62
- package/dist/src/memory/embedding-quantization.js +0 -156
- package/dist/src/memory/ewc-consolidation.d.ts +0 -305
- package/dist/src/memory/ewc-consolidation.js +0 -611
- package/dist/src/memory/graph-edge-writer.d.ts +0 -95
- package/dist/src/memory/graph-edge-writer.js +0 -217
- package/dist/src/memory/hybrid-retrieval.d.ts +0 -77
- package/dist/src/memory/hybrid-retrieval.js +0 -192
- package/dist/src/memory/intelligence.d.ts +0 -405
- package/dist/src/memory/intelligence.js +0 -1316
- package/dist/src/memory/lucene-bm25.d.ts +0 -19
- package/dist/src/memory/lucene-bm25.js +0 -308
- package/dist/src/memory/memory-bridge.d.ts +0 -537
- package/dist/src/memory/memory-bridge.js +0 -2460
- package/dist/src/memory/memory-initializer.d.ts +0 -556
- package/dist/src/memory/memory-initializer.js +0 -3021
- package/dist/src/memory/neural-package-bridge.d.ts +0 -48
- package/dist/src/memory/neural-package-bridge.js +0 -87
- package/dist/src/memory/rabitq-index.d.ts +0 -60
- package/dist/src/memory/rabitq-index.js +0 -242
- package/dist/src/memory/sona-optimizer.d.ts +0 -267
- package/dist/src/memory/sona-optimizer.js +0 -779
- package/dist/src/memory/structured-distill.d.ts +0 -48
- package/dist/src/memory/structured-distill.js +0 -125
- package/dist/src/output.d.ts +0 -9
- package/dist/src/output.js +0 -9
- package/dist/src/parser.d.ts +0 -89
- package/dist/src/parser.js +0 -516
- package/dist/src/plugins/manager.d.ts +0 -133
- package/dist/src/plugins/manager.js +0 -415
- package/dist/src/plugins/store/discovery.d.ts +0 -99
- package/dist/src/plugins/store/discovery.js +0 -1224
- package/dist/src/plugins/store/index.d.ts +0 -76
- package/dist/src/plugins/store/index.js +0 -141
- package/dist/src/plugins/store/search.d.ts +0 -46
- package/dist/src/plugins/store/search.js +0 -230
- package/dist/src/plugins/store/types.d.ts +0 -279
- package/dist/src/plugins/store/types.js +0 -7
- package/dist/src/plugins/tests/demo-plugin-store.d.ts +0 -7
- package/dist/src/plugins/tests/demo-plugin-store.js +0 -126
- package/dist/src/plugins/tests/standalone-test.d.ts +0 -12
- package/dist/src/plugins/tests/standalone-test.js +0 -188
- package/dist/src/plugins/tests/test-plugin-store.d.ts +0 -7
- package/dist/src/plugins/tests/test-plugin-store.js +0 -206
- package/dist/src/production/circuit-breaker.d.ts +0 -101
- package/dist/src/production/circuit-breaker.js +0 -241
- package/dist/src/production/error-handler.d.ts +0 -92
- package/dist/src/production/error-handler.js +0 -299
- package/dist/src/production/index.d.ts +0 -23
- package/dist/src/production/index.js +0 -18
- package/dist/src/production/monitoring.d.ts +0 -161
- package/dist/src/production/monitoring.js +0 -356
- package/dist/src/production/rate-limiter.d.ts +0 -80
- package/dist/src/production/rate-limiter.js +0 -201
- package/dist/src/production/retry.d.ts +0 -48
- package/dist/src/production/retry.js +0 -179
- package/dist/src/prompt.d.ts +0 -44
- package/dist/src/prompt.js +0 -501
- package/dist/src/runtime/headless.d.ts +0 -60
- package/dist/src/runtime/headless.js +0 -284
- package/dist/src/runtime/parent-death-watchdog.d.ts +0 -42
- package/dist/src/runtime/parent-death-watchdog.js +0 -70
- package/dist/src/ruvector/agent-wasm.d.ts +0 -228
- package/dist/src/ruvector/agent-wasm.js +0 -463
- package/dist/src/ruvector/ast-analyzer.d.ts +0 -67
- package/dist/src/ruvector/ast-analyzer.js +0 -277
- package/dist/src/ruvector/codemods/engine.d.ts +0 -45
- package/dist/src/ruvector/codemods/engine.js +0 -291
- package/dist/src/ruvector/codemods/scope-analysis.d.ts +0 -29
- package/dist/src/ruvector/codemods/scope-analysis.js +0 -162
- package/dist/src/ruvector/coverage-router.d.ts +0 -160
- package/dist/src/ruvector/coverage-router.js +0 -531
- package/dist/src/ruvector/coverage-tools.d.ts +0 -33
- package/dist/src/ruvector/coverage-tools.js +0 -157
- package/dist/src/ruvector/diff-classifier.d.ts +0 -175
- package/dist/src/ruvector/diff-classifier.js +0 -699
- package/dist/src/ruvector/diskann-backend.d.ts +0 -78
- package/dist/src/ruvector/diskann-backend.js +0 -310
- package/dist/src/ruvector/enhanced-model-router.d.ts +0 -172
- package/dist/src/ruvector/enhanced-model-router.js +0 -577
- package/dist/src/ruvector/graph-analyzer.d.ts +0 -187
- package/dist/src/ruvector/graph-analyzer.js +0 -929
- package/dist/src/ruvector/graph-backend.d.ts +0 -79
- package/dist/src/ruvector/graph-backend.js +0 -220
- package/dist/src/ruvector/index.d.ts +0 -38
- package/dist/src/ruvector/index.js +0 -86
- package/dist/src/ruvector/lora-adapter.d.ts +0 -292
- package/dist/src/ruvector/lora-adapter.js +0 -710
- package/dist/src/ruvector/model-prices.d.ts +0 -50
- package/dist/src/ruvector/model-prices.js +0 -72
- package/dist/src/ruvector/model-router.d.ts +0 -408
- package/dist/src/ruvector/model-router.js +0 -1162
- package/dist/src/ruvector/neural-router.d.ts +0 -182
- package/dist/src/ruvector/neural-router.js +0 -825
- package/dist/src/ruvector/output-verifier.d.ts +0 -83
- package/dist/src/ruvector/output-verifier.js +0 -277
- package/dist/src/ruvector/q-learning-router.d.ts +0 -227
- package/dist/src/ruvector/q-learning-router.js +0 -721
- package/dist/src/ruvector/router-calibrator.d.ts +0 -65
- package/dist/src/ruvector/router-calibrator.js +0 -120
- package/dist/src/ruvector/router-parallel-recorder.d.ts +0 -127
- package/dist/src/ruvector/router-parallel-recorder.js +0 -183
- package/dist/src/ruvector/router-trajectory.d.ts +0 -194
- package/dist/src/ruvector/router-trajectory.js +0 -281
- package/dist/src/ruvector/run-transcript-recorder.d.ts +0 -154
- package/dist/src/ruvector/run-transcript-recorder.js +0 -209
- package/dist/src/ruvector/ruvllm-wasm.d.ts +0 -179
- package/dist/src/ruvector/ruvllm-wasm.js +0 -379
- package/dist/src/ruvector/semantic-router.d.ts +0 -77
- package/dist/src/ruvector/semantic-router.js +0 -178
- package/dist/src/ruvector/task-embedder.d.ts +0 -56
- package/dist/src/ruvector/task-embedder.js +0 -237
- package/dist/src/ruvector/trajectory-tree.d.ts +0 -113
- package/dist/src/ruvector/trajectory-tree.js +0 -237
- package/dist/src/ruvector/vector-db.d.ts +0 -73
- package/dist/src/ruvector/vector-db.js +0 -301
- package/dist/src/ruvector/wasm-embedder.d.ts +0 -13
- package/dist/src/ruvector/wasm-embedder.js +0 -143
- package/dist/src/security/builtin-aidefence.d.ts +0 -34
- package/dist/src/security/builtin-aidefence.js +0 -86
- package/dist/src/services/agentic-flow-bridge.d.ts +0 -50
- package/dist/src/services/agentic-flow-bridge.js +0 -95
- package/dist/src/services/ai-job-dedup.d.ts +0 -61
- package/dist/src/services/ai-job-dedup.js +0 -136
- package/dist/src/services/checkpoint-gate.d.ts +0 -140
- package/dist/src/services/checkpoint-gate.js +0 -223
- package/dist/src/services/claim-service.d.ts +0 -204
- package/dist/src/services/claim-service.js +0 -818
- package/dist/src/services/config-file-manager.d.ts +0 -37
- package/dist/src/services/config-file-manager.js +0 -224
- package/dist/src/services/container-worker-pool.d.ts +0 -204
- package/dist/src/services/container-worker-pool.js +0 -589
- package/dist/src/services/daemon-autostart.d.ts +0 -17
- package/dist/src/services/daemon-autostart.js +0 -102
- package/dist/src/services/distill-oracle.d.ts +0 -190
- package/dist/src/services/distill-oracle.js +0 -349
- package/dist/src/services/distill-tuning.d.ts +0 -111
- package/dist/src/services/distill-tuning.js +0 -510
- package/dist/src/services/evolve-proof.d.ts +0 -253
- package/dist/src/services/evolve-proof.js +0 -343
- package/dist/src/services/fable-harness.d.ts +0 -208
- package/dist/src/services/fable-harness.js +0 -388
- package/dist/src/services/git-workspace-identity.d.ts +0 -42
- package/dist/src/services/git-workspace-identity.js +0 -99
- package/dist/src/services/global-ai-budget.d.ts +0 -135
- package/dist/src/services/global-ai-budget.js +0 -415
- package/dist/src/services/harness-benchmark.d.ts +0 -68
- package/dist/src/services/harness-benchmark.js +0 -94
- package/dist/src/services/harness-canary.d.ts +0 -60
- package/dist/src/services/harness-canary.js +0 -69
- package/dist/src/services/harness-corpus-harvester.d.ts +0 -51
- package/dist/src/services/harness-corpus-harvester.js +0 -74
- package/dist/src/services/harness-flywheel-generations.d.ts +0 -115
- package/dist/src/services/harness-flywheel-generations.js +0 -360
- package/dist/src/services/harness-flywheel-runtime.d.ts +0 -25
- package/dist/src/services/harness-flywheel-runtime.js +0 -92
- package/dist/src/services/harness-flywheel.d.ts +0 -48
- package/dist/src/services/harness-flywheel.js +0 -207
- package/dist/src/services/harness-frozen-eval.d.ts +0 -22
- package/dist/src/services/harness-frozen-eval.js +0 -66
- package/dist/src/services/harness-hosts.d.ts +0 -40
- package/dist/src/services/harness-hosts.js +0 -88
- package/dist/src/services/harness-improvement-ledger.d.ts +0 -63
- package/dist/src/services/harness-improvement-ledger.js +0 -101
- package/dist/src/services/harness-loop.d.ts +0 -53
- package/dist/src/services/harness-loop.js +0 -85
- package/dist/src/services/harness-qualification.d.ts +0 -66
- package/dist/src/services/harness-qualification.js +0 -143
- package/dist/src/services/harness-replay.d.ts +0 -37
- package/dist/src/services/harness-replay.js +0 -92
- package/dist/src/services/harness-verify.d.ts +0 -33
- package/dist/src/services/harness-verify.js +0 -26
- package/dist/src/services/harness-worker.d.ts +0 -23
- package/dist/src/services/harness-worker.js +0 -66
- package/dist/src/services/headless-worker-executor.d.ts +0 -358
- package/dist/src/services/headless-worker-executor.js +0 -1269
- package/dist/src/services/index.d.ts +0 -13
- package/dist/src/services/index.js +0 -11
- package/dist/src/services/memory-backup.d.ts +0 -53
- package/dist/src/services/memory-backup.js +0 -198
- package/dist/src/services/memory-distillation.d.ts +0 -41
- package/dist/src/services/memory-distillation.js +0 -277
- package/dist/src/services/native-training.d.ts +0 -68
- package/dist/src/services/native-training.js +0 -141
- package/dist/src/services/registry-api.d.ts +0 -58
- package/dist/src/services/registry-api.js +0 -146
- package/dist/src/services/repo-supervisor.d.ts +0 -70
- package/dist/src/services/repo-supervisor.js +0 -228
- package/dist/src/services/ruvector-training.d.ts +0 -222
- package/dist/src/services/ruvector-training.js +0 -693
- package/dist/src/services/swarm-memory-branches.d.ts +0 -135
- package/dist/src/services/swarm-memory-branches.js +0 -213
- package/dist/src/services/weight-eft.d.ts +0 -305
- package/dist/src/services/weight-eft.js +0 -296
- package/dist/src/services/worker-daemon.d.ts +0 -439
- package/dist/src/services/worker-daemon.js +0 -1865
- package/dist/src/services/worker-queue.d.ts +0 -194
- package/dist/src/services/worker-queue.js +0 -513
- package/dist/src/services/workspace-lease.d.ts +0 -55
- package/dist/src/services/workspace-lease.js +0 -191
- package/dist/src/suggest.d.ts +0 -53
- package/dist/src/suggest.js +0 -200
- package/dist/src/transfer/anonymization/index.d.ts +0 -25
- package/dist/src/transfer/anonymization/index.js +0 -175
- package/dist/src/transfer/deploy-seraphine.d.ts +0 -13
- package/dist/src/transfer/deploy-seraphine.js +0 -205
- package/dist/src/transfer/export.d.ts +0 -25
- package/dist/src/transfer/export.js +0 -113
- package/dist/src/transfer/index.d.ts +0 -12
- package/dist/src/transfer/index.js +0 -31
- package/dist/src/transfer/ipfs/client.d.ts +0 -109
- package/dist/src/transfer/ipfs/client.js +0 -307
- package/dist/src/transfer/ipfs/upload.d.ts +0 -95
- package/dist/src/transfer/ipfs/upload.js +0 -413
- package/dist/src/transfer/models/seraphine.d.ts +0 -72
- package/dist/src/transfer/models/seraphine.js +0 -373
- package/dist/src/transfer/serialization/cfp.d.ts +0 -49
- package/dist/src/transfer/serialization/cfp.js +0 -183
- package/dist/src/transfer/storage/gcs.d.ts +0 -82
- package/dist/src/transfer/storage/gcs.js +0 -272
- package/dist/src/transfer/storage/index.d.ts +0 -6
- package/dist/src/transfer/storage/index.js +0 -6
- package/dist/src/transfer/store/discovery.d.ts +0 -84
- package/dist/src/transfer/store/discovery.js +0 -382
- package/dist/src/transfer/store/download.d.ts +0 -70
- package/dist/src/transfer/store/download.js +0 -334
- package/dist/src/transfer/store/index.d.ts +0 -84
- package/dist/src/transfer/store/index.js +0 -153
- package/dist/src/transfer/store/publish.d.ts +0 -76
- package/dist/src/transfer/store/publish.js +0 -294
- package/dist/src/transfer/store/registry.d.ts +0 -58
- package/dist/src/transfer/store/registry.js +0 -285
- package/dist/src/transfer/store/search.d.ts +0 -54
- package/dist/src/transfer/store/search.js +0 -232
- package/dist/src/transfer/store/tests/standalone-test.d.ts +0 -12
- package/dist/src/transfer/store/tests/standalone-test.js +0 -190
- package/dist/src/transfer/store/types.d.ts +0 -193
- package/dist/src/transfer/store/types.js +0 -6
- package/dist/src/transfer/test-seraphine.d.ts +0 -6
- package/dist/src/transfer/test-seraphine.js +0 -105
- package/dist/src/transfer/tests/test-store.d.ts +0 -7
- package/dist/src/transfer/tests/test-store.js +0 -214
- package/dist/src/transfer/types.d.ts +0 -245
- package/dist/src/transfer/types.js +0 -6
- package/dist/src/types.d.ts +0 -13
- package/dist/src/types.js +0 -13
- package/dist/src/update/checker.d.ts +0 -34
- package/dist/src/update/checker.js +0 -191
- package/dist/src/update/executor.d.ts +0 -33
- package/dist/src/update/executor.js +0 -217
- package/dist/src/update/index.d.ts +0 -33
- package/dist/src/update/index.js +0 -64
- package/dist/src/update/rate-limiter.d.ts +0 -20
- package/dist/src/update/rate-limiter.js +0 -96
- package/dist/src/update/validator.d.ts +0 -17
- package/dist/src/update/validator.js +0 -123
|
@@ -1,1733 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* V3 CLI Daemon Command
|
|
3
|
-
* Manages background worker daemon (Node.js-based, similar to shell helpers)
|
|
4
|
-
*/
|
|
5
|
-
import { output } from '../output.js';
|
|
6
|
-
import { getDaemon, startDaemon, stopDaemon } from '../services/worker-daemon.js';
|
|
7
|
-
import { fork } from 'child_process';
|
|
8
|
-
import { fileURLToPath } from 'url';
|
|
9
|
-
import { dirname, join, resolve } from 'path';
|
|
10
|
-
import { homedir } from 'os';
|
|
11
|
-
import * as fs from 'fs';
|
|
12
|
-
// Start daemon subcommand
|
|
13
|
-
const startCommand = {
|
|
14
|
-
name: 'start',
|
|
15
|
-
description: 'Start the worker daemon with all enabled background workers',
|
|
16
|
-
options: [
|
|
17
|
-
{ name: 'workers', short: 'w', type: 'string', description: 'Comma-separated list of workers to enable (default: map,audit,optimize,consolidate,testgaps)' },
|
|
18
|
-
// ADR-174 M3: consolidate now runs a real memory-distillation pass
|
|
19
|
-
// (memory_entries -> episodes/reasoning_patterns/causal_edges) instead of
|
|
20
|
-
// a no-op stub. This opt-out skips just that pass for the life of this
|
|
21
|
-
// daemon process, without touching persisted worker-enabled state — for
|
|
22
|
-
// permanently disabling the worker use `daemon enable -w consolidate --disable`.
|
|
23
|
-
{ name: 'no-distill', type: 'boolean', description: 'Disable the consolidate worker\'s memory-distillation pass (ADR-174)' },
|
|
24
|
-
{ name: 'quiet', short: 'Q', type: 'boolean', description: 'Suppress output' },
|
|
25
|
-
{ name: 'background', short: 'b', type: 'boolean', description: 'Run daemon in background (detached process)', default: true },
|
|
26
|
-
{ name: 'foreground', short: 'f', type: 'boolean', description: 'Run daemon in foreground (blocks terminal)' },
|
|
27
|
-
// #2661: --headless is the explicit consent gate for scheduled AI workers.
|
|
28
|
-
// Without it (or daemon.aiWorkers.enabled / RUFLO_DAEMON_AI_WORKERS=1),
|
|
29
|
-
// every worker runs its $0 local path — the daemon never spawns
|
|
30
|
-
// `claude --print` merely because the Claude CLI is on PATH.
|
|
31
|
-
{ name: 'headless', type: 'boolean', description: 'Enable AI workers (scheduled `claude --print` execution, governed by the user-global AI budget). Default: off — workers run local-only' },
|
|
32
|
-
{ name: 'sandbox', type: 'string', description: 'Default sandbox mode for headless workers', choices: ['strict', 'permissive', 'disabled'] },
|
|
33
|
-
{ name: 'max-cpu-load', type: 'string', description: 'Override maxCpuLoad resource threshold (e.g. 4.0)' },
|
|
34
|
-
{ name: 'min-free-memory', type: 'string', description: 'Override minFreeMemoryPercent resource threshold (e.g. 15)' },
|
|
35
|
-
// #2356: self-terminating lifecycle. Caps how long a forgotten daemon can
|
|
36
|
-
// keep dispatching headless worker sweeps. Default 12h (or RUFLO_DAEMON_TTL_SECS); 0 = run until stopped.
|
|
37
|
-
{ name: 'ttl', type: 'string', description: 'Max daemon age in seconds before graceful self-shutdown (0 = run until stopped; default 43200 = 12h)' },
|
|
38
|
-
// #1914: workspace root for this daemon. Set automatically when the
|
|
39
|
-
// background launcher forks the foreground child so the daemon process
|
|
40
|
-
// carries its workspace path in argv — `killStaleDaemons` then only
|
|
41
|
-
// reaps daemons belonging to the current workspace (ADR-014 scope).
|
|
42
|
-
{ name: 'workspace', type: 'string', description: 'Workspace root for this daemon (internal — set automatically when forking)' },
|
|
43
|
-
],
|
|
44
|
-
examples: [
|
|
45
|
-
{ command: 'claude-flow daemon start', description: 'Start daemon in background (default)' },
|
|
46
|
-
{ command: 'claude-flow daemon start --foreground', description: 'Start in foreground (blocks terminal)' },
|
|
47
|
-
{ command: 'claude-flow daemon start -w map,audit,optimize', description: 'Start with specific workers' },
|
|
48
|
-
{ command: 'claude-flow daemon start --headless --sandbox strict', description: 'Start with headless workers in strict sandbox' },
|
|
49
|
-
{ command: 'claude-flow daemon start --no-distill', description: 'Start with the consolidate worker\'s distillation pass disabled' },
|
|
50
|
-
],
|
|
51
|
-
action: async (ctx) => {
|
|
52
|
-
const quiet = ctx.flags.quiet;
|
|
53
|
-
const foreground = ctx.flags.foreground;
|
|
54
|
-
const noDistill = ctx.flags['no-distill'];
|
|
55
|
-
// ADR-174 M3: honored by WorkerDaemon's consolidate worker directly via
|
|
56
|
-
// process.env — set it now so the foreground path (which runs in this
|
|
57
|
-
// same process) picks it up too; the background path forwards it to the
|
|
58
|
-
// forked child's env explicitly (see startBackgroundDaemon below).
|
|
59
|
-
if (noDistill) {
|
|
60
|
-
process.env.RUFLO_DAEMON_NO_DISTILL = '1';
|
|
61
|
-
}
|
|
62
|
-
// #1914: a forked daemon child receives --workspace <root>; the launcher
|
|
63
|
-
// and interactive invocations have no flag and fall back to cwd.
|
|
64
|
-
const projectRoot = resolveWorkspaceFlag(ctx.flags.workspace) ?? process.cwd();
|
|
65
|
-
const isDaemonProcess = process.env.CLAUDE_FLOW_DAEMON === '1';
|
|
66
|
-
// Parse resource threshold overrides from CLI flags
|
|
67
|
-
const config = {};
|
|
68
|
-
// #2661: thread --headless into DaemonConfig so it actually gates the
|
|
69
|
-
// headless executor. Previously the flag was forwarded to the forked
|
|
70
|
-
// child but never consumed — AI workers auto-enabled whenever the
|
|
71
|
-
// Claude CLI was detected. Only set when true so config.json/env can
|
|
72
|
-
// still opt in when the flag is absent.
|
|
73
|
-
if (ctx.flags.headless === true) {
|
|
74
|
-
config.aiWorkersEnabled = true;
|
|
75
|
-
}
|
|
76
|
-
const rawMaxCpu = ctx.flags['max-cpu-load'];
|
|
77
|
-
const rawMinMem = ctx.flags['min-free-memory'];
|
|
78
|
-
// Strict numeric pattern to prevent command injection when forwarding to subprocess (S1)
|
|
79
|
-
const NUMERIC_RE = /^\d+(\.\d+)?$/;
|
|
80
|
-
const sanitize = (s) => s.replace(/[\x00-\x1f\x7f-\x9f]/g, '');
|
|
81
|
-
if (rawMaxCpu || rawMinMem) {
|
|
82
|
-
const thresholds = {};
|
|
83
|
-
if (rawMaxCpu) {
|
|
84
|
-
const val = parseFloat(rawMaxCpu);
|
|
85
|
-
if (NUMERIC_RE.test(rawMaxCpu) && isFinite(val) && val > 0 && val <= 1000) {
|
|
86
|
-
thresholds.maxCpuLoad = val;
|
|
87
|
-
}
|
|
88
|
-
else if (!quiet) {
|
|
89
|
-
output.printWarning(`Ignoring invalid --max-cpu-load value: ${sanitize(rawMaxCpu)}`);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
if (rawMinMem) {
|
|
93
|
-
const val = parseFloat(rawMinMem);
|
|
94
|
-
if (NUMERIC_RE.test(rawMinMem) && isFinite(val) && val >= 0 && val <= 100) {
|
|
95
|
-
thresholds.minFreeMemoryPercent = val;
|
|
96
|
-
}
|
|
97
|
-
else if (!quiet) {
|
|
98
|
-
output.printWarning(`Ignoring invalid --min-free-memory value: ${sanitize(rawMinMem)}`);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
if (thresholds.maxCpuLoad !== undefined || thresholds.minFreeMemoryPercent !== undefined) {
|
|
102
|
-
config.resourceThresholds = thresholds;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
// #2356: parse --ttl (seconds → ms). Integer-only so 0 (disable) is valid;
|
|
106
|
-
// INT_RE forbids the decimals NUMERIC_RE allows, since a TTL is whole seconds.
|
|
107
|
-
const rawTtl = ctx.flags.ttl;
|
|
108
|
-
const INT_RE = /^\d+$/;
|
|
109
|
-
if (rawTtl !== undefined) {
|
|
110
|
-
if (INT_RE.test(rawTtl)) {
|
|
111
|
-
config.ttlMs = parseInt(rawTtl, 10) * 1000;
|
|
112
|
-
}
|
|
113
|
-
else if (!quiet) {
|
|
114
|
-
output.printWarning(`Ignoring invalid --ttl value: ${sanitize(rawTtl)}`);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
// Check if background daemon already running (skip if we ARE the daemon process)
|
|
118
|
-
//
|
|
119
|
-
// #2407 — without an atomic lockfile, N concurrent `daemon start` calls
|
|
120
|
-
// (devcontainer setup + VS Code task + MCP hook within ~500 ms) all see
|
|
121
|
-
// an empty PID file in the same instant, all proceed past this dedup,
|
|
122
|
-
// and all fork their own background daemon. One incident accumulated
|
|
123
|
-
// 39 zombie daemons holding ~8.5 GiB → kernel panic.
|
|
124
|
-
//
|
|
125
|
-
// Wrap the check + the subsequent fork in an O_EXCL lockfile so the
|
|
126
|
-
// dedup is process-atomic. Lock holder gets to spawn; competing callers
|
|
127
|
-
// see EEXIST, wait briefly, then re-read the PID file (which the holder
|
|
128
|
-
// has now written), and return "already running" cleanly.
|
|
129
|
-
// #2484 — previously the lockfile was released BEFORE startBackgroundDaemon
|
|
130
|
-
// ran, opening a race window where a concurrent caller could see no lock
|
|
131
|
-
// AND no PID file (PID hadn't been written yet) and proceed to fork ITS
|
|
132
|
-
// OWN background daemon. EDortta reported 4 identical daemons per Claude
|
|
133
|
-
// Code session on v3.10.37 under exactly this pattern (MCP startup
|
|
134
|
-
// racing with a sibling invocation).
|
|
135
|
-
//
|
|
136
|
-
// Fix: hold the lock across the entire spawn lifecycle (dedup check →
|
|
137
|
-
// killStaleDaemons → fork → PID file write), so the lock-loser ALWAYS
|
|
138
|
-
// sees either a live lock or a populated PID file, never the empty
|
|
139
|
-
// window in between.
|
|
140
|
-
let lockFd = null;
|
|
141
|
-
let lockFile = '';
|
|
142
|
-
if (!isDaemonProcess) {
|
|
143
|
-
const stateDir = join(resolve(projectRoot), '.claude-flow');
|
|
144
|
-
lockFile = join(stateDir, 'daemon.lock');
|
|
145
|
-
try {
|
|
146
|
-
fs.mkdirSync(stateDir, { recursive: true });
|
|
147
|
-
}
|
|
148
|
-
catch { /* exists */ }
|
|
149
|
-
try {
|
|
150
|
-
lockFd = fs.openSync(lockFile, fs.constants.O_CREAT | fs.constants.O_EXCL | fs.constants.O_WRONLY);
|
|
151
|
-
fs.writeSync(lockFd, String(process.pid));
|
|
152
|
-
}
|
|
153
|
-
catch (e) {
|
|
154
|
-
if (e.code === 'EEXIST') {
|
|
155
|
-
// Another `daemon start` is mid-spawn. Wait up to 5s for it to
|
|
156
|
-
// finish, then re-check the PID file. If the holder crashed
|
|
157
|
-
// mid-spawn, fall through and reset; killStaleDaemons + a fresh
|
|
158
|
-
// attempt will recover.
|
|
159
|
-
const deadline = Date.now() + 5000;
|
|
160
|
-
while (Date.now() < deadline) {
|
|
161
|
-
await new Promise((r) => setTimeout(r, 100));
|
|
162
|
-
const winnerPid = getBackgroundDaemonPid(projectRoot);
|
|
163
|
-
if (winnerPid && isProcessRunning(winnerPid)) {
|
|
164
|
-
if (!quiet) {
|
|
165
|
-
output.printWarning(`Daemon already running in background (PID: ${winnerPid}). Stop it first with: daemon stop`);
|
|
166
|
-
}
|
|
167
|
-
return { success: true };
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
// Stale lockfile from a crashed prior attempt — clear it and
|
|
171
|
-
// proceed without a held lock. Worst case we double-spawn ONCE
|
|
172
|
-
// and the killStaleDaemons sweep below cleans up.
|
|
173
|
-
try {
|
|
174
|
-
fs.unlinkSync(lockFile);
|
|
175
|
-
}
|
|
176
|
-
catch { /* ignore */ }
|
|
177
|
-
}
|
|
178
|
-
else {
|
|
179
|
-
throw e;
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
// Dedup check while holding the lock.
|
|
183
|
-
try {
|
|
184
|
-
const bgPid = getBackgroundDaemonPid(projectRoot);
|
|
185
|
-
if (bgPid && isProcessRunning(bgPid)) {
|
|
186
|
-
if (!quiet) {
|
|
187
|
-
output.printWarning(`Daemon already running in background (PID: ${bgPid}). Stop it first with: daemon stop`);
|
|
188
|
-
}
|
|
189
|
-
// Release the lock on the early-return path.
|
|
190
|
-
if (lockFd !== null) {
|
|
191
|
-
try {
|
|
192
|
-
fs.closeSync(lockFd);
|
|
193
|
-
}
|
|
194
|
-
catch { /* ignore */ }
|
|
195
|
-
try {
|
|
196
|
-
fs.unlinkSync(lockFile);
|
|
197
|
-
}
|
|
198
|
-
catch { /* ignore */ }
|
|
199
|
-
}
|
|
200
|
-
return { success: true };
|
|
201
|
-
}
|
|
202
|
-
// #1551: Kill any stale daemon processes that weren't tracked by PID file
|
|
203
|
-
await killStaleDaemons(projectRoot, quiet);
|
|
204
|
-
}
|
|
205
|
-
catch (e) {
|
|
206
|
-
// Anything that throws here MUST still release the lock before
|
|
207
|
-
// rethrowing so we don't leave the lockfile behind for future
|
|
208
|
-
// invocations to wait on for 5s before recovering.
|
|
209
|
-
if (lockFd !== null) {
|
|
210
|
-
try {
|
|
211
|
-
fs.closeSync(lockFd);
|
|
212
|
-
}
|
|
213
|
-
catch { /* ignore */ }
|
|
214
|
-
try {
|
|
215
|
-
fs.unlinkSync(lockFile);
|
|
216
|
-
}
|
|
217
|
-
catch { /* ignore */ }
|
|
218
|
-
lockFd = null;
|
|
219
|
-
}
|
|
220
|
-
throw e;
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
// Background mode (default): fork a detached process. The lock (if held)
|
|
224
|
-
// is released AFTER startBackgroundDaemon's PID file write completes —
|
|
225
|
-
// see the cleanup inside startBackgroundDaemon's caller path below.
|
|
226
|
-
// #1968: previously only forwarded resource thresholds — `--workers`,
|
|
227
|
-
// `--headless`, and `--sandbox` were dropped on the floor when the
|
|
228
|
-
// launcher forked the foreground child, so `daemon start --workers map`
|
|
229
|
-
// got the full default worker set instead.
|
|
230
|
-
if (!foreground) {
|
|
231
|
-
try {
|
|
232
|
-
return await startBackgroundDaemon(projectRoot, quiet, {
|
|
233
|
-
maxCpuLoad: rawMaxCpu,
|
|
234
|
-
minFreeMemory: rawMinMem,
|
|
235
|
-
workers: ctx.flags.workers,
|
|
236
|
-
headless: ctx.flags.headless,
|
|
237
|
-
sandbox: ctx.flags.sandbox,
|
|
238
|
-
ttl: rawTtl,
|
|
239
|
-
noDistill,
|
|
240
|
-
});
|
|
241
|
-
}
|
|
242
|
-
finally {
|
|
243
|
-
// Release the lock NOW — startBackgroundDaemon has either written
|
|
244
|
-
// the PID file (success path) or thrown (in which case the next
|
|
245
|
-
// caller's killStaleDaemons sweep handles the orphan).
|
|
246
|
-
if (lockFd !== null) {
|
|
247
|
-
try {
|
|
248
|
-
fs.closeSync(lockFd);
|
|
249
|
-
}
|
|
250
|
-
catch { /* ignore */ }
|
|
251
|
-
try {
|
|
252
|
-
fs.unlinkSync(lockFile);
|
|
253
|
-
}
|
|
254
|
-
catch { /* ignore */ }
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
// Foreground path: release the lock before we start the (potentially
|
|
259
|
-
// long-running) foreground daemon so other callers can dedup against
|
|
260
|
-
// our PID file (foreground writes its own PID).
|
|
261
|
-
if (lockFd !== null) {
|
|
262
|
-
try {
|
|
263
|
-
fs.closeSync(lockFd);
|
|
264
|
-
}
|
|
265
|
-
catch { /* ignore */ }
|
|
266
|
-
try {
|
|
267
|
-
fs.unlinkSync(lockFile);
|
|
268
|
-
}
|
|
269
|
-
catch { /* ignore */ }
|
|
270
|
-
lockFd = null;
|
|
271
|
-
}
|
|
272
|
-
// Foreground mode: run in current process (blocks terminal)
|
|
273
|
-
try {
|
|
274
|
-
const stateDir = join(projectRoot, '.claude-flow');
|
|
275
|
-
const pidFile = join(stateDir, 'daemon.pid');
|
|
276
|
-
// Ensure state directory exists
|
|
277
|
-
if (!fs.existsSync(stateDir)) {
|
|
278
|
-
fs.mkdirSync(stateDir, { recursive: true });
|
|
279
|
-
}
|
|
280
|
-
// NOTE: Do NOT write PID file here — startDaemon() writes it internally.
|
|
281
|
-
// Writing it before startDaemon() causes checkExistingDaemon() to detect
|
|
282
|
-
// our own PID and return early, leaving no workers scheduled (#1478 Bug 1).
|
|
283
|
-
// Clean up PID file on exit
|
|
284
|
-
const cleanup = () => {
|
|
285
|
-
try {
|
|
286
|
-
if (fs.existsSync(pidFile)) {
|
|
287
|
-
fs.unlinkSync(pidFile);
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
catch { /* ignore */ }
|
|
291
|
-
};
|
|
292
|
-
process.on('exit', cleanup);
|
|
293
|
-
process.on('SIGINT', () => { cleanup(); process.exit(0); });
|
|
294
|
-
process.on('SIGTERM', () => { cleanup(); process.exit(0); });
|
|
295
|
-
// Ignore SIGHUP on macOS/Linux — prevents daemon death when terminal closes (#1283)
|
|
296
|
-
if (process.platform !== 'win32') {
|
|
297
|
-
process.on('SIGHUP', () => { });
|
|
298
|
-
}
|
|
299
|
-
if (!quiet) {
|
|
300
|
-
const spinner = output.createSpinner({ text: 'Starting worker daemon...', spinner: 'dots' });
|
|
301
|
-
spinner.start();
|
|
302
|
-
const daemon = await startDaemon(projectRoot, config);
|
|
303
|
-
const status = daemon.getStatus();
|
|
304
|
-
spinner.succeed('Worker daemon started (foreground mode)');
|
|
305
|
-
output.writeln();
|
|
306
|
-
output.printBox([
|
|
307
|
-
`PID: ${status.pid}`,
|
|
308
|
-
`Started: ${status.startedAt?.toISOString()}`,
|
|
309
|
-
status.config.ttlMs > 0
|
|
310
|
-
? `TTL: ${Math.round(status.config.ttlMs / 3600000)}h (self-shutdown)`
|
|
311
|
-
: `TTL: off (runs until stopped)`,
|
|
312
|
-
`AI Workers: ${status.config.aiWorkersEnabled ? 'enabled (budget-capped)' : 'off (local-only, default)'}`,
|
|
313
|
-
`Workers: ${status.config.workers.filter(w => w.enabled).length} enabled`,
|
|
314
|
-
`Max Concurrent: ${status.config.maxConcurrent}`,
|
|
315
|
-
`Max CPU Load: ${status.config.resourceThresholds.maxCpuLoad}`,
|
|
316
|
-
`Min Free Memory: ${status.config.resourceThresholds.minFreeMemoryPercent}%`,
|
|
317
|
-
].join('\n'), 'Daemon Status');
|
|
318
|
-
output.writeln();
|
|
319
|
-
output.writeln(output.bold('Scheduled Workers'));
|
|
320
|
-
output.printTable({
|
|
321
|
-
columns: [
|
|
322
|
-
{ key: 'type', header: 'Worker', width: 15 },
|
|
323
|
-
{ key: 'interval', header: 'Interval', width: 12 },
|
|
324
|
-
{ key: 'priority', header: 'Priority', width: 10 },
|
|
325
|
-
{ key: 'description', header: 'Description', width: 30 },
|
|
326
|
-
],
|
|
327
|
-
data: status.config.workers
|
|
328
|
-
.filter(w => w.enabled)
|
|
329
|
-
.map(w => ({
|
|
330
|
-
type: output.highlight(w.type),
|
|
331
|
-
interval: `${Math.round(w.intervalMs / 60000)}min`,
|
|
332
|
-
priority: w.priority === 'critical' ? output.error(w.priority) :
|
|
333
|
-
w.priority === 'high' ? output.warning(w.priority) :
|
|
334
|
-
output.dim(w.priority),
|
|
335
|
-
description: w.description,
|
|
336
|
-
})),
|
|
337
|
-
});
|
|
338
|
-
output.writeln();
|
|
339
|
-
output.writeln(output.dim('Press Ctrl+C to stop daemon'));
|
|
340
|
-
// Listen for worker events
|
|
341
|
-
daemon.on('worker:start', ({ type }) => {
|
|
342
|
-
output.writeln(output.dim(`[daemon] Worker starting: ${type}`));
|
|
343
|
-
});
|
|
344
|
-
daemon.on('worker:complete', ({ type, durationMs }) => {
|
|
345
|
-
output.writeln(output.success(`[daemon] Worker completed: ${type} (${durationMs}ms)`));
|
|
346
|
-
});
|
|
347
|
-
daemon.on('worker:error', ({ type, error }) => {
|
|
348
|
-
output.writeln(output.error(`[daemon] Worker failed: ${type} - ${error}`));
|
|
349
|
-
});
|
|
350
|
-
// Keep process alive — setInterval creates a ref'd handle that prevents
|
|
351
|
-
// Node.js from exiting even when startDaemon's timers are unref'd (#1478 Bug 2).
|
|
352
|
-
setInterval(() => { }, 60_000);
|
|
353
|
-
await new Promise(() => { }); // Never resolves - daemon runs until killed
|
|
354
|
-
}
|
|
355
|
-
else {
|
|
356
|
-
await startDaemon(projectRoot, config);
|
|
357
|
-
setInterval(() => { }, 60_000); // Keep alive with ref'd handle (#1478)
|
|
358
|
-
await new Promise(() => { }); // Keep alive
|
|
359
|
-
}
|
|
360
|
-
return { success: true };
|
|
361
|
-
}
|
|
362
|
-
catch (error) {
|
|
363
|
-
output.printError(`Failed to start daemon: ${error instanceof Error ? error.message : String(error)}`);
|
|
364
|
-
return { success: false, exitCode: 1 };
|
|
365
|
-
}
|
|
366
|
-
},
|
|
367
|
-
};
|
|
368
|
-
/**
|
|
369
|
-
* Validate path for security - prevents path traversal and injection
|
|
370
|
-
*/
|
|
371
|
-
function validatePath(path, label) {
|
|
372
|
-
// Must be absolute after resolution
|
|
373
|
-
const resolved = resolve(path);
|
|
374
|
-
// Check for null bytes (injection attack)
|
|
375
|
-
if (path.includes('\0')) {
|
|
376
|
-
throw new Error(`${label} contains null bytes`);
|
|
377
|
-
}
|
|
378
|
-
// Check for shell metacharacters in path components
|
|
379
|
-
if (/[;&|`$<>]/.test(path)) {
|
|
380
|
-
throw new Error(`${label} contains shell metacharacters`);
|
|
381
|
-
}
|
|
382
|
-
// Prevent path traversal outside expected directories
|
|
383
|
-
if (!resolved.includes('.claude-flow') && !resolved.includes('bin')) {
|
|
384
|
-
// Allow only paths within project structure
|
|
385
|
-
const cwd = process.cwd();
|
|
386
|
-
if (!resolved.startsWith(cwd)) {
|
|
387
|
-
throw new Error(`${label} escapes project directory`);
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
/**
|
|
392
|
-
* #1914: Resolve the `--workspace` flag to an absolute path, or return null
|
|
393
|
-
* if it is absent / not a usable string. Rejects values with null bytes or
|
|
394
|
-
* shell metacharacters (defence-in-depth — the value is later embedded in a
|
|
395
|
-
* forked child's argv and compared against `ps`/`tasklist` output).
|
|
396
|
-
*/
|
|
397
|
-
export function resolveWorkspaceFlag(raw) {
|
|
398
|
-
if (typeof raw !== 'string')
|
|
399
|
-
return null;
|
|
400
|
-
const trimmed = raw.trim();
|
|
401
|
-
if (!trimmed)
|
|
402
|
-
return null;
|
|
403
|
-
if (trimmed.includes('\0') || /[;&|`$<>]/.test(trimmed))
|
|
404
|
-
return null;
|
|
405
|
-
return resolve(trimmed);
|
|
406
|
-
}
|
|
407
|
-
/**
|
|
408
|
-
* #1914: True when a process command line (from `ps -eo command` on POSIX or
|
|
409
|
-
* the tasklist Window Title column on Windows) belongs to a daemon started
|
|
410
|
-
* for `workspaceRoot`. The launcher (`startBackgroundDaemon`) always appends
|
|
411
|
-
* `--workspace <root>` as the FINAL argv entry, so an exact trailing match
|
|
412
|
-
* after stripping trailing whitespace/quotes is unambiguous — even for
|
|
413
|
-
* workspace paths containing spaces — and never a bare path-prefix match,
|
|
414
|
-
* so workspace `/a/proj` does not reap `/a/proj-other`'s daemon. A daemon
|
|
415
|
-
* whose argv puts `--workspace` mid-list (only possible via a hand-rolled
|
|
416
|
-
* invocation) simply won't be auto-reaped — `daemon stop` still handles it
|
|
417
|
-
* via the PID file.
|
|
418
|
-
*/
|
|
419
|
-
export function daemonCommandLineBelongsToWorkspace(commandLine, workspaceRoot) {
|
|
420
|
-
return commandLine.replace(/[\s"']+$/u, '').endsWith(`--workspace ${workspaceRoot}`);
|
|
421
|
-
}
|
|
422
|
-
/**
|
|
423
|
-
* #2356: extract the workspace root from a daemon process command line for the
|
|
424
|
-
* global `daemon status --all` view. The launcher always appends
|
|
425
|
-
* `--workspace <root>` as the FINAL argv entry (see startBackgroundDaemon), so
|
|
426
|
-
* we capture everything after it to end-of-line and strip trailing quotes.
|
|
427
|
-
* Returns null for pre-#1914 daemons that never stamped a workspace.
|
|
428
|
-
*/
|
|
429
|
-
export function extractWorkspaceFromDaemonLine(commandLine) {
|
|
430
|
-
const m = commandLine.match(/--workspace\s+(.+?)\s*$/u);
|
|
431
|
-
if (!m)
|
|
432
|
-
return null;
|
|
433
|
-
const ws = m[1].replace(/["']+$/u, '').trim();
|
|
434
|
-
return ws.length > 0 ? ws : null;
|
|
435
|
-
}
|
|
436
|
-
async function startBackgroundDaemon(projectRoot, quiet, forwarded = {}) {
|
|
437
|
-
const { maxCpuLoad, minFreeMemory, workers, headless, sandbox, ttl, noDistill } = forwarded;
|
|
438
|
-
// Validate and resolve project root
|
|
439
|
-
const resolvedRoot = resolve(projectRoot);
|
|
440
|
-
validatePath(resolvedRoot, 'Project root');
|
|
441
|
-
const stateDir = join(resolvedRoot, '.claude-flow');
|
|
442
|
-
const pidFile = join(stateDir, 'daemon.pid');
|
|
443
|
-
const logFile = join(stateDir, 'daemon.log');
|
|
444
|
-
// Validate all paths
|
|
445
|
-
validatePath(stateDir, 'State directory');
|
|
446
|
-
validatePath(pidFile, 'PID file');
|
|
447
|
-
validatePath(logFile, 'Log file');
|
|
448
|
-
// Ensure state directory exists
|
|
449
|
-
if (!fs.existsSync(stateDir)) {
|
|
450
|
-
fs.mkdirSync(stateDir, { recursive: true });
|
|
451
|
-
}
|
|
452
|
-
// Get path to CLI (from dist/src/commands/daemon.js -> bin/cli.js)
|
|
453
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
454
|
-
const __dirname = dirname(__filename);
|
|
455
|
-
// dist/src/commands -> dist/src -> dist -> package root -> bin/cli.js
|
|
456
|
-
const cliPath = resolve(join(__dirname, '..', '..', '..', 'bin', 'cli.js'));
|
|
457
|
-
validatePath(cliPath, 'CLI path');
|
|
458
|
-
// Verify CLI path exists
|
|
459
|
-
if (!fs.existsSync(cliPath)) {
|
|
460
|
-
output.printError(`CLI not found at: ${cliPath}`);
|
|
461
|
-
return { success: false, exitCode: 1 };
|
|
462
|
-
}
|
|
463
|
-
// Platform-aware spawn flags. We use child_process.fork() because the daemon
|
|
464
|
-
// child is itself a Node script — fork() spawns Node directly and skips the
|
|
465
|
-
// cmd.exe interpretation pass that broke Windows + Node 25 when
|
|
466
|
-
// process.execPath contained a space (#1691). It also avoids the [DEP0190]
|
|
467
|
-
// shell:true security warning.
|
|
468
|
-
const isWin = process.platform === 'win32';
|
|
469
|
-
const forkOpts = {
|
|
470
|
-
cwd: resolvedRoot,
|
|
471
|
-
// detached: true on every platform (#1766). On Windows, leaving detached:false
|
|
472
|
-
// kept the child in the parent's process group AND the IPC pipe held the
|
|
473
|
-
// child to npx — when npx exited, the IPC pipe tore down and the daemon
|
|
474
|
-
// died within ~1s. detached:true + child.disconnect() (below) gives the
|
|
475
|
-
// child its own session/pgid and breaks the IPC pipe so the daemon
|
|
476
|
-
// genuinely survives parent exit. On POSIX, detached:true was already the
|
|
477
|
-
// path; this just makes Windows match.
|
|
478
|
-
detached: true,
|
|
479
|
-
// Use 'ignore' for all stdio + 'ignore' for the IPC channel via silent:true off.
|
|
480
|
-
// fork() defaults to creating an IPC channel; we don't need it here, so we
|
|
481
|
-
// pass stdio explicitly. Passing fs.openSync() FDs causes the child to die
|
|
482
|
-
// on Windows when the parent exits and closes the FDs (#1478 Bug 3) — the
|
|
483
|
-
// daemon writes its own logs via appendFileSync to .claude-flow/logs/.
|
|
484
|
-
stdio: ['ignore', 'ignore', 'ignore', 'ipc'],
|
|
485
|
-
windowsHide: true,
|
|
486
|
-
env: {
|
|
487
|
-
...process.env,
|
|
488
|
-
CLAUDE_FLOW_DAEMON: '1',
|
|
489
|
-
// Prevent macOS SIGHUP kill when terminal closes
|
|
490
|
-
...(process.platform === 'darwin' ? { NOHUP: '1' } : {}),
|
|
491
|
-
},
|
|
492
|
-
};
|
|
493
|
-
// Forward args to the foreground child. fork() resolves the script path
|
|
494
|
-
// via Node's normal module resolution, so cliPath does not need to be
|
|
495
|
-
// shell-quoted even when it contains spaces.
|
|
496
|
-
const forkArgs = ['daemon', 'start', '--foreground', '--quiet'];
|
|
497
|
-
// Validate with strict numeric pattern to prevent injection via crafted flags.
|
|
498
|
-
const SPAWN_NUMERIC_RE = /^\d+(\.\d+)?$/;
|
|
499
|
-
if (maxCpuLoad && SPAWN_NUMERIC_RE.test(maxCpuLoad)) {
|
|
500
|
-
forkArgs.push('--max-cpu-load', maxCpuLoad);
|
|
501
|
-
}
|
|
502
|
-
if (minFreeMemory && SPAWN_NUMERIC_RE.test(minFreeMemory)) {
|
|
503
|
-
forkArgs.push('--min-free-memory', minFreeMemory);
|
|
504
|
-
}
|
|
505
|
-
// #2356: forward the TTL so the background daemon enforces it too. Integer
|
|
506
|
-
// seconds only (incl. 0 to disable) — reject anything else before it hits argv.
|
|
507
|
-
if (typeof ttl === 'string' && /^\d+$/.test(ttl)) {
|
|
508
|
-
forkArgs.push('--ttl', ttl);
|
|
509
|
-
}
|
|
510
|
-
// #1968: forward worker-selection / sandbox flags. The previous launcher
|
|
511
|
-
// dropped these, so `daemon start --workers map` ran with the default
|
|
512
|
-
// five-worker set instead of just `map`. Validate each before passing
|
|
513
|
-
// through — argv goes straight to a forked process so reject anything
|
|
514
|
-
// that doesn't look like a comma-separated worker-name list or one of
|
|
515
|
-
// the allowed sandbox modes.
|
|
516
|
-
const WORKERS_RE = /^[a-z][a-z0-9_-]*(,[a-z][a-z0-9_-]*)*$/;
|
|
517
|
-
if (typeof workers === 'string' && workers.length > 0 && WORKERS_RE.test(workers)) {
|
|
518
|
-
forkArgs.push('--workers', workers);
|
|
519
|
-
}
|
|
520
|
-
if (headless === true) {
|
|
521
|
-
forkArgs.push('--headless');
|
|
522
|
-
}
|
|
523
|
-
if (typeof sandbox === 'string' && (sandbox === 'strict' || sandbox === 'permissive' || sandbox === 'disabled')) {
|
|
524
|
-
forkArgs.push('--sandbox', sandbox);
|
|
525
|
-
}
|
|
526
|
-
// ADR-174 M3: forward the distillation opt-out to the forked foreground
|
|
527
|
-
// child; its own `start` action sets RUFLO_DAEMON_NO_DISTILL from this flag.
|
|
528
|
-
if (noDistill === true) {
|
|
529
|
-
forkArgs.push('--no-distill');
|
|
530
|
-
}
|
|
531
|
-
// #1914: stamp the workspace into argv (kept LAST) so the foreground daemon
|
|
532
|
-
// process is self-identifying and `killStaleDaemons` only reaps daemons
|
|
533
|
-
// belonging to this workspace. resolvedRoot was validatePath()'d above.
|
|
534
|
-
forkArgs.push('--workspace', resolvedRoot);
|
|
535
|
-
const child = fork(cliPath, forkArgs, forkOpts);
|
|
536
|
-
// Get PID from spawned process directly (no shell echo needed)
|
|
537
|
-
const pid = child.pid;
|
|
538
|
-
if (!pid || pid <= 0) {
|
|
539
|
-
output.printError('Failed to get daemon PID');
|
|
540
|
-
return { success: false, exitCode: 1 };
|
|
541
|
-
}
|
|
542
|
-
// Unref BEFORE writing PID file — prevents race where parent exits
|
|
543
|
-
// but child hasn't fully detached yet (fixes macOS daemon death #1283).
|
|
544
|
-
child.unref();
|
|
545
|
-
// #1766: also break the IPC pipe explicitly. unref() releases the libuv
|
|
546
|
-
// handle but does NOT close the IPC channel; on Windows the open IPC
|
|
547
|
-
// pipe keeps the daemon tied to its parent npx, and when npx exits the
|
|
548
|
-
// pipe is torn down and the daemon exits with it. disconnect() severs
|
|
549
|
-
// the IPC pipe so the daemon truly stands on its own. Wrapped in try
|
|
550
|
-
// because disconnect() throws if the IPC channel is already gone.
|
|
551
|
-
try {
|
|
552
|
-
child.disconnect();
|
|
553
|
-
}
|
|
554
|
-
catch { /* IPC channel already closed */ }
|
|
555
|
-
// Longer delay to let the child process start and write its own PID file.
|
|
556
|
-
// 100ms was too short on Windows; the child's checkExistingDaemon() would
|
|
557
|
-
// find the parent-written PID and return early (#1478 Bug 1).
|
|
558
|
-
await new Promise(resolve => setTimeout(resolve, 500));
|
|
559
|
-
// Write PID file only if the child hasn't already written its own.
|
|
560
|
-
// The foreground child calls writePidFile() internally, but on some platforms
|
|
561
|
-
// it may not have started yet, so we write as a fallback.
|
|
562
|
-
if (!fs.existsSync(pidFile)) {
|
|
563
|
-
fs.writeFileSync(pidFile, String(pid));
|
|
564
|
-
}
|
|
565
|
-
if (!quiet) {
|
|
566
|
-
output.printSuccess(`Daemon started in background (PID: ${pid})`);
|
|
567
|
-
output.printInfo(`Logs: ${logFile}`);
|
|
568
|
-
output.printInfo(`Stop with: claude-flow daemon stop`);
|
|
569
|
-
// #2661: worktree-fanout warning. Each Git worktree gets its own daemon
|
|
570
|
-
// (per-workspace scope, #1914), so `init --start-daemon` across N
|
|
571
|
-
// worktrees quietly accumulates N daemons. Surface the fleet size at
|
|
572
|
-
// start time so the accumulation is visible where it happens.
|
|
573
|
-
try {
|
|
574
|
-
const fleet = await scanRunningDaemons();
|
|
575
|
-
if (fleet.length > 1) {
|
|
576
|
-
output.writeln();
|
|
577
|
-
output.printWarning(`Found ${fleet.length} ruflo daemons running across workspaces/worktrees.`);
|
|
578
|
-
output.printInfo('Scheduled AI workers are off by default and every AI launch is capped by the user-global budget.');
|
|
579
|
-
output.printInfo('Inspect: ruflo daemon status --all');
|
|
580
|
-
output.printInfo('Stop all: ruflo daemon stop --all');
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
catch { /* best-effort visibility — never fail the start */ }
|
|
584
|
-
// #2661 root-fix — one-time migration warning for a pre-existing
|
|
585
|
-
// multi-daemon fleet that already had AI workers enabled before this
|
|
586
|
-
// fix landed. Separate from the always-shown notice above: this one
|
|
587
|
-
// fires at most once ever, and only for the genuinely risky shape.
|
|
588
|
-
await maybeShowMultiDaemonMigrationWarning();
|
|
589
|
-
}
|
|
590
|
-
return { success: true };
|
|
591
|
-
}
|
|
592
|
-
// Stop daemon subcommand
|
|
593
|
-
const stopCommand = {
|
|
594
|
-
name: 'stop',
|
|
595
|
-
description: 'Stop the worker daemon and all background workers',
|
|
596
|
-
options: [
|
|
597
|
-
{ name: 'quiet', short: 'Q', type: 'boolean', description: 'Suppress output' },
|
|
598
|
-
// #2661: emergency stop for worktree-daemon fleets. Stops every ruflo
|
|
599
|
-
// daemon owned by the current user across ALL workspaces/worktrees.
|
|
600
|
-
{ name: 'all', short: 'a', type: 'boolean', description: 'Stop ruflo daemons in ALL workspaces/worktrees (not just the current one)' },
|
|
601
|
-
],
|
|
602
|
-
examples: [
|
|
603
|
-
{ command: 'claude-flow daemon stop', description: 'Stop the daemon in this workspace' },
|
|
604
|
-
{ command: 'claude-flow daemon stop --all', description: 'Stop ruflo daemons in every workspace/worktree' },
|
|
605
|
-
],
|
|
606
|
-
action: async (ctx) => {
|
|
607
|
-
const quiet = ctx.flags.quiet;
|
|
608
|
-
const projectRoot = process.cwd();
|
|
609
|
-
// #2661: `stop --all` — the containment lever for daemon fanout across
|
|
610
|
-
// Git worktrees. Only processes positively identified as ruflo daemons
|
|
611
|
-
// (via their self-identifying argv) are touched; each receives SIGTERM
|
|
612
|
-
// so its own shutdown path reaps in-flight Claude process groups.
|
|
613
|
-
if (ctx.flags.all) {
|
|
614
|
-
return stopAllDaemons(quiet);
|
|
615
|
-
}
|
|
616
|
-
try {
|
|
617
|
-
if (!quiet) {
|
|
618
|
-
const spinner = output.createSpinner({ text: 'Stopping worker daemon...', spinner: 'dots' });
|
|
619
|
-
spinner.start();
|
|
620
|
-
// Try to stop in-process daemon first
|
|
621
|
-
await stopDaemon();
|
|
622
|
-
// Also kill any background daemon by PID
|
|
623
|
-
const killed = await killBackgroundDaemon(projectRoot);
|
|
624
|
-
// #1551: Also kill stale daemon processes not tracked by PID file
|
|
625
|
-
await killStaleDaemons(projectRoot, true);
|
|
626
|
-
spinner.succeed(killed ? 'Worker daemon stopped' : 'Worker daemon was not running');
|
|
627
|
-
}
|
|
628
|
-
else {
|
|
629
|
-
await stopDaemon();
|
|
630
|
-
await killBackgroundDaemon(projectRoot);
|
|
631
|
-
await killStaleDaemons(projectRoot, true);
|
|
632
|
-
}
|
|
633
|
-
return { success: true };
|
|
634
|
-
}
|
|
635
|
-
catch (error) {
|
|
636
|
-
output.printError(`Failed to stop daemon: ${error instanceof Error ? error.message : String(error)}`);
|
|
637
|
-
return { success: false, exitCode: 1 };
|
|
638
|
-
}
|
|
639
|
-
},
|
|
640
|
-
};
|
|
641
|
-
/**
|
|
642
|
-
* #2661: stop every running ruflo daemon across all workspaces/worktrees.
|
|
643
|
-
*
|
|
644
|
-
* Reuses the same positive identification as `daemon status --all`
|
|
645
|
-
* (scanRunningDaemons): a process is only touched when its command line is
|
|
646
|
-
* self-identifying as a ruflo daemon (`daemon start --foreground` +
|
|
647
|
-
* claude-flow markers). Interactive Claude sessions and non-ruflo processes
|
|
648
|
-
* are never candidates. Each daemon gets SIGTERM first — its own shutdown
|
|
649
|
-
* handler cancels in-flight headless Claude process groups and removes its
|
|
650
|
-
* PID file — with a SIGKILL fallback for daemons that don't exit within 2s.
|
|
651
|
-
* Only ruflo-owned registry entries (each workspace's daemon.pid) are removed.
|
|
652
|
-
*/
|
|
653
|
-
async function stopAllDaemons(quiet) {
|
|
654
|
-
// Stop any in-process daemon plus this workspace's tracked daemon first,
|
|
655
|
-
// matching plain `daemon stop` semantics for the current directory.
|
|
656
|
-
try {
|
|
657
|
-
await stopDaemon();
|
|
658
|
-
}
|
|
659
|
-
catch { /* not running in-process */ }
|
|
660
|
-
await killBackgroundDaemon(process.cwd());
|
|
661
|
-
const daemons = await scanRunningDaemons();
|
|
662
|
-
if (daemons.length === 0) {
|
|
663
|
-
if (!quiet) {
|
|
664
|
-
output.printInfo('No ruflo daemons are running in any workspace.');
|
|
665
|
-
}
|
|
666
|
-
return { success: true, data: { stopped: 0 } };
|
|
667
|
-
}
|
|
668
|
-
const isWin = process.platform === 'win32';
|
|
669
|
-
let stopped = 0;
|
|
670
|
-
for (const d of daemons) {
|
|
671
|
-
try {
|
|
672
|
-
if (isWin) {
|
|
673
|
-
const { execFileSync } = await import('child_process');
|
|
674
|
-
// /t terminates the daemon's child tree too (no /f: graceful first).
|
|
675
|
-
execFileSync('taskkill', ['/pid', String(d.pid), '/t'], { encoding: 'utf-8', timeout: 5000 });
|
|
676
|
-
}
|
|
677
|
-
else {
|
|
678
|
-
process.kill(d.pid, 'SIGTERM');
|
|
679
|
-
}
|
|
680
|
-
stopped++;
|
|
681
|
-
if (!quiet) {
|
|
682
|
-
output.printInfo(`Stopping daemon PID ${d.pid}${d.workspace ? ` (${d.workspace})` : ''}`);
|
|
683
|
-
}
|
|
684
|
-
}
|
|
685
|
-
catch { /* exited between scan and kill */ }
|
|
686
|
-
}
|
|
687
|
-
// Give SIGTERM handlers time to reap children and clean up, then
|
|
688
|
-
// force-kill anything still alive (POSIX; taskkill /t already recursed).
|
|
689
|
-
await new Promise((r) => setTimeout(r, 2000));
|
|
690
|
-
for (const d of daemons) {
|
|
691
|
-
if (!isWin && isProcessRunning(d.pid)) {
|
|
692
|
-
try {
|
|
693
|
-
process.kill(d.pid, 'SIGKILL');
|
|
694
|
-
}
|
|
695
|
-
catch { /* already dead */ }
|
|
696
|
-
}
|
|
697
|
-
// Remove the ruflo-owned PID file for that workspace — but only when it
|
|
698
|
-
// still points at the daemon we just stopped (never clobber a newer one).
|
|
699
|
-
if (d.workspace) {
|
|
700
|
-
try {
|
|
701
|
-
const pidFile = join(d.workspace, '.claude-flow', 'daemon.pid');
|
|
702
|
-
if (fs.existsSync(pidFile)) {
|
|
703
|
-
const filePid = parseInt(fs.readFileSync(pidFile, 'utf-8').trim(), 10);
|
|
704
|
-
if (filePid === d.pid)
|
|
705
|
-
fs.unlinkSync(pidFile);
|
|
706
|
-
}
|
|
707
|
-
}
|
|
708
|
-
catch { /* workspace removed or unreadable — nothing to clean */ }
|
|
709
|
-
}
|
|
710
|
-
}
|
|
711
|
-
if (!quiet) {
|
|
712
|
-
output.printSuccess(`Stopped ${stopped} ruflo daemon(s) across all workspaces.`);
|
|
713
|
-
}
|
|
714
|
-
return { success: true, data: { stopped } };
|
|
715
|
-
}
|
|
716
|
-
/**
|
|
717
|
-
* Kill background daemon process using PID file
|
|
718
|
-
*/
|
|
719
|
-
async function killBackgroundDaemon(projectRoot) {
|
|
720
|
-
const pidFile = join(projectRoot, '.claude-flow', 'daemon.pid');
|
|
721
|
-
if (!fs.existsSync(pidFile)) {
|
|
722
|
-
return false;
|
|
723
|
-
}
|
|
724
|
-
try {
|
|
725
|
-
const pid = parseInt(fs.readFileSync(pidFile, 'utf-8').trim(), 10);
|
|
726
|
-
if (isNaN(pid)) {
|
|
727
|
-
fs.unlinkSync(pidFile);
|
|
728
|
-
return false;
|
|
729
|
-
}
|
|
730
|
-
// Check if process is running
|
|
731
|
-
try {
|
|
732
|
-
process.kill(pid, 0); // Signal 0 = check if alive
|
|
733
|
-
}
|
|
734
|
-
catch {
|
|
735
|
-
// Process not running, clean up stale PID file
|
|
736
|
-
fs.unlinkSync(pidFile);
|
|
737
|
-
return false;
|
|
738
|
-
}
|
|
739
|
-
// Kill the process
|
|
740
|
-
process.kill(pid, 'SIGTERM');
|
|
741
|
-
// Wait a moment then force kill if needed
|
|
742
|
-
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
743
|
-
try {
|
|
744
|
-
process.kill(pid, 0);
|
|
745
|
-
// Still alive, force kill
|
|
746
|
-
process.kill(pid, 'SIGKILL');
|
|
747
|
-
}
|
|
748
|
-
catch {
|
|
749
|
-
// Process terminated
|
|
750
|
-
}
|
|
751
|
-
// Clean up PID file
|
|
752
|
-
if (fs.existsSync(pidFile)) {
|
|
753
|
-
fs.unlinkSync(pidFile);
|
|
754
|
-
}
|
|
755
|
-
return true;
|
|
756
|
-
}
|
|
757
|
-
catch (error) {
|
|
758
|
-
// Clean up PID file on any error
|
|
759
|
-
if (fs.existsSync(pidFile)) {
|
|
760
|
-
fs.unlinkSync(pidFile);
|
|
761
|
-
}
|
|
762
|
-
return false;
|
|
763
|
-
}
|
|
764
|
-
}
|
|
765
|
-
/**
|
|
766
|
-
* Kill stale daemon processes not tracked by the PID file (#1551, #1857).
|
|
767
|
-
* Uses `ps` on POSIX and `tasklist` on Windows to find all daemon
|
|
768
|
-
* processes for this project and kill them.
|
|
769
|
-
*/
|
|
770
|
-
async function killStaleDaemons(projectRoot, quiet) {
|
|
771
|
-
if (process.platform === 'win32') {
|
|
772
|
-
return killStaleDaemonsWindows(projectRoot, quiet);
|
|
773
|
-
}
|
|
774
|
-
return killStaleDaemonsPosix(projectRoot, quiet);
|
|
775
|
-
}
|
|
776
|
-
async function killStaleDaemonsPosix(projectRoot, quiet) {
|
|
777
|
-
try {
|
|
778
|
-
const { execFileSync } = await import('child_process');
|
|
779
|
-
const psOutput = execFileSync('ps', ['-eo', 'pid,command'], { encoding: 'utf-8', timeout: 5000 });
|
|
780
|
-
const lines = psOutput.split('\n');
|
|
781
|
-
const currentPid = process.pid;
|
|
782
|
-
const trackedPid = getBackgroundDaemonPid(projectRoot);
|
|
783
|
-
// #1914: only ever reap daemons belonging to THIS workspace (ADR-014).
|
|
784
|
-
const resolvedRoot = resolve(projectRoot);
|
|
785
|
-
let killed = 0;
|
|
786
|
-
for (const line of lines) {
|
|
787
|
-
if (!line.includes('daemon start --foreground'))
|
|
788
|
-
continue;
|
|
789
|
-
if (!line.includes('claude-flow') && !line.includes('@claude-flow/cli'))
|
|
790
|
-
continue;
|
|
791
|
-
// #1914: skip daemons from other workspaces (or pre-#1914 versions that
|
|
792
|
-
// didn't stamp --workspace — let `daemon stop` handle those via PID file).
|
|
793
|
-
if (!daemonCommandLineBelongsToWorkspace(line, resolvedRoot))
|
|
794
|
-
continue;
|
|
795
|
-
const pidStr = line.trim().split(/\s+/)[0];
|
|
796
|
-
const pid = parseInt(pidStr, 10);
|
|
797
|
-
if (isNaN(pid) || pid === currentPid || pid === trackedPid)
|
|
798
|
-
continue;
|
|
799
|
-
if (!isProcessRunning(pid))
|
|
800
|
-
continue;
|
|
801
|
-
try {
|
|
802
|
-
process.kill(pid, 'SIGTERM');
|
|
803
|
-
killed++;
|
|
804
|
-
if (!quiet) {
|
|
805
|
-
output.printWarning(`Killed stale daemon process (PID: ${pid})`);
|
|
806
|
-
}
|
|
807
|
-
}
|
|
808
|
-
catch { /* ignore — may have exited between check and kill */ }
|
|
809
|
-
}
|
|
810
|
-
if (killed > 0 && !quiet) {
|
|
811
|
-
output.printInfo(`Cleaned up ${killed} stale daemon process(es)`);
|
|
812
|
-
}
|
|
813
|
-
}
|
|
814
|
-
catch {
|
|
815
|
-
// ps not available or failed — skip stale cleanup
|
|
816
|
-
}
|
|
817
|
-
}
|
|
818
|
-
/**
|
|
819
|
-
* #1857: Windows replacement for the POSIX `ps -eo pid,command` path.
|
|
820
|
-
* Uses `tasklist /v /fo csv` which returns CSV with the full Window
|
|
821
|
-
* Title column (last field) — Node-spawned daemon processes carry
|
|
822
|
-
* their command line there. Best-effort like the POSIX path: any
|
|
823
|
-
* tooling failure (tasklist missing, parse error, etc.) is swallowed
|
|
824
|
-
* silently so cleanup doesn't break daemon start.
|
|
825
|
-
*/
|
|
826
|
-
async function killStaleDaemonsWindows(projectRoot, quiet) {
|
|
827
|
-
try {
|
|
828
|
-
const { execFileSync } = await import('child_process');
|
|
829
|
-
// /v includes the Window Title; /fo csv uses comma-separated quoted fields
|
|
830
|
-
const out = execFileSync('tasklist', ['/v', '/fo', 'csv', '/nh'], { encoding: 'utf-8', timeout: 5000 });
|
|
831
|
-
const lines = out.split(/\r?\n/);
|
|
832
|
-
const currentPid = process.pid;
|
|
833
|
-
const trackedPid = getBackgroundDaemonPid(projectRoot);
|
|
834
|
-
// #1914: only ever reap daemons belonging to THIS workspace (ADR-014).
|
|
835
|
-
const resolvedRoot = resolve(projectRoot);
|
|
836
|
-
let killed = 0;
|
|
837
|
-
for (const line of lines) {
|
|
838
|
-
if (!line.trim())
|
|
839
|
-
continue;
|
|
840
|
-
// Match daemon command line markers — the Window Title field
|
|
841
|
-
// typically holds the full invocation. Skip rows that aren't ours.
|
|
842
|
-
if (!line.includes('daemon start --foreground'))
|
|
843
|
-
continue;
|
|
844
|
-
if (!line.includes('claude-flow') && !line.includes('@claude-flow/cli'))
|
|
845
|
-
continue;
|
|
846
|
-
// #1914: skip daemons from other workspaces (or pre-#1914 versions).
|
|
847
|
-
if (!daemonCommandLineBelongsToWorkspace(line, resolvedRoot))
|
|
848
|
-
continue;
|
|
849
|
-
// Parse CSV: tasklist quotes each field, so split on `","`
|
|
850
|
-
const fields = line.split(/","/).map(f => f.replace(/^"|"$/g, ''));
|
|
851
|
-
// fields[0] = Image Name, fields[1] = PID, …
|
|
852
|
-
const pidStr = fields[1];
|
|
853
|
-
const pid = parseInt(pidStr ?? '', 10);
|
|
854
|
-
if (isNaN(pid) || pid === currentPid || pid === trackedPid)
|
|
855
|
-
continue;
|
|
856
|
-
if (!isProcessRunning(pid))
|
|
857
|
-
continue;
|
|
858
|
-
try {
|
|
859
|
-
// taskkill is the Windows equivalent of kill — /pid <n> /f forces.
|
|
860
|
-
// Use SIGTERM-equivalent (no /f) first; the daemon's signal handler
|
|
861
|
-
// catches and cleans up; force-kill is the next start's job.
|
|
862
|
-
execFileSync('taskkill', ['/pid', String(pid), '/t'], { encoding: 'utf-8', timeout: 5000 });
|
|
863
|
-
killed++;
|
|
864
|
-
if (!quiet) {
|
|
865
|
-
output.printWarning(`Killed stale daemon process (PID: ${pid})`);
|
|
866
|
-
}
|
|
867
|
-
}
|
|
868
|
-
catch { /* taskkill failed — process may have exited; ignore */ }
|
|
869
|
-
}
|
|
870
|
-
if (killed > 0 && !quiet) {
|
|
871
|
-
output.printInfo(`Cleaned up ${killed} stale daemon process(es)`);
|
|
872
|
-
}
|
|
873
|
-
}
|
|
874
|
-
catch {
|
|
875
|
-
// tasklist not available or failed — skip stale cleanup. Defensive
|
|
876
|
-
// shape matches the POSIX path. Not tested on Windows by the
|
|
877
|
-
// maintainer; please report regressions on the issue tracker.
|
|
878
|
-
}
|
|
879
|
-
}
|
|
880
|
-
/**
|
|
881
|
-
* Get PID of background daemon from PID file
|
|
882
|
-
*/
|
|
883
|
-
function getBackgroundDaemonPid(projectRoot) {
|
|
884
|
-
const pidFile = join(projectRoot, '.claude-flow', 'daemon.pid');
|
|
885
|
-
if (!fs.existsSync(pidFile)) {
|
|
886
|
-
return null;
|
|
887
|
-
}
|
|
888
|
-
try {
|
|
889
|
-
const pid = parseInt(fs.readFileSync(pidFile, 'utf-8').trim(), 10);
|
|
890
|
-
return isNaN(pid) ? null : pid;
|
|
891
|
-
}
|
|
892
|
-
catch {
|
|
893
|
-
return null;
|
|
894
|
-
}
|
|
895
|
-
}
|
|
896
|
-
/**
|
|
897
|
-
* Check if a process is running
|
|
898
|
-
*/
|
|
899
|
-
function isProcessRunning(pid) {
|
|
900
|
-
try {
|
|
901
|
-
process.kill(pid, 0); // Signal 0 = check if alive
|
|
902
|
-
return true;
|
|
903
|
-
}
|
|
904
|
-
catch {
|
|
905
|
-
return false;
|
|
906
|
-
}
|
|
907
|
-
}
|
|
908
|
-
/**
|
|
909
|
-
* #2356: enumerate every running ruflo daemon across ALL workspaces. Reuses
|
|
910
|
-
* the same `ps`/`tasklist` scan as killStaleDaemons but, instead of killing,
|
|
911
|
-
* returns each live daemon's PID + workspace so `daemon status --all` can
|
|
912
|
-
* surface daemons leaked in other projects. Best-effort: any tooling failure
|
|
913
|
-
* yields an empty list (matching the kill-stale paths).
|
|
914
|
-
*/
|
|
915
|
-
async function scanRunningDaemons() {
|
|
916
|
-
const isWin = process.platform === 'win32';
|
|
917
|
-
try {
|
|
918
|
-
const { execFileSync } = await import('child_process');
|
|
919
|
-
const out = isWin
|
|
920
|
-
? execFileSync('tasklist', ['/v', '/fo', 'csv', '/nh'], { encoding: 'utf-8', timeout: 5000 })
|
|
921
|
-
: execFileSync('ps', ['-eo', 'pid,command'], { encoding: 'utf-8', timeout: 5000 });
|
|
922
|
-
const lines = out.split(/\r?\n/);
|
|
923
|
-
const found = [];
|
|
924
|
-
for (const line of lines) {
|
|
925
|
-
if (!line.includes('daemon start --foreground'))
|
|
926
|
-
continue;
|
|
927
|
-
if (!line.includes('claude-flow') && !line.includes('@claude-flow/cli'))
|
|
928
|
-
continue;
|
|
929
|
-
let pid;
|
|
930
|
-
let cmd;
|
|
931
|
-
if (isWin) {
|
|
932
|
-
// tasklist /fo csv: quoted fields; PID is field[1], Window Title is last.
|
|
933
|
-
const fields = line.split(/","/).map(f => f.replace(/^"|"$/g, ''));
|
|
934
|
-
pid = parseInt(fields[1] ?? '', 10);
|
|
935
|
-
cmd = fields[fields.length - 1] ?? line;
|
|
936
|
-
}
|
|
937
|
-
else {
|
|
938
|
-
pid = parseInt(line.trim().split(/\s+/)[0], 10);
|
|
939
|
-
cmd = line;
|
|
940
|
-
}
|
|
941
|
-
if (Number.isNaN(pid) || !isProcessRunning(pid))
|
|
942
|
-
continue;
|
|
943
|
-
found.push({ pid, workspace: extractWorkspaceFromDaemonLine(cmd) });
|
|
944
|
-
}
|
|
945
|
-
return found;
|
|
946
|
-
}
|
|
947
|
-
catch {
|
|
948
|
-
return [];
|
|
949
|
-
}
|
|
950
|
-
}
|
|
951
|
-
function defaultMultiDaemonWarningMarker() {
|
|
952
|
-
return join(homedir(), '.claude-flow', 'multi-daemon-warning-shown.json');
|
|
953
|
-
}
|
|
954
|
-
/**
|
|
955
|
-
* #2661 root-fix — one-time upgrade migration warning. A user who had
|
|
956
|
-
* `aiWorkersEnabled: true` configured BEFORE this fix landed (old config
|
|
957
|
-
* file or RUFLO_DAEMON_AI_WORKERS=1) and already has multiple worktree
|
|
958
|
-
* daemons running is exactly the P0 scenario the issue describes — surface
|
|
959
|
-
* it plainly, ONCE ever (not on every `daemon start`, which would just be
|
|
960
|
-
* noise once the user has seen and acted on it). The supervisor/lease
|
|
961
|
-
* mechanism (task #9) already makes only one of those daemons actually
|
|
962
|
-
* schedule AI workers going forward; this warning's job is purely to make
|
|
963
|
-
* a pre-existing fleet VISIBLE the first time this code runs, not to take
|
|
964
|
-
* any destructive action — nothing here stops or kills another daemon.
|
|
965
|
-
*
|
|
966
|
-
* `opts` exists for tests ONLY, mirroring the injectable-dependency pattern
|
|
967
|
-
* used elsewhere in this codebase (e.g. helper-refresh.ts's
|
|
968
|
-
* sourceDirOverride) — real callers always use the defaults.
|
|
969
|
-
*/
|
|
970
|
-
export async function maybeShowMultiDaemonMigrationWarning(opts) {
|
|
971
|
-
const markerFile = opts?.markerFile ?? defaultMultiDaemonWarningMarker();
|
|
972
|
-
try {
|
|
973
|
-
if (fs.existsSync(markerFile))
|
|
974
|
-
return;
|
|
975
|
-
const fleet = await (opts?.fleetScanner ?? scanRunningDaemons)();
|
|
976
|
-
if (fleet.length <= 1)
|
|
977
|
-
return;
|
|
978
|
-
let anyAiEnabled = false;
|
|
979
|
-
for (const d of fleet) {
|
|
980
|
-
if (!d.workspace)
|
|
981
|
-
continue;
|
|
982
|
-
try {
|
|
983
|
-
const statePath = join(d.workspace, '.claude-flow', 'daemon-state.json');
|
|
984
|
-
if (!fs.existsSync(statePath))
|
|
985
|
-
continue;
|
|
986
|
-
const st = JSON.parse(fs.readFileSync(statePath, 'utf-8'));
|
|
987
|
-
if (st?.config?.aiWorkersEnabled === true) {
|
|
988
|
-
anyAiEnabled = true;
|
|
989
|
-
break;
|
|
990
|
-
}
|
|
991
|
-
}
|
|
992
|
-
catch { /* unreadable state — skip this daemon */ }
|
|
993
|
-
}
|
|
994
|
-
// Only the genuinely risky shape (pre-existing fleet + AI workers
|
|
995
|
-
// enabled somewhere in it) warrants the migration warning. A harmless
|
|
996
|
-
// multi-daemon fleet with AI workers off everywhere already gets the
|
|
997
|
-
// lighter, always-shown fleet-size notice at daemon start.
|
|
998
|
-
if (anyAiEnabled) {
|
|
999
|
-
output.writeln();
|
|
1000
|
-
output.printWarning(`Ruflo found ${fleet.length} worktree daemons. Scheduled AI workers are now supervisor-gated.`);
|
|
1001
|
-
output.printInfo('Inspect: ruflo daemon status --all');
|
|
1002
|
-
output.printInfo('Stop all: ruflo daemon stop --all');
|
|
1003
|
-
output.printInfo('Pause autonomous launches: ruflo daemon budget pause');
|
|
1004
|
-
output.writeln();
|
|
1005
|
-
}
|
|
1006
|
-
const dir = dirname(markerFile);
|
|
1007
|
-
if (!fs.existsSync(dir))
|
|
1008
|
-
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
1009
|
-
fs.writeFileSync(markerFile, JSON.stringify({ shownAt: new Date().toISOString(), fleetSize: fleet.length, anyAiEnabled }), { mode: 0o600 });
|
|
1010
|
-
}
|
|
1011
|
-
catch { /* best-effort visibility — never fail the command */ }
|
|
1012
|
-
}
|
|
1013
|
-
/**
|
|
1014
|
-
* #2356: render the global `daemon status --all` view. For each running daemon
|
|
1015
|
-
* it reads that workspace's daemon-state.json to show age + configured TTL,
|
|
1016
|
-
* and flags any daemon that has outlived its TTL (or 12h when TTL is unknown)
|
|
1017
|
-
* as stale — the visibility that was missing when leaked daemons ran for days.
|
|
1018
|
-
*/
|
|
1019
|
-
async function renderAllDaemonsStatus() {
|
|
1020
|
-
const daemons = await scanRunningDaemons();
|
|
1021
|
-
output.writeln();
|
|
1022
|
-
if (daemons.length === 0) {
|
|
1023
|
-
output.printBox('No ruflo daemons are running in any workspace.', 'RuFlo Daemons (all workspaces)');
|
|
1024
|
-
return { success: true, data: { daemons: [] } };
|
|
1025
|
-
}
|
|
1026
|
-
const now = Date.now();
|
|
1027
|
-
const TWELVE_HOURS_MS = 12 * 60 * 60 * 1000;
|
|
1028
|
-
let staleCount = 0;
|
|
1029
|
-
const rows = daemons.map(d => {
|
|
1030
|
-
let startedAt;
|
|
1031
|
-
let ttlMs;
|
|
1032
|
-
if (d.workspace) {
|
|
1033
|
-
try {
|
|
1034
|
-
const statePath = join(d.workspace, '.claude-flow', 'daemon-state.json');
|
|
1035
|
-
if (fs.existsSync(statePath)) {
|
|
1036
|
-
const st = JSON.parse(fs.readFileSync(statePath, 'utf-8'));
|
|
1037
|
-
if (st?.startedAt)
|
|
1038
|
-
startedAt = new Date(st.startedAt);
|
|
1039
|
-
if (typeof st?.config?.ttlMs === 'number')
|
|
1040
|
-
ttlMs = st.config.ttlMs;
|
|
1041
|
-
}
|
|
1042
|
-
}
|
|
1043
|
-
catch { /* unreadable/partial state — show what we have */ }
|
|
1044
|
-
}
|
|
1045
|
-
const ageMs = startedAt ? now - startedAt.getTime() : undefined;
|
|
1046
|
-
const overTtl = ttlMs !== undefined && ttlMs > 0 && ageMs !== undefined && ageMs > ttlMs;
|
|
1047
|
-
const overTwelveH = ageMs !== undefined && ageMs > TWELVE_HOURS_MS;
|
|
1048
|
-
const isStale = overTtl || overTwelveH;
|
|
1049
|
-
if (isStale)
|
|
1050
|
-
staleCount++;
|
|
1051
|
-
const ageText = ageMs !== undefined ? formatTimeAgo(startedAt).replace(' ago', '') : '?';
|
|
1052
|
-
const ttlText = ttlMs !== undefined
|
|
1053
|
-
? (ttlMs > 0 ? `${Math.round(ttlMs / 3600000)}h` : 'off')
|
|
1054
|
-
: '?';
|
|
1055
|
-
return {
|
|
1056
|
-
pid: isStale ? output.warning(String(d.pid)) : String(d.pid),
|
|
1057
|
-
workspace: d.workspace ?? output.dim('(unknown)'),
|
|
1058
|
-
age: isStale ? output.warning(ageText) : ageText,
|
|
1059
|
-
ttl: ttlText === 'off' ? output.dim('off') : ttlText,
|
|
1060
|
-
};
|
|
1061
|
-
});
|
|
1062
|
-
output.printTable({
|
|
1063
|
-
columns: [
|
|
1064
|
-
{ key: 'pid', header: 'PID', width: 8 },
|
|
1065
|
-
{ key: 'age', header: 'Age', width: 8 },
|
|
1066
|
-
{ key: 'ttl', header: 'TTL', width: 6 },
|
|
1067
|
-
{ key: 'workspace', header: 'Workspace', width: 50 },
|
|
1068
|
-
],
|
|
1069
|
-
data: rows,
|
|
1070
|
-
});
|
|
1071
|
-
output.writeln();
|
|
1072
|
-
if (staleCount > 0) {
|
|
1073
|
-
output.printWarning(`${staleCount} daemon(s) have outlived their TTL (or have run >12h). Stop one with: cd <workspace> && ruflo daemon stop`);
|
|
1074
|
-
}
|
|
1075
|
-
else {
|
|
1076
|
-
output.printInfo(`${daemons.length} daemon(s) running, all within their TTL.`);
|
|
1077
|
-
}
|
|
1078
|
-
if (daemons.length > 1) {
|
|
1079
|
-
output.printInfo('Stop all daemons across workspaces with: ruflo daemon stop --all');
|
|
1080
|
-
}
|
|
1081
|
-
// #2661 root-fix — repository supervisor state, one row per distinct
|
|
1082
|
-
// repository among the scanned daemons' workspaces. Resolving identity
|
|
1083
|
-
// per workspace is cheap (a couple of `git rev-parse` calls, cached).
|
|
1084
|
-
try {
|
|
1085
|
-
const { resolveGitWorkspaceIdentity } = await import('../services/git-workspace-identity.js');
|
|
1086
|
-
const { getRepoSupervisorRegistry } = await import('../services/repo-supervisor.js');
|
|
1087
|
-
const { getWorkspaceLeaseRegistry } = await import('../services/workspace-lease.js');
|
|
1088
|
-
const seenRepos = new Map(); // repositoryId -> a representative workspace path
|
|
1089
|
-
for (const d of daemons) {
|
|
1090
|
-
if (!d.workspace)
|
|
1091
|
-
continue;
|
|
1092
|
-
const identity = resolveGitWorkspaceIdentity(d.workspace);
|
|
1093
|
-
if (identity.isGit && !seenRepos.has(identity.repositoryId)) {
|
|
1094
|
-
seenRepos.set(identity.repositoryId, d.workspace);
|
|
1095
|
-
}
|
|
1096
|
-
}
|
|
1097
|
-
if (seenRepos.size > 0) {
|
|
1098
|
-
const supervisorReg = getRepoSupervisorRegistry();
|
|
1099
|
-
const leaseReg = getWorkspaceLeaseRegistry();
|
|
1100
|
-
const lines = [];
|
|
1101
|
-
for (const [repositoryId, sampleWorkspace] of seenRepos) {
|
|
1102
|
-
const record = supervisorReg.getRecord(repositoryId);
|
|
1103
|
-
const activeLeases = leaseReg.listActive(repositoryId).length;
|
|
1104
|
-
const label = repositoryId.slice(0, 12);
|
|
1105
|
-
lines.push(record
|
|
1106
|
-
? ` ${label}… supervisor: ${record.worktreeRoot} (pid ${record.pid}) | active leases: ${activeLeases}`
|
|
1107
|
-
: ` ${label}… supervisor: ${output.dim('none elected')} | active leases: ${activeLeases}`);
|
|
1108
|
-
}
|
|
1109
|
-
output.writeln();
|
|
1110
|
-
output.printBox(lines.join('\n'), 'Repository Supervisors (#2661 root-fix)');
|
|
1111
|
-
}
|
|
1112
|
-
}
|
|
1113
|
-
catch { /* supervisor registry unavailable — skip the panel */ }
|
|
1114
|
-
// #2661: user-global AI launch usage — the shared budget every daemon
|
|
1115
|
-
// draws from, independent of worktree count.
|
|
1116
|
-
try {
|
|
1117
|
-
const { getGlobalAiBudget } = await import('../services/global-ai-budget.js');
|
|
1118
|
-
const budget = getGlobalAiBudget();
|
|
1119
|
-
const usage = budget.getUsage();
|
|
1120
|
-
const limits = budget.getLimits();
|
|
1121
|
-
output.writeln();
|
|
1122
|
-
// #2661: per-workspace 24h launch attribution — which worktree is
|
|
1123
|
-
// actually spending the shared budget.
|
|
1124
|
-
const byWs = usage.byWorkspace.slice(0, 5).map((w) => ` ${w.launches}× ${w.workspace}`);
|
|
1125
|
-
output.printBox([
|
|
1126
|
-
`Launches (last hour): ${usage.lastHour}/${limits.maxLaunchesPerHour}`,
|
|
1127
|
-
`Launches (last 24h): ${usage.lastDay}/${limits.maxLaunchesPerDay}`,
|
|
1128
|
-
`Active Claude children: ${usage.active}/${limits.maxConcurrentGlobal}`,
|
|
1129
|
-
usage.pausedUntil
|
|
1130
|
-
? output.warning(`PAUSED until ${new Date(usage.pausedUntil).toISOString()} (${usage.pauseReason ?? 'quota error'})`)
|
|
1131
|
-
: `Circuit breaker: ${output.dim('closed (normal)')}`,
|
|
1132
|
-
...(byWs.length > 0 ? ['Launches by workspace (24h):', ...byWs] : []),
|
|
1133
|
-
].join('\n'), 'Global AI Budget (all workspaces)');
|
|
1134
|
-
}
|
|
1135
|
-
catch { /* budget ledger unavailable — skip the panel */ }
|
|
1136
|
-
return { success: true, data: { daemons: rows.length } };
|
|
1137
|
-
}
|
|
1138
|
-
// Status subcommand
|
|
1139
|
-
const statusCommand = {
|
|
1140
|
-
name: 'status',
|
|
1141
|
-
description: 'Show daemon and worker status',
|
|
1142
|
-
options: [
|
|
1143
|
-
{ name: 'verbose', short: 'v', type: 'boolean', description: 'Show detailed worker statistics' },
|
|
1144
|
-
{ name: 'show-modes', type: 'boolean', description: 'Show worker execution modes (local/headless) and sandbox settings' },
|
|
1145
|
-
// #2356: the default status reads only the CURRENT workspace, so a daemon
|
|
1146
|
-
// leaked in another project is invisible. --all scans every running ruflo
|
|
1147
|
-
// daemon across all workspaces (the global view that surfaces leaks).
|
|
1148
|
-
{ name: 'all', short: 'a', type: 'boolean', description: 'List ruflo daemons across ALL workspaces (global view — surfaces leaked daemons)' },
|
|
1149
|
-
],
|
|
1150
|
-
examples: [
|
|
1151
|
-
{ command: 'claude-flow daemon status', description: 'Show daemon status' },
|
|
1152
|
-
{ command: 'claude-flow daemon status -v', description: 'Show detailed status' },
|
|
1153
|
-
{ command: 'claude-flow daemon status --show-modes', description: 'Show worker execution modes' },
|
|
1154
|
-
{ command: 'claude-flow daemon status --all', description: 'List daemons across all workspaces' },
|
|
1155
|
-
],
|
|
1156
|
-
action: async (ctx) => {
|
|
1157
|
-
const verbose = ctx.flags.verbose;
|
|
1158
|
-
const showModes = ctx.flags['show-modes'];
|
|
1159
|
-
// #2356: global view across every workspace, not just cwd.
|
|
1160
|
-
if (ctx.flags.all) {
|
|
1161
|
-
return renderAllDaemonsStatus();
|
|
1162
|
-
}
|
|
1163
|
-
const projectRoot = process.cwd();
|
|
1164
|
-
try {
|
|
1165
|
-
const daemon = getDaemon(projectRoot);
|
|
1166
|
-
const status = daemon.getStatus();
|
|
1167
|
-
// Also check for background daemon
|
|
1168
|
-
const bgPid = getBackgroundDaemonPid(projectRoot);
|
|
1169
|
-
const bgRunning = bgPid ? isProcessRunning(bgPid) : false;
|
|
1170
|
-
const isRunning = status.running || bgRunning;
|
|
1171
|
-
const displayPid = bgPid || status.pid;
|
|
1172
|
-
// #2661: this CLI process constructs its own (default-config) daemon
|
|
1173
|
-
// instance, so status.config.aiWorkersEnabled reflects THIS process,
|
|
1174
|
-
// not the running background daemon. The background daemon persists
|
|
1175
|
-
// its real consent state into daemon-state.json — prefer that when it
|
|
1176
|
-
// is the one running.
|
|
1177
|
-
let aiWorkersEnabled = status.config.aiWorkersEnabled;
|
|
1178
|
-
if (bgRunning) {
|
|
1179
|
-
try {
|
|
1180
|
-
const st = JSON.parse(fs.readFileSync(join(projectRoot, '.claude-flow', 'daemon-state.json'), 'utf-8'));
|
|
1181
|
-
if (typeof st?.config?.aiWorkersEnabled === 'boolean') {
|
|
1182
|
-
aiWorkersEnabled = st.config.aiWorkersEnabled;
|
|
1183
|
-
}
|
|
1184
|
-
}
|
|
1185
|
-
catch { /* no/partial state — fall back to in-process config */ }
|
|
1186
|
-
}
|
|
1187
|
-
output.writeln();
|
|
1188
|
-
// Daemon status box
|
|
1189
|
-
const statusIcon = isRunning ? output.success('●') : output.error('○');
|
|
1190
|
-
const statusText = isRunning ? output.success('RUNNING') : output.error('STOPPED');
|
|
1191
|
-
const mode = bgRunning ? output.dim(' (background)') : status.running ? output.dim(' (foreground)') : '';
|
|
1192
|
-
output.printBox([
|
|
1193
|
-
`Status: ${statusIcon} ${statusText}${mode}`,
|
|
1194
|
-
`PID: ${displayPid}`,
|
|
1195
|
-
status.startedAt ? `Started: ${status.startedAt.toISOString()}` : '',
|
|
1196
|
-
status.config.ttlMs > 0
|
|
1197
|
-
? `TTL: ${Math.round(status.config.ttlMs / 3600000)}h (self-shutdown)`
|
|
1198
|
-
: `TTL: ${output.dim('off (runs until stopped)')}`,
|
|
1199
|
-
// #2661: surface the AI-consent gate so "why is audit local-only?"
|
|
1200
|
-
// is answerable from `daemon status` alone.
|
|
1201
|
-
`AI Workers: ${aiWorkersEnabled ? output.warning('enabled (budget-capped)') : output.dim('off (local-only, default)')}`,
|
|
1202
|
-
`Workers Enabled: ${status.config.workers.filter(w => w.enabled).length}`,
|
|
1203
|
-
`Max Concurrent: ${status.config.maxConcurrent}`,
|
|
1204
|
-
`Max CPU Load: ${status.config.resourceThresholds.maxCpuLoad}`,
|
|
1205
|
-
`Min Free Memory: ${status.config.resourceThresholds.minFreeMemoryPercent}%`,
|
|
1206
|
-
].filter(Boolean).join('\n'), 'RuFlo Daemon');
|
|
1207
|
-
output.writeln();
|
|
1208
|
-
output.writeln(output.bold('Worker Status'));
|
|
1209
|
-
const workerData = status.config.workers.map(w => {
|
|
1210
|
-
const state = status.workers.get(w.type);
|
|
1211
|
-
// Check for headless mode from worker config or state
|
|
1212
|
-
const isHeadless = w.headless || state?.headless || false;
|
|
1213
|
-
const sandboxMode = w.sandbox || state?.sandbox || null;
|
|
1214
|
-
return {
|
|
1215
|
-
type: w.enabled ? output.highlight(w.type) : output.dim(w.type),
|
|
1216
|
-
enabled: w.enabled ? output.success('✓') : output.dim('○'),
|
|
1217
|
-
status: state?.isRunning ? output.warning('running') :
|
|
1218
|
-
w.enabled ? output.success('idle') : output.dim('disabled'),
|
|
1219
|
-
runs: state?.runCount ?? 0,
|
|
1220
|
-
success: state ? `${Math.round((state.successCount / Math.max(state.runCount, 1)) * 100)}%` : '-',
|
|
1221
|
-
lastRun: state?.lastRun ? formatTimeAgo(state.lastRun) : output.dim('never'),
|
|
1222
|
-
nextRun: state?.nextRun && w.enabled ? formatTimeUntil(state.nextRun) : output.dim('-'),
|
|
1223
|
-
mode: isHeadless ? output.highlight('headless') : output.dim('local'),
|
|
1224
|
-
sandbox: isHeadless ? (sandboxMode || 'strict') : output.dim('-'),
|
|
1225
|
-
};
|
|
1226
|
-
});
|
|
1227
|
-
// Build columns based on --show-modes flag
|
|
1228
|
-
const baseColumns = [
|
|
1229
|
-
{ key: 'type', header: 'Worker', width: 12 },
|
|
1230
|
-
{ key: 'enabled', header: 'On', width: 4 },
|
|
1231
|
-
{ key: 'status', header: 'Status', width: 10 },
|
|
1232
|
-
{ key: 'runs', header: 'Runs', width: 6 },
|
|
1233
|
-
{ key: 'success', header: 'Success', width: 8 },
|
|
1234
|
-
{ key: 'lastRun', header: 'Last Run', width: 12 },
|
|
1235
|
-
{ key: 'nextRun', header: 'Next Run', width: 12 },
|
|
1236
|
-
];
|
|
1237
|
-
const modeColumns = showModes ? [
|
|
1238
|
-
{ key: 'mode', header: 'Mode', width: 10 },
|
|
1239
|
-
{ key: 'sandbox', header: 'Sandbox', width: 12 },
|
|
1240
|
-
] : [];
|
|
1241
|
-
output.printTable({
|
|
1242
|
-
columns: [...baseColumns, ...modeColumns],
|
|
1243
|
-
data: workerData,
|
|
1244
|
-
});
|
|
1245
|
-
if (verbose) {
|
|
1246
|
-
output.writeln();
|
|
1247
|
-
output.writeln(output.bold('Worker Configuration'));
|
|
1248
|
-
output.printTable({
|
|
1249
|
-
columns: [
|
|
1250
|
-
{ key: 'type', header: 'Worker', width: 12 },
|
|
1251
|
-
{ key: 'interval', header: 'Interval', width: 10 },
|
|
1252
|
-
{ key: 'priority', header: 'Priority', width: 10 },
|
|
1253
|
-
{ key: 'avgDuration', header: 'Avg Duration', width: 12 },
|
|
1254
|
-
{ key: 'description', header: 'Description', width: 30 },
|
|
1255
|
-
],
|
|
1256
|
-
data: status.config.workers.map(w => {
|
|
1257
|
-
const state = status.workers.get(w.type);
|
|
1258
|
-
return {
|
|
1259
|
-
type: w.type,
|
|
1260
|
-
interval: `${Math.round(w.intervalMs / 60000)}min`,
|
|
1261
|
-
priority: w.priority,
|
|
1262
|
-
avgDuration: state?.averageDurationMs ? `${Math.round(state.averageDurationMs)}ms` : '-',
|
|
1263
|
-
description: w.description,
|
|
1264
|
-
};
|
|
1265
|
-
}),
|
|
1266
|
-
});
|
|
1267
|
-
}
|
|
1268
|
-
return { success: true, data: status };
|
|
1269
|
-
}
|
|
1270
|
-
catch (error) {
|
|
1271
|
-
// Daemon not initialized
|
|
1272
|
-
output.writeln();
|
|
1273
|
-
output.printBox([
|
|
1274
|
-
`Status: ${output.error('○')} ${output.error('NOT INITIALIZED')}`,
|
|
1275
|
-
'',
|
|
1276
|
-
'Run "claude-flow daemon start" to start the daemon',
|
|
1277
|
-
].join('\n'), 'RuFlo Daemon');
|
|
1278
|
-
return { success: true };
|
|
1279
|
-
}
|
|
1280
|
-
},
|
|
1281
|
-
};
|
|
1282
|
-
// Trigger subcommand - manually run a worker
|
|
1283
|
-
const triggerCommand = {
|
|
1284
|
-
name: 'trigger',
|
|
1285
|
-
description: 'Manually trigger a specific worker',
|
|
1286
|
-
options: [
|
|
1287
|
-
{ name: 'worker', short: 'w', type: 'string', description: 'Worker type to trigger', required: true },
|
|
1288
|
-
{ name: 'headless', type: 'boolean', description: 'Run triggered worker in headless mode (E2B sandbox)' },
|
|
1289
|
-
],
|
|
1290
|
-
examples: [
|
|
1291
|
-
{ command: 'claude-flow daemon trigger -w map', description: 'Trigger the map worker' },
|
|
1292
|
-
{ command: 'claude-flow daemon trigger -w audit', description: 'Trigger security audit' },
|
|
1293
|
-
{ command: 'claude-flow daemon trigger -w audit --headless', description: 'Trigger audit in headless sandbox' },
|
|
1294
|
-
],
|
|
1295
|
-
action: async (ctx) => {
|
|
1296
|
-
const workerType = ctx.flags.worker;
|
|
1297
|
-
if (!workerType) {
|
|
1298
|
-
output.printError('Worker type is required. Use --worker or -w flag.');
|
|
1299
|
-
output.writeln();
|
|
1300
|
-
output.writeln('Available workers: map, audit, optimize, consolidate, testgaps, predict, document, ultralearn, refactor, benchmark, deepdive, preload');
|
|
1301
|
-
return { success: false, exitCode: 1 };
|
|
1302
|
-
}
|
|
1303
|
-
try {
|
|
1304
|
-
// #2661: an explicit `trigger --headless` is user consent for AI
|
|
1305
|
-
// execution of THIS run (still governed by the global AI budget).
|
|
1306
|
-
// Without the flag, config.json / env opt-in still applies.
|
|
1307
|
-
const daemon = getDaemon(process.cwd(), ctx.flags.headless === true ? { aiWorkersEnabled: true } : undefined);
|
|
1308
|
-
const spinner = output.createSpinner({ text: `Running ${workerType} worker...`, spinner: 'dots' });
|
|
1309
|
-
spinner.start();
|
|
1310
|
-
const result = await daemon.triggerWorker(workerType);
|
|
1311
|
-
if (result.success) {
|
|
1312
|
-
spinner.succeed(`Worker ${workerType} completed in ${result.durationMs}ms`);
|
|
1313
|
-
if (result.output) {
|
|
1314
|
-
output.writeln();
|
|
1315
|
-
output.writeln(output.bold('Output'));
|
|
1316
|
-
output.printJson(result.output);
|
|
1317
|
-
}
|
|
1318
|
-
}
|
|
1319
|
-
else {
|
|
1320
|
-
spinner.fail(`Worker ${workerType} failed: ${result.error}`);
|
|
1321
|
-
}
|
|
1322
|
-
return { success: result.success, data: result };
|
|
1323
|
-
}
|
|
1324
|
-
catch (error) {
|
|
1325
|
-
output.printError(`Failed to trigger worker: ${error instanceof Error ? error.message : String(error)}`);
|
|
1326
|
-
return { success: false, exitCode: 1 };
|
|
1327
|
-
}
|
|
1328
|
-
},
|
|
1329
|
-
};
|
|
1330
|
-
// Enable/disable worker subcommand
|
|
1331
|
-
const enableCommand = {
|
|
1332
|
-
name: 'enable',
|
|
1333
|
-
description: 'Enable or disable a specific worker',
|
|
1334
|
-
options: [
|
|
1335
|
-
{ name: 'worker', short: 'w', type: 'string', description: 'Worker type', required: true },
|
|
1336
|
-
{ name: 'disable', short: 'd', type: 'boolean', description: 'Disable instead of enable' },
|
|
1337
|
-
],
|
|
1338
|
-
examples: [
|
|
1339
|
-
{ command: 'claude-flow daemon enable -w predict', description: 'Enable predict worker' },
|
|
1340
|
-
{ command: 'claude-flow daemon enable -w document --disable', description: 'Disable document worker' },
|
|
1341
|
-
],
|
|
1342
|
-
action: async (ctx) => {
|
|
1343
|
-
const workerType = ctx.flags.worker;
|
|
1344
|
-
const disable = ctx.flags.disable;
|
|
1345
|
-
if (!workerType) {
|
|
1346
|
-
output.printError('Worker type is required. Use --worker or -w flag.');
|
|
1347
|
-
return { success: false, exitCode: 1 };
|
|
1348
|
-
}
|
|
1349
|
-
try {
|
|
1350
|
-
const daemon = getDaemon(process.cwd());
|
|
1351
|
-
daemon.setWorkerEnabled(workerType, !disable);
|
|
1352
|
-
output.printSuccess(`Worker ${workerType} ${disable ? 'disabled' : 'enabled'}`);
|
|
1353
|
-
return { success: true };
|
|
1354
|
-
}
|
|
1355
|
-
catch (error) {
|
|
1356
|
-
output.printError(`Failed to ${disable ? 'disable' : 'enable'} worker: ${error instanceof Error ? error.message : String(error)}`);
|
|
1357
|
-
return { success: false, exitCode: 1 };
|
|
1358
|
-
}
|
|
1359
|
-
},
|
|
1360
|
-
};
|
|
1361
|
-
// Helper functions for time formatting
|
|
1362
|
-
function formatTimeAgo(date) {
|
|
1363
|
-
const seconds = Math.floor((Date.now() - date.getTime()) / 1000);
|
|
1364
|
-
if (seconds < 60)
|
|
1365
|
-
return `${seconds}s ago`;
|
|
1366
|
-
if (seconds < 3600)
|
|
1367
|
-
return `${Math.floor(seconds / 60)}m ago`;
|
|
1368
|
-
if (seconds < 86400)
|
|
1369
|
-
return `${Math.floor(seconds / 3600)}h ago`;
|
|
1370
|
-
return `${Math.floor(seconds / 86400)}d ago`;
|
|
1371
|
-
}
|
|
1372
|
-
function formatTimeUntil(date) {
|
|
1373
|
-
const seconds = Math.floor((date.getTime() - Date.now()) / 1000);
|
|
1374
|
-
if (seconds < 0)
|
|
1375
|
-
return 'now';
|
|
1376
|
-
if (seconds < 60)
|
|
1377
|
-
return `in ${seconds}s`;
|
|
1378
|
-
if (seconds < 3600)
|
|
1379
|
-
return `in ${Math.floor(seconds / 60)}m`;
|
|
1380
|
-
if (seconds < 86400)
|
|
1381
|
-
return `in ${Math.floor(seconds / 3600)}h`;
|
|
1382
|
-
return `in ${Math.floor(seconds / 86400)}d`;
|
|
1383
|
-
}
|
|
1384
|
-
// #1565: Supervisor installer subcommand. Writes a native auto-restart
|
|
1385
|
-
// unit (launchd plist on macOS, systemd-user .service on Linux) so the
|
|
1386
|
-
// daemon survives crashes and reboots without requiring the operator
|
|
1387
|
-
// to manually run `daemon start` after every failure.
|
|
1388
|
-
const installSupervisorCommand = {
|
|
1389
|
-
name: 'install-supervisor',
|
|
1390
|
-
description: 'Install OS-level auto-restart supervisor (launchd on macOS, systemd-user on Linux)',
|
|
1391
|
-
options: [
|
|
1392
|
-
{ name: 'force', short: 'f', type: 'boolean', description: 'Overwrite existing unit file', default: 'false' },
|
|
1393
|
-
{ name: 'load', type: 'boolean', description: 'Load/enable the unit immediately', default: 'true' },
|
|
1394
|
-
{ name: 'dry-run', type: 'boolean', description: 'Print the unit file content without writing', default: 'false' },
|
|
1395
|
-
],
|
|
1396
|
-
examples: [
|
|
1397
|
-
{ command: 'claude-flow daemon install-supervisor', description: 'Install + load (auto-restart enabled)' },
|
|
1398
|
-
{ command: 'claude-flow daemon install-supervisor --no-load', description: 'Write unit file but do not enable yet' },
|
|
1399
|
-
{ command: 'claude-flow daemon install-supervisor --dry-run', description: 'Preview the unit file' },
|
|
1400
|
-
],
|
|
1401
|
-
action: async (ctx) => {
|
|
1402
|
-
const force = ctx.flags.force === true;
|
|
1403
|
-
const load = ctx.flags.load !== false;
|
|
1404
|
-
const dryRun = ctx.flags['dry-run'] === true || ctx.flags.dryRun === true;
|
|
1405
|
-
const projectRoot = process.cwd();
|
|
1406
|
-
const platform = process.platform;
|
|
1407
|
-
if (platform === 'win32') {
|
|
1408
|
-
output.printError('Windows scheduled-task installer is not yet implemented.');
|
|
1409
|
-
output.printInfo('Use Task Scheduler manually, or follow this issue: https://github.com/ruvnet/ruflo/issues/1565');
|
|
1410
|
-
return { success: false, exitCode: 1 };
|
|
1411
|
-
}
|
|
1412
|
-
if (platform !== 'darwin' && platform !== 'linux') {
|
|
1413
|
-
output.printError(`Unsupported platform: ${platform}. Supported: darwin (launchd), linux (systemd-user).`);
|
|
1414
|
-
return { success: false, exitCode: 1 };
|
|
1415
|
-
}
|
|
1416
|
-
// Resolve absolute paths the unit file will reference.
|
|
1417
|
-
const home = process.env.HOME ?? process.env.USERPROFILE ?? '';
|
|
1418
|
-
if (!home) {
|
|
1419
|
-
output.printError('HOME/USERPROFILE not set; cannot resolve user unit path.');
|
|
1420
|
-
return { success: false, exitCode: 1 };
|
|
1421
|
-
}
|
|
1422
|
-
const nodeBin = process.execPath;
|
|
1423
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
1424
|
-
const __dirname = dirname(__filename);
|
|
1425
|
-
const cliJs = resolve(join(__dirname, '..', '..', '..', 'bin', 'cli.js'));
|
|
1426
|
-
if (!fs.existsSync(cliJs)) {
|
|
1427
|
-
output.printError(`CLI not found at: ${cliJs}`);
|
|
1428
|
-
return { success: false, exitCode: 1 };
|
|
1429
|
-
}
|
|
1430
|
-
if (platform === 'darwin') {
|
|
1431
|
-
const plistDir = join(home, 'Library', 'LaunchAgents');
|
|
1432
|
-
const plistPath = join(plistDir, 'io.ruv.ruflo.daemon.plist');
|
|
1433
|
-
const logDir = join(projectRoot, '.claude-flow', 'logs');
|
|
1434
|
-
const plist = `<?xml version="1.0" encoding="UTF-8"?>
|
|
1435
|
-
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
1436
|
-
<plist version="1.0">
|
|
1437
|
-
<dict>
|
|
1438
|
-
<key>Label</key><string>io.ruv.ruflo.daemon</string>
|
|
1439
|
-
<key>ProgramArguments</key>
|
|
1440
|
-
<array>
|
|
1441
|
-
<string>${nodeBin}</string>
|
|
1442
|
-
<string>${cliJs}</string>
|
|
1443
|
-
<string>daemon</string><string>start</string><string>--foreground</string><string>--quiet</string>
|
|
1444
|
-
</array>
|
|
1445
|
-
<key>WorkingDirectory</key><string>${projectRoot}</string>
|
|
1446
|
-
<key>RunAtLoad</key><true/>
|
|
1447
|
-
<key>KeepAlive</key>
|
|
1448
|
-
<dict>
|
|
1449
|
-
<key>SuccessfulExit</key><false/>
|
|
1450
|
-
<key>Crashed</key><true/>
|
|
1451
|
-
</dict>
|
|
1452
|
-
<key>ThrottleInterval</key><integer>10</integer>
|
|
1453
|
-
<key>StandardOutPath</key><string>${logDir}/supervisor.out.log</string>
|
|
1454
|
-
<key>StandardErrorPath</key><string>${logDir}/supervisor.err.log</string>
|
|
1455
|
-
<key>EnvironmentVariables</key>
|
|
1456
|
-
<dict>
|
|
1457
|
-
<key>CLAUDE_FLOW_DAEMON</key><string>1</string>
|
|
1458
|
-
</dict>
|
|
1459
|
-
</dict>
|
|
1460
|
-
</plist>
|
|
1461
|
-
`;
|
|
1462
|
-
if (dryRun) {
|
|
1463
|
-
output.writeln(plist);
|
|
1464
|
-
return { success: true };
|
|
1465
|
-
}
|
|
1466
|
-
if (fs.existsSync(plistPath) && !force) {
|
|
1467
|
-
output.printWarning(`Already installed: ${plistPath}`);
|
|
1468
|
-
output.printInfo('Use --force to overwrite.');
|
|
1469
|
-
return { success: false, exitCode: 1 };
|
|
1470
|
-
}
|
|
1471
|
-
if (!fs.existsSync(plistDir))
|
|
1472
|
-
fs.mkdirSync(plistDir, { recursive: true });
|
|
1473
|
-
if (!fs.existsSync(logDir))
|
|
1474
|
-
fs.mkdirSync(logDir, { recursive: true });
|
|
1475
|
-
fs.writeFileSync(plistPath, plist, 'utf-8');
|
|
1476
|
-
output.printSuccess(`Wrote ${plistPath}`);
|
|
1477
|
-
if (load) {
|
|
1478
|
-
try {
|
|
1479
|
-
const { execFileSync } = await import('child_process');
|
|
1480
|
-
// unload first in case a previous version is loaded
|
|
1481
|
-
try {
|
|
1482
|
-
execFileSync('launchctl', ['unload', plistPath], { encoding: 'utf-8', timeout: 5000 });
|
|
1483
|
-
}
|
|
1484
|
-
catch { /* ok */ }
|
|
1485
|
-
execFileSync('launchctl', ['load', '-w', plistPath], { encoding: 'utf-8', timeout: 5000 });
|
|
1486
|
-
output.printSuccess('Supervisor loaded — daemon will auto-restart on crash and survive reboot.');
|
|
1487
|
-
}
|
|
1488
|
-
catch (err) {
|
|
1489
|
-
output.printWarning(`launchctl load failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1490
|
-
output.printInfo(`Run manually: launchctl load -w ${plistPath}`);
|
|
1491
|
-
}
|
|
1492
|
-
}
|
|
1493
|
-
else {
|
|
1494
|
-
output.printInfo(`Run when ready: launchctl load -w ${plistPath}`);
|
|
1495
|
-
}
|
|
1496
|
-
return { success: true };
|
|
1497
|
-
}
|
|
1498
|
-
// Linux: systemd-user
|
|
1499
|
-
const unitDir = join(home, '.config', 'systemd', 'user');
|
|
1500
|
-
const unitPath = join(unitDir, 'ruflo-daemon.service');
|
|
1501
|
-
const unit = `[Unit]
|
|
1502
|
-
Description=RuFlo background worker daemon
|
|
1503
|
-
After=default.target
|
|
1504
|
-
|
|
1505
|
-
[Service]
|
|
1506
|
-
Type=simple
|
|
1507
|
-
WorkingDirectory=${projectRoot}
|
|
1508
|
-
Environment=CLAUDE_FLOW_DAEMON=1
|
|
1509
|
-
ExecStart=${nodeBin} ${cliJs} daemon start --foreground --quiet
|
|
1510
|
-
Restart=on-failure
|
|
1511
|
-
RestartSec=10
|
|
1512
|
-
# Restart on Crashed (signal) too
|
|
1513
|
-
StartLimitIntervalSec=300
|
|
1514
|
-
StartLimitBurst=5
|
|
1515
|
-
|
|
1516
|
-
[Install]
|
|
1517
|
-
WantedBy=default.target
|
|
1518
|
-
`;
|
|
1519
|
-
if (dryRun) {
|
|
1520
|
-
output.writeln(unit);
|
|
1521
|
-
return { success: true };
|
|
1522
|
-
}
|
|
1523
|
-
if (fs.existsSync(unitPath) && !force) {
|
|
1524
|
-
output.printWarning(`Already installed: ${unitPath}`);
|
|
1525
|
-
output.printInfo('Use --force to overwrite.');
|
|
1526
|
-
return { success: false, exitCode: 1 };
|
|
1527
|
-
}
|
|
1528
|
-
if (!fs.existsSync(unitDir))
|
|
1529
|
-
fs.mkdirSync(unitDir, { recursive: true });
|
|
1530
|
-
fs.writeFileSync(unitPath, unit, 'utf-8');
|
|
1531
|
-
output.printSuccess(`Wrote ${unitPath}`);
|
|
1532
|
-
if (load) {
|
|
1533
|
-
try {
|
|
1534
|
-
const { execFileSync } = await import('child_process');
|
|
1535
|
-
execFileSync('systemctl', ['--user', 'daemon-reload'], { encoding: 'utf-8', timeout: 5000 });
|
|
1536
|
-
execFileSync('systemctl', ['--user', 'enable', '--now', 'ruflo-daemon.service'], { encoding: 'utf-8', timeout: 10000 });
|
|
1537
|
-
output.printSuccess('Supervisor enabled — daemon will auto-restart on crash and survive reboot.');
|
|
1538
|
-
output.printInfo('Note: requires `loginctl enable-linger $USER` for restart-after-logout on some distros.');
|
|
1539
|
-
}
|
|
1540
|
-
catch (err) {
|
|
1541
|
-
output.printWarning(`systemctl --user enable failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1542
|
-
output.printInfo(`Run manually: systemctl --user daemon-reload && systemctl --user enable --now ruflo-daemon.service`);
|
|
1543
|
-
}
|
|
1544
|
-
}
|
|
1545
|
-
else {
|
|
1546
|
-
output.printInfo(`Run when ready: systemctl --user daemon-reload && systemctl --user enable --now ruflo-daemon.service`);
|
|
1547
|
-
}
|
|
1548
|
-
return { success: true };
|
|
1549
|
-
},
|
|
1550
|
-
};
|
|
1551
|
-
const uninstallSupervisorCommand = {
|
|
1552
|
-
name: 'uninstall-supervisor',
|
|
1553
|
-
description: 'Remove the auto-restart supervisor unit (launchd on macOS, systemd-user on Linux)',
|
|
1554
|
-
options: [],
|
|
1555
|
-
action: async () => {
|
|
1556
|
-
const platform = process.platform;
|
|
1557
|
-
const home = process.env.HOME ?? process.env.USERPROFILE ?? '';
|
|
1558
|
-
if (platform === 'darwin') {
|
|
1559
|
-
const plistPath = join(home, 'Library', 'LaunchAgents', 'io.ruv.ruflo.daemon.plist');
|
|
1560
|
-
try {
|
|
1561
|
-
const { execFileSync } = await import('child_process');
|
|
1562
|
-
try {
|
|
1563
|
-
execFileSync('launchctl', ['unload', plistPath], { encoding: 'utf-8', timeout: 5000 });
|
|
1564
|
-
}
|
|
1565
|
-
catch { /* ok */ }
|
|
1566
|
-
}
|
|
1567
|
-
catch { /* ignore */ }
|
|
1568
|
-
if (fs.existsSync(plistPath)) {
|
|
1569
|
-
fs.unlinkSync(plistPath);
|
|
1570
|
-
output.printSuccess(`Removed ${plistPath}`);
|
|
1571
|
-
}
|
|
1572
|
-
else {
|
|
1573
|
-
output.printInfo(`Not installed: ${plistPath}`);
|
|
1574
|
-
}
|
|
1575
|
-
return { success: true };
|
|
1576
|
-
}
|
|
1577
|
-
if (platform === 'linux') {
|
|
1578
|
-
const unitPath = join(home, '.config', 'systemd', 'user', 'ruflo-daemon.service');
|
|
1579
|
-
try {
|
|
1580
|
-
const { execFileSync } = await import('child_process');
|
|
1581
|
-
try {
|
|
1582
|
-
execFileSync('systemctl', ['--user', 'disable', '--now', 'ruflo-daemon.service'], { encoding: 'utf-8', timeout: 5000 });
|
|
1583
|
-
}
|
|
1584
|
-
catch { /* ok */ }
|
|
1585
|
-
}
|
|
1586
|
-
catch { /* ignore */ }
|
|
1587
|
-
if (fs.existsSync(unitPath)) {
|
|
1588
|
-
fs.unlinkSync(unitPath);
|
|
1589
|
-
output.printSuccess(`Removed ${unitPath}`);
|
|
1590
|
-
}
|
|
1591
|
-
else {
|
|
1592
|
-
output.printInfo(`Not installed: ${unitPath}`);
|
|
1593
|
-
}
|
|
1594
|
-
return { success: true };
|
|
1595
|
-
}
|
|
1596
|
-
output.printError(`Unsupported platform: ${platform}`);
|
|
1597
|
-
return { success: false, exitCode: 1 };
|
|
1598
|
-
},
|
|
1599
|
-
};
|
|
1600
|
-
// #2661 root-fix — `daemon budget show|pause|resume`. The budget state was
|
|
1601
|
-
// previously visible only inline in `daemon status --all`; these give it an
|
|
1602
|
-
// independently scriptable surface (e.g. `ruflo daemon budget pause` before
|
|
1603
|
-
// a long interactive session, `... resume` after).
|
|
1604
|
-
const budgetShowCommand = {
|
|
1605
|
-
name: 'show',
|
|
1606
|
-
description: 'Show the user-global AI launch budget (launches, active children, circuit-breaker state)',
|
|
1607
|
-
options: [],
|
|
1608
|
-
examples: [{ command: 'claude-flow daemon budget show', description: 'Show current budget usage and limits' }],
|
|
1609
|
-
action: async () => {
|
|
1610
|
-
const { getGlobalAiBudget } = await import('../services/global-ai-budget.js');
|
|
1611
|
-
const budget = getGlobalAiBudget();
|
|
1612
|
-
const usage = budget.getUsage();
|
|
1613
|
-
const limits = budget.getLimits();
|
|
1614
|
-
output.writeln();
|
|
1615
|
-
const byWs = usage.byWorkspace.slice(0, 10).map((w) => ` ${w.launches}× ${w.workspace}`);
|
|
1616
|
-
output.printBox([
|
|
1617
|
-
`Launches (last hour): ${usage.lastHour}/${limits.maxLaunchesPerHour}`,
|
|
1618
|
-
`Launches (last 24h): ${usage.lastDay}/${limits.maxLaunchesPerDay}`,
|
|
1619
|
-
`Active Claude children: ${usage.active}/${limits.maxConcurrentGlobal}`,
|
|
1620
|
-
usage.pausedUntil
|
|
1621
|
-
? output.warning(`PAUSED until ${new Date(usage.pausedUntil).toISOString()} (${usage.pauseReason ?? 'quota error'})`)
|
|
1622
|
-
: `Circuit breaker: ${output.dim('closed (normal)')}`,
|
|
1623
|
-
...(byWs.length > 0 ? ['Launches by workspace (24h):', ...byWs] : []),
|
|
1624
|
-
].join('\n'), 'Global AI Budget');
|
|
1625
|
-
return { success: true, data: { usage, limits } };
|
|
1626
|
-
},
|
|
1627
|
-
};
|
|
1628
|
-
const budgetPauseCommand = {
|
|
1629
|
-
name: 'pause',
|
|
1630
|
-
description: 'Pause ALL autonomous Claude launches across every daemon until resumed',
|
|
1631
|
-
options: [
|
|
1632
|
-
{ name: 'reason', short: 'r', type: 'string', description: 'Optional reason recorded in the pause receipt' },
|
|
1633
|
-
],
|
|
1634
|
-
examples: [{ command: 'claude-flow daemon budget pause --reason "conserving quota for a demo"', description: 'Pause autonomous launches' }],
|
|
1635
|
-
action: async (ctx) => {
|
|
1636
|
-
const { getGlobalAiBudget } = await import('../services/global-ai-budget.js');
|
|
1637
|
-
await getGlobalAiBudget().pause(ctx.flags.reason);
|
|
1638
|
-
output.printSuccess('Autonomous AI worker launches paused across all daemons. Resume with: ruflo daemon budget resume');
|
|
1639
|
-
return { success: true };
|
|
1640
|
-
},
|
|
1641
|
-
};
|
|
1642
|
-
const budgetResumeCommand = {
|
|
1643
|
-
name: 'resume',
|
|
1644
|
-
description: 'Resume autonomous Claude launches (clears a manual pause or a quota-triggered circuit-breaker pause)',
|
|
1645
|
-
options: [],
|
|
1646
|
-
examples: [{ command: 'claude-flow daemon budget resume', description: 'Resume autonomous launches' }],
|
|
1647
|
-
action: async () => {
|
|
1648
|
-
const { getGlobalAiBudget } = await import('../services/global-ai-budget.js');
|
|
1649
|
-
await getGlobalAiBudget().resume();
|
|
1650
|
-
output.printSuccess('Autonomous AI worker launches resumed.');
|
|
1651
|
-
return { success: true };
|
|
1652
|
-
},
|
|
1653
|
-
};
|
|
1654
|
-
const budgetCommand = {
|
|
1655
|
-
name: 'budget',
|
|
1656
|
-
description: 'Inspect and control the user-global AI launch budget (#2661)',
|
|
1657
|
-
subcommands: [budgetShowCommand, budgetPauseCommand, budgetResumeCommand],
|
|
1658
|
-
options: [],
|
|
1659
|
-
examples: [
|
|
1660
|
-
{ command: 'claude-flow daemon budget show', description: 'Show current usage/limits' },
|
|
1661
|
-
{ command: 'claude-flow daemon budget pause', description: 'Pause all autonomous launches' },
|
|
1662
|
-
{ command: 'claude-flow daemon budget resume', description: 'Resume autonomous launches' },
|
|
1663
|
-
],
|
|
1664
|
-
// Bare `daemon budget` (no subcommand) shows usage — same as `show`.
|
|
1665
|
-
action: async (ctx) => {
|
|
1666
|
-
const result = await budgetShowCommand.action(ctx);
|
|
1667
|
-
return result ?? { success: true };
|
|
1668
|
-
},
|
|
1669
|
-
};
|
|
1670
|
-
// Main daemon command
|
|
1671
|
-
export const daemonCommand = {
|
|
1672
|
-
name: 'daemon',
|
|
1673
|
-
description: 'Manage background worker daemon (Node.js-based, auto-runs like shell helpers)',
|
|
1674
|
-
subcommands: [
|
|
1675
|
-
startCommand,
|
|
1676
|
-
stopCommand,
|
|
1677
|
-
statusCommand,
|
|
1678
|
-
triggerCommand,
|
|
1679
|
-
enableCommand,
|
|
1680
|
-
budgetCommand,
|
|
1681
|
-
installSupervisorCommand,
|
|
1682
|
-
uninstallSupervisorCommand,
|
|
1683
|
-
],
|
|
1684
|
-
options: [],
|
|
1685
|
-
examples: [
|
|
1686
|
-
{ command: 'claude-flow daemon start', description: 'Start the daemon' },
|
|
1687
|
-
{ command: 'claude-flow daemon start --headless', description: 'Start with headless workers (E2B sandbox)' },
|
|
1688
|
-
{ command: 'claude-flow daemon status', description: 'Check daemon status' },
|
|
1689
|
-
{ command: 'claude-flow daemon stop', description: 'Stop the daemon' },
|
|
1690
|
-
{ command: 'claude-flow daemon trigger -w audit', description: 'Run security audit' },
|
|
1691
|
-
],
|
|
1692
|
-
action: async () => {
|
|
1693
|
-
output.writeln();
|
|
1694
|
-
output.writeln(output.bold('RuFlo Daemon - Background Task Management'));
|
|
1695
|
-
output.writeln();
|
|
1696
|
-
output.writeln('Node.js-based background worker system that auto-runs like shell daemons.');
|
|
1697
|
-
output.writeln('Manages 12 specialized workers for continuous optimization and monitoring.');
|
|
1698
|
-
output.writeln();
|
|
1699
|
-
output.writeln(output.bold('Headless Mode'));
|
|
1700
|
-
output.writeln('Workers can run in headless mode using E2B sandboxes for isolated execution.');
|
|
1701
|
-
output.writeln('Use --headless flag with start/trigger commands. Sandbox modes: strict, permissive, disabled.');
|
|
1702
|
-
output.writeln();
|
|
1703
|
-
output.writeln(output.bold('Available Workers'));
|
|
1704
|
-
output.printList([
|
|
1705
|
-
`${output.highlight('map')} - Codebase mapping (5 min interval)`,
|
|
1706
|
-
`${output.highlight('audit')} - Security analysis (10 min interval)`,
|
|
1707
|
-
`${output.highlight('optimize')} - Performance optimization (15 min interval)`,
|
|
1708
|
-
`${output.highlight('consolidate')} - Memory distillation: memory_entries -> episodes/reasoning_patterns/causal_edges (30 min interval, ADR-174; --no-distill to disable)`,
|
|
1709
|
-
`${output.highlight('testgaps')} - Test coverage analysis (20 min interval)`,
|
|
1710
|
-
`${output.highlight('predict')} - Predictive preloading (2 min, disabled by default)`,
|
|
1711
|
-
`${output.highlight('document')} - Auto-documentation (60 min, disabled by default)`,
|
|
1712
|
-
`${output.highlight('ultralearn')} - Deep knowledge acquisition (manual trigger)`,
|
|
1713
|
-
`${output.highlight('refactor')} - Code refactoring suggestions (manual trigger)`,
|
|
1714
|
-
`${output.highlight('benchmark')} - Performance benchmarking (manual trigger)`,
|
|
1715
|
-
`${output.highlight('deepdive')} - Deep code analysis (manual trigger)`,
|
|
1716
|
-
`${output.highlight('preload')} - Resource preloading (manual trigger)`,
|
|
1717
|
-
]);
|
|
1718
|
-
output.writeln();
|
|
1719
|
-
output.writeln(output.bold('Subcommands'));
|
|
1720
|
-
output.printList([
|
|
1721
|
-
`${output.highlight('start')} - Start the daemon`,
|
|
1722
|
-
`${output.highlight('stop')} - Stop the daemon`,
|
|
1723
|
-
`${output.highlight('status')} - Show daemon status`,
|
|
1724
|
-
`${output.highlight('trigger')} - Manually run a worker`,
|
|
1725
|
-
`${output.highlight('enable')} - Enable/disable a worker`,
|
|
1726
|
-
]);
|
|
1727
|
-
output.writeln();
|
|
1728
|
-
output.writeln('Run "claude-flow daemon <subcommand> --help" for details');
|
|
1729
|
-
return { success: true };
|
|
1730
|
-
},
|
|
1731
|
-
};
|
|
1732
|
-
export default daemonCommand;
|
|
1733
|
-
//# sourceMappingURL=daemon.js.map
|