DeepCode P4 Code Workbench 架构解析:基于 P2/P3 会话栈的桌面代码审查能力层
【免费下载链接】DeepCode"DeepCode: Open Agentic Coding (Agent Harness & Loop Engineering & Multi-Agent Orchestration)"项目地址: https://gitcode.com/GitHub_Trending/deepc/DeepCode
导读
本文以 DeepCode 仓库的 P4_CODE_WORKBENCH_ARCHITECTURE.md 为骨架,系统讲解 P4 Code Workbench 如何在 P2/P3 已有的 Thread、Turn、Item、Approval 与本地 stdio App Server 之上,新增代码审查(Code Review)能力:文件浏览与编辑、Git 状态/差异/丢弃、隔离 worktree、Thread 专属 PTY 终端与测试发现/运行。读完本文,你将掌握 P4 的服务边界划分、RPC 协议扩展、安全不变量及其源码级实现依据,并了解各服务的核心行为约束、消息大小限制与验证方式,可直接对照 core/application 与 app_server 继续深入。
一、P4 在整体架构中的定位
DeepCode 的整体架构按阶段演进:P1 搭建本地 stdio App Server 与基础协议,P2/P3 建立 Thread、Turn、Item、Approval 等会话与执行模型(参见 P1_APP_SERVER_ARCHITECTURE.md、P2_AGENT_EXECUTION_ARCHITECTURE.md、P3_DESKTOP_RUNTIME_ARCHITECTURE.md)。P4 的工作是在这条会话栈之上叠加代码审查能力,让桌面端 Inspector 可以直接查看文件树、浏览 Git 变更、运行测试并在 Thread 终端中执行命令。
P4 架构有三个贯穿始终的原则:
- 业务规则集中在
core/application:所有路径校验、权限判断、并发保护、revision 检查都放在 Python 应用层,React 前端不直接访问文件系统、Git 或 shell。 - React 保持"哑客户端":前端只消费 RPC 返回的结构化数据,不复制业务规则。
- Rust bridge 不复制规则:Rust 侧只负责消息传输与 sidecar 生命周期,不实现文件、Git、终端等业务判断。
也就是说,P4 的所有新增能力都通过本地 App Server 暴露为协议方法,前端与 Rust 只是"通道"。
二、服务边界:五个核心服务与各自的职责
P4 把代码审查能力拆分为五个服务,全部位于 core/application,职责边界非常清晰:
2.1 WorkspaceService:一切路径操作的共同边界
WorkspaceService是所有文件系统访问的守门人,其resolve方法(见 workspace_service.py)完成四层校验:
- Project/Thread 存在性校验:通过
ThreadRepository与ProjectRepository加载 Thread 与 Project,缺失即抛ThreadNotFoundError/ProjectNotFoundError; - trust 校验:需要 trusted 的操作(写文件、discard、worktree 变更、终端、测试运行)通过
require_trusted=True强制要求project.trust_state is TrustState.TRUSTED,否则抛ProjectNotTrustedError; - canonical root 校验:Thread 的
workspace_path经Path.expanduser().resolve(strict=True)规范化,并确认是目录;若 Thread 不拥有 worktree,还必须root.is_relative_to(project_root),防止 workspace 越出项目边界(WorkspaceOutOfScopeError); - owned worktree 校验:若 Thread 拥有 worktree,则要求
root == worktree且worktree/.git存在,即路径必须是真实注册的 Git worktree,而不是任意目录。
path方法(workspace_service.py)负责把 UI 提供的相对路径映射为物理路径:
- 拒绝绝对路径、包含
..的路径、包含 NUL 字节的路径; candidate.resolve(strict=True)后再做一次is_relative_to(context.root)检查,无论是否存在 symlink 指向 workspace 之外,最终解析结果都必须留在 workspace 根之内;- 对尚不存在的目标(如新建文件),则先解析父目录再拼接文件名,同样做边界检查。
这是安全不变量 #1 和 #2(UI 路径永远是 workspace-relative、canonicalization 只在后端执行)的落地实现。
2.2 FileService:有界目录树 + UTF-8 小文件 + SHA-256 乐观编辑
FileService(file_service.py)只做三类事:
- 有界目录树:
list方法限制depth在 1~8、limit在 1~750(MAX_TREE_ENTRIES = 750),排序优先目录后文件、忽略.git目录,扫描超限即标记truncated; - UTF-8 小文件读取:
read方法默认只读 128 KiB(DEFAULT_READ_LIMIT = MAX_READ_LIMIT = 128 * 1024),发现 NUL 字节或非法 UTF-8 即抛BinaryFileError,确保只返回文本文件;截断的文件不计算 SHA-256(sha256=None if truncated),即"截断内容不返回可写 revision",防止前端基于不完整内容发起覆盖写; - 原子小范围编辑:
write方法要求内容不超过MAX_EDIT_BYTES = 128 * 1024,通过同目录NamedTemporaryFile写入、fsync、os.replace原子替换、再fsync目录实现崩溃安全落盘。
write的乐观并发控制值得单独强调:
- 对已存在文件,必须提供
expected_sha256,若与当前内容哈希不一致则抛FileChangedError("file changed since it was read"),实现"旧视图不能覆盖新内容"(安全不变量 #3); - 对不存在的文件,若带了
expected_sha256则视为"目标已消失"; - 写入前后还会二次比对哈希,防止保存期间文件被并发修改;
- 活动 Turn 期间禁止编辑:
TurnRepository.active_for_thread(thread_id)非空即抛ConflictError,避免与 Agent 执行中的文件操作冲突。
2.3 GitService:只返回结构化 status/diff,discard 必须提交 revision
GitService(git_service.py)把所有 Git 交互封装成三类操作:
status:调用
git status --porcelain=v1 -z --branch --untracked-files=all,解析出GitStatusEntry(path、original_path、index_status、worktree_status、kind),并支持_parse_branch_header处理 detached HEAD、"No commits yet" 等边界;diff:支持
scope为all/staged/working三种,对 untracked 文件直接读取内容构造"新文件"hunk;对 tracked 文件调用git diff --no-ext-diff --no-color --find-renames --unified=3 [--cached|HEAD]并解析成结构化的FileDiff/DiffHunk/DiffLine,每个 FileDiff 通过_attach_revision附带一个基于 path、status、binary 标记与 patch 内容计算的 SHA-256 revision;discard:逐文件执行,前置条件非常严格:
- 必须
require_trusted=True,且 Thread 无活动 Turn; - 重新调用
diff获取当前变更,要求恰好命中 1 个文件; - 提交的
expected_revision必须与后端重新计算的 revision 一致,否则抛FileChangedError——这是"逐文件 discard 必须提交该 revision,并在后端重新计算后才允许执行"的实现; - untracked 文件直接删除;unborn 仓库(无 HEAD)用
git rm --cached加删除;正常情况用git restore --source=HEAD --staged --worktree恢复。
- 必须
revision 机制保证了"旧视图不能覆盖新内容"这一安全不变量在 Git 侧同样成立:UI 看到某个 diff 后,若文件在审查期间被改动,discard 会因 revision 不匹配而被拒绝。
Git 输出还有硬上限:MAX_GIT_OUTPUT = 8 MiB(含逐文件 diff 累计)、MAX_DIFF_FILES = 500、MAX_DIFF_LINES = 4000、MAX_DIFF_TEXT_BYTES = 256 KiB,任何一项超限都会抛出InvalidArgumentError,且通过 drain 线程在超限时直接 kill 子进程,避免 sidecar 因超长输出卡死。
2.4 WorktreeService:确定性 sibling 目录 + 专属分支 + ownership manifest
WorktreeService(worktree_service.py)为 Thread fork 出来的并行 Turn 提供隔离 workspace:
- 确定性路径:worktree 一律创建在项目父目录下的
.<projectName>.deepcode-worktrees/<threadId>,分支固定为deepcode/<threadId>; - 要求项目即 Git root:
git rev-parse --show-toplevel必须等于 project canonical path,否则拒绝创建(保证 sibling 目录可控); - ownership manifest:每次创建在
.<projectName>.deepcode-worktrees/.deepcode-manifests/<threadId>.json写入包含version/threadId/projectId/repositoryRoot/worktreePath/branch的清单;清理时必须校验数据库归属(Thread 记录)、managed path(必须是期望路径)、branch(git worktree list --porcelain注册分支与git branch --show-current实际分支都必须等于deepcode/<threadId>)和 manifest 四者一致,绝不根据目录名猜测所有权(安全不变量 #5); - 脏 worktree 必须显式 force:
remove的disposition只允许keep或clean;clean时若git status --porcelain非空且未传force=True,则抛ConflictError;活动 Turn(_require_idle)或 Thread 终端仍存活(_terminal_activity)都会阻止清理; - reclaim 逻辑:若 worktree 目录与
.git存在但 Thread 记录未注册,只要 manifest 匹配、分支匹配,就回收使用;否则报 ConflictError。
2.5 TerminalService:Thread 专属 PTY,有界增量输出,不写 durable event log
TerminalService(terminal_service.py)管理 Unix PTY 终端:
- Thread 专属:每个 terminal 绑定
thread_id,_owned校验 terminal 必须属于发起 Thread;create时以 workspace 为cwd、start_new_session=True建立独立进程组,shell 取$SHELL(无则/bin/sh),并注入DEEPCODE_THREAD_ID环境变量; - 大小与输入输出上限:窗口行列校验在 20~500 列、5~200 行;
write单次输入上限 64 KiB;输出读取循环每次最多 16 KiB 并增量发布terminal.output通知; - 不写 durable event log:输出通过订阅-发布(
subscribe/unsubscribe)直接推送给 App Server 写入 stdout 通知,不落入持久化事件仓库; - 退出清理:进程退出后发布
terminal.exit(带 exitCode),close_all在应用退出时终止所有会话;_terminate先killpg(SIGTERM),0.75 秒后未退出再killpg(SIGKILL);清理 worktree 前若 terminal 仍活跃会被拒绝(set_terminal_activity_check注入的回调); - Windows 平台:PTY 路径需要 Windows ConPTY 适配器,当前实现直接抛
NotSupportedApplicationError,并在文档中说明适用前提是 Unix 环境。
2.6 TestService:只暴露探测到的白名单命令,结果落为 durable TestResult Item
TestService(test_service.py)与 core/verification.py 配合:
- 探测而非猜测:
discover_verification_commands只从仓库真实标记中探测——有pytest.ini/pyproject.toml/setup.cfg/tests目录则暴露pytest;根目录存在test_*.py/*_test.py且导入 unittest 则暴露unittest;package.json有非空testscript(排除 "no test specified")则暴露npm test;存在Cargo.toml则暴露cargo test; - 运行条件:
test/run要求 trusted Project、指定turn_id必须属于该 Thread 且当前无活动 Turn; - 有界输出:stdout/stderr 各自只保留最后 64 KiB(
MAX_VERIFICATION_OUTPUT),output_truncated标记截断;超时范围限制在 1~1800 秒,超时后killpg终止; - 持久化:运行结果写入
ItemKind.TEST_RESULT,成功为ItemStatus.COMPLETED、失败为FAILED,payload 携带 command、exitCode、timedOut、durationMs、stdout、stderr,并通过item.created事件广播——测试结果成为会话历史的一部分。
三、协议与 UI:RPC 扩展与方法清单
3.1 Canonical schema 新增的 P4 方法
P4 在协议层新增四组方法(完整方法常量见 app_server/protocol/methods.py):
file/list git/status terminal/create file/read git/diff terminal/write file/write git/discard terminal/resize git/worktree/create test/discover terminal/close git/worktree/remove test/run对应的协议 schema 定义位于 protocol/app-server.schema.json。所有方法都由 app_server/server.py 的Dispatcher分发给五个服务。
3.2 消息与数据硬上限
文档明确的大小约束在源码中可逐条验证:
| 约束 | 数值 | 源码位置 |
|---|---|---|
| App Server 单条消息上限 | 1 MiB | codec.pyDEFAULT_MAX_MESSAGE_BYTES = 1024 * 1024 |
| 文件读取/写入 | 128 KiB | file_service.py |
| 测试 stdout/stderr 各保留 | 最后 64 KiB | verification.pyMAX_VERIFICATION_OUTPUT |
| 结构化 Diff 文件数 | 500 | git_service.pyMAX_DIFF_FILES |
| 结构化 Diff 行数 | 4000 | git_service.pyMAX_DIFF_LINES |
| 结构化 Diff 文本字节 | 256 KiB | git_service.pyMAX_DIFF_TEXT_BYTES |
超限时的降级行为:若请求或响应超过 1 MiB,App Server 返回稳定的RESPONSE_TOO_LARGE(错误码-32004,stable_codeRESPONSE_TOO_LARGE)并继续服务,不会让 sidecar 因超长行退出——对应 server.py 的_write_response逻辑;对超长请求还会_discard_remainder丢弃本行剩余内容再继续读下一行。事件回放(event/replay)则通过二分查找切出最大可装入单条消息的 cursor 页(_fit_replay_response),保证大历史也能分页送达。
3.3 UI 层消费与按需加载
Inspector 的Changes、Files、Tests、Terminal四个标签全部消费真实 RPC 数据,而非 mock:
- Changes →
git/status、git/diff、git/discard; - Files →
file/list、file/read、file/write; - Tests →
test/discover、test/run; - Terminal →
terminal/create、terminal/write、terminal/resize、terminal/close。
Monaco 与 xterm 均按需加载:常规首屏 bundle 不包含编辑器和终端运行时代码,降低启动体积。Thread fork(thread/fork)随后为并行 Turn 创建 owned worktree,使各 Turn 在隔离 workspace 中执行。
四、安全不变量:七条规则的源码对应
文档列出七条安全不变量,逐条可找到实现证据:
- UI 提供的路径永远是 workspace-relative,canonicalization 只在后端执行→ workspace_service.py
path()拒绝绝对路径与..,所有resolve在后端完成; - symlink 不能把读取、写入或 discard 导向 workspace 之外→ 同上,
resolved.is_relative_to(context.root)二次校验;FileService.list用follow_symlinks=False标记 symlink 类型; - 写文件和 discard 都使用 optimistic revision,旧视图不能覆盖新内容→ file_service.py 的 SHA-256 校验、git_service.py 的 revision 比对;
- discard 仅处理明确选择的文件,并由 UI 二次确认;worktree force clean 另有独立确认→ discard 只接受单个
path且后端重新计算 diff 恰好命中一个文件;force 作为显式参数(worktree_service.py),UI 侧还需独立弹窗; - worktree 清理同时校验数据库归属、managed path、branch 和 manifest,不根据目录名称猜测所有权→ worktree_service.py
_require_manifest四要素比对; - PTY 和 test subprocess 使用显式
cwd,不调用全局os.chdir()→ terminal_service.py 与 verification.py 均以cwd=root启动子进程; - Terminal input、Git output、文件内容、目录项和 test output 均有硬上限→ 64 KiB 输入、8 MiB Git 输出、128 KiB 文件、750 目录项、64 KiB 测试输出的常量定义。
此外,写文件、discard、worktree 变更、test/run、terminal/create 均要求trusted Project,把代码审查能力限制在用户显式信任的仓库内(参见 project_trust.py 的 trust 流程)。
五、验证证据:测试如何覆盖这些边界
P4 的验证证据在仓库中有大量对应测试:
5.1 应用层组合测试
tests/application/test_p4_code_workbench.py 覆盖:
test_file_tree_read_and_optimistic_edit_stay_inside_workspace——文件树与乐观编辑的 workspace 边界;test_file_read_truncates_on_a_utf8_boundary_without_hashing_partial_content——UTF-8 截断边界与"截断内容不返回可写 revision";test_git_status_and_diff_include_tracked_and_untracked_files、test_git_diff_handles_unborn_repositories_renames_and_deletions——unborn 仓库、rename、delete 等 diff 边界;test_worktree_lifecycle_requires_explicit_dirty_cleanup、test_worktree_recreates_a_missing_registered_path_only_with_its_manifest——脏 worktree 显式 force 清理与 manifest reclaim;test_terminal_is_thread_owned_and_emits_output_and_exit——PTY ownership 与输出/退出通知;test_allowlisted_test_run_creates_durable_test_result_item、test_test_discovery_requires_a_real_npm_script_and_bounds_failure_output——白名单测试发现与输出截断。
5.2 App Server 协议层测试
tests/app_server/test_p4_server.py 覆盖:
test_p4_file_git_and_terminal_protocol_round_trip——file/git/terminal 方法完整 RPC 往返;test_oversized_result_returns_a_protocol_error_without_stopping_server——超限响应返回RESPONSE_TOO_LARGE且服务器继续服务;test_event_replay_is_split_into_byte_bounded_cursor_pages——事件回放按字节边界分页。
5.3 前端与 Rust 侧验证
- React TypeScript、ESLint、5 项 Vitest 与 production build 通过;慢旧 Thread 请求不能覆盖当前 Inspector 的竞态保护在桌面端测试中有对应覆盖;
- Rust fmt、单元测试与 clippy
-D warnings通过; - PyInstaller sidecar 与 macOS arm64
DeepCode.apprelease bundle 构建通过,并从 bundle 内 sidecar 实测file/read、git/diff、PTY 与 graceful shutdown。
文档记录 P4 组合共51 项通过,这些边界场景——UTF-8 截断、symlink fence、stale SHA/Diff revision、unborn repository、rename/delete/untracked discard、manifest reclaim、脏 worktree、PTY ownership、test output truncation 与 oversized RPC response recovery——正是 P4 安全不变量能被持续验证的关键。
六、总结与延伸阅读
P4 Code Workbench 的核心贡献可以概括为:在既有会话模型之上,用五个职责单一的服务 + 一组受限 RPC 方法 + 七条可验证的安全不变量,把"代码审查"这一桌面功能完整装进了"业务规则只在 core/application、前端与 Rust 不复制规则"的架构约束中。对使用者和二次开发者而言,理解WorkspaceService的路径围栏、FileService/GitService的双重乐观 revision、WorktreeService的四要素归属校验,是安全使用该能力的前提。
继续深入可参考:
- 架构总览:README.md、README_ZH.md
- 分层架构:P2_AGENT_EXECUTION_ARCHITECTURE.md、P3_DESKTOP_RUNTIME_ARCHITECTURE.md、P4_CODE_WORKBENCH_ARCHITECTURE.md
- 协议 schema:protocol/app-server.schema.json
- 核心实现:core/application/workspace_service.py、file_service.py、git_service.py、worktree_service.py、terminal_service.py、test_service.py
- 协议层:app_server/server.py、app_server/protocol/methods.py
- 测试证据:tests/application/test_p4_code_workbench.py、tests/app_server/test_p4_server.py
【免费下载链接】DeepCode"DeepCode: Open Agentic Coding (Agent Harness & Loop Engineering & Multi-Agent Orchestration)"项目地址: https://gitcode.com/GitHub_Trending/deepc/DeepCode
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考