feat: add remote apply-patch passthrough and pipeline timeline polish

This commit is contained in:
Codex
2026-05-07 07:25:41 +00:00
parent 5945c720a6
commit c4f57510e7
5 changed files with 913 additions and 22 deletions
+202 -6
View File
@@ -5,11 +5,159 @@ export interface ParsedSshArgs {
remoteCommand: string | null;
}
const remoteApplyPatchSource = String.raw`#!/usr/bin/env python3
import sys
from pathlib import Path
def die(message):
print(f"apply_patch: {message}", file=sys.stderr)
sys.exit(1)
def write_file(path, text):
target = Path(path)
target.parent.mkdir(parents=True, exist_ok=True)
target.write_text(text)
def move_file(source, destination):
source_path = Path(source)
destination_path = Path(destination)
if not source_path.exists():
die(f"file not found: {source}")
if destination_path.exists():
die(f"target file already exists: {destination}")
destination_path.parent.mkdir(parents=True, exist_ok=True)
source_path.rename(destination_path)
def read_file(path):
try:
return Path(path).read_text()
except FileNotFoundError:
die(f"file not found: {path}")
def apply_update(path, body):
old = read_file(path)
output = []
search_from = 0
index = 0
while index < len(body):
if body[index].startswith("*** End of File"):
index += 1
continue
if not body[index].startswith("@@"):
die(f"expected hunk header in {path}")
index += 1
sequence = []
while index < len(body) and not body[index].startswith("@@"):
line = body[index]
index += 1
if line.startswith("*** End of File"):
continue
if line.startswith(" "):
sequence.append((" ", line[1:]))
elif line.startswith("-"):
sequence.append(("-", line[1:]))
elif line.startswith("+"):
sequence.append(("+", line[1:]))
elif line.rstrip("\n") == r"\ No newline at end of file":
continue
else:
die(f"bad hunk line in {path}: {line[:80].rstrip()}")
search = "".join(text for kind, text in sequence if kind in (" ", "-"))
replacement = "".join(text for kind, text in sequence if kind in (" ", "+"))
offset = old.find(search, search_from)
if offset < 0:
offset = old.find(search)
if offset < 0:
die(f"hunk context not found in {path}")
output.append(old[search_from:offset])
output.append(replacement)
search_from = offset + len(search)
output.append(old[search_from:])
write_file(path, "".join(output))
def main():
if len(sys.argv) > 1 and sys.argv[1] in ("-h", "--help"):
print("apply_patch: read *** Begin Patch format from stdin; supports add/update/delete/move")
return
lines = sys.stdin.read().splitlines(keepends=True)
if not lines or lines[0].strip() != "*** Begin Patch":
die("patch must start with *** Begin Patch")
index = 1
while index < len(lines):
line = lines[index].rstrip("\n")
if line == "*** End Patch":
print("Done!")
return
if line.startswith("*** Add File: "):
path = line[len("*** Add File: "):].strip()
index += 1
content = []
while index < len(lines) and not lines[index].startswith("*** "):
if not lines[index].startswith("+"):
die(f"add file lines must start with + for {path}")
content.append(lines[index][1:])
index += 1
if Path(path).exists():
die(f"file already exists: {path}")
write_file(path, "".join(content))
continue
if line.startswith("*** Delete File: "):
path = line[len("*** Delete File: "):].strip()
try:
Path(path).unlink()
except FileNotFoundError:
die(f"file not found: {path}")
index += 1
continue
if line.startswith("*** Update File: "):
path = line[len("*** Update File: "):].strip()
index += 1
move_to = None
if index < len(lines) and lines[index].startswith("*** Move to: "):
move_to = lines[index][len("*** Move to: "):].strip()
index += 1
body = []
while index < len(lines) and not (
lines[index].startswith("*** Add File: ")
or lines[index].startswith("*** Update File: ")
or lines[index].startswith("*** Delete File: ")
or lines[index].startswith("*** End Patch")
):
if lines[index].startswith("*** Move to: "):
die("move marker must appear before update hunks")
body.append(lines[index])
index += 1
if body:
apply_update(path, body)
elif move_to is None and not Path(path).exists():
die(f"file not found: {path}")
if move_to is not None:
move_file(path, move_to)
continue
die(f"unexpected patch line: {line}")
die("missing *** End Patch")
if __name__ == "__main__":
main()
`;
const sshOptionsWithValue = new Set([
"-B", "-b", "-c", "-D", "-E", "-e", "-F", "-I", "-i", "-J", "-L", "-l", "-m", "-O", "-o", "-p", "-Q", "-R", "-S", "-W", "-w",
]);
export function parseSshArgs(args: string[]): ParsedSshArgs {
const subcommand = args[0] ?? "";
if (subcommand === "apply-patch" || subcommand === "patch") {
const toolArgs = ["apply_patch", ...args.slice(1)];
return { remoteCommand: toolArgs.map(shellQuote).join(" ") };
}
const remote: string[] = [];
let remoteStarted = false;
for (let index = 0; index < args.length; index += 1) {
@@ -32,6 +180,27 @@ export function parseSshArgs(args: string[]): ParsedSshArgs {
return { remoteCommand: remote.length === 0 ? null : remote.join(" ") };
}
function shellQuote(value: string): string {
return `'${value.replace(/'/g, `'\\''`)}'`;
}
function remoteToolBootstrapCommand(): string {
const encoded = Buffer.from(remoteApplyPatchSource, "utf8").toString("base64");
return [
"UNIDESK_SSH_TOOL_DIR=/tmp/unidesk-ssh-tools",
'mkdir -p "$UNIDESK_SSH_TOOL_DIR"',
`printf %s ${shellQuote(encoded)} | base64 -d > "$UNIDESK_SSH_TOOL_DIR/apply_patch"`,
'chmod 700 "$UNIDESK_SSH_TOOL_DIR/apply_patch"',
'export PATH="$UNIDESK_SSH_TOOL_DIR:$PATH"',
].join("; ");
}
function wrapRemoteCommand(command: string | null): string {
const bootstrap = remoteToolBootstrapCommand();
if (command === null) return `${bootstrap}; exec "\${SHELL:-/bin/bash}" -l`;
return `${bootstrap}; stty -echo 2>/dev/null || true; ${command}`;
}
function brokerSource(): string {
return String.raw`
const open = JSON.parse(process.argv[2] || process.argv[1] || "{}");
@@ -40,8 +209,10 @@ const url = "ws://127.0.0.1:8080/ws/ssh?token=" + encodeURIComponent(token);
const ws = new WebSocket(url);
let exitCode = 255;
let canSend = false;
let sessionReady = false;
let opened = false;
const pending = [];
const pendingInput = [];
const openTimer = setTimeout(() => {
if (opened) return;
process.stderr.write("unidesk ssh bridge timed out waiting for provider session\n");
@@ -64,6 +235,22 @@ function flush() {
}
}
function sendInput(value) {
const text = JSON.stringify(value);
if (!sessionReady || ws.readyState !== WebSocket.OPEN) {
pendingInput.push(text);
return;
}
ws.send(text);
}
function flushInput() {
if (!sessionReady || ws.readyState !== WebSocket.OPEN) return;
while (pendingInput.length > 0) {
ws.send(pendingInput.shift());
}
}
function decodeData(data) {
return typeof data === "string" ? data : Buffer.from(data).toString("utf8");
}
@@ -92,10 +279,15 @@ ws.addEventListener("message", (event) => {
}
if (message.type === "ssh.opened") {
opened = true;
sessionReady = true;
clearTimeout(openTimer);
if (open.stdinEotOnEnd === true) setTimeout(flushInput, 200);
else flushInput();
return;
}
if (message.type === "ssh.dispatched") {
return;
}
if (message.type === "ssh.dispatched") return;
if (message.type === "ssh.error") {
clearTimeout(openTimer);
process.stderr.write(String(message.message || "ssh bridge error") + "\n");
@@ -120,12 +312,13 @@ ws.addEventListener("error", () => {
});
process.stdin.on("data", (chunk) => {
send({ type: "ssh.input", data: Buffer.from(chunk).toString("base64"), encoding: "base64" });
flush();
sendInput({ type: "ssh.input", data: Buffer.from(chunk).toString("base64"), encoding: "base64" });
});
process.stdin.on("end", () => {
send({ type: "ssh.eof" });
flush();
if (open.stdinEotOnEnd === true) {
sendInput({ type: "ssh.input", data: Buffer.from([4]).toString("base64"), encoding: "base64" });
}
sendInput({ type: "ssh.eof" });
});
`;
}
@@ -141,9 +334,12 @@ export async function runSsh(config: UniDeskConfig, providerId: string, args: st
if (!providerId) throw new Error("ssh requires provider id, for example: bun scripts/cli.ts ssh D518");
const parsed = parseSshArgs(args);
const size = terminalSize();
const openTimeoutMs = Math.max(15000, Number(process.env.UNIDESK_SSH_OPEN_TIMEOUT_MS || 60000));
const payload = {
providerId,
command: parsed.remoteCommand,
command: wrapRemoteCommand(parsed.remoteCommand),
stdinEotOnEnd: parsed.remoteCommand !== null,
openTimeoutMs,
cols: size.cols,
rows: size.rows,
};