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
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ Lithe 对外开放项目环境、Maven、运行配置和执行输出,复用界
JDK/Maven 环境设置内;未打开项目时显示提示,不创建授权。读取、配置和执行权限按项目处理。配置与执行
默认关闭。项目关闭、窗口销毁和应用退出都关闭连接。禁用 MCP 后,已接受的
执行仍归 IDE 管理,不能因为一个客户端断线误杀用户正在看的服务。
Windows 从打开连接前就登记授权代际;撤销后迟到的原生连接必须关闭,旧轮询
只能清理自身连接。项目关闭会等待正在打开的连接完成清理,不能只遍历已显示的配置。
Windows 从打开连接前就登记授权代际;撤销后迟到的原生连接必须关闭,旧通知
只能影响自身连接。项目关闭会等待正在打开的连接完成清理,不能只遍历已显示的配置。
Windows 原生层为 `open` 注入当前窗口的 `ownerId`,覆盖调用方提供的所有者;
该请求由严格 camelCase 结构解码。`closeOwner` 沿用独立的 `ownerID` 字段,
不可对所有动作全局替换字段名,否则窗口销毁时无法撤销访问。
Expand All @@ -42,6 +42,20 @@ Lithe 对外开放项目环境、Maven、运行配置和执行输出,复用界
普通应用的主输出面板与服务标签均持有执行 ID;Windows 取消 Java 启动确认时
同步清除准备中状态,关闭项目时先撤销 API,再清理已有运行资源。

- Windows 请求调度由原生 broker 接收后通过 Tauri 窗口定向事件推送,发送端由连接持有,
关闭时释放;不再依赖 WebView 的 200ms 定时器,也不拿前端后台暂停当作授权失效。
通知只包含请求身份,应用恢复后先通过 `claim` 领取未过期、未取消且未领取的请求。
领取响应携带剩余有效期;前端从发起领取前使用 Windows WebView 的 `performance.now()`
单调时钟计时,该平台时钟包含休眠且不受系统时间校正影响,执行前拒绝超时响应,
避免领取结果本身滞留在暂停的 WebView 中。真正执行仍走原工作区能力接口,不能把业务状态复制到 Rust 通信层。
- 一次事件或回应失败不撤销授权;下一次新请求可以自然恢复,但不自动重放任何
已领取操作。超时后用户需要先检查操作状态。设置页展示授权和最近请求结果,
不把外部客户端能列出工具等同于工作区可执行。自有失败提示在显示时走共享 i18n,
不直接显示协议英文;原始工具诊断仍原样返回 MCP 客户端。
- MCP 列表明确提供不缓存、私有的缓存策略;SDK 声明支持新协议并不代表应用
用默认值构造的结果满足新协议。旧版、新版直接发现及兼容初始化分别使用独立
helper 进程验证,不能只在旧版会话中追加一个新版本请求。

正确示例:MCP 的 Maven execute 调用现有 Maven 功能的 runGoals/runCustomGoal,
沿用项目的工具链与 Profile;不要在辅助程序里自行执行 `mvn` 并读取另一套配置。

Expand All @@ -60,6 +74,19 @@ Lithe 对外开放项目环境、Maven、运行配置和执行输出,复用界
4. **将 MCP 协议写两遍**:增加版本兼容和测试成本。使用官方 SDK 并保留平台
应用动作接线,避免为了共享而迁移整个原生工作台。

5. **只延长心跳或无限重试**:只能掩盖 WebView 调度暂停,不能阻止恢复后执行旧操作。
原生通知加执行前领取去掉心跳依赖,同时保留请求上限、超时和显式撤销。
6. **把 Git/Agent 进度回调用于连接通知**:前者随单次命令结束,后者随授权连接结束,
生命周期不同,因此采用单独的原生适配器入口,不改变既有进度回调契约。
7. **复用有序 Tauri Channel 并假定下次发送会恢复**:锁定的 Tauri 2.11.5 在
`ipc/channel.rs` 中先分配序号再发送;前端 `@tauri-apps/api/core` 会等待缺失序号。
一条消息丢失会使后续消息及结束通知持续等待。改用官方独立事件,每条请求自己
领取与超时,不自建有序重传协议;监听先于 open 注册,并随失败、撤销和关闭清理。
正确做法是丢失 A 后仍可领取新请求 B;不要为补齐通知而重放已领取的 A。
8. **使用 `Date.now()` 并仅拒绝负差值**:系统回拨后差值仍可能为正且小于预算,
放行实际已过期的请求。Windows 适配器提供单调计时,其他平台不可直接假设其
WebView 的休眠计时行为相同。

## 后果

用户复制一次客户端配置后,可以通过 AI 使用 IDE 的环境、Maven 和运行操作。
Expand All @@ -76,14 +103,22 @@ Rust 命令可以直接替代当前窗口或工作区的状态所有权。
- `cargo test --manifest-path rust/Cargo.toml -p lithe-ide-host` 验证授权、路径、
模块参数、输出游标、连接互斥、请求路由和打包输入不变。
- `node --test scripts/test-ide-mcp.mjs` 在先构建辅助程序后验证真实 stdio
握手、工具发现、结构化结果和可见工具错误。
握手、工具发现、结构化结果和可见工具错误。Windows CI 在共享 Rust 测试后
显式构建 helper 并执行同一协议矩阵,不能仅测试依赖库而遗漏发行平台的 stdio 程序。
- `./scripts/verify-shared-contracts.sh`、`./scripts/verify-windows-boundaries.sh`、
`./scripts/verify-runtime-bundle-immutability.sh` 验证契约和平台边界。
- Windows 的 `ide-capabilities.test.ts` 验证旧执行 ID、撤销期间的设置写入、
设置补丁和共享权限拒绝;macOS 的 `IdeCapabilitiesTests.swift` 验证授权与
工作区代际、模块不被错误激活以及执行身份。
- `mcp-lifecycle.test.ts` 用可控原生响应和手动调度器验证打开期间关闭项目、
全局退出和旧轮询失败不会恢复授权或撤销新连接;测试完成后必须清理所有计时器。
- `mcp-lifecycle.test.ts` 用可控通知和领取响应验证打开期间关闭项目、全局退出、
迟到/重复事件、单次失败后新请求恢复与未知回应不重放;不用真实睡眠模拟休眠。
另用独立进程中的 `mcp-events.integration.test.ts` 加载真实 Tauri 事件 API,验证
跳过通知后仍交付、限定接收 WebView、原生 open 失败后及正常关闭后解除监听。
单调/系统双时钟测试覆盖回拨后差值仍为正的过期请求,中英文错误词条随显示语言选择。
- 共享 broker 测试把心跳和请求时间设为已过期,验证原生推送不依赖心跳、过期或
已取消请求不能领取、通道错误不撤销授权,以及关闭后等待者与通道被释放。
Windows 原生测试通过真实 loopback HTTP、Core 和事件发送回调验证通知与响应,
实际前端事件 API 由上述独立测试覆盖。
- Worker 宿主测试验证未声明权限时不能发现或调用项目、显式工作区不被当前焦点
替换以及应用层拒绝原样返回。Maven 的 API 保存等待真实写入结果;失败进程的
输出读取不能被 Windows Git 兼容层误判为 IPC 失败。
Expand Down
19 changes: 19 additions & 0 deletions .github/workflows/ci-windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,15 @@ jobs:
node .agents/skills/write-stable-tests/scripts/run-bun-tests-with-timing.mjs --suite-timeout-ms 60000 --report .artifacts/test-stability/windows-notifications.json -- src/features/notifications
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }

- name: Test MCP delivery and authorization lifecycle
shell: pwsh
run: |
node .agents/skills/write-stable-tests/scripts/run-bun-tests-with-timing.mjs --report .artifacts/test-stability/windows-mcp-lifecycle.json -- src/features/host-api/mcp-lifecycle.test.ts src/features/host-api/ide-capabilities.test.ts
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
# The real Tauri event API must run without other suites' module mocks.
node .agents/skills/write-stable-tests/scripts/run-bun-tests-with-timing.mjs --report .artifacts/test-stability/windows-mcp-events.json -- src/features/host-api/mcp-events.integration.test.ts
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }

- name: Test declarative plugin lifecycle and UI
shell: pwsh
timeout-minutes: 9
Expand Down Expand Up @@ -719,6 +728,16 @@ jobs:
timeout-minutes: 20
run: ./.agents/skills/write-stable-tests/scripts/test-stability-windows.ps1 -Scope SharedRust -SuiteTimeoutSeconds 1080

- name: Verify Windows MCP stdio protocol
if: needs.changes.outputs.rust_core == 'true'
shell: pwsh
timeout-minutes: 5
run: |
cargo build --locked --manifest-path rust/Cargo.toml -p lithe-ide-host --features mcp --bin lithe-mcp
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
node --test --test-reporter=spec --test-reporter-destination=stdout --test-reporter=junit --test-reporter-destination=.artifacts/test-stability/mcp-protocol.junit.xml scripts/test-ide-mcp.mjs
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }

- name: Test Windows Rust host
if: needs.changes.outputs.windows_rust == 'true'
shell: pwsh
Expand Down
11 changes: 11 additions & 0 deletions rust/lithe-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,14 @@ pub fn git_askpass_main(prompt: &str) -> i32 {

/// Shared classification for ordinary project watcher events after root resolution.
pub use project::is_workspace_event_relevant;

/// Connects an authorized native window to the local IDE broker's request notifications.
/// The host retains the callback until close; receivers claim IDs through `ideHost.control`
/// before invoking application actions. This adapter boundary has no UI dependencies.
pub fn attach_ide_request_dispatcher(
host_id: &str,
owner: &str,
dispatcher: std::sync::Arc<dyn Fn(String) -> Result<(), String> + Send + Sync>,
) -> Result<(), String> {
lithe_ide_host::attach_dispatcher(host_id, owner, dispatcher)
}
Loading
Loading