Conversation
Windows 端此前没有 Agent 后端:backendCapabilities.agent 为 false, platform_invoke 不认识任何 agent 命令,features/ai 是不接后端的遗留代码。 macOS 的 Agent 侧栏早已使用共享的 rust/lithe-agent-host,本提交把 Windows 接到同一个 crate,为后续面板 UI 打底(Refs 1lck#956)。 - windows/tauri/src-tauri/src/agent.rs(新增):按连接 ID 登记 AgentHandle, 只提供 agent_open / agent_send / agent_close 三个命令;连接 ID 由界面生成, 界面先订阅 agent_event 再开连接,避免事件早于标识到达;重复或空 ID 直接 拒绝,不会顶掉在用的连接;事件只发给打开它的窗口,不广播给其他项目; 显示关闭走阻塞池,避免 AgentHandle::close 的等待卡住异步运行时;项目窗口 销毁和应用退出都会释放连接与进程树,退出路径同步等待。 - windows/tauri/src-tauri/src/main.rs:注册三个命令,在 WindowEvent::Destroyed 里释放该窗口的连接,在 RunEvent::Exit 里关闭全部连接。 - windows/tauri/src-tauri/Cargo.toml、Cargo.lock:直接依赖 lithe-agent-host。 - windows/tauri/src/platform/tauri-core.ts:把三个命令登记为原生命令,让前端 的 invoke 边界直接把长连接命令交给 Tauri 主机,而不是走共享命令信封。 - windows/tauri/src/platform/tauri-core.test.ts:新增路由断言。 - rust/lithe-agent-host/src/lib.rs:Windows 上以 CREATE_NO_WINDOW 启动适配器, 否则 GUI 外壳每开一次 Agent 都会弹出一个控制台窗口。 - .agents/notes/.../2026-09-25-shared-acp-agent-conversation.md:记录 Windows 连接桥的所有权、事件寻址和退出清理,并更新适用范围与验证命令。 - shared/platform-feature-matrix.json 与生成的 docs/development/platform-parity-matrix.*: 把连接桥加入四个 Agent 条目的 Windows 证据,并注明 UI 仍待接入。 未包含:Agent 面板 UI、供应商切换、订阅额度展示,以及 backendCapabilities.agent 的开关(留给面板 PR,避免顺带放开 features/ai 里那些没有后端的历史命令)。 验证: - cargo test --manifest-path windows/tauri/src-tauri/Cargo.toml agent:: → 5 passed - test-stability-windows.ps1 -Scope WindowsRust → 202 passed,0 failed(新增 5 个 用例 80–926 ms,报告 .artifacts/test-stability/index.html) - cargo test -p lithe-agent-host → 47 passed - test-stability-windows.ps1 -Scope Frontend -FrontendTestPath src/platform/tauri-core.test.ts → 5 passed - bun run typecheck → 干净;cargo fmt --check(两个 crate)→ 干净 - verify-test-stability.ps1、verify-agent-notes.mjs、generate-platform-feature-matrix.mjs --check → 通过 - verify-windows-boundaries.ps1 在本机因未跟踪的 windows/build-* 与 windows/winui 构建目录报「must not restore the retired Qt/C++ implementation」,与本次改动无关 (git ls-files 中没有任何 .cpp/.h);其余检查项逐条通过。 已知限制:连接桥还没有用真实适配器在 Windows 上跑过端到端,平台矩阵的 Windows 验证状态仍是待验证。
1lck
left a comment
There was a problem hiding this comment.
Lithe Review
结论:存在 1 个 P1 阻塞问题,另有 1 个 P2 建议。
范围: preview ← PR #960,共同基点 130c39f485983d9691e017b9c346747dfb58845d 至 f107358eaf1a8e979803c336f595f12c6160e42d。
Findings
- P1:窗口关闭时从注册表移除连接,却把清理留在未登记的后台任务中。 随后的应用退出无法等待这些任务,Agent 进程树可能残留。
- P2:agent_send / agent_close 未校验调用窗口。 其他窗口只要传入已有 connection ID,就能操作原窗口的连接。
位置、触发时序及修复建议见两条行内意见,均为本次新增连接桥引入。
Scope Check
已核对 #956 的一期范围:本 PR 复用共享 lithe-agent-host,新增 open/send/close、按窗口事件投递、销毁与退出钩子、CREATE_NO_WINDOW 及原生命令路由;没有另写 ACP 协议实现。面板 UI、供应商与额度展示仍未接入,不能视为整个 #956 完成。继续关闭旧 agent 能力开关有明确原因:旧命令尚无后端,不把这一点作为缺陷。
Verification
- 本地 diff、程序包只读、功能矩阵及变更门禁、Windows 边界、Agent Notes 和跨平台测试稳定性静态检查通过。
- 当前 head 的已有 GitHub checks 无失败或待完成项,但分支与 preview 存在合并冲突;应解决冲突后重新验证。
- 检查了新增 403 行连接桥、Tauri main 事件钩子、共享 AgentHandle::open/send/close/Drop 与进程终止路径、路由测试、Note 和矩阵。锁定 Tauri 2.11.5 的本地依赖源码确认 App::run 退出不会等待任意未登记清理任务。
- 现有连接桥测试主要以无法启动的假命令验证注册表和 stopped 事件,不能证明存活 Agent 的进程树在窗口销毁/退出竞争下已被回收。
- 本次未运行完整构建、测试或原生 UI;没有生成新的编译缓存。代码缺陷依据当前 head 的调用链分析,未声称已在 Windows/macOS 实机复现。
建议先修复 P1,并用可控关闭 gate 覆盖“窗口销毁移出注册表→清理未结束→应用退出”的顺序;真实适配器端到端验证仍需 Windows。
| /// for its bounded stop window. | ||
| pub fn close_window_connections(window: &str) { | ||
| for connection in take_window_connections(window) { | ||
| tauri::async_runtime::spawn_blocking(move || connection.handle.close()); |
There was a problem hiding this comment.
[P1] 退出前也要等待已从注册表移出的连接清理
take_window_connections 先把连接从全局 map 移除,这里再把 AgentHandle::close() 丢到 blocking pool,并丢弃任务句柄。随后 shutdown() 只 drain map 中剩下的连接,因此看不到这些正在关闭的 Agent。close_connection() 也有先移除、再异步等待的同类窗口。
触发时序:存在一个仍运行的 Agent,销毁其项目窗口,在后台关闭任务执行前或尚未完成时退出应用。退出钩子调用 agent::shutdown() 时注册表已空,可以立即返回;Tauri 的 App::run 最终直接退出进程,后台线程和 AgentHandle 的 Drop 不能作为完成保证,外部 Agent/子进程可能残留。现有用“缺失可执行文件”构造的测试没有存活进程,无法覆盖这个路径。
这是本次新增桥接层的生命周期缺口。建议让 registry 同时拥有 active 和 closing 连接/任务,退出时禁止新连接并有界等待两类清理完成;不要仅等待仍在 active map 中的 handle。补充使用可控 gate 的销毁→退出竞争测试,并以受控假适配器验证整棵进程树退出。
证据为当前代码时序与锁定的 Tauri 2.11.5 退出路径,未在 Windows 实机运行该复现。
There was a problem hiding this comment.
已按建议修掉,见本分支新增 commit 2caa6ce:
- 注册表移出连接与「正在关闭」计数现在在同一个临界区完成(
take_connections),所以退出 drain 不可能看不到已被取走的连接;agent_close命令路径也不再是「先移除、后异步等待」。 shutdown()先在同一次加锁里停止接受新连接(Registry.accepting)并取走剩余连接、同步关闭它们,再以CLOSING_WAIT(15 秒)有界等待仍在阻塞池里的关闭,超时打印告警后放弃。- 可控 gate 覆盖:
exit_wait_returns_once_the_last_close_is_released(有关闭挂起时退出等待阻塞、释放后返回)、exit_wait_gives_up_at_its_deadline_while_a_close_is_pending(超时放弃)、taking_a_connection_for_a_close_counts_it_before_any_task_runs(移出连接时计数立即生效)、an_exit_drain_takes_every_connection_and_refuses_new_ones(drain 后拒绝新连接)。
仍缺的验证与你指出的一致:这些用例证明的是计数与等待语序,没有用存活的真实 Agent 验证整棵进程树在「窗口销毁→退出」竞争下已被回收,那一步仍需要 Windows 真机加真实适配器。
分支已合入 preview f53f65d。cargo test --manifest-path windows/tauri/src-tauri/Cargo.toml agent:: 11 passed;test-stability-windows.ps1 -Scope WindowsRust 208 passed(最慢用例 2.45s,低于 15s 上限);Agent Note 与功能矩阵 --check 通过。
|
|
||
| /// Queues one shared `AgentCommand` on an open connection. | ||
| #[tauri::command] | ||
| pub fn agent_send(connection_id: String, command: Value) -> Result<(), String> { |
There was a problem hiding this comment.
[P2] send/close 应校验调用者仍是连接所属窗口
agent_open 从注入的 Webview 记录 owner,但 agent_send 和 agent_close 均只接受前端提供的 connection ID,没有注入调用窗口,也没有与 Connection.window 比较。全局 map 的查找/移除只按 ID,因此窗口 B 使用窗口 A 已打开的 ID,就能给 A 发 prompt/cancel/permission 命令,或关闭 A 的连接;仅把输出事件定向给 A 不能保证命令侧隔离。
可构造复现:A 用 ID connection-a 打开连接,B 调用 agent_close({ connectionId: 'connection-a' });当前代码会移除并停止 A 的连接。该行为属于本次新增命令的归属边界,按 P2 提出。
建议 send/close 也注入 Webview,并在锁内查找/移除前校验 owner;内部窗口销毁/退出清理可保留独立的受信路径。补充 A 可操作 A、B 操作 A 被拒绝且连接仍在的双窗口测试。
There was a problem hiding this comment.
已修:agent_send 和 agent_close 现在同样注入 tauri::Webview,在注册表锁内用 verify_owner 比对连接归属窗口。close_connection 的 select 在移除之前就会返回错误,所以被拒绝的调用既不发送也不移除连接;窗口销毁/退出的内部清理走独立的受信路径。
新增测试 a_window_cannot_drive_or_close_another_windows_connection:A 打开连接后,B 的 send 与 close 都返回 "belongs to another window",随后 A 的连接仍在(send 只报 payload 非法)。Tauri 命令的 JS 参数形状没有变化,前端无需改动。
修复 review 提出的两个问题:
- P1 退出前必须等待已从注册表移出的连接清理:窗口销毁时连接先被移出
注册表、清理却留在未登记的阻塞任务里,随后的应用退出只 drain 注册表,
看不到这些连接,可能残留 Agent 进程树。现在注册表的移出与「正在关闭」
计数在同一个临界区完成;退出先在同一次加锁里停止接受新连接并取走剩余
连接、同步关闭它们,再有界等待(CLOSING_WAIT,15 秒)仍在阻塞池里的
关闭,超时打印告警后放弃(AgentHandle::close 自带停止窗口并强杀进程树,
这个上限只兜底任务尚未被调度的情况)。
- P2 agent_send / agent_close 未校验调用窗口:其他窗口只要知道 connection
ID 就能驱动或关闭别人的连接。现在两个命令都注入 Webview,并在注册表锁
内比对连接归属窗口;被拒绝的调用不会移除也不是关闭连接。
改动点:
- windows/tauri/src-tauri/src/agent.rs:注册表改为 Registry{connections,
accepting},新增 open_in / drain_in / take_connections(移出并计数),
queue_close、wait_for_closing、verify_owner;关闭计数用 Condvar 通知,
退出等待有界。
- windows/tauri/src-tauri/src/agent.rs 测试从 5 个增加到 11 个:新增跨窗口
send/close 被拒且连接仍在、移出连接时计数立即生效、退出等待在有关闭时
挂起、超时后放弃、最后一个关闭结束时返回、退出 drain 后拒绝新连接;按
窗口释放连接的用例同时断言计数归零。
- .agents/notes/implemented/architecture/2026-09-25-shared-acp-agent-conversation.md:
补记窗口归属校验、退出停止接受新连接与有界等待,以及「未用存活 Agent
验证整棵进程树回收」的覆盖边界。
验证:cargo test --manifest-path windows/tauri/src-tauri/Cargo.toml agent::
(11 passed);test-stability-windows.ps1 -Scope WindowsRust(208 passed,
最慢用例 2.45s,低于 15s 上限);rustfmt;node scripts/verify-agent-notes.mjs。
…ndows-agent-host 合入 preview 到 f53f65d。冲突只在由 shared/platform-feature-matrix.json 生成的 三个文件:保留本分支的 Windows Agent 连接桥 evidence、verify 文案与 lastReviewed=2026-09-28,同时保留 preview 新增的图标资源验收句子;随后用 node scripts/generate-platform-feature-matrix.mjs 重新生成 md/csv, `--check` 通过。
已按 review 修复,并合入最新 preview新提交: P1 退出等待(
P2 命令归属(同文件)
测试(5 → 11 个) 新增跨窗口 send/close 被拒且连接仍在、移出连接时计数立即生效、退出等待在有关闭时挂起、超时后放弃、最后一个关闭结束时返回、退出 drain 后拒绝新连接;按窗口释放连接的用例补了计数归零断言。 验证
仍未覆盖:面板 UI、供应商切换与额度展示未接入;计数与等待语序有测试,但没有用存活的真实 Agent 验证整棵进程树在「窗口销毁→退出」竞争下已被回收,这一步仍需 Windows 真机加真实适配器。 |
连接桥的关闭语义此前只用假启动失败和可控 gate 覆盖,没有用存活的 Agent 验证“整棵进程树”是否真的被回收。Windows 适配器(如 codex-acp)会派生出 独立的 app-server 并继承 stdout,只杀掉外层进程会留下它并占住输出管道。 - 新增 `fake_acp_adapter` 假适配器:以 NDJSON 应答 ACP 握手(initialize 返回 gateway authMethod,session/new 返回会话),派生一个继承 stdout 的 grandchild 模拟 app-server,并把两个进程 ID 写进 pid 文件;孙进程带 120 秒自活上限,避免测试失败时泄漏进程。 - 它是普通 bin 而不是 `test-support` 特性下的目标:后者没有任何测试套件 启用(`fake_dap_adapter` 的测试因此在 CI 中从不运行)。它也不会被打包, `tauri.conf.json` 只打包应用二进制。 - agent::tests 新增两条端到端路径断言:`agent_close` 关闭连接、以及窗口 销毁触发的 `close_window_connections`,都必须让外层进程和继承 stdout 的 孙进程同时退出,失败时先杀掉幸存者再断言。 - 顺带修掉测试噪音:RecordingSink 对已结束测试的迟到事件不再 panic,等 待中的测试仍以自己的 recv_timeout 失败。 - Agent Note 更新进程树回收的验证状态与仍待真机验收的边界。
|
再看这次 review 的结论“现有连接桥测试主要以无法启动的假命令验证注册表和 stopped 事件,不能证明存活 Agent 的进程树在窗口销毁/退出竞争下已被回收”,补了 commit 2db7192( 新增假适配器
为什么是普通 bin 而不是 新增两条端到端断言(都在
两条都先 顺带修掉一个测试噪音: 本地验证:
P1/P2 的修复没有回退,面板 UI、供应商与额度展示仍未接入,本期范围没变。 |
cargo test --no-run 只产出 libtest harness,不会在 target/debug 下生成平名的 fake-acp-adapter.exe,干净检出上两条进程树测试因此以 "The system cannot find the file specified" 失败。 fixture_path() 改为在平名产物缺失时按需执行 cargo build --bin fake-acp-adapter,并在调用方传入 --target-dir 时跟随其 profile 目录;构建失败时把 cargo stderr 带进 panic,避免只看到 os error 2。
- platform_invoke 增加第 6 个参数 agent_events;Tauri 会把缺失的尾部 Option 参数注入为 None,所以旧的前端调用点不需要改动。 - agent.install / agent.installCli 的 SharedEvent 投递到 agent 通道, 其余事件(含 git.*)仍走原来的 git 通道。 - 安装类命令不再套用 30 秒默认信封超时:npm 下载可能持续几分钟, 共享 host 自己以 15 分钟为界,macOS 也不设信封超时。 - 前端 installProgressEvent 先校验 kind 与数值字段再上报,避免面板 渲染出 undefined。
面板(对齐 macOS 的右侧 Agent 侧栏) - features/agent 新增面板本体:会话标签、消息流、工具与权限提示、输入框、 上下文用量与订阅额度、历史会话列表与恢复、适配器状态/安装/卸载。 - 连接是「一个窗口一个项目」的模块单例:隐藏面板保留运行中的回合, 切换项目停掉旧进程树,窗口销毁交给 Tauri 宿主回收。 - 传输层直接复用 `@tauri-apps/api/event` 的 listen,并把 `listen` 从 tauri-core 里移除,恢复 tauri-core.test.ts 只 mock Channel/invoke 的前提。 - 可写目录统一走 platform/app-data-directory.ts(appDataDir),不从安装 目录推导,避免破坏发行包基线与增量更新。 设置与入口 - 设置页新增 Agent 供应商配置:API Key 存安全存储 (agent-provider-api-key/<agentId>),参数改为每行一个的 Textarea, 与 macOS 的 agentArguments 按换行拆分保持一致。 - 侧栏活动栏、快捷键命令与视图动作新增 Agent 面板入口;settings.agentPanel 记录面板可见性并参与归一化。 - 能力开关:backendCapabilities.agent 打开,新增 aiChat=false;tauri-core 把 agent.status/install/uninstall/installCli 映射到 agent,旧的 acp_*/codex_*/ ai_provider/_chat 仍映射到 aiChat,继续显示「待开发」而不是「未实现」。
- 功能矩阵 9 条 Agent 行补齐 Windows 证据与实现状态:入口发现、上下文用量、 Codex 订阅与额度已实现;历史、管理、文件引用、供应商配置、ACP 对话为部分 实现,verificationStatus 仍保持 pending(真机验证未做)。 - 同步重新生成 docs/development/platform-parity-matrix.md 与 .csv。 - Agent Note 补充本轮 4 条决策:右栏挂载与 settings.agentPanel、供应商 API Key 的安全存储键、启动规则对齐 macOS 的 agentLaunchConfiguration、能力开关拆分, 以及安装进度/安装超时的归属。
新增步骤通过计时 runner 显式枚举 `src/features/agent` 与 `src/config/backend-capabilities.test.ts`(本地 38 项通过,最慢 2 ms), 报告写入 `.artifacts/test-stability/windows-agent.json` 并随现有上传步骤归档。 此前这些用例不在任何 CI 步骤里,改动 `windows/*` 已由分类器选中前端 lane, 所以只补测试入口,不动分类逻辑。
对齐 macOS `AgentHistoryFeatureModel` 的 Lithe 侧标注能力: - 新增 `agent-history-annotations.ts`(纯函数:作用域键、解析、重命名、标记) 与 `agent-history-store.ts`(平台 store `agent-history.json` 的读写)。 标注只属于 Lithe,按“Agent ID + 工作区路径”隔离,不进发行包,也不写 Agent 自己的会话文件;每次修改前先重读再合并,两个窗口不会互相覆盖。 - 新增 `agent-history-view.ts`:筛选(全部/收藏/已移除)、按标题或会话 ID 搜索、更新时间倒序(无时间戳的排最后、同刻按 ID 升序),以及已加载会话 的消息数统计,语义与 macOS `AgentHistoryPresentation` 一致。 - 历史列表增加筛选、搜索框、复制会话 ID、收藏、重命名、单条移除与恢复, 以及多选批量收藏/移除/恢复;批量操作只作用于当前可见行,移除前先确认, 并说明 Agent 原始记录会保留。 - `useAgentHistory` 在面板内持有标注;面板把标注、已加载会话与动作传给列表。 - 新增 14 项纯函数测试(作用域键规范化、解析容错、空标题拒绝、标记合并、 筛选/搜索/排序、消息数);标注逻辑与 store 分开,测试不加载 Tauri 运行时。 本地 53 项通过(`src/features/agent` + `src/config/backend-capabilities.test.ts`)。 - 矩阵与 Agent Note 记录该增量:Markdown 导出与独立历史页仍未实现。
对齐 macOS `AgentHistoryFeatureModel.export`: - 模型新增 `canExportTranscript` / `historyTranscript`:按需重放未打开的会话, 导出用的加载不打开标签页、不改当前选中会话;重放串行执行,避免共享连接上 两次回放交错。加载失败、连接中断或部分回放一律返回 null,绝不返回半份记录。 - `agent-history-export.ts` 复刻 macOS `AgentHistoryDocument.markdown`:一段 一个会话标题与会话 ID,消息按角色分节,工具消息保留状态、输入、输出与内容块; 标题里的换行会折叠,避免破坏标题层级。 - `agent-history-save.ts` 用原生保存对话框选定目标,只写用户选中的路径;取消 对话框不写文件,超过 32 MiB 直接报错,平台错误不直接抛给用户。 - 面板与历史列表接入:行内导出与「选择」模式下的批量导出,导出中禁用按钮, 全部加载成功后才写文件并显示失败原因。 - 新增 6 项模型测试(不打开标签页、失败返回 null、连接中断返回 null、已加载不 重复加载、两次导出串行、不可重放时拒绝)与 3 项 Markdown 渲染测试;本地 62 项通过。纯渲染与保存分开,测试仍不加载 Tauri 运行时。
本 PR 的完整范围原 scope(
目前还差的部分以下都还没进本 PR,我会在本 PR 上继续补(不新开 PR,原因见下):
关于新开 PR不支持把本 PR 之后的工作拆成“基于本 PR 分支的 stacked PR”: |
|
还在开发的话 可以把状态设置为草稿 |
- 从系统拖入文件即可引用:复用 file-system 的 extractDroppedFilePaths,与文件选择器 走同一条 addFileReferences 校验、去重与数量上限;高亮只在真正携带 Files 时出现, 在子元素之间移动不会闪烁。 - 输入 `@` 打开项目文件选择器并按已输入前缀过滤,选中后把 `@query` 文本替换为文件 引用标签,草稿其余内容不变。 - 匹配规则抽成纯函数 services/agent-composer-mentions.ts:引用必须位于行首或空白 之后且不含空白与第二个 `@`,因此邮箱地址和已完成的引用不会重新弹出选择器;不需要 DOM 或真实适配器即可测试。 - i18n 新增 agent.composer.dropFiles,并顺带补齐本 PR 后续设置页要用的键。
- CLI 安装/升级入口(agent.installCli):设置页显示 PATH 上 CLI 的版本与安装来源; 只有 host 明确给出 canUpdate,或根本没检测到 CLI 时才提供按钮,升级进度复用 agentInstallProgress;成功后显示已验证版本,安装器带警告时单独一行展示,不把 警告当成失败,也不在无法判断来源时让用户以为会自动覆盖。 - 供应商配置导入(agent.parseProviderConfiguration):粘贴 Codex TOML 或 Claude JSON, 由共享 Rust 解析出 endpoint、model 与协议后填入供应商表单;只读元数据、不搬运 密钥;chatCompletions 以及与所选适配器协议不匹配时给出明确提示,而不是猜一个协议。 - 服务层新增 installAgentCli / parseAgentCliUpdateResult 与 agent-provider-import, agent.install 与 agent.installCli 共用的进度 channel 抽成 installProgressChannel。 - tauri-core 能力门控加入 agent.parseProviderConfiguration,Agent 面板关闭时与其他 agent.* 一样走统一不可用路径。
…sue-956-windows-agent-host 冲突解决: - windows/tauri/src-tauri/src/main.rs:窗口销毁时同时保留上游的 core::close_ide_hosts 与本分支的 agent::close_window_connections。 - windows/tauri/src/platform/tauri-core.ts:采用上游新的 isPlatformDispatcherCommand 路由规则(取代原有 nativeCommands 白名单),并在其上保留本分支改动—— platform_invoke 的 agentEvents 通道、agent.* 能力门控(含 agent.parseProviderConfiguration);tauri-core.test.ts 的 agent 连接用例改为断言该规则。 - docs/development/platform-parity-matrix.csv/.md:生成物,按合并后的 shared/platform-feature-matrix.json 重新生成。 - shared/platform-feature-matrix.json 自动合并成功:上游新增 13 行,本分支 agent 行的 状态与证据保持不变,无重复 id。
- agent-management:Windows 说明改为已接入 CLI 安装/升级——只有 host 明确给出可更新来源 (canUpdate)或完全没有检测到 CLI 时才显示按钮,升级后显示 host 验证过的版本,安装器报错 但版本确实提升时单独一行展示警告;仍未接入的是取消安装、预检清单与本机 CLI 配置文件读取。 证据补上 hooks/use-agent-management.ts。 - agent-provider-configuration:说明粘贴配置文本导入已接入(共享 Rust 解析出 endpoint、模型与 协议,只读元数据、不搬运密钥,chatCompletions 或协议不匹配时报错);仍未接入的是一键读取 本机 CLI 配置文件、多供应商列表与取消关联。证据补上 services/agent-provider-import.ts。 - agent-file-references:Windows 由 partial 改为 implemented。补上从系统拖拽文件与在输入框 输入 @ 打开选择器(两者共用同一套去重、数量上限与可移除标签)的说明与证据;真实拖放体验与 Claude 端到端仍待验证,所以 verificationStatus 保持 pending。 - Agent Note 同步以上实现、验证命令与遗留项。 - docs/development/platform-parity-matrix.csv/.md 由 node scripts/generate-platform-feature-matrix.mjs 重新生成。
…sue-956-windows-agent-host
Tauri 默认开启原生拖放(dragDropEnabled),WebView2 收到系统文件时只发原生事件, DOM 的 HTML5 drop 拿不到文件;项目树用鼠标事件模拟拖动,也不会触发 drop。 此前输入区只挂了 HTML5 onDrop,真实窗口里两条路径都不会添加引用。 - 拖放路由新增 agent 目标:落点在 data-agent-file-drop-target 内时,原生拖放与 项目树松手都派发 lithe-agent-file-drop 事件,交给输入区同一个 attach 入口 (复用去重与 32 个上限);文件夹不作为引用 - 新增路由与派发单测;矩阵 agent-file-references 证据与说明、Agent Note 同步
Windows 真机端到端发现两个问题,都出在 npm 全局安装写的是前缀目录里的 <命令>.cmd 批处理启动器,而不是 Unix 上的符号链接: - 新建会话报 spawn EINVAL:适配器不经 shell 启动 CLI_PATH 指向的程序,Node.js 拒绝这样启动 .cmd。现在 cli_update::launch_executable 在包声明的 bin 是原生 .exe 时(Claude Code)改传这个 .exe;脚本 bin(Codex 的 codex.js)仍传启动器, codex-acp 在 Windows 上自己用 shell 启动。API Key 与订阅两条路径共用 - CLI 来源被识别成 Unknown、不给升级按钮:.cmd 的规范路径不指向包,现在先按同 目录 node_modules/<包>/package.json 找到声明的 bin 再做原有身份校验;另一个 npm 前缀的启动器仍然只给手动指引 新增 launcher_tests(映射逻辑全平台运行,来源识别仅 Windows);矩阵 agent-management 与 Agent Note 同步。本机验证:Node 24 / npm 11,真实安装 claude-acp 0.81.2,.cmd 启动、ACP 握手、session/new 成功,关闭后进程树清空。
这个 PR 做了什么
Windows 端此前没有 Agent 后端:
backendCapabilities.agent是false,platform_invoke里没有任何 agent 分支(translate()未命中的命令直接返回Windows platform command is not implemented),windows/tauri/src/features/ai/是不接后端的历史代码(#440 已判定不作为参考)。而 macOS 的 Agent 侧栏已经建在共享的rust/lithe-agent-host上。这个 PR 把 Windows 接到同一个 crate,先把连接桥做出来,为后续面板 UI 打底。Refs #956(该 issue 的一期)。
改动点
windows/tauri/src-tauri/src/agent.rs(新增):按连接 ID 登记AgentHandle,只提供agent_open/agent_send/agent_close三个命令,转发的就是shared/fixtures/agent/acp-events-v1.json那套 JSON,协议、会话、取消语义仍只有共享 crate 一份。agent_event再开连接,因此不存在“事件早于连接标识到达”的竞态;重复或为空的 ID 直接拒绝,不会静默顶掉在用的连接。agent_send/agent_close也注入Webview,在注册表锁内比对连接归属窗口,被拒的调用既不发送也不移除连接,窗口 B 拿到窗口 A 的 ID 也驱动不了它。AgentHandle::close的等待放到阻塞池,不占住异步运行时。take_connections),退出先在同一次加锁里停止接受新连接并取走剩余连接、同步关闭它们,再以 15 秒上限等待仍在阻塞池里的关闭,超时打印告警后放弃。windows/tauri/src-tauri/src/bin/fake_acp_adapter.rs(新增):受控假适配器,让“整棵进程树被回收”有确定性证据。它按 NDJSON 应答 ACP 握手(initialize返回authMethods: [{ id: "gateway" }],所以能走完initialize → authenticate → Ready),记录自身 PID 后派生一个继承 stdout 的孙进程模拟codex-acp的 app-server,并把两个 PID 写进 pid 文件;孙进程带 120 秒自活上限,测试被中断也不会把 fixture 进程泄漏给后续 CI。它注册成普通 bin 而不是test-support目标——后者没有任何测试套件启用,fake_dap_adapter的测试因此在 CI 里从不运行;它也不会被打包,tauri.conf.json只打包应用二进制。agent::tests:13 个用例。除原有的非法启动配置、空或重复连接 ID、启动失败上报、未知连接与非法命令、按窗口释放连接、跨窗口 send/close 被拒、关闭计数在移出连接时即生效、出口等待的挂起/唤醒/超时、退出 drain 后拒绝新连接之外,新增两条进程树断言:closing_a_live_connection_reclaims_the_whole_agent_tree(agent_close路径)与destroying_a_window_reclaims_the_agent_tree_it_opened(窗口销毁路径),都先等到ready事件确认握手完成再关闭,断言外层进程与孙进程都已退出,失败时先杀掉幸存者再断言。windows/tauri/src-tauri/src/main.rs:注册三个命令;WindowEvent::Destroyed释放该窗口的连接;RunEvent::Exit关闭全部连接。windows/tauri/src-tauri/Cargo.toml/Cargo.lock:直接依赖lithe-agent-host。windows/tauri/src/platform/tauri-core.ts:把三个命令登记为原生命令,invoke边界直接把长连接命令交给 Tauri 主机,而不是塞进共享命令信封。windows/tauri/src/platform/tauri-core.test.ts:新增路由断言。rust/lithe-agent-host/src/lib.rs:Windows 上以CREATE_NO_WINDOW启动适配器。Windows 外壳是 GUI 进程、没有控制台,不加这个标志每次起 Agent 都会弹出一个控制台窗口。.agents/notes/.../2026-09-25-shared-acp-agent-conversation.md:记录 Windows 连接桥的所有权、事件寻址、退出清理、CREATE_NO_WINDOW,以及进程树验收的覆盖范围与仍待真机验收的边界,更新适用范围和验证命令。shared/platform-feature-matrix.json与生成的docs/development/platform-parity-matrix.*:把连接桥加入四个 Agent 条目的 Windows 证据,Windows 验证状态仍是待验证。明确不做
backendCapabilities.agent:那个开关是按能力生效的,翻开会同时放开acp_*、_chat、get_available_agents这些没有后端的历史命令,把它们从“待开发”变成运行期报错。面板 PR 落地时再决定门禁怎么划。windows/tauri/src/features/ai/(另见 [Cleanup] 处理 windows/tauri/src/features/ai 历史遗留 AI 前端(无后端、默认不可用) #957)。验证
cargo test --manifest-path windows/tauri/src-tauri/Cargo.toml agent::→ 13 passed./.agents/skills/write-stable-tests/scripts/test-stability-windows.ps1 -Scope WindowsRust→ 211 passed,0 failed(含新增 3 个用例,最慢两条约 0.95 s,上限 15 s;报告.artifacts/test-stability/index.html)cargo test -p lithe-agent-host→ 47 passed./.agents/skills/write-stable-tests/scripts/test-stability-windows.ps1 -Scope Frontend -FrontendTestPath src/platform/tauri-core.test.ts→ 5 passedbun run typecheck→ 干净cargo fmt --manifest-path rust/Cargo.toml --all -- --check、cargo fmt --manifest-path windows/tauri/src-tauri/Cargo.toml -- --check→ 干净./.agents/skills/write-stable-tests/scripts/verify-test-stability.ps1、node scripts/verify-agent-notes.mjs、node scripts/generate-platform-feature-matrix.mjs --check→ 通过scripts/verify-windows-boundaries.ps1:本机因未跟踪的windows/build-*、windows/winui构建目录触发 “must not restore the retired Qt/C++ implementation”,与本次改动无关(git ls-files里没有任何.cpp/.h),该脚本由 CI 在干净检出上运行(.github/workflows/ci-windows.yml:204)。测试都在本机 Windows 上跑过,不依赖真实 Agent、网络或已安装的适配器。除“能解析但起不来”的假启动之外,进程树用例用的是本仓库自带的
fake_acp_adapter假适配器,不会碰用户环境。已知限制
@agentclientprotocol/codex-acp、node/npm 探测、.cmdshim 启动、以及配合本机 Codex CLI 或真实 Key 的完整对话)。平台矩阵的 Windows 验证状态保持“待验证”,等真机验收后再改。