应用内的下载(桌面 Agent 安装/更新、运行时引导)一旦开始就无法干预,且异常终止后留下的临时文件没有任何机制回收。以下结论都在代码里核对过,不是推测。
1. 没有取消入口
internal/binding/services.go 没有暴露任何 Cancel 方法(全文件只有一处 "Request was cancelled" 的错误文案,没有可调用的取消 API)。
frontend/src/components/DownloadProgress.tsx 是纯展示组件,一个按钮都没有。而 DesktopAppSection.tsx 在下载期间把 5 个按钮全部 disabled={Boolean(pending)}。
结果:用户点了下载之后只有两个选择——等它跑完,或者杀掉整个应用。超时是 installTimeout = 20 * time.Minute(desktopapp.go:102)和 RuntimeDownloadTimeout = 10 * time.Minute(bootstrap.go:23)。选错镜像、网络慢、点错按钮,都得干等。
2. 正常错误路径的清理其实是对的
先说清楚不用改的部分,避免修的人白做:
downloadFile(desktopapp.go:742-751)拷贝失败时 os.Remove(destination)
- macOS 装机路径有
defer os.RemoveAll(tempDir)(desktopapp.go:366)
- Windows 路径用
keepInstaller 守卫 defer 删除(desktopapp.go:694-699)
fetchTo(bootstrap.go)在拷贝失败和校验和不匹配时都 os.Remove(path)
返回 error 的路径都干净。 问题在于 defer 跑不到的时候。
3. 进程被杀 = 永久残留,而且不在系统 temp 里
这是真正的缺陷。installRuntime(bootstrap.go:232-253)把临时文件放在 ~/.oneagent/runtimes 下,不是系统 temp:
target := runtimeDir(runtime.Home, runtimeID, entry.Version)
parent := filepath.Dir(target) // ~/.oneagent/runtimes
...
file, err := os.CreateTemp(directory, ".download-") // bootstrap.go:333
staging, err := os.MkdirTemp(parent, ".staging-") // bootstrap.go:249
系统重启不会清理这个位置,OS 的 temp 回收策略也管不到。
grep 全仓库确认:没有任何启动时清理残留的代码。.download-*、.staging-*、oneagent-desktop-agent-* 三类前缀都没有对应的扫描清理逻辑。
体积不小:runtimes.lock.json 里 Node.js 制品是 node-v24.18.1-*.tar.gz,约 50MB 一份。用户每次强退一个进行中的下载,就在自己家目录里留 50MB,反复几次就是几百 MB,而且他不知道那些点号开头的文件是什么、能不能删。
复现
- 触发运行时安装(装任意需要 Node 的 Agent),或桌面 Agent 的「更新」
- 下载进行到一半时强制退出应用(Cmd+Q 或 kill)
ls -la ~/.oneagent/runtimes/ — .download-* 还在,且不会被任何后续操作清理
建议的修法
取消能力:给 binding 加一个 Cancel,把已有的 context.CancelFunc 接出去。取消路径本身已经通了(downloadFile/fetchTo 都用 NewRequestWithContext,contextError(ctx) 检查也铺好了),缺的只是从 UI 到 context 的这一段接线,以及 DownloadProgress 上的一个按钮。
暂停/续传:比取消复杂得多,需要 HTTP Range 支持和分片状态持久化。注意 fetchTo 里已有的注释明确说过,回退镜像时进度条要重新开始,因为「镜像的字节数说明不了官方源的情况」——续传要跨源就得处理这个。建议先做取消,暂停单独评估。
启动时清理:应用启动时扫 ~/.oneagent/runtimes,删掉超过某个时长(比如 24 小时)的 .download-* 和 .staging-*。用时间阈值而不是无条件删,避免误删另一个正在运行的实例的进行中文件。系统 temp 里的 oneagent-desktop-agent-* 优先级低一些,OS 会回收,但同样处理更好。
相关代码
internal/desktopapp/desktopapp.go:717 downloadFile
internal/install/bootstrap.go:288 downloadArtifact、fetchTo、installRuntime:232
internal/binding/services.go 缺 Cancel
frontend/src/components/DownloadProgress.tsx 缺取消按钮
应用内的下载(桌面 Agent 安装/更新、运行时引导)一旦开始就无法干预,且异常终止后留下的临时文件没有任何机制回收。以下结论都在代码里核对过,不是推测。
1. 没有取消入口
internal/binding/services.go没有暴露任何 Cancel 方法(全文件只有一处"Request was cancelled"的错误文案,没有可调用的取消 API)。frontend/src/components/DownloadProgress.tsx是纯展示组件,一个按钮都没有。而DesktopAppSection.tsx在下载期间把 5 个按钮全部disabled={Boolean(pending)}。结果:用户点了下载之后只有两个选择——等它跑完,或者杀掉整个应用。超时是
installTimeout = 20 * time.Minute(desktopapp.go:102)和RuntimeDownloadTimeout = 10 * time.Minute(bootstrap.go:23)。选错镜像、网络慢、点错按钮,都得干等。2. 正常错误路径的清理其实是对的
先说清楚不用改的部分,避免修的人白做:
downloadFile(desktopapp.go:742-751)拷贝失败时os.Remove(destination)defer os.RemoveAll(tempDir)(desktopapp.go:366)keepInstaller守卫 defer 删除(desktopapp.go:694-699)fetchTo(bootstrap.go)在拷贝失败和校验和不匹配时都os.Remove(path)返回 error 的路径都干净。 问题在于 defer 跑不到的时候。
3. 进程被杀 = 永久残留,而且不在系统 temp 里
这是真正的缺陷。
installRuntime(bootstrap.go:232-253)把临时文件放在~/.oneagent/runtimes下,不是系统 temp:系统重启不会清理这个位置,OS 的 temp 回收策略也管不到。
grep全仓库确认:没有任何启动时清理残留的代码。.download-*、.staging-*、oneagent-desktop-agent-*三类前缀都没有对应的扫描清理逻辑。体积不小:
runtimes.lock.json里 Node.js 制品是node-v24.18.1-*.tar.gz,约 50MB 一份。用户每次强退一个进行中的下载,就在自己家目录里留 50MB,反复几次就是几百 MB,而且他不知道那些点号开头的文件是什么、能不能删。复现
ls -la ~/.oneagent/runtimes/—.download-*还在,且不会被任何后续操作清理建议的修法
取消能力:给 binding 加一个 Cancel,把已有的
context.CancelFunc接出去。取消路径本身已经通了(downloadFile/fetchTo都用NewRequestWithContext,contextError(ctx)检查也铺好了),缺的只是从 UI 到 context 的这一段接线,以及DownloadProgress上的一个按钮。暂停/续传:比取消复杂得多,需要 HTTP Range 支持和分片状态持久化。注意
fetchTo里已有的注释明确说过,回退镜像时进度条要重新开始,因为「镜像的字节数说明不了官方源的情况」——续传要跨源就得处理这个。建议先做取消,暂停单独评估。启动时清理:应用启动时扫
~/.oneagent/runtimes,删掉超过某个时长(比如 24 小时)的.download-*和.staging-*。用时间阈值而不是无条件删,避免误删另一个正在运行的实例的进行中文件。系统 temp 里的oneagent-desktop-agent-*优先级低一些,OS 会回收,但同样处理更好。相关代码
internal/desktopapp/desktopapp.go:717downloadFileinternal/install/bootstrap.go:288downloadArtifact、fetchTo、installRuntime:232internal/binding/services.go缺 Cancelfrontend/src/components/DownloadProgress.tsx缺取消按钮