mirror of
https://github.com/OpenRouterTeam/spawn.git
synced 2026-05-14 16:50:37 +00:00
fix: use child_process.spawn for interactive sessions to fix TTY passthrough (#1780)
Bun.spawn() doesn't properly restore TTY state after @clack/prompts manipulates stdin raw mode during provisioning. This causes laggy/broken keyboard input in SSH sessions launched via `spawn run`. Node's child_process.spawn() with stdio: "inherit" does a clean FD handoff, matching the already-working pattern in runInteractiveCommand() used by `spawn ls` resume. Co-authored-by: lab <6723574+louisgv@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
0843c5e708
commit
ef2748069f
8 changed files with 65 additions and 102 deletions
|
|
@ -1,6 +1,7 @@
|
|||
// aws/aws.ts — Core AWS Lightsail provider: auth, provisioning, SSH execution
|
||||
|
||||
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import { spawn } from "node:child_process";
|
||||
import { createHash, createHmac } from "node:crypto";
|
||||
import {
|
||||
logInfo,
|
||||
|
|
@ -1033,23 +1034,18 @@ export async function interactiveSession(cmd: string): Promise<number> {
|
|||
const term = sanitizeTermValue(process.env.TERM || "xterm-256color");
|
||||
const fullCmd = `export TERM=${term} PATH="$HOME/.npm-global/bin:$HOME/.claude/local/bin:$HOME/.local/bin:$HOME/.bun/bin:$PATH" && exec bash -l -c ${JSON.stringify(cmd)}`;
|
||||
const escapedCmd = fullCmd.replace(/'/g, "'\\''");
|
||||
const proc = Bun.spawn(
|
||||
[
|
||||
"ssh",
|
||||
const exitCode = await new Promise<number>((resolve, reject) => {
|
||||
const child = spawn("ssh", [
|
||||
...SSH_OPTS,
|
||||
"-t",
|
||||
`${SSH_USER}@${instanceIp}`,
|
||||
`bash -c '${escapedCmd}'`,
|
||||
],
|
||||
{
|
||||
stdio: [
|
||||
"inherit",
|
||||
"inherit",
|
||||
"inherit",
|
||||
],
|
||||
},
|
||||
);
|
||||
const exitCode = await proc.exited;
|
||||
], {
|
||||
stdio: "inherit",
|
||||
});
|
||||
child.on("close", (code) => resolve(code ?? 0));
|
||||
child.on("error", reject);
|
||||
});
|
||||
|
||||
// Post-session summary
|
||||
process.stderr.write("\n");
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// daytona/daytona.ts — Core Daytona provider: API, SSH, provisioning, execution
|
||||
|
||||
import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
||||
import { spawn } from "node:child_process";
|
||||
import {
|
||||
logInfo,
|
||||
logWarn,
|
||||
|
|
@ -519,14 +520,13 @@ export async function interactiveSession(cmd: string): Promise<number> {
|
|||
fullCmd,
|
||||
];
|
||||
|
||||
const proc = Bun.spawn(args, {
|
||||
stdio: [
|
||||
"inherit",
|
||||
"inherit",
|
||||
"inherit",
|
||||
],
|
||||
const exitCode = await new Promise<number>((resolve, reject) => {
|
||||
const child = spawn(args[0], args.slice(1), {
|
||||
stdio: "inherit",
|
||||
});
|
||||
child.on("close", (code) => resolve(code ?? 0));
|
||||
child.on("error", reject);
|
||||
});
|
||||
const exitCode = await proc.exited;
|
||||
|
||||
// Post-session summary
|
||||
process.stderr.write("\n");
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// digitalocean/digitalocean.ts — Core DigitalOcean provider: API, auth, SSH, provisioning
|
||||
|
||||
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import { spawn } from "node:child_process";
|
||||
import * as v from "valibot";
|
||||
import {
|
||||
logInfo,
|
||||
|
|
@ -1034,23 +1035,13 @@ export async function interactiveSession(cmd: string, ip?: string): Promise<numb
|
|||
const term = sanitizeTermValue(process.env.TERM || "xterm-256color");
|
||||
const fullCmd = `export TERM=${term} PATH="$HOME/.local/bin:$HOME/.bun/bin:$PATH" && exec bash -l -c ${JSON.stringify(cmd)}`;
|
||||
|
||||
const proc = Bun.spawn(
|
||||
[
|
||||
"ssh",
|
||||
...SSH_OPTS,
|
||||
"-t",
|
||||
`root@${serverIp}`,
|
||||
fullCmd,
|
||||
],
|
||||
{
|
||||
stdio: [
|
||||
"inherit",
|
||||
"inherit",
|
||||
"inherit",
|
||||
],
|
||||
},
|
||||
);
|
||||
const exitCode = await proc.exited;
|
||||
const exitCode = await new Promise<number>((resolve, reject) => {
|
||||
const child = spawn("ssh", [...SSH_OPTS, "-t", `root@${serverIp}`, fullCmd], {
|
||||
stdio: "inherit",
|
||||
});
|
||||
child.on("close", (code) => resolve(code ?? 0));
|
||||
child.on("error", reject);
|
||||
});
|
||||
|
||||
// Post-session summary
|
||||
process.stderr.write("\n");
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// fly/lib/fly.ts — Core Fly.io provider: API, auth, orgs, provisioning, execution
|
||||
|
||||
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import { spawn } from "node:child_process";
|
||||
import {
|
||||
logInfo,
|
||||
logWarn,
|
||||
|
|
@ -1062,9 +1063,8 @@ export async function interactiveSession(cmd: string): Promise<number> {
|
|||
const escapedCmd = fullCmd.replace(/'/g, "'\\''");
|
||||
const flyCmd = getCmd()!;
|
||||
|
||||
const proc = Bun.spawn(
|
||||
[
|
||||
flyCmd,
|
||||
const exitCode = await new Promise<number>((resolve, reject) => {
|
||||
const child = spawn(flyCmd, [
|
||||
"ssh",
|
||||
"console",
|
||||
"-a",
|
||||
|
|
@ -1072,17 +1072,13 @@ export async function interactiveSession(cmd: string): Promise<number> {
|
|||
"--pty",
|
||||
"-C",
|
||||
`bash -c '${escapedCmd}'`,
|
||||
],
|
||||
{
|
||||
stdio: [
|
||||
"inherit",
|
||||
"inherit",
|
||||
"inherit",
|
||||
],
|
||||
], {
|
||||
stdio: "inherit",
|
||||
env: process.env,
|
||||
},
|
||||
);
|
||||
const exitCode = await proc.exited;
|
||||
});
|
||||
child.on("close", (code) => resolve(code ?? 0));
|
||||
child.on("error", reject);
|
||||
});
|
||||
|
||||
// Post-session summary
|
||||
process.stderr.write("\n");
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// gcp/gcp.ts — Core GCP Compute Engine provider: gcloud CLI wrapper, auth, provisioning, SSH
|
||||
|
||||
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import { spawn } from "node:child_process";
|
||||
import {
|
||||
logInfo,
|
||||
logWarn,
|
||||
|
|
@ -919,24 +920,19 @@ export async function interactiveSession(cmd: string): Promise<number> {
|
|||
const term = sanitizeTermValue(process.env.TERM || "xterm-256color");
|
||||
const fullCmd = `export TERM=${term} PATH="$HOME/.npm-global/bin:$HOME/.claude/local/bin:$HOME/.local/bin:$HOME/.bun/bin:$PATH" && exec bash -l -c ${JSON.stringify(cmd)}`;
|
||||
|
||||
const proc = Bun.spawn(
|
||||
[
|
||||
"ssh",
|
||||
const exitCode = await new Promise<number>((resolve, reject) => {
|
||||
const child = spawn("ssh", [
|
||||
...SSH_OPTS,
|
||||
"-t",
|
||||
`${username}@${gcpServerIp}`,
|
||||
`bash -c ${shellQuote(fullCmd)}`,
|
||||
],
|
||||
{
|
||||
stdio: [
|
||||
"inherit",
|
||||
"inherit",
|
||||
"inherit",
|
||||
],
|
||||
], {
|
||||
stdio: "inherit",
|
||||
env: process.env,
|
||||
},
|
||||
);
|
||||
const exitCode = await proc.exited;
|
||||
});
|
||||
child.on("close", (code) => resolve(code ?? 0));
|
||||
child.on("error", reject);
|
||||
});
|
||||
|
||||
// Post-session summary
|
||||
process.stderr.write("\n");
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// hetzner/hetzner.ts — Core Hetzner Cloud provider: API, auth, SSH, provisioning
|
||||
|
||||
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import { spawn } from "node:child_process";
|
||||
import {
|
||||
logInfo,
|
||||
logWarn,
|
||||
|
|
@ -634,23 +635,13 @@ export async function interactiveSession(cmd: string, ip?: string): Promise<numb
|
|||
const term = sanitizeTermValue(process.env.TERM || "xterm-256color");
|
||||
const fullCmd = `export TERM=${term} PATH="$HOME/.local/bin:$HOME/.bun/bin:$PATH" && exec bash -l -c ${JSON.stringify(cmd)}`;
|
||||
|
||||
const proc = Bun.spawn(
|
||||
[
|
||||
"ssh",
|
||||
...SSH_OPTS,
|
||||
"-t",
|
||||
`root@${serverIp}`,
|
||||
fullCmd,
|
||||
],
|
||||
{
|
||||
stdio: [
|
||||
"inherit",
|
||||
"inherit",
|
||||
"inherit",
|
||||
],
|
||||
},
|
||||
);
|
||||
const exitCode = await proc.exited;
|
||||
const exitCode = await new Promise<number>((resolve, reject) => {
|
||||
const child = spawn("ssh", [...SSH_OPTS, "-t", `root@${serverIp}`, fullCmd], {
|
||||
stdio: "inherit",
|
||||
});
|
||||
child.on("close", (code) => resolve(code ?? 0));
|
||||
child.on("error", reject);
|
||||
});
|
||||
|
||||
// Post-session summary
|
||||
process.stderr.write("\n");
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
import { copyFileSync, mkdirSync, readFileSync } from "node:fs";
|
||||
import { dirname } from "node:path";
|
||||
import { spawn } from "node:child_process";
|
||||
|
||||
// ─── Execution ───────────────────────────────────────────────────────────────
|
||||
|
||||
|
|
@ -68,22 +69,14 @@ export function uploadFile(localPath: string, remotePath: string): void {
|
|||
|
||||
/** Launch an interactive shell session locally. */
|
||||
export async function interactiveSession(cmd: string): Promise<number> {
|
||||
const proc = Bun.spawn(
|
||||
[
|
||||
"bash",
|
||||
"-c",
|
||||
cmd,
|
||||
],
|
||||
{
|
||||
stdio: [
|
||||
"inherit",
|
||||
"inherit",
|
||||
"inherit",
|
||||
],
|
||||
return new Promise<number>((resolve, reject) => {
|
||||
const child = spawn("bash", ["-c", cmd], {
|
||||
stdio: "inherit",
|
||||
env: process.env,
|
||||
},
|
||||
);
|
||||
return proc.exited;
|
||||
});
|
||||
child.on("close", (code) => resolve(code ?? 0));
|
||||
child.on("error", reject);
|
||||
});
|
||||
}
|
||||
|
||||
// ─── Connection Tracking ─────────────────────────────────────────────────────
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// sprite/sprite.ts — Core Sprite provider: CLI installation, auth, provisioning, execution
|
||||
|
||||
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
||||
import { spawn } from "node:child_process";
|
||||
import {
|
||||
logInfo,
|
||||
logWarn,
|
||||
|
|
@ -592,14 +593,13 @@ export async function interactiveSession(cmd: string): Promise<number> {
|
|||
cmd,
|
||||
];
|
||||
|
||||
const proc = Bun.spawn(args, {
|
||||
stdio: [
|
||||
"inherit",
|
||||
"inherit",
|
||||
"inherit",
|
||||
],
|
||||
const exitCode = await new Promise<number>((resolve, reject) => {
|
||||
const child = spawn(args[0], args.slice(1), {
|
||||
stdio: "inherit",
|
||||
});
|
||||
child.on("close", (code) => resolve(code ?? 0));
|
||||
child.on("error", reject);
|
||||
});
|
||||
const exitCode = await proc.exited;
|
||||
|
||||
// Post-session summary
|
||||
process.stderr.write("\n");
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue