mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-17 04:17:11 +00:00
fix(cursor): use stdio ignore, sequential polling, move CursorConfig to types
- Change spawn stdio from piped to 'ignore' preventing buffer deadlock - Replace setInterval with sequential setTimeout polling - Fix TOCTOU in SIGKILL escalation (send directly without probing) - Move CursorConfig interface to types.ts - Change detectProvider default from 'openai' to 'unknown' - Remove redundant removePidFile() when PID is null
This commit is contained in:
+10
-16
@@ -9,15 +9,9 @@ import { spawn, ChildProcess } from 'child_process';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as http from 'http';
|
||||
import type { CursorDaemonStatus } from './types';
|
||||
import type { CursorConfig, CursorDaemonStatus } from './types';
|
||||
import { getCcsDir } from '../utils/config-manager';
|
||||
|
||||
// Temporary interface until #521 adds cursor to unified config
|
||||
interface CursorConfig {
|
||||
port: number;
|
||||
model: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Cursor directory path.
|
||||
*/
|
||||
@@ -155,11 +149,11 @@ export async function startDaemon(
|
||||
const safeResolve = (result: { success: boolean; pid?: number; error?: string }) => {
|
||||
if (resolved) return;
|
||||
resolved = true;
|
||||
if (checkInterval) clearInterval(checkInterval);
|
||||
if (checkTimeout) clearTimeout(checkTimeout);
|
||||
resolve(result);
|
||||
};
|
||||
|
||||
let checkInterval: NodeJS.Timeout | null = null;
|
||||
let checkTimeout: NodeJS.Timeout | null = null;
|
||||
|
||||
try {
|
||||
// Spawn a placeholder Node.js process
|
||||
@@ -185,9 +179,8 @@ export async function startDaemon(
|
||||
];
|
||||
|
||||
proc = spawn(process.execPath, args, {
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
stdio: 'ignore',
|
||||
detached: true,
|
||||
shell: process.platform === 'win32',
|
||||
});
|
||||
|
||||
// Unref so parent can exit
|
||||
@@ -200,7 +193,7 @@ export async function startDaemon(
|
||||
// Wait for daemon to be ready (poll for up to 30 seconds)
|
||||
let attempts = 0;
|
||||
const maxAttempts = 30;
|
||||
checkInterval = setInterval(async () => {
|
||||
const pollHealth = async () => {
|
||||
attempts++;
|
||||
|
||||
if (await isDaemonRunning(config.port)) {
|
||||
@@ -218,8 +211,11 @@ export async function startDaemon(
|
||||
success: false,
|
||||
error: 'Daemon did not start within 30 seconds',
|
||||
});
|
||||
} else {
|
||||
checkTimeout = setTimeout(pollHealth, 1000);
|
||||
}
|
||||
}, 1000);
|
||||
};
|
||||
checkTimeout = setTimeout(pollHealth, 1000);
|
||||
|
||||
proc.on('error', (err) => {
|
||||
safeResolve({
|
||||
@@ -263,7 +259,6 @@ export async function stopDaemon(): Promise<{ success: boolean; error?: string }
|
||||
|
||||
if (!pid) {
|
||||
// No PID file — daemon is not running or was already stopped
|
||||
removePidFile();
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
@@ -287,8 +282,7 @@ export async function stopDaemon(): Promise<{ success: boolean; error?: string }
|
||||
|
||||
// Escalate to SIGKILL if process still alive after SIGTERM attempts
|
||||
try {
|
||||
process.kill(pid, 0); // Check if still alive
|
||||
process.kill(pid, 'SIGKILL'); // Escalate to force kill
|
||||
process.kill(pid, 'SIGKILL');
|
||||
} catch {
|
||||
// Already dead — good
|
||||
}
|
||||
|
||||
@@ -163,7 +163,7 @@ export function detectProvider(modelId: string): string {
|
||||
if (modelId.includes('gpt') || /^o\d/.test(modelId)) return 'openai';
|
||||
if (modelId.includes('gemini')) return 'google';
|
||||
if (modelId.includes('cursor')) return 'cursor';
|
||||
return 'openai';
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,6 +4,15 @@
|
||||
* TypeScript interfaces for the Cursor module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Cursor daemon configuration.
|
||||
* Temporary interface until #521 adds cursor to unified config.
|
||||
*/
|
||||
export interface CursorConfig {
|
||||
port: number;
|
||||
model: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cursor authentication credentials
|
||||
*/
|
||||
|
||||
@@ -72,8 +72,8 @@ describe('detectProvider', () => {
|
||||
expect(detectProvider('cursor-small')).toBe('cursor');
|
||||
});
|
||||
|
||||
it('defaults to openai for unknown models', () => {
|
||||
expect(detectProvider('unknown-model')).toBe('openai');
|
||||
it('defaults to unknown for unrecognized models', () => {
|
||||
expect(detectProvider('unknown-model')).toBe('unknown');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user