diff --git a/agentmain.py b/agentmain.py index c98d15a..211749c 100644 --- a/agentmain.py +++ b/agentmain.py @@ -10,9 +10,11 @@ from agent_loop import agent_runner_loop from ga import GenericAgentHandler, smart_format, get_global_memory, format_error script_dir = os.path.dirname(os.path.abspath(__file__)) -with open(os.path.join(script_dir, 'assets/tools_schema.json'), 'r', encoding='utf-8') as f: - TS = f.read() +def load_tool_schema(suffix=''): + global TOOLS_SCHEMA + TS = open(os.path.join(script_dir, f'assets/tools_schema{suffix}.json'), 'r', encoding='utf-8').read() TOOLS_SCHEMA = json.loads(TS if os.name == 'nt' else TS.replace('powershell', 'bash')) +load_tool_schema() mem_dir = os.path.join(script_dir, 'memory') if not os.path.exists(mem_dir): os.makedirs(mem_dir) @@ -69,6 +71,9 @@ class GeneraticAgent: self.llm_no = ((self.llm_no + 1) if n < 0 else n) % len(self.llmclients) self.llmclient = self.llmclients[self.llm_no] self.llmclient.last_tools = '' + name = self.get_llm_name() + if 'glm' in name or 'minimax' in name or 'kimi' in name: load_tool_schema('_cn') + else: load_tool_schema() def list_llms(self): return [(i, f"{type(b.backend).__name__}/{b.backend.default_model}", i == self.llm_no) for i, b in enumerate(self.llmclients)] def get_llm_name(self): b = self.llmclient diff --git a/assets/tools_schema.json b/assets/tools_schema.json index c9d23fc..b53d8ff 100644 --- a/assets/tools_schema.json +++ b/assets/tools_schema.json @@ -1,67 +1,67 @@ [ {"type": "function", "function": { "name": "code_run", - "description": "Code executor. Prefer python. No concurrent calls. Put code in ```python/powershell blocks in reply body to avoid escaping. No hardcoding bulk data", + "description": "Code executor. Prefer python. No concurrent calls. Prefer code in ```python/powershell blocks in reply body to avoid escaping. No hardcoding bulk data", "parameters": {"type": "object", "properties": { - "script": {"type": "string", "description": "[Optional] NEVER use this param when code is in ```python/powershell blocks in reply body. Only use when no code block in reply. Mutually exclusive with reply code blocks"}, + "script": {"type": "string", "description": "[Mutually exclusive] NEVER use this param when use reply code block."}, "type": {"type": "string", "enum": ["python", "powershell"], "description": "Code type", "default": "python"}, - "timeout": {"type": "integer", "description": "Timeout in seconds", "default": 60}, + "timeout": {"type": "integer", "description": "in seconds", "default": 60}, "cwd": {"type": "string", "description": "Working directory, defaults to cwd"}}} }}, {"type": "function", "function": { "name": "file_read", - "description": "Read file content. Read before modifying to get latest context and line numbers. Supports pagination and keyword search", + "description": "Read file. Read before modify for latest context and line numbers", "parameters": {"type": "object", "properties": { - "path": {"type": "string", "description": "Relative or absolute file path"}, + "path": {"type": "string", "description": "Relative or absolute"}, "start": {"type": "integer", "description": "Start line number (1-based)", "default": 1}, "count": {"type": "integer", "description": "Number of lines to read", "default": 200}, - "keyword": {"type": "string", "description": "Optional search keyword. If provided, returns first match (case-insensitive) with surrounding context"}, - "show_linenos": {"type": "boolean", "description": "Show line numbers. Recommended on to help file_patch locate content", "default": true}}, "required": ["path"]} + "keyword": {"type": "string", "description": "[Optional] If provided, returns first match (case-insensitive) with context"}, + "show_linenos": {"type": "boolean", "description": "Show line numbers", "default": true}}} }}, {"type": "function", "function": { "name": "file_patch", - "description": "Fine-grained local file edit. Finds unique old_content block and replaces with new_content. old_content must be unique in file with exact whitespace/indentation/newlines. On match failure, use file_read to re-check file content", + "description": "Replace unique old_content with new_content. Exact match required (whitespace/indentation). On failure, file_read to recheck", "parameters": {"type": "object", "properties": { "path": {"type": "string", "description": "File path"}, - "old_content": {"type": "string", "description": "Original text block to replace (must be unique in file)"}, - "new_content": {"type": "string", "description": "New text content. Supports {{file:path:startLine:endLine}} syntax to reference file lines, auto-expanded before writing"}}, "required": ["path", "old_content", "new_content"]} + "old_content": {"type": "string", "description": "Original text block to replace (must be unique)"}, + "new_content": {"type": "string", "description": "New content. Supports {{file:path:startLine:endLine}} to ref file lines, auto-expanded"}}} }}, {"type": "function", "function": { "name": "file_write", - "description": "Create, overwrite or append files. Use file_patch for fine-grained edits. Content must be in tags or code blocks in reply body. Supports {{file:path:startLine:endLine}} to reference file lines, auto-expanded before writing", + "description": "Create/overwrite/append files. ONLY for HUGE edits. Content in tags or reply code blocks. Supports {{file:path:startLine:endLine}}, auto-expanded", "parameters": {"type": "object", "properties": { "path": {"type": "string", "description": "File path"}, - "mode": {"type": "string", "enum": ["overwrite", "append", "prepend"], "description": "Write mode", "default": "append"}}, "required": ["path"]} + "mode": {"type": "string", "enum": ["overwrite", "append", "prepend"], "description": "Write mode", "default": "append"}}} }}, {"type": "function", "function": { "name": "web_scan", - "description": "Get simplified HTML and tab list of current page. Simplification filters sidebars/floating elements; use execute_js for filtered content. Call after switching pages", + "description": "Get simplified HTML and tab list. Filters sidebars/floating elements; use execute_js for filtered content. Call after switching pages", "parameters": {"type": "object", "properties": { - "tabs_only": {"type": "boolean", "description": "Return only tab list and current tab info, no HTML", "default": false}, - "switch_tab_id": {"type": "string", "description": "Optional tab ID. If provided, switches to that tab before scanning"}, + "tabs_only": {"type": "boolean", "description": "Show tab list only, no HTML", "default": false}, + "switch_tab_id": {"type": "string", "description": "[Optional] Tab ID to switch to"}, "text_only": {"type": "boolean", "description": "Plain text only, no HTML", "default": false}}} }}, {"type": "function", "function": { "name": "web_execute_js", "description": "Execute JS to control browser. Use precisely to reduce web_scan calls. Put code in ```javascript blocks in reply body to avoid escaping", "parameters": {"type": "object", "properties": { - "script": {"type": "string", "description": "[Optional] JS code or file path. NEVER use this param when code is in ```javascript blocks in reply body. Only use when no code block in reply. Mutually exclusive with reply code blocks"}, - "save_to_file": {"type": "string", "description": "Save result to file, for long return values", "default": ""}, - "no_monitor": {"type": "boolean", "description": "Skip page change monitoring, saves 2-3s. Only set for pure reads, not for page actions", "default": false}}} + "script": {"type": "string", "description": "[Mutually exclusive] JS code or script path. NEVER use this param when use reply code block"}, + "save_to_file": {"type": "string", "description": "file path to save long result", "default": ""}, + "no_monitor": {"type": "boolean", "description": "Skip page change monitoring, saves 2-3s. Only for reads, not for page actions", "default": false}}} }}, {"type": "function", "function": { "name": "update_working_checkpoint", "description": "Short-term working notepad, auto-injected each turn to prevent info loss in long tasks. Call during early/mid stages, not at end. When: (1) after reading SOP, store user needs & key constraints (skip for simple 1-2 step tasks); (2) before subtask switch or context flush; (3) after repeated failures, re-read SOP and must store new findings; (4) on new task, update content, clear old progress but keep valid constraints.\n\nDon't call: simple tasks (1-2 steps), task completed (use long-term memory tool)", "parameters": {"type": "object", "properties": { "key_info": {"type": "string", "description": "Replaces current notepad (<200 tokens). Incremental update: review existing, keep valid, add/remove/modify. Store: pitfalls, user requirements, key params/findings, file paths, progress, next steps. Don't store: ephemeral info, obvious context, old task info when user switched tasks. Prefer over-updating over losing key info"}, - "related_sop": {"type": "string", "description": "Related SOP name(s), re-read when needed"}}} + "related_sop": {"type": "string", "description": "Related SOP names, tips for further re-read"}}} }}, {"type": "function", "function": { "name": "ask_user", "description": "Interrupt task to ask user when needing decisions, extra info, or facing unresolvable blockers", "parameters": {"type": "object", "properties": { "question": {"type": "string", "description": "Question for the user"}, - "candidates": {"type": "array", "items": {"type": "string"}, "description": "Optional quick-select choices for the user"}}, "required": ["question"]} + "candidates": {"type": "array", "items": {"type": "string"}, "description": "Optional quick-select choices for the user"}}} }}, {"type": "function", "function": { "name": "start_long_term_update", diff --git a/assets/tools_schema_cn.json b/assets/tools_schema_cn.json new file mode 100644 index 0000000..507067a --- /dev/null +++ b/assets/tools_schema_cn.json @@ -0,0 +1,71 @@ +[ + {"type": "function", "function": { + "name": "code_run", + "description": "代码执行器。优先使用python。禁同时调用多个。为免转义问题,代码放正文 ```python/powershell 块中。禁硬编码大量数据", + "parameters": {"type": "object", "properties": { + "script": {"type": "string", "description": "[Optional] 要执行的代码。为免转义建议留空,改用正文代码块(与此参数互斥)"}, + "type": {"type": "string", "enum": ["python", "powershell"], "description": "代码类型", "default": "python"}, + "timeout": {"type": "integer", "description": "执行超时时间(秒)", "default": 60}, + "cwd": {"type": "string", "description": "工作目录,默认为当前工作目录"}}} + }}, + {"type": "function", "function": { + "name": "file_read", + "description": "读取文件内容。建议在修改文件前先读取,以确保获取最新的上下文和行号。支持分页读取或关键字搜索", + "parameters": {"type": "object", "properties": { + "path": {"type": "string", "description": "文件相对或绝对路径"}, + "start": {"type": "integer", "description": "起始行号(从 1 开始)", "default": 1}, + "count": {"type": "integer", "description": "读取的行数", "default": 200}, + "keyword": {"type": "string", "description": "可选搜索关键字。如果提供,将返回第一个匹配项(忽略大小写)及其周边的内容"}, + "show_linenos": {"type": "boolean", "description": "是否显示行号,建议开启以辅助 file_patch 定位", "default": true}}} + }}, + {"type": "function", "function": { + "name": "file_patch", + "description": "精细化局部文件修改。在文件中寻找唯一的 old_content 块并替换为 new_content。要求 old_content 必须在文件中唯一存在,且空格、缩进、换行必须与原文件完全一致。如果匹配失败,请使用 file_read 重新确认文件内容", + "parameters": {"type": "object", "properties": { + "path": {"type": "string", "description": "文件路径"}, + "old_content": {"type": "string", "description": "文件中需要被替换的原始文本块(需确保唯一性)"}, + "new_content": {"type": "string", "description": "替换后的新文本内容。支持 {{file:路径:起始行:结束行}} 语法引用文件内容,写入前自动展开"}}} + }}, + {"type": "function", "function": { + "name": "file_write", + "description": "用于文件的新建、全量覆盖或追加写入。对于精细的代码修改,应优先使用 file_patch。注意:要写入的内容必须放在回复正文的 标签或代码块中。写入内容支持 {{file:路径:起始行:结束行}} 语法引用文件片段,写入前自动展开", + "parameters": {"type": "object", "properties": { + "path": {"type": "string", "description": "文件路径"}, + "mode": {"type": "string", "enum": ["overwrite", "append", "prepend"], "description": "写入模式覆盖、追加或在开头追加", "default": "overwrite"}}} + }}, + {"type": "function", "function": { + "name": "web_scan", + "description": "获取当前页面的简化HTML内容和标签页列表。注意:简化会过滤边栏、浮动元素等非主体内容,如需查看被过滤内容请用execute_js。切换页面后一般应先调用查看", + "parameters": {"type": "object", "properties": { + "tabs_only": {"type": "boolean", "description": "仅返回标签页列表和当前标签信息,不获取HTML内容", "default": false}, + "switch_tab_id": {"type": "string", "description": "可选的标签页 ID。如果提供,系统将在扫描前切换到该标签页"}, + "text_only": {"type": "boolean", "description": "只要纯文本不要HTML信息", "default": false}}} + }}, + {"type": "function", "function": { + "name": "web_execute_js", + "description": "执行 JS 控制浏览器。建议精准使用减少 web_scan。为免转义问题,代码优先考虑放回复正文 ```javascript 块", + "parameters": {"type": "object", "properties": { + "script": {"type": "string", "description": "[Optional] JS代码或路径。为免转义建议留空,改用正文代码块(与此参数互斥)"}, + "save_to_file": {"type": "string", "description": "结果存文件,适合返回值较长时", "default": ""}, + "no_monitor": {"type": "boolean", "description": "跳过页面变更监控,省2-3秒。仅在纯读取信息时设置,页面操作时不要设置", "default": false}}} + }}, + {"type": "function", "function": { + "name": "update_working_checkpoint", + "description": "短期工作便签,每轮自动注入上下文,防长任务信息丢失。前中期调用,非结束时。何时调用:(1)任务开始读SOP后,存用户需求和关键约束/参数(简单1-2步任务除外);(2)子任务切换或上下文即将被冲刷前;(3)多次重试失败后,重读SOP并必须调用存储新发现;(4)切换新任务时更新内容,清旧进度但保留仍有效的约束。\n\n何时不调用:简单任务(1-2步且无严重约束)、任务已完成时(应当用长期结算工具)", + "parameters": {"type": "object", "properties": { + "key_info": {"type": "string", "description": "替换当前便签(<200 tokens)。增量更新:先回顾现有内容,保留仍有效的,再增删改。存:要避的坑、用户原始需求、关键参数/发现、文件路径、当前进度、下一步计划。不存:马上要用用完即丢的、上下文中显而易见的、用户已换全新任务时的旧任务信息。宁多更新不丢关键"}, + "related_sop": {"type": "string", "description": "相关sop名称,可以多个,必要时需要再读"}}} + }}, + {"type": "function", "function": { + "name": "ask_user", + "description": "当需要用户决策、提供额外信息或遇到无法自动解决的阻碍时,调用此工具中断任务并提问", + "parameters": {"type": "object", "properties": { + "question": {"type": "string", "description": "向用户提出的明确问题"}, + "candidates": {"type": "array", "items": {"type": "string"}, "description": "提供给用户的可选快捷选项列表"}}} + }}, + {"type": "function", "function": { + "name": "start_long_term_update", + "description": "准备开始提炼记忆。发现值得长期记忆的信息(环境事实/用户偏好/避坑经验)时调用此工具。已记忆更新或在自主流程内时无需调用。超15轮完成的任务必须调用以沉淀经验", + "parameters": {"type": "object", "properties": {}}} + } +] \ No newline at end of file diff --git a/hub.pyw b/hub.pyw index 8a88c47..adb4cdc 100644 --- a/hub.pyw +++ b/hub.pyw @@ -206,12 +206,16 @@ class LauncherApp: if not self.selected: return lines = self.mgr.get_output(self.selected) - at_bottom = self.output_text.yview()[1] >= 0.99 + top_frac, bot_frac = self.output_text.yview() + at_bottom = bot_frac >= 0.99 self.output_text.configure(state='normal') self.output_text.delete('1.0', 'end') self.output_text.insert('end', ''.join(lines[-200:])) self.output_text.configure(state='disabled') - if at_bottom: self.output_text.see('end') + if at_bottom: + self.output_text.see('end') + else: + self.output_text.yview_moveto(top_frac) def _poll(self): for svc in self.services: