# dshcode

> 为 dsh 增加一次性任务 profile：传入一段任务文本运行 Agent 后打印最终答复并退出，适合脚本与 CI 调用。

## Metadata

- Author: [@whitelonng](https://github.com/whitelonng)
- Repo: <https://github.com/whitelonng/dshcode.git>
- GitHub: [whitelonng/dshcode](https://github.com/whitelonng/dshcode)
- Stars: 90
- Language: TypeScript
- License: [MIT](https://spdx.org/licenses/MIT.html)
- Topics: `agent`, `deepseek`, `deepseekharness-plugin`, `dsh-plugin`, `harness`
- Forks: 8
- Open Issues: 0
- Last push: 2026-08-20T20:02:51.000Z
- Added: 2026-08-20T00:00:00.000Z

## Install

```bash
dsh plugin --profile web add github:whitelonng/dshcode/packages/bundle/headless
```

## Wiki

> 入口包名：`@deepseek-ai/dsh-headless`，落地页 ID：`whitelonng/dshcode/packages/bundle/headless`。下文统称"headless 组合包"。

## 一句话定位
它在 dsh 基础组合包之上加一层"一次性任务"驱动，让用户用 `dsh --profile headless "<任务文本>"` 把一段任务丢给 Agent，Agent 完成（或异常）后打印最终答复并直接退出；它不启动 Host、HTTP server、Web runtime 或浏览器，因此是 CI 与脚本里调用 dsh 的最薄形态。

## 核心能力
- 直接叠在 dsh-base 之上，提供编码 persona 与工具模式（`cordis.patch.yml:7-20`）
- 禁用 HMR，把 Code Mode 的 worker 作为核心执行能力挂上（`cordis.patch.yml:14-20`、`:23-26`）
- 一个进程只接一个任务：通过 commander 解析位置参数 `task`（多词用空格合并），缺失或纯空白时打印 usage 错误并退出 1（`src/startup.ts:31-57`）
- 创建一个全新的持久化 Agent，把任务作为普通用户消息提交，等 Agent 回到 idle 后 flush 会话（`src/index.ts:96-127`）
- 抽取本次任务区间内最后一条非空 assistant 文本写入 stdout，最终 `turn/end` 原因是 `completed` 时退出 0，其他情况退出 1；错误原因时把 `code: message` 追加到 stderr（`src/index.ts:60-134`）

## 技术实现
- **语言**: TypeScript（ESM 模块）
- **关键依赖**: `@deepseek-ai/dsh-cmdline`（命令行宿主）、`@deepseek-ai/dsh-code-runtime-worker-thread`（Code Mode worker）、`@deepseek-ai/schemastery`（Config schema）、`commander`（CLI 解析）（`package.json:46-51`）
- **架构模式**: Cordis bundle patch——通过 `package.json#dsh.bundle.patch` 声明 `cordis.patch.yml`，在 base 之上插入 system-prompt/hmr/tools 的覆盖行与三条 insert 行（code-runtime / headless-startup / headless-runner）；headless-runner 是普通函数插件，headless-startup 提供 `headlessStartup` 服务供前者的 lazy config 读取任务（`cordis.patch.yml:1-35`、`src/startup.ts:19`）
- **入口文件**: `src/index.ts`（runner，`name = 'headless-runner'`）、`src/startup.ts`（CLI provider，`name = 'headless-startup'`）、`cordis.patch.yml`（bundle 注入）

## 适用场景
需要把 dsh 当一个命令行工具来用——比如 CI 里让 Agent 跑一次代码审查、shell 脚本里临时问个文件写法、cron 里把日报生成挂在 dsh 上，而不是开 Web UI 交互。它适合"一次任务-一次答复-进程结束"的场景，不适合需要多轮追问的会话。

## 前置依赖与兼容性
| 依赖 | 最低版本 | 说明 |
|---|---|---|
| DSH | 未单独声明；与 dsh 1.0.5 同源发布 | 安装到已用 `dsh` 启动器的 profile 才能激活，runner 强制依赖 launcher 提供的 `ctx.appExit` |
| Node | `^22.19.0 \|\| >=24.0.0` | 源自仓库根 `package.json:8-10` 的 engines 字段 |
| 平台 | macOS / Linux / Windows | 跨平台，本包自身不带原生模块 |
| 原生模块 | 无 | 本包不引入原生模块；base 的 sandbox 等行可能引入 |

> 本包通过 workspace 依赖 `@deepseek-ai/dsh-agent` / `@deepseek-ai/dsh-llm` / `@deepseek-ai/dsh-session` 等基础包（`package.json:52-59`），它们随 dsh-base 间接安装。

## 安装方式
```bash
dsh plugin --profile web add github:whitelonng/dshcode/packages/bundle/headless
```

## 配置项
本插件不向用户暴露独立配置面；唯一可调的字段是它从启动器读取的"任务文本"：

| 配置 | 类型 | 说明 | 默认值 |
|---|---|---|---|
| `headless-runner.task` | string | runner 这一次要执行的提示词文本，从启动器 `--profile headless "..."` 的位置参数自动填入；空白任务在启动期会被拒 | 由命令行 `dsh --profile headless "<task>"` 提供 |
| `system-prompt.persona` | string | 模板字符串，注入 `{{model}}` 与 `{{cwd}}` 后给到 system prompt。普通用户无须改，会被自己 profile 覆盖 | `"You are a coding agent powered by the {{model}} model. Your working directory is {{cwd}}."` |
| `hmr.disabled` | boolean | headless profile 强制关闭 HMR（`cordis.patch.yml:14-15`） | `true` |
| `tools.mode`（env `DSH_TOOLS_MODE`） | string | Code Mode 进程级开关，与 Web profile 同源 | 取自 `process.env.DSH_TOOLS_MODE` |

## 常见问题

**Q: 这个组合包和 dsh-base 有什么区别？**

A: base 把基础工具、模型、持久化等 60+ 能力按"出厂默认集合"全部注入；headless 只在 base 之上叠一层极简驱动层——不挂 Host、HTTP server、Web runtime 或浏览器插件，每一个进程只接一个任务、跑完后打印最终 assistant 文本再退出 0/1。

**Q: 进程退出码代表什么？**

A: 最终 turn 的原因是 `completed` 时退出 0；`aborted` 或 `error` 时退出 1。错误情况下，stderr 会额外输出一行 `dsh: <code>: <message>`，成功时 stderr 为空（见 `src/index.ts:130-133`）。

**Q: 不带任务文本直接跑会怎样？**

A: 启动器先打印 usage 错误 `a task is required, for example: dsh --profile headless "run the tests"` 并以 exit 1 结束，runner 在收到服务之前就被拒，不会启动 Agent（见 `src/startup.ts:53-55`、`tests/startup.spec.ts:91-97`）。

**Q: 可以在 dsh launcher 之外使用这个 profile 吗？**

A: 不可以。runner 必须在挂载前从宿主拿到退出请求服务；launcher 之外启动会在激活时直接抛 `headless-runner: the launcher must provide ctx.appExit before the tree mounts`（见 `src/index.ts:144-147`、`tests/headless.spec.ts:243-246`）。

**Q: 一个进程里能跑多个任务吗？**

A: 不能。每次 `dsh --profile headless` 调用只提交一个任务并等待返回到 idle，要再跑只能重新启动一个进程。profile 名义上是一个交互后端，实际上是单发任务运行器（见 `README.md:19`）。

**Q: 默认的 HMR 和 Code Mode 状态是什么？**

A: HMR 在 headless profile 下被显式禁用，Code Mode 由 `DSH_TOOLS_MODE` 环境变量控制——值决定是否启用 worker 线程代码执行，与 Web profile 同源（见 `cordis.patch.yml:14-20`）。

**Q: 会话数据存在哪里？**

A: 由 dsh-base 那一层的 `session-persistence-jsonl` 负责，落到 `$DSH_HOME/sessions`；headless 自身不另起存储面。

**Q: Agent 用什么模型？**

A: 跟随 dsh-base 的 `agent-default-model` 默认值（`deepseek-official` / `deepseek-v4-flash`）。要换模型在自己 profile 的 `cordis.patch.yml` 里按 id 整行覆盖这一行即可。

## 上手难度
入门 — 命令本身就是一个位置参数；只跑一次的话连配置都不用看，想自定义默认模型或 persona 时在自己 profile 的 patch 文件里覆盖即可。

## 已知问题与限制
- **一个进程一个任务**：runner 没有交互后续输入，提交完任务后它等到 Agent 返回 idle，并把区间内最后一条非空 assistant 文本打印出来（`README.md:19`）
- **`ctx.appExit` 由启动器持有**：在 `dsh` 启动器之外启动 headless profile 会在激活时直接抛错，直到宿主提供该退出请求（`README.md:20`、`src/index.ts:144-147`）
- **空白任务在启动期被拒**：纯空白或缺失的 `task` 不会启动 Agent，进程以 1 退出（`src/startup.ts:53`、`tests/startup.spec.ts:91-97`）
- **headless profile 中的 HMR 不启用**：单发进程不需要热更新，patch 显式 `disabled: true`（`cordis.patch.yml:14-15`）

---

This document is auto-generated by [deepseek-plugin.org](https://deepseek-plugin.org). HTML page: [dshcode](https://deepseek-plugin.org/plugins/whitelonng/dshcode/packages/bundle/headless)
Wiki generated by AI (model: `MiniMax-M3`)
