Skip to content

feat(desktop): harden Windows packaging foundation and bundled sidecar runtime#488

Open
jinyu918 wants to merge 68 commits into1024XEngineer:mainfrom
jinyu918:feat/desktop-packaging-foundation-rebased-20260509
Open

feat(desktop): harden Windows packaging foundation and bundled sidecar runtime#488
jinyu918 wants to merge 68 commits into1024XEngineer:mainfrom
jinyu918:feat/desktop-packaging-foundation-rebased-20260509

Conversation

@jinyu918
Copy link
Copy Markdown
Collaborator

@jinyu918 jinyu918 commented May 9, 2026

背景

这个分支主要补齐并加固了桌面端在 Windows 打包场景下的运行基础设施,使 CialloClaw 从“开发态可运行”进一步走向“打包后可启动、可通信、可更新”。

相较于 upstream/main,本分支重点解决的是 packaged desktop 在真实安装包环境中的一组问题,而不只是单点修复:

  • 桌面端虽然可以打包,但缺少稳定的 bundled backend/runtime foundation
  • 打包后的本地 sidecar 启动、寻址、连接恢复和日志可观测性不够完善
  • named-pipe RPC 在 packaged 场景下容易出现阻塞、订阅丢失、请求卡死或响应错配
  • shell-ball / dashboard 在打包环境下与本地服务的交互稳定性不足
  • 安装/升级过程中未可靠处理仍在运行的桌面进程,容易导致文件占用和更新失败

这个分支解决了什么问题

1. 补齐 Windows 打包基础能力

分支为桌面端补齐了 Windows 打包所需的基础配置和产物约束,包括:

  • 增加 Windows 双安装包目标(NSIS / MSI)
  • 补齐打包图标与 bundle 元数据
  • 将本地 Go sidecar 纳入桌面安装包分发链路
  • 让 packaged runtime 使用独立的运行时路径与本地服务启动入口

这部分改动让桌面端不再依赖开发态的本地环境假设,而是具备了完整的“随包分发 + 随包启动”的基础能力。

2. 让 bundled local-service 在打包环境中稳定启动

分支把 bundled local-service 的启动流程做成了真正可用于安装包环境的方案,并补齐了失败处理与可观测性,包括:

  • 从桌面端启动随包分发的本地 sidecar
  • 加固 sidecar 启动参数、路径与 build target 约束
  • 记录 sidecar 启动失败与运行失败信息
  • 为 packaged 启动链路补充测试覆盖

这部分的目标是解决“包打出来了,但本地服务起不来 / 起了连不上 / 出错难定位”的问题。

3. 加固 packaged 场景下的 named-pipe RPC 传输

这是这条分支最核心的一部分。围绕 packaged desktop 与 local-service 的通信链路,分支做了多项稳定性增强:

  • 对部分请求引入 isolated named-pipe request 路径,避免共享流上相互阻塞
  • 保持 shared / isolated 请求职责分离,并补齐通知转发
  • 修复 packaged 场景下 named-pipe subscription 恢复问题
  • 在 pipe 请求卡死或响应异常时提供 fallback 与恢复路径
  • 序列化 same-task stream replay,减少同任务并发回放造成的错序/串扰
  • 保留 dev 场景下的 pipe fallback,避免打包修复反向破坏开发态

这部分主要解决的是:

  • 打包后请求卡住不返回
  • shell-ball 或 dashboard 看起来“发出去了但没响应”
  • 订阅事件收不到或恢复不正确
  • 同一任务的事件流和响应混杂

4. 对 shell-ball / 桌面交互做 packaged 对齐

在打包通信链路稳定之后,分支也同步收敛了 shell-ball 与 runtime 的交互行为,使其更符合 packaged 场景的真实执行链路,包括:

  • 加固 shell-ball submit fallback
  • 对齐 shell-ball contract tests 与运行时行为
  • 修复 packaged RPC 流下的若干交互不一致问题
  • 补充 task polling / bubble / notification 相关稳定性修复

这部分的重点不是做新功能,而是让悬浮球、任务流和本地服务在打包环境中的行为与预期一致,减少“开发态正常、打包后异常”的断层。

5. 修复安装/升级时未停进程的问题

分支还补上了安装器层面的最后一环:

  • 在 NSIS 安装器中加入 preinstall / preuninstall hook
  • 在 WiX/MSI 中加入安装/卸载前的进程终止逻辑
  • 在安装或升级前主动停止:
    • cialloclaw-desktop.exe
    • cialloclaw-service.exe

这部分解决的是安装包覆盖升级时桌面进程仍在运行导致的文件占用、替换失败和升级不完整问题。

总结

整体上,这个分支解决的不是“如何把桌面程序打成一个安装包”这么简单的问题,而是:

让 CialloClaw 的 Windows 安装包在真实用户环境中具备完整、稳定、可升级的运行基础。

它主要完成了三件事:

  1. 让 bundled backend 能随包启动
  2. 让 packaged desktop 与 local-service 的通信真正稳定可用
  3. 让安装/升级流程能够处理运行中进程,避免更新失败

验证

本分支已经围绕打包链路做了针对性验证,包括:

  • 本地服务启动入口相关测试
  • 打包 / sidecar 启动相关测试
  • Windows bundle 生成验证(NSIS / MSI)
  • shell-ball / runtime 对齐相关 contract tests 与修复

影响范围

主要涉及以下模块:

  • apps/desktop/src-tauri/
  • apps/desktop/src/features/shell-ball/
  • apps/desktop/src/rpc/
  • services/local-service/cmd/server/
  • services/local-service/internal/rpc/
  • services/local-service/internal/orchestrator/

gdemonc and others added 30 commits April 23, 2026 13:42
Introduce the desktop build entrypoints and Tauri bundle configuration needed to emit NSIS and MSI installers while packaging the Go local-service as an external binary.
Start the Go sidecar from the desktop host, grant the required shell capability, and allow the packaged runtime to route workspace and database paths through an explicit app data directory.
…g-foundation

# Conflicts:
#	apps/desktop/src-tauri/gen/schemas/capabilities.json
Wait for the bundled service pipe before the renderer issues RPCs, cross-build the Go sidecar explicitly for Windows, and remove unnecessary webview shell spawn access now that Rust owns sidecar startup.
Teach the desktop Windows workflow to react to local-service changes, install Go, compile the Windows sidecar first, and then run the existing no-bundle Tauri build against the real packaging layout.
Refactor the server entrypoint into testable helpers and add focused CLI/startup tests so the packaging branch no longer drops Go coverage in the cmd/server package.
Timeout named-pipe bridge waits in the Tauri host and fail over packaged renderer requests to the local HTTP JSON-RPC endpoint so dashboard and control-panel loads no longer hang when the pipe session stops returning responses.
Generated with [codeagent](https://github.com/qbox/codeagent)
Co-authored-by: gdemonc <146809967+gdemonc@users.noreply.github.com>
Accept Tauri loopback-style origins on the debug RPC endpoint so packaged builds can recover from stalled named-pipe requests without being blocked by CORS.
Open a fresh pipe stream for each desktop RPC and keep the local-service stream handler responsive so one stalled packaged window no longer blocks later dashboard or control-panel reads.
Keep a shared named-pipe session alive for the desktop host so subscribed windows continue receiving task and delivery notifications while packaged RPC requests still wait for sidecar readiness.
Remove the leftover merge markers from the Tauri bundle config and keep the packaged sidecar path aligned with the current desktop packaging flow.
Remove the leftover merge markers from the desktop Windows workflow, keep the cached Go setup, preserve the stable desktop checks, and use the current sidecar build command.
Keep cross-task named-pipe reads concurrent but serialize requests that target the same task so shared notification drains cannot race and drop replayed task updates or deliveries.
…sk deduper

fix: Deduplicate terminal shell-ball task bubbles and add terminal task deduper
fix: Handle invalid JSON in loadStoredValue and add contract test
Keep the default named pipe when the bundled sidecar is unavailable so debug and manual local-service sessions still connect. Serialize task-starting shared-stream RPCs to prevent runtime-notification cross-talk under concurrent requests, and cover the startup entrypoint with targeted tests.
Register the existing Windows icon assets in the Tauri bundle config so NSIS builds stop failing during installer generation. This keeps the desktop packaging path aligned with the packaged app assets already shipped in src-tauri/icons.
@codecov
Copy link
Copy Markdown

codecov Bot commented May 9, 2026

Copy link
Copy Markdown
Contributor

@fennoai fennoai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found 2 issues worth addressing. Security and documentation passes did not surface additional noteworthy problems.

Comment thread apps/desktop/src-tauri/src/main.rs
Comment thread apps/desktop/src-tauri/src/main.rs Outdated
@jinyu918
Copy link
Copy Markdown
Collaborator Author

jinyu918 commented May 9, 2026

@xgopilot 你提的 #488 (review) 这个问题确实存在,也属于这个分支负责的范围:隔离 named-pipe 请求一旦超时,当前实现会继续把后续悬浮球 bubble submit 走 isolated 路径,等于在“已知可疑”的通道上反复重试,存在持续堆积失控后台 worker 的风险。它不一定每次都永久泄漏线程,但确实缺少降级保护。现在重新review一下

@fennoai
Copy link
Copy Markdown
Contributor

fennoai Bot commented May 9, 2026

Review

No new findings in the re-review scope.

The prior concern about isolated named-pipe timeouts repeatedly sending later shell-ball bubble submits down the same suspect path appears resolved in this branch:

  • apps/desktop/src-tauri/src/main.rs:248-250 now routes through should_use_isolated_transport(...) instead of unconditionally reusing the isolated path for eligible payloads.
  • apps/desktop/src-tauri/src/main.rs:318-340 marks the isolated transport degraded on timeout and immediately gates future eligible requests back to the shared session.
  • apps/desktop/src-tauri/src/main.rs:1785-1804 keeps the isolated path narrowly scoped to floating-ball bubble submits, so the downgrade only affects the risky path we were discussing.
  • apps/desktop/src-tauri/src/main.rs:1670-1674 adds a regression test for the degradation latch itself.

Residual gap: I did not find a transport-level test that forces a real isolated timeout end-to-end; the new coverage is unit-level around the latch. I was able to run go test ./services/local-service/internal/rpc -run 'TestServerShutdown|TestServer', which passed. I could not run the Rust-side test here because cargo is unavailable in this environment.

View job run

Comment thread services/local-service/internal/orchestrator/task_continuation.go Outdated
Comment thread services/local-service/internal/rpc/server_lifecycle.go Outdated
Comment thread services/local-service/internal/execution/service.go Outdated
@jinyu918
Copy link
Copy Markdown
Collaborator Author

jinyu918 commented May 9, 2026

@MeteorsLiu 好,#488 (comment)#488 (comment) 已经改了,不过对于 https://github.com/1024XEngineer/CialloClaw/pull/488#discussion_r3212819830,ai跟我说的是这里的前提其实不是“RPC server 只会跟着整个进程启动一次、退出一次”,而是把 Server 作为一个可显式 Start/Shutdown 的生命周期组件来设计。当前这层抽象除了服务真实运行,也承载了测试里的几个约束:并发 Start 要拒绝、干净 Shutdown 后允许再次 Start、如果 shutdown 超时则后续要 fence reuse。单靠 context.Done() 只能表达“这个 context 被取消了”,但表达不了这些 server 自身状态,所以这里还是保留了显式状态位。另外这里也不是想靠 context 传递业务错误;terminalErr 主要是为了区分“正常停机后可复用”和“停机不完整、后续不应继续复用”这两类状态。因为 ctx.Err() 最终只有 Canceled/DeadlineExceeded,不足以保留这层 server 生命周期语义。然后测试貌似已经把这个契约写死了,要改还要改这几处地方,主要是在server_transport_test.go下的

@MeteorsLiu
Copy link
Copy Markdown
Collaborator

@MeteorsLiu 好,#488 (comment)#488 (comment) 已经改了,不过对于 https://github.com/1024XEngineer/CialloClaw/pull/488#discussion_r3212819830,ai跟我说的是这里的前提其实不是“RPC server 只会跟着整个进程启动一次、退出一次”,而是把 Server 作为一个可显式 Start/Shutdown 的生命周期组件来设计。当前这层抽象除了服务真实运行,也承载了测试里的几个约束:并发 Start 要拒绝、干净 Shutdown 后允许再次 Start、如果 shutdown 超时则后续要 fence reuse。单靠 context.Done() 只能表达“这个 context 被取消了”,但表达不了这些 server 自身状态,所以这里还是保留了显式状态位。另外这里也不是想靠 context 传递业务错误;terminalErr 主要是为了区分“正常停机后可复用”和“停机不完整、后续不应继续复用”这两类状态。因为 ctx.Err() 最终只有 Canceled/DeadlineExceeded,不足以保留这层 server 生命周期语义。然后要改貌似还要改这几处地方

ai胡说八道,ctx.Cause 让他看这个,生命周期这一块你去想清楚,验证一下

@jinyu918
Copy link
Copy Markdown
Collaborator Author

jinyu918 commented May 9, 2026

@MeteorsLiu 好,#488 (comment)#488 (comment) 已经改了,不过对于 https://github.com/1024XEngineer/CialloClaw/pull/488#discussion_r3212819830,ai跟我说的是这里的前提其实不是“RPC server 只会跟着整个进程启动一次、退出一次”,而是把 Server 作为一个可显式 Start/Shutdown 的生命周期组件来设计。当前这层抽象除了服务真实运行,也承载了测试里的几个约束:并发 Start 要拒绝、干净 Shutdown 后允许再次 Start、如果 shutdown 超时则后续要 fence reuse。单靠 context.Done() 只能表达“这个 context 被取消了”,但表达不了这些 server 自身状态,所以这里还是保留了显式状态位。另外这里也不是想靠 context 传递业务错误;terminalErr 主要是为了区分“正常停机后可复用”和“停机不完整、后续不应继续复用”这两类状态。因为 ctx.Err() 最终只有 Canceled/DeadlineExceeded,不足以保留这层 server 生命周期语义。然后要改貌似还要改这几处地方

ai胡说八道,ctx.Cause 让他看这个,生命周期这一块你去想清楚,验证一下

好,学长,学到了,已经改好了,具体变更见cf2270a

@MeteorsLiu
Copy link
Copy Markdown
Collaborator

@MeteorsLiu 好,#488 (comment)#488 (comment) 已经改了,不过对于 https://github.com/1024XEngineer/CialloClaw/pull/488#discussion_r3212819830,ai跟我说的是这里的前提其实不是“RPC server 只会跟着整个进程启动一次、退出一次”,而是把 Server 作为一个可显式 Start/Shutdown 的生命周期组件来设计。当前这层抽象除了服务真实运行,也承载了测试里的几个约束:并发 Start 要拒绝、干净 Shutdown 后允许再次 Start、如果 shutdown 超时则后续要 fence reuse。单靠 context.Done() 只能表达“这个 context 被取消了”,但表达不了这些 server 自身状态,所以这里还是保留了显式状态位。另外这里也不是想靠 context 传递业务错误;terminalErr 主要是为了区分“正常停机后可复用”和“停机不完整、后续不应继续复用”这两类状态。因为 ctx.Err() 最终只有 Canceled/DeadlineExceeded,不足以保留这层 server 生命周期语义。然后要改貌似还要改这几处地方

ai胡说八道,ctx.Cause 让他看这个,生命周期这一块你去想清楚,验证一下

好,学长,学到了,已经改好了,具体变更见cf2270a

没见到相关更改,是不是回复错了

@jinyu918
Copy link
Copy Markdown
Collaborator Author

jinyu918 commented May 9, 2026

@MeteorsLiu 好,#488 (comment)#488 (comment) 已经改了,不过对于 https://github.com/1024XEngineer/CialloClaw/pull/488#discussion_r3212819830,ai跟我说的是这里的前提其实不是“RPC server 只会跟着整个进程启动一次、退出一次”,而是把 Server 作为一个可显式 Start/Shutdown 的生命周期组件来设计。当前这层抽象除了服务真实运行,也承载了测试里的几个约束:并发 Start 要拒绝、干净 Shutdown 后允许再次 Start、如果 shutdown 超时则后续要 fence reuse。单靠 context.Done() 只能表达“这个 context 被取消了”,但表达不了这些 server 自身状态,所以这里还是保留了显式状态位。另外这里也不是想靠 context 传递业务错误;terminalErr 主要是为了区分“正常停机后可复用”和“停机不完整、后续不应继续复用”这两类状态。因为 ctx.Err() 最终只有 Canceled/DeadlineExceeded,不足以保留这层 server 生命周期语义。然后要改貌似还要改这几处地方

ai胡说八道,ctx.Cause 让他看这个,生命周期这一块你去想清楚,验证一下

好,学长,学到了,已经改好了,具体变更见cf2270a

没见到相关更改,是不是回复错了

这里现在不用 WithCancelCause,ai判断是这层 context 已经不承担“传递 terminal error / lifecycle cause”的职责了。然后现在服务已经收窄成 one-shot,对这个 context 来说只需要支持父级取消传播和 Shutdown 主动 cancel,所以普通的 WithCancel(ctx) 就够了。上面那个提交是收束的一个,不过他没收干净,我再补了一个a70f959

@MeteorsLiu
Copy link
Copy Markdown
Collaborator

MeteorsLiu commented May 9, 2026

@MeteorsLiu 好,#488 (comment)#488 (comment) 已经改了,不过对于 https://github.com/1024XEngineer/CialloClaw/pull/488#discussion_r3212819830,ai跟我说的是这里的前提其实不是“RPC server 只会跟着整个进程启动一次、退出一次”,而是把 Server 作为一个可显式 Start/Shutdown 的生命周期组件来设计。当前这层抽象除了服务真实运行,也承载了测试里的几个约束:并发 Start 要拒绝、干净 Shutdown 后允许再次 Start、如果 shutdown 超时则后续要 fence reuse。单靠 context.Done() 只能表达“这个 context 被取消了”,但表达不了这些 server 自身状态,所以这里还是保留了显式状态位。另外这里也不是想靠 context 传递业务错误;terminalErr 主要是为了区分“正常停机后可复用”和“停机不完整、后续不应继续复用”这两类状态。因为 ctx.Err() 最终只有 Canceled/DeadlineExceeded,不足以保留这层 server 生命周期语义。然后要改貌似还要改这几处地方

ai胡说八道,ctx.Cause 让他看这个,生命周期这一块你去想清楚,验证一下

好,学长,学到了,已经改好了,具体变更见cf2270a

没见到相关更改,是不是回复错了

这里现在不用 WithCancelCause,ai判断是这层 context 已经不承担“传递 terminal error / lifecycle cause”的职责了。然后现在服务已经收窄成 one-shot,对这个 context 来说只需要支持父级取消传播和 Shutdown 主动 cancel,所以普通的 WithCancel(ctx) 就够了。上面那个提交是收束的一个,不过他没收干净,我再补了一个a70f959

这是在干啥?

  1. 为啥不按照我的意见改,如果不按照请给出具体解释
  2. 为什么不重新思考下数据流,而是完全相信ai?你应该需要自己判断下是否需要设计可以重复Start/Shutdown功能,否则就是过度设计
  3. 如果你实在不理解ai在改啥,请说不知道,我直接给你approve。review本身是帮助你发现问题的,如果你把它变成了“走流程”,全都转发给ai,那没必要review了。

@jinyu918
Copy link
Copy Markdown
Collaborator Author

jinyu918 commented May 9, 2026

@MeteorsLiu 好,#488 (comment)#488 (comment) 已经改了,不过对于 https://github.com/1024XEngineer/CialloClaw/pull/488#discussion_r3212819830,ai跟我说的是这里的前提其实不是“RPC server 只会跟着整个进程启动一次、退出一次”,而是把 Server 作为一个可显式 Start/Shutdown 的生命周期组件来设计。当前这层抽象除了服务真实运行,也承载了测试里的几个约束:并发 Start 要拒绝、干净 Shutdown 后允许再次 Start、如果 shutdown 超时则后续要 fence reuse。单靠 context.Done() 只能表达“这个 context 被取消了”,但表达不了这些 server 自身状态,所以这里还是保留了显式状态位。另外这里也不是想靠 context 传递业务错误;terminalErr 主要是为了区分“正常停机后可复用”和“停机不完整、后续不应继续复用”这两类状态。因为 ctx.Err() 最终只有 Canceled/DeadlineExceeded,不足以保留这层 server 生命周期语义。然后要改貌似还要改这几处地方

ai胡说八道,ctx.Cause 让他看这个,生命周期这一块你去想清楚,验证一下

好,学长,学到了,已经改好了,具体变更见cf2270a

没见到相关更改,是不是回复错了

这里现在不用 WithCancelCause,ai判断是这层 context 已经不承担“传递 terminal error / lifecycle cause”的职责了。然后现在服务已经收窄成 one-shot,对这个 context 来说只需要支持父级取消传播和 Shutdown 主动 cancel,所以普通的 WithCancel(ctx) 就够了。上面那个提交是收束的一个,不过他没收干净,我再补了一个a70f959

这是在干啥?

  1. 为啥不按照我的意见改,如果不按照请给出具体解释
  2. 为什么不重新思考下数据流,而是完全相信ai?你应该需要自己判断下是否需要设计可以重复Start/Shutdown功能,否则就是过度设计
  3. 如果你实在不理解ai在改啥,请说不知道,我直接给你approve。review本身是帮助你发现问题的,如果你把它变成了“走流程”,全都转发给ai,那没必要review了。

嗯,这个可能是我的决策问题吧,一开始我是想着都改的,不过ai说明WithCancelCause是只有在需要给取消附带一个可读取的额外原因才需要,所以我最后选择了保留,然后现在是改了的,最新提交:874d5ea

@1024XEngineer 1024XEngineer deleted a comment from fennoai Bot May 10, 2026
@MeteorsLiu
Copy link
Copy Markdown
Collaborator

bf98f64 这个是哪个main,为啥会产生一堆文件变更

@jinyu918
Copy link
Copy Markdown
Collaborator Author

bf98f64 这个是哪个main,为啥会产生一堆文件变更

这个是思凯那边在优化代码,然后不解决的话会有冲突

@MeteorsLiu
Copy link
Copy Markdown
Collaborator

bf98f64 这个是哪个main,为啥会产生一堆文件变更

这个是思凯那边在优化代码,然后不解决的话会有冲突

我意思是你是不是merge错了,merge对的话是不会产生这些变更的,我不管谁推commit了

@jinyu918
Copy link
Copy Markdown
Collaborator Author

bf98f64 这个是哪个main,为啥会产生一堆文件变更

这个是思凯那边在优化代码,然后不解决的话会有冲突

我意思是你是不是merge错了,merge对的话是不会产生这些变更的,我不管谁推commit了

耶?我看看

@gdemonc
Copy link
Copy Markdown
Collaborator

gdemonc commented May 10, 2026

bf98f64 这个是哪个main,为啥会产生一堆文件变更

这个是思凯那边在优化代码,然后不解决的话会有冲突

我意思是你是不是merge错了,merge对的话是不会产生这些变更的,我不管谁推commit了

耶?我看看

还真是,这最新的commit怎么有不相干的东西

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants