Merge main into feature/gemini-provider
Resolved conflicts to include both: - Main's earliestPendingTimestamp for accurate observation timestamps - PR's conversationHistory and currentProvider for Gemini provider switching 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
export const HOOK_TIMEOUTS = {
|
||||
DEFAULT: 5000, // Standard HTTP timeout (up from 2000ms)
|
||||
DEFAULT: 120000, // Standard HTTP timeout (up from 2000ms)
|
||||
HEALTH_CHECK: 1000, // Worker health check (up from 500ms)
|
||||
WORKER_STARTUP_WAIT: 1000,
|
||||
WORKER_STARTUP_RETRIES: 15,
|
||||
|
||||
+18
-99
@@ -1,10 +1,8 @@
|
||||
import path from "path";
|
||||
import { homedir } from "os";
|
||||
import { spawnSync } from "child_process";
|
||||
import { existsSync, writeFileSync, readFileSync, mkdirSync } from "fs";
|
||||
import { readFileSync } from "fs";
|
||||
import { logger } from "../utils/logger.js";
|
||||
import { HOOK_TIMEOUTS, getTimeout } from "./hook-constants.js";
|
||||
import { ProcessManager } from "../services/process/ProcessManager.js";
|
||||
import { SettingsDefaultsManager } from "./SettingsDefaultsManager.js";
|
||||
import { getWorkerRestartInstructions } from "../utils/error-messages.js";
|
||||
|
||||
@@ -96,123 +94,44 @@ async function getWorkerVersion(): Promise<string> {
|
||||
|
||||
/**
|
||||
* Check if worker version matches plugin version
|
||||
* If mismatch detected, restart the worker automatically
|
||||
* Logs a warning if mismatch is detected
|
||||
*/
|
||||
async function ensureWorkerVersionMatches(): Promise<void> {
|
||||
async function checkWorkerVersion(): Promise<void> {
|
||||
const pluginVersion = getPluginVersion();
|
||||
const workerVersion = await getWorkerVersion();
|
||||
|
||||
if (pluginVersion !== workerVersion) {
|
||||
logger.info('SYSTEM', 'Worker version mismatch detected - restarting worker', {
|
||||
logger.warn('SYSTEM', 'Worker version mismatch', {
|
||||
pluginVersion,
|
||||
workerVersion
|
||||
workerVersion,
|
||||
hint: 'Restart worker with: claude-mem worker restart'
|
||||
});
|
||||
|
||||
// Give files time to sync before restart
|
||||
await new Promise(resolve => setTimeout(resolve, getTimeout(HOOK_TIMEOUTS.PRE_RESTART_SETTLE_DELAY)));
|
||||
|
||||
// Restart the worker
|
||||
await ProcessManager.restart(getWorkerPort());
|
||||
|
||||
// Give it a moment to start
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
// Verify it's healthy
|
||||
if (!await isWorkerHealthy()) {
|
||||
throw new Error(`Worker failed to restart after version mismatch. Expected ${pluginVersion}, was running ${workerVersion}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the worker service using ProcessManager
|
||||
* Handles both Unix (Bun) and Windows (compiled exe) platforms
|
||||
*/
|
||||
async function startWorker(): Promise<boolean> {
|
||||
// Clean up legacy PM2 (one-time migration)
|
||||
const dataDir = SettingsDefaultsManager.get('CLAUDE_MEM_DATA_DIR');
|
||||
const pm2MigratedMarker = path.join(dataDir, '.pm2-migrated');
|
||||
|
||||
// Ensure data directory exists (may not exist on fresh install)
|
||||
mkdirSync(dataDir, { recursive: true });
|
||||
|
||||
if (!existsSync(pm2MigratedMarker)) {
|
||||
spawnSync('pm2', ['delete', 'claude-mem-worker'], { stdio: 'ignore' });
|
||||
// Mark migration as complete
|
||||
writeFileSync(pm2MigratedMarker, new Date().toISOString(), 'utf-8');
|
||||
logger.debug('SYSTEM', 'PM2 cleanup completed and marked');
|
||||
}
|
||||
|
||||
const port = getWorkerPort();
|
||||
const result = await ProcessManager.start(port);
|
||||
|
||||
if (!result.success) {
|
||||
logger.error('SYSTEM', 'Failed to start worker', {
|
||||
platform: process.platform,
|
||||
port,
|
||||
error: result.error,
|
||||
marketplaceRoot: MARKETPLACE_ROOT
|
||||
});
|
||||
}
|
||||
|
||||
return result.success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure worker service is running
|
||||
* Checks health and auto-starts if not running
|
||||
* Also ensures worker version matches plugin version
|
||||
* Polls until worker is ready (assumes worker-cli.js start was called by hooks.json)
|
||||
*/
|
||||
export async function ensureWorkerRunning(): Promise<void> {
|
||||
// Check if already healthy (will throw on fetch errors)
|
||||
let healthy = false;
|
||||
try {
|
||||
healthy = await isWorkerHealthy();
|
||||
} catch (error) {
|
||||
// Worker not running or unreachable - continue to start it
|
||||
healthy = false;
|
||||
}
|
||||
const maxRetries = 25; // 5 seconds total
|
||||
const pollInterval = 200;
|
||||
|
||||
if (healthy) {
|
||||
// Worker is healthy, but check if version matches
|
||||
await ensureWorkerVersionMatches();
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to start the worker
|
||||
const started = await startWorker();
|
||||
|
||||
if (!started) {
|
||||
const port = getWorkerPort();
|
||||
throw new Error(
|
||||
getWorkerRestartInstructions({
|
||||
port,
|
||||
customPrefix: `Worker service failed to start on port ${port}.`
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// Wait for worker to become responsive after starting
|
||||
// Try up to 5 times with 500ms delays (2.5 seconds total)
|
||||
for (let i = 0; i < 5; i++) {
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
for (let i = 0; i < maxRetries; i++) {
|
||||
try {
|
||||
if (await isWorkerHealthy()) {
|
||||
await ensureWorkerVersionMatches();
|
||||
await checkWorkerVersion(); // logs warning on mismatch, doesn't restart
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
// Continue trying
|
||||
} catch {
|
||||
// Continue polling
|
||||
}
|
||||
await new Promise(r => setTimeout(r, pollInterval));
|
||||
}
|
||||
|
||||
// Worker started but isn't responding
|
||||
const port = getWorkerPort();
|
||||
logger.error('SYSTEM', 'Worker started but not responding to health checks');
|
||||
throw new Error(
|
||||
getWorkerRestartInstructions({
|
||||
port,
|
||||
customPrefix: `Worker service started but is not responding on port ${port}.`
|
||||
})
|
||||
);
|
||||
throw new Error(getWorkerRestartInstructions({
|
||||
port: getWorkerPort(),
|
||||
customPrefix: 'Worker did not become ready within 5 seconds.'
|
||||
}));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user