Refactor worker management and cleanup hooks

- Removed ensureWorkerRunning calls from multiple hooks (cleanup, context, new, save, summary) to streamline code and avoid unnecessary checks.
- Introduced fixed port usage for worker communication across hooks.
- Enhanced error handling in newHook, saveHook, and summaryHook to provide clearer messages for worker connection issues.
- Updated worker service to start without health checks, relying on PM2 for management.
- Cached Claude executable path to optimize repeated calls.
- Improved logging for better traceability of worker actions and errors.
This commit is contained in:
Alex Newman
2025-11-04 14:21:19 -05:00
parent 37f836b719
commit a46a028ddb
25 changed files with 317 additions and 2914 deletions
+11 -94
View File
@@ -1,106 +1,23 @@
import path from 'path';
import { existsSync } from 'fs';
import { spawn } from 'child_process';
import { getPackageRoot } from './paths.js';
const FIXED_PORT = parseInt(process.env.CLAUDE_MEM_WORKER_PORT || '37777', 10);
const HEALTH_CHECK_URL = `http://127.0.0.1:${FIXED_PORT}/health`;
/**
* Check if worker is responding by hitting health endpoint
* Ensure worker service is running
* Just starts PM2 - no health checks, no retries, no delays
* PM2 handles the rest (already running = no-op, failures = exit code)
*/
async function checkWorkerHealth(): Promise<boolean> {
try {
const response = await fetch(HEALTH_CHECK_URL, {
signal: AbortSignal.timeout(500)
});
return response.ok;
} catch {
return false;
}
}
export function ensureWorkerRunning(): void {
const packageRoot = getPackageRoot();
const pm2Path = path.join(packageRoot, 'node_modules', '.bin', 'pm2');
const ecosystemPath = path.join(packageRoot, 'ecosystem.config.cjs');
/**
* Ensure worker service is running with retry logic
* Auto-starts worker if not running (v4.0.0 feature)
*
* @returns true if worker is responding, false if failed to start
*/
export async function ensureWorkerRunning(): Promise<boolean> {
try {
// Check if worker is already responding
if (await checkWorkerHealth()) {
return true;
}
console.error('[claude-mem] Worker not responding, starting...');
// Find worker service path
const packageRoot = getPackageRoot();
const workerPath = path.join(packageRoot, 'plugin', 'scripts', 'worker-service.cjs');
if (!existsSync(workerPath)) {
console.error(`[claude-mem] Worker service not found at ${workerPath}`);
return false;
}
// Start worker with PM2 (bundled dependency)
const ecosystemPath = path.join(packageRoot, 'ecosystem.config.cjs');
const pm2Path = path.join(packageRoot, 'node_modules', '.bin', 'pm2');
// Fail loudly if bundled pm2 is missing
if (!existsSync(pm2Path)) {
throw new Error(
`PM2 binary not found at ${pm2Path}. ` +
`This is a bundled dependency - try running: npm install`
);
}
if (!existsSync(ecosystemPath)) {
throw new Error(
`PM2 ecosystem config not found at ${ecosystemPath}. ` +
`Plugin installation may be corrupted.`
);
}
// Spawn worker with PM2
const proc = spawn(pm2Path, ['start', ecosystemPath], {
detached: true,
stdio: 'ignore',
cwd: packageRoot
});
// Fail loudly on spawn errors
proc.on('error', (err) => {
throw new Error(`Failed to spawn PM2: ${err.message}`);
});
proc.unref();
console.error('[claude-mem] Worker started with PM2');
// Wait for worker to become healthy (retry 3 times with 500ms delay)
for (let i = 0; i < 3; i++) {
await new Promise(resolve => setTimeout(resolve, 500));
if (await checkWorkerHealth()) {
console.error('[claude-mem] Worker is healthy');
return true;
}
}
console.error('[claude-mem] Worker failed to become healthy after startup');
return false;
} catch (error: any) {
console.error(`[claude-mem] Failed to start worker: ${error.message}`);
return false;
}
}
/**
* Check if worker is currently running
*/
export async function isWorkerRunning(): Promise<boolean> {
return checkWorkerHealth();
spawn(pm2Path, ['start', ecosystemPath], {
cwd: packageRoot,
stdio: 'inherit'
});
}
/**