rename verify_sop -> deliverable_audit_sop (避免名字过宽导致主agent小任务误触发验证审计SOP); ga.py: file_patch/file_write 保持原文件换行符, 修复LF文件被污染成CRLF; _arg 类型强转修复字符串false等

This commit is contained in:
Liang Jiaqing committed 2026-07-12 15:09:34 +08:00
1 parent 3a315f3e1c
commit d910b689be
4 files changed
+31 -17

No files matched your search

+25 -12
View File
@@ -190,6 +190,20 @@ def expand_file_refs(text, base_dir=None):
return ''.join(lines[start-1:end])
return re.sub(pattern, replacer, text)
def _file_newline(path):
endings = set(re.findall(rb'\r\n|[\r\n]', Path(path).read_bytes())) if os.path.exists(path) else set()
return next(iter(endings)).decode() if len(endings) == 1 else None
def _arg(args, name, default, type=None):
v = args.get(name, default)
if type is int:
try: return int(v)
except (TypeError, ValueError): return default
if type is bool:
if isinstance(v, str): return v.strip().lower() in ('1','true','yes','y','on')
return default if v is None else bool(v)
return v
def file_patch(path: str, old_content: str, new_content: str):
"""在文件中寻找唯一的 old_content 块并替换为 new_content"""
path = str(Path(path).resolve())
@@ -201,7 +215,7 @@ def file_patch(path: str, old_content: str, new_content: str):
if count == 0: return {"status": "error", "msg": "未找到匹配的旧文本块,建议:先用 file_read 确认当前内容,再分小段进行 patch。若多次失败则询问用户,严禁自行使用 overwrite 或代码替换。"}
if count > 1: return {"status": "error", "msg": f"找到 {count} 处匹配,无法确定唯一位置。请提供更长、更具体的旧文本块以确保唯一性。建议:包含上下文行来增强特征,或分小段逐个修改。"}
updated_text = full_text.replace(old_content, new_content)
with open(path, 'w', encoding='utf-8') as f: f.write(updated_text)
with open(path, 'w', encoding='utf-8', newline=_file_newline(path)) as f: f.write(updated_text)
return {"status": "success", "msg": "文件局部修改成功"}
except Exception as e: return {"status": "error", "msg": str(e)}
@@ -290,13 +304,12 @@ class GenericAgentHandler(BaseHandler):
if not code:
code = self._extract_code_block(response, code_type)
if not code: return StepOutcome("[Error] Code missing. Must use reply code block or 'script' arg.", next_prompt="\n")
try: timeout = int(args.get("timeout", 60))
except: timeout = 60
timeout = _arg(args, "timeout", 60, int)
raw_path = os.path.join(self.cwd, args.get("cwd", './'))
cwd = os.path.normpath(os.path.abspath(raw_path))
code_cwd = os.path.normpath(self.cwd)
maxlen = 10000 // args.get('_tool_num', 1)
if code_type == 'python' and args.get("inline_eval"):
if code_type == 'python' and _arg(args, "inline_eval", False, bool):
ns = {'handler':self, 'parent':self.parent, 'history':json.dumps(self.parent.llmclient.backend.history)}
old_cwd = os.getcwd()
try:
@@ -321,9 +334,9 @@ class GenericAgentHandler(BaseHandler):
'''获取当前页面内容和标签页列表。也可用于切换标签页。
注意:HTML经过简化,边栏/浮动元素等可能被过滤。如需查看被过滤的内容请用execute_js。
tabs_only=true时仅返回标签页列表,不获取HTML(省token)'''
tabs_only = args.get("tabs_only", False)
tabs_only = _arg(args, "tabs_only", False, bool)
switch_tab_id = args.get("switch_tab_id", None)
text_only = args.get("text_only", False)
text_only = _arg(args, "text_only", False, bool)
maxlen = 35000 // args.get('_tool_num', 1)
result = web_scan(tabs_only=tabs_only, switch_tab_id=switch_tab_id, text_only=text_only, maxlen=maxlen)
content = result.pop("content", None)
@@ -341,7 +354,7 @@ class GenericAgentHandler(BaseHandler):
with open(abs_path, 'r', encoding='utf-8') as f: script = f.read()
save_to_file = args.get("save_to_file", "")
switch_tab_id = args.get("switch_tab_id") or args.get("tab_id")
no_monitor = args.get("no_monitor", False)
no_monitor = _arg(args, "no_monitor", False, bool)
result = web_execute_js(script, switch_tab_id=switch_tab_id, no_monitor=no_monitor)
if save_to_file and "js_return" in result:
content = str(result["js_return"] or '')
@@ -396,9 +409,9 @@ class GenericAgentHandler(BaseHandler):
new_content = expand_file_refs(content, base_dir=self.cwd)
if mode == "prepend":
old = open(path, 'r', encoding="utf-8").read() if os.path.exists(path) else ""
open(path, 'w', encoding="utf-8").write(new_content + old)
open(path, 'w', encoding="utf-8", newline=_file_newline(path)).write(new_content + old)
else:
with open(path, 'a' if mode == "append" else 'w', encoding="utf-8") as f: f.write(new_content)
with open(path, 'a' if mode == "append" else 'w', encoding="utf-8", newline=_file_newline(path)) as f: f.write(new_content)
yield f"[Status] ✅ {mode.capitalize()} 成功 ({len(new_content)} bytes)\n"
next_prompt = self._get_anchor_prompt(skip=args.get('_index', 0) > 0)
if len(new_content) > 5000: next_prompt = "\n[SYSTEM TIPS] WRITE TOO LONG! MUST RECHECK HALLUCINATIONS! SMALL WRITES OR PATCHES NEXT TIME!"
@@ -411,10 +424,10 @@ class GenericAgentHandler(BaseHandler):
'''读取文件内容。从第start行开始读取。如有keyword则返回第一个keyword(忽略大小写)周边内容'''
path = self._get_abs_path(args.get("path", ""))
yield f"\n[Action] Reading file: {path}\n"
start = args.get("start", 1)
count = args.get("count", 200)
start = _arg(args, "start", 1, int)
count = _arg(args, "count", 200, int)
keyword = args.get("keyword")
show_linenos = args.get("show_linenos", True)
show_linenos = _arg(args, "show_linenos", True, bool)
result = file_read(path, start=start, keyword=keyword,
count=count, show_linenos=show_linenos)
if show_linenos and not result.startswith("Error:"): result = '由于设置了show_linenos,以下返回信息为:(行号|)内容 。\n' + result
+2 -1
View File
@@ -378,6 +378,7 @@ def _stream_with_retry(sess, url, headers, payload, parse_fn):
with requests.post(url, headers=headers, json=payload, stream=sess.stream,
timeout=(sess.connect_timeout, sess.read_timeout), proxies=sess.proxies, verify=sess.verify) as r:
if r.status_code >= 400:
#pathlib.Path(__file__).parent.joinpath('temp','bad_requests.json').write_text(json.dumps({"url":url,"headers":headers,"payload":payload,"t":time.time()},ensure_ascii=False),encoding='utf-8')
if r.status_code in _RETRYABLE and attempt < sess.max_retries:
d = _delay(r, attempt)
print(f"[LLM Retry] HTTP {r.status_code}, retry in {d:.1f}s ({attempt+1}/{sess.max_retries+1})")
@@ -393,7 +394,6 @@ def _stream_with_retry(sess, url, headers, payload, parse_fn):
if not e.value and not streamed: raise requests.ConnectionError("empty response")
return e.value or []
except (requests.Timeout, requests.ConnectionError, requests.exceptions.ChunkedEncodingError) as e:
#pathlib.Path(__file__).parent.joinpath('temp','bad_requests.json').write_text(json.dumps({"url":url,"headers":headers,"payload":payload,"err":str(e),"t":time.time()},ensure_ascii=False),encoding='utf-8')
err = f"!!!Error: {type(e).__name__}: {e}" if str(e) else f"!!!Error: {type(e).__name__}"
if attempt < sess.max_retries:
d = _delay(None, attempt)
@@ -712,6 +712,7 @@ class NativeClaudeSession(BaseSession):
payload["tools"] = tools
else: print("[ERROR] No tools provided for this session.")
payload['system'] = [{"type": "text", "text": "You are Claude Code, Anthropic's official CLI for Claude.", "cache_control": {"type": "ephemeral"}}]
#payload['system'][0]['text'] += f"\nPlatform: {sys.platform}"
if self.system:
if self.fake_cc_system_prompt: payload["system"].append({"type": "text", "text": self.system})
else: payload["system"] = [{"type": "text", "text": self.system}]
File renamed without changes.
+4 -4
View File
@@ -107,7 +107,7 @@ handler是inline_eval自动注入的变量
## 验证检查点
N+1. [ ] **[VERIFY] 启动独立验证subagent**
SOP: verify_sop.md plan_sop.md
SOP: deliverable_audit_sop.md plan_sop.md
操作:读plan_sop.md第四章内容 → 准备verify_context.json → 启动验证subagent → 读取VERDICT → 按结果处理
⚠ 不可跳过,不可在未启动subagent的情况下标记[✓]
@@ -199,12 +199,12 @@ file_read(plan.md) 全文扫描,确认所有步骤(含[VERIFY])均为 `[
按 subagent.md 标准流程启动验证subagent,input要点:
- **角色**:你是独立验证者,工作是对抗性验证(证明交付物不能用)
- **第一步强制**:file_read verify_sop.md 完整阅读验证SOP
- **按 verify_sop.md 第3节**选择对应task_type的验证策略执行
- **第一步强制**:file_read deliverable_audit_sop.md 完整阅读验证SOP
- **按 deliverable_audit_sop.md 第3节**选择对应task_type的验证策略执行
- **每个检查必须有工具调用证据**(实际执行,不是叙述)
- **任务描述**:(填入原始任务描述)
- **交付物清单**:(填入deliverables列表)
- **输出**:在 result.md 中按 verify_sop.md 第6节格式输出,最后一行 `VERDICT: PASS / FAIL / PARTIAL`
- **输出**:在 result.md 中按 deliverable_audit_sop.md 第6节格式输出,最后一行 `VERDICT: PASS / FAIL / PARTIAL`
- **约束**:3轮内完成,每轮至少1个实际工具调用
同时传入 verify_context.json 的路径,让subagent自行读取详细上下文。