dsh 后台终端作业管理插件:在独立 PTY 会话中运行长时进程,写入普通文本与转义控制序列,分页读取环形缓冲输出,并支持优雅或强制终止作业。
安装
# GitHub 源码(首次需按提示配置 allowBuilds 构建授权后重试)
dsh plugin --profile web add github:JohnXu22786/pty-runner
装任何插件都等于在你的机器上跑第三方代码,权限和你本人一样大——能读你的文件、用你的凭据、访问网络,工具审批管不到它。GitHub 来源的插件还会在安装时执行构建脚本。请只安装可信来源,并尽量锁定 commit(github:owner/repo#sha)。
README
后台终端作业管理插件,为 DeepSeek Harness(dsh)提供持久的后台进程(PTY)管理能力。
AI agent 在跑开发服务器、watch 任务、长时构建等进程时,普通的一次性 shell 调用无法胜任:进程会随调用结束被回收,agent 也拿不到交互输入与后续输出。本插件把这类进程放进独立的后台终端会话(job),agent 可以随时:
- 启动进程并让它独立运行(PTY,支持交互程序)
- 向进程写入输入(普通文本或
\x03这类控制序列) - 分页读取进程输出(环形缓冲、正则过滤、断点续读)
- 按状态/分组查看所有作业
- 优雅或强制终止进程,并在插件卸载时统一清理
功能一览
| 能力 | 说明 |
|---|---|
| 后台运行 | 进程在独立伪终端中运行,不随工具调用结束而退出 |
| 多作业并发 | 同时管理任意数量的作业,每个作业独立缓冲与状态 |
| 交互输入 | 写入普通文本与转义控制序列(Ctrl+C、方向键、ESC 等) |
| 环形输出缓冲 | 每作业保留最近 N 行(默认 5000),自动剥离终端转义序列 |
| 分页读取 | 按全局行号断点续读,支持正则过滤(类似 grep)与忽略大小写 |
| 退出检测 | 区分正常结束(exit 0)、崩溃(非零退出码/信号)与主动终止 |
| 端口提示 | 启动前检查目标端口占用;自动从输出中识别服务地址(如 http://localhost:5173) |
| 分组管理 | 作业打组标签,可一键停止整组 |
| 超时保护 | 可选 timeoutMs,超时自动终止作业 |
| 退出通知 | 可选:作业结束时向会话注入一条通知(无需轮询) |
| 跨平台 | Windows(ConPTY)/ macOS / Linux;无原生模块时自动降级为管道后端 |
在 DSH 中安装
npm i -g @deepseek-ai/dsh # 或 npx @deepseek-ai/dsh
dsh plugin --profile demo add github:JohnXu22786/pty-runner
dsh --profile demo # 启动
卸载:
dsh plugin --profile demo remove dsh-backstage
安装
要求:Node.js ≥ 20,npm 或 pnpm。
方式一:作为 bundle 装入 profile(推荐)
在任意目录(例如本插件目录的上层)安装 dsh CLI 后:
npm i -g @deepseek-ai/dsh # 或 npx @deepseek-ai/dsh
dsh plugin --profile demo add /path/to/pty-runner
dsh --profile demo --dump-config # 应看到 "# == dsh-backstage" 层
dsh --profile demo # 启动
dsh plugin add 会完成三件事:把本包链接进 profile 的依赖、把 dsh-backstage 追加到 dsh.profile.bundles、并应用本包 cordis.patch.yml 中声明的插件行。之后 dsh plugin --profile demo remove dsh-backstage 可完整卸载。
方式二:作为 overlay 加载(本地开发)
本包自带的 cordis.patch.yml 声明插件行,也可以直接用 --patch 挂载到任意 profile:
# POSIX
dsh web --patch /path/to/pty-runner/cordis.patch.yml
Windows 注意:patch 里的
name若指向本地文件,必须写成file:///URL 形式, 否则加载器会报ERR_UNSUPPORTED_ESM_URL_SCHEME。示例:- insert: - id: backstage name: 'file:///D:/path/to/pty-runner/src/index.js'以 bundle 方式安装(方式一)时
name解析为包名,无此限制。
启动后日志出现 [backstage] ready: backend=auto bufferCapacity=5000 ... 即加载成功。
配置
插件接受可选配置(在 patch 行的 config 块或 profile 的 patch 层中覆盖),全部有默认值,非法值会导致插件加载失败而非半配置运行:
- insert:
- id: backstage
name: dsh-backstage
config:
bufferCapacity: 20000 # 每作业保留的输出行数(100-200000,默认 5000)
defaultGroup: 'default' # 未指定分组时的默认组标签
backend: 'auto' # 'pty'(真伪终端)| 'pipe'(管道降级)| 'auto'
stopGraceMs: 1500 # 优雅终止等待时长,超时后强杀(50-60000)
portScan: true # 启动前是否检查请求的端口占用
backend 说明:pty 使用 node-pty(Windows 上基于 ConPTY),交互程序行为与真人终端一致;
pipe 使用普通子进程管道,无终端语义(依赖 TTY 才刷新的程序可能缓冲输出),
零原生依赖;auto 优先 pty,原生模块不可用时自动降级 pipe。
工具接口
插件向模型暴露 7 个工具(名称均以 bg_ 前缀,避免与内置工具家族冲突):
| 工具 | 作用 |
|---|---|
bg_launch |
启动后台作业(命令、参数、工作目录、环境变量、标题、分组、超时、端口预检、退出通知) |
bg_send |
向作业终端写入输入(支持转义控制序列) |
bg_read |
分页读取输出缓冲(断点 from、limit、正则 pattern、忽略大小写) |
bg_list |
列出全部/按分组或状态过滤的作业(含 PID、状态、输出统计、识别到的服务地址) |
bg_stop |
终止单个作业(优雅→强杀,可 drop 删除记录) |
bg_stop_group |
终止指定分组的全部作业 |
bg_drop |
清理已结束作业的记录(按 id 或按分组) |
输入转义
bg_send 的 data 支持反斜杠转义,写入前解码为真实字节:
| 写法 | 含义 | 写法 | 含义 |
|---|---|---|---|
\xHH |
十六进制字节(如 \x03 = Ctrl+C) |
\cX |
控制字符(\cC = Ctrl+C,\c? = DEL) |
\n \r \t |
换行/回车/制表 | \e \a \0 \b |
ESC / BEL / NUL / 退格 |
\\ |
反斜杠字面量 | \uXXXX |
Unicode 字符 |
未知转义(如 \q)原样保留,不会静默丢弃。Windows 的 pty(ConPTY)后端下,裸 \n 会被转换为 ConPTY 行模式所需的 \r(\r\n 保持原样);pipe 后端按原字节写入。
退出状态语义
每个作业的 status 取值:running → stopping → 终态 finished(exit 0)| failed(非零退出码或信号,即崩溃检测)| stopped(由 stop/超时/插件卸载终止,reason 区分 user / timeout / shutdown / forced)。
输出缓冲语义
bg_read 返回的 lines 携带全局行号 index;响应中的 next 是下一页起点,truncated 与 dropped 提示历史是否因容量上限被截断。未换行的末行(进程还在输出)会作为最新一行参与读取。
事件与服务接口
插件通过 ctx.provide('backstage', api) 提供同名服务,其他插件可 inject: ['backstage'] 后编程式驱动作业,或订阅事件:
export const inject = ['tools', 'backstage']
export function apply(ctx) {
// 订阅作业退出事件(返回 disposer,插件卸载自动注销)
ctx.backstage.onExit(({ id, status, exitCode, reason }) => { /* ... */ })
// 其余方法:launch / stop / stopGroup / read / write / list / stats / drop ...
}
事件:exit(作业结束,载荷含 id/status/exitCode/reason/info)、created(新作业)、data(有输出)、dropped(记录被清理),对应服务面上的 onExit / onCreated / onData / onDropped 订阅助手。
退出通知
bg_launch 传 notifyOnExit: true 时,作业一结束插件就通过 exec.agent.inject() 向会话注入一条 notice 形式的上下文(含作业状态、退出码与读取指引),下一次模型请求即可看到——无需轮询。注入失败(agent 已销毁等)被静默吞掉,不影响作业状态结算。
插件如何被 harness 加载
本插件遵循 dsh(Cordis)插件契约,是一个标准的「bundle 型」插件包:
- 清单:
package.json的dsh.bundle.patch字段指向cordis.patch.yml——bundle 贡献的配置层。profile 组装时按dsh.profile.bundles顺序应用各 bundle 层,再叠加用户 patch 层。 - 入口:
src/index.js是插件模块,package.json的main指向它。模块以具名导出声明插件元信息,harness 的加载器(Cordis)解析模块并调用:name—— 插件名(backstage)inject: ['tools']—— 声明依赖的 harness 服务,全部就绪后才加载本插件apply(ctx, config)—— 注册能力:ctx.tools.register(defineTool(...))注册 7 个工具(注册即受插件生命周期托管,卸载自动注销);ctx.provide('backstage', api)提供服务;ctx.effect(...)注册卸载清理(Cordis 会等待该 disposer 的 Promise,卸载会停掉全部作业后才结束)
- 工具定义:
src/tools/index.js用@deepseek-ai/dsh-tools的defineTool声明参数 JSON Schema、canonical 输出与面向模型的render,schema 自动汇入系统提示词组装。 - 卸载:插件行被移除或 harness 关闭时,Cordis 逆序执行所有 effect——注册的工具、服务与作业进程随之清理,不留后台孤儿。
与语言无关地概括:一个 dsh 插件 = 一个导出 apply(ctx, config) 的模块 + 一个在 patch 层中声明它的行。本插件的 cordis.patch.yml 就是那行声明,package.json 的 dsh.bundle 告诉安装器它贡献哪一层。
示例
命令行冒烟(不依赖 dsh)
npm install
node examples/mini-harness.js # 用内置微型 harness 加载插件并跑通 启动→读→写→停 全流程
npm test # 95 项单元与集成测试(node --test)
examples/mini-harness.js 同时是「插件化 harness 如何加载它」的最小可读示例:它模拟了 ctx.tools.register / ctx.provide / ctx.effect,按真实契约调用 apply。
对话用例
用户:把 vite dev server 在后台跑起来,注意端口 5173 别被占用。 agent:
bg_launch(command: "npm", args: ["run", "dev"], title: "vite", ports: [5173], notifyOnExit: true)→ 返回 job idbg_xxx、status running、warnings 为空(端口空闲)。 之后 agent 需要时:bg_read(id, limit: 50)查看最新日志;bg_send(id, data: "\\x03")中断;改完代码bg_send(id, data: "rs\\n")触发重启;收工时bg_stop(id, drop: true)。
目录结构
pty-runner/
├── package.json # 包元数据 + dsh.bundle 清单
├── cordis.patch.yml # bundle 配置层:声明插件行
├── src/
│ ├── index.js # 插件入口:name / inject / apply
│ ├── core/ # 与 harness 无关的核心(均可独立测试)
│ │ ├── registry.js # 作业注册表与生命周期状态机
│ │ ├── launcher.js # pty / pipe 双后端进程启动器
│ │ ├── history.js # 行级环形缓冲与分页读取
│ │ ├── ansi.js # 有状态 ANSI 转义序列剥离
│ │ ├── escape.js # 输入转义解码与回显描述
│ │ ├── portprobe.js # 端口占用探测与服务地址识别
│ │ ├── config.js # 配置默认值与校验
│ │ └── index.js # core 导出聚合(package exports "./core")
│ ├── tools/index.js # 7 个 defineTool 定义
│ └── host/service.js # backstage 服务面
├── examples/mini-harness.js
└── test/ # node --test 测试
已知限制
pipe后端无 TTY 语义:行缓冲的程序输出可能延迟,交互式 TUI 不可用;生产使用请装 node-pty(pty/auto后端)。- 进程输出按文本处理(UTF-8 解码);二进制输出不做保真承诺。未换行的超长行(超过 65536 字符,约 64 KiB ASCII)会保留尾部并计入
dropped,防止内存失控。 stop的强杀兜底在极端顽固的进程下最多再等 2 秒,之后标记为forced并交由系统回收。- Windows 下 node-pty 的
kill()不接受信号名(会抛错),插件内部已按平台处理;个别 Windows 环境会在创建 ConPTY 会话(启动/终止)时打印 node-pty 内部的AttachConsole failed噪音,不影响功能。
许可证
MIT — 见 LICENSE。
链接
同类插件
superdesigndev/treg★ 425
给 Agent 的工具目录:按「要做的事」检索约 2,600 个外部接口(SEO 与 SERP、外链、社交、人物与公司信息补全、广告库、抓取),查看参数与单次调用价格后直接调用,凭据由服务端注入。附带技能,MCP 行在未设置 TREG_TOKEN 前保持禁用。
Lum1104/dsh-browser★ 198
Chrome 侧边栏扩展,让 DSH 直接操控你的浏览器,无需视觉能力。
zhaoolee/notes★ 142
将 DSH 对话导出为锤子便签风格 PNG,或在配置的账号工作区中新建和更新 Markdown 便签。
liustack/modsearch★ 111
纯文本 agent 的联网搜索桥:搜索网页与 X,返回结构化 JSON 证据(search/fetch/引用)。
taxueseek/argo★ 91
专为 agent 打造的搜索工具:多语言,覆盖中文/英文/学术/代码/购物/金融/新闻/百科。
Vladimir-Human/ru-marketplace-mcp#dsh★ 63
面向俄罗斯十家电商平台的技能与可选 MCP 行:跨 Wildberries、Detsky Mir、Yandex Market 比价,以及各平台的搜索、商品卡与评论。安装后 13 个技能立即可用;两行 MCP 默认关闭,需将 RU_MARKETPLACE_MCP_DIR 指向本地克隆,该克隆需要 Python 3.12+ 与 uv。