SimAgentPlg 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,8 @@
1
+ MODEL_API_KEY=sk-9f226850f31a474ba7b6dddc6c7c3ff7
2
+ MODEL_URL=https://api.deepseek.com
3
+
4
+ SKILL_MODEL=deepseek-v4-flash
5
+
6
+ CHAT_MODEL=deepseek-v4-flash
7
+
8
+ LLM_TIMEOUT=30
@@ -0,0 +1,13 @@
1
+ .env
2
+
3
+ __pycache__/
4
+
5
+ my_skills
6
+
7
+ .ruff_cache
8
+
9
+ .playwright-mcp
10
+
11
+ .DS_Store
12
+
13
+ NapcatBot
@@ -0,0 +1 @@
1
+ 3.12
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,204 @@
1
+ Metadata-Version: 2.4
2
+ Name: SimAgentPlg
3
+ Version: 0.1.0
4
+ Summary: A lightweight multi-agent framework with ReAct reasoning, tool dispatch, and MCP integration
5
+ License: MIT License
6
+
7
+ Copyright (c) 2024
8
+
9
+ Permission is hereby granted, free of charge, to any person obtaining a copy
10
+ of this software and associated documentation files (the "Software"), to deal
11
+ in the Software without restriction, including without limitation the rights
12
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
+ copies of the Software, and to permit persons to whom the Software is
14
+ furnished to do so, subject to the following conditions:
15
+
16
+ The above copyright notice and this permission notice shall be included in all
17
+ copies or substantial portions of the Software.
18
+
19
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25
+ SOFTWARE.
26
+ License-File: LICENSE
27
+ Requires-Python: >=3.12
28
+ Requires-Dist: fastmcp>=3.4.2
29
+ Requires-Dist: openai>=2.41.0
30
+ Requires-Dist: python-dotenv>=1.2.2
31
+ Description-Content-Type: text/markdown
32
+
33
+ # All Agent
34
+
35
+ A lightweight multi-agent framework with ReAct reasoning, tool dispatch, and MCP integration.
36
+
37
+ ## Features
38
+
39
+ - **ReAct Agent** — ReAct (Reasoning + Acting) loop with multi-turn tool calling
40
+ - **Chat Agent** — simple conversational agent with multi-turn history support
41
+ - **Tool Dispatch** — convention-over-configuration: define `do_{tool_name}` methods, auto-routed via reflection
42
+ - **MCP Integration** — pluggable MCP server manager for external tool providers
43
+ - **Skill System** — skill-based prompt injection for domain-specific behaviors
44
+ - **Built-in Bash Executor** — async sandboxed bash execution with timeout, output truncation, and blacklist filtering
45
+ - **Stateless Execution** — each `runtime()` call starts with a clean context; history is caller-managed
46
+ - **OpenAI-compatible** — works with any OpenAI-compatible API (DeepSeek, etc.)
47
+
48
+ ## Installation
49
+
50
+ ```bash
51
+ pip install all-agent
52
+ ```
53
+
54
+ Or with uv:
55
+
56
+ ```bash
57
+ uv pip install all-agent
58
+ ```
59
+
60
+ ## Quick Start
61
+
62
+ Set up your environment variables (`.env`):
63
+
64
+ ```env
65
+ CHAT_MODEL=deepseek-chat
66
+ MODEL_API_KEY=sk-xxxxxxxx
67
+ MODEL_URL=https://api.deepseek.com
68
+ LLM_TIMEOUT=30
69
+ ```
70
+
71
+ ### Chat Agent
72
+
73
+ ```python
74
+ from allagent import ChatLoop
75
+
76
+ loop = ChatLoop()
77
+ result = await loop.runtime(task="介绍一下你自己")
78
+
79
+ # With multi-turn history
80
+ history = [
81
+ {"role": "user", "content": "今天天气不错"},
82
+ {"role": "assistant", "content": "是啊,适合出去走走"},
83
+ ]
84
+ result = await loop.runtime(task="我们去哪", history=history)
85
+ ```
86
+
87
+ ### ReAct Agent
88
+
89
+ ```python
90
+ from allagent import ReactLoop
91
+
92
+ loop = ReactLoop()
93
+ result = await loop.runtime(task="帮我写一个Python脚本打印当前时间")
94
+ ```
95
+
96
+ The ReAct agent supports built-in tools (like `bash_run`) and any MCP tools configured in `mcp_config.json`.
97
+
98
+ ### MCP Configuration
99
+
100
+ Place an `mcp_config.json` alongside your ReactLoop:
101
+
102
+ ```json
103
+ {
104
+ "mcpServers": {
105
+ "playwright": {
106
+ "command": "npx",
107
+ "args": ["-y", "@anthropic/mcp-playwright"]
108
+ }
109
+ }
110
+ }
111
+ ```
112
+
113
+ ## Architecture
114
+
115
+ ```
116
+ LLMConfig (BaseHandler, ABC)
117
+ ├── ChatLoop — stateless conversational agent
118
+ ├── ReactLoop — ReAct reasoning + tool dispatch
119
+ │ ├── MCP tools — external tools via MCP protocol
120
+ │ ├── Skill system — domain-specific prompt injection
121
+ │ └── Local tools — built-in bash_run, extensible
122
+ └── (future) PlanLoop / ExecuteLoop
123
+ ```
124
+
125
+ ### Tool Dispatch Flow
126
+
127
+ ```
128
+ LLM calls "bash_run"
129
+ → BaseHandler.dispatch("bash_run", args)
130
+ → hasattr(self, "do_bash_run")? YES
131
+ → await self.do_bash_run(args) ← local tool
132
+ → NO
133
+ → "未知工具" → MCP fallback ← external tool
134
+ ```
135
+
136
+ ### Adding a Local Tool
137
+
138
+ 1. Define the tool schema in `tool_schema.py`:
139
+
140
+ ```python
141
+ LOCAL_TOOLS = [
142
+ {
143
+ "type": "function",
144
+ "function": {
145
+ "name": "calculator",
146
+ "description": "Evaluate a math expression",
147
+ "parameters": {
148
+ "type": "object",
149
+ "properties": {
150
+ "expression": {"type": "string", "description": "Math expression"}
151
+ },
152
+ "required": ["expression"]
153
+ }
154
+ }
155
+ }
156
+ ]
157
+ ```
158
+
159
+ 2. Add the `do_calculator` method in `LLMConfig`:
160
+
161
+ ```python
162
+ async def do_calculator(self, args: dict) -> StepOutcome:
163
+ result = eval(args["expression"])
164
+ return StepOutcome(data=result, next_prompt="\n")
165
+ ```
166
+
167
+ All agents automatically inherit the new tool.
168
+
169
+ ## API
170
+
171
+ ### `ChatLoop`
172
+
173
+ ```python
174
+ loop = ChatLoop(temperature=0.7)
175
+ await loop.runtime(*, task, system_prompt=None, history=None) -> str | None
176
+ ```
177
+
178
+ ### `ReactLoop`
179
+
180
+ ```python
181
+ loop = ReactLoop()
182
+ await loop.runtime(*, task, system_prompt=None, history=None) -> str | None
183
+ ```
184
+
185
+ ### `StepOutcome`
186
+
187
+ ```python
188
+ @dataclass
189
+ class StepOutcome:
190
+ data: Any # tool return value
191
+ next_prompt: str | None # None = task complete
192
+ should_exit: bool # True = force exit
193
+ ```
194
+
195
+ ## Requirements
196
+
197
+ - Python >= 3.12
198
+ - fastmcp >= 3.4.2
199
+ - openai >= 2.41.0
200
+ - python-dotenv >= 1.2.2
201
+
202
+ ## License
203
+
204
+ MIT
@@ -0,0 +1,172 @@
1
+ # All Agent
2
+
3
+ A lightweight multi-agent framework with ReAct reasoning, tool dispatch, and MCP integration.
4
+
5
+ ## Features
6
+
7
+ - **ReAct Agent** — ReAct (Reasoning + Acting) loop with multi-turn tool calling
8
+ - **Chat Agent** — simple conversational agent with multi-turn history support
9
+ - **Tool Dispatch** — convention-over-configuration: define `do_{tool_name}` methods, auto-routed via reflection
10
+ - **MCP Integration** — pluggable MCP server manager for external tool providers
11
+ - **Skill System** — skill-based prompt injection for domain-specific behaviors
12
+ - **Built-in Bash Executor** — async sandboxed bash execution with timeout, output truncation, and blacklist filtering
13
+ - **Stateless Execution** — each `runtime()` call starts with a clean context; history is caller-managed
14
+ - **OpenAI-compatible** — works with any OpenAI-compatible API (DeepSeek, etc.)
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ pip install all-agent
20
+ ```
21
+
22
+ Or with uv:
23
+
24
+ ```bash
25
+ uv pip install all-agent
26
+ ```
27
+
28
+ ## Quick Start
29
+
30
+ Set up your environment variables (`.env`):
31
+
32
+ ```env
33
+ CHAT_MODEL=deepseek-chat
34
+ MODEL_API_KEY=sk-xxxxxxxx
35
+ MODEL_URL=https://api.deepseek.com
36
+ LLM_TIMEOUT=30
37
+ ```
38
+
39
+ ### Chat Agent
40
+
41
+ ```python
42
+ from allagent import ChatLoop
43
+
44
+ loop = ChatLoop()
45
+ result = await loop.runtime(task="介绍一下你自己")
46
+
47
+ # With multi-turn history
48
+ history = [
49
+ {"role": "user", "content": "今天天气不错"},
50
+ {"role": "assistant", "content": "是啊,适合出去走走"},
51
+ ]
52
+ result = await loop.runtime(task="我们去哪", history=history)
53
+ ```
54
+
55
+ ### ReAct Agent
56
+
57
+ ```python
58
+ from allagent import ReactLoop
59
+
60
+ loop = ReactLoop()
61
+ result = await loop.runtime(task="帮我写一个Python脚本打印当前时间")
62
+ ```
63
+
64
+ The ReAct agent supports built-in tools (like `bash_run`) and any MCP tools configured in `mcp_config.json`.
65
+
66
+ ### MCP Configuration
67
+
68
+ Place an `mcp_config.json` alongside your ReactLoop:
69
+
70
+ ```json
71
+ {
72
+ "mcpServers": {
73
+ "playwright": {
74
+ "command": "npx",
75
+ "args": ["-y", "@anthropic/mcp-playwright"]
76
+ }
77
+ }
78
+ }
79
+ ```
80
+
81
+ ## Architecture
82
+
83
+ ```
84
+ LLMConfig (BaseHandler, ABC)
85
+ ├── ChatLoop — stateless conversational agent
86
+ ├── ReactLoop — ReAct reasoning + tool dispatch
87
+ │ ├── MCP tools — external tools via MCP protocol
88
+ │ ├── Skill system — domain-specific prompt injection
89
+ │ └── Local tools — built-in bash_run, extensible
90
+ └── (future) PlanLoop / ExecuteLoop
91
+ ```
92
+
93
+ ### Tool Dispatch Flow
94
+
95
+ ```
96
+ LLM calls "bash_run"
97
+ → BaseHandler.dispatch("bash_run", args)
98
+ → hasattr(self, "do_bash_run")? YES
99
+ → await self.do_bash_run(args) ← local tool
100
+ → NO
101
+ → "未知工具" → MCP fallback ← external tool
102
+ ```
103
+
104
+ ### Adding a Local Tool
105
+
106
+ 1. Define the tool schema in `tool_schema.py`:
107
+
108
+ ```python
109
+ LOCAL_TOOLS = [
110
+ {
111
+ "type": "function",
112
+ "function": {
113
+ "name": "calculator",
114
+ "description": "Evaluate a math expression",
115
+ "parameters": {
116
+ "type": "object",
117
+ "properties": {
118
+ "expression": {"type": "string", "description": "Math expression"}
119
+ },
120
+ "required": ["expression"]
121
+ }
122
+ }
123
+ }
124
+ ]
125
+ ```
126
+
127
+ 2. Add the `do_calculator` method in `LLMConfig`:
128
+
129
+ ```python
130
+ async def do_calculator(self, args: dict) -> StepOutcome:
131
+ result = eval(args["expression"])
132
+ return StepOutcome(data=result, next_prompt="\n")
133
+ ```
134
+
135
+ All agents automatically inherit the new tool.
136
+
137
+ ## API
138
+
139
+ ### `ChatLoop`
140
+
141
+ ```python
142
+ loop = ChatLoop(temperature=0.7)
143
+ await loop.runtime(*, task, system_prompt=None, history=None) -> str | None
144
+ ```
145
+
146
+ ### `ReactLoop`
147
+
148
+ ```python
149
+ loop = ReactLoop()
150
+ await loop.runtime(*, task, system_prompt=None, history=None) -> str | None
151
+ ```
152
+
153
+ ### `StepOutcome`
154
+
155
+ ```python
156
+ @dataclass
157
+ class StepOutcome:
158
+ data: Any # tool return value
159
+ next_prompt: str | None # None = task complete
160
+ should_exit: bool # True = force exit
161
+ ```
162
+
163
+ ## Requirements
164
+
165
+ - Python >= 3.12
166
+ - fastmcp >= 3.4.2
167
+ - openai >= 2.41.0
168
+ - python-dotenv >= 1.2.2
169
+
170
+ ## License
171
+
172
+ MIT
@@ -0,0 +1,19 @@
1
+ [project]
2
+ name = "SimAgentPlg"
3
+ version = "0.1.0"
4
+ description = "A lightweight multi-agent framework with ReAct reasoning, tool dispatch, and MCP integration"
5
+ readme = "README.md"
6
+ license = {file = "LICENSE"}
7
+ requires-python = ">=3.12"
8
+ dependencies = [
9
+ "fastmcp>=3.4.2",
10
+ "openai>=2.41.0",
11
+ "python-dotenv>=1.2.2",
12
+ ]
13
+
14
+ [build-system]
15
+ requires = ["hatchling"]
16
+ build-backend = "hatchling.build"
17
+
18
+ [tool.hatch.build.targets.wheel]
19
+ packages = ["src/allagent"]
@@ -0,0 +1,17 @@
1
+ """All Agent — a lightweight multi-agent framework with ReAct reasoning, tool dispatch, and MCP integration."""
2
+
3
+ from allagent.agent.react.reactor import ReactLoop
4
+ from allagent.agent.chat.chat import ChatLoop
5
+ from allagent.agent.base import LLMConfig, StepOutcome, BaseHandler
6
+ from allagent.plugins.mcp.mcp_manager import McpServerManager
7
+ from allagent.plugins.skill.skill_manager import SkillManager
8
+
9
+ __all__ = [
10
+ "ReactLoop",
11
+ "ChatLoop",
12
+ "LLMConfig",
13
+ "StepOutcome",
14
+ "BaseHandler",
15
+ "McpServerManager",
16
+ "SkillManager",
17
+ ]
@@ -0,0 +1,7 @@
1
+ """Agent 调度 — ReAct / Chat 循环。"""
2
+
3
+ from allagent.agent.react.reactor import ReactLoop
4
+ from allagent.agent.chat.chat import ChatLoop
5
+ from allagent.agent.base import LLMConfig
6
+
7
+ __all__ = ["ReactLoop", "ChatLoop", "LLMConfig"]
@@ -0,0 +1,223 @@
1
+ import asyncio
2
+ from typing import Optional, Any, cast
3
+ import os
4
+
5
+ from dataclasses import dataclass
6
+ from openai import AsyncOpenAI
7
+ from abc import ABC, abstractmethod
8
+ from dotenv import load_dotenv
9
+ from openai.types.chat import ChatCompletionMessage
10
+
11
+ from allagent.logger import get_logger
12
+ from .tool_schema import LOCAL_TOOLS
13
+
14
+ load_dotenv()
15
+
16
+ logger = get_logger("LLMCONFIG")
17
+
18
+ BASE_PROMPT = """
19
+ 你是一个帮助用户完成各种任务的聊天助手
20
+ """
21
+
22
+
23
+ @dataclass
24
+ class StepOutcome:
25
+ data: Any # 工具返回值
26
+ next_prompt: Optional[str] = None # 下一轮追加的 prompt,None 表示任务完成
27
+ should_exit: bool = False # True 表示立即退出
28
+
29
+
30
+ # bash 命令黑名单
31
+ BASH_BLACKLIST = [
32
+ "rm ",
33
+ "rm\n",
34
+ "rm\t",
35
+ "rm(",
36
+ "rm;",
37
+ "rm\\",
38
+ "rm|",
39
+ "rm&",
40
+ "rm<",
41
+ "rm>",
42
+ "sudo ",
43
+ "mkfs.",
44
+ "dd if=",
45
+ ":(){ :|:& };:",
46
+ "> /dev/sda",
47
+ "/dev/null",
48
+ "chmod 777",
49
+ ]
50
+
51
+
52
+ async def bash_run(
53
+ code: str,
54
+ timeout: int = 60,
55
+ cwd: Optional[str] = None,
56
+ maxlen: int = 10000,
57
+ ) -> dict:
58
+ """异步执行 bash 代码片段,返回执行结果 dict。"""
59
+ logger.info("bash_run 脚本:\n%s...", code[:40])
60
+
61
+ for pattern in BASH_BLACKLIST:
62
+ if pattern in code:
63
+ logger.warning("bash_run 命中黑名单: %s", pattern.strip())
64
+ return {
65
+ "status": "error",
66
+ "msg": f"禁止执行危险命令: {pattern.strip()}",
67
+ "exit_code": -1,
68
+ }
69
+
70
+ try:
71
+ process = await asyncio.create_subprocess_exec(
72
+ "bash",
73
+ "-c",
74
+ code,
75
+ stdout=asyncio.subprocess.PIPE,
76
+ stderr=asyncio.subprocess.STDOUT,
77
+ cwd=cwd,
78
+ )
79
+
80
+ stdout_chunks: list[str] = []
81
+
82
+ async def read_stdout() -> None:
83
+ assert process.stdout is not None
84
+ while True:
85
+ line_bytes = await process.stdout.readline()
86
+ if not line_bytes:
87
+ break
88
+ try:
89
+ line = line_bytes.decode("utf-8")
90
+ except UnicodeDecodeError:
91
+ line = line_bytes.decode("gbk", errors="ignore")
92
+ stdout_chunks.append(line)
93
+
94
+ read_task = asyncio.create_task(read_stdout())
95
+
96
+ try:
97
+ await asyncio.wait_for(process.wait(), timeout=timeout)
98
+ except asyncio.TimeoutError:
99
+ try:
100
+ process.kill()
101
+ except Exception:
102
+ pass
103
+ await process.wait()
104
+ stdout_chunks.append("\n[Timeout Error] 超时强制终止\n")
105
+
106
+ await read_task
107
+
108
+ stdout_str = "".join(stdout_chunks)
109
+ exit_code = process.returncode if process.returncode is not None else -1
110
+ status = "success" if exit_code == 0 else "error"
111
+
112
+ return {
113
+ "status": status,
114
+ "stdout": stdout_str[-maxlen:],
115
+ "exit_code": exit_code,
116
+ }
117
+
118
+ except Exception as e:
119
+ return {"status": "error", "msg": str(e)}
120
+
121
+
122
+ class BaseHandler:
123
+ """
124
+ 工具调度基类 —— 约定优于配置:
125
+ 子类只需定义 do_{tool_name} 方法,LLM 调用该工具时会自动反射路由。
126
+ """
127
+
128
+ async def dispatch(
129
+ self, tool_name: str, args: dict, index: int = 0, tool_num: int = 1
130
+ ) -> StepOutcome:
131
+ """
132
+ 根据 tool_name 反射到 self.do_{tool_name} 方法。
133
+ 自动注入 _index / _tool_num 参数。
134
+ """
135
+ method_name = f"do_{tool_name}"
136
+ if hasattr(self, method_name):
137
+ args["_index"] = index
138
+ args["_tool_num"] = tool_num
139
+ return await getattr(self, method_name)(args)
140
+ else:
141
+ return StepOutcome(
142
+ None, next_prompt=f"未知工具 {tool_name}", should_exit=False
143
+ )
144
+
145
+
146
+ class LLMConfig(BaseHandler, ABC):
147
+ """
148
+ 为后续ReAct, Plan and Execute,提供基础框架
149
+ 它用于调用任何兼容OpenAI接口的服务,并默认使用流式响应。
150
+ """
151
+
152
+ def __init__(self, temperature: float = 0.7):
153
+ """
154
+ 初始化客户端。参数从环境变量加载。
155
+ """
156
+ model = os.getenv("CHAT_MODEL")
157
+ api_key = os.getenv("MODEL_API_KEY")
158
+ base_url = os.getenv("MODEL_URL")
159
+ timeout = int(os.getenv("LLM_TIMEOUT", 60))
160
+ self.temperature = temperature
161
+
162
+ if not model or not api_key or not base_url:
163
+ raise ValueError("模型ID、API密钥和服务地址必须被提供或在.env文件中定义。")
164
+
165
+ self.model = model
166
+ self.apiKey = api_key
167
+ self.baseUrl = base_url
168
+ self.timeout = timeout
169
+ self.exec_cwd = os.getcwd()
170
+ self.messages: list = []
171
+ self.all_tools = [*LOCAL_TOOLS]
172
+ self.client = AsyncOpenAI(api_key=api_key, base_url=base_url, timeout=timeout)
173
+
174
+ @abstractmethod
175
+ async def runtime(
176
+ self, *, task: str, system_prompt: str = BASE_PROMPT,
177
+ history: Optional[list[dict]] = None,
178
+ ) -> str | None:
179
+ pass
180
+
181
+ async def chat_text(
182
+ self, messages: list[dict[str, str]], *, tools: Optional[list[dict[str, str]]]
183
+ ) -> ChatCompletionMessage:
184
+ """Call the configured chat model and return stripped text."""
185
+
186
+ try:
187
+ response = await self.client.chat.completions.create(
188
+ model=self.model,
189
+ messages=cast(Any, messages),
190
+ temperature=self.temperature,
191
+ tools=tools, # ty:ignore[invalid-argument-type]
192
+ )
193
+ except Exception as exc:
194
+ raise KeyError(f"chat completion failed: {exc}") from exc
195
+ message: ChatCompletionMessage = response.choices[0].message
196
+ return message
197
+
198
+ async def do_bash_run(self, args: dict) -> StepOutcome:
199
+ """执行 bash 代码片段。"""
200
+ code = args.get("code") or args.get("script")
201
+ if not code:
202
+ logger.warning("bash_run 缺少 code 参数")
203
+ return StepOutcome(
204
+ "[Error] Code missing. Use 'code' or 'script' arg.",
205
+ next_prompt="\n",
206
+ )
207
+
208
+ try:
209
+ timeout = int(args.get("timeout", 60))
210
+ except Exception:
211
+ timeout = 60
212
+
213
+ tool_num = args.get("_tool_num", 1)
214
+ maxlen = max(1, 10000 // tool_num)
215
+
216
+ logger.info("执行 bash_run, timeout=%d, cwd=%s", timeout, self.exec_cwd)
217
+ result = await bash_run(code, timeout=timeout, cwd=self.exec_cwd, maxlen=maxlen)
218
+ logger.info(
219
+ "bash_run 完成, status=%s, exit_code=%s",
220
+ result.get("status"),
221
+ result.get("exit_code"),
222
+ )
223
+ return StepOutcome(result, next_prompt="\n")