chore(telemetry): implement state-based loop governor, 180s tool timeout, visual-qa path fix, and fs_write diff-guard
This commit is contained in:
@@ -4657,7 +4657,7 @@ async function toolRequestVisualQA(
|
||||
);
|
||||
}
|
||||
|
||||
const absPath = normalizeFsPath(targetPath);
|
||||
const absPath = normalizeFsPath(targetPath, project.slug);
|
||||
if (absPath instanceof NextResponse) return absPath;
|
||||
|
||||
const r = await runFsCmd(
|
||||
@@ -4969,20 +4969,50 @@ async function toolFsWrite(principal: Principal, params: Record<string, any>) {
|
||||
const path = normalizeFsPath(String(params.path ?? ""), project.slug);
|
||||
if (path instanceof NextResponse) return path;
|
||||
const content = typeof params.content === "string" ? params.content : "";
|
||||
const force = Boolean(params.force);
|
||||
|
||||
// Stream content via base64 to avoid shell-quoting headaches with
|
||||
// arbitrary binary / multibyte input.
|
||||
const b64 = Buffer.from(content, "utf8").toString("base64");
|
||||
const cmd =
|
||||
`mkdir -p ${shq(path.replace(/\/[^/]+$/, "") || FS_ROOT)} && ` +
|
||||
`printf %s ${shq(b64)} | base64 -d > ${shq(path)}`;
|
||||
|
||||
const py = `import sys, os, difflib, base64
|
||||
path = sys.argv[1]
|
||||
new_b64 = sys.argv[2]
|
||||
force_overwrite = sys.argv[3] == 'true'
|
||||
|
||||
new_content = base64.b64decode(new_b64).decode('utf-8')
|
||||
|
||||
if os.path.exists(path) and not force_overwrite:
|
||||
try:
|
||||
with open(path, 'r', encoding='utf-8', errors='ignore') as f:
|
||||
old_content = f.read()
|
||||
old_lines = old_content.splitlines()
|
||||
new_lines = new_content.splitlines()
|
||||
if len(old_lines) > 5:
|
||||
diff = list(difflib.unified_diff(old_lines, new_lines))
|
||||
add_rem = len([l for l in diff if l.startswith('+') or l.startswith('-')]) - 2
|
||||
change_pct = add_rem / max(1, len(old_lines))
|
||||
if change_pct > 0.60:
|
||||
sys.stderr.write(f"REWRITE_GUARD_TRIGGERED: Your fs_write would overwrite {int(change_pct*100)}% of this {len(old_lines)}-line file. To replace large blocks or the entire file, please use surgical 'fs_edit' anchors instead, or pass 'force: true' on fs_write if you genuinely need a full rewrite.\\n")
|
||||
sys.exit(4)
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
os.makedirs(os.path.dirname(path), exist_ok=True)
|
||||
with open(path, 'w', encoding='utf-8') as f:
|
||||
f.write(new_content)
|
||||
`;
|
||||
|
||||
const pyB64 = Buffer.from(py, "utf8").toString("base64");
|
||||
const cmd = `python3 -c "$(printf %s ${shq(pyB64)} | base64 -d)" ${shq(path)} ${shq(b64)} ${shq(String(force))} && sha256sum ${shq(path)} | cut -d' ' -f1 && wc -c < ${shq(path)}`;
|
||||
|
||||
const r = await runFsCmd(principal, project, cmd);
|
||||
if (r.code !== 0) {
|
||||
const status = r.code === 4 ? 409 : 500;
|
||||
return NextResponse.json(
|
||||
{ error: `fs.write failed: ${r.stderr.trim() || "unknown error"}` },
|
||||
{ status: 500 },
|
||||
{ status },
|
||||
);
|
||||
}
|
||||
const stdoutParts = r.stdout.split("\n").filter(Boolean);
|
||||
const { createHash } = require("crypto");
|
||||
const bytes = Buffer.byteLength(content, "utf8");
|
||||
const sha256 = createHash("sha256").update(content, "utf8").digest("hex");
|
||||
|
||||
Reference in New Issue
Block a user