Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .git-blame-ignore-revs
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,11 @@
# P1-01 include 规范化(2026-09-27)
5524a35241ba67ba061aa1a279fd943213601a2f
7a90db6d94bf70e4e200cb39c0122bdb1fc7fa66

# P2-02 / P2-03 / P2-04 已合入本分支的纯搬迁与 include 改写
4009835e29da6c901a0c4ea1ef13a6e99479bbf5
bd8ac3ebd916eafbce1040cea39ff9d966bb3493
8c8538b0e83a2f57d00597d7c5b853777fd55c63
bb94c967934cf3c355a143435bc2b292a917275f
05ab77d55f9b0c073708b17a0e3c34580b92ac78
295041e71071b2fa66bbc3c8517a1697c9d6c00b
10 changes: 5 additions & 5 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ Rewind support uses per-user-turn checkpoints. `SessionManager::track_file_write
- **父目录 mtime 不能替代子目录 mtime。** 在已有 hash 目录里新建 `workspace.json` 不会改 `projects_dir` 的 mtime,只会改那个 hash 目录的。所以缓存键必须是每个 hash 目录自己的时间戳。
- **写路径主动失效。** `register_new` / `set_name` / `hide` 都 `dir_probes_.erase(hash)`。写盘走 atomic_file 的 tmp+rename、rename 本就会推进目录 mtime,这层只是不把可见性正确性押在文件系统的时间戳行为上。

回归测试:`tests/desktop/workspace_registry_test.cpp` 的 `WorkspaceRegistryProbeCache.*`(其中 `MarkerAddedLaterIsStillPickedUp` 就是负缓存不能挡住后补 marker 的哨兵)+ `web/src/lib/sidebarAuxiliaryFetch.test.js`。
回归测试:`tests/workspace/workspace_registry_test.cpp` 的 `WorkspaceRegistryProbeCache.*`(其中 `MarkerAddedLaterIsStillPickedUp` 就是负缓存不能挡住后补 marker 的哨兵)+ `web/src/lib/sidebarAuxiliaryFetch.test.js`。

Daemon session multiplexing uses `SessionRegistry`. Each session entry owns its own `SessionManager`, `PermissionManager`, `AgentLoop`, async permission prompter, and question prompter. `EventDispatcher` gives each emitted event a monotonic sequence number and keeps a bounded replay ring.

Expand Down Expand Up @@ -307,7 +307,7 @@ The desktop shell runs a webview against workspace-local daemon processes. It do

Key modules:

- `workspace_registry`: persisted workspace list and names.
- `workspace_registry` (lives in `src/workspace/workspace_registry.{hpp,cpp}` since refactor20260927 P2-04; shared with daemon, TUI and headless): persisted workspace list and names.
- `daemon_pool`: per-workspace daemon process management.
- `web_host`: native webview wrapper and bridge binding.
- `tray_icon_win` and `notifications_win`: Windows tray and notification integration (`notifications*` and `custom_toast*` now live in `src/platform/native_ui/`, refactor20260927 P2-03).
Expand Down Expand Up @@ -554,15 +554,15 @@ SidePanel 折叠 UI:`ChatView` 把 `SidePanel` 包到 `<div class="ace-side-pane
| `GET /api/history?cwd=&max=` / `POST /api/history` | per-cwd 输入历史,与 TUI 共享同一份 `<cwd_hash>/input_history.jsonl`,经 `InputHistoryStore::append` atomic rename |
| `PUT /api/skills/:name` body `{enabled}` / `GET /api/skills/:name/body` | 启停切换 + 查看 SKILL.md;PUT 写 `cfg.skills.disabled` 数组并 `save_config` + `SkillRegistry::reload` |
| `GET /api/files?cwd=&path=&show_hidden=` → `[{name,path,kind,size?,modified_ms?}]` | SidePanel 文件 tab 的 lazy 文件树。`cwd` 必须 ∈ `{deps.cwd}` 白名单;`path` 走 `weakly_canonical(cwd/path)` + prefix 检查防越权。硬编码 noise 黑名单(.git/node_modules/dist/build/__pycache__/.venv/venv/target/.next/.cache)始终过滤。隐藏文件(dot 开头)默认过滤,`show_hidden=1` 透出 |
| `GET /api/files/content?cwd=&path=` → `text/plain; charset=utf-8` body | SidePanel 预览 tab 读文件原文。> 5MB → 415 `{error:"file too large",size:N}`;前 512 字节出现 `\0` → 415 `{error:"binary"}`;不存在 → 404。实现:`src/web/handlers/files_handler.{hpp,cpp}`(纯函数,17 个 unit test 覆盖路径越权 / 噪音过滤 / 排序 / 二进制嗅探) |
| `GET /api/files/content?cwd=&path=` → `text/plain; charset=utf-8` body | SidePanel 预览 tab 读文件原文。> 5MB → 415 `{error:"file too large",size:N}`;前 512 字节出现 `\0` → 415 `{error:"binary"}`;不存在 → 404。实现:`src/workspace/files_handler.{hpp,cpp}`(D21 从 web 移出,纯函数,17 个 unit test 覆盖路径越权 / 噪音过滤 / 排序 / 二进制嗅探) |
| `GET /api/commands?workspace=<hash>` → `{builtins:[{name,description}][, skills:[{name,description}]]}` | InputBar 斜杠下拉的命令清单。builtins **硬编码白名单 = init + compact**(描述与 TUI `register_builtin_commands` / `register_init_command` 对齐)。**`workspace` 参数(由 `expand-webui-skill-commands` 引入)**:缺省 → 不返回 `skills` 字段(向后兼容旧客户端);提供 → handler 用 `acecode::initialize_skill_registry(tmp, *cfg, workspace_cwd)` 临时构造一个 SkillRegistry 扫该 workspace 的项目链(`.agent/skills`、`.acecode/skills` + 全局 + external_dirs),与 daemon 全局 SkillRegistry 合并(workspace local 优先,first-wins by name),按字典序输出 skills 字段。`/init` `/compact` 在 web 端选中后只插入输入框 + chip 高亮,daemon 端**不**做特殊执行(原 add-webui-slash-commands 决策);`/<skill-name> args` 由下面新加的 expander 真展开。实现:`src/web/handlers/commands_handler.{hpp,cpp}`(纯函数 `build_commands_payload` + gtest case)|
| `POST /api/sessions/:id/messages`(行为扩展) | `expand-webui-skill-commands` 引入:在 `send_input` 之前调 `try_expand_skill_command(text, registry)`(`src/web/handlers/skill_command_expander.{hpp,cpp}`)。命中已知 skill 名(按 session 的 workspace cwd 临时 scan)→ text 被替换为 `build_skill_invocation_hint(meta, args)` 的**轻量提示**:`[SYSTEM: User invoked /<name> skill] + Description + Use skill_view(name=...) to load full SKILL.md + User's request: <args>`。**不**注入 SKILL.md body / supporting_files,LLM 第一次看到提示后主动 invoke `skill_view` tool 把 SKILL.md 拉一次进 context,后续重复同名 `/skill` 调用不再注入(避免 context 膨胀)。TUI `src/skills/skill_commands.cpp::cmd.execute` 也走同一个 `build_skill_invocation_hint`,跨端行为统一。Builtin (`/init`/`/compact`) 不在 SkillRegistry 中 → 透传走普通 user message;未知命令 (`/foobar`) 同样透传。**不**新增执行端点,**不**改 AgentLoop API |
| `POST /api/open-in-explorer` body `{path}` → `{ok:true}` / 400 `{ok:false,error}` / 501 | webapp 兼容模式(Edge --app,无 webview bridge)右键菜单「在资源管理器中打开」的 REST 通路。门控 = `WebServerDeps.open_in_explorer` 回调是否注入(worker.cpp 仅在 `native_folder_picker_enabled` 时填,复用 `desktop::open_path_in_file_manager`:接受任意现存本地绝对普通文件或目录,不做 workspace/managed-root 边界判断)。Windows 文件用 Explorer `/select` 选中,目录保持直接打开。前端 `DesktopContextMenu::openTargetInExplorer` bridge 优先、REST 兜底 |
| `GET /api/fs/roots` → `{host, os, home, roots[{path,label,drive_type,total_bytes?,free_bytes?}], quick[{kind,path}], workspaces[{hash,name,path}]}` / `GET /api/fs/list?path=<abs>&show_hidden=1` → `{path, parent, truncated, entries[{name,path,kind,hidden,size?,modified_ms?,link_target?}]}` | Web「选择文件 / 文件夹」弹窗(`add-web-path-picker`)的服务端目录浏览器:**已鉴权即可只读浏览 daemon 全盘**,不走 `/api/files` 的 workspace 白名单,噪音目录不过滤,junction 照常列出并附 `link_target`;路径只做词法归一**不 canonical**(经 `C:\Users\shao` junction 进入返回的仍是 `C:/...`,与原生对话框一致,否则同一目录会注册出两个 workspace hash);5000 条上限置 `truncated`;`ec.message()` 在中文 Windows 是 GBK,入 JSON 前必须 `ensure_utf8`,否则 dump 抛异常整个请求变裸 500。触发策略:有 Desktop bridge 走原生对话框,没有(普通浏览器 / Edge --app 兼容模式 / 远程 Web)一律 web 弹窗,不再调用 daemon 的原生 REST 对话框(那会在 daemon 所在机器桌面弹框并把请求挂死)。实现:`src/web/handlers/fs_browser_handler.{hpp,cpp}` 纯函数 + `routes_fs.cpp`;前端 `lib/pathPicker.js`(纯逻辑)+ `lib/pathPickerHost.js`(Promise 形态的单实例请求)+ `components/PathPickerModal.jsx` / `PathPickerHost.jsx`(App 顶层挂载),三处接入点 `lib/workspacePicker.js` / `lib/environmentSettings.js::pickEnvironmentPath` / `lib/desktopPreviewFilePicker.js::pickPreviewFile` |

`SessionMeta` 增加 `forked_from` / `fork_message_id` 字段(空时省略,老 meta 文件向后兼容)。Web 上每条消息 hover 浮出 `[复制] [分叉]` actions(codex 风格);分叉成功后立刻切到新 session(同 sidebar)。

handler 实现在 `src/web/handlers/{fork,models,history,skills,files}_handler.{hpp,cpp}`(纯函数,有 unit test),路由注册在 `src/web/server.cpp`。`/static/<...>` + `/` + SPA fallback 用 `CROW_CATCHALL_ROUTE` 一举处理(Crow 1.3.2 的 `<path>` 模板路由有兼容性问题)。
handler 实现在 `src/web/handlers/{fork,models,history,skills}_handler.{hpp,cpp}` 与 `src/workspace/files_handler.{hpp,cpp}`(纯函数,有 unit test),路由注册在 `src/web/server.cpp`。`/static/<...>` + `/` + SPA fallback 用 `CROW_CATCHALL_ROUTE` 一举处理(Crow 1.3.2 的 `<path>` 模板路由有兼容性问题)。

**SidePanel 「变更」tab**(redesign-sidepanel-git-changes):会话 cwd 是 git 仓库时整体切换为 **git 级视图**——`GET /api/git/changes`(numstat + name-status 按路径 join,200 条 cap + truncated 标记)列文件,单文件 diff 点击才拉(`GET /api/git/diff`,1MB cap,untracked 用 `--no-index` 合成新增 patch,diff2html 行内渲染),头部 `<branch> → <base ▾>` 只切比较基线不做 checkout(候选 = origin/<默认分支>(默认)+ HEAD)。前端缓存收口在 `web/src/lib/gitChanges.js`((cwd,base) 键 + 单文件 patch LRU 20;失效 = 回合结束 / `acecode:git-state-changed` 事件(GitSessionPill checkout 成功后广播)/ 手动刷新 / 可见且 30s 过期;面板隐藏只标脏不请求)。bash/sed/外部 IDE 的改动在 git 视图天然可见——旧「hunks 聚合抓不到 bash 改动」的 limitation 只剩**非 git 项目**(该场景保留原会话级 `lib/sessionChanges.js` 聚合视图,不变)。

Expand All @@ -575,7 +575,7 @@ handler 实现在 `src/web/handlers/{fork,models,history,skills,files}_handler.{
`acecode-desktop.exe`(`-DACECODE_BUILD_DESKTOP=ON` 编出)是个 webview/webview 壳,默认管 N 个 daemon 子进程 — 每 workspace 一个独立 daemon(独立 loopback 端口 + 独立 token + 独立 Job Object)。daemon 内部代码零改动:每个 daemon 进程仍只服务自己的 `current_path()`,通过 `lpCurrentDirectory` 在 `CreateProcess` 时落地。

关键模块(除注明外全在 `src/desktop/`;refactor20260927 P2-03 起 locale、folder_picker / context_picker / open_in_explorer / notifications* / custom_toast* / strings 下沉到 `src/platform/` 与 `src/platform/native_ui/`):
- `workspace_registry.{hpp,cpp}` — 扫 `.acecode/projects/<hash>/workspace.json`(`{cwd, name}`),默认命名 = `fs::path(cwd).filename()`,行内重命名走 `set_name` 原子写。
- `workspace_registry.{hpp,cpp}`(已移到 `src/workspace/`,refactor20260927 P2-04)— 扫 `.acecode/projects/<hash>/workspace.json`(`{cwd, name}`),默认命名 = `fs::path(cwd).filename()`,行内重命名走 `set_name` 原子写。
- `daemon_pool.{hpp,cpp}` — `unordered_map<hash, Slot>`,per-key condvar 串行化同 hash 并发 activate;`stop_all` best-effort。`IDaemonSupervisor` 虚基类便于单测 mock。
- `pick_active.{hpp,cpp}` — 启动选 active workspace 的纯函数:`state.json::last_active_workspace_hash` → process cwd 的 hash → registry 第一项 → 空。
- `platform/native_ui/folder_picker_win.cpp` — `IFileOpenDialog` + `FOS_PICKFOLDERS`,COM STA。
Expand Down
10 changes: 5 additions & 5 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@
set(ACECODE_WEB_DIST "${CMAKE_SOURCE_DIR}/web/dist")
set(ACECODE_WEB_EMBED_DIR "${ACECODE_WEB_DIST}")
if(NOT EXISTS "${ACECODE_WEB_DIST}")
message(WARNING

Check warning on line 108 in CMakeLists.txt

View workflow job for this annotation

GitHub Actions / unit-tests (linux-x64)

[acecode] web/dist/ not found — embedding a minimal fallback page. Run
"[acecode] web/dist/ not found — embedding a minimal fallback page. "
"Run `pnpm install && pnpm build` inside web/ before re-configuring for the full UI.")
set(ACECODE_WEB_EMBED_DIR "${CMAKE_BINARY_DIR}/generated/web-dist-fallback")
Expand Down Expand Up @@ -225,22 +225,22 @@
${CMAKE_SOURCE_DIR}/src/provider/builtin_model_catalog.cpp
${CMAKE_SOURCE_DIR}/src/platform/process/os_process_posix.cpp
${CMAKE_SOURCE_DIR}/src/platform/process/os_process_windows.cpp
${CMAKE_SOURCE_DIR}/src/daemon/runtime_files.cpp
${CMAKE_SOURCE_DIR}/src/ipc/runtime_files.cpp
${CMAKE_SOURCE_DIR}/src/platform/native_ui/custom_toast.cpp
${CMAKE_SOURCE_DIR}/src/platform/native_ui/custom_toast_win.cpp
${CMAKE_SOURCE_DIR}/src/desktop/agent_browser_navigation_state.cpp
${CMAKE_SOURCE_DIR}/src/desktop/agent_browser_page_directory.cpp
${CMAKE_SOURCE_DIR}/src/desktop/agent_browser_runtime.cpp
${CMAKE_SOURCE_DIR}/src/ipc/agent_browser_runtime.cpp
${CMAKE_SOURCE_DIR}/src/desktop/context_items.cpp
${CMAKE_SOURCE_DIR}/src/platform/native_ui/context_picker.cpp
${CMAKE_SOURCE_DIR}/src/platform/native_ui/folder_picker_win.cpp
${CMAKE_SOURCE_DIR}/src/platform/locale.cpp
${CMAKE_SOURCE_DIR}/src/platform/native_ui/notifications.cpp
${CMAKE_SOURCE_DIR}/src/desktop/open_request.cpp
${CMAKE_SOURCE_DIR}/src/ipc/open_request.cpp
${CMAKE_SOURCE_DIR}/src/platform/native_ui/open_in_explorer.cpp
${CMAKE_SOURCE_DIR}/src/desktop/startup_progress.cpp
${CMAKE_SOURCE_DIR}/src/platform/native_ui/strings.cpp
${CMAKE_SOURCE_DIR}/src/desktop/workspace_registry.cpp
${CMAKE_SOURCE_DIR}/src/workspace/workspace_registry.cpp
${CMAKE_SOURCE_DIR}/src/utils/append_file.cpp
${CMAKE_SOURCE_DIR}/src/platform/clipboard.cpp
${CMAKE_SOURCE_DIR}/src/utils/cwd_hash.cpp
Expand Down Expand Up @@ -393,7 +393,7 @@
)
elseif(WIN32)
# advapi32: restrictive ACL writes in utils/atomic_file.hpp.
# ws2_32: daemon/runtime_files.cpp port probes.
# ws2_32: ipc/runtime_files.cpp port probes.
# ole32/shell32/user32: native folder picker and clipboard integration.
# gdi32/msimg32: the self-drawn toast renderer (layered window + GDI).
# winmm: PlaySound for the self-drawn toast.
Expand Down
2 changes: 1 addition & 1 deletion docs/agent-browser.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ macOS 的交互工具(click/fill/type/press/hover/drag/scroll)额外暴露
- 公共 native host API:`src/desktop/agent_browser_host.hpp`
- Windows WebView2 host:`src/desktop/agent_browser_host.cpp`
- macOS WKWebView host:`src/desktop/agent_browser_host_mac.mm`
- runtime manifest 与 URL policy:`src/desktop/agent_browser_runtime.{hpp,cpp}`
- runtime manifest 与 URL policy:`src/ipc/agent_browser_runtime.{hpp,cpp}`
- 鉴权代理 client:`src/tool/agent_browser/cdp_client.{hpp,cpp}`
- 工具 schema/动作:`src/tool/agent_browser/browser_tools.{hpp,cpp}`

Expand Down
4 changes: 2 additions & 2 deletions docs/desktop-shell/design.md
Original file line number Diff line number Diff line change
Expand Up @@ -388,10 +388,10 @@ The components themselves (`ace-app`, `ace-chat`, …) are untouched.

Unit-testable in `acecode_testable` (no FTXUI, no WebView):

- `src/desktop/runtime_files.{hpp,cpp}` — wrappers around
- `src/ipc/runtime_files.{hpp,cpp}` — wrappers around
`<data_dir>/run/` reads. Already exists for the daemon side; add a
symmetric `read_runtime_state()` returning a parsed struct.
→ `tests/desktop/runtime_files_test.cpp`
→ `tests/ipc/runtime_files_test.cpp`
- `src/desktop/runtime_json_parser.{hpp,cpp}` — parses a single
`{"port":…,"token":…}` line. Pure function.
→ `tests/desktop/runtime_json_parser_test.cpp`
Expand Down
2 changes: 1 addition & 1 deletion docs/desktop-shell/multi-workspace.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ daemon slot 已启动,并向 daemon `POST /api/workspaces` 注册 cwd,返回同

| 路径 | 作用 |
|---|---|
| `src/desktop/workspace_registry.{hpp,cpp}` | 扫盘 / 读写 `workspace.json` / 默认命名 |
| `src/workspace/workspace_registry.{hpp,cpp}` | 扫盘 / 读写 `workspace.json` / 默认命名 |
| `src/desktop/daemon_pool.{hpp,cpp}` | 通用进程池;Desktop 当前只使用 `__shared_daemon__/default` slot |
| `src/desktop/daemon_supervisor.{hpp,cpp}` | 单 daemon 子进程托管(spawn / probe / stop / Job Object)。提了 `IDaemonSupervisor` 虚基类便于单测 mock |
| `src/platform/native_ui/folder_picker_win.cpp` | `IFileOpenDialog` 包装 |
Expand Down
2 changes: 1 addition & 1 deletion docs/desktop-shell/tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
- [ ] **T1.5** Extend `src/web/auth.cpp::require_auth` to honor
`cfg.web.require_token_on_loopback`. Default `false` preserves current
behavior.
- [ ] **T1.6** Update `daemon/runtime_files.cpp` to write the same payload
- [ ] **T1.6** Update `src/ipc/runtime_files.cpp` to write the same payload
that `--emit-runtime-json` emits (no behavior change for service mode).
- [ ] **T1.7** Tests for `--port 0`, `--emit-runtime-json`, and the new
`require_auth` branch in `tests/daemon/`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,23 @@ python scripts/refactor/normalize_includes.py --scope tests

脚本只改引号内的路径,分支上新增的文件同样会被改写;分支里引用 7 个 helper 头的 include 会随 `src_layout_map.tsv` 的 P1-01 行自动改到新位置。P1-02 起 `layer-lint` 作业阻断 `../` include,没跑脚本的分支 CI 会直接失败。P2-09 的 `migrate_branch.py --apply-map` 内置同一套 include 解析,走 patch 迁移路线的分支不需要单独处理。

## P2-04 公告(2026-09-27)

master 合入 P2-04 后,下列文件已从 daemon / desktop / web 移到冻结前的过渡模块目录(P3 再整体挂到 `src/base/` 下)。按 D24(2026-09-27),旧路径**不留转发头**,`.hpp` / `.cpp` 都直接不存在了,分支里对旧路径的 include 只能靠下面的脚本改写:

| 旧路径 | 新路径 | include 写法 |
|---|---|---|
| `src/daemon/runtime_files.{hpp,cpp}`、`src/daemon/guid.hpp` | `src/ipc/` | `"ipc/runtime_files.hpp"`、`"ipc/guid.hpp"` |
| `src/desktop/{open_request,agent_browser_runtime}.{hpp,cpp}`、`src/desktop/daemon_protocol.hpp` | `src/ipc/` | `"ipc/open_request.hpp"` 等 |
| `src/desktop/workspace_registry.{hpp,cpp}` | `src/workspace/` | `"workspace/workspace_registry.hpp"` |
| `src/web/handlers/files_handler.{hpp,cpp}` | `src/workspace/` | `"workspace/files_handler.hpp"` |
| `tests/daemon/runtime_files_test.cpp`、`tests/desktop/{agent_browser_runtime,open_request}_test.cpp` | `tests/ipc/` | — |
| `tests/desktop/workspace_registry_test.cpp`、`tests/web/files_handler_test.cpp` | `tests/workspace/` | — |

另外 `get_acecode_dir()` / `get_run_dir()` / `get_logs_dir()` 的声明从 `config/config.hpp` 移到 `utils/paths.hpp`(namespace 仍是 `acecode`,`config.hpp` 透传 include,老写法照常编译);只为这三个函数 include `config/config.hpp` 的文件请改为 include `utils/paths.hpp`。`ACECODE_NATIVE_BRIDGE_SOURCES` 里四行路径已改。

分支在 rebase 之前先运行 P1-01 公告里的同一个脚本(`normalize_includes.py --scope src` / `--scope tests`),它会按 `src_layout_map.tsv` 的 P2-04 行把旧模块根写法与旧目录里的同目录裸名一并改到新位置;分支里新增的、引用这些头的文件同样会被改写。走 patch 迁移路线的分支由 `migrate_branch.py --apply-map` 内置同一套解析处理。

## 复现与验收

在包含 P0-03 工具的 checkout 中运行:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@
- lint 显示 provider↔session、provider↔tool、provider↔pa 三个环消失;
- `system_prompt.cpp` 对 compact.hpp 的无用 include 已删除;
- byte-stable 用例与 tool_preamble 测试通过。
- [ ] 3.2 【P2-03】【子】【并】平台件下沉到 `platform/` 与 `pty/`,对应 layout-map.md §2 中标 P2-03 的行。〔认领: Claude-p2-03 2026-09-27〕〔接手: Codex-root 2026-09-28,用户确认原 Claude 任务及子任务已停止〕
- [x] 3.2 【P2-03】【子】【并】平台件下沉到 `platform/` 与 `pty/`,对应 layout-map.md §2 中标 P2-03 的行。〔认领: Claude-p2-03 2026-09-27〕〔接手: Codex-root 2026-09-28,用户确认原 Claude 任务及子任务已停止〕〔验收: Codex-root 2026-09-28,PR #79 已合入;四平台复核见 verification/P2-03-platform-and-pty.md §8〕
- `hooks/hook_runner` → `platform/process/process_runner`(ProcessSpec,hooks 保留别名);
- `lsp_process` → `piped_process`,`lsp_which` → `which`;
- `daemon/platform*` → `os_process*`;
Expand Down
Loading
Loading