DeepSeek Harness 插件

JohnXu22786/pty-runner

Star 数 ★ 0 分类 工具与能力 收录于 2026-08-16

dsh 后台终端作业管理插件:在独立 PTY 会话中运行长时进程,写入普通文本与转义控制序列,分页读取环形缓冲输出,并支持优雅或强制终止作业。

安装

# GitHub 源码(首次需按提示配置 allowBuilds 构建授权后重试)

dsh plugin --profile web add github:JohnXu22786/pty-runner

装任何插件都等于在你的机器上跑第三方代码,权限和你本人一样大——能读你的文件、用你的凭据、访问网络,工具审批管不到它。GitHub 来源的插件还会在安装时执行构建脚本。请只安装可信来源,并尽量锁定 commit(github:owner/repo#sha)。

README

English

后台终端作业管理插件,为 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 分页读取输出缓冲(断点 fromlimit、正则 pattern、忽略大小写)
bg_list 列出全部/按分组或状态过滤的作业(含 PID、状态、输出统计、识别到的服务地址)
bg_stop 终止单个作业(优雅→强杀,可 drop 删除记录)
bg_stop_group 终止指定分组的全部作业
bg_drop 清理已结束作业的记录(按 id 或按分组)

输入转义

bg_senddata 支持反斜杠转义,写入前解码为真实字节:

写法 含义 写法 含义
\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 取值:runningstopping → 终态 finished(exit 0)| failed(非零退出码或信号,即崩溃检测)| stopped(由 stop/超时/插件卸载终止,reason 区分 user / timeout / shutdown / forced)。

输出缓冲语义

bg_read 返回的 lines 携带全局行号 index;响应中的 next 是下一页起点,truncateddropped 提示历史是否因容量上限被截断。未换行的末行(进程还在输出)会作为最新一行参与读取。

事件与服务接口

插件通过 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_launchnotifyOnExit: true 时,作业一结束插件就通过 exec.agent.inject() 向会话注入一条 notice 形式的上下文(含作业状态、退出码与读取指引),下一次模型请求即可看到——无需轮询。注入失败(agent 已销毁等)被静默吞掉,不影响作业状态结算。

插件如何被 harness 加载

本插件遵循 dsh(Cordis)插件契约,是一个标准的「bundle 型」插件包:

  1. 清单package.jsondsh.bundle.patch 字段指向 cordis.patch.yml——bundle 贡献的配置层。profile 组装时按 dsh.profile.bundles 顺序应用各 bundle 层,再叠加用户 patch 层。
  2. 入口src/index.js 是插件模块,package.jsonmain 指向它。模块以具名导出声明插件元信息,harness 的加载器(Cordis)解析模块并调用:
    • name —— 插件名(backstage
    • inject: ['tools'] —— 声明依赖的 harness 服务,全部就绪后才加载本插件
    • apply(ctx, config) —— 注册能力:ctx.tools.register(defineTool(...)) 注册 7 个工具(注册即受插件生命周期托管,卸载自动注销);ctx.provide('backstage', api) 提供服务;ctx.effect(...) 注册卸载清理(Cordis 会等待该 disposer 的 Promise,卸载会停掉全部作业后才结束)
  3. 工具定义src/tools/index.js@deepseek-ai/dsh-toolsdefineTool 声明参数 JSON Schema、canonical 输出与面向模型的 render,schema 自动汇入系统提示词组装。
  4. 卸载:插件行被移除或 harness 关闭时,Cordis 逆序执行所有 effect——注册的工具、服务与作业进程随之清理,不留后台孤儿。

与语言无关地概括:一个 dsh 插件 = 一个导出 apply(ctx, config) 的模块 + 一个在 patch 层中声明它的行。本插件的 cordis.patch.yml 就是那行声明,package.jsondsh.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 id bg_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

内容来自项目 README(GitHub)↗

链接

同类插件

查看整个分类 →