f494d3b168
- Introduced SettingsDefaultsManager to centralize default settings and loading logic. - Updated context-generator, SDKAgent, SettingsRoutes, and worker-utils to utilize the new manager for loading settings. - Removed redundant code for reading settings from files and environment variables. - Ensured fallback to default values when settings file is missing or invalid.
109 lines
3.3 KiB
TypeScript
109 lines
3.3 KiB
TypeScript
import path from "path";
|
|
import { homedir } from "os";
|
|
import { spawnSync } from "child_process";
|
|
import { getPackageRoot } from "./paths.js";
|
|
import { SettingsDefaultsManager } from "../services/worker/settings/SettingsDefaultsManager.js";
|
|
|
|
// Named constants for health checks
|
|
const HEALTH_CHECK_TIMEOUT_MS = 100;
|
|
const WORKER_STARTUP_WAIT_MS = 500;
|
|
const WORKER_STARTUP_RETRIES = 10;
|
|
|
|
/**
|
|
* Get the worker port number
|
|
* Priority: ~/.claude-mem/settings.json > env var > default
|
|
*/
|
|
export function getWorkerPort(): number {
|
|
const settingsPath = path.join(homedir(), '.claude-mem', 'settings.json');
|
|
const settings = SettingsDefaultsManager.loadFromFile(settingsPath);
|
|
return parseInt(settings.CLAUDE_MEM_WORKER_PORT, 10);
|
|
}
|
|
|
|
/**
|
|
* Check if worker is responsive by trying the health endpoint
|
|
*/
|
|
async function isWorkerHealthy(): Promise<boolean> {
|
|
try {
|
|
const port = getWorkerPort();
|
|
const response = await fetch(`http://127.0.0.1:${port}/health`, {
|
|
signal: AbortSignal.timeout(HEALTH_CHECK_TIMEOUT_MS)
|
|
});
|
|
return response.ok;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Start the worker using PM2
|
|
*/
|
|
async function startWorker(): Promise<boolean> {
|
|
try {
|
|
// Find the ecosystem config file (built version in plugin/)
|
|
const pluginRoot = getPackageRoot();
|
|
const ecosystemPath = path.join(pluginRoot, 'ecosystem.config.cjs');
|
|
|
|
if (!existsSync(ecosystemPath)) {
|
|
throw new Error(`Ecosystem config not found at ${ecosystemPath}`);
|
|
}
|
|
|
|
// Try to use local PM2 from node_modules first, fall back to global PM2
|
|
// On Windows, PM2 executable is pm2.cmd, not pm2
|
|
const localPm2Base = path.join(pluginRoot, 'node_modules', '.bin', 'pm2');
|
|
const localPm2Cmd = process.platform === 'win32' ? localPm2Base + '.cmd' : localPm2Base;
|
|
const pm2Command = existsSync(localPm2Cmd) ? localPm2Cmd : 'pm2';
|
|
|
|
// Start using PM2 with the ecosystem config
|
|
// CRITICAL: Must set cwd to pluginRoot so PM2 starts from marketplace directory
|
|
// Using spawnSync with array args to avoid command injection risks
|
|
const result = spawnSync(pm2Command, ['start', ecosystemPath], {
|
|
cwd: pluginRoot,
|
|
stdio: 'pipe',
|
|
encoding: 'utf-8',
|
|
windowsHide: true
|
|
});
|
|
if (result.status !== 0) {
|
|
throw new Error(result.stderr || 'PM2 start failed');
|
|
}
|
|
|
|
// Wait for worker to become healthy
|
|
for (let i = 0; i < WORKER_STARTUP_RETRIES; i++) {
|
|
await new Promise(resolve => setTimeout(resolve, WORKER_STARTUP_WAIT_MS));
|
|
if (await isWorkerHealthy()) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
} catch (error) {
|
|
// Failed to start worker
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Ensure worker service is running
|
|
* Checks health and auto-starts if not running
|
|
*/
|
|
export async function ensureWorkerRunning(): Promise<void> {
|
|
// Check if already healthy
|
|
if (await isWorkerHealthy()) {
|
|
return;
|
|
}
|
|
|
|
// Try to start the worker
|
|
const started = await startWorker();
|
|
|
|
if (!started) {
|
|
const port = getWorkerPort();
|
|
const pluginRoot = getPackageRoot();
|
|
throw new Error(
|
|
`Worker service failed to start on port ${port}.\n\n` +
|
|
`To start manually, run:\n` +
|
|
` cd ${pluginRoot}\n` +
|
|
` npx pm2 start ecosystem.config.cjs\n\n` +
|
|
`If already running, try: npx pm2 restart claude-mem-worker`
|
|
);
|
|
}
|
|
}
|