Worker: runs can keep the files they generate
grok's Docscope round generated a real sample .docx and the finished page with scripts inside run, then tried to cp them into place: each run starts from a fresh checkout, so none of it reached the branch and the round burned its steps. The run tool now takes keep: [paths] and commits exactly those files afterwards (inside the checkout, not workflows, up to 2 MB, at most ten), and the rules say plainly that everything else a run does is thrown away. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UpT7eWzeLhRwuYmsSQnbRb
This commit is contained in:
1 parent
f64a48300d
commit
23d6383060
2 files changed
+47
-5
No files matched your search
@@ -736,3 +736,18 @@ assert any("below the first screen" in p for p in vt.layout_problems({"phone": {
|
||||
b31 = vt.build_body({**w16, "prior": "ventures/mdecks/v0.html", "prior_note": "Keep its look and layout."}, plan16)
|
||||
assert "ventures/mdecks/v0.html" in b31 and "Keep its look and layout." in b31
|
||||
print("ALL OK 23")
|
||||
|
||||
# 32 a run can land the files it generated (keep), and nothing outside the checkout or in workflows
|
||||
import tempfile as _tf
|
||||
d32 = _tf.mkdtemp()
|
||||
os.makedirs(os.path.join(d32, "ventures/x"))
|
||||
W32 = World([card(1, D)])
|
||||
tm.http = W32.http
|
||||
h = tm.Hands.__new__(tm.Hands)
|
||||
h.n, h.branch, h.pr, h.steps, h.checkout = 1, "gh-pages", None, 0, None
|
||||
h._checkout = lambda: d32
|
||||
out = h.call("run", {"command": "printf 'const SAMPLE=1;' > ventures/x/sample.js && echo ok > junk.txt",
|
||||
"keep": ["ventures/x/sample.js", "../../etc/passwd", ".github/workflows/x.yml"]})
|
||||
assert "ventures/x/sample.js: kept" in out and "refused or missing" in out, out
|
||||
assert W32.files.get("ventures/x/sample.js") == "const SAMPLE=1;" and "junk.txt" not in W32.files
|
||||
print("ALL OK 24")
|
||||
+32
-5
@@ -400,8 +400,13 @@ WORKER_TOOLS = [
|
||||
{"type": "function", "function": {"name": "write_file", "description": "Create or replace one file on the branch with the full new content (commits it).",
|
||||
"parameters": {"type": "object", "properties": {"path": {"type": "string"}, "content": {"type": "string"},
|
||||
"message": {"type": "string"}}, "required": ["path", "content"]}}},
|
||||
{"type": "function", "function": {"name": "run", "description": "Run a shell command on a checkout of the branch (tests, lint). Returns exit code and output.",
|
||||
"parameters": {"type": "object", "properties": {"command": {"type": "string"}}, "required": ["command"]}}},
|
||||
{"type": "function", "function": {"name": "run", "description": "Run a shell command on a fresh checkout of the branch (tests, checks, "
|
||||
"generators). Returns exit code and output. Whatever the command creates or changes is thrown away, except the paths "
|
||||
"listed in `keep`, which are committed to the branch afterwards.",
|
||||
"parameters": {"type": "object", "properties": {"command": {"type": "string"},
|
||||
"keep": {"type": "array", "items": {"type": "string"},
|
||||
"description": "Repo-relative paths the command created or changed that should land on the branch."}},
|
||||
"required": ["command"]}}},
|
||||
{"type": "function", "function": {"name": "open_pr", "description": "Open the pull request when the work is done and checked. Call this exactly once, last.",
|
||||
"parameters": {"type": "object", "properties": {"title": {"type": "string"}, "summary": {"type": "string",
|
||||
"description": "What changed and how you checked it."}}, "required": ["title", "summary"]}}},
|
||||
@@ -411,8 +416,9 @@ WORKER_RULES = """You are the worker for card #{n} in {repo}: "{title}".
|
||||
|
||||
{body}
|
||||
|
||||
You have hands: list_files, read_file, write_file (one file per call, full content), run (shell on a checkout of your
|
||||
branch), open_pr. Work on the branch `{branch}`; everything you write lands there. Keep the change small and focused.
|
||||
You have hands: list_files, read_file, write_file (one file per call, full content), run (shell on a fresh checkout of
|
||||
your branch), open_pr. Work on the branch `{branch}`. Only two things reach it: write_file, and files a run lists in
|
||||
`keep` (for generated files, e.g. a sample built by a script). Anything else a run does is thrown away. Keep the change small and focused.
|
||||
Read before you write. Always `run` the project's tests or checks before opening the PR (that run is the PR's check). When it is done and checked, call open_pr once
|
||||
with a short summary; the PR must close the card, and Time adds that line for you. Do not touch .github/workflows.
|
||||
The card's finish line is: DONE: {done}"""
|
||||
@@ -472,12 +478,33 @@ class Hands:
|
||||
except subprocess.TimeoutExpired: # a server or watcher never exits: tell the model, don't crash the card
|
||||
return (f"timed out after {limit}s and was stopped. Servers and watchers never exit; run one-shot commands "
|
||||
f"(a parser, a test script, node -e '...') instead")
|
||||
kept = []
|
||||
root = os.path.realpath(self.checkout)
|
||||
for rel in (a.get("keep") or [])[:10]:
|
||||
full = os.path.realpath(os.path.join(root, str(rel)))
|
||||
rel = os.path.relpath(full, root)
|
||||
if not full.startswith(root + os.sep) or rel.startswith(".github/workflows") or not os.path.isfile(full):
|
||||
kept.append(f"{rel}: refused or missing")
|
||||
continue
|
||||
data = open(full, "rb").read()
|
||||
if len(data) > 2_000_000:
|
||||
kept.append(f"{rel}: too big to keep")
|
||||
continue
|
||||
body = {"message": f"card #{self.n}: {rel} (generated)", "branch": self.branch, "content": base64.b64encode(data).decode()}
|
||||
try:
|
||||
body["sha"] = gh("GET", f"/repos/{REPO}/contents/{rel}?ref={self.branch}")["sha"]
|
||||
except RuntimeError:
|
||||
pass
|
||||
gh("PUT", f"/repos/{REPO}/contents/{rel}", body)
|
||||
kept.append(f"{rel}: kept")
|
||||
sha = subprocess.run(["git", "rev-parse", "HEAD"], cwd=self.checkout, capture_output=True, text=True).stdout.strip()
|
||||
# GitHub does not run pull_request workflows for commits made with the Actions token, so the worker's own
|
||||
# test run is recorded as a commit status; land() counts statuses as checks.
|
||||
gh("POST", f"/repos/{REPO}/statuses/{sha}", {"state": "success" if p.returncode == 0 else "failure",
|
||||
"context": "time/worker-run", "description": a["command"][:100]})
|
||||
return f"exit {p.returncode}\n{(p.stdout + p.stderr)[-6000:]}"
|
||||
self.checkout = None if kept else self.checkout
|
||||
note = ("\n" + "; ".join(kept)) if kept else ""
|
||||
return f"exit {p.returncode}\n{(p.stdout + p.stderr)[-6000:]}{note}"
|
||||
if name == "open_pr":
|
||||
pr = gh("POST", f"/repos/{REPO}/pulls", {"title": a["title"][:200], "head": self.branch, "base": default_branch(),
|
||||
"body": f"{a['summary']}\n\nCloses #{self.n}\n\n<sub>worker: Venice seat via Time</sub>"})
|
||||
|
||||
Reference in new issue
Block a user