feat(agent): unified cross-platform shell execution contract (cmd/bash)

Windows runs 'cmd.exe /d /s /c <command>' as a string command line; Linux uses ['/bin/bash', '-lc', command]. Removes implicit shell=True behavior and aligns the agent system prompt per platform.
This commit is contained in:
2026-09-17 16:40:02 +08:00
parent ce56c77023
commit 60200260ab
5 changed files with 424 additions and 54 deletions
+8 -16
View File
@@ -32,6 +32,7 @@ from typing import Any, Callable, Dict, List, Optional
from .types import (AgentMessage, AgentTool, AgentToolResult, AbortSignal,
ToolCall, new_id)
from ..platform_shell import kill_process_tree, popen_flags, shell_command # P1-02
try:
from core.debug_log import debug_log as _dbg_log # 🆕 计时观察日志(线程安全/静默)
@@ -448,8 +449,8 @@ def tool_bash(tool_call_id: str, args: Dict[str, Any], signal: AbortSignal,
return AgentToolResult.text("command 不能为空", is_error=True)
timeout = min(float(args.get("timeout", 120)), 600)
cwd = ctx.get("cwd") or os.getcwd()
# 对照 pi bash 工具:命令始终经 shell 解释(支持管道/别名/内置命令)
use_shell = ctx.get("shell", True)
# P1-02:显式 shell 契约(Windows cmd.exe /d /cLinux /bin/bash -lc),
# 不再依赖 shell=True 的平台默认值;ctx 的 "shell" 键已废弃(bash 工具即 shell 工具)
on_timer = ctx.get("on_timer")
t0 = time.time()
# 🆕 计时观察①:计时器启动时刻 + 模型实际传的 timeout 值
@@ -466,19 +467,9 @@ def tool_bash(tool_call_id: str, args: Dict[str, Any], signal: AbortSignal,
pass
def _kill_tree(proc):
"""杀整个进程树:Windows 上默认 kill 只杀 cmd 壳,孤儿子进程继续
持管道 → 假超时(设定10s 实际20s)。taskkill /T 整树杀"""
try:
if os.name == "nt":
subprocess.run(["taskkill", "/F", "/T", "/PID", str(proc.pid)],
capture_output=True, timeout=10)
else:
proc.kill()
except Exception:
try:
proc.kill()
except Exception:
pass
"""杀整个进程树:Windows taskkill /F /TLinux 独立进程组
SIGTERM → 宽限 → SIGKILL(见 core.platform_shell.kill_process_tree"""
kill_process_tree(proc)
# 🐛 修复:text=True 不带 encoding 时按系统码页(中文 Windows=GBK)解码,
# 子进程输出 UTF-8python/git/中文 echo)→ _readerthread UnicodeDecodeError。
@@ -486,10 +477,11 @@ def tool_bash(tool_call_id: str, args: Dict[str, Any], signal: AbortSignal,
_env = dict(os.environ, PYTHONIOENCODING="utf-8")
try:
proc = subprocess.Popen(
command, shell=use_shell,
shell_command(command), # P1-02:显式 shell 命令
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
text=True, encoding="utf-8", errors="replace",
cwd=cwd, env=_env,
**popen_flags(), # P1-02Linux 独立进程组(start_new_session
)
except Exception as e:
return AgentToolResult.text(f"执行失败: {e}", is_error=True)