feat: add aipod spec Artificer assembly
This commit is contained in:
@@ -0,0 +1,385 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
allow_loose=0
|
||||
|
||||
die() {
|
||||
printf 'apply_patch: %s\n' "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
mk_temp() {
|
||||
mktemp "${TMPDIR:-/tmp}/unidesk-apply-patch.XXXXXX" || exit 1
|
||||
}
|
||||
|
||||
ensure_parent() {
|
||||
case "$1" in
|
||||
*/*)
|
||||
dir=${1%/*}
|
||||
[ -n "$dir" ] || dir=/
|
||||
mkdir -p "$dir"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
read_text_preserve_newlines() {
|
||||
marker="__UNIDESK_APPLY_PATCH_EOF_$$__"
|
||||
text=$(cat "$1"; printf '%s' "$marker") || die "failed to read $1"
|
||||
printf '%s' "${text%"$marker"}"
|
||||
}
|
||||
|
||||
write_file() {
|
||||
target=$1
|
||||
source=$2
|
||||
ensure_parent "$target"
|
||||
cp "$source" "$target" || die "failed to write $target"
|
||||
}
|
||||
|
||||
line_number_for_prefix() {
|
||||
newline_count=$(printf '%s' "$1" | tr -cd '\n' | wc -c | tr -d ' ')
|
||||
printf '%s\n' $((newline_count + 1))
|
||||
}
|
||||
|
||||
replace_once_with_perl() {
|
||||
command -v perl >/dev/null 2>&1 || return 127
|
||||
perl -0777 -e '
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
sub fail {
|
||||
print STDERR "apply_patch: ", @_, "\n";
|
||||
exit 1;
|
||||
}
|
||||
|
||||
sub read_all {
|
||||
my ($path, $label) = @_;
|
||||
open my $fh, "<", $path or fail("failed to read $label");
|
||||
binmode $fh;
|
||||
local $/;
|
||||
my $data = <$fh>;
|
||||
close $fh;
|
||||
return defined $data ? $data : "";
|
||||
}
|
||||
|
||||
my ($target, $search_file, $replace_file, $hunk_id, $allow_loose, $out) = @ARGV;
|
||||
my $old = read_all($target, $target);
|
||||
my $search = read_all($search_file, "hunk search");
|
||||
my $replacement = read_all($replace_file, "hunk replacement");
|
||||
my $new;
|
||||
|
||||
if ($search eq "") {
|
||||
fail("hunk $hunk_id in $target has no context; add unchanged/deleted anchor lines or pass --allow-loose after manual review") if $allow_loose ne "1";
|
||||
print STDERR "apply_patch: hunk $hunk_id matched $target:1 (loose)\n";
|
||||
$new = $replacement . $old;
|
||||
} else {
|
||||
my $pos = -1;
|
||||
my $offset = 0;
|
||||
my $count = 0;
|
||||
while (1) {
|
||||
my $found = index($old, $search, $offset);
|
||||
last if $found < 0;
|
||||
$pos = $found if $count == 0;
|
||||
$count += 1;
|
||||
last if $count > 1 && $allow_loose ne "1";
|
||||
$offset = $found + length($search);
|
||||
}
|
||||
fail("hunk $hunk_id context not found in $target") if $count == 0;
|
||||
fail("hunk $hunk_id context matched multiple locations in $target; add more unchanged context or pass --allow-loose after manual review") if $count > 1 && $allow_loose ne "1";
|
||||
my $prefix = substr($old, 0, $pos);
|
||||
my $suffix = substr($old, $pos + length($search));
|
||||
my $line = ($prefix =~ tr/\n//) + 1;
|
||||
print STDERR "apply_patch: hunk $hunk_id matched $target:$line\n";
|
||||
$new = $prefix . $replacement . $suffix;
|
||||
}
|
||||
|
||||
open my $ofh, ">", $out or fail("failed to render patched file");
|
||||
binmode $ofh;
|
||||
print $ofh $new or fail("failed to render patched file");
|
||||
close $ofh or fail("failed to render patched file");
|
||||
' "$@"
|
||||
}
|
||||
|
||||
replace_once() {
|
||||
target=$1
|
||||
search_file=$2
|
||||
replace_file=$3
|
||||
hunk_id=$4
|
||||
[ -e "$target" ] || die "file not found: $target"
|
||||
|
||||
fast_out=$(mk_temp)
|
||||
if replace_once_with_perl "$target" "$search_file" "$replace_file" "$hunk_id" "$allow_loose" "$fast_out"; then
|
||||
write_file "$target" "$fast_out"
|
||||
rm -f "$fast_out"
|
||||
return 0
|
||||
else
|
||||
status=$?
|
||||
fi
|
||||
rm -f "$fast_out"
|
||||
[ "$status" = 127 ] || exit "$status"
|
||||
|
||||
marker="__UNIDESK_APPLY_PATCH_EOF_$$__"
|
||||
old=$(cat "$target"; printf '%s' "$marker") || die "failed to read $target"
|
||||
old=${old%"$marker"}
|
||||
search=$(cat "$search_file"; printf '%s' "$marker") || die "failed to read hunk search"
|
||||
search=${search%"$marker"}
|
||||
replacement=$(cat "$replace_file"; printf '%s' "$marker") || die "failed to read hunk replacement"
|
||||
replacement=${replacement%"$marker"}
|
||||
|
||||
if [ -z "$search" ]; then
|
||||
[ "$allow_loose" = 1 ] || die "hunk $hunk_id in $target has no context; add unchanged/deleted anchor lines or pass --allow-loose after manual review"
|
||||
printf 'apply_patch: hunk %s matched %s:1 (loose)\n' "$hunk_id" "$target" >&2
|
||||
new=$replacement$old
|
||||
else
|
||||
case "$old" in
|
||||
*"$search"*)
|
||||
prefix=${old%%"$search"*}
|
||||
suffix=${old#*"$search"}
|
||||
case "$suffix" in
|
||||
*"$search"*)
|
||||
[ "$allow_loose" = 1 ] || die "hunk $hunk_id context matched multiple locations in $target; add more unchanged context or pass --allow-loose after manual review"
|
||||
;;
|
||||
esac
|
||||
printf 'apply_patch: hunk %s matched %s:%s\n' "$hunk_id" "$target" "$(line_number_for_prefix "$prefix")" >&2
|
||||
new=$prefix$replacement$suffix
|
||||
;;
|
||||
*)
|
||||
die "hunk $hunk_id context not found in $target"
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
out=$(mk_temp)
|
||||
printf '%s' "$new" > "$out" || die "failed to render patched file"
|
||||
write_file "$target" "$out"
|
||||
rm -f "$out"
|
||||
}
|
||||
|
||||
apply_update() {
|
||||
target=$1
|
||||
body=$2
|
||||
in_hunk=0
|
||||
search_file=
|
||||
replace_file=
|
||||
changed=0
|
||||
hunk_number=0
|
||||
has_add=0
|
||||
has_delete=0
|
||||
seen_add=0
|
||||
anchor_before_add=0
|
||||
anchor_after_add=0
|
||||
explicit_eof=0
|
||||
|
||||
finish_hunk() {
|
||||
[ "$in_hunk" = 1 ] || return 0
|
||||
if [ "$allow_loose" != 1 ]; then
|
||||
[ -s "$search_file" ] || die "hunk $hunk_number in $target has no context; add unchanged/deleted anchor lines or pass --allow-loose after manual review"
|
||||
if [ "$has_add" = 1 ] && [ "$has_delete" = 0 ]; then
|
||||
if [ "$anchor_before_add" != 1 ] || { [ "$anchor_after_add" != 1 ] && [ "$explicit_eof" != 1 ]; }; then
|
||||
die "hunk $hunk_number in $target is insert-only without both leading and trailing context; include nearby unchanged lines after the insertion or pass --allow-loose after manual review"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
replace_once "$target" "$search_file" "$replace_file" "$hunk_number"
|
||||
rm -f "$search_file" "$replace_file"
|
||||
search_file=
|
||||
replace_file=
|
||||
in_hunk=0
|
||||
changed=1
|
||||
}
|
||||
|
||||
while IFS= read -r line || [ -n "$line" ]; do
|
||||
case "$line" in
|
||||
"*** End of File"*)
|
||||
continue
|
||||
;;
|
||||
"@@"*)
|
||||
finish_hunk
|
||||
search_file=$(mk_temp)
|
||||
replace_file=$(mk_temp)
|
||||
hunk_number=$((hunk_number + 1))
|
||||
has_add=0
|
||||
has_delete=0
|
||||
seen_add=0
|
||||
anchor_before_add=0
|
||||
anchor_after_add=0
|
||||
explicit_eof=0
|
||||
in_hunk=1
|
||||
continue
|
||||
;;
|
||||
esac
|
||||
[ "$in_hunk" = 1 ] || die "expected hunk header in $target"
|
||||
case "$line" in
|
||||
" "*)
|
||||
text=${line#?}
|
||||
printf '%s\n' "$text" >> "$search_file"
|
||||
printf '%s\n' "$text" >> "$replace_file"
|
||||
if [ "$seen_add" = 1 ]; then anchor_after_add=1; else anchor_before_add=1; fi
|
||||
;;
|
||||
"-"*)
|
||||
text=${line#?}
|
||||
printf '%s\n' "$text" >> "$search_file"
|
||||
has_delete=1
|
||||
if [ "$seen_add" = 1 ]; then anchor_after_add=1; else anchor_before_add=1; fi
|
||||
;;
|
||||
"+"*)
|
||||
text=${line#?}
|
||||
printf '%s\n' "$text" >> "$replace_file"
|
||||
has_add=1
|
||||
seen_add=1
|
||||
;;
|
||||
"\\ No newline at end of file")
|
||||
;;
|
||||
"*** End of File"*)
|
||||
explicit_eof=1
|
||||
;;
|
||||
*)
|
||||
die "bad hunk line in $target: $line"
|
||||
;;
|
||||
esac
|
||||
done < "$body"
|
||||
|
||||
finish_hunk
|
||||
[ "$changed" = 1 ] || [ -e "$target" ] || die "file not found: $target"
|
||||
}
|
||||
|
||||
is_top_header() {
|
||||
case "$1" in
|
||||
"*** Add File: "*|"*** Update File: "*|"*** Delete File: "*|"*** End Patch")
|
||||
return 0
|
||||
;;
|
||||
*)
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
pushed=0
|
||||
pushed_line=
|
||||
next_patch_line() {
|
||||
if [ "$pushed" = 1 ]; then
|
||||
line=$pushed_line
|
||||
pushed=0
|
||||
pushed_line=
|
||||
return 0
|
||||
fi
|
||||
if IFS= read -r line; then
|
||||
return 0
|
||||
fi
|
||||
[ -n "${line:-}" ]
|
||||
}
|
||||
|
||||
push_patch_line() {
|
||||
pushed_line=$1
|
||||
pushed=1
|
||||
}
|
||||
|
||||
parse_add_file() {
|
||||
target=$1
|
||||
[ ! -e "$target" ] || die "file already exists: $target"
|
||||
tmp=$(mk_temp)
|
||||
while next_patch_line; do
|
||||
if is_top_header "$line"; then
|
||||
push_patch_line "$line"
|
||||
break
|
||||
fi
|
||||
case "$line" in
|
||||
"+"*)
|
||||
printf '%s\n' "${line#?}" >> "$tmp"
|
||||
;;
|
||||
*)
|
||||
rm -f "$tmp"
|
||||
die "add file lines must start with + for $target"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
write_file "$target" "$tmp"
|
||||
rm -f "$tmp"
|
||||
}
|
||||
|
||||
parse_update_file() {
|
||||
target=$1
|
||||
move_to=
|
||||
body=$(mk_temp)
|
||||
if next_patch_line; then
|
||||
case "$line" in
|
||||
"*** Move to: "*)
|
||||
move_to=${line#"*** Move to: "}
|
||||
;;
|
||||
*)
|
||||
push_patch_line "$line"
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
while next_patch_line; do
|
||||
if is_top_header "$line"; then
|
||||
push_patch_line "$line"
|
||||
break
|
||||
fi
|
||||
case "$line" in
|
||||
"*** Move to: "*)
|
||||
rm -f "$body"
|
||||
die "move marker must appear before update hunks"
|
||||
;;
|
||||
*)
|
||||
printf '%s\n' "$line" >> "$body"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
if [ -s "$body" ]; then
|
||||
apply_update "$target" "$body"
|
||||
elif [ -z "$move_to" ] && [ ! -e "$target" ]; then
|
||||
rm -f "$body"
|
||||
die "file not found: $target"
|
||||
fi
|
||||
rm -f "$body"
|
||||
if [ -n "$move_to" ]; then
|
||||
[ -e "$target" ] || die "file not found: $target"
|
||||
[ ! -e "$move_to" ] || die "target file already exists: $move_to"
|
||||
ensure_parent "$move_to"
|
||||
mv "$target" "$move_to" || die "failed to move $target to $move_to"
|
||||
fi
|
||||
}
|
||||
|
||||
main() {
|
||||
while [ "$#" -gt 0 ]; do
|
||||
case "$1" in
|
||||
-h|--help)
|
||||
printf 'apply_patch: sh-only helper; reads *** Begin Patch format from stdin; --allow-loose bypasses low-context hunk guards\n'
|
||||
return 0
|
||||
;;
|
||||
--allow-loose)
|
||||
allow_loose=1
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
die "unsupported option: $1"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
next_patch_line || die "patch must start with *** Begin Patch"
|
||||
[ "$line" = "*** Begin Patch" ] || die "patch must start with *** Begin Patch"
|
||||
while next_patch_line; do
|
||||
case "$line" in
|
||||
"*** End Patch")
|
||||
printf 'Done!\n'
|
||||
return 0
|
||||
;;
|
||||
"*** Add File: "*)
|
||||
parse_add_file "${line#"*** Add File: "}"
|
||||
;;
|
||||
"*** Delete File: "*)
|
||||
target=${line#"*** Delete File: "}
|
||||
rm -f "$target" || die "failed to delete $target"
|
||||
;;
|
||||
"*** Update File: "*)
|
||||
parse_update_file "${line#"*** Update File: "}"
|
||||
;;
|
||||
*)
|
||||
die "unexpected patch line: $line"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
die "missing *** End Patch"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
+59
-3
@@ -14,12 +14,14 @@ function jsonHelp() {
|
||||
"tran <provider> argv <command...>",
|
||||
"tran <provider> script -- '<shell script>'",
|
||||
"tran <provider>:/absolute/workspace script -- '<shell script>'",
|
||||
"tran <provider>:/absolute/workspace apply-patch < patch.diff",
|
||||
"tran <provider>:k3s kubectl <args...>",
|
||||
"tran <provider>:k3s script -- '<shell script>'",
|
||||
"tran <provider>:k3s:<namespace>:<workload>[:container] argv <command...>",
|
||||
"tran <provider>:k3s:<namespace>:<workload>[:container] script -- '<shell script>'",
|
||||
"tran <provider>:k3s:<namespace>:<workload>[:container] apply-patch < patch.diff",
|
||||
],
|
||||
unsupported: ["apply-patch", "upload", "download", "Windows win/ps/cmd routes"],
|
||||
unsupported: ["upload", "download", "Windows win/ps/cmd routes"],
|
||||
valuesPrinted: false,
|
||||
};
|
||||
}
|
||||
@@ -87,6 +89,54 @@ async function readStdinText() {
|
||||
return Buffer.concat(chunks).toString("utf8");
|
||||
}
|
||||
|
||||
async function readApplyPatchHelperSource() {
|
||||
const helperPath = new URL("./apply_patch", import.meta.url);
|
||||
try {
|
||||
const text = await Bun.file(helperPath).text();
|
||||
if (!text.startsWith("#!")) fail("infra-failed", "adjacent apply_patch helper is not executable script shaped", { helper: "tools/apply_patch" });
|
||||
return text;
|
||||
} catch (error) {
|
||||
fail("infra-failed", "adjacent apply_patch helper is required for runner-side tran apply-patch", { helper: "tools/apply_patch", error: error instanceof Error ? error.message : String(error) });
|
||||
}
|
||||
}
|
||||
|
||||
function applyPatchToolArgs(args) {
|
||||
const supported = new Set(["--allow-loose", "--help", "-h"]);
|
||||
for (const arg of args) {
|
||||
if (!supported.has(arg)) fail("schema-invalid", `unsupported apply-patch option: ${arg}`, { supported: [...supported].sort() });
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
function base64Text(value) {
|
||||
return Buffer.from(value, "utf8").toString("base64").replace(/.{1,76}/g, "$&\n").trimEnd();
|
||||
}
|
||||
|
||||
async function applyPatchRemoteScript(args) {
|
||||
const toolArgs = applyPatchToolArgs(args);
|
||||
const [helperSource, patchText] = await Promise.all([readApplyPatchHelperSource(), readStdinText()]);
|
||||
if (patchText.trim().length === 0) fail("schema-invalid", "apply-patch requires patch text on stdin");
|
||||
const helperMarker = "__AGENTRUN_TRAN_APPLY_PATCH_HELPER__";
|
||||
const patchMarker = "__AGENTRUN_TRAN_APPLY_PATCH_BODY__";
|
||||
const toolArgText = toolArgs.length > 0 ? ` ${shellArgv(toolArgs)}` : "";
|
||||
return [
|
||||
"set -eu",
|
||||
"helper=$(mktemp \"${TMPDIR:-/tmp}/agentrun-apply-patch.XXXXXX\") || exit 1",
|
||||
"patch_file=$(mktemp \"${TMPDIR:-/tmp}/agentrun-apply-patch-body.XXXXXX\") || exit 1",
|
||||
"cleanup() { rm -f \"$helper\" \"$patch_file\"; }",
|
||||
"trap cleanup EXIT HUP INT TERM",
|
||||
"decode_b64() { base64 -d; }",
|
||||
`decode_b64 > \"$helper\" <<'${helperMarker}'`,
|
||||
base64Text(helperSource),
|
||||
helperMarker,
|
||||
`decode_b64 > \"$patch_file\" <<'${patchMarker}'`,
|
||||
base64Text(patchText),
|
||||
patchMarker,
|
||||
"chmod 700 \"$helper\"",
|
||||
`\"$helper\"${toolArgText} < \"$patch_file\"`,
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
async function scriptCommand(args) {
|
||||
if (args[0] === "--") {
|
||||
const rest = args.slice(1);
|
||||
@@ -101,7 +151,8 @@ async function scriptCommand(args) {
|
||||
async function hostCommand(route, args) {
|
||||
if (args.length === 0) return { command: null, cwd: route.workspace, tty: true };
|
||||
const op = args[0];
|
||||
if (op === "apply-patch" || op === "upload" || op === "download") {
|
||||
if (op === "apply-patch") return { command: await applyPatchRemoteScript(args.slice(1)), cwd: route.workspace, tty: false };
|
||||
if (op === "upload" || op === "download") {
|
||||
fail("unsupported-operation", `AgentRun tran does not support ${op}; use host/source controlled tools outside the runner for that operation`, { operation: op });
|
||||
}
|
||||
if (op === "script" || op === "shell") return { command: await scriptCommand(args.slice(1)), cwd: route.workspace, tty: false };
|
||||
@@ -121,10 +172,11 @@ function k3sExecPrefix(route) {
|
||||
|
||||
async function k3sCommand(route, args) {
|
||||
const op = args[0] || "kubectl";
|
||||
if (op === "apply-patch" || op === "upload" || op === "download") {
|
||||
if (op === "upload" || op === "download") {
|
||||
fail("unsupported-operation", `AgentRun tran does not support ${op}; use host/source controlled tools outside the runner for that operation`, { operation: op });
|
||||
}
|
||||
if (!route.resource) {
|
||||
if (op === "apply-patch") fail("schema-invalid", "k3s apply-patch requires namespace and workload route", { route: route.raw });
|
||||
if (op === "kubectl") return { command: shellArgv(["env", "KUBECONFIG=/etc/rancher/k3s/k3s.yaml", "kubectl", ...args.slice(1)]), cwd: null, tty: false };
|
||||
if (op === "script" || op === "shell") {
|
||||
const script = await scriptCommand(args.slice(1));
|
||||
@@ -133,6 +185,10 @@ async function k3sCommand(route, args) {
|
||||
if (op === "argv") return { command: shellArgv(["env", "KUBECONFIG=/etc/rancher/k3s/k3s.yaml", ...args.slice(1)]), cwd: null, tty: false };
|
||||
return { command: shellArgv(["env", "KUBECONFIG=/etc/rancher/k3s/k3s.yaml", ...args]), cwd: null, tty: false };
|
||||
}
|
||||
if (op === "apply-patch") {
|
||||
const script = await applyPatchRemoteScript(args.slice(1));
|
||||
return { command: shellArgv([...k3sExecPrefix(route), "sh", "-lc", script]), cwd: null, tty: false };
|
||||
}
|
||||
if (op === "script" || op === "shell") {
|
||||
const script = await scriptCommand(args.slice(1));
|
||||
return { command: shellArgv([...k3sExecPrefix(route), "sh", "-lc", script]), cwd: null, tty: false };
|
||||
|
||||
Reference in New Issue
Block a user