diff --git a/ga.py b/ga.py index e700f9bc..82182e64 100644 --- a/ga.py +++ b/ga.py @@ -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 diff --git a/llmcore.py b/llmcore.py index c0be64c4..684d21ad 100644 --- a/llmcore.py +++ b/llmcore.py @@ -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}] diff --git a/memory/verify_sop.md b/memory/deliverable_audit_sop.md similarity index 100% rename from memory/verify_sop.md rename to memory/deliverable_audit_sop.md diff --git a/memory/plan_sop.md b/memory/plan_sop.md index 5436c0b3..06d28d92 100644 --- a/memory/plan_sop.md +++ b/memory/plan_sop.md @@ -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自行读取详细上下文。