DeepSeek Harness Hub
← 返回列表

CI 结构化输出JohnXu22786/headless-json

DeepSeek Harnessspec-screened在 GitHub 查看 ↗
⚠ 装前注意

面向 DeepSeek Harnessdsh的结构化、机器可读 CI 输出。

基本兼容但装前注意:未发布到 npm registry,仅可从源码安装 · 最近上游提交 2026/9/17 · 已提供中文文档

为 DeepSeek Harness (dsh) 提供的结构化、机器可读的 CI 输出:JSON/NDJSON 会话报告、JUnit XML、语义化退出码、产物收集与隐私脱敏——一个 dsh 配置文件包外加一个独立 CLI。

综合分
30.7
GitHub 分
30.7
用户评分
★ Stars
1
周下载量
安装插件(需先安装 dsh CLI 引擎:npm install -g @deepseek-ai/dsh)
dsh plugin --profile web add JohnXu22786/headless-json
未发布到 npm registry,仅可从源码安装,改用 GitHub 源安装
数据截至 2026/9/18(元数据每日更新 · 实装验证按队列轮转,单条结论的验证时间见上方)
安装兼容性检查⚠ 装前注意

以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。

npm 包dsh-headless-json(未发布到 npm,仅可源码安装)
Node 引擎要求 >=18.0.0 · 基线 Node 22.19 满足
dsh CLI 依赖未声明 dsh 版本约束
入口文件main/exports/bin 已声明

未发布到 npm registry,仅可从源码安装

验证方式:npm registry 存在性 + package.json 静态校验 · 最后验证 2026/9/19 10:25:36

用户评分
还没有人投票,来当第一个
订阅周报,不错过优质插件更新
每周一封 · 高评分插件 + 新用户活动

README

dsh-headless-json

面向 DeepSeek Harness(dsh)的结构化、机器可读 CI 输出。

English documentation: README.md

dsh 官方 headless 模式只打印最后一条助手文本并以 0/1 退出——适合快速冒烟,但对 CI 来说信息太薄。dsh-headless-json 是一个 profile bundle,把任意 dsh 会话变成真正的 CI 产物:

- 事务级 JSON 报告(会话元数据 + 每个结构化事件 + 结果 + 统计);
- 增量 NDJSON 事件流,运行期间可实时 tail;
- JUnit XML 报告,供 GitLab CI / Jenkins / Azure DevOps 等直接展示;
- 语义化退出码,区分成功、失败、超时、阻断、中止与中断;
- 工件清单,收集会话输出中引用的文件路径;
- 隐私层(文本截断、参数隐藏、密钥掩码、路径相对化);
- 双入口:运行时内的 dsh 工具(output_status、output_events、set_options)+ 独立 CLI,用于离线渲染与退出码接线。

一切皆确定:给定相同的会话日志与相同的选项,产出的字节完全相同。

目录

- 功能
- 工作原理
- 包结构
- 安装与接入
- 一次运行产出什么
- dsh 工具
- CLI 参考
- 配置
- 输出格式
- JSON 报告
- 事件
- NDJSON 流
- JUnit XML
- 退出码语义
- 隐私与脱敏
- 工件
- 确定性
- 开发
- 局限与兼容性

功能

| 领域 | 内容 |
| --- | --- |
| 会话事件流化 | 订阅 dsh 会话事件源(session/created、session/event、session/flush、session/disposed),为每个 turn/step/工具调用派生出结构化事件:类型、模型、耗时、token、工具名、参数摘要、结果、错误、状态。 |
| JSON 输出 | 完整事务级报告:会话元数据 + 事件列表 + 结果/退出码 + 统计。可配置增量 NDJSON 流。 |
| JUnit XML 输出 | 工具调用、步骤与轮次映射为测试用例,任何 CI 系统都能像展示测试一样展示 dsh 运行。 |
| 退出码语义 | 稳定分类(成功/失败/超时/阻断/空/中止/中断),默认码全部可覆盖。 |
| 工件收集 | 用户/助手/工具文本中引用的文件路径收集为清单;同机运行时记录存在性与大小。 |
| 隐私开关 | 工具输出截断、参数隐藏、密钥掩码、路径相对化——挂载时配置,运行时经 set_options 调整。 |
| 工具链 | 三个 dsh 工具(output_status、output_events、set_options)+ 独立 CLI(dsh-headless-json render\|exit)。 |

工作原理

本包是一个 dsh profile bundle:npm 包,manifest 声明:

"dsh": { "bundle": { "patch": "./cordis.patch.yml" } }

补丁向加载器树插入一个插件(headless-json)。插件订阅会话服务事件,为每个会话维护一个 Capture,事件到达时实时写出 NDJSON 行,并在会话销毁时(dsh 正常优雅关停的一部分)写出 JSON/JUnit 报告。

dsh 会话事件流
│  session/created · session/event · session/flush · session/disposed
▼
┌─────────────────── CaptureManager ───────────────────┐
│  每会话 Capture:派生结构化事件                        │
│  (turn/step/tool 耗时、token、模型、错误)             │
│  + 工件扫描 + 原始类型分布                            │
└────────────┬──────────────────────────┬─────────────┘
│ 实时 NDJSON 行           │ 会话结束时
▼                          ▼
events.ndjson            report.json · junit.xml

- 采集与输出分离:事件在内存中只派生一次;脱敏在输出时应用,因此用 set_options 改选项会影响其后每一次写出。
- 不打扰 stdout:插件从不写 stdout,与官方 headless runner(独占 stdout)干净共存。
- 错误隔离:每个监听器都有防护;畸形事件永远不会拖垮插件树或污染报告。

包结构

headless-json/
├── package.json              # dsh.bundle.patch 清单 + bin
├── cordis.patch.yml          # 加载器补丁(插入插件行)
├── lib/                      # 编译产物 ESM(tsc 输出)
├── bin/dsh-headless-json.js  # CLI 入口
├── src/                      # TypeScript 源码(仅仓库)
├── test/                     # node:test 测试套件(仅仓库)
├── examples/                 # 样例会话日志 + 生成产物
├── README.md / README.zh.md
└── LICENSE                   # MIT

上面是仓库目录树;发布的 npm 包只包含 lib、bin、cordis.patch.yml、examples、两篇 README 与 LICENSE。

安装与接入

要求:Node.js ≥ 18,以及一个组合了 sessions 服务的 dsh 安装(任何 base/headless profile 都满足)。

1. 构建(仅从源码开发时需要)

npm install
npm run build        # 或:npm test(构建 + 跑完整测试套件)

2. 把 bundle 加进 dsh profile

用 CLI(转发给 pnpm):

dsh plugin --profile headless add /path/to/headless-json

或直接从本仓库安装:

dsh plugin --profile headless add github:JohnXu22786/headless-json

或手动:在 profile 的 package.json 依赖中加上 dsh-headless-json,并加入有序的 dsh.profile.bundles 列表,然后在 profile 目录 pnpm install。dsh 下次运行时会把已安装的 bundle 与 dsh.profile.bundles 对账。

3. 运行

dsh --profile headless "run the test suite"

运行结束后,在工作目录的 dsh-output/ 下得到:

dsh-output/report.json    # 事务级 JSON 报告
dsh-output/junit.xml      # JUnit XML 报告
dsh-output/events.ndjson  # 仅当 output.ndjson = true 时

4. 接入 CI

语义退出码在报告中;CLI 把它变成进程退出码:

dsh --profile headless "run the test suite"
code=$(dsh-headless-json exit dsh-output/report.json)
exit $code

或直接把 JUnit 报告交给 CI 收集器:

dsh-headless-json render dsh-output/report.json --format junit --out junit.xml

一次运行产出什么

每个会话,插件写出:

| 文件 | 时机 | 内容 |
| --- | --- | --- |
| report.json | 会话结束 | 完整报告(见 JSON 报告) |
| junit.xml | 会话结束 | JUnit XML(见 JUnit XML) |
| events.ndjson | 实时 | 每事件一行 JSON,实时追加,末尾一行 session_end |

文件名支持 {session} 占位符(如 "json_file": "report-{session}.json");未用时,同一进程内的第二个会话会在扩展名前追加 -。output.write_empty 为 false 时,零事件的会话不写任何文件。

dsh 工具

本 bundle 经 ctx.tools.register 注册三个工具,对所有 agent 可见,按调用方 agent 的会话解析。

| 工具 | 参数 | 返回 |
| --- | --- | --- |
| output_status | 无 | 实时采集状态:事件/turn/工具计数、挂起中的工具调用、脱敏设置、流状态。 |
| output_events | types?(kind 数组)、since?(seq)、limit?(默认 200) | 请求的结构化事件,与文件相同的脱敏处理。 |
| set_options | 部分 output / redact / artifacts / events / exit | 校验后的新生效选项。 |

对话示例:

set_options({ "redact": { "args": "hide" } })
→ { "applied": true, "redact": { "args": "hide", ... } }

CLI 参考

dsh-headless-json render  [options]   渲染报告/事件文件
dsh-headless-json exit  [options]     打印语义退出码
dsh-headless-json --version | --help

可以是:

- 插件产出的 report.json;
- 插件产出的 events.ndjson 流;
- 原始 dsh 会话 .jsonl 日志(头部行 + session/ 事件行);
- 原始会话事件的 JSON 数组。

| 选项 | 含义 |
| --- | --- |
| --format json\|junit\|ndjson | 输出格式(默认 json)。 |
| --out  | 写入文件而非 stdout。 |
| --pretty | 美化 JSON 报告。 |
| --set  | 覆盖选项,如 --set redact.text_length=1200,可重复。 |
| --text-length  | --set redact.text_length= 的简写。 |
| --arg-length  | --set redact.arg_length= 的简写。 |
| --args full\|truncate\|hide | --set redact.args= 的简写。 |
| --paths relative\|absolute | --set redact.paths= 的简写。 |
| --no-secrets | 关闭密钥掩码。 |
| --max-events  | --set events.max_events= 的简写。 |
| --cwd  | 路径相对化的基准目录。 |
| --include-log-only | 把仅日志类事件类型以 other 呈现。 |

说明:

- exit 打印退出码并用该码退出进程——CI 接线的关键。
- 脱敏类 --set 覆盖只对原始会话日志重新渲染生效。events.ndjson 流与 report.json 在采集写出时已脱敏,再输出时保持原样。

配置

所有键为 snake_case,可通过 bundle 补丁的 config 块、set_options 工具或 CLI --set 配置。配置文件容忍未知键(便于前向兼容的部署);set_options 与 CLI --set 拒绝未知键,包括未知的嵌套键。

cordis.patch.yml(profile 覆盖示例)
- id: headless-json
config:
output:
dir: dsh-output
json: true
junit: true
ndjson: true
redact:
text_length: 4000
args: truncate
paths: relative
secrets: true

| 键 | 默认 | 说明 |
| --- | --- | --- |
| output.dir | dsh-output | 报告目录(相对 cwd)。 |
| output.json | true | 写 report.json。 |
| output.junit | true | 写 junit.xml。 |
| output.ndjson | false | 实时流式写 events.ndjson。 |
| output.json_file | report.json | JSON 文件名(支持 {session} 占位符)。 |
| output.junit_file | junit.xml | JUnit 文件名。 |
| output.ndjson_file | events.ndjson | NDJSON 文件名。 |
| output.write_empty | true | 为零事件的会话也写报告。 |
| output.pretty | false | 美化 JSON 报告。 |
| redact.text_length | 4000 | 输出中任何文本值的最大字符数(0 = 不限)。 |
| redact.arg_length | 500 | 工具参数摘要的最大字符数。 |
| redact.args | truncate | full(解析对象)、truncate(摘要)、hide([hidden])。 |
| redact.paths | relative | relative(相对会话 cwd)或 absolute。 |
| redact.secrets | true | 掩码形似密钥的字符串。 |
| redact.secret_patterns | [] | 额外的正则源字符串,在内置模式之外追加应用。 |
| artifacts.collect | true | 扫描文本中的文件路径引用。 |
| artifacts.check_exists | true | stat 命中的路径(仅运行机上有意义)。 |
| artifacts.max_entries | 500 | 工件清单最大条目数。 |
| artifacts.pattern_extras | [] | 额外的路径正则源字符串。 |
| events.max_events | 1000 | 报告事件列表的最大条数(0 = 不限)。 |
| events.trim | balanced | head 只保留前 N 条;balanced 保留头尾。 |
| events.include_log_only | false | 把仅日志类型(request/header、session/end-seed 等)以 other 呈现。 |
| exit. | 见表格 | 各类别退出码(见 退出码语义)。 |
| capture.text_cap | 100000 | 单段存储文本的硬上限(字符)。 |

输出格式

JSON 报告

{
"schema_version": 1,
"plugin": { "name": "dsh-headless-json", "version": "0.1.0" },
"generated_at": 1753000000000,          // 会话结束时间(派生,确定性)
"session": {
"id": "...", "cwd": ".", "cwd_name": "repo",
"created_at": ..., "started_at": ..., "ended_at": ...,
"event_count": 42,
"parent_session": null, "agent_preset": "minimal", "delegation_depth": null
},
"outcome": {
"status": "success",                  // 稳定分类
"exit_code": 0,                       // 语义退出码
"reason": "completed",                // 原始 turn/end 原因
"complete": true,
"undelivered_tool_calls": [],
"error": null                         // 出错时为 {code, message, status?}
},
"stats": {
"duration_ms": 4123.5,
"turns": 1, "steps": 2,
"assistant_messages": 3, "user_messages": 1,
"tool_calls": 2, "tool_errors": 0, "undelivered_tool_calls": 0,
"chunk_count": 14,
"tokens": { "input": 900, "output": 300, "cache_read": 0, "cache_write": 0, "reasoning": 0 },
"events_by_type": { "assistant/message": 3, "tool/call": 2, ... },  // 原始 dsh 类型
"by_tool": { "bash": { "calls": 2, "errors": 0, "latency_ms_total": 800, "latency_ms_max": 500 } }
},
"events": [ /* 见下 / ],
"events_truncated": null,               // 被裁剪时为 {kept,total,dropped}
"artifacts": [ { "path": "src/options.ts", "kind": "file", "size": 4281, "references": 3 } ]
}

事件

每个事件都带 seq(会话日志序号,稳定排序键)、time(Unix 毫秒)与 kind:

| kind | 附加字段 |
| --- | --- |
| turn_start | turn |
| turn_end | turn、reason、error(code/message/status)、cause(中止原因)、latency_ms、complete |
| step_start / step_end | turn、step、(latency_ms) |
| user_message | turn、step、text、reasoning、blocks |
| assistant_message | turn、step、provider、model、latency_ms、usage、text、reasoning、blocks、stream(chunk 数量/时间) |
| tool_call | turn、step、call_id、tool、args_mode、args、args_summary、latency_ms、undelivered |
| tool_result | turn、step、call_id、tool、status(success/error)、latency_ms、text、blocks、error |
| todo_write | count、todos |
| request_context | provider、model、context_window |
| other | type(仅 events.include_log_only 时) |

工具调用与结果按 call_id 关联;耗时由会话日志时间戳计算。会话结束时仍未收到 tool/result 的 tool_call 会标记 undelivered: true,并列入 outcome.undelivered_tool_calls。

NDJSON 流

每行一个 JSON 对象,按到达顺序即时写出:同样的结构化事件,末尾追加一行汇总:

{"kind":"session_end","session_id":"...","generated_at":...,
"outcome":{"status":"success","exit_code":0,"reason":"completed"},
"stats":{"duration_ms":4123,"turns":1,"steps":2,"tool_calls":2,"tool_errors":0}}

每次 session/flush 检查点都会 fsync 文件,whenIdle() 后读存储的消费者能看到持久化的流。tool_call 行在结果到达之前即被流式写出,因此这些行上的 latency_ms / undelivered 是暂定值;关联的 tool_result 行(或最终报告)携带权威值。

JUnit XML

| dsh 概念 | JUnit 映射 |
| --- | --- |
| 整个会话 |  → 一个 ,属性含会话 id、cwd、status、exit_code、reason、插件版本 |
| 总体结果 |  — 仅当 outcome.status === "success" 时通过,否则 / |
| 每个 turn |  — completed 通过;error/max-tokens 失败;blocked/aborted/interrupted 跳过 |
| 每个 step |  带耗时;未关闭则跳过 |
| 每个工具调用 |  带耗时,结果文本在 ;失败产生  |

时间以十进制秒计,timestamp 为 ISO-8601 UTC,所有文本经 XML 1.0 转义(控制字符替换)。

退出码语义

最终的 turn/end 原因映射为稳定分类与文档化退出码;每个值都可经 exit. 覆盖:

| 分类 | 最终 turn/end 原因 | 默认码 |
| --- | --- | --- |
| success | completed | 0 |
| error | error | 1 |
| timeout | max-tokens | 2 |
| blocked | blocked | 3 |
| empty | (完全没有事件) | 4 |
| aborted | aborted | 130 |
| interrupted | interrupted | 130 |

有事件但没有闭合 turn/end 的会话按 error/incomplete 报告。130 遵循 SIGINT 惯例,表示用户主动中止。

隐私与脱敏

默认设置保守但可用。一切都在输出时应用。

- 文本截断 — 每个文本/reasoning 值按 redact.text_length 截断,带确定的 …[+N more chars] 标记。
- 参数处理 — redact.args:
- full:解析后的参数对象,字符串值掩码/截断;
- truncate(默认):掩码摘要,上限 redact.arg_length;
- hide:字面量 [hidden]。
- 密钥掩码(redact.secrets)— 内置覆盖 sk-… 密钥、Bearer … 头、PEM 私钥、GitHub/Google token、JWT、长 hex(保留短前缀以便辨认如 commit SHA)与形似 token 的字符串。turn/end 的错误消息按自由文本处理,同样掩码。可用 redact.secret_patterns(正则源字符串,配置期校验)追加。
- 路径相对化 — redact.paths: relative(默认)从工件路径中剥离会话 cwd,并把 session.cwd 渲染为 .;absolute 保留完整路径。

实际效果:默认输出不包含绝对工作区路径、不包含原始密钥串、不包含无界工具输出。

工件

artifacts.collect 开启时,每个用户/助手/工具文本都会扫描形似路径的候选(带分隔符的 POSIX/Windows 绝对路径与相对路径;URL 与邮箱被排除)。每个唯一候选成为一条工件条目并记录引用次数;artifacts.check_exists(默认开)且在同机运行时,kind(file/dir/missing)与 size 由文件系统填充。清单以 artifacts.max_entries 为界。

确定性

给定相同的会话事件与相同的生效选项:

- JSON 报告逐字节一致——对象键顺序由构造固定,事件按 seq 排序,映射型字段按排序键序列化,数字统一保留 3 位小数;
- JUnit XML 与 NDJSON 行出自同一序列化核心,三种格式永远一致。

generated_at(以及 JUnit 的 timestamp)由会话自身时间线派生——取最后一条事件的时间,因此从不依赖墙钟。

开发

npm install
npm run build        # tsc -> lib/
npm test             # 构建 + 跑完整套件(node:test)
npm run test:only    # 对当前构建跑测试
npm run typecheck    # tsc --noEmit

测试套件覆盖:事件订阅接线(基于最小假 context)、采集派生、序列化确定性、脱敏、退出码映射、JUnit 结构与转义、NDJSON 往返、CLI 端到端。

局限与兼容性

- 预览期 API。 dsh 处于开发者预览阶段,会话事件形态与服务语义可能演进。采集管线对所有事件做防御性读取(畸形输入只计数、不致命),所用的事件词汇均来自已发布的会话类型定义。
- 工具 schema 形态。 三个 dsh 工具以符合 ctx.tools.register 期望形状的普通对象注册;若未来 dsh 改变定义 DSL,只需更新 src/tools.ts。组合中没有 ctx.tools 时,插件仍然采集与报告,只是跳过工具注册(带日志警告)。
- 退出码与官方 headless。 官方 headless runner 按自身契约以 0/1 退出;本插件不触碰 headlessIo。需要细粒度退出码请用 exit 命令(或读 report.json)。
- 重渲染脱敏。 只有原始会话日志能用不同的 --set 脱敏重渲染(脱敏是采集时属性,report.json 与 NDJSON 行已脱敏,再输出时保持原样)。

License

MIT — 见 LICENSE。

上游仓库有新提交时邮件通知你(每天最多一封,无更新不打扰),随时一键退订。

💬 加入 DPharness 群聊

插件用法、部署报错、新插件第一时间同步——群里问,比一个人翻文档快。

点击加入 QQ 群
DPharness 群聊二维码,手机 QQ 扫码进群
扫码进群