5.6 KiB
5.6 KiB
P1-02 证据:Windows/Linux shell 与进程树终止
日期:2026-07-09(无人值守轮次) 状态:完成(Windows 侧自动化全绿;Linux 侧逻辑已实现并单测覆盖参数/提示词,进程组用例在 Linux 上运行时生效)
目标(摘自 REPAIR_BACKLOG.md)
- Windows 明确通过
cmd.exe执行;Linux 明确通过/bin/bash -lc执行,不依赖shell=True的平台默认值。 - 超时与主动中止都终止完整子进程树(Windows
taskkill /F /T;Linux 独立 POSIX 进程组,SIGTERM→宽限→SIGKILL 整组)。 - 只保留一份通用
SYSTEM_PROMPT.md,运行时插入短平台 shell/path 段;两平台互不串段。 - 保留输出流、超时、截断、工具结果结构;不加命令审批/沙箱/路径限制。
改动文件
| 文件 | 改动 |
|---|---|
core/platform_shell.py |
新增窄平台适配:shell_command()、popen_flags()、kill_process_tree()、shell_prompt_section()、apply_platform_section()、占位符 {{SHELL_PLATFORM_SECTION}} |
core/agent/tools.py |
tool_bash 的 Popen 改 shell_command(command) + popen_flags();_kill_tree 委托 kill_process_tree;移除 ctx["shell"] 隐式开关 |
core/llm_engine.py |
load_system_prompt() 读文件后过 apply_platform_section()(每次请求仍重读,既有行为不变) |
SYSTEM_PROMPT.md |
通用正文化:第 1 节去 Windows 路径/conda 环境名;原 1.1「shell 真相」cmd 表整体移入运行时 Windows 段;工具表与 3.2 去掉 cmd.exe/dir/findstr 字样;占位符落在原 1.1 位置 |
tests/test_cross_platform_shell.py |
新增 20 项断言(A 平台参数 / B 提示词 / C 进程树 / D 安全边界) |
关键设计决策
- Windows 用字符串命令行,不用 argv 列表。
["cmd.exe","/d","/c",cmd]列表形态会被 CPythonlist2cmdline把内部引号转义成\",cmd 不认, 带引号路径直接'...\python.exe"' is not recognized(实测复现)。最终契约:cmd.exe /d /s /c "<command>"作为字符串交给 CreateProcessW,cmd 按 /s 规则解析 /c 参数 (外层引号剥离、内部引号保留)。实测矩阵:引号 Python 路径rc=0;echo a && echo b正确;%USERPROFILE%展开;;/单引号行为与旧文档一致。 注:旧shell=True之所以能跑,是因为 CPython 对带引号程序名直接 CreateProcess(不经 cmd); 新契约统一显式过 cmd,行为更可预测且与提示词一致。 - POSIX 安全不变量:
kill_process_tree仅当os.getpgid(pid) == pid(确认start_new_session生效、子进程是组首)才killpg,否则退化单进程kill,绝不误杀调用方所在组。 流程:SIGTERM 整组 → 轮询至grace_s=3.0s→ SIGKILL 整组。 - 提示词单一来源:
SYSTEM_PROMPT.md唯一;apply_platform_section只做占位符替换, 无占位符(兜底提示词)原样返回。load_system_prompt()每请求重读 → 平台段永远对应当前平台。
验证结果(本机 Windows 11 x64,CPython 3.10.21,全部显式超时)
| 命令 | 结果 |
|---|---|
python tests/test_cross_platform_shell.py |
20/20 PASS(EXIT=0) |
python tests/test_bash_stream.py |
30/30 PASS(EXIT=0) |
python tests/test_tool_params.py |
35/35 PASS(EXIT=0) |
python tests/run_tests.py(agent core,含 test_agent_core) |
41/41(EXIT=0) |
python tests/smoke_mode.py(完整 agent 回合,走 tool_bash) |
ALL PASS(EXIT=0) |
| P0-03 一致性检查(AGENTS.md 指针 / ARCHITECTURE 历史资料标记 / 无第三方任务书正文) | 3/3 PASS |
测试点明细(test_cross_platform_shell.py)
- A 平台参数:Windows 命令 ==
cmd.exe /d /s /c "echo hi";Linux argv ==["/bin/bash","-lc","echo hi"]; Linuxpopen_flags() == {"start_new_session": True},Windows 为空。 - B 提示词:通用正文含占位符且无平台泄漏(无
cmd.exe//bin/bash);Windows 段含cmd.exe无/bin/bash,Linux 段反之;替换后通用正文逐字节相同(B6);load_system_prompt()== 文件+当前平台段。 - C 进程树(真实进程,父挂 30s + 孙每 0.2s 写心跳文件):
- C1 超时 3s → 错误结果含「超时」,耗时 <15s,父与孙都不存在(心跳静默 >0.6s 且无完成标记);
- C2 主动中止 1.5s → 错误结果含「中止」,父与孙都不存在;
- C3 Linux 独立进程组(Windows 上 SKIP;
pgid==pid断言 + killpg 后子进程消失,Linux 运行即生效)。
- D 安全边界:已退出进程、
None输入均不抛异常。
调试过程记录(铁律:所有调试命令显式超时)
- 首跑 C1 失败:
'...\python.exe"' is not recognized→ 定位为list2cmdline引号转义; 读 CPython 3.10subprocess.py确认shell=True实为直接 CreateProcessW(带引号程序名不经 cmd)。 - 尝试
["cmd.exe","/d","/s","/c", '"'+cmd+'"']列表 → 仍失败(同样被转义)。 - 改字符串命令行 + 实测矩阵(引号路径/&&/管道/%VAR%)→ 全过,定稿。
- 首跑进程树用例
hb_last=None:父脚本模板漏传child.py脚本路径(把心跳路径当脚本)→ 手动subprocess.run复现(超时 6s/3s 探针)→ 修模板,20/20 通过。
Linux 侧待办(不阻塞本任务)
- 在 Linux 环境跑一次
python tests/test_cross_platform_shell.py(C3 生效)+test_bash_stream.py即可闭环;代码路径与 Windows 共用同一套kill_process_tree/shell_command分派。