harness2: per-role models, sweep batch, task file assets endpoint
- Role models: run_improvement_via_tasks gains role_models
(probe/editor/selector/reflector) plus --model-<role> CLI flags;
missing/empty falls back to shared --model, both empty means
server default, so default behavior is unchanged.
- Sweep: run_sweep + examples/harness2_sweep.py manifest CLI with
shared library_root (library restore), per-run failure isolation
and sweep-report.json summary; preflight gates task creation.
- Deliverable assets: GET /tasks/{id}/files for local-executor
tasks (safe path resolution, UTF-8 text <=256KB, listing <=200;
docker/cloud/cross-host 409 with explicit reason, since tenant
Docker shares one /app/work per project and per-task attribution
would misattribute); probe rollout deliverable_files from the
endpoint plus DELIVERABLE FILE reply blocks (endpoint wins on
collision, bad blocks dropped), surfaced in
deliverables/<label>/files/ context and full_result summary.
- Docs: harness2.md comparison table updated for all three items.
This commit is contained in:
1 parent
9e444eca38
commit
c20192bce8
8 files changed
+856
-28
No files matched your search
+58
-1
@@ -241,6 +241,56 @@ def parse_harness_files(text: str) -> dict[str, str]:
|
||||
return out
|
||||
|
||||
|
||||
_DELIVERABLE_FILE_START_RX = re.compile(r"^=== DELIVERABLE FILE:\s*(.+?)\s*===$")
|
||||
_DELIVERABLE_FILE_END = "=== END DELIVERABLE FILE ==="
|
||||
_DELIVERABLE_FILE_MAX = 8
|
||||
_DELIVERABLE_FILE_MAX_CHARS = 65536
|
||||
|
||||
|
||||
def _is_safe_deliverable_path(path: str) -> bool:
|
||||
if not path or path.startswith("/") or "\x00" in path:
|
||||
return False
|
||||
return all(p not in ("", ".", "..") for p in path.split("/"))
|
||||
|
||||
|
||||
def parse_deliverable_files(text: str) -> dict[str, str]:
|
||||
"""解析探针回复里的产物文件块,返回 {相对路径: 内容}。
|
||||
|
||||
与编辑文件块的严格语义不同:这里故意宽容——坏块(未闭合、
|
||||
非法路径、超量、超长)只丢弃该块、不抛错,因为产物文件是
|
||||
交付文本之外的 enrichment,绝不能因为一块坏内容毁掉整轮
|
||||
交付物。超长内容截断并打截断标记,让评审知道它不完整;
|
||||
超过 8 块时只取前 8 块。同一路径出现两次时后一块覆盖前一块。
|
||||
"""
|
||||
out: dict[str, str] = {}
|
||||
cur_path: str | None = None
|
||||
cur_lines: list[str] = []
|
||||
for line in (text or "").splitlines():
|
||||
m = _DELIVERABLE_FILE_START_RX.match(line.strip())
|
||||
if m:
|
||||
if cur_path is not None:
|
||||
cur_path = None
|
||||
cur_lines = []
|
||||
path = m.group(1).strip()
|
||||
if _is_safe_deliverable_path(path) and len(out) < _DELIVERABLE_FILE_MAX:
|
||||
cur_path = path
|
||||
cur_lines = []
|
||||
continue
|
||||
if line.strip() == _DELIVERABLE_FILE_END:
|
||||
if cur_path is not None:
|
||||
content = "\n".join(cur_lines) + ("\n" if cur_lines else "")
|
||||
if len(content) > _DELIVERABLE_FILE_MAX_CHARS:
|
||||
content = (content[:_DELIVERABLE_FILE_MAX_CHARS]
|
||||
+ "\n...[truncated]")
|
||||
out[cur_path] = content
|
||||
cur_path = None
|
||||
cur_lines = []
|
||||
continue
|
||||
if cur_path is not None:
|
||||
cur_lines.append(line)
|
||||
return out
|
||||
|
||||
|
||||
MAX_K = 3
|
||||
STAGE_ATTEMPTS = 2 # 提议被拒后给一次按反馈修正的机会,再拒就回落
|
||||
|
||||
@@ -490,6 +540,7 @@ class Rollout:
|
||||
trajectory: str = ""
|
||||
cost_usd: float = 0.0
|
||||
error: str = ""
|
||||
deliverable_files: dict[str, str] = field(default_factory=dict)
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -589,7 +640,12 @@ def build_context(
|
||||
(dd / f"{r.label}.md").write_text(
|
||||
r.deliverable_text or "(no deliverable)", encoding="utf-8"
|
||||
)
|
||||
lines.append("context/deliverables/ what each rollout produced")
|
||||
for name, content in (r.deliverable_files or {}).items():
|
||||
dest = dd / r.label / "files" / name
|
||||
dest.parent.mkdir(parents=True, exist_ok=True)
|
||||
dest.write_text(content, encoding="utf-8")
|
||||
lines.append("context/deliverables/ what each rollout produced"
|
||||
" (+ <label>/files/ when it produced files)")
|
||||
write_tree(harness, root / "harness")
|
||||
lines.append("context/harness/ the harness tree you may edit, and ONLY this")
|
||||
if library_dir and Path(library_dir).is_dir():
|
||||
@@ -842,6 +898,7 @@ class ModeResult:
|
||||
"delivered": r.delivered,
|
||||
"error": (r.error or "")[:400],
|
||||
"deliverable_text": (r.deliverable_text or "")[:500],
|
||||
"deliverable_files": sorted(r.deliverable_files or {}),
|
||||
}
|
||||
for lbl, r in self.rollouts.items()
|
||||
},
|
||||
|
||||
Reference in new issue
Block a user