mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-12 18:15:48 +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:
1 parent
f1dfd69a4a
commit
a3b172cc43
6 files changed
+47
-12
No files matched your search
@@ -22,6 +22,7 @@ import {
|
|||||||
getCliproxyWritablePath,
|
getCliproxyWritablePath,
|
||||||
} from './config-generator';
|
} from './config-generator';
|
||||||
import { isCliproxyRunning } from './stats-fetcher';
|
import { isCliproxyRunning } from './stats-fetcher';
|
||||||
|
import { registerSession } from './session-tracker';
|
||||||
|
|
||||||
/** Background proxy process reference */
|
/** Background proxy process reference */
|
||||||
let proxyProcess: ChildProcess | null = null;
|
let proxyProcess: ChildProcess | null = null;
|
||||||
@@ -219,6 +220,13 @@ export async function ensureCliproxyService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
log(`CLIProxy service started on port ${port}`);
|
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 };
|
return { started: true, alreadyRunning: false, port };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ import * as fs from 'fs';
|
|||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as crypto from 'crypto';
|
import * as crypto from 'crypto';
|
||||||
import { getCliproxyDir } from './config-generator';
|
import { getCliproxyDir } from './config-generator';
|
||||||
|
import { getPortProcess, isCLIProxyProcess } from '../utils/port-utils';
|
||||||
|
import { CLIPROXY_DEFAULT_PORT } from './config-generator';
|
||||||
|
|
||||||
/** Session lock file structure */
|
/** Session lock file structure */
|
||||||
interface SessionLock {
|
interface SessionLock {
|
||||||
@@ -227,18 +229,43 @@ export function cleanupOrphanedSessions(port: number): void {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Stop the CLIProxy process and clean up session lock.
|
* 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
|
* @returns Object with success status and details
|
||||||
*/
|
*/
|
||||||
export function stopProxy(): {
|
export async function stopProxy(): Promise<{
|
||||||
stopped: boolean;
|
stopped: boolean;
|
||||||
pid?: number;
|
pid?: number;
|
||||||
sessionCount?: number;
|
sessionCount?: number;
|
||||||
error?: string;
|
error?: string;
|
||||||
} {
|
}> {
|
||||||
const lock = readSessionLock();
|
const lock = readSessionLock();
|
||||||
|
|
||||||
if (!lock) {
|
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
|
// Check if proxy is running
|
||||||
|
|||||||
@@ -1069,7 +1069,7 @@ async function handleStop(): Promise<void> {
|
|||||||
console.log(header('Stop CLIProxy'));
|
console.log(header('Stop CLIProxy'));
|
||||||
console.log('');
|
console.log('');
|
||||||
|
|
||||||
const result = stopProxy();
|
const result = await stopProxy();
|
||||||
|
|
||||||
if (result.stopped) {
|
if (result.stopped) {
|
||||||
console.log(ok(`CLIProxy stopped (PID ${result.pid})`));
|
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)
|
// 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) {
|
if (!match) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -100,7 +100,7 @@ async function getPortProcessWindows(port: number): Promise<PortProcess | null>
|
|||||||
timeout: 3000,
|
timeout: 3000,
|
||||||
});
|
});
|
||||||
|
|
||||||
const taskMatch = tasklistOut.match(/^([^\\s]+)/);
|
const taskMatch = tasklistOut.match(/^([^\s]+)/);
|
||||||
const processName = taskMatch ? taskMatch[1] : `PID-${pid}`;
|
const processName = taskMatch ? taskMatch[1] : `PID-${pid}`;
|
||||||
|
|
||||||
return { pid, processName };
|
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
|
* POST /api/cliproxy/proxy-stop - Stop the CLIProxy service
|
||||||
* Returns: { stopped, pid?, sessionCount?, error? }
|
* 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 {
|
try {
|
||||||
const result = stopProxy();
|
const result = await stopProxy();
|
||||||
res.json(result);
|
res.json(result);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
res.status(500).json({ error: (error as Error).message });
|
res.status(500).json({ error: (error as Error).message });
|
||||||
|
|||||||
@@ -299,13 +299,13 @@ describe('Session Tracker', function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('stopProxy', function () {
|
describe('stopProxy', function () {
|
||||||
it('should return error when no lock exists', function () {
|
it('should return error when no lock exists', async function () {
|
||||||
const result = stopProxy();
|
const result = await stopProxy();
|
||||||
assert.strictEqual(result.stopped, false);
|
assert.strictEqual(result.stopped, false);
|
||||||
assert.strictEqual(result.error, 'No active CLIProxy session found');
|
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
|
// Create lock with dead PID
|
||||||
const lock = {
|
const lock = {
|
||||||
port: testPort,
|
port: testPort,
|
||||||
@@ -315,7 +315,7 @@ describe('Session Tracker', function () {
|
|||||||
};
|
};
|
||||||
fs.writeFileSync(sessionLockPath, JSON.stringify(lock));
|
fs.writeFileSync(sessionLockPath, JSON.stringify(lock));
|
||||||
|
|
||||||
const result = stopProxy();
|
const result = await stopProxy();
|
||||||
assert.strictEqual(result.stopped, false);
|
assert.strictEqual(result.stopped, false);
|
||||||
assert.ok(result.error.includes('not running'));
|
assert.ok(result.error.includes('not running'));
|
||||||
assert.strictEqual(fs.existsSync(sessionLockPath), false);
|
assert.strictEqual(fs.existsSync(sessionLockPath), false);
|
||||||
|
|||||||
Reference in new issue
Block a user