Add docs/agent-handoff (backlog, execution state, verification, platform plan, per-task evidence), repo AGENTS.md, and architecture notes updated for the dual-renderer design.
75 lines
5.6 KiB
Markdown
75 lines
5.6 KiB
Markdown
# 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 安全边界) |
|
||
|
||
## 关键设计决策
|
||
|
||
1. **Windows 用字符串命令行,不用 argv 列表。**
|
||
`["cmd.exe","/d","/c",cmd]` 列表形态会被 CPython `list2cmdline` 把内部引号转义成 `\"`,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,行为更可预测且与提示词一致。
|
||
2. **POSIX 安全不变量**:`kill_process_tree` 仅当 `os.getpgid(pid) == pid`(确认 `start_new_session`
|
||
生效、子进程是组首)才 `killpg`,否则退化单进程 `kill`,绝不误杀调用方所在组。
|
||
流程:SIGTERM 整组 → 轮询至 `grace_s=3.0s` → SIGKILL 整组。
|
||
3. **提示词单一来源**:`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"]`;
|
||
Linux `popen_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` 输入均不抛异常。
|
||
|
||
### 调试过程记录(铁律:所有调试命令显式超时)
|
||
|
||
1. 首跑 C1 失败:`'...\python.exe"' is not recognized` → 定位为 `list2cmdline` 引号转义;
|
||
读 CPython 3.10 `subprocess.py` 确认 `shell=True` 实为直接 CreateProcessW(带引号程序名不经 cmd)。
|
||
2. 尝试 `["cmd.exe","/d","/s","/c", '"'+cmd+'"']` 列表 → 仍失败(同样被转义)。
|
||
3. 改**字符串**命令行 + 实测矩阵(引号路径/&&/管道/%VAR%)→ 全过,定稿。
|
||
4. 首跑进程树用例 `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` 分派。
|