feat(coding-agent/eval): added local python-runner subprocess execution
- Replaced Python execution with a local `python -u runner.py` subprocess and NDJSON stdin/stdout framing. - Removed shared-gateway architecture, including coordinator lifecycle APIs, `useSharedGateway` wiring, and `jupyter` CLI/actions. - Simplified setup checks to a plain Python 3 availability probe and removed automatic dependency-install fallbacks. - Updated kernel cancellation and display processing to use status frames, SIGINT/SIGTERM escalation, and normalized output coercion. - Added `python-runner` integration and display tests while deleting legacy websocket and kernel lifecycle test suites.
This commit is contained in:
@@ -21,7 +21,6 @@ export interface SetupCommandArgs {
|
||||
|
||||
const VALID_COMPONENTS: SetupComponent[] = ["python", "stt"];
|
||||
|
||||
const PYTHON_PACKAGES = ["jupyter_kernel_gateway", "ipykernel"];
|
||||
const MANAGED_PYTHON_ENV = getPythonEnvDir();
|
||||
|
||||
/**
|
||||
@@ -65,10 +64,6 @@ export function parseSetupArgs(args: string[]): SetupCommandArgs | undefined {
|
||||
interface PythonCheckResult {
|
||||
available: boolean;
|
||||
pythonPath?: string;
|
||||
uvPath?: string;
|
||||
pipPath?: string;
|
||||
missingPackages: string[];
|
||||
installedPackages: string[];
|
||||
usingManagedEnv?: boolean;
|
||||
managedEnvPath?: string;
|
||||
}
|
||||
@@ -85,8 +80,6 @@ function managedPythonPath(): string {
|
||||
async function checkPythonSetup(): Promise<PythonCheckResult> {
|
||||
const result: PythonCheckResult = {
|
||||
available: false,
|
||||
missingPackages: [],
|
||||
installedPackages: [],
|
||||
managedEnvPath: MANAGED_PYTHON_ENV,
|
||||
};
|
||||
|
||||
@@ -94,109 +87,24 @@ async function checkPythonSetup(): Promise<PythonCheckResult> {
|
||||
const managedPath = managedPythonPath();
|
||||
const hasManagedEnv = await Bun.file(managedPath).exists();
|
||||
|
||||
result.uvPath = $which("uv") ?? undefined;
|
||||
result.pipPath = $which("pip3") ?? $which("pip") ?? undefined;
|
||||
|
||||
const candidates = [systemPythonPath, hasManagedEnv ? managedPath : undefined].filter(
|
||||
(candidate): candidate is string => !!candidate,
|
||||
);
|
||||
if (candidates.length === 0) {
|
||||
const pythonPath = systemPythonPath ?? (hasManagedEnv ? managedPath : undefined);
|
||||
if (!pythonPath) {
|
||||
return result;
|
||||
}
|
||||
|
||||
result.pythonPath = systemPythonPath ?? managedPath;
|
||||
let bestMatch = {
|
||||
pythonPath: candidates[0],
|
||||
missingPackages: [...PYTHON_PACKAGES],
|
||||
installedPackages: [] as string[],
|
||||
usingManagedEnv: candidates[0] === managedPath,
|
||||
};
|
||||
|
||||
for (const pythonPath of candidates) {
|
||||
const installedPackages: string[] = [];
|
||||
const missingPackages: string[] = [];
|
||||
for (const pkg of PYTHON_PACKAGES) {
|
||||
const moduleName = pkg === "jupyter_kernel_gateway" ? "kernel_gateway" : pkg;
|
||||
const script = `import importlib.util; raise SystemExit(0 if importlib.util.find_spec('${moduleName}') else 1)`;
|
||||
const check = await $`${pythonPath} -c ${script}`.quiet().nothrow();
|
||||
if (check.exitCode === 0) {
|
||||
installedPackages.push(pkg);
|
||||
} else {
|
||||
missingPackages.push(pkg);
|
||||
}
|
||||
}
|
||||
|
||||
if (missingPackages.length < bestMatch.missingPackages.length) {
|
||||
bestMatch = {
|
||||
pythonPath,
|
||||
missingPackages,
|
||||
installedPackages,
|
||||
usingManagedEnv: pythonPath === managedPath,
|
||||
};
|
||||
}
|
||||
|
||||
if (missingPackages.length === 0) {
|
||||
result.available = true;
|
||||
result.pythonPath = pythonPath;
|
||||
result.missingPackages = missingPackages;
|
||||
result.installedPackages = installedPackages;
|
||||
result.usingManagedEnv = pythonPath === managedPath;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
result.pythonPath = bestMatch.pythonPath;
|
||||
result.missingPackages = bestMatch.missingPackages;
|
||||
result.installedPackages = bestMatch.installedPackages;
|
||||
result.usingManagedEnv = bestMatch.usingManagedEnv;
|
||||
const probe = await $`${pythonPath} -c "import sys;sys.exit(0)"`.quiet().nothrow();
|
||||
result.pythonPath = pythonPath;
|
||||
result.available = probe.exitCode === 0;
|
||||
result.usingManagedEnv = pythonPath === managedPath;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Install Python packages using uv (preferred) or pip.
|
||||
*/
|
||||
async function installPythonPackages(
|
||||
packages: string[],
|
||||
pythonPath: string,
|
||||
uvPath?: string,
|
||||
pipPath?: string,
|
||||
): Promise<{ success: boolean; usedManagedEnv: boolean }> {
|
||||
if (uvPath) {
|
||||
console.log(chalk.dim(`Installing via uv: ${packages.join(" ")}`));
|
||||
const result = await $`${uvPath} pip install ${packages}`.nothrow();
|
||||
if (result.exitCode === 0) {
|
||||
return { success: true, usedManagedEnv: false };
|
||||
}
|
||||
}
|
||||
|
||||
if (pipPath) {
|
||||
console.log(chalk.dim(`Installing via pip: ${packages.join(" ")}`));
|
||||
const result = await $`${pipPath} install ${packages}`.nothrow();
|
||||
if (result.exitCode === 0) {
|
||||
return { success: true, usedManagedEnv: false };
|
||||
}
|
||||
}
|
||||
|
||||
console.log(chalk.dim(`Falling back to managed virtual environment: ${MANAGED_PYTHON_ENV}`));
|
||||
|
||||
if (uvPath) {
|
||||
const createEnv = await $`${uvPath} venv ${MANAGED_PYTHON_ENV}`.quiet().nothrow();
|
||||
if (createEnv.exitCode !== 0) {
|
||||
return { success: false, usedManagedEnv: true };
|
||||
}
|
||||
const installInManagedEnv = await $`${uvPath} pip install --python ${MANAGED_PYTHON_ENV} ${packages}`.nothrow();
|
||||
return { success: installInManagedEnv.exitCode === 0, usedManagedEnv: true };
|
||||
}
|
||||
|
||||
const createEnv = await $`${pythonPath} -m venv ${MANAGED_PYTHON_ENV}`.quiet().nothrow();
|
||||
if (createEnv.exitCode !== 0) {
|
||||
return { success: false, usedManagedEnv: true };
|
||||
}
|
||||
|
||||
const managedPython = managedPythonPath();
|
||||
const installInManagedEnv = await $`${managedPython} -m pip install ${packages}`.nothrow();
|
||||
return { success: installInManagedEnv.exitCode === 0, usedManagedEnv: true };
|
||||
}
|
||||
// Python installation helper removed: the subprocess runner has no Python
|
||||
// package dependencies beyond a working interpreter. `omp setup python --check`
|
||||
// remains as a probe; users install optional libs (pandas, matplotlib, ...)
|
||||
// directly via pip or the in-process `%pip` magic.
|
||||
|
||||
/**
|
||||
* Run the setup command.
|
||||
@@ -232,67 +140,13 @@ async function handlePythonSetup(flags: { json?: boolean; check?: boolean }): Pr
|
||||
console.log(chalk.dim(`Using managed environment: ${check.managedEnvPath}`));
|
||||
}
|
||||
|
||||
if (check.uvPath) {
|
||||
console.log(chalk.dim(`uv: ${check.uvPath}`));
|
||||
} else if (check.pipPath) {
|
||||
console.log(chalk.dim(`pip: ${check.pipPath}`));
|
||||
}
|
||||
|
||||
if (check.installedPackages.length > 0) {
|
||||
console.log(chalk.green(`${theme.status.success} Installed: ${check.installedPackages.join(", ")}`));
|
||||
}
|
||||
|
||||
if (check.missingPackages.length === 0) {
|
||||
if (check.available) {
|
||||
console.log(chalk.green(`\n${theme.status.success} Python execution is ready`));
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(chalk.yellow(`${theme.status.warning} Missing: ${check.missingPackages.join(", ")}`));
|
||||
|
||||
if (flags.check) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!check.uvPath && !check.pipPath) {
|
||||
console.error(chalk.red(`\n${theme.status.error} No package manager found`));
|
||||
console.error(chalk.dim("Install uv (recommended) or pip:"));
|
||||
console.error(chalk.dim(" curl -LsSf https://astral.sh/uv/install.sh | sh"));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log("");
|
||||
const install = await installPythonPackages(check.missingPackages, check.pythonPath, check.uvPath, check.pipPath);
|
||||
|
||||
if (!install.success) {
|
||||
console.error(chalk.red(`\n${theme.status.error} Installation failed`));
|
||||
console.error(chalk.dim("Try installing manually:"));
|
||||
if (install.usedManagedEnv) {
|
||||
if (check.uvPath) {
|
||||
console.error(chalk.dim(` uv venv ${MANAGED_PYTHON_ENV}`));
|
||||
console.error(
|
||||
chalk.dim(` uv pip install --python ${MANAGED_PYTHON_ENV} ${check.missingPackages.join(" ")}`),
|
||||
);
|
||||
} else {
|
||||
console.error(chalk.dim(` ${check.pythonPath} -m venv ${MANAGED_PYTHON_ENV}`));
|
||||
console.error(chalk.dim(` ${managedPythonPath()} -m pip install ${check.missingPackages.join(" ")}`));
|
||||
}
|
||||
} else {
|
||||
console.error(chalk.dim(` ${check.uvPath ? "uv pip" : "pip"} install ${check.missingPackages.join(" ")}`));
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const recheck = await checkPythonSetup();
|
||||
if (recheck.available) {
|
||||
console.log(chalk.green(`\n${theme.status.success} Python execution is ready`));
|
||||
if (recheck.usingManagedEnv) {
|
||||
console.log(chalk.dim(`Managed Python environment: ${recheck.managedEnvPath}`));
|
||||
}
|
||||
} else {
|
||||
console.error(chalk.red(`\n${theme.status.error} Setup incomplete`));
|
||||
console.error(chalk.dim(`Still missing: ${recheck.missingPackages.join(", ")}`));
|
||||
process.exit(1);
|
||||
}
|
||||
console.error(chalk.red(`\n${theme.status.error} Python interpreter reported failure`));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
async function handleSttSetup(flags: { json?: boolean; check?: boolean }): Promise<void> {
|
||||
@@ -359,9 +213,8 @@ ${chalk.bold("Usage:")}
|
||||
${APP_NAME} setup <component> [options]
|
||||
|
||||
${chalk.bold("Components:")}
|
||||
python Install Jupyter kernel dependencies for Python code execution
|
||||
python Verify a Python 3 interpreter is reachable for code execution
|
||||
stt Install speech-to-text dependencies (openai-whisper, recording tools)
|
||||
Packages: ${PYTHON_PACKAGES.join(", ")}
|
||||
|
||||
${chalk.bold("Options:")}
|
||||
-c, --check Check if dependencies are installed without installing
|
||||
|
||||
Reference in New Issue
Block a user