fix: keep active ssh downloads alive

This commit is contained in:
Codex
2026-06-11 18:08:56 +00:00
parent 0a9d07c760
commit a8d49845ca
5 changed files with 132 additions and 23 deletions
+33 -3
View File
@@ -1311,6 +1311,7 @@ async function runRemoteSshWebSocketStreamRemoteCommand(
remoteCommand: string,
handlers: SshRemoteCommandStreamHandlers,
input?: string,
options: { inactivityTimeoutMs?: number } = {},
): Promise<SshCaptureResult> {
const streamInvocation = {
...invocation,
@@ -1321,7 +1322,8 @@ async function runRemoteSshWebSocketStreamRemoteCommand(
cols: Number(process.stdout.columns) > 0 ? Number(process.stdout.columns) : 100,
rows: Number(process.stdout.rows) > 0 ? Number(process.stdout.rows) : 30,
};
const runtimeTimeoutMs = sshRuntimeTimeoutMs();
const inactivityTimeoutMs = options.inactivityTimeoutMs ?? sshRuntimeTimeoutMs();
const runtimeTimeoutMs = options.inactivityTimeoutMs === undefined ? inactivityTimeoutMs : Math.max(inactivityTimeoutMs * 4, 60 * 60_000);
const payload = {
providerId: invocation.providerId,
command: wrapSshRemoteCommand(remoteCommand),
@@ -1330,6 +1332,7 @@ async function runRemoteSshWebSocketStreamRemoteCommand(
stdinEotOnEnd: false,
openTimeoutMs: Math.max(15000, Number(process.env.UNIDESK_SSH_OPEN_TIMEOUT_MS || 60000)),
runtimeTimeoutMs,
runtimeTimeoutMode: options.inactivityTimeoutMs === undefined ? "wall-clock" : "inactivity",
cols: size.cols,
rows: size.rows,
};
@@ -1395,11 +1398,32 @@ async function runRemoteSshWebSocketStreamRemoteCommand(
return await new Promise<SshCaptureResult>((resolve) => {
let killTimer: ReturnType<typeof setTimeout> | null = null;
let inactivityTimer: ReturnType<typeof setTimeout> | null = null;
const refreshActivityTimer = (): void => {
if (settled || options.inactivityTimeoutMs === undefined) return;
if (inactivityTimer !== null) clearTimeout(inactivityTimer);
inactivityTimer = setTimeout(() => {
exitCode = 124;
stderr += formatSshRuntimeTimeoutHint(sshRuntimeTimeoutHint({
invocation: streamInvocation,
transport: "frontend-websocket",
timeoutMs: inactivityTimeoutMs,
}));
try {
ws.close();
} catch {
// Ignore.
}
killTimer = setTimeout(() => finish(124), 2000);
finish(124);
}, inactivityTimeoutMs);
};
const finish = (code: number): void => {
if (settled) return;
settled = true;
clearTimeout(openTimer);
clearTimeout(runtimeTimer);
if (inactivityTimer !== null) clearTimeout(inactivityTimer);
if (killTimer !== null) clearTimeout(killTimer);
void streamWrites.then(() => {
const finalCode = streamError === null ? code : 255;
@@ -1439,6 +1463,7 @@ async function runRemoteSshWebSocketStreamRemoteCommand(
killTimer = setTimeout(() => finish(124), 2000);
finish(124);
}, runtimeTimeoutMs);
refreshActivityTimer();
ws.addEventListener("open", () => {
canSend = true;
@@ -1454,16 +1479,21 @@ async function runRemoteSshWebSocketStreamRemoteCommand(
stderr += `${text}\n`;
return;
}
if (message.type === "ssh.dispatched") return;
if (message.type === "ssh.dispatched") {
refreshActivityTimer();
return;
}
if (message.type === "ssh.opened") {
sessionReady = true;
clearTimeout(openTimer);
refreshActivityTimer();
if (input !== undefined) sendInputChunked(input);
sendWhenSessionReady({ type: "ssh.eof" });
flushSessionMessages();
return;
}
if (message.type === "ssh.data") {
refreshActivityTimer();
const chunk = Buffer.from(String(message.data ?? ""), message.encoding === "base64" ? "base64" : "utf8");
if (message.stream === "stderr") {
stderr += chunk.toString("utf8");
@@ -1514,7 +1544,7 @@ async function runRemoteSshOverFrontend(session: FrontendSession, target: string
if (isSshFileTransferOperation(normalizedArgs)) {
const executor: SshRemoteCommandExecutor = {
runRemoteCommand: (remoteCommand, input) => runRemoteSshWebSocketCaptureRemoteCommand(session, invocation, remoteCommand, input),
streamRemoteCommand: (remoteCommand, handlers, input) => runRemoteSshWebSocketStreamRemoteCommand(session, invocation, remoteCommand, handlers, input),
streamRemoteCommand: (remoteCommand, handlers, input, options) => runRemoteSshWebSocketStreamRemoteCommand(session, invocation, remoteCommand, handlers, input, options),
};
return await runSshFileTransferOperation(invocation, normalizedArgs, executor, {
buildRouteCommand: remoteCommandForRoute,