diff --git a/assets/tools_schema.json b/assets/tools_schema.json index c35ace8..7699bd0 100644 --- a/assets/tools_schema.json +++ b/assets/tools_schema.json @@ -23,11 +23,11 @@ "parameters": {"type": "object", "properties": { "path": {"type": "string", "description": "文件路径。"}, "old_content": {"type": "string", "description": "文件中需要被替换的原始文本块(需确保唯一性)。"}, - "new_content": {"type": "string", "description": "替换后的新文本内容。"}}, "required": ["path", "old_content", "new_content"]} + "new_content": {"type": "string", "description": "替换后的新文本内容。支持 {{file:路径:起始行:结束行}} 语法引用文件内容,写入前自动展开。"}}, "required": ["path", "old_content", "new_content"]} }}, {"type": "function", "function": { "name": "file_write", - "description": "用于文件的新建、全量覆盖或追加写入。对于精细的代码修改,应优先使用 file_patch。注意:要写入的内容必须放在回复正文的 标签或代码块中。", + "description": "用于文件的新建、全量覆盖或追加写入。对于精细的代码修改,应优先使用 file_patch。注意:要写入的内容必须放在回复正文的 标签或代码块中。写入内容支持 {{file:路径:起始行:结束行}} 语法引用文件片段,写入前自动展开。", "parameters": {"type": "object", "properties": { "path": {"type": "string", "description": "文件路径。"}, "mode": {"type": "string", "enum": ["overwrite", "append", "prepend"], "description": "写入模式覆盖、追加或在开头追加。", "default": "overwrite"}}, "required": ["path"]} @@ -44,7 +44,7 @@ "name": "web_execute_js", "description": "万能网页操控工具。通过执行 JavaScript 脚本实现对浏览器的完全控制(如点击、滚动、提取特定数据)。鼓励在有把握情况下(记忆中有selector/做法等)精准使用以减少web_scan调用。执行结果可选择保存到本地文件进行后续分析。", "parameters": {"type": "object", "properties": { - "script": {"type": "string", "description": "要执行的 JavaScript 代码或JS文件路径。"}, + "script": {"type": "string", "description": "要执行的代码或JS文件路径。"}, "save_to_file": {"type": "string", "description": "结果存文件,适合返回值较长时。不支持await。", "default": ""}, "no_monitor": {"type": "boolean", "description": "跳过页面变更监控,省2-3秒。仅在纯读取信息时设置,页面操作时不要设置。", "default": false}}, "required": ["script"]} }}, diff --git a/ga.py b/ga.py index c023666..ff66bd7 100644 --- a/ga.py +++ b/ga.py @@ -179,13 +179,26 @@ def web_execute_js(script, switch_tab_id=None, no_monitor=False): global driver try: if driver is None: first_init_driver() - if len(driver.get_all_sessions()) == 0: - return {"status": "error", "msg": "没有可用的浏览器标签页,查L3记忆分析原因。"} + if len(driver.get_all_sessions()) == 0: return {"status": "error", "msg": "没有可用的浏览器标签页,查L3记忆分析原因。"} if switch_tab_id: driver.default_session_id = switch_tab_id result = execute_js_rich(script, driver, no_monitor=no_monitor) return result except Exception as e: return {"status": "error", "msg": format_error(e)} + +def expand_file_refs(text, base_dir=None): + """展开文本中的 {{file:路径:起始行:结束行}} 引用为实际文件内容。 + 可与普通文本混排。展开失败抛 ValueError。 + base_dir: 相对路径的基准目录,默认为进程 cwd。""" + pattern = r'\{\{file:(.+?):(\d+):(\d+)\}\}' + def replacer(match): + path, start, end = match.group(1), int(match.group(2)), int(match.group(3)) + path = os.path.abspath(os.path.join(base_dir or '.', path)) + if not os.path.isfile(path): raise ValueError(f"引用文件不存在: {path}") + with open(path, 'r', encoding='utf-8') as f: lines = f.readlines() + if start < 1 or end > len(lines) or start > end: raise ValueError(f"行号越界: {path} 共{len(lines)}行, 请求{start}-{end}") + return ''.join(lines[start-1:end]) + return re.sub(pattern, replacer, text) def file_patch(path: str, old_content: str, new_content: str): """在文件中寻找唯一的 old_content 块并替换为 new_content。 @@ -343,6 +356,10 @@ class GenericAgentHandler(BaseHandler): yield f"[Action] Patching file: {path}\n" old_content = args.get("old_content", "") new_content = args.get("new_content", "") + try: new_content = expand_file_refs(new_content, base_dir=self.cwd) + except ValueError as e: + yield f"[Status] ❌ 引用展开失败: {e}\n" + return StepOutcome({"status": "error", "msg": str(e)}, next_prompt="\n") result = file_patch(path, old_content, new_content) yield f"\n{smart_format(result)}\n" next_prompt = self._get_anchor_prompt() @@ -368,8 +385,8 @@ class GenericAgentHandler(BaseHandler): if not blocks: yield f"[Status] ❌ 失败: 未在回复中找到代码块内容\n" return StepOutcome({"status": "error", "msg": "No content found, if you want a blank, you should use code_run"}, next_prompt="\n") - new_content = blocks try: + new_content = expand_file_refs(blocks, 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)