Improve ssh tran file transfer reliability

This commit is contained in:
Codex
2026-05-27 04:08:11 +00:00
parent 29ec9254bf
commit 27ed8a261d
8 changed files with 607 additions and 10 deletions
+32 -6
View File
@@ -1,6 +1,7 @@
import { spawn } from "node:child_process";
import { type UniDeskConfig, repoRoot } from "./config";
import { isApplyPatchV2HelpArgs, runApplyPatchV2, type ApplyPatchV2Executor } from "./apply-patch-v2";
import { isSshFileTransferOperation, runSshFileTransferOperation, type SshRemoteCommandExecutor } from "./ssh-file-transfer";
export interface ParsedSshArgs {
remoteCommand: string | null;
@@ -824,6 +825,9 @@ export function isSshSkillDiscoveryArgs(args: string[]): boolean {
export function parseSshArgs(args: string[]): ParsedSshArgs {
const subcommand = args[0] ?? "";
if (isSshFileTransferOperation(args)) {
return { remoteCommand: null, requiresStdin: false, invocationKind: "helper" };
}
if (isSshSkillDiscoveryArgs(args)) {
const toolArgs = subcommand === "skill" ? ["skill-discover", ...args.slice(2)] : ["skill-discover", ...args.slice(1)];
return { remoteCommand: shellArgv(toolArgs), requiresStdin: false, invocationKind: "helper", requiredHelpers: ["skill-discover"] };
@@ -981,6 +985,9 @@ function parseWinRouteArgs(route: ParsedSshRoute, args: string[]): ParsedSshArgs
if (operation.length === 0) {
throw new Error(`ssh ${route.raw} requires a Windows operation, for example: ssh ${route.providerId}:win cmd ver or ssh ${route.providerId}:win skills`);
}
if (operation === "upload" || operation === "download") {
return { remoteCommand: null, requiresStdin: false, invocationKind: "helper" };
}
if (operation === "skills" || operation === "skill-discover" || operation === "discover-skills") {
return {
remoteCommand: buildWindowsPowerShellInvocation(buildWindowsSkillsDiscoveryScript(args.slice(1))),
@@ -1103,7 +1110,7 @@ function buildWindowsCmdLauncherScript(cmdLine: string): string {
].join(" ");
}
function buildWindowsPowerShellInvocation(script: string): string {
export function buildWindowsPowerShellInvocation(script: string): string {
return shellArgv([
windowsPowerShellExePath,
"-NoProfile",
@@ -1292,6 +1299,9 @@ function parseK3sRouteArgs(route: ParsedSshRoute, args: string[]): ParsedSshArgs
function parseK3sControlPlaneOperation(route: ParsedSshRoute, args: string[]): ParsedSshArgs {
const operation = args[0] ?? "guard";
if (operation === "upload" || operation === "download") {
throw new Error(`ssh ${route.providerId}:k3s ${operation} requires a workload route: ssh ${route.providerId}:k3s:<namespace>:<workload> ${operation} ...`);
}
if (operation === "apply-patch" || operation === "apply-patch-v1") {
throw new Error(`ssh ${route.providerId}:k3s apply-patch requires a workload route: ssh ${route.providerId}:k3s:<namespace>:<workload> apply-patch`);
}
@@ -1323,6 +1333,9 @@ function parseK3sTargetOperation(route: ParsedSshRoute, args: string[]): ParsedS
}
const operation = args[0] ?? "";
const operationArgs = args.slice(1);
if (operation === "upload" || operation === "download") {
return { remoteCommand: null, requiresStdin: false, invocationKind: "helper" };
}
if (operation === "apply-patch") {
if (isApplyPatchV2HelpArgs(operationArgs)) {
return { remoteCommand: null, requiresStdin: false, invocationKind: "helper" };
@@ -1361,8 +1374,8 @@ function buildK3sTargetObjectCommand(action: "get" | "describe", route: ParsedSs
return shellArgv(["env", `KUBECONFIG=${nativeK3sKubeconfig}`, "kubectl", action, "-n", route.namespace, normalizeK3sRouteResource(route.resource), ...args]);
}
function buildK3sTargetCommand(route: ParsedSshRoute, command: string[]): string {
return buildK3sExecCommand([...k3sRouteTargetArgs(route), "--", ...command]);
function buildK3sTargetCommand(route: ParsedSshRoute, command: string[], options: { stdin?: boolean } = {}): string {
return buildK3sExecCommand([...k3sRouteTargetArgs(route), ...(options.stdin === true ? ["--stdin"] : []), "--", ...command]);
}
function k3sRouteTargetArgs(route: ParsedSshRoute): string[] {
@@ -2072,15 +2085,19 @@ function terminalSize(): { cols: number; rows: number } {
};
}
export function remoteCommandForRoute(route: ParsedSshRoute, command: string[]): string {
if (route.plane === "k3s") return buildK3sTargetCommand(route, command);
export function remoteCommandForRoute(route: ParsedSshRoute, command: string[], options: { stdin?: boolean } = {}): string {
if (route.plane === "k3s") return buildK3sTargetCommand(route, command, options);
if (route.plane === "win") throw new Error(`ssh apply-patch does not support win routes yet: ${route.raw}`);
return shellArgv(command);
}
async function runSshCaptureCommand(config: UniDeskConfig, invocation: ParsedSshInvocation, command: string[], input?: string): Promise<SshCaptureResult> {
const remoteCommand = remoteCommandForRoute(invocation.route, command, { stdin: input !== undefined });
return await runSshCaptureRemoteCommand(config, invocation, remoteCommand, input);
}
async function runSshCaptureRemoteCommand(config: UniDeskConfig, invocation: ParsedSshInvocation, remoteCommand: string, input?: string): Promise<SshCaptureResult> {
const startedAtMs = Date.now();
const remoteCommand = remoteCommandForRoute(invocation.route, command);
const size = terminalSize();
const runtimeTimeoutMs = sshRuntimeTimeoutMs();
const payload = {
@@ -2184,6 +2201,15 @@ export async function runSsh(config: UniDeskConfig, providerId: string, args: st
const invocation = parseSshInvocation(providerId, args);
const parsed = invocation.parsed;
const operationName = args[0] ?? "";
if (isSshFileTransferOperation(args)) {
const executor: SshRemoteCommandExecutor = {
runRemoteCommand: (remoteCommand, input) => runSshCaptureRemoteCommand(config, invocation, remoteCommand, input),
};
return await runSshFileTransferOperation(invocation, args, executor, {
buildRouteCommand: remoteCommandForRoute,
buildWindowsPowerShellCommand: buildWindowsPowerShellInvocation,
});
}
if (operationName === "apply-patch") {
const executor: ApplyPatchV2Executor = { run: (command, input) => runSshCaptureCommand(config, invocation, command, input) };
return await runApplyPatchV2({