mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-11 03:13:12 +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:
1 parent
94789676b9
commit
ce1915366d
4 files changed
+22
-19
No files matched your search
+10
-16
@@ -9,15 +9,9 @@ import { spawn, ChildProcess } from 'child_process';
|
|||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as http from 'http';
|
import * as http from 'http';
|
||||||
import type { CursorDaemonStatus } from './types';
|
import type { CursorConfig, CursorDaemonStatus } from './types';
|
||||||
import { getCcsDir } from '../utils/config-manager';
|
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.
|
* Get Cursor directory path.
|
||||||
*/
|
*/
|
||||||
@@ -155,11 +149,11 @@ export async function startDaemon(
|
|||||||
const safeResolve = (result: { success: boolean; pid?: number; error?: string }) => {
|
const safeResolve = (result: { success: boolean; pid?: number; error?: string }) => {
|
||||||
if (resolved) return;
|
if (resolved) return;
|
||||||
resolved = true;
|
resolved = true;
|
||||||
if (checkInterval) clearInterval(checkInterval);
|
if (checkTimeout) clearTimeout(checkTimeout);
|
||||||
resolve(result);
|
resolve(result);
|
||||||
};
|
};
|
||||||
|
|
||||||
let checkInterval: NodeJS.Timeout | null = null;
|
let checkTimeout: NodeJS.Timeout | null = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Spawn a placeholder Node.js process
|
// Spawn a placeholder Node.js process
|
||||||
@@ -185,9 +179,8 @@ export async function startDaemon(
|
|||||||
];
|
];
|
||||||
|
|
||||||
proc = spawn(process.execPath, args, {
|
proc = spawn(process.execPath, args, {
|
||||||
stdio: ['ignore', 'pipe', 'pipe'],
|
stdio: 'ignore',
|
||||||
detached: true,
|
detached: true,
|
||||||
shell: process.platform === 'win32',
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Unref so parent can exit
|
// 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)
|
// Wait for daemon to be ready (poll for up to 30 seconds)
|
||||||
let attempts = 0;
|
let attempts = 0;
|
||||||
const maxAttempts = 30;
|
const maxAttempts = 30;
|
||||||
checkInterval = setInterval(async () => {
|
const pollHealth = async () => {
|
||||||
attempts++;
|
attempts++;
|
||||||
|
|
||||||
if (await isDaemonRunning(config.port)) {
|
if (await isDaemonRunning(config.port)) {
|
||||||
@@ -218,8 +211,11 @@ export async function startDaemon(
|
|||||||
success: false,
|
success: false,
|
||||||
error: 'Daemon did not start within 30 seconds',
|
error: 'Daemon did not start within 30 seconds',
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
checkTimeout = setTimeout(pollHealth, 1000);
|
||||||
}
|
}
|
||||||
}, 1000);
|
};
|
||||||
|
checkTimeout = setTimeout(pollHealth, 1000);
|
||||||
|
|
||||||
proc.on('error', (err) => {
|
proc.on('error', (err) => {
|
||||||
safeResolve({
|
safeResolve({
|
||||||
@@ -263,7 +259,6 @@ export async function stopDaemon(): Promise<{ success: boolean; error?: string }
|
|||||||
|
|
||||||
if (!pid) {
|
if (!pid) {
|
||||||
// No PID file — daemon is not running or was already stopped
|
// No PID file — daemon is not running or was already stopped
|
||||||
removePidFile();
|
|
||||||
return { success: true };
|
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
|
// Escalate to SIGKILL if process still alive after SIGTERM attempts
|
||||||
try {
|
try {
|
||||||
process.kill(pid, 0); // Check if still alive
|
process.kill(pid, 'SIGKILL');
|
||||||
process.kill(pid, 'SIGKILL'); // Escalate to force kill
|
|
||||||
} catch {
|
} catch {
|
||||||
// Already dead — good
|
// 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('gpt') || /^o\d/.test(modelId)) return 'openai';
|
||||||
if (modelId.includes('gemini')) return 'google';
|
if (modelId.includes('gemini')) return 'google';
|
||||||
if (modelId.includes('cursor')) return 'cursor';
|
if (modelId.includes('cursor')) return 'cursor';
|
||||||
return 'openai';
|
return 'unknown';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -4,6 +4,15 @@
|
|||||||
* TypeScript interfaces for the Cursor module.
|
* 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
|
* Cursor authentication credentials
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -72,8 +72,8 @@ describe('detectProvider', () => {
|
|||||||
expect(detectProvider('cursor-small')).toBe('cursor');
|
expect(detectProvider('cursor-small')).toBe('cursor');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('defaults to openai for unknown models', () => {
|
it('defaults to unknown for unrecognized models', () => {
|
||||||
expect(detectProvider('unknown-model')).toBe('openai');
|
expect(detectProvider('unknown-model')).toBe('unknown');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user