第二章:环境与运行
本章目标:帮助你理解 dsh 的开发环境配置、CLI 启动机制、Profile 系统的完整工作原理,以及如何从源码运行和调试 dsh。阅读本章后,你应该能成功运行
dsh --profile headless "hello"并理解背后发生的每一步。
2.1 开发环境配置
2.1.1 前置条件
| 工具 | 版本要求 | 用途 |
|---|---|---|
| Node.js | ^22.19 或 >=24 | 运行时,利用原生 TypeScript 支持 |
| pnpm | 最新版 | 包管理,支持 workspace 协议 |
| Git | 任意现代版本 | 版本控制 |
2.1.2 安装与构建
2.1.3 关键环境变量
| 变量 | 必需? | 说明 |
|---|---|---|
DEEPSEEK_API_KEY | 是 | DeepSeek API 密钥,用于 LLM 调用 |
DEEPSEEK_BASE_URL | 否 | 自定义 API 端点 |
DSH_HOME | 否 | Harness home 目录(默认 ~/.dsh) |
DSH_TELEMETRY_DISABLED | 否 | 任意非空值禁用遥测 |
2.1.4 .env 文件
dsh 支持根目录 .env 文件,真实 API 测试和演示从这里读取凭证。永远不要提交凭证到版本控制。
2.2 CLI 启动入口
2.2.1 启动入口文件
所有支持的 Node 应用程序都从 dsh CLI 启动。唯一的入口文件是:
这个文件导出的核心函数是 runProfile(),它是所有 Profile 启动的统一入口。
2.2.2 runProfile 的工作流
2.2.3 runProfile 选项
2.2.4 信号处理
SIGTERM 是监控器的普通停止请求,在每个表面上都以 0 退出;SIGINT 是用户中断,报告 130。
2.3 Profile 系统
2.3.1 Profile 的概念
Profile 是一个命名的插件组合,存储在 Harness home($DSH_HOME)中。它包含:
Bundle 列表:定义堆叠顺序
用户 Patch 层:
cordis.patch.ymlOut-of-tree 插件:用户安装的外部插件
2.3.2 预定义 Profile
| Profile | 用途 | Bundle |
|---|---|---|
web | 浏览器应用 | dsh-base + dsh-web-app |
headless | 单次任务执行,无服务器 | dsh-base + dsh-headless |
sdk | JSON-RPC SDK 服务器 | dsh-base + dsh-sdk-app |
sdk-minimal | 精简 SDK(不使用 dsh-base) | 独立 dsh-sdk-minimal |
acp | 自动化 Agent Client Protocol | dsh-base + dsh-acp-app |
2.3.3 dsh-base:共享基础层
dsh-base 是 web、headless、sdk、acp 共享的第一层,包含:
模型适配器
工具注册
持久化
沙箱和审批策略
设置
凭证
遥测
2.3.4 Patch 层叠加顺序
重要:Home 级 Patch 覆盖 Profile 级,因为机器本地偏好应该覆盖单个 Profile 的设置。
2.3.5 Profile 的源码实现
Profile 加载的核心在 packages/boot/app-boot/src/profile.ts 的 loadProfile() 函数:
2.3.6 自定义 Profile
然后运行:
2.4 从源码运行
2.4.1 直接运行(推荐)
这使用 node --import tsx/esm 来运行 TypeScript 源码,无需先构建。
2.4.2 Headless 模式
Headless 模式是一次性任务执行:
其启动插件(packages/bundle/headless/src/startup.ts)的工作原理:
它将任务文本作为 Cordis 服务提供,然后 runner 插件从服务中读取并执行。
2.4.3 SDK 模式
启动 JSON-RPC 服务器,TypeScript SDK 可以连接并发送请求。
2.4.4 PTC 模式(Python Tool Calling)
2.5 构建系统
2.5.1 TypeScript 项目布局
dsh 使用双平面布局:
Source 平面:
src/中的 TypeScript 源码,通过 tsconfigpaths解析Artifact 平面:
lib/中的构建输出(lib/types/为类型声明,lib/为运行时)
2.5.2 常用命令
2.5.3 ESM 约束
所有包使用纯 ESM("type": "module")。关键约束:
跨包使用包名导入
本地相对导入使用
.ts扩展名dshCLI 源码启动通过 tsx 的 ESM-only hook(node --import tsx/esm)所有到达的模块必须保持 ESM(不能有 CJS-only 导出)
2.6 调试与诊断
2.6.1 查看配置
打印出的每一行都可以被你的 Patch 替换。
2.6.2 查看事件映射
2.6.3 查看模块依赖图
生成的文件在 docs/module-graph.md。
2.7 测试
2.7.1 测试类型
| 命令 | 用途 | 需要 API Key? |
|---|---|---|
pnpm run test | 单元测试 | 否 |
pnpm run test:coverage | CI 覆盖率门控 | 否 |
pnpm run test:e2e | 真实 API 测试 | 是 |
pnpm run test:expected | 进程预期测试 | 否 |
pnpm run test:snapshot | 快照回放测试 | 否 |
pnpm run test:snapshot:record | 重录快照 | 是 |
2.7.2 快照测试
快照测试是 dsh 中最重要的测试类型之一。它通过键控无密钥录制-会话回放来验证行为:
2.7.3 Host 沙箱失败
如果需要 gh、pnpm、构建、测试或生成器命令因为沙箱阻断凭证、网络、IPC、监视或嵌套 sandbox-exec 而失败,应使用最窄的主机升级重试。永远不要绕过测试失败。
2.8 常见问题
Q: 为什么 dsh 不是一个独立的可执行文件?
因为 dsh 的设计理念是所有功能都是插件。CLI 只是一个启动器,它从配置文件加载插件树。你通过修改配置来改变行为,而不是通过编译不同的可执行文件。
Q: 如何添加一个新的 Profile?
在
$DSH_HOME/profiles/下创建目录添加
package.json,在dsh.profile.bundles中列出需要的 Bundle添加
cordis.patch.yml进行自定义配置运行
dsh --profile <name>
Q: 为什么使用 Vendored Cordis?
审计:框架代码和产品代码在同一个 monorepo
修补:可以直接修复框架 bug
Scope 重命名:避免在 npm 上抢占上游包名
锁定:确保所有用户使用相同版本的框架
Q: pnpm dsh 和 dsh 有什么区别?
pnpm dsh:从源码运行,使用 tsx 的 ESM hookdsh:从构建后的lib/运行,需要先pnpm run build
2.9 小结
| 概念 | 一句话解释 |
|---|---|
| Profile | 命名的 Bundle 组合 + 用户 Patch |
| Bundle | 可分发的配置行 + 代码 |
| Patch 层 | 按顺序叠加的配置覆盖 |
| runProfile() | 统一的启动入口 |
| Source/Artifact 平面 | src/ 和 lib/ 的双平面布局 |
| ESM | 全部使用纯 ESM,禁止 CJS |
下一步:第三章:Cordis 框架基础——深入理解 Cordis 的 Plugin/Service/Context 模型,以及如何编写你的第一个 Cordis 插件。