PAD-12: per-terminal transport (direct Tailscale SSH or Warpgate, pre-session fallback, pinned host keys, agent key from Vault), one-call win10 launch and one-call collect, remote call timings, agentd ping
This commit is contained in:
@@ -6,6 +6,8 @@ import re
|
||||
from dataclasses import dataclass, field
|
||||
from typing import List, Optional
|
||||
|
||||
from ..transports import fetch_each
|
||||
|
||||
|
||||
@dataclass
|
||||
class Run:
|
||||
@@ -55,6 +57,11 @@ class Executor:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def parse_exit(text):
|
||||
m = re.search(r"exit=(-?\d+)", text or "")
|
||||
return int(m.group(1)) if m else None
|
||||
|
||||
|
||||
def fill(template, params):
|
||||
"""Replace {name} placeholders that exist in params; leave anything else untouched."""
|
||||
return re.sub(r"\{(\w+)\}", lambda m: str(params[m.group(1)]) if m.group(1) in params else m.group(0), template)
|
||||
@@ -101,7 +108,7 @@ class RunnerExecutor(Executor):
|
||||
run_dir, task = h["run_dir"], h["task"]
|
||||
script_name, script = t.runner(run_dir, run.repo, self.command(run, run_dir), self.stdout_name)
|
||||
files = {"prompt.md": run.prompt, script_name: script}
|
||||
if hasattr(t, "start_run"): # remote POSIX: stage + launch in one relay round trip
|
||||
if hasattr(t, "start_run"): # remote: stage + launch in one call (direct-ssh; POSIX also on the relay)
|
||||
t.start_run(task, run_dir, files, script_name)
|
||||
else:
|
||||
t.mkdir(run_dir, clean=True)
|
||||
@@ -121,9 +128,14 @@ class RunnerExecutor(Executor):
|
||||
return "finished" if text and "exit=" in text else "running"
|
||||
|
||||
def exit_code(self, handle):
|
||||
text = self.transport.read_text(self.transport.join(handle["run_dir"], "exit.txt")) or ""
|
||||
m = re.search(r"exit=(-?\d+)", text)
|
||||
return int(m.group(1)) if m else None
|
||||
return parse_exit(self.transport.read_text(self.transport.join(handle["run_dir"], "exit.txt")))
|
||||
|
||||
def fetch(self, handle, ops):
|
||||
"""Reads of the run directory for `collect`: one call on remote transports (see transports.fetch)."""
|
||||
t = self.transport
|
||||
if hasattr(t, "fetch"):
|
||||
return t.fetch(handle["run_dir"], ops)
|
||||
return fetch_each(t, handle["run_dir"], ops)
|
||||
|
||||
def kill(self, handle):
|
||||
self.transport.kill(handle["task"], handle["run_dir"])
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
resume with `--resume <session_id>`. The JSON result carries `result` (last message) and `session_id`."""
|
||||
import json
|
||||
|
||||
from .base import ExecResult, RunnerExecutor
|
||||
from .base import ExecResult, RunnerExecutor, parse_exit
|
||||
|
||||
|
||||
def parse_output(text):
|
||||
@@ -26,13 +26,14 @@ def parse_output(text):
|
||||
class ClaudeCodeExecutor(RunnerExecutor):
|
||||
stdout_name = "out.json"
|
||||
|
||||
COLLECT = [("read", "exit.txt"), ("read", "out.json"), ("tail", "stderr.txt", 5)]
|
||||
|
||||
def collect(self, handle):
|
||||
t, d = self.transport, handle["run_dir"]
|
||||
obj = parse_output(t.read_text(t.join(d, "out.json")))
|
||||
exit_text, out, tail = self.fetch(handle, self.COLLECT) # one remote call
|
||||
obj = parse_output(out)
|
||||
err = bool(obj.get("is_error")) or obj.get("subtype", "success") != "success"
|
||||
last = "" if err else (obj.get("result") or "")
|
||||
tail = self.stderr_tail(handle)
|
||||
if err:
|
||||
tail = ("%s %s" % (obj.get("subtype", ""), str(obj.get("result", ""))[:400]) + "\n" + tail).strip()
|
||||
return ExecResult(last.strip(), self.exit_code(handle), obj.get("session_id") or handle.get("thread_id"),
|
||||
return ExecResult(last.strip(), parse_exit(exit_text), obj.get("session_id") or handle.get("thread_id"),
|
||||
self.logs(handle, "out.json", "stderr.txt"), tail)
|
||||
|
||||
@@ -2,22 +2,22 @@
|
||||
Thread id comes from the `thread.started` event in events.jsonl; the last message from last.md."""
|
||||
import json
|
||||
|
||||
from .base import ExecResult, RunnerExecutor
|
||||
from .base import ExecResult, RunnerExecutor, parse_exit
|
||||
|
||||
|
||||
class CodexExecutor(RunnerExecutor):
|
||||
stdout_name = "events.jsonl"
|
||||
COLLECT = [("read", "exit.txt"), ("read", "last.md"), ("grep", "events.jsonl", "thread.started"),
|
||||
("tail", "stderr.txt", 5), ("tail", "events.jsonl", 1)]
|
||||
|
||||
def collect(self, handle):
|
||||
t, d = self.transport, handle["run_dir"]
|
||||
last = t.read_text(t.join(d, "last.md")) or ""
|
||||
exit_text, last, line, err_tail, last_event = self.fetch(handle, self.COLLECT) # one remote call
|
||||
thread = handle.get("thread_id")
|
||||
line = t.grep_first(t.join(d, "events.jsonl"), "thread.started")
|
||||
if line:
|
||||
try:
|
||||
thread = json.loads(line).get("thread_id") or thread
|
||||
except ValueError:
|
||||
pass
|
||||
tail = (self.stderr_tail(handle) + "\n" + t.tail(t.join(d, "events.jsonl"), 1)[-600:]).strip()
|
||||
return ExecResult(last.strip(), self.exit_code(handle), thread,
|
||||
tail = (err_tail + "\n" + last_event[-600:]).strip()
|
||||
return ExecResult((last or "").strip(), parse_exit(exit_text), thread,
|
||||
self.logs(handle, "events.jsonl", "stderr.txt", "last.md"), tail)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""Shell/script executor: runs `{script}` with the prompt on stdin. The last message is `last.md` in the run
|
||||
directory if the script wrote it (its path is passed as {out}), else the tail of stdout. No threads."""
|
||||
from .base import ExecResult, RunnerExecutor
|
||||
from .base import ExecResult, RunnerExecutor, parse_exit
|
||||
|
||||
|
||||
class ShellExecutor(RunnerExecutor):
|
||||
@@ -10,10 +10,11 @@ class ShellExecutor(RunnerExecutor):
|
||||
def resume(self):
|
||||
return False
|
||||
|
||||
COLLECT = [("read", "exit.txt"), ("read", "last.md"), ("read", "stdout.txt"), ("tail", "stderr.txt", 5)]
|
||||
|
||||
def collect(self, handle):
|
||||
t, d = self.transport, handle["run_dir"]
|
||||
last = t.read_text(t.join(d, "last.md"))
|
||||
exit_text, last, stdout, tail = self.fetch(handle, self.COLLECT) # one remote call
|
||||
if last is None:
|
||||
last = (t.read_text(t.join(d, "stdout.txt")) or "")[-6000:]
|
||||
return ExecResult(last.strip(), self.exit_code(handle), None,
|
||||
self.logs(handle, "stdout.txt", "stderr.txt"), self.stderr_tail(handle))
|
||||
last = (stdout or "")[-6000:]
|
||||
return ExecResult(last.strip(), parse_exit(exit_text), None,
|
||||
self.logs(handle, "stdout.txt", "stderr.txt"), tail)
|
||||
|
||||
Reference in New Issue
Block a user