diff --git a/app.go b/app.go index 0db3563..f2ac98a 100644 --- a/app.go +++ b/app.go @@ -13,6 +13,7 @@ import ( "regexp" "runtime" "runtime/debug" + "strconv" "strings" "sync" "time" @@ -295,20 +296,15 @@ func (a *App) Stop() StartResult { cmd := a.dshCmd a.mu.Unlock() - if cmd == nil || cmd.Process == nil { - // 没记录,直接 pkill 兜底 - if pkill := lookPath("pkill"); pkill != "" { - _ = exec.Command(pkill, "-f", "dsh/lib/bin.js").Run() - } - } else { + if cmd != nil && cmd.Process != nil { _ = cmd.Process.Kill() _, _ = cmd.Process.Wait() } - // 兜底 pkill,确保所有 dsh 子进程都死 - if pkill := lookPath("pkill"); pkill != "" { - _ = exec.Command(pkill, "-f", "dsh/lib/bin.js").Run() - } + // 兜底:把 dsh 整个进程树都杀(dsh 自己 + dsh-pet Electron helper + GPU/renderer/utility 等) + // 不杀全,dsh-pet 会在 EPIPE 时弹 JavaScript 错误弹窗。 + killDSHTree() + time.Sleep(500 * time.Millisecond) a.mu.Lock() @@ -363,9 +359,7 @@ func (a *App) stopDSH() { if cmd != nil && cmd.Process != nil { _ = cmd.Process.Kill() } - if pkill := lookPath("pkill"); pkill != "" { - _ = exec.Command(pkill, "-f", "dsh/lib/bin.js").Run() - } + killDSHTree() } // buildChildEnv 手动把 homebrew 路径塞进子进程 PATH。 @@ -659,8 +653,81 @@ type InstallResult struct { Output string `json:"output"` } -// CheckUpdate runs `npm view version` to get the latest version on the registry. -// 不依赖 outdated — outdated 需要本地有 installed record,registry-only 查不到。 +// killDSHTree kills all processes related to the dsh service: +// - the dsh CLI itself (npm/node dsh/lib/bin.js) +// - dsh-pet Electron helper (main / renderer / gpu / utility / network) +// - any Electron app launched from ~/.dsh/electron +// +// 不只 kill 主进程: dsh-pet 还在跑时,EPIPE 会在 pipe 关闭后让它抛 +// "Uncaught Exception" 弹窗,污染退出 UX。 +func killDSHTree() { + pids := listDSHPids() + if len(pids) == 0 { + return + } + log.Printf("killDSHTree: %d pids to kill", len(pids)) + for _, pid := range pids { + // kill -9 强杀,避免 dsh-pet 还在做 graceful shutdown 又触发 EPIPE + if err := exec.Command("kill", "-9", pid).Run(); err != nil { + log.Printf("killDSHTree: kill %s: %v", pid, err) + } + } +} + +// listDSHPids 列出所有跟 dsh 相关的进程 PID(去重 + 排除自身)。 +// 用 ps + grep 而不是 pgrep,以兼容 macOS 默认 PATH 不带 /usr/bin 的情况。 +func listDSHPids() []string { + out, err := runQuiet("ps", "-axo", "pid=,command=") + if err != nil { + log.Printf("listDSHPids: ps err: %v", err) + return nil + } + seen := map[string]bool{} + var pids []string + self := os.Getpid() + for _, line := range strings.Split(out, "\n") { + line = strings.TrimSpace(line) + if line == "" { + continue + } + // 格式: "PID COMMAND..." + sp := strings.IndexByte(line, ' ') + if sp <= 0 { + continue + } + pidStr := line[:sp] + cmd := line[sp+1:] + // 跳过自身进程,免得自杀 + if pid, _ := strconv.Atoi(pidStr); pid == self { + continue + } + // 匹配规则: dsh 启动的所有相关进程 + if isDSHProcess(cmd) { + if !seen[pidStr] { + seen[pidStr] = true + pids = append(pids, pidStr) + } + } + } + return pids +} + +// isDSHProcess 判断一条 ps command 是不是 dsh 相关的进程。 +// 规则: +func isDSHProcess(cmd string) bool { + matches := []string{ + "dsh/lib/bin.js", // dsh CLI 主进程 + "dsh-pet", // dsh-pet Electron helper + ".dsh/electron", // dsh 内置的 Electron runtime + "dsh-pet-electron-helper", // dsh-pet user-data-dir + } + for _, m := range matches { + if strings.Contains(cmd, m) { + return true + } + } + return false +} func (a *App) CheckUpdate() UpdateInfo { installed := "" if p := lookPath("dsh"); p != "" { diff --git a/dsh-plugins-installed.md b/dsh-plugins-installed.md new file mode 100644 index 0000000..becf35f --- /dev/null +++ b/dsh-plugins-installed.md @@ -0,0 +1,157 @@ +# DSH 已装插件清单 + +> 统计时间:2026-09-11 +> 跳过安装:hindsight(用户要求) +> 卸载:dsh-deep-whale(用户决定不要) +> **合计:24 个插件**(web profile 23 + dsh-tui profile 1) + +--- + +## Web profile(23 个) + +DSH Web GUI 与命令行共用的主 profile。 + +### 基础(1 个) + +| # | 插件 | 版本 | 简介 | +|---|---|---|---| +| 1 | dshmarket | 1.46.0 | 插件市场,3000+ 社区插件、分类筛选、一键更新停用、主题切换与配置备份 | + +### 界面与终端(1 个,dsh-TUI 装在 tui profile) + +### 多智能体与上下文(2 个) + +| # | 插件 | 版本 | 简介 | +|---|---|---|---| +| 2 | @nanmicoder/dsh-agent-teams | 0.1.18 | 多智能体团队编排:分配任务、互发消息、共享状态 | +| 3 | dsh-context | 0.50.0 | 上下文占用可视化仪表盘 + `/context` 命令 | + +### 记忆系统(1 个) + +| # | 插件 | 版本 | 简介 | +|---|---|---|---| +| 4 | dsh-mnemon | 0.5.8 | 本地优先持久记忆、语义召回 + 知识图谱、跨 Agent 共享记忆 | + +### 视觉能力(3 个) + +| # | 插件 | 版本 | 简介 | +|---|---|---|---| +| 5 | @liustack/modlens | 3.16.6 | 图片转结构化 JSON(OCR、版面、语义) | +| 6 | @anionex/dsh-vision-toolkit | 0.1.44 | 免 Key 视觉工具箱:图片问答、多图比较、长截图 OCR、像素对比 | +| 7 | dsh-vision-router | 2.1.6 | 免 Key 视觉链:看图问答、视觉定位、裁剪、OCR、抠图 | + +### 搜索与文档(2 个) + +| # | 插件 | 版本 | 简介 | +|---|---|---|---| +| 8 | @liustack/modsearch | 5.10.2 | 联网搜索带引用:搜网页、搜 X、返回结构化 JSON 证据 | +| 9 | @tt-a1i/archify-dsh | 0.1.0 | 自动生成架构图、工作流图、时序图、数据流图、生命周期图 | + +### 开发辅助(4 个) + +| # | 插件 | 版本 | 简介 | +|---|---|---|---| +| 10 | @linxin666/dsh-web-all | 0.3.21 | Web UI 全家桶:任务看板、Git 泳道图、技能中心、远程移动端 UI | +| 11 | dsh-better-sidebar | 0.19.1 | 侧边栏工作台:文件渲染编辑、终端、Git 变更、子代理活动 | +| 12 | dsh-chat-import | 0.11.2 | 把 13 家 coding agent 的对话历史导入成可续聊的 DSH 会话 | +| 13 | dsh-cost-meter | 1.3.1 | 会话与当日 API 费用统计、预算进度、官方余额显示 | +| 14 | @yejiming/dsh-data-agent | 0.1.5 | 数据库连接、SQL 工具、共享数据分析预设(Web + TUI 都支持) | + +### 日常办公(8 个) + +| # | 插件 | 版本 | 简介 | +|---|---|---|---| +| 15 | dsh-univer-office | 0.2.14 | 表格/文档/幻灯片/画布/多维表格,多 Agent 协作工作树 | +| 16 | deepseek-ippt | 0.1.2 | 原生幻灯片编辑器 + 模板目录,Agent 可创建/编辑/导出 PPT | +| 17 | @ethanyoq/dsh-invoice-downloader | 0.1.1 | 本地 IMAP 邮箱发票下载 → OCR → 归档 → Excel 汇总 | +| 18 | @xmanrui/dsh-im | 4.20.2 | 接入 9 种聊天软件(飞书/微信/钉钉/企业微信/QQ/Slack/Telegram/Discord/WhatsApp) | +| 19 | dsh-plugin-reactive-resume | 0.1.0 | 基于 Reactive Resume 的简历制作与投递 | +| 20 | dsh-pocket | 2.10.6 | 手机远程访问 DSH Web,扫码即用,局域网/公网 | +| 21 | @zhaoolee/dsh-notes | 0.1.2 | 把 Agent 输出保存到自托管 Notes 服务 | +| 22 | @cloudbase/dsh-plugin | 0.1.1 | 腾讯云 CloudBase:数据库、存储、认证、部署 | + +### 外观与娱乐(1 个) + +| # | 插件 | 版本 | 简介 | +|---|---|---|---| +| 23 | dsh-pet | 0.2.8 | 25 个透明动画、屏幕漫游、点击反应、拖拽交互的桌面宠物(当前:400px、右下角、仅浏览器) | + +--- + +## DSH-TUI profile(1 个) + +| # | 插件 | 版本 | 简介 | +|---|---|---|---| +| 1 | @deepseek-harness-tui/dsh-tui | 0.10.1 | Claude Code 风格全屏终端:鲸鱼顶栏、实时工作状态、token 速度、键盘控制 | + +--- + +## 用户自定义配置 + +### 1. dsh-pet 用户配置 — `~/.dsh/dsh-pet/main-config.json` + +- 单只宠物,蓝毛小女仆,**size: 400px**,**display: 仅浏览器(web)** +- 位置:右下角,距右/下各 24px +- 启用碎碎念(whisper)、工作状态联动(workStatus)、余额展示 + +### 2. 插件 patch 层 — `~/.dsh/profiles/web/cordis.patch.yml` + +- 禁用了 `@linxin666/dsh-web-all` 内置的 `web-ui-pet` 子模块(避免和 dsh-pet 重复出现) + +--- + +## 跳过的插件 + +- **hindsight**(@vectorize-io/hindsight-coding-agents)—— 用户主动要求跳过 + +## 已卸载的插件 + +- **dsh-deep-whale**(鲸鱼娘皮肤)—— 用户决定不要,已完整卸载(package.json + node_modules 全清) + +--- + +## 复现安装的完整命令清单 + +```bash +# 基础必装 +dsh plugin --profile web add dshmarket + +# 界面与终端 +dsh plugin --profile dsh-tui add @deepseek-harness-tui/dsh-tui +dsh plugin --profile web add @linxin666/dsh-web-all@latest +dsh plugin --profile web add dsh-better-sidebar + +# 多智能体与上下文 +dsh plugin --profile web add @nanmicoder/dsh-agent-teams +dsh plugin --profile web add dsh-context + +# 记忆系统 +dsh plugin --profile web add dsh-mnemon + +# 视觉能力 +dsh plugin --profile web add @liustack/modlens@3.16.6 +dsh plugin --profile web add @anionex/dsh-vision-toolkit +npx @deepseek-ai/dsh plugin --profile web add dsh-vision-router + +# 搜索与文档 +npx -y @deepseek-ai/dsh plugin --profile web add @liustack/modsearch@latest +dsh plugin --profile web add @tt-a1i/archify-dsh@0.1.0 + +# 开发辅助 +dsh plugin --profile web add dsh-chat-import +dsh plugin --profile web add github:Han-1413141/dsh-cost-meter#v1.3.1 +dsh plugin --profile web add @yejiming/dsh-data-agent + +# 日常办公 +dsh plugin --profile web add dsh-univer-office +dsh plugin --profile web add deepseek-ippt +dsh plugin --profile web add @ethanyoq/dsh-invoice-downloader +dsh plugin --profile web add @xmanrui/dsh-im +dsh plugin --profile web add dsh-plugin-reactive-resume +dsh plugin --profile web add dsh-pocket -w +dsh plugin --profile web add @zhaoolee/dsh-notes +dsh plugin --profile web add @cloudbase/dsh-plugin + +# 外观与娱乐 +dsh plugin --profile web add dsh-pet +``` diff --git a/main.go b/main.go index 5b64281..5b1b1cb 100644 --- a/main.go +++ b/main.go @@ -27,6 +27,12 @@ func main() { u := app.CheckUpdate() fmt.Printf("installed=%q latest=%q hasUpdate=%v\n", u.Installed, u.Latest, u.HasUpdate) + fmt.Println("=== listDSHPids ===") + pids := listDSHPids() + fmt.Printf("found %d dsh-related pids:\n", len(pids)) + for _, p := range pids { + fmt.Printf(" - %s\n", p) + } fmt.Println("=== done, exiting ===") return }