Files
sorrow404null bc0b92bcdc docs: add agent handoff docs, verification guide, and repair evidence
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.
2026-09-17 16:40:06 +08:00

5.6 KiB
Raw Permalink Blame History

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 /TLinux 独立 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>" 作为字符串交给 CreateProcessWcmd 按 /s 规则解析 /c 参数 (外层引号剥离、内部引号保留)。实测矩阵:引号 Python 路径 rc=0echo 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 x64CPython 3.10.21,全部显式超时)

命令 结果
python tests/test_cross_platform_shell.py 20/20 PASSEXIT=0
python tests/test_bash_stream.py 30/30 PASSEXIT=0
python tests/test_tool_params.py 35/35 PASSEXIT=0
python tests/run_tests.pyagent core,含 test_agent_core 41/41EXIT=0
python tests/smoke_mode.py(完整 agent 回合,走 tool_bash ALL PASSEXIT=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 上 SKIPpgid==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.pyC3 生效)+ test_bash_stream.py 即可闭环;代码路径与 Windows 共用同一套 kill_process_tree/shell_command 分派。