--- name: dsh-desktop-dev-shell description: 在 Windows 本机把**官方 dsh 跑起来**并完成端到端取证 —— 覆盖两个形态:① **web 实例**(`apps/cli` 的 `dsh` CLI,起实例 / 查回环监听 / token+cookie jar 取 200 / 探测 Windows 沙箱后端)② **桌面壳**(`apps/desktop`,需源码构建,且能挂自研 client 插件做真跑验证,含**离线可用**验证:黑洞服务造"挂起"、有界超时、如实状态)。⚠️ **作用域 = 本机源码/开发态**(与 `dsh-instance-diagnose` 的边界:**服务器上的用户实例**属对方)。当出现「**本机** dsh 实例起不来」「壳起不来」「plugin tree failed to load」「atomic-write: timed out waiting for the writer lock」「curl 实例页 404 / 401 / 303」「怀疑实例内工具被整体拒绝 / refusing to run the command unconfined」「Electron 一出窗口就 FATAL」「pnpm 卡在 added 一百多不动」「pnpm run 每次都重装」「dev:desktop 起不来」「装了插件但界面没变化」「启动器日志停在 `[devkit] release=` 不动」、**或「跑起来不是登录注册页 / 跟服务器版本不像一套」**时使用;也用于判断"壳能不能离线用 / 启动链有没有网络依赖",在有 safe-delete 护栏的机器上删除 `node_modules` 这类巨型树,或需要绕开 pnpm 直接构建这个 monorepo 的场景。 version: 1.0.0 updated_at: 2026-09-22 last_change: 【2026-09-22 按要求统一版本号】frontmatter `version` → `1.0.0`(原 v2.0.1);正文与历史中的版本号为当时记录,未改动。此前 v2.0.1(2026-09-22):触发词加「**本机**」限定并写明与 `dsh-instance-diagnose` 的作用域边界 —— 实测「实例起不来」原本会撞(两技能都像命中,且偏向本技能),用户其实指服务器实例时不该加载这里。此前 v2.0.0(2026-09-21):并入原 `dsh-windows-run-web-instance`(该技能已随之删除,其内容现为 §11)—— 两形态共用一条判据链(Node 22 / 监听≠就绪 / token 栅栏 / 陈旧写锁)。 agent_created: true --- # dsh-desktop-dev-shell — 在 Windows 上跑起官方 dsh 并做真跑验证 ## 0. 为什么存在 `@deepseek-ai/dsh` 的**运行时**可以 `npx` 直跑,但**桌面壳**(`apps/desktop` + `apps/desktop-host`)是 `private: true`、未发布 npm ⇒ **只能克隆源码构建**。这条路在 Windows 上有一串**看起来像"方案不行"、其实是环境**的坑,逐条定位一次要花几小时。本技能把这些坑与处置固化下来(含 **web 实例**形态,见 §10)。 ## 1. 两种形态:先选对入口 官方 dsh 在 Windows 本机有**两种跑法**,本技能两条都管。**先判定你要哪一种**,再跳到对应章节: | 形态 | 入口 | 有没有 HTTP 端口 | 适用章节 | |---|---|---|---| | **web 实例** | `apps/cli` 的 `dsh --profile web` | ✅ 有(回环 + token 栅栏) | **§11** | | **桌面壳** | `apps/desktop`(源码构建) | ❌ **不开监听端口**(Electron 管道 + `dsh-app://`) | §2–§10 | **两形态共用的判据链**(无论走哪条都先过一遍): 1. **必须 managed Node 22** —— 原生模块按 Node 22 编译,用 24 会 `ERR_DLOPEN_FAILED`。 2. **监听 ≠ 就绪** —— 插件层晚于端口挂载,起听后立刻访问会拿到 404 / 拒连 ⇒ **轮询到成功响应**再判。 3. **陈旧写锁会让实例"永久起不来"** —— 实例被中途掐断留下 `/.credentials.yaml.lock` ⇒ 之后同一 `DSH_HOME` 每次启动都 `exit 1`(报 `plugin tree failed to load` + `atomic-write: timed out waiting for the writer lock`)。 ✅ 处置 = **换全新 `DSH_HOME`**(立刻恢复)或启动前清掉该 `.lock`。**别改官方源码。** ⇒ **探针 / 守护脚本一律「每次全新 DSH_HOME + 就绪前不掐断」**。 4. **`ELECTRON_RUN_AS_NODE=1` 可能就在环境里** ⇒ 不删掉的话 Electron 退化成纯 Node、**不出 GUI**(像"壳起不来")。 > ⚠️ 形态选错的典型症状:**去 `curl` 桌面壳的 HTTP 端口找页面** —— 壳压根不开端口(见 §2 第 1 条)。 ## 2. 桌面壳:三条必须先知道的判断 1. **壳不开监听端口。** 官方 README 原文:"**It opens no listening port**" ⇒ Electron 主进程与内置 Node 子进程走**带版本的定长帧管道**,渲染层用 **`dsh-app://`** 取资源。 ⇒ ⛔ 别去 `curl` 壳的 HTTP 端口找页面(没有);要验渲染层请用 **`--remote-debugging-port`**。 2. **开发态里 `DSH_DESKTOP_DEV_PROJECT_DIR` 直接充当 profile。** `main.ts` 里 `activeProject = development ?? paths.profile` ⇒ **跳过** project-manager 的 staging / healthCheck / activate 事务。 ⇒ 想挂插件,**自己造 profile 目录**,别指望走事务。 3. ⛔ **别用 `grep -nE "https?://" <源码>` 判"有没有网络依赖"** —— 它会**结构性漏掉** `electron-updater`: 它的 feed 地址在**打包产物**的 `app-update.yml` 里,**源码里一个 URL 字面量都没有**(本项目因此错下过一次结论)。 ✅ 正确口径(本线实测,逐处读码得来):壳的启动链只有**三类**碰网 —— ① `main.ts` 启动后 **10 s** 自动 `checkAndPrompt(false)` → `electron-updater`:**三重可容忍** (`enabled()` 要求 `app.isPackaged` **且** `resourcesPath/app-update.yml` 存在 ⇒ 开发态/未配 publish 的包**零网络**; 用 `void` 发起**不阻塞**窗口;全程 try/catch + `manual=false` ⇒ **静默失败不弹窗**) ② `project-manager.ts` 的 `DESKTOP_REGISTRY='https://registry.npmjs.org/'` ⇒ 只在**装/更新插件**时(离线装不了插件) ③ `host-process.ts` 的 `async fetch(request)` 是**进程内 fd 转发**(壳↔host),⛔ 不走网络 ⇒ 结论:**"启动 → 出窗口 → 实例页"这一段不依赖网络**,可以做成离线可用。 ⚠️ 但**装成正式包并配了 `app-update.yml` 之后**,10 s 那次检查会真的发请求 ⇒ 届时应复测。 4. **`dev.ts` 每次启动都重写 profile 的 `package.json`。** 所以手工挂进去的插件**会被抹掉**。 ⇒ 要么每次重新挂,要么**别用 `dev.ts`**(见 §4)。 ## 3. 依赖安装:pnpm 在本机"卡在 added ~100"的真根因 **症状**:`pnpm run <任意脚本>` 都先跑一遍安装,然后**卡在 `added ~104` 十几分钟不动**(`downloaded 0`,包全在 store 里)。 **根因(三条叠加,缺一不可)**: 1. **pnpm 的 before-run 安装钩子默认开启** ⇒ 每次 `pnpm run` 都先做依赖校验,判定"需装"就真的装。 2. 该次安装要**按虚拟 store 重建已有的 `node_modules`**;若 `.pnpm` 是上一轮**失败安装留下的半成品**,这一步在本机退化成**每包数秒**。 ⚠️ **不在网络、不在磁盘、不在 pnpm 版本**(11.7 与 11.21 一样中招)—— 别在这三个方向浪费时间。 3. 若上一轮用过**非默认 `virtual-store-dir`**,顶层符号链接会全指向那棵树,并留下 `.pnpm-workspace-state-v1.json` 记录该配置 ⇒ 任何**不带该配置**的 `pnpm run` 都被判"配置变了要重建" ⇒ **死循环**。 🔴 附带后果:官方 `dev.ts` 的 `dependencyDir` **写死 `node_modules/.pnpm/node_modules`** ⇒ 非默认虚拟 store 会让官方 dev 流程**直接失效**。 **处置(三步)**: ```bash # ① 把损坏/陈旧的树【改名移走】——⛔ 不是删除 mv node_modules/.pnpm <外部目录>/pnpm-old mv node_modules/.pnpm-new <外部目录>/pnpm-new mv node_modules <外部目录>/node_modules-old # ② 空目录上做默认布局安装 pnpm install --frozen-lockfile --config.minimumReleaseAge=0 # ③ 此后所有 pnpm 调用都带这一条(⭐ 能透传到嵌套 pnpm run,无需 shim) pnpm --config.verify-deps-before-run=false run