UNPKG

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
/** * 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