环境搭建与第一次对话
本章导读:DeepSeek Harness(dsh)从零到全栈【1】认识 DeepSeek Harness:Agent、Harness 与“一切皆插件“ 回答了"DeepSeek Harness 是什么",本章回答"怎么让它跑起来"。你会走过两条启动路径,
npx一键启动与源码运行,把环境要求精确到补丁号,拿到并安放好你的 API Key,然后按官方 quickstart 完成第一次对话:配置模型、选择工作区、发出第一个提示词、观察权限审批。最后我们把 CLI 形态、SSH 远程场景与源码构建排错一并交代。学完本章,你将拥有一个可以日常使用的 dsh 环境,它也是后续所有章节的实验场。
摘要:本章带你从零跑通 DeepSeek Harness(dsh)。先对比
npx一键启动与源码构建两条路径的适用场景;再把环境要求精确到补丁号——Node 引擎下限^22.19.0 || >=24.0.0由node:sqlite、原生类型剥离与依赖链三重约束决定,并识破"Node 18 可用"的误区。随后完成 API Key 配置与 Web UI 首次对话全流程(配模型 → 选工作区 → 新建会话 → 发提示词),理解密钥只写地存放在$DSH_HOME/.credentials.yaml。最后概览web/headless等五种 profile 形态、SSH 远程访问与源码构建排错,并通过三个动手实验巩固所学。
文章目录
- 环境搭建与第一次对话
- 学习目标
- 正文
- 2.1 两条启动路径:买成品家具,还是进木工房
- 2.2 环境要求:版本精确到补丁号
- 2.3 获取并设置 API Key
- 2.4 Web UI 首次使用全流程
- 2.5 界面功能导览
- 2.6 CLI 形态概览:`dsh` 不止是一个聊天窗口
- 2.7 SSH 远程服务器场景
- 2.8 从源码构建排错
- 动手实验
- 实验一:环境自检(约 3 分钟)
- 实验二:Web UI 完整首跑(约 10 分钟)
- 实验三:headless 一发入魂(约 3 分钟)
- 常见坑
- 小结
- 参考资料
学习目标
- 能独立通过
npx @deepseek-ai/dsh web或源码路径(pnpm install→pnpm run build→pnpm dsh web)启动 Web UI,并说出两条路径各自适合的场景。 - 能解释
engines.node写作^22.19.0 || >=24.0.0的三重原因(node:sqlite、原生类型剥离、依赖下限),并识破网上"Node 18 可用"的错误说法。 - 能完成"配置 API Key → 选择工作区 → 新建会话 → 首次对话"全流程,并说出密钥的真实存放位置是
$DSH_HOME/.credentials.yaml,而不是 settings。 - 能说出
web与headless两个内置 profile(运行档位)各适合什么,并列出至少 3 个来自官方 CLI 参考的启动命令。 - 能在 SSH 远程服务器上启动 dsh,并用本地端口转发在浏览器里访问它。
正文
2.1 两条启动路径:买成品家具,还是进木工房
- dsh 有两种合法的启动方式。把它们想成买家具:一条是快递送来的成品(
npx拉取 npm 上已构建的包),开箱即用;另一条是自己进木工房(clone 源码自己构建),费事,但图纸和木料都归你改。
路径一:npx 一键启动(推荐大多数读者)
# 来自 README.zh.md「运行」节npx @deepseek-ai/dsh web- 这条命令会从 npm 拉取
@deepseek-ai/dsh包并启动 Web UI,默认监听http://127.0.0.1:3080。本机启动时它会用默认浏览器打开页面;通过 SSH 启动时只打印宿主机 URL,因为本地转发地址由 SSH 客户端或编辑器持有。如果你只想起服务器、不想让它自动开浏览器,传--no-open。
路径二:从源码运行(适合二开与跟读源码)
gitclone https://github.com/deepseek-ai/deepseek-harness.gitcddeepseek-harnesspnpminstallpnpmrun buildpnpmdsh web这里有一个初学者最容易误解的细节:
pnpm dsh web不会重新构建。按官方 README 的原话,"pnpm run build会准备仓库产物。pnpm dsh web会直接使用这些已构建产物,不会重新构建。"也就是说,build和dsh是两个独立步骤:前者把 monorepo 里的包和 Web 前端编译成产物;后者通过node --import tsx/esm直接运行apps/cli/src/bin.ts这个 TypeScript 入口,加载的却是磁盘上已存在的构建结果。作为对照,npm 安装版运行的是构建后的
apps/cli/lib/bin.js(apps/cli/package.json中的"bin": { "dsh": "lib/bin.js" }),同样不触发重新构建。补两个
npx的实用细节。其一,npx 第一次运行会下载并缓存包,之后的启动走缓存,不是每次都重新下载;想钉死版本以精确复现本教程的行为,可以显式带版本号:npx @deepseek-ai/dsh@0.1.2-alpha.1 web,npm 的版本号语法对所有包一致。其二,npx 拉取的是 npm 上的发布产物,行为以官方 release 为准;本系列以0.1.2-alpha.1为版本基准,而项目处于开发者预览期、迭代很快,若你读到本文时版本已更新,个别界面文案可能与描述有出入,一切以你手上版本的实际行为为准。怎么选?如果你只想体验产品、跟着本系列前四章学习使用,用
npx,它永远与官方发布的版本对齐。如果你打算改源码、写插件、或者像本系列后面章节那样"源码对照着学",走源码路径:改完代码跑一次pnpm run build,再用pnpm dsh验证。两者共用同一套$DSH_HOME配置与凭据,切换路径不需要重配任何东西。
2.2 环境要求:版本精确到补丁号
- 先看根目录
package.json的原文:
// 来自 package.json(版本基准 0.1.2-alpha.1) "packageManager": "pnpm@11.7.0", "engines": { "node": "^22.19.0 || >=24.0.0" }- 解释:
^22.19.0表示 22.19.0 及以上、但小于 23 的 22.x 版本;>=24.0.0表示 24 及更高。Node 23 整条线被排除在外,Node 18、20 更是连门都没有。
💡深挖:下限为什么偏偏是 22.19?仓库里的决策记录.agents/notes/implemented/process/2026-07-06-node-engine-floor.zh.md给出了完整推理,概括为三重约束:
node:sqlite内置模块。会话持久化与存储层直接使用 Node 内置的 SQLite 绑定(例如packages/storage/storage-sqlite/src/schema.ts顶层import { DatabaseSync } from 'node:sqlite')。该模块在 Node 22.13(LTS 线)才取消--experimental-sqlite标志要求,更早的版本在导入时就抛异常。- 原生 TypeScript 类型剥离。Node 从 22.18(LTS 线)起默认支持直接运行
.ts文件(剥离类型),更早需要--experimental-strip-types标志。 - 依赖链的下限。LLM 适配器
@deepseek-ai/dsh-llm-pi-ai依赖的@earendil-works/pi-ai自己声明engines.node >=22.19.0,把 22.x 线的门槛从 22.18 再抬到 22.19。
而 Node 23 之所以被排除:23.0–23.5 上上述源码特性仍需要标志,且 23 是已结束生命周期(EOL)的非 LTS 线,宣传它只会增加一条没人该用的运行时。这种"精确到补丁号"的引擎声明在开源项目里少见,但它是后续一切安装问题的第一现场。
pnpm 从哪来:源码路径需要 pnpm。仓库用packageManager字段钉死了pnpm@11.7.0,标准做法是用 Corepack(Node 官方的包管理器版本管理器,随 Node 22 附带)启用:
corepackenable# 之后在仓库目录里 pnpm 会自动切到 11.7.0- 若你的 Node 发行版没有附带 Corepack,
npm install -g corepack后再执行上面命令即可。
各操作系统差异:本系列主环境是 Windows(作者机器即 Windows + Git Bash),仓库的测试门禁覆盖 Linux 与 Windows(package.json里还能看到专门的 Windows 门禁与 Wine 方案),Python SDK 则官方支持 Linux x64/arm64、macOS 14+(arm64)与 Windows x64。macOS/Linux 上 shell 命令按 POSIX 习惯即可;Windows 上建议用 PowerShell 或 Git Bash,路径分隔符差异由 Node 自行处理。
命令层面三平台几乎一致,差异集中在环境变量的写法:Git Bash 用
export NAME=value,PowerShell 用$env:NAME="value",CMD 用set NAME=value——后文遇到需要设环境变量的地方会提醒你按所用的 shell 选择。安装 Node 本身:macOS 推荐用 Homebrew 装 LTS 线;Linux 发行版软件源里的 Node 往往过旧,用 nvm 或 NodeSource 管理;Windows 直接下官网安装器即可。还有一个值得说透的疑问:
engines只是一份声明,包管理器默认并不强制执行。npm 在版本不匹配时只给警告照常安装;pnpm 同样默认放行,除非开启engine-strict——仓库的下限决策记录里,正是用pnpm install --engine-strict来保证"宣传的 LTS 分支不低于依赖链的下限"。所以"装上了"不等于"支持":^22.19.0 || >=24.0.0是官方 CI 矩阵(22.19、24、26 三个版本)实测过的承诺边界,你硬要用 22.19 以下或 23.x 运行,没人拦你,但出了问题只能自己兜底。
Python SDK 的额外要求:如果你后续想从 Python 程序里嵌入 dsh(pip install deepseek-harness-sdk),需要 Python 3.10 或更高版本;SDK 的 wheel 打包了同一个dsh命令和原生运行时,普通 SDK 使用不需要系统安装 Node.js。详见docs/user/guide/python-sdk.zh.md,本篇不展开。
⚠️常见误区:"Node 18 可用"是错的。网上能搜到一些第三方教程(包括某云厂商的帮助文档)声称 Node 18 即可运行,这与仓库engines的声明直接矛盾。它们多半写于项目发布早期或干脆照抄了别的工具的要求。判据永远只有一个:你手头检出的仓库里package.json的engines字段。用 Node 18/20 实际运行时,报错往往不是"版本不支持",而是在node:sqlite导入或模块解析处抛出莫名其妙的异常——不直说版本问题,这正是它难排查的原因。
2.3 获取并设置 API Key
- dsh 出厂对接的是 DeepSeek 官方 API。到 platform.deepseek.com 注册并创建一个 API Key(形如
sk-...的字符串),充少量余额即可开始。 - 然后回到 Web UI:打开设置 → 模型,DeepSeek 卡片上有一个 API 密钥字段,粘贴并保存。官方 quickstart(
docs/user/guide/index.zh.md)的原话是:“模型路由会立即可用,不需要重启服务器。” 这是很多工具做不到的体验:你不用重启dsh web进程,下一次请求就走新模型。 - 密钥去了哪里?
docs/user/guide/providers.zh.md写得很明确:
密钥是只写的。保存后,页面只会收到脱敏描述符,永远不会收到明文密钥。密钥存储在
$DSH_HOME/.credentials.yaml中,settings 只保留它的凭据引用。
$DSH_HOME是 dsh 的家目录,默认是你用户目录下的.dsh(源码依据:packages/util/home-paths/src/index.ts中DSH_HOME_DIR_NAME = '.dsh',defaultDshHome()返回homedir() + '.dsh'),可用环境变量DSH_HOME改指别处。也就是说,Windows 上你的密钥在C:\Users\<你>\.dsh\.credentials.yaml。这个"凭据与配置分离"的设计意味着:备份或迁移settings.yaml时不会顺手把密钥带出去;想换机器,单独搬.credentials.yaml。
💡深挖:凭据的完整解析顺序。apps/cli/reference/README.zh.md的「共享部署行为」节规定,提供方凭据依次从四个位置解析:继承环境 →$DSH_HOME/.credentials.yaml→ 调用目录的.env→$DSH_HOME/.env。搜索类工具查找DEEPSEEK_API_KEY(另接受DEEPSEEK_SEARCH_BASE_URL)。这意味着除了在 Web UI 里粘贴,你还有两条等价的路:其一,环境变量,DeepSeek 模型适配器的默认凭据引用就是DEEPSEEK_API_KEY(packages/llm/llm-deepseek中DEFAULT_API_KEY_ENV = 'DEEPSEEK_API_KEY',README 的apiKeyEnv字段表同),export DEEPSEEK_API_KEY=sk-...之后 Web UI 里那一步都可以省掉;其二,在启动dsh的目录放一个普通.env文件写上DEEPSEEK_API_KEY=sk-...,适合"每个项目一把钥匙"的项目级隔离。三种方式的取舍:GUI 保存最省事且密钥只写;环境变量适合 CI 与一次性运行;调用目录.env适合多项目并行的隔离需求。另外注意文档的一个细节:“受管文档从不物化进process.env,而两个.env文件都是普通启动环境层”,.credentials.yaml里保存的密钥不会被注入子进程环境,凭据体系的边界是刻意的。
- 顺带交代隐私默认值:会话遥测默认按反馈门控,在你主动执行
/feedback之前,不会有任何数据上传;DSH_TELEMETRY_MODE=DISABLED则让全部数据留在本地。对要在公司环境里试用的读者,这是个值得记住的默认。
2.4 Web UI 首次使用全流程
- 现在把官方 quickstart(
docs/user/guide/index.zh.md)完整走一遍。以下五步是本章的主干,建议对照着做。
第一步:启动并进入页面。在你想当作工作目录的地方运行npx @deepseek-ai/dsh web。终端会打印一行dsh web:开头的 URL——注意这不是普通的http://127.0.0.1:3080,而是携带进程 token 的启动 URL。浏览器打开它后,会用这个 token 换取一张签名会话 cookie,然后重定向到干净的根 URL(这是 v0.1.2 引入的一次性 token 认证:token 只在启动时有效一次,页面凭证由 cookie 承担)。本机启动时浏览器自动打开;若操作系统交接失败,stderr 会打印不含凭据的诊断,服务器继续运行——你手动打开终端里那个带 token 的 URL 即可。
第二步:配置模型。就是上一节的内容:设置 → 模型 → 粘贴 DeepSeek API Key → 保存。保存后模型选择器里会出现 DeepSeek 的模型。
第三步:选择工作区——新手第一大卡点。官方指南原文:“点击选择工作区,添加启动dsh时所在的项目目录,然后选中它。选中工作区前,会话输入框不可用。”
为什么必须选?这里要把一个概念掰开:
dsh进程会把启动时所在的目录作为默认文件系统位置,但新打开的 Web UI 不会自动选中任何工作区,你要在界面里手动添加并选中一个。工作区(workspace)决定 agent 能看见、能读写的文件边界:它就是 agent 的"工位",工位之外的文件它既看不见也翻不动。所以"我的项目怎么找不到"这类问题,十有八九是工作区没选对,而不是 agent 笨。这也解释了一个新手常困惑的现象:你在哪个目录运行
npx @deepseek-ai/dsh web,那个目录就成了"默认文件系统位置"。所以最佳实践是把它当成项目的随行工具,cd进项目根目录再启动,Web UI 里添加的也就是同一个目录,两边天然对齐。工作区之后还可以随时换,但第一次选对能省掉很多"它怎么看不到这个文件"的来回。
⚠️常见误区:把父目录或 home 目录当工作区。如果你把D:\或用户主目录选成工作区,agent 每次列目录都会面对成千上万无关文件,既浪费上下文又危险。正确姿势:启动dsh前先cd到项目根目录,Web UI 里也选中同一个目录,两边对齐。
第四步:新建会话,发出第一个提示词。官方示例原话是:
Summarize this repository and identify its main packages.
把它发给 agent(智能体)。它会读取和编辑工作区文件、运行命令、委派工作并维护计划。官方指南同时预告了接下来会发生什么:"如果根据当前权限策略,某项操作需要审批,Web UI 会先询问你。"新会话默认使用workspace-write权限预设(docs/subsystems/permission-presets.zh.md:workspace-write预设 = 沙箱模式workspace-write+ 审批策略ask)——Bash 和文件系统修改被限制在会话工作区与平台临时根目录内,读取和网络访问不受限制。所以上面这条总结请求中的读取操作会直接执行;而一旦操作要越过权限策略,界面会弹出审批请求(approval),等你点批准或拒绝。
第五步:读回复。对话流里你会看到模型的回答、工具调用树(哪些文件被读了、哪些命令跑了)以及引用的文件链接。回复结束后,你可以继续追问,也可以换一个会话从头再来——每个会话都是独立持久化的,关掉浏览器甚至关掉服务器,会话都在。
- 两个值得在第一次对话时就养成的观察习惯。第一,看审批:读取类操作(读文件、列目录)在
workspace-write下不经过你;真正弹审批的,是要越过权限策略的操作。第一次亲眼见到审批弹窗很重要——后面所有关于权限的讨论,都建立在你见过这个界面的前提上。第二,看会话的持久性:官方 Python SDK 指南提到 home 下有sessions/目录存放未压缩的 JSONL 会话日志(docs/user/guide/python-sdk.zh.md),dsh 的会话以事件溯源方式逐条记录,Web UI 的会话历史也来自同一套持久化。你不需要现在就去读这些日志,但知道"每一个会话、每一次工具调用都落了盘",等第 06 章教排错时你会回来找它们。
2.5 界面功能导览
首次对话走通后,快速认一遍界面。以下每一项都能在packages/client/README.zh.md的包表中找到对应实现,不是凭印象描述:
- 侧边栏(
ui-sidebar):工作区与会话导航,会话历史按会话列出。 - 工作区选择器(
ui-workspace):选择与创建工作区,即 2.4 第三步用的那个入口。 - 对话区与输入框(
ui-conversation):输入框支持@file/@session引用(ui-reference),可以把具体文件或历史会话挂进提示词。 - 模型选择器(
ui-model-selection):已配置的提供方出现在这里;按providers.zh.md的说法,“选择模型也会将其设为新会话的默认值”,已发送过请求的会话保留自己日志中记录的模型。 - 权限预设选择器(
ui-permission-presets):在workspace-write与danger-full-access(沙箱danger-full-access+ 审批never)之间切换当前会话的访问模式;后者完全放权,新手不建议日常使用。 - 计划模式指示(
ui-plan):显示 plan mode(计划模式)状态与退出控件——开了它,agent 会先给计划再动手,复杂任务前很有用(第 04 章细讲)。 - 审批弹窗(
ui-approval):需要你决策的权限请求在这里出现。 - 目标与后台任务(
ui-goal/ui-jobs):跨轮目标与当前会话的后台任务状态。 - 产出物引用(
ui-deliverables):一轮结束生了哪些文件,轮次尾部会生成可点击的文件引用,不用自己去目录里翻。 - 轨迹视图(
ui-trajectory):agent 活动的其他观察角度,想回看它每一步做了什么时有用。 - 设置页(
ui-settings及其分区):模型(ui-settings-models,就是配 Key 的地方)、常规(ui-settings-general)、插件(ui-settings-plugins)等。
两个 v0.1.2 相关的细节值得单独说。其一是界面语言:在"设置 → 常规"中从已注册语言里选择,UI 文案立即切换;内置zh与en两种(packages/client/locale/README.zh.md),选择在 loopback 页面上以locale.preference存进$DSH_HOME/settings.yaml。其二是一次性 token 认证,2.4 第一步已经说过。
💡深挖:agent 也能"看见"这个网页。packages/bundle/web-app/README.zh.md记录了一个默认开启的配置项surfaceContext:它给 agent 提供当前 GUI 的定位上下文(规范本地 URL、“this page” 指代什么),并把DSH_WEB_URL环境变量暴露给它的 shell 命令。这意味着你可以直接对 agent 说"打开你自己的页面看看",它知道自己在为哪个界面服务。这类"自我指涉"的能力来自 patch 层里的一行配置——它长什么样、为什么能这样写。
2.6 CLI 形态概览:dsh不止是一个聊天窗口
- Web UI 只是 dsh 五种内置运行形态之一。dsh 的启动器(launcher)用 profile 来区分形态——profile(运行档位)是一组按顺序叠加的插件组合包配置层,这个概念第 03 章正式定义,这里先记住"一个 profile 一种形态"即可。下表完整取自
apps/cli/README.zh.md的「入口模式」表:
| 命令 | 用途 |
|---|---|
dsh --profile <name> | 启动位于$DSH_HOME/profiles/<name>的指定 profile。 |
dsh --profile acp | 通过 ACP stdio 为自动化 client 提供服务,直至断开连接。 |
dsh --profile headless "job" | 运行一个全新的持久化会话,打印最终答案并退出。 |
dsh --profile sdk | 通过 JSON-RPC stdio 为 SDK client 提供服务,直至关闭或断开连接。 |
dsh --profile sdk-minimal | 以独立极简 agent 配置树为 SDK client 提供服务。 |
dsh web | --profile web的别名。 |
dsh plugin --profile <name> <pnpm args> | 通过在 profile 目录中转发给 pnpm 来管理该 profile 的插件。 |
日常最常用的是两个极端:
web给人看(浏览器里的交互式 GUI),headless给程序用(headless,无头模式,没有界面,标准输入输出就是它的脸)。headless的行为契约在apps/cli/reference/README.zh.md里规定得非常细,几条最值得记住:stdout 只打印最终文本;提供方的推理分片(reasoning delta)以dsh: reasoning:为标题流式写入 stderr;任务原因为completed时以 0 退出,否则以 1 退出;没有任务的调用是该应用的用法错误。此外随附的 headless profile 不挂载浏览器连接、HTTP 服务器与 Web 运行时,不打开任何监听端口,它就是一个纯 stdio 的一次性执行器。这份契约让dsh --profile headless "..."可以直接嵌进 shell 脚本和 CI 里用:退出码判断成败,stdout 拿结果,stderr 看过程。
注意五个内置 profile 里没有终端交互式聊天形态——想要 TUI(终端用户界面),社区的
turtle-ui是现成例子(来自官方参考的示例):dsh plugin --profile tui add github:deepseek-harness/turtle-ui,装完dsh --profile tui启动。启动器自身的 flag(必须写在应用参数之前,遇到第一个无法识别的 token 就停手,其后全部交给 profile 里的应用去解析):
| flag | 行为 |
|---|---|
--profile <name> | 选择要启动的 profile。 |
--patch <path> | 追加 patch 覆盖层,可重复:--patch a.yml --patch b.yml。 |
--dump-config | 不启动,打印组合后的完整配置树。 |
--dump-default-config | 只打印组合包各层,不含用户层与--patch。 |
-V/--version | 打印启动器版本。 |
--help | 打印启动器帮助。 |
- "参数边界"值得用一个官方示例说透(来自
apps/cli/README.zh.md):
# 来自 apps/cli/README.zh.md「应用参数」节dsh--profileweb--port8080# --port belongs to the web appdsh--profileheadless"run the tests"dsh--profileweb--help# the web app's flags, not the launcher'sdsh--help# the launcher's own help同一个
--help,跟在--profile web后面打印的就是 web 应用自己的参数帮助,裸写才是启动器的帮助——启动器解析到--profile web之后的第一个陌生 token 就收手,把控制权连同剩余参数整个交给 profile。这条规则解释了 dsh 命令行的一个独特观感:不同 profile 有各自完全不同的第二段参数表,因为那段参数本来就属于注入该 profile 的应用插件,而不是启动器。--dump-config与--dump-default-config互斥(args.ts里对同时给出两者的情况直接报错 “mutually exclusive”),且 dump 模式不接受应用参数。这对组合是检查"我的配置到底长什么样"的官方入口——第 03 章的动手实验就会用它打印可被 patch 的组装树。web 应用的参数:
--host、--port、可重复的--trusted-host、--no-open。flag 优先于配置行——源码里 web 端口的配置行写作port: !!js ctx.webStartup.port ?? 3080,flag 填充了表达式前面的那个值。两个边界要记住:dsh web之后的 flag 属于 web 应用而不是启动器;CLI有意不支持--host 0.0.0.0,传了会以用法错误退出(要不要对局域网开放是安全决策,不能一行 flag 顺手带走,见 2.7)。插件管理:
dsh plugin --profile <name> add <package>在 profile 目录里转发给 pnpm 安装插件,remove/why/update等 pnpm 子命令同样可用(前提:pnpm 在 PATH 上)。这是 dsh"一切皆插件"在操作层面的样子——第 01 章的口号,落地成一个安装命令。
2.7 SSH 远程服务器场景
- 在一台没有桌面的远程服务器上跑 dsh、用自己的笔记本浏览器访问,是本工具设计里明确支持的场景。机制如下(依据
apps/cli/reference/README.zh.md):- 当继承环境中
SSH_CONNECTION或SSH_TTY非空时,dsh跳过浏览器交接,因为"本地转发地址由 SSH 客户端或编辑器持有;宿主机 URL 仍会打印"。这与 README 的说法一致:SSH 下只打印宿主机 URL。("编辑器"指的正是 VS Code Remote-SSH 这类场景——编辑器内置的端口转发代替了你手动建隧道,dsh 不区分这两者,一律不抢着开浏览器。) - 所以流程是:服务器上启动
dsh web(加不加--no-open均可,反正 SSH 下本来就不会尝试开浏览器),复制打印出的带 token 的 URL;本地终端建一条端口转发,再在本地浏览器打开同一地址:
- 当继承环境中
# 示例代码:本地终端执行,把服务器的 3080 转发到本机 3080ssh-L3080:127.0.0.1:3080 user@your-server# 然后本地浏览器打开 http://127.0.0.1:3080(或服务器打印的带 token URL)一个安全细节:带 token 的启动 URL 在转发后的本地浏览器里打开完全没问题,因为转发的就是127.0.0.1上的这条 TCP 链路,token 只在你的机器与服务器之间走了一趟。但如果 URL 里的 token 被泄露给同网段的其他人,对方就能用 token 换到合法 cookie——所以别把启动 URL 贴进群聊或 issue,这是它"一次性"设计想压缩的风险窗口。
- 默认情况下 GUI 只接受本机连接。如果你的拓扑需要局域网内其他机器访问,CLI flag 不允许
0.0.0.0,需要通过配置绑定其他网络接口,并用可重复的--trusted-host把访问用的主机名加入/api浏览器信任围栏。Host 与 Origin 检查控制可达性,token 交换认证每个 Host API 方法与 WebSocket stream——两道门缺一不可。细节见packages/bundle/web-app/README.zh.md。
2.8 从源码构建排错
- 源码路径偶尔会卡住,这里按"症状 → 根因"列全官方文档可证的部分,外加两条实践中反复出现的经验。
症状一:pnpm dsh启动即报模块解析错误,且错误里没有任何构建指引。这是官方文档点名的行为:“Typert Host 产物缺失时,profile 启动会因不含构建指引的模块解析错误而失败。”(apps/cli/reference/README.zh.md「源码执行」节。)根因是没跑pnpm run build,或构建中途失败。解法:在仓库根目录重新pnpm run build。
症状二:改了前端代码但页面行为没变。官方原话:“启动器不会检查产物是否为最新,因此已有的陈旧组合包可能继续运行旧版浏览器代码,直至重新构建。”pnpm dsh只管加载、不负责新鲜度,改完代码记得重新构建。
症状三:构建中途内存溢出。根package.json的build:lib:host脚本显式带着--max-old-space-size=4096——官方自己就把 TypeScript 构建的堆上限提到 4 GB,说明这是真实瓶颈。机器内存紧张时,关掉大程序再构建,或给 Node 调更大的堆。
症状四:pnpm 版本不对。仓库用packageManager: "pnpm@11.7.0"钉死版本。别用全局随手装的旧版 pnpm;corepack enable之后在仓库目录里执行pnpm -v确认它自动切到了 11.7.0——Corepack 读到的正是packageManager字段,在仓库目录内外可能给出不同版本,这是特性不是故障。若输出的版本号对不上,多半是 shell 里还缓存着别的 pnpm(which pnpm看看它从哪来)。
两条经验型建议(非官方文档明文,属社区与笔者实践):其一,仓库路径尽量用纯英文且不含空格——monorepo 深层依赖路径加上非 ASCII 字符或空格,是 Windows 上工具链出幺蛾子的高发组合;其二,Windows 默认 260 字符路径上限可能被pnpm install的深层 node_modules 打穿,开启系统长路径支持或把仓库放到短路径下(如D:\work\dsh)。
最后一条与网络有关:企业代理环境下,pnpm install认HTTP_PROXY/HTTPS_PROXY;而运行 dsh 源码进程时,官方要求"当支持环境代理的 Node 版本必须遵循HTTP_PROXY和HTTPS_PROXY时,请设置NODE_USE_ENV_PROXY=1"——Node 不会默认为内部请求启用环境代理,这个开关是官方给出的答案。
动手实验
实验一:环境自检(约 3 分钟)
node-v# 期望:v22.19.0 ~ v22.x,或 v24.x+;出现 v18/v20 则先升级npx @deepseek-ai/dsh-V# 期望:打印形如 0.1.2-alpha.1 的版本号(以你安装的为准)- 第一条对不上
^22.19.0 || >=24.0.0就不要往下走。第二条验证 npm 包可以被拉取、启动器可执行。想看启动器完整帮助,把-V换成--help。
实验二:Web UI 完整首跑(约 10 分钟)
# 示例代码:准备实验目录并启动mkdirdsh-lab&&cddsh-labecho"dsh tutorial says hi">notes.txt npx @deepseek-ai/dsh web- 浏览器自动打开带 token 的启动 URL,落地到干净的根页面;
- 设置 → 模型,粘贴 DeepSeek API Key,保存——不重启服务器;
- 选择工作区,添加并选中刚才的
dsh-lab目录,确认输入框从不可用变为可输入; - 新建会话,发送官方示例提示词
Summarize this repository and identify its main packages.,观察它的工具调用与回答; - 再发一条
请读取 notes.txt,告诉我里面写了什么。——让它读一个你自己准备的本地文件,确认工作区打通; - (可选)发一条
在工作区创建 hello.txt,内容为今天日期。,如果权限策略要求审批,观察弹窗的样式与选项,然后批准,回工作区目录确认文件真的出现了。
实验三:headless 一发入魂(约 3 分钟)
npx @deepseek-ai/dsh--profileheadless"用一句话解释什么是 agent harness"echo$?# Git Bash 查看退出码;completed 时应为 0预期:stdout 只有最终的一句回答(没有过程日志),推理分片若有则出现在 stderr 且带dsh: reasoning:前缀,退出码为 0。同目录下你会得到一个已持久化的会话——它和 Web UI 的会话存在同一个$DSH_HOME里。
常见坑
| 症状 | 原因 | 解法 |
|---|---|---|
启动报端口错误,或浏览器打不开127.0.0.1:3080 | 3080 被其他进程占用 | dsh web --port 8080换端口(flag 优先于配置行),或结束占用进程 |
| 浏览器没有自动打开 | SSH 会话自动跳过交接;或 OS 交接失败 | 复制终端打印的带 token URL 手动打开;本机想彻底禁用自动打开用--no-open |
| 会话输入框灰置、无法输入 | 新打开的 Web UI 不会自动选中工作区 | 点击"选择工作区",添加并选中启动dsh的目录 |
| agent 说"找不到我的项目/文件" | 选错了工作区目录(选成父目录或别的目录) | 重新选择正确的项目根目录;工作区决定 agent 的文件可见与修改范围 |
| 安装或运行时在奇怪的位置报错 | 第三方文档误导装了 Node 18/20(如某云厂商帮助文档称 Node 18 可用,与engines矛盾) | 以仓库package.json的engines为准:^22.19.0 || >=24.0.0,用 nvm 等工具切版本 |
| 源码启动报"不含构建指引的模块解析错误" | 没跑pnpm run build或构建失败 | 仓库根目录重新pnpm run build |
| 改了代码但行为没变 | 启动器不检查产物新鲜度,继续用旧产物 | 重新pnpm run build后再pnpm dsh |
小结
- 两条启动路径:
npx @deepseek-ai/dsh web拉取 npm 成品开箱即用;源码路径pnpm install→pnpm run build→pnpm dsh web,其中pnpm dsh通过node --import tsx/esm运行apps/cli/src/bin.ts,直接使用已构建产物、不重新构建。 - Node 引擎下限
^22.19.0 || >=24.0.0由三重约束决定:node:sqlite需 22.13+、原生类型剥离需 22.18+、依赖pi-ai宣称>=22.19.0;Node 23 整线被有意排除。"Node 18 可用"的说法与engines矛盾,是错的。 - API Key 在 Web UI 的"设置 → 模型"里粘贴保存后立即生效,无需重启服务器;密钥是只写的,实际存放在
$DSH_HOME/.credentials.yaml(默认~/.dsh下),settings 只保留凭据引用。 - Web UI 首跑四步:配模型 → 选择工作区 → 新建会话 → 发提示词;未选中工作区前输入框不可用,这是新手第一大卡点。
- 新会话默认
workspace-write权限预设:Bash 与文件修改限制在工作区与临时目录内,读取与网络不受限,越界操作会触发审批弹窗。 - 五个内置 profile 中,
web给人用、headless给程序用:stdout 只出最终文本、reasoning 走 stderr、completed退出码 0;启动器 flag(--profile/--patch/--dump-*)必须写在应用参数之前。 - SSH 下 dsh 跳过浏览器交接、只打印宿主机 URL,本地转发交给 SSH 客户端;CLI 有意不支持
--host 0.0.0.0,局域网访问走配置加--trusted-host。
参考资料
官方文档(仓库路径):
README.zh.md——运行节:npx 与源码两条路径的官方命令docs/user/guide/index.zh.md——Web UI 使用指南(本章 quickstart 主干)docs/user/guide/providers.zh.md——配置模型:密钥存放位置与只写语义(深入配置留待第 05 章)docs/user/guide/python-sdk.zh.md——Python SDK 前置条件apps/cli/README.zh.md——CLI 入口模式与应用参数边界apps/cli/reference/README.zh.md——CLI 行为参考:flag、headless 契约、凭据解析顺序、SSH 行为packages/bundle/web-app/README.zh.md——web 应用配置:token 认证、trusted-host、surfaceContextdocs/subsystems/permission-presets.zh.md——权限预设的组合语义.agents/notes/implemented/process/2026-07-06-node-engine-floor.zh.md——Node 引擎下限的完整决策记录
源码:
package.json——engines、packageManager、dshscript、build:lib:host内存参数apps/cli/package.json——bin: { "dsh": "lib/bin.js" }apps/cli/src/args.ts——启动器 flag 解析与--dump-*互斥校验packages/util/home-paths/src/index.ts——$DSH_HOME默认为~/.dshpackages/storage/storage-sqlite/src/schema.ts——node:sqlite的真实使用packages/client/locale/README.zh.md——界面语言切换packages/client/README.zh.md——Web UI 各功能与包的对应表
外部资料:
- DeepSeek 开放平台(获取 API Key):https://platform.deepseek.com/
- Node.js 官网(下载 LTS):https://nodejs.org/
- Corepack(Node 包管理器版本管理):https://github.com/nodejs/corepack
- 仓库与文档站:https://github.com/deepseek-ai/deepseek-harness / https://deepseek-harness.github.io/deepseek-harness/