Read and change a machine's files from the backend

The first half of EXPLORER.md: server/src/files.rs, which lists a
directory, reads a file, writes one, and creates a file or a directory on
whichever machine a setup names.

Each operation is one small POSIX script run through `Transport`, the way
the import listing and the usage fetch already ask a machine a question,
so the local and the ssh case are one implementation rather than two that
drift. The path crosses as a positional argument and never as script
text; `PATH_PRELUDE` is the one line that gives a leading `~` its
meaning, because a shell expands a tilde in text and not in an argument,
and it is the far machine's home that has to answer.

A read has four answers -- text, binary, tooBig, or the machine's own
error -- because a binary file drawn as text and a big one cut off
silently are both wrong in ways the reader cannot see. A write carries
the sha256 the read reported and is refused with a 409 when the file has
moved on, which is what happens whenever an agent is editing the file
somebody is reading.

`Transport::capture_with_input` is the one description of "run this
there, with this on stdin", and `ship_attachment` moves onto it rather
than assembling a second ssh invocation of its own. It is also the only
capture that hands back the exit status, which is how the write says
"this is not the file you read" without that answer looking like a
failure.

Exercised on both transports against the sandbox -- ssh to this VM with a
throwaway key, since the quoting and the stdin path are what that proves
-- including a filename with an apostrophe, one with a tab, an unreadable
file, a binary one, one over the limit, and the 409.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
irisandClaude Opus 5 committed 2026-09-03 23:28:00 -04:00
1 parent f6bee1b8a5
commit cc7e4f63ef
8 files changed
+1008 -46

No files matched your search

+33
View File
@@ -248,6 +248,39 @@ awk -v mb="$BIG_MB" 'BEGIN {
}
print "{\"type\":\"assistant\",\"message\":{\"role\":\"assistant\",\"usage\":{\"input_tokens\":180000,\"output_tokens\":900}}}"
}' > "$big"
# A tree for the file explorer, at the sandbox home's `~/files`, holding
# the states that are otherwise only reachable by finding a real machine
# in one of them. The default state is the one everybody looks at, so what
# is worth having here is the rest: an empty directory, names a shell would
# mangle, something that is not text, something too big to send, something
# nobody may read, a link that navigates, and one file per language so the
# highlighter is exercised rather than assumed.
FILES=$ROOT/home/files
mkdir -p "$FILES/empty" "$FILES/sub"
printf 'one\ntwo\nthree\n' >"$FILES/hello.txt"
# A real tab, via printf: `\t` inside double quotes is a backslash and a t,
# which is a different (and easier) name than the one worth testing.
tabbed=$(printf 'with\ta tab.txt')
printf 'a tab in the name\n' >"$FILES/$tabbed"
printf "an apostrophe in the name\n" >"$FILES/it's a file.txt"
printf 'fn main() {\n // a comment\n println!("hello, {}", 1_000);\n}\n' >"$FILES/main.rs"
printf 'fun main() {\n // a comment\n println("hello")\n}\n' >"$FILES/Main.kt"
printf 'def main():\n # a comment\n print("hello")\n' >"$FILES/main.py"
printf '#!/bin/sh\n# a comment\necho hello\n' >"$FILES/run.sh"
chmod +x "$FILES/run.sh"
printf '{"a": 1, "b": [true, null]}\n' >"$FILES/data.json"
# Not UTF-8, so it reads as binary rather than as mojibake.
printf '\377\376\000\001binary\n' >"$FILES/picture.bin"
# Over FILE_LIMIT (1 MiB), so the read refuses before anything transfers.
awk 'BEGIN { for (i = 0; i < 40000; i++) print "a line of a log that nobody is going to read" }' >"$FILES/big.log"
# Unreadable on purpose: a directory listing still shows it, and opening it
# fails with the machine's own words rather than with an empty file.
printf 'secret\n' >"$FILES/unreadable.txt"
chmod 000 "$FILES/unreadable.txt"
printf 'in a subdirectory\n' >"$FILES/sub/inside.txt"
ln -sfn sub "$FILES/link-to-sub"
ln -sfn nowhere "$FILES/broken-link"
fi
if [ -n "$regen_config" ]; then