agent-Specialization/cache_research/gateway/audit_execution_plane/execution_plane_audit.md
JOJO d9bd599c1c feat(runtime): Gateway 独立启动与公共协议落地(改造第 0-3 步)
- 闭环修复:审批 task_id 显式化(N2)、通知回退路径门闸 finally 释放(N1)、
  personalization 原子写(S10,公共 atomic_write_json)
- server.tasks 拆解:__init__ 轻量化、Blueprint/路由移至 blueprint.py/web.py,
  app_legacy 装配点惰性挂载;models.py 循环依赖死导入移除
- 执行链 Web 解耦:emit_event/run_background 安全包装(socketio 未绑定静默),
  清除 4 处裸 socketio.emit(含 task_stopped 先写事件流再推送的顺序修复)
- RuntimeService 补建对话下沉:chat 任务无 conversation_id 时服务层装配,
  客户端无需复制 Web 两步流程
- 事件协议:get_task_events 返回 meta.window_start 缺口检测水位
- 验收:test_runtime_standalone_lifecycle 子进程隔离(config import 固化问题),
  独立启动 + 协议全链路 + 审批语义全绿;全量 74 测试失败恰为 4 项存量
2026-09-07 23:00:15 +08:00

30 KiB
Raw Blame History

Execution Plane 盘点与耦合点报告Gateway 化第 4 步前置调研)

版本v12026-09-07 定位为「Runtime 与执行环境分层Execution Contract」盘点工具执行链路现状纯只读调研未修改任何项目代码。 范围:core/main_terminal_parts/modules/terminal_ops/modules/persistent_terminal/modules/file_manager/modules/host_sandbox_runner.pymodules/docker_readonly_exec.pymodules/landlock_launcher.pycore/web_terminal.pycore/main_terminal.pyserver/chat_flow_tool_loop.py 及必要的关联文件sender/事件链)。 行号以 2026-09-07 工作区代码为准;若与代码冲突,以代码为准。


0. 执行链路全景(一次理解)

模型循环 (Runtime 层)
  chat_flow_tool_loop.py:522 _execute_tool_calls_impl
    ├─ web_terminal.evaluate_tool_permission()          # 权限裁决S8
    ├─ capture_monitor_snapshot(file_manager)           # 执行前快照读tool_start 附带)
    └─ web_terminal.handle_tool_call(function_name, args)  # tools_execution.py:1057 单一入口
         ↓ 按 tool_name 分派(约 30 个分支)
         命令执行   → self.terminal_ops.run_command(...) / bg_manager.create_background_command(...)
         文件读写   → self.file_manager.*host 本地 or ContainerFileProxy→docker exec
         终端会话   → self.terminal_manager.open_terminal/send_to_terminal/get_terminal_snapshot
         子智能体   → self.sub_agent_manager.create_sub_agent/execute_tool_for_sub_agent
         其他       → memory/search/ocr/todo/workflow/personalization 等 manager
         ↓ 最终执行环境
         Host:  asyncio.subprocess[(sandbox-exec|bwrap|WSL bwrap) 或 裸 shell]
         Docker: docker exec [-u readonly-uid] [/bin/bash -lc cmd](只读走 docker_readonly_exec + landlock
         文件:   进程内 直接读写host或 docker exec python helpercontainer_file_proxy.py

现状核心观察:工具 handlerRuntime 侧)直接持有三个"执行环境句柄"属性——self.terminal_opsself.file_managerself.terminal_manager(外加 self.sub_agent_managerself.background_command_manager。执行环境选择逻辑host 沙箱 vs docker vs direct内嵌在 terminal_ops/file_manager 各自的方法内部,由 self.container_sessionContainerHandlemode ∈ {docker, host})与 self.host_execution_modesandbox/direct两把开关驱动。Runtime 与 Execution Plane 之间目前没有独立的接口层


1. 执行面清单

约定

  • ① = 工具入口handler 分支);② = 中间层;③ = 最终执行后端;「触达执行环境」= 会产生真实子进程 / 真实文件写 / 真实容器会话。
  • 所有行号 = 该分支/函数定义行handler 分支为 elif tool_name == 行)。

A. 命令执行类

子类 ① 入口handler ② 中间层 ③ 最终执行后端 执行环境来源
run_command前台 tools_execution.py:1879 + :1936 await self.terminal_ops.run_command(...) terminal_ops/run.py:434 run_command:160 _run_command_subprocess 见下方 A1/A2/A3 三分支 self.terminal_opscontainer_session + host_execution_mode
run_command后台 tools_execution.py:1909-1924 bg_manager.create_background_command(terminal_ops=...) background_command_manager.py:39 create_background_command:185 _run_command_thread自带完整 docker/host 分支,不调 terminal_ops.run_command 同 A1/A2/A3逻辑为复制实现 terminal_ops 的属性快照session/python_env/host_execution_mode/网络权限/写权限)
custom_tool自定义工具 tools_execution.py:1218 await self.custom_tool_executor.run(...) custom_tool_executor.py:40 run:69 await self.terminal_ops.run_command(...) 同 run_command self.custom_tool_executor 构造时注入的 self.terminal_opsmain_terminal.py:167
install_package若暴露 terminal_ops/run.py:410 install_package _run_command_subprocess 同 run_command 同上

A1 后端Docker 容器内执行(当 container_session.mode == "docker"

  • terminal_ops/run.py:177-207docker exec -e PATH=... -w <mount_path>/<rel> <container_name> /bin/bash -lc <command>
  • 只读(sandbox_write_access=False)时追加 docker_readonly_exec_args()docker_readonly_exec.py:66-u 10001:10001 非特权 uid 强制 DAC 只读),并 docker_readonly_wrap_innerdocker_readonly_exec.py:172)加 Landlock 进程级只读域(landlock_launcher.py:91 install_readonly_domain_landlock_enabled 开关,失败自动降级纯 DAC
  • 后台同构:background_command_manager.py:232-260

A2 后端:宿主机 sandbox 内执行host_execution_mode != "direct"host_sandbox_enabled()

  • terminal_ops/run.py:218-242:先 build_host_sandbox_plan(可写,host_sandbox_runner.py:197)或 build_host_sandbox_readonly_plan(只读,:213)生成 SandboxPlan,再 asyncio.create_subprocess_exec
  • 按平台展开macOS sandbox-exec_build_macos_plan :251、Linux bwrap_build_linux_plan :409、Windows WSL bwrap_build_windows_plan_build_windows_wsl_plan :626网络权限由 plan 注入(_build_macos_network_policy :186restricted/full/none)。
  • 后台同构:background_command_manager.py:262-290

A3 后端裸宿主机执行direct无沙箱

  • terminal_ops/run.py:255-262 asyncio.create_subprocess_shell;后台同构 background_command_manager.py:290+
  • host_execution_mode == "sandbox"host_sandbox_enabled() 为假时直接返回错误(terminal_ops/run.py:249-253)。

B. 文件读写类(文件工具 = 进程内操作,不经 OS 沙箱子进程;沙箱语义在进程内复刻)

子类 ① 入口handler ② 中间层 ③ 最终执行后端
read_file/read_skill tools_execution.py:1219/1223tools_read.py:419 _handle_read_toolread/serach/extract 3 模式)→ file_manager.read_file/read_text_segment/search_text/extract_segmentsread_mixin.py:95/130/221/295 file_managerbase.py:50 host进程内路径读写先过 _validate_pathpath_mixin.py:83 + _ensure_host_accesspath_mixin.py:215 复刻 macOS 禁读清单docker_container_callContainerFileProxy.runcontainer_file_proxy.py:464)→ docker exec -i <container> python <helper>
write_file tools_execution.py:1625-1650(写前 _track_shallow_versioning 浅备份)→ file_manager.write_filecrud_mixin.py:242 同左 host直接 Path.write_text(受 _ensure_host_access("write") 授权范围限制dockercontainer proxy _write_filecontainer_file_proxy.py:246
edit_file tools_execution.py:1652-1675file_manager.replace_many_in_filereplace_mixin.py:125 同左 host进程内替换dockerproxy
create_file / delete_file / rename_file / create_folder tools_execution.py:1569/1589/1604/1677 crud_mixin.py:53/93/133/193 host进程内dockerproxy
save_webpage写文件 tools_execution.py:1847 file_manager.write_file(target_path, ...) 同 write_file 同 write_file
conversation_review写文件 tools_execution.py:2094 save_review_file()target.write_text(...)绕过 file_manager,直写工作区 WORKSPACE_REVIEW_DIRNAME host 进程内直写
view_image / view_video tools_execution.py:1243/1281只 stat/read 元数据,设置 pending_image_view/pending_video_view,不读内容) 进程内 Path.stat
update_project_memory / manage_personalization tools_execution.py:681/2519 _handle_update_project_memory / _execute_manage_personalization 写运行态目录文件(.astrion/memory、personalization.jsonS10

注:save_webpageconversation_review 是文件工具里绕开 FileManager 抽象的两个直写点(前者最终仍走 file_manager.write_file后者完全绕过

C. 终端会话类(持久终端)

子类 ① 入口handler ② 中间层 ③ 最终执行后端
terminal_session open tools_execution.py:1315-1337terminal_manager.open_terminalterminal_manager.py:212 PersistentTerminal.startpersistent_terminal/start.py:72 docker_start_docker_terminal:241_start_existing_container_terminal:258/ _start_new_container_terminal:315host_start_host_terminal:141build_host_sandbox_shell_plan)或 direct _start_plain_host_terminal:227
terminal_input tools_execution.py:1349-1357terminal_manager.send_to_terminalterminal_manager.py:521 PersistentTerminal.send_commandpersistent_terminal/command.py:70 向已启动的子进程 stdin 写 + 读 stdoutio.py:91 _read_output
terminal_snapshot tools_execution.py:1361-1366terminal_manager.get_terminal_snapshotterminal_manager.py:697 PersistentTerminal.get_snapshotlifecycle.py:58 纯缓冲读,不触执行
terminal_session close/reset/list tools_execution.py:1328-1345 terminal_manager.close_terminal/reset_terminal/list_terminals:325/375/500 进程终止/信号

终端会话的 sandbox 后端选择:terminal_manager.py:100-107 / 132-178sandbox_mode 默认 hostcontainer_session.mode=="docker" 时转 docker_build_sandbox_options 注入 docker_readonly_execcontainer_name/mount_path)。终端的 broadcast 回调 = 上级 terminal 注入的 message_callbackweb_terminal.py:135/147Web 侧经 attach_user_broadcast 指向 emit_event 安全包装)。

D. 子智能体类(执行派生,工具执行复用主进程链路)

子类 ① 入口handler ② 中间层 ③ 最终执行后端
create_sub_agent阻塞/后台) tools_execution.py:2147 分支,调用点 :2213(多智能体)/ :2248(传统) sub_agent/manager.py:225 create_sub_agentexecute_tool_for_sub_agentmanager.py:1003)→ self.terminal.handle_tool_call(...):1019)→ 回到主进程执行链 A/B/C 复用主 terminal 环境container_session/host_execution_mode 同源,manager.py:167 set_container_session / :171 set_host_execution_mode
terminate_sub_agent / get_sub_agent_status / send_message_to_sub_agent / stop_sub_agent / answer_sub_agent_question / wait_sub_agent_outputsleep tools_execution.py:2299/2325/2356/2407/2427/1369 sub_agent/manager.py:629/809/1307/570/… 控制面(状态/消息不直接触执行环境wait 阻塞等输出
read_mediafile子智能体专用 sub_agent/manager.py:1015handle_read_mediafile 进程内读媒体文件(返回 base64 给模型)

E. 其他(触达但与执行环境弱相关)

子类 ① 入口handler 最终后端
sleep含 wait_sub_agent_* tools_execution.py:1369 asyncio 等待,不触执行环境
ocr_image / vlm_analyze tools_execution.py:1237 self.ocr_client.vlm_analyze(读工作区图片文件 + 模型推理)
web_search / extract_webpage / save_webpage tools_execution.py:1679/1740/1784 外部网络 APITavilysave 落盘见 B
todo / memory / conversation_search / manage_personalization tools_execution.py:2132/1955/2026/2503 内存/运行态文件TODO 存储在 context_manager记忆在 memory_managerpersonalization 覆写文件)
trigger_easter_egg tools_execution.py:2500 前端效果,不触执行

一句话执行面清单:真正触碰"执行环境"(能产生真实副作用)的工具 = 命令执行run_command 前台/后台、custom_tool、install_package文件写write_file/edit_file/create_file/delete_file/rename_file/create_folder/save_webpage/conversation_review/update_project_memory/manage_personalization文件读read_file 族、read_mediafile、ocr终端会话terminal_session/terminal_input子智能体(其内部工具执行复用主链路)。其余为控制面/查询面。


2. 耦合点清单:执行链路中的 Web 概念依赖

2.1 依赖链总览sender / 广播怎么流进执行链路)

执行链路事件出口(工具循环内 sender('tool_start'/'tool_approval_required'/...)
  chat_flow_tool_loop.py:522 _execute_tool_calls_impl  ← sender 参数透传
    └─ 上游 sender 定义点:
       (1) tasks/models.py:901 任务级 sender_run_chat_task 内定义run_chat_task_sync 传入)
            ├─ _append_event(rec, ...)          # TaskRecord 事件流S3非 Web
            └─ socketio.emit(..., room=f"user_{username}")   # ←【仍耦合 · 直接 import server.extensions.socketio】
       (2) chat_flow_task_main.py:1511 raw_sender 包装(补 conversation_id/task_id/client_sid
       (3) REST/回调适配层 sendertasks/api.py / 通知链chat_flow_task_main.py:618 dispatch_completion_create_chat_task 完成通知派发)
  terminal.broadcast / context_manager._web_terminal_callbackshell 输出、token_update、todo_updated
    └─ web_terminal.py:135/147/161  注入 message_callback
         └─ server/context/broadcast.py:16 make_terminal_callback → emit_event(..., room=f"user_{username}")
              └─ server/extensions.py:10 emit_event    # ←【已解耦安全包装socketio 未绑定即静默】

2.2 分档清单

已在第 1/2 步解耦经安全包装Gateway 可独立初始化)

位置 内容 说明
server/extensions.py:10-23 emit_event(event, data, room, ...) socketio.server is None(未绑定 app时静默跳过 → 非 Web 进程零副作用
server/extensions.py:25-29 run_background(fn, ...) 未绑定时降级 daemon 线程 → 任务线程可脱离 socketio 启动
server/context/broadcast.py:16-25 make_terminal_callback(username) 只调 emit_event(安全包装),不直接 import socketio.emit
server/context/broadcast.py:28-38 attach_user_broadcast(terminal, username) 把 terminal.message_callback / terminal_manager.broadcast 指到安全包装
terminal_manager.broadcastPersistentTerminal.broadcast 终端 IO 事件出口 值为 terminal 注入的 message_callbackWeb 侧指向 emit_event 包装CLI 侧为 Noneterminal.py 构造时 broadcast_callback=Nonemain_terminal.py:134
context_manager._web_terminal_callbacktoken_update/todo_updated/编辑摘要) utils/context_manager/token_mixin.py:236todo_annotation_mixin.py:138 值 = terminal.message_callbackweb_terminal.py:161task 运行时被 models.py:930 切到任务 sender
chat_flow_tool_loop.py:478-479 emit_event('status_update', ...) 经 extensions 安全包装
chat_flow_task_main.py:1850/2640/2692 run_background(...) 经 extensions 安全包装
执行链路对 Flask session 的读取 server/chat_flow_task_main.py / chat_flow_tool_loop.py / tasks/models.py / tools_execution.py / terminal_ops / file_manager / persistent_terminal grep session[ = 0 命中T12 已成立session 读取只剩适配层 resources.py:149(带 has_request_context() 兜底)

⚠️ / 仍耦合(执行链路直接依赖 Web 广播对象)

位置 内容 耦合性质
server/tasks/models.py:919-920(任务级 sender from server.extensions import socketio; socketio.emit(event_type, data, room=f"user_{rec.username}") 直接 import socketio 实例并调用 .emit——绕过了 emit_event 包装,未绑定时会抛异常socketio.server None 时 emit 行为未定义/异常)。这是主 Run 全部事件的实时推送口含工具事件Gateway 独立进程下会炸
server/tasks/models.py:1045-1054 socketio.emit('task_stopped', stopped_payload, room=f"user_{rec.username}") 同上(终态推送口)
server/chat_flow_task_main.py:975-989(完成通知链预写回显) from .extensions import socketio; socketio.emit(...) 直接 socketio.emit完成通知派发链路poll_completion_notifications
server/chat_flow_task_main.py:1118-1125(多智能体 idle 派发回显) 同上 socketio.emit(...) 直接 socketio.emit多智能体 idle 消息回显)
server/chat_flow_task_main.py:1488 handle_task_with_senderfrom .extensions import socketio ⚠️ 导入但未见直接使用(仅靠 sender 参数),属残留导入
sender 函数签名贯穿工具循环 chat_flow_tool_loop.py:522 _execute_tool_calls_impl(*, web_terminal, tool_calls, sender, ...),约 30+ 处 sender('...') ⚠️ 工具执行循环把「事件发送」以 sender 回调参数贯穿设计上已与具体传输解耦——sender 是可注入的),但调用方models.py:901绑死了 socketio等于参数化了接口、没参数化实现
web_terminalWebTerminal 特有属性) 工具循环参数 web_terminal_execute_tool_calls_impl:522handler 内大量 getattr(self, ...) 读取 WebTerminal 特有属性:task_idchat_flow_task_main.py:1531 getattr(web_terminal, "task_id", None))、multi_agent_modesub_agent_managermcp_client_managerbackground_command_managercustom_tool_executorocr_clienteaster_egg_managerhost_network_permissioncurrent_permission_modedata_dir/web/users/ 路径判断tools_execution.py:2163、:2466、:2482 _is_web = '/web/users/' in _data_dir Runtime/工具实现把「身份是 Web 用户」编进了执行逻辑_is_web 判定影响 custom-role 目录解析);getattr(web_terminal, "task_id", None) 恒 NoneWebTerminal 全仓无 task_id 赋值点,记忆 N2
web_mode / api_client.web_mode web_terminal.py:127-128 ⚠️ 输出静默是 Web 概念,但包在 WEB_API_SILENT 环境变量开关内,非硬耦合

2.3 结论

  • 第 1/2 步已把「传输出口」包装成了 emit_event/run_background + sender 回调注入,工具循环内部已不出现 socketio.* 直接调用(唯一例外 chat_flow_tool_loop.py:478 用的也是安全包装 emit_event
  • 残余硬耦合集中在 4 处直接 socketio.emittasks/models.py:920 / :1054chat_flow_task_main.py:989 / :1125。它们是「Gateway 独立启动验收」的最后障碍T12 只查了 test_request_contextsession[,未查 socketio.emit 裸调用)。
  • 执行环境的 Web 语义残留tools_execution.py_is_web'/web/users/' in data_dir)影响 role 目录解析——这是身份/路径耦合不在纯执行环境范围内但同属「Runtime 依赖 Web 概念」,第 4 步应一并划界(建议归入 Execution Contract 的 workspace 解析,或至少记录)。

3. 抽象接口建议(只归纳现状,不发明新能力)

3.1 Execution Plane 接口面应覆盖的操作集(全部来自现状调用点)

# 操作 现状调用点handler → 后端) 参数(现状签名) 关键语义(不许丢)
E1 run_command(cmd, workdir, timeout, write_access, network) tools_execution.py:1936 → terminal_ops/run.py:434 command, working_dir, timeout, sandbox_write_access, network_permission 返回 {success,status,output,return_code,truncated,elapsed_ms}status ∈ {completed, timeout, error, cancelled};字符上限 MAX_RUN_COMMAND_CHARS
E2 run_command_background(cmd, timeout, network, write_access) tools_execution.py:1916 → background_command_manager.py:39 terminal_ops, command, timeout, conversation_id, wait_seconds, network_permission, sandbox_write_access 返回 command_id/status=running_background;轮询/等待需按 command_id 寻址
E3 write_file(path, content, mode) tools_execution.py:1638 → crud_mixin.py:242 path, content, mode 返回 {path, original_file, new_file}(编辑摘要依赖 original/new 全文)
E4 edit_file(path, replacements) tools_execution.py:1665 → replace_mixin.py:125 path, replacements 同上
E5 read_file(path, type, start_line, end_line, max_chars, ...) tools_read.py:419 → read_mixin.py path/type(3 模式)/range/max_chars/size_limit 返回 {path, content, truncated, char_count}不含原文全文type=search/extract 各有参数)
E6 create_file / delete_file / rename_file / create_folder / delete_folder tools_execution.py:1569-1677 → crud_mixin.py 同名 路径相对工作区
E7 open_terminal / send_input / snapshot / close / reset / list tools_execution.py:1315-1368 → terminal_manager.py 见 C 表 终端只读身份(docker_readonly_exec / readonly shell planbroadcastIO 事件流出口)
E8 path_validate(path)(执行前授权检查) tools_execution.py:446/489/960 → path_mixin.py:83chat_flow_tool_loop.py:61/396 path(valid, error, full_path, rel_path?) 权限模式/沙箱范围/禁读清单host_execution_mode 参与判定)
E9 执行环境快照(供子智能体提示词) get_execution_mode_state()main_terminal.py:448execution_env_text.py 注入到子智能体 system prompt 的 execution_mode 说明
E10 命令校验/超时钳制 terminal_ops/command.py:53 _validate_command / :76 _clamp_timeout FORBIDDEN_COMMANDS / TERMINAL_COMMAND_TIMEOUT 语义

(未列入的「查询/控制面」——memory、todo、approval、workflow、easter_egg、sender 事件——不属于 Execution Plane它们应留在 Runtime 层。)

3.2 现有后端与实现文件对应

后端 最终执行实现文件 入口函数 适用工具
Docker 容器(会话句柄 mode=docker modules/terminal_ops/run.py:177-207(前台)、modules/background_command_manager.py:232-260(后台)、modules/persistent_terminal/start.py:241-413(终端)、modules/container_file_proxy.py:448-501(文件) _run_command_subprocess / _run_command_thread / _start_docker_terminal / ContainerFileProxy.run 命令、终端、文件
Docker 只读加固 modules/docker_readonly_exec.py:55-190uid/gid、exec args、landlock wrapmodules/landlock_launcher.py:74-175probe_abi/install_readonly_domain/selftest docker_readonly_exec_args / docker_readonly_wrap_inner / ensure_landlock_ready 受限档命令/终端
宿主机 OS 沙箱 modules/host_sandbox_runner.py:174-640host_sandbox_enabled + 三平台 plan 构建) build_host_sandbox_plan:197build_host_sandbox_readonly_plan:213build_host_sandbox_shell_plan:229 命令(前台/后台)、持久终端 shell
宿主机 direct modules/terminal_ops/run.py:255-262persistent_terminal/start.py:227-240file_manager host 直读写 create_subprocess_shell / _start_plain_host_terminal 命令、终端unrestricted+direct
进程内文件操作host modules/file_manager/*read/crud/replace/patch/list mixinmodules/host_sandbox_policy.py:125-170(禁读/可写清单) _validate_path + _ensure_host_access + 各 mixin 方法 全部文件工具

结构性提示(仅归纳):同一个「执行命令」语义在 4 个文件里各有一份后端选择代码terminal_ops/run.py、background_command_manager.py 的 _run_command_thread、persistent_terminal/start.py、container_file_proxy.pyhost/docker 分支判定条件互相复制。Execution Plane 接口一旦建立,这 4 处是天然的收敛点(本报告不要求本次重构,仅指出现状)。


4. 替身执行器接入点评估(内存替身,供 Runtime 测试)

前提约束(现状决定接入点形态)

  1. 工具 handler 直接持有 self.terminal_ops / self.file_manager / self.terminal_managermain_terminal.py:123/125/131 构造);
  2. handle_tool_call单一工具入口tools_execution.py:1057所有执行分支都在里面
  3. 后台命令不走 terminal_ops.run_command,而是 BackgroundCommandManager._run_command_threadbackground_command_manager.py:185自己的实现——只替换 terminal_ops 会漏掉后台 run_command
  4. CustomToolExecutor 只依赖 terminal_ops.run_command构造注入main_terminal.py:167替换 terminal_ops 即覆盖;
  5. 子智能体工具执行复用主 terminal 的 handle_tool_callsub_agent/manager.py:1019——替换主执行链即覆盖子智能体
  6. 文件工具 = 进程内操作 + container_session 代理file_manager 与 terminal_ops 是两个独立对象,替身需分别处理。

候选方案

方案 做法 侵入面估计 优点 缺点
A. handler 分支注入「执行后端」接口(最小侵入,推荐) MainTerminalmain_terminal.py 构造区 ~:121加一个可选属性 execution_backend(默认 Nonehandle_tool_call 的 run_command 分支tools_execution.py:1879-1943含后台分支 :1909与文件写分支:1625 write_file / :1652 edit_file可选 :1569 create_file/:1589 delete_file/:1604 rename_file/:1677 create_folder插入 if self.execution_backend: ... else: 原逻辑 改 1 个文件tools_execution.py+ 1 个构造点main_terminal.py~6-8 处小分支插入需要为替身定义最小接口E1/E2/E3/E4约 4 个方法) 语义最清晰Runtime 侧显式「执行环境依赖」,替身可同时挡命令+后台+文件写;不改 terminal_ops/background_command_manager 源码;后台命令也覆盖(在分支层拦截) 需在 handler 内维护分支双轨(真实/替身有长期漂移风险文件读E5如果也要替身需再加分支
B. 构造期替换三件套(对象级替换) 给 MainTerminal 构造加参数(或测试子类覆写),用 FakeTerminalOps / FakeFileManager / FakeTerminalManager 替换 self.terminal_ops/file_manager/terminal_managermain_terminal.py:123/125/131 三行);后台命令因在 :1916 透传 terminal_ops=self.terminal_opsFakeTerminalOps 需实现 _resolve_active_container_session/_validate_command/_resolve_work_path 等被 BackgroundCommandManager 读取的属性 改 1 个文件main_terminal.py 构造区 3 行 + 可选工厂)+ 需实现 3 个替身类接口面较大TerminalOperator 约 15+ 公开方法、FileManager 约 10+ 方法、TerminalManager 约 8 个);后台命令无法被完全拦截_run_command_thread 只取 terminal_ops 快照后自己执行,需另 patch 对 handler 零侵入;贴近真实装配(资源解析同路径) 替身类面太大、后台命令漏网点;容易「替了个寂寞」
C. 实例方法级 monkeypatch(测试夹具式,零源码改动) pytest fixture / 测试装配中直接替换实例方法:TerminalOperator.run_command_run_command_subprocessBackgroundCommandManager._run_command_threadFileManager.write_file/replace_many_in_file/create_file/delete_file/rename_file/create_folder 0 个源码文件改动,但需列出 ~8-10 个方法覆盖清单(易漏);依赖内部方法名稳定性 对产品代码零侵入,快速验证 脆弱(内部改名即失效);只覆盖了「当前测到的路径」,后台命令线程路径尤其容易漏

推荐

第 4 步首版建议方案 Ahandler 分支注入 execution_backend),理由:

  • 与「Runtime ↔ Execution Plane 分层」目标同构——替身执行器就是 Execution Plane 的第一个非 Web 实现;
  • 覆盖齐全:前台/后台命令、custom_tool其唯一出口就是 terminal_ops.run_command被分支层拦截、子智能体复用 handle_tool_call、文件写
  • 侵入面可数1 个文件tools_execution.py+ 1 个构造点main_terminal.py约 6-8 个小分支;
  • 后续接真实后端Docker/host/远端)时,替身接口可直接演进为正式 Execution Plane 接口E1-E4 起步)。

若只想先验证「Runtime 循环不触发真实副作用」而不关心文件工具,可先只拦 run_command 前台+后台 2 个分支tools_execution.py:1909-1943侵入面缩到最小~2 处 + 1 属性)。


附:关键文件:行号索引

  • 工具入口:core/main_terminal_parts/tools_execution.py:1057handle_tool_call、:391evaluate_tool_permission
  • 执行器构造:core/main_terminal.py:121-142
  • Web 广播注入:core/web_terminal.py:100-152
  • 命令后端:modules/terminal_ops/run.py:160/434;后台:modules/background_command_manager.py:39/185;自定义:modules/custom_tool_executor.py:40
  • 终端后端:modules/terminal_manager.py:212/521modules/persistent_terminal/start.py:72/141/241modules/persistent_terminal/base.py:66
  • 文件后端:modules/file_manager/base.py:50/77-100modules/container_file_proxy.py:448-501modules/host_sandbox_policy.py:125-170
  • OS 沙箱:modules/host_sandbox_runner.py:174/197/213/229Docker 只读:modules/docker_readonly_exec.py:55/66/172Landlockmodules/landlock_launcher.py:74/91/138
  • 事件/senderserver/tasks/models.py:901-920/1045-1054server/chat_flow_task_main.py:989/1125/1511server/chat_flow_tool_loop.py:505/522/828/969/986/1037server/extensions.py:10/25server/context/broadcast.py:16/28
  • 身份/执行环境装配:server/context/resources.py:221/296/414/450host/docker 容器句柄来源)

报告完毕。本报告为纯只读调研产物;所有结论均基于静态阅读与 grep 定位,未经运行时验证。