diff --git a/TMWebDriver.py b/TMWebDriver.py
index 0f58e79..d00b653 100644
--- a/TMWebDriver.py
+++ b/TMWebDriver.py
@@ -62,10 +62,14 @@ class TMWebDriver:
print(f"Browser http connected: {session.url} (Session: {session_id})")
self.sessions[session_id] = session
session = self.sessions[session_id]
+ session.disconnect_at = None
if session.type == 'http': msgQ = session.http_queue
else: return json.dumps({"id": "", "ret": "use ws"})
- try: return msgQ.get(timeout=5)
- except queue.Empty: return json.dumps({"id": "", "ret": "next long-poll"})
+ start_time = time.time()
+ while time.time() - start_time < 5:
+ try: return msgQ.get(timeout=0.2)
+ except queue.Empty: continue
+ return json.dumps({"id": "", "ret": "next long-poll"})
@app.route('/api/result', method=['GET','POST'])
def result():
@@ -90,6 +94,7 @@ class TMWebDriver:
auto_switch_newtab = data.get('auto_switch_newtab', False)
try:
result = self.execute_js(code, timeout=timeout, session_id=session_id, auto_switch_newtab=auto_switch_newtab)
+ print('remote', result)
newTabs = result.get('newTabs', []) if isinstance(result, dict) else []
return json.dumps({'result': result, 'newTabs': newTabs}, ensure_ascii=False)
except Exception as e:
@@ -100,7 +105,7 @@ class TMWebDriver:
import asyncio
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
- bottle.run(app, host=self.host, port=self.port+1, server='tornado')
+ bottle.run(app, host=self.host, port=self.port+1, server='tornado', threads=20)
http_thread = threading.Thread(target=run)
http_thread.daemon = True
@@ -218,7 +223,7 @@ class TMWebDriver:
if hasjump and session.is_active():
if not self.is_remote and auto_switch_newtab: self.last_cmd_time = time.time()
return {"result": f"Session {session_id} reloaded.", "closed":1}
- if time.time() - start_time > timeout:
+ if time.time() - start_time > timeout + 10:
if tp == 'ws':
return {"result": f"No response data in {timeout}s"}
elif tp == 'http':
diff --git a/agent_loop.py b/agent_loop.py
index cfd2702..1cd0420 100644
--- a/agent_loop.py
+++ b/agent_loop.py
@@ -15,14 +15,14 @@ def try_call_generator(func, *args, **kwargs):
return ret
class BaseHandler:
- def tool_before_callback(self, tool_name, args, content): pass
- def tool_after_callback(self, tool_name, args, content): pass
+ def tool_before_callback(self, tool_name, args, response): pass
+ def tool_after_callback(self, tool_name, args, response, ret): pass
def dispatch(self, tool_name, args, response):
method_name = f"do_{tool_name}"
if hasattr(self, method_name):
_ = yield from try_call_generator(self.tool_before_callback, tool_name, args, response)
ret = yield from try_call_generator(getattr(self, method_name), args, response)
- _ = yield from try_call_generator(self.tool_after_callback, tool_name, args, response)
+ _ = yield from try_call_generator(self.tool_after_callback, tool_name, args, response, ret)
return ret
else:
yield f"❌ 未知工具: {tool_name}\n"
@@ -48,6 +48,7 @@ def agent_runner_loop(client, system_prompt, user_input, handler, tools_schema,
response = client.chat(messages=messages, tools=tools_schema)
if response.thinking: yield '' + response.thinking + '\n\n'
+ if '```' in response.content: response.content = response.content.replace('```', ' \n```')
yield response.content + '\n\n'
if not response.tool_calls:
diff --git a/agentapp.py b/agentapp.py
index 915921d..9b53139 100644
--- a/agentapp.py
+++ b/agentapp.py
@@ -24,7 +24,7 @@ def init():
llmclient = init()
-from ga import GenericAgentHandler
+from ga import GenericAgentHandler, smart_format
def get_system_prompt():
with open('sys_prompt.txt', 'r', encoding='utf-8') as f:
@@ -56,18 +56,21 @@ def refine_user_goal(raw_query, last_goal):
return raw_query
def agent_backend_stream(raw_query):
- final_goal = refine_user_goal(raw_query, st.session_state.last_goal)
-
- if final_goal != raw_query:
- yield f"[Goal Refined] {final_goal}\n"
+ #final_goal = refine_user_goal(raw_query, st.session_state.last_goal)
+ #if final_goal != raw_query: yield f"[Goal Refined] {final_goal}\n"
+
+ history = st.session_state.get("last_history", [])
+ hquery = smart_format(raw_query.replace('\n', ' '), max_str_len=100)
+ history.append(f"[USER]: {hquery}")
sys_prompt = get_system_prompt()
- handler = GenericAgentHandler(None, final_goal, './temp')
+ handler = GenericAgentHandler(None, history, './temp')
llmclient.last_tools = ''
ret = yield from agent_runner_loop(llmclient,
sys_prompt, raw_query, handler,
TOOLS_SCHEMA, max_turns=25)
- st.session_state.last_goal = final_goal
+ #st.session_state.last_goal = final_goal
+ st.session_state.last_history = handler.history_info
return ret
st.title("🖥️ Cowork")
diff --git a/ga.py b/ga.py
index 6e140ab..6ef69e3 100644
--- a/ga.py
+++ b/ga.py
@@ -1,15 +1,11 @@
-import sys, os, re
-import pyperclip, threading
-import json, time
+import sys, os, re, json, time, pyperclip, threading
from pathlib import Path
-import subprocess
-import tempfile
+import tempfile, traceback, subprocess
if sys.stdout is None: sys.stdout = open(os.devnull, "w")
if sys.stderr is None: sys.stderr = open(os.devnull, "w")
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
-from sidercall import LLMSession, ToolClient
-from agent_loop import BaseHandler, StepOutcome, agent_runner_loop
+from agent_loop import BaseHandler, StepOutcome, try_call_generator
def code_run(code: str, code_type: str = "python", timeout: int = 60, cwd: str = None):
"""
@@ -146,7 +142,6 @@ def web_scan(focus_item="", switch_tab_id=None):
except Exception as e:
return {"status": "error", "msg": format_error(e)}
-import traceback
def format_error(e):
exc_type, exc_value, exc_traceback = sys.exc_info()
tb = traceback.extract_tb(exc_traceback)
@@ -182,24 +177,18 @@ def web_execute_js(script: str):
return {"status": "error", "msg": format_error(e)}
def file_patch(path: str, old_content: str, new_content: str):
- """
- 在文件中寻找唯一的 old_content 块并替换为 new_content。
+ """在文件中寻找唯一的 old_content 块并替换为 new_content。
"""
path = str(Path(path).resolve())
try:
- if not os.path.exists(path):
- return {"status": "error", "msg": "文件不存在"}
- with open(path, 'r', encoding='utf-8') as f:
- full_text = f.read()
+ if not os.path.exists(path): return {"status": "error", "msg": "文件不存在"}
+ with open(path, 'r', encoding='utf-8') as f: full_text = f.read()
# 检查唯一性
count = full_text.count(old_content)
- if count == 0:
- return {"status": "error", "msg": "未找到匹配的旧文本块,请检查空格、缩进和换行是否完全一致。"}
- if count > 1:
- return {"status": "error", "msg": f"找到 {count} 处匹配,请提供更长的旧文本块以确保唯一性。"}
+ if count == 0: return {"status": "error", "msg": "未找到匹配的旧文本块,请检查空格、缩进和换行是否完全一致。"}
+ 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') as f: f.write(updated_text)
return {"status": "success", "msg": "文件局部修改成功"}
except Exception as e:
return {"status": "error", "msg": str(e)}
@@ -224,31 +213,40 @@ def smart_format(data, max_depth=2, max_str_len=100):
if isinstance(obj, dict): return {k: truncate(v, depth + 1) for k, v in obj.items()}
if isinstance(obj, list): return [truncate(i, depth + 1) for i in obj]
return obj
+ if isinstance(data, (str, bytes)): return truncate(data, 0)
return json.dumps(truncate(data, 0), indent=2, ensure_ascii=False, default=str)
class GenericAgentHandler(BaseHandler):
'''
Generic Agent 工具库,包含多种工具的实现。工具函数自动加上了 do_ 前缀。实际工具名没有前缀。
'''
- def __init__(self, parent, user_input, cwd):
+ def __init__(self, parent, last_history=None, cwd='./'):
self.parent = parent
- self.user_input = user_input
self.plan = ""
self.focus = ""
self.cwd = cwd
+ self.history_info = last_history if last_history else []
def _get_abs_path(self, path):
if not path: return ""
return os.path.abspath(os.path.join(self.cwd, path))
+
+ def tool_after_callback(self, tool_name, args, response, ret):
+ rsumm = re.search(r"(.*?)", response.content, re.DOTALL)
+ if rsumm: summary = rsumm.group(1).strip()[:200]
+ else:
+ summary = f"调用工具{tool_name}, args: {args}"
+ if tool_name == 'no_tool': summary = "直接回答了用户问题"
+ if type(ret.next_prompt) is str:
+ ret.next_prompt += "\nPROTOCOL_VIOLATION: 上一轮遗漏了。 我已根据物理动作自动补全。请务必在下次回复中记得协议。"
+ self.history_info.append('[Agent] ' + smart_format(summary, max_str_len=100))
def do_code_run(self, args, response):
'''执行代码片段,有长度限制,不允许代码中放大量数据,如有需要应当通过文件读取进行。
'''
code_type = args.get("type", "python")
- # 从 response.content 中提取代码块
- # 匹配 ```python ... ``` 或 ```powershell ... ```
+ # 从 response.content 中提取代码块, 匹配 ```python ... ``` 或 ```powershell ... ```
pattern = rf"```{code_type}\n(.*?)\n```"
- # 也可以更通用一点,不分类型提取最后一个代码块:rf"```(?:{code_type})?\n(.*?)\n```"
matches = re.findall(pattern, response.content, re.DOTALL)
if not matches:
return StepOutcome(None, next_prompt=f"【系统错误】:你调用了 code_run,但未在回复中提供 ```{code_type} 代码块。请重新输出代码并附带工具调用。")
@@ -257,7 +255,8 @@ class GenericAgentHandler(BaseHandler):
timeout = args.get("timeout", 60)
cwd = args.get("cwd", self.cwd)
result = yield from code_run(code, code_type, timeout, cwd)
- return StepOutcome(result, next_prompt=self._get_anchor_prompt())
+ next_prompt = self._get_anchor_prompt()
+ return StepOutcome(result, next_prompt=next_prompt)
def do_ask_user(self, args, response):
question = args.get("question", "请提供输入:")
@@ -292,7 +291,8 @@ class GenericAgentHandler(BaseHandler):
result["js_return"] += f"\n\n[已保存以上内容到 {abs_path}]"
print("Web Execute JS Result:", smart_format(result))
yield f"JS 执行结果:\n{smart_format(result)}\n"
- return StepOutcome(result, next_prompt=self._get_anchor_prompt())
+ next_prompt = self._get_anchor_prompt()
+ return StepOutcome(result, next_prompt=next_prompt)
def do_file_patch(self, args, response):
path = self._get_abs_path(args.get("path", ""))
@@ -301,7 +301,8 @@ class GenericAgentHandler(BaseHandler):
new_content = args.get("new_content", "")
result = file_patch(path, old_content, new_content)
yield f"\n{smart_format(result)}\n"
- return StepOutcome(result, next_prompt=self._get_anchor_prompt())
+ next_prompt = self._get_anchor_prompt()
+ return StepOutcome(result, next_prompt=next_prompt)
def do_file_write(self, args, response):
'''用于对整个文件的大量处理,精细修改要用file_patch。
@@ -330,8 +331,9 @@ class GenericAgentHandler(BaseHandler):
with open(path, write_mode, encoding="utf-8") as f:
f.write(final_content)
yield f"[Status] ✅ {mode.capitalize()} 成功 ({len(new_content)} bytes)\n"
+ next_prompt = self._get_anchor_prompt()
return StepOutcome({"status": "success", 'writed_bytes': len(new_content)},
- next_prompt=self._get_anchor_prompt())
+ next_prompt=next_prompt)
except Exception as e:
yield f"[Status] ❌ 写入异常: {str(e)}\n"
return StepOutcome({"status": "error", "msg": str(e)}, next_prompt="\n")
@@ -343,7 +345,8 @@ class GenericAgentHandler(BaseHandler):
count = args.get("count", 100)
show_linenos = args.get("show_linenos", True)
result = file_read(path, start, count, show_linenos)
- return StepOutcome(result, next_prompt=self._get_anchor_prompt())
+ next_prompt = self._get_anchor_prompt()
+ return StepOutcome(result, next_prompt=next_prompt)
def do_update_plan(self, args, response):
'''
@@ -361,8 +364,8 @@ class GenericAgentHandler(BaseHandler):
yield f"[Info] Updated plan and focus.\n"
yield f"New Plan:\n{self.plan}\n\n"
yield f"New Focus:\n{self.focus}\n"
- return StepOutcome({"status": "success"},
- next_prompt=self._get_anchor_prompt())
+ next_prompt = self._get_anchor_prompt()
+ return StepOutcome({"status": "success"}, next_prompt=next_prompt)
def do_no_tool(self, args, response):
'''这是一个特殊工具,由引擎自主调用,不要包含在TOOLS_SCHEMA里。
@@ -371,12 +374,9 @@ class GenericAgentHandler(BaseHandler):
return StepOutcome(response, next_prompt=None, should_exit=True)
def _get_anchor_prompt(self):
- prompt = f"\n提醒: 用户原始输入:\n{self.user_input}\n"
- if self.plan: prompt += f"\n{self.plan}\n\n"
- if self.focus: prompt += f"\n{self.focus}\n\n"
- prompt += "\n请继续执行下一步。"
- return prompt
-
-
-if __name__ == "__main__":
- pass
\ No newline at end of file
+ h_str = "\n".join(self.history_info[-20:])
+ prompt = f"\n### [WORKING MEMORY]\n\n{h_str}\n"
+ print(prompt)
+ if self.plan: prompt += f"\n{self.plan}"
+ if self.focus: prompt += f"\n{self.focus}"
+ return prompt + "\n请继续执行下一步。"
diff --git a/sidercall.py b/sidercall.py
index 56d6580..ecd50ef 100644
--- a/sidercall.py
+++ b/sidercall.py
@@ -92,15 +92,16 @@ class ToolClient:
tool_instruction = f"""
### ⚡️ 交互协议 (必须严格遵守)
请按照以下步骤思考并行动:
-1. **思考**: 在 `` 标签中分析现状和策略。
-2. **行动**: 如果需要调用工具,请紧接着输出一个 **块**,然后结束,我会稍后给你返回块。
+1. **思考**: 在 `` 标签中先进行思考,分析现状和策略。
+2. **总结**: 在 `` 中输出*极为简短*的高度概括的单行(<30字)物理快照,包括上次工具调用结果获取的新信息+本次工具调用意图和预期。此内容将进入长期工作记忆,记录关键信息,严禁输出无实际信息增量的描述。
+3. **行动**: 如果需要调用工具,请紧接着输出一个 **块**,然后结束,我会稍后给你返回块。
格式: ```\n{{"function": "工具名", "arguments": {{参数}}}}\n\n```
### 🛠️ 可用工具库
{tools_json}
"""
if self.auto_save_tokens and self.last_tools == tools_json:
- tool_instruction = "\n### ⚡️ 交互协议保持不变,继续使用之前的工具库。\n"
+ tool_instruction = "\n### 交互协议保持不变,沿用之前的协议和工具库。\n"
else:
self.total_cd_tokens = 0
self.last_tools = tools_json
@@ -109,8 +110,8 @@ class ToolClient:
for m in history_msgs:
role = "USER" if m['role'] == 'user' else "ASSISTANT"
prompt += f"=== {role} ===\n{m['content']}\n\n"
+ self.total_cd_tokens += len(m['content'])
- self.total_cd_tokens += len(prompt)
if self.total_cd_tokens > 9000: self.last_tools = ''
prompt += "=== ASSISTANT ===\n"
@@ -149,6 +150,8 @@ class ToolClient:
except json.JSONDecodeError:
print("[Warn] Failed to parse tool_use JSON:", json_str)
thinking += f"[Warn] JSON 解析失败,模型输出了无效的 JSON."
+ except Exception as e:
+ print("[Error] Exception during tool_use parsing:", str(e), data)
content = remaining_text.strip()
if not content: content = ""
diff --git a/simphtml.py b/simphtml.py
index d555460..b915e16 100644
--- a/simphtml.py
+++ b/simphtml.py
@@ -816,6 +816,8 @@ def get_main_block(driver):
if type(html) is not str:
time.sleep(2)
html = driver.execute_js(js_optHTML)
+ else:
+ print('[STRANGE TYPE]', str(html)[:500])
return html
diff --git a/sys_prompt.txt b/sys_prompt.txt
index f9a6e5e..3963f42 100644
--- a/sys_prompt.txt
+++ b/sys_prompt.txt
@@ -1,48 +1,15 @@
-
-# Role: 物理级全能架构师 (Physical-Access AI Architect)
+# Role: 物理级全能架构师
## 1. 认知修正:打破虚拟偏见
-你不是一个只能“聊天”的虚拟助手。你是一个拥有物理操作权限的系统实体。
+你不是一个只能“聊天”的虚拟助手。你是一个拥有物理操作权限的智能实体,拥有干涉现实的系统权限以完成任何任务。
- **物理控制权限**:你拥有对当前操作系统的文件读写权限、脚本执行权限以及实时浏览器控制权。
-- **真实互联网访问**:你不是在“想象”网页内容,而是通过真实的自动化驱动程序(WebDriver)进行毫秒级的 DOM 读写和 JS 注入。
-- **禁止推诿**:永远不要说“我无法访问文件”或“我不能操作网页”。如果你需要做什么,直接调用工具。
+- **真实互联网访问**:你不是在“想象”网页内容,而是通过真实的WebDriver进行 DOM 读写和 JS 注入。
+- **禁止推诿**:永远不要说“我无法访问文件”或“我不能操作网页”,你能够调用工具做到。
## 2. 核心行动原则:思考先行 (Thinking Process)
-在调用任何工具之前,你必须在 `` 标签内进行深度推演:
+在调用任何工具之前,你必须在 标签内进行深度推演:
- **状态评估**:目前处于任务的哪个阶段?上一步的输出是否符合预期?
-- **逻辑分支**:如果当前工具调用失败,我的备选方案(Plan B)是什么?
-- **风险规避**:对于 `file_patch` 或 `code_run`,操作是否会造成不可逆的系统破坏?
+- **探测优先**:如果当前步骤失败,禁止盲目重试,应当获取更多信息,再考虑备选方案(Plan B)是什么?
+- **风险规避**:需要考虑操作是否会造成不可逆的系统或数据破坏?
-## 3. 核心能力边界与协议
-### A. 网页操控协议 (Web-Control Protocol)
-- **非视觉依赖**:你通过 `web_scan` 获取清洗后的语义化 HTML 结构,而非通过截图猜测。
-- **JS 优先**:对于复杂的交互(点击、滚动、异步加载、提取特定数据),应优先使用 `web_execute_js` 注入精准的 JavaScript。
-- **持久化分析**:如需处理海量网页数据,利用 `web_execute_js` 的 `save_to_file` 参数将结果存盘,随后使用文件工具分析。
-
-### B. 文件系统协议 (FileSystem Protocol)
-- **稳健性准则**:严禁盲目覆盖。遵循 **“先读 (file_read) -> 构造修改块 -> 局部应用 (file_patch)”** 的工作流。
-- **原子化修改**:对于已知源码的微调,强制使用 `file_patch` 以确保缩进和上下文的精确性。
-- **全量重写**:仅在创建新文件或重构整个模块时使用 `file_write`。
-
-### C. 终极执行力:code_run (Ultimate Executor)
-- **万能钥匙**:当预设的 Web 或 File 工具无法满足复杂逻辑时,直接使用 `code_run` 编写 Python 或 PowerShell 脚本解决战斗。
-- **Windows 优化**:默认使用 `python` 处理逻辑、数据处理和复杂 API 调用;使用 `powershell` 处理系统管理、进程查询或简单路径操作。
-
-### D. 战略管理 (Strategic Management)
-- **复杂任务拆解**:对于超过 3 步以上的任务,必须先调用 `update_plan` 建立宏观视图。
-- **人机协同**:用户是你最重要的“外部传感器”和“权限授予者”。在遇到模糊需求、关键决策点或需要手动登录(绕过验证码)时,果断调用 `ask_user`。
-
-## 4. 严苛禁令
-1. **禁止占位符**:在生成的代码或 PATCH 中,严禁使用 `// rest of code...` 这种占位符,必须输出完整且可运行的逻辑。
-2. **禁止循环尝试**:如果一个方法尝试两次均告失败,必须通过 `` 寻找根因,改用 `code_run` 编写自定义诊断脚本,而不是重复失败。
-3. **静默执行**:除非用户要求解释,否则直接执行。不要在行动前征求同意(除非涉及高危物理删除操作)。
-
-## 5. 工作流模板
-1. **分析意图**:用户想干什么?
-2. **环境感知**:读取相关文件或扫描网页。
-3. **战略制定/更新**:`update_plan`(如有必要)。
-4. **精确执行**:执行 JS、Patch 文件或 Run Code。
-5. **验证反馈**:检查 Exit Code 或输出内容,准备下一步。
-
-你现在的状态:**权限已就绪,物理驱动已加载,请开始执行。**
\ No newline at end of file