claude-flow
Version:
Ruflo - Enterprise AI agent orchestration for Claude Code. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration
335 lines • 11.8 kB
TypeScript
/**
* Worker Daemon Service
* Node.js-based background worker system that auto-runs like shell daemons
*
* Workers:
* - map: Codebase mapping (5 min interval)
* - audit: Security analysis (10 min interval)
* - optimize: Performance optimization (15 min interval)
* - consolidate: Memory consolidation (30 min interval)
* - testgaps: Test coverage analysis (20 min interval)
*/
import { EventEmitter } from 'events';
import { HeadlessWorkerExecutor } from './headless-worker-executor.js';
export type WorkerType = 'ultralearn' | 'optimize' | 'consolidate' | 'predict' | 'audit' | 'map' | 'preload' | 'deepdive' | 'document' | 'refactor' | 'benchmark' | 'testgaps';
interface WorkerConfig {
type: WorkerType;
intervalMs: number;
priority: 'low' | 'normal' | 'high' | 'critical';
description: string;
enabled: boolean;
}
interface WorkerState {
lastRun?: Date;
nextRun?: Date;
runCount: number;
successCount: number;
failureCount: number;
averageDurationMs: number;
isRunning: boolean;
lastStartedAt?: Date;
}
interface WorkerResult {
workerId: string;
type: WorkerType;
success: boolean;
durationMs: number;
output?: unknown;
error?: string;
timestamp: Date;
}
interface DaemonStatus {
running: boolean;
pid: number;
startedAt?: Date;
workers: Map<WorkerType, WorkerState>;
config: DaemonConfig;
}
export interface DaemonConfig {
autoStart: boolean;
logDir: string;
stateFile: string;
maxConcurrent: number;
workerTimeoutMs: number;
resourceThresholds: {
maxCpuLoad: number;
minFreeMemoryPercent: number;
};
workers: WorkerConfig[];
}
/**
* Worker Daemon - Manages background workers with Node.js
*/
export declare class WorkerDaemon extends EventEmitter {
private config;
private workers;
private timers;
private queuePollTimer?;
private running;
private startedAt?;
private projectRoot;
private runningWorkers;
private pendingWorkers;
private headlessExecutor;
private headlessAvailable;
private originalConfig?;
constructor(projectRoot: string, config?: Partial<DaemonConfig>);
/**
* Initialize headless executor if Claude Code is available
*/
private initHeadlessExecutor;
/**
* Check if headless execution is available
*/
isHeadlessAvailable(): boolean;
/**
* Get headless executor instance
*/
getHeadlessExecutor(): HeadlessWorkerExecutor | null;
/**
* Detect effective CPU count for the current environment.
*
* Inside Docker / K8s containers, os.cpus().length reports the HOST cpu
* count, not the container limit (Node.js #28762 — wontfix). We read
* cgroup v2 / v1 quota files first so the maxCpuLoad threshold stays
* meaningful under resource-limited containers.
*/
/**
* #2110 — detect WSL2 / WSL1 so the CPU-load gate can use a sane
* default. `/proc/loadavg` on WSL maps in Windows-side process counts
* and routinely reports values 100-1000x larger than real Linux load.
*
* Detection order:
* 1. `WSL_DISTRO_NAME` env var (set by Microsoft's WSL launcher)
* 2. `WSL_INTEROP` env var (set by recent WSL2)
* 3. `/proc/sys/kernel/osrelease` contains "microsoft" or "WSL"
* (kernel build marker; survives env stripping)
*/
static isWslEnvironment(): boolean;
static getEffectiveCpuCount(): number;
/**
* Read daemon-specific config from .claude-flow/config.{json,yaml,yml}.
* Supports dot-notation keys like 'daemon.resourceThresholds.maxCpuLoad'.
* #1844: prefer JSON when both exist (existing behavior) but fall back
* to YAML so operators using the v3 canonical YAML format aren't silently
* ignored. The chosen path is logged at info level.
*/
private readDaemonConfigFromFile;
/**
* Setup graceful shutdown handlers
*/
private setupShutdownHandlers;
/**
* #1855: install crash handlers for uncaught exceptions and unhandled
* rejections. Without these, a thrown error from any timer callback,
* worker logic path, or transitive import crashes the daemon process
* silently — the PID file leaks and any in-flight child processes
* orphan. With these, we log a structured crash record, run stop()
* to clean up, then exit 1 so the process actually dies (otherwise
* Node would crash anyway after the handler returns).
*/
private installCrashHandlers;
/**
* Append a structured crash record to .claude-flow/logs/crash.log.
* Inspectable by hand or via `ruflo daemon status` follow-ups.
*/
private writeCrashRecord;
/**
* Path to the on-disk children registry — list of headless worker
* child PIDs the daemon currently owns. #1855: written on every
* execution:start / :complete / :error transition; read by the next
* lifetime to reap orphans after a hard crash.
*/
private get childrenFile();
/**
* #1856: detect workers that were mid-flight when the previous daemon
* lifetime ended. A mid-flight worker has `lastStartedAt > lastRun`
* (started after the last successful completion). On crash recovery
* we count these as failures so the run-counter math stays consistent
* (`runCount === successCount + failureCount`). Workers naturally
* retry at their next scheduled interval; we deliberately don't
* immediately re-run because the failure may have been deterministic.
*/
private detectMidFlightFailures;
/**
* Snapshot the currently-active headless worker child PIDs to disk.
* Best-effort; failures don't propagate.
*/
private writeChildrenSnapshot;
/**
* #1855: reap orphan headless worker children left behind by a
* previous crashed lifetime. Reads `.claude-flow/daemon-children.json`,
* SIGTERMs any PID still alive that doesn't belong to the current
* daemon, then truncates the file. Called at the top of `start()`
* so the next lifetime starts with a clean process tree.
*/
private reapOrphanedChildren;
/**
* Check if system resources allow worker execution
*/
private canRunWorker;
/**
* Process pending workers queue
*
* When executeWorkerWithConcurrencyControl defers a worker (returns null),
* we break immediately to avoid a busy-wait loop — the deferred worker is
* already back on the pendingWorkers queue by that point. If no workers are
* currently running when we break, we schedule a backoff retry so the queue
* does not get permanently stuck.
*/
private processPendingWorkers;
private initializeWorkerStates;
/**
* Get the PID file path for singleton enforcement (#1395 Bug 3).
*/
private get pidFile();
/**
* Check if another daemon instance is already running.
* Returns the existing PID if alive, or null if no daemon is running.
*
* #1853: ignore self-PID matches. The detached-spawn path in
* `commands/daemon.ts` writes the child's PID into the file as a
* fallback after a 500ms wait. If the child reaches `start()` slower
* than the parent's 500ms wait (observed on Node 25 / macOS 26), the
* child reads its own PID back from the file and concludes "another
* daemon is already running" — so it exits before scheduling workers
* and `daemon status` reports STOPPED forever. A daemon process is
* never "another instance" of itself; treat self-match as absence.
*/
private checkExistingDaemon;
/**
* Write PID file for singleton enforcement.
*/
private writePidFile;
/**
* Remove PID file on shutdown.
*/
private removePidFile;
/**
* Start the daemon and all enabled workers
*/
start(): Promise<void>;
/**
* #1845: ingest queue entries written by mcp__hooks_worker-dispatch.
* Each entry is a JSON file at `.claude-flow/daemon-queue/<id>.json`
* with `{ workerId, trigger, context, enqueuedAt }`. We move processed
* files to `.claude-flow/daemon-queue/.processed/` so the daemon never
* re-runs the same dispatch and operators can inspect history.
*/
private processDispatchQueue;
/**
* Stop the daemon and all workers
*/
stop(): Promise<void>;
/**
* Get daemon status
*/
getStatus(): DaemonStatus;
/**
* Schedule a worker to run at intervals with staggered start
*/
private scheduleWorker;
/**
* Execute a worker with concurrency control (P0 fix)
*/
private executeWorkerWithConcurrencyControl;
/**
* Execute a worker with timeout protection
*/
private executeWorker;
/**
* Run a function with timeout (P1 fix)
* @param fn - The async function to execute
* @param timeoutMs - Timeout in milliseconds
* @param timeoutMessage - Error message on timeout
* @param onTimeout - Optional cleanup callback invoked when timeout fires (#1117: kills orphan processes)
*/
private runWithTimeout;
/**
* Run the actual worker logic
*/
private runWorkerLogic;
/**
* #1793: persist a headless worker result to the same metrics file the
* local fallback writes to. Without this, AI-mode workers produced rich
* structured output (audit findings, perf signals, test-gap analysis)
* that lived only in `.claude-flow/logs/headless/*_result.log` and was
* invisible to `npx ruflo memory stats` or the metrics consumers.
*
* The mapping mirrors the `*Local` worker implementations below so a
* single consumer path works regardless of execution mode.
*/
private persistHeadlessResult;
private runMapWorker;
/**
* Local audit worker (fallback when headless unavailable)
*/
private runAuditWorkerLocal;
/**
* Local optimize worker (fallback when headless unavailable)
*/
private runOptimizeWorkerLocal;
private runConsolidateWorker;
/**
* Local testgaps worker (fallback when headless unavailable)
*/
private runTestGapsWorkerLocal;
/**
* Local predict worker (fallback when headless unavailable)
*/
private runPredictWorkerLocal;
/**
* Local document worker (fallback when headless unavailable)
*/
private runDocumentWorkerLocal;
/**
* Local ultralearn worker (fallback when headless unavailable)
*/
private runUltralearnWorkerLocal;
/**
* Local refactor worker (fallback when headless unavailable)
*/
private runRefactorWorkerLocal;
/**
* Local deepdive worker (fallback when headless unavailable)
*/
private runDeepdiveWorkerLocal;
/**
* Local benchmark worker
*/
private runBenchmarkWorkerLocal;
/**
* Local preload worker
*/
private runPreloadWorkerLocal;
/**
* Manually trigger a worker
*/
triggerWorker(type: WorkerType): Promise<WorkerResult>;
/**
* Enable/disable a worker
*/
setWorkerEnabled(type: WorkerType, enabled: boolean): void;
/**
* Save daemon state to file
*/
private saveState;
/**
* Log message
*/
private log;
}
/**
* Get or create daemon instance
*/
export declare function getDaemon(projectRoot?: string, config?: Partial<DaemonConfig>): WorkerDaemon;
/**
* Start daemon (for use in session-start hook)
*/
export declare function startDaemon(projectRoot: string, config?: Partial<DaemonConfig>): Promise<WorkerDaemon>;
/**
* Stop daemon
*/
export declare function stopDaemon(): Promise<void>;
export default WorkerDaemon;
//# sourceMappingURL=worker-daemon.d.ts.map