mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-17 06:17:09 +00:00
fix(cliproxy): register session on dashboard start and add port-based stop fallback
Dashboard Stop/Update buttons failed with contradictory errors: - Stop: "No active CLIProxy session found" - Update: "CLIProxy was already running" Root cause: service-manager spawned proxy but never called registerSession(), so stopProxy() couldn't find the lock file. Changes: - service-manager.ts: Add registerSession() after spawn - session-tracker.ts: Make stopProxy() async with port-based fallback - routes.ts: Update proxy-stop endpoint to async - cliproxy-command.ts: Update handleStop() to await async stopProxy() - port-utils.ts: Fix double-escaped regex for Windows port detection - session-tracker.test.js: Update tests for async stopProxy()
This commit is contained in:
@@ -22,6 +22,7 @@ import {
|
||||
getCliproxyWritablePath,
|
||||
} from './config-generator';
|
||||
import { isCliproxyRunning } from './stats-fetcher';
|
||||
import { registerSession } from './session-tracker';
|
||||
|
||||
/** Background proxy process reference */
|
||||
let proxyProcess: ChildProcess | null = null;
|
||||
@@ -219,6 +220,13 @@ export async function ensureCliproxyService(
|
||||
}
|
||||
|
||||
log(`CLIProxy service started on port ${port}`);
|
||||
|
||||
// 5. Register session so stopProxy() can find and kill this process
|
||||
if (proxyProcess.pid) {
|
||||
registerSession(port, proxyProcess.pid);
|
||||
log(`Session registered for PID ${proxyProcess.pid}`);
|
||||
}
|
||||
|
||||
return { started: true, alreadyRunning: false, port };
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@ import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as crypto from 'crypto';
|
||||
import { getCliproxyDir } from './config-generator';
|
||||
import { getPortProcess, isCLIProxyProcess } from '../utils/port-utils';
|
||||
import { CLIPROXY_DEFAULT_PORT } from './config-generator';
|
||||
|
||||
/** Session lock file structure */
|
||||
interface SessionLock {
|
||||
@@ -227,18 +229,43 @@ export function cleanupOrphanedSessions(port: number): void {
|
||||
|
||||
/**
|
||||
* Stop the CLIProxy process and clean up session lock.
|
||||
* Falls back to port-based detection if no session lock exists.
|
||||
* @returns Object with success status and details
|
||||
*/
|
||||
export function stopProxy(): {
|
||||
export async function stopProxy(): Promise<{
|
||||
stopped: boolean;
|
||||
pid?: number;
|
||||
sessionCount?: number;
|
||||
error?: string;
|
||||
} {
|
||||
}> {
|
||||
const lock = readSessionLock();
|
||||
|
||||
if (!lock) {
|
||||
return { stopped: false, error: 'No active CLIProxy session found' };
|
||||
// No session lock - try to find process by port (legacy/untracked proxy)
|
||||
const portProcess = await getPortProcess(CLIPROXY_DEFAULT_PORT);
|
||||
|
||||
if (!portProcess) {
|
||||
return { stopped: false, error: 'No active CLIProxy session found' };
|
||||
}
|
||||
|
||||
if (!isCLIProxyProcess(portProcess)) {
|
||||
return {
|
||||
stopped: false,
|
||||
error: `Port ${CLIPROXY_DEFAULT_PORT} is in use by ${portProcess.processName}, not CLIProxy`,
|
||||
};
|
||||
}
|
||||
|
||||
// Found CLIProxy running without session lock - kill it
|
||||
try {
|
||||
process.kill(portProcess.pid, 'SIGTERM');
|
||||
return { stopped: true, pid: portProcess.pid, sessionCount: 0 };
|
||||
} catch (err) {
|
||||
const error = err as NodeJS.ErrnoException;
|
||||
if (error.code === 'ESRCH') {
|
||||
return { stopped: false, error: 'CLIProxy process already terminated' };
|
||||
}
|
||||
return { stopped: false, pid: portProcess.pid, error: `Failed to stop: ${error.message}` };
|
||||
}
|
||||
}
|
||||
|
||||
// Check if proxy is running
|
||||
|
||||
@@ -1069,7 +1069,7 @@ async function handleStop(): Promise<void> {
|
||||
console.log(header('Stop CLIProxy'));
|
||||
console.log('');
|
||||
|
||||
const result = stopProxy();
|
||||
const result = await stopProxy();
|
||||
|
||||
if (result.stopped) {
|
||||
console.log(ok(`CLIProxy stopped (PID ${result.pid})`));
|
||||
|
||||
@@ -88,7 +88,7 @@ async function getPortProcessWindows(port: number): Promise<PortProcess | null>
|
||||
}
|
||||
|
||||
// Parse netstat output to get PID (last column)
|
||||
const match = netstatOut.match(/\\s+(\d+)\\s*$/m);
|
||||
const match = netstatOut.match(/\s+(\d+)\s*$/m);
|
||||
if (!match) {
|
||||
return null;
|
||||
}
|
||||
@@ -100,7 +100,7 @@ async function getPortProcessWindows(port: number): Promise<PortProcess | null>
|
||||
timeout: 3000,
|
||||
});
|
||||
|
||||
const taskMatch = tasklistOut.match(/^([^\\s]+)/);
|
||||
const taskMatch = tasklistOut.match(/^([^\s]+)/);
|
||||
const processName = taskMatch ? taskMatch[1] : `PID-${pid}`;
|
||||
|
||||
return { pid, processName };
|
||||
|
||||
@@ -1394,9 +1394,9 @@ apiRoutes.post('/cliproxy/proxy-start', async (_req: Request, res: Response): Pr
|
||||
* POST /api/cliproxy/proxy-stop - Stop the CLIProxy service
|
||||
* Returns: { stopped, pid?, sessionCount?, error? }
|
||||
*/
|
||||
apiRoutes.post('/cliproxy/proxy-stop', (_req: Request, res: Response): void => {
|
||||
apiRoutes.post('/cliproxy/proxy-stop', async (_req: Request, res: Response): Promise<void> => {
|
||||
try {
|
||||
const result = stopProxy();
|
||||
const result = await stopProxy();
|
||||
res.json(result);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: (error as Error).message });
|
||||
|
||||
@@ -299,13 +299,13 @@ describe('Session Tracker', function () {
|
||||
});
|
||||
|
||||
describe('stopProxy', function () {
|
||||
it('should return error when no lock exists', function () {
|
||||
const result = stopProxy();
|
||||
it('should return error when no lock exists', async function () {
|
||||
const result = await stopProxy();
|
||||
assert.strictEqual(result.stopped, false);
|
||||
assert.strictEqual(result.error, 'No active CLIProxy session found');
|
||||
});
|
||||
|
||||
it('should cleanup stale lock when proxy is not running', function () {
|
||||
it('should cleanup stale lock when proxy is not running', async function () {
|
||||
// Create lock with dead PID
|
||||
const lock = {
|
||||
port: testPort,
|
||||
@@ -315,7 +315,7 @@ describe('Session Tracker', function () {
|
||||
};
|
||||
fs.writeFileSync(sessionLockPath, JSON.stringify(lock));
|
||||
|
||||
const result = stopProxy();
|
||||
const result = await stopProxy();
|
||||
assert.strictEqual(result.stopped, false);
|
||||
assert.ok(result.error.includes('not running'));
|
||||
assert.strictEqual(fs.existsSync(sessionLockPath), false);
|
||||
|
||||
Reference in New Issue
Block a user