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:
can1357
2026-05-12 09:09:24 +02:00
parent dcf3f1c8a3
commit 8d144e17ec
26 changed files with 1735 additions and 3004 deletions
+14 -161
View File
@@ -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