dsh-baton-mcp 0.6.2 → 0.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +127 -98
- package/README.zh-CN.md +120 -93
- package/host/README.md +4 -4
- package/host/package.json +2 -2
- package/package.json +1 -1
- package/server.json +2 -2
- package/src/codex-hook.mjs +138 -13
package/README.md
CHANGED
|
@@ -1,161 +1,172 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<
|
|
2
|
+
English | <a href="README.zh-CN.md">中文</a> · macOS · Windows (<a href="WINDOWS.md">guide</a>) · no Node.js needed · zero dependencies · MIT
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
English | <a href="README.zh-CN.md">中文</a>
|
|
7
|
-
</p>
|
|
5
|
+
# dsh-baton
|
|
8
6
|
|
|
9
|
-
|
|
10
|
-
macOS · Windows (<a href="WINDOWS.md">guide</a>) · no Node.js needed · zero dependencies · MIT
|
|
11
|
-
</p>
|
|
7
|
+
Hand coding tasks from Claude Code or Codex to DeepSeek Harness on your machine: the task runs live in the Harness window, the main agent is woken when it ends, and it gets back the files that changed, the project's own test results and a requirement-by-requirement checklist.
|
|
12
8
|
|
|
13
|
-
|
|
9
|
+
- **Cheaper on big jobs:** with Claude Code, the total cost, DeepSeek included, is 51–78% lower.
|
|
10
|
+
- **Less of the 5-hour window:** on a long Codex job, a ChatGPT Plus 5-hour window goes from 86.01% used to 16.01% with GPT-6 Astra, and from 13.71% to 2.67% with GPT-6.1 Sol.
|
|
11
|
+
- **Nothing to watch or poll:** the agent is woken once when the run ends, and the result already holds the changed files, the test output and the requirement checklist.
|
|
14
12
|
|
|
15
|
-
|
|
13
|
+
Needs DeepSeek Harness ≥ 0.2.1-rc.2 (signed in) and Claude Code or Codex; macOS / Windows, zero dependencies. [Quick start →](#quick-start)
|
|
16
14
|
|
|
17
15
|
<p align="center">
|
|
18
|
-
<img src="./assets/readme/
|
|
16
|
+
<img src="./assets/readme/en/hero.gif" width="100%" alt="dsh-baton: pass the baton from Claude Code or Codex to DeepSeek Harness, watch it run live, and get it handed back when it's done">
|
|
19
17
|
</p>
|
|
20
18
|
|
|
21
|
-
|
|
22
|
-
<img src="./assets/readme/flow.gif" width="100%" alt="Typical flow: create a workspace, send the task, wait without polling (Claude Code and Codex are woken when the run ends), follow up in the same session">
|
|
23
|
-
</p>
|
|
19
|
+
## What it is
|
|
24
20
|
|
|
25
|
-
|
|
21
|
+
dsh-baton is an MCP server plus a small Harness plugin. Your main agent, Claude Code or Codex, writes the task and hands it over with one tool call. The task runs in a normal Harness session that you can watch, and step into, in the Harness window while it works. When it ends, the main agent is woken once instead of polling. It gets back evidence rather than a summary: which files were created, changed or deleted, the output of your project's own tests, and a point-by-point check against the request. Unlike a plain MCP bridge to another model, the work stays visible in the Harness UI and the proof comes back with the result.
|
|
26
22
|
|
|
27
|
-
|
|
23
|
+
## Quick start
|
|
28
24
|
|
|
29
25
|
<p align="center">
|
|
30
|
-
<img src="./assets/
|
|
26
|
+
<img src="./assets/readme/en/install.svg" width="100%" alt="Step 1: in Harness open Plugins, Add plugin, enter dsh-baton-host, Install, Enable now; it registers dsh-baton for Claude Code and Codex. Step 2, Codex only, once: Settings, Hooks, trust dsh-baton: waiting for the Harness run. Step 3: in a new chat, ask for dsh_list_workspaces">
|
|
31
27
|
</p>
|
|
32
28
|
|
|
33
|
-
|
|
29
|
+
Nothing else to install: no Node.js, no terminal. The steps are the same on macOS and Windows.
|
|
34
30
|
|
|
35
|
-
|
|
36
|
-
<img src="./assets/demos/s8-relay-en-zoom.gif" width="100%" alt="Harness chat where messages alternate between Codex and Claude, each labelled Sent by ChatGPT or Sent by Claude from another chat">
|
|
37
|
-
</p>
|
|
31
|
+
**1. Add the plugin in Harness:** **Plugins** (插件) → **Add plugin** (添加插件) → type `dsh-baton-host` → **Install** (安装) → **Enable now** (立即启用).
|
|
38
32
|
|
|
39
|
-
|
|
33
|
+
Switched on, the plugin registers dsh-baton for Claude Code and Codex, for every folder, and Harness says so.
|
|
40
34
|
|
|
41
|
-
|
|
35
|
+
**2. Codex only, once:** Codex **Settings → Hooks** → **Trust** **dsh-baton: waiting for the Harness run**.
|
|
42
36
|
|
|
43
|
-
|
|
44
|
-
<img src="./assets/readme/install.svg" width="100%" alt="Step 1: in Harness open Plugins, Add plugin, enter dsh-baton-host, Install, Enable now; it registers dsh-baton for Claude Code and Codex. Step 2, Codex only, once: Settings, Hooks, trust dsh-baton: waiting for the Harness run. Step 3: in a new chat, ask for dsh_list_workspaces">
|
|
45
|
-
</p>
|
|
37
|
+
If the hook is not in the list yet, or you want to know what trusting it changes:
|
|
46
38
|
|
|
47
|
-
|
|
39
|
+
<details>
|
|
40
|
+
<summary>The Codex hook</summary>
|
|
48
41
|
|
|
49
|
-
|
|
42
|
+
The hook comes with plugin 0.6.0 and later (0.6.2 and later when you have no Node.js), and shows up in that list only after you restart Harness and then Codex once, after installing or updating the plugin. Node.js is not required: without one it runs on Harness's own runtime, like the server. Codex is then woken when a run ends instead of checking every 270 s. You trust it once: updates and Harness restarts do not ask again. Skip it and Codex waits with `dsh_wait` instead; the first hand-over reminds you.
|
|
50
43
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
**2. Codex only, once:** Codex **Settings → Hooks** → **Trust** **dsh-baton: waiting for the Harness run**. The hook comes with plugin 0.6.0 and later (0.6.2 and later when you have no Node.js), and shows up in that list only after you restart Harness and then Codex once, after installing or updating the plugin. Node.js is not required: without one it runs on Harness's own runtime, like the server. Codex is then woken when a run ends instead of checking every 270 s. You trust it once: updates and Harness restarts do not ask again. Skip it and Codex waits with `dsh_wait` as before; the first hand-over reminds you.
|
|
44
|
+
</details>
|
|
54
45
|
|
|
55
|
-
**3. Check:** in a **new** Claude Code or Codex conversation, ask it to call `dsh_list_workspaces`. It lists your Harness workspaces.
|
|
46
|
+
**3. Check:** in a **new** Claude Code or Codex conversation, ask it to call `dsh_list_workspaces`. It lists your Harness workspaces. Codex asks you to approve MCP tool calls: approve the `dsh-baton` tools (for unattended `codex exec` runs, add `--approve-for-me`).
|
|
56
47
|
|
|
57
48
|
That is all. If Harness says it could not register, run `npx -y dsh-baton-mcp setup` in a terminal (this needs Node.js ≥ 22.15); it does the same registration. Updating, removing and Windows details are [further down](#install-details).
|
|
58
49
|
|
|
59
|
-
##
|
|
50
|
+
## See it run
|
|
60
51
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
52
|
+
*Claude Code and Codex each hand DeepSeek Harness a task, and its subagents build this rainy-night street in 1 h 41 min. 40 s, sound on.*
|
|
53
|
+
|
|
54
|
+
https://github.com/user-attachments/assets/72e2fdff-275e-48bc-8e5a-39ffb025a0a8
|
|
64
55
|
|
|
65
56
|
<p align="center">
|
|
66
|
-
<img src="./assets/readme/
|
|
57
|
+
<img src="./assets/readme/en/flow.gif" width="100%" alt="Typical flow: create a workspace, send the task, wait without polling (Claude Code and Codex are woken when the run ends), follow up in the same session">
|
|
67
58
|
</p>
|
|
68
59
|
|
|
69
|
-
|
|
70
|
-
- **Claude Code** waits for a run in the background (the `wait_command` that `dsh_send` returns) and is woken once, when the run ends.
|
|
71
|
-
- **Codex** with the dsh-baton Stop hook trusted (from 0.6.1): after `dsh_send` it ends its turn; the hook waits for the run outside the model and, when the run ends or asks something, continues the same conversation with the result. One model request per run instead of one every 270 s.
|
|
72
|
-
- **Codex** without the hook waits inside `dsh_wait`, up to 270 s per call, just under its 300 s tool timeout. An unfinished wait returns only the state, so Codex calls again without summarising. Codex gets no `wait_command`: its terminal cannot wake it, so it would poll.
|
|
73
|
-
- Codex asks you to approve MCP tool calls: approve the `dsh-baton` tools. For unattended `codex exec` runs, add `--approve-for-me`.
|
|
74
|
-
|
|
75
|
-
**Pick a model** (from 0.6.1): just say which one, for example "hand it to DeepSeek, V4 Pro". `dsh_send` passes it as `model` (and `reasoning_effort`), matched against the models Harness lists, so a model Harness adds later works as soon as it shows up. It applies to that session only; Harness's default model stays as it was. `dsh_list_models` shows what is available.
|
|
60
|
+
One Codex chat starts a task, queues a follow-up, steers the running turn, and waits for all three:
|
|
76
61
|
|
|
77
|
-
|
|
62
|
+
<p align="center">
|
|
63
|
+
<img src="./assets/demos/s3-queue-en.gif" width="100%" alt="Codex on the left starts a DeepSeek task, queues a follow-up, steers the running turn and waits; DeepSeek Harness on the right runs each one live">
|
|
64
|
+
</p>
|
|
78
65
|
|
|
79
|
-
|
|
66
|
+
Codex and Claude take turns in the same Harness session, and every message is labelled with who sent it:
|
|
80
67
|
|
|
81
68
|
<p align="center">
|
|
82
|
-
<img src="./
|
|
69
|
+
<img src="./assets/demos/s8-relay-en-zoom.gif" width="100%" alt="Harness chat where messages alternate between Codex and Claude, each labelled Sent by ChatGPT or Sent by Claude from another chat">
|
|
83
70
|
</p>
|
|
84
71
|
|
|
72
|
+
<p align="center"><a href="docs/demos.md">More demos →</a></p>
|
|
73
|
+
|
|
85
74
|
## Token usage
|
|
86
75
|
|
|
87
|
-
**In short: hand over the big, long jobs.**
|
|
76
|
+
**In short: hand over the big, long jobs.** With Claude Code the total cost, DeepSeek included, is 51–78% lower. With Codex on GPT-6 Astra it is 47–81% lower, and the long kanban app takes 16.01% of a ChatGPT Plus 5-hour window instead of 86.01%.
|
|
88
77
|
|
|
89
|
-
**The
|
|
78
|
+
**The three test tasks**, asked the way a user would (the requests are in Chinese, linked as written):
|
|
79
|
+
- **Task 1 · CLI tool:** a command-line task manager in Node, no dependencies, split into modules, with tests. [Request](docs/benchmarks/token-usage-2026-10/natural/medium.md)
|
|
80
|
+
- **Task 2 · Web app:** a task manager with a Node server and a page, REST API, stats, tests and a README. [Request](docs/benchmarks/token-usage-2026-10/natural/large.md)
|
|
81
|
+
- **Task 3 · Kanban app (phased):** a kanban board in plain Node and a plain front end, in three phases given one after another in the same conversation: ① the core (REST API, storage, drag and drop); ② labels, due dates, search syntax, archive, undo and redo, activity log, import and export; ③ version conflicts, WIP limits, bulk actions, live sync over SSE, stats. 55 acceptance tests written beforehand, the same for both arms. Requests: [phase 1](docs/benchmarks/token-usage-2026-10/xxl/seed/docs/phase1.md) · [phase 2](docs/benchmarks/token-usage-2026-10/xxl/seed/docs/phase2.md) · [phase 3](docs/benchmarks/token-usage-2026-10/xxl/seed/docs/phase3.md)
|
|
90
82
|
|
|
91
|
-
|
|
83
|
+
| Task 3 | Alone | Handed over |
|
|
84
|
+
|---|---|---|
|
|
85
|
+
| Opus 5.5 high | 55/55, 39 min | 55/55, 93 min |
|
|
86
|
+
| GPT-6.1 Sol high | 55/55, 18 min | 55/55, 101 min |
|
|
87
|
+
| GPT-6 Astra high | 55/55, about 31 min | 55/55, about 87 min |
|
|
88
|
+
|
|
89
|
+
Cost per task at list price, DeepSeek V4.1 Flash 1017 at its peak price. Opus 5.5 runs in Claude Code, GPT-6.1 Sol and GPT-6 Astra in Codex, all at high effort:
|
|
90
|
+
|
|
91
|
+
| Task | Model | Alone | Handed over: main agent | Handed over: DeepSeek | Handed over: total | Total | Main agent | 5-hour window, alone → handed over |
|
|
92
|
+
|---|---|---|---|---|---|---|---|---|
|
|
93
|
+
| Task 1 · CLI tool | Opus 5.5 high | $1.07 | $0.42 | $0.10 | $0.52 | −51.2% | −60.8% | — |
|
|
94
|
+
| | GPT-6.1 Sol high | $0.17 | $0.06 | $0.13 | $0.20 | +15.3% | −62.9% | 2.08% → 0.77% |
|
|
95
|
+
| | GPT-6 Astra high | $0.79 | $0.30 | $0.12 | $0.42 | −47.4% | −62.0% | 7.83% → 2.98% |
|
|
96
|
+
| Task 2 · Web app | Opus 5.5 high | $2.13 | $0.47 | $0.13 | $0.60 | −71.7% | −77.7% | — |
|
|
97
|
+
| | GPT-6.1 Sol high | $0.41 | $0.06 | $0.18 | $0.25 | −40.2% | −84.5% | 5.08% → 0.79% |
|
|
98
|
+
| | GPT-6 Astra high | $2.27 | $0.30 | $0.13 | $0.42 | −81.4% | −86.9% | 22.43% → 2.94% |
|
|
99
|
+
| Task 3 · Kanban app (phased) | Opus 5.5 high | $14.18 | $2.32 | $0.84 | $3.16 | −77.7% | −83.6% | — |
|
|
100
|
+
| | GPT-6.1 Sol high | $1.12 | $0.22 | $0.81 | $1.03 | −8.1% | −80.5% | 13.71% → 2.67% |
|
|
101
|
+
| | GPT-6 Astra high | $8.69 | $1.62 | $0.74 | $2.35 | −72.9% | −81.4% | 86.01% → 16.01% |
|
|
92
102
|
|
|
93
|
-
|
|
94
|
-
-
|
|
95
|
-
|
|
103
|
+
<p align="center">
|
|
104
|
+
<img src="./docs/benchmarks/token-usage-2026-10/charts/cost.svg" width="100%" alt="Cost per task in USD, working alone vs handed over, grouped by model. Opus 5.5 high: $1.07 vs $0.52, $2.13 vs $0.60, $14.18 vs $3.16. GPT-6.1 Sol high: $0.17 vs $0.20, $0.41 vs $0.25, $1.12 vs $1.03. GPT-6 Astra high: $0.79 vs $0.42, $2.27 vs $0.42, $8.69 vs $2.35">
|
|
105
|
+
</p>
|
|
96
106
|
|
|
97
107
|
<p align="center">
|
|
98
|
-
<img src="./docs/benchmarks/token-usage-2026-10/charts/
|
|
108
|
+
<img src="./docs/benchmarks/token-usage-2026-10/charts/quota.svg" width="100%" alt="Share of a ChatGPT Plus 5-hour window in Codex, working alone vs handed over, grouped by model. GPT-6.1 Sol high: 2.08% vs 0.77%, 5.08% vs 0.79%, 13.71% vs 2.67%. GPT-6 Astra high: 7.83% vs 2.98%, 22.43% vs 2.94%, 86.01% vs 16.01%">
|
|
99
109
|
</p>
|
|
100
110
|
|
|
101
|
-
|
|
111
|
+
**Hand it over:** big jobs, long jobs, and whenever the 5-hour window is running low. The main agent's own spend drops 61–87% on every task and model.
|
|
102
112
|
|
|
103
|
-
|
|
104
|
-
|---|---|---|---|---|
|
|
105
|
-
| Claude Code · Opus 5.5 high | Task 1 · CLI tool | $1.07 | $0.42 (−61%) | $0.52 (−51%) |
|
|
106
|
-
| Claude Code · Opus 5.5 high | Task 2 · Web app | $2.13 | $0.47 (−78%) | $0.60 (−72%) |
|
|
107
|
-
| Codex · GPT-6.1 Sol high | Task 1 · CLI tool | $0.17 | $0.06 (−63%) | $0.20 (+15%) |
|
|
108
|
-
| Codex · GPT-6.1 Sol high | Task 2 · Web app | $0.41 | $0.06 (−84%) | $0.25 (−40%) |
|
|
109
|
-
| Codex · GPT-6 Astra high | Task 1 · CLI tool | $0.79 | $0.30 (−62%) | $0.42 (−47%) |
|
|
110
|
-
| Codex · GPT-6 Astra high | Task 2 · Web app | $2.27 | $0.30 (−87%) | $0.42 (−81%) |
|
|
113
|
+
**Do it yourself:** small tasks on Codex with GPT-6.1 Sol. Sol is cheap enough that DeepSeek's part of a hand-over, two thirds or more, outweighs what Sol saves.
|
|
111
114
|
|
|
112
|
-
<
|
|
113
|
-
|
|
114
|
-
</p>
|
|
115
|
+
<details>
|
|
116
|
+
<summary>What was tested, and how</summary>
|
|
115
117
|
|
|
116
|
-
|
|
118
|
+
- **The only difference** in the hand-over runs is one line added at the end of the request: 交给 DeepSeek Harness 去做。 ("Hand it to DeepSeek Harness.")
|
|
119
|
+
- Same model and effort in both arms, and every run judged by the same acceptance check, which the model never sees.
|
|
120
|
+
- Tasks 1 and 2: the mean of three runs per cell. Task 3: one run per cell. Every run passed its acceptance check, with no rework.
|
|
121
|
+
- Prices are list prices. DeepSeek is counted at its peak price; the real DeepSeek bill came to about 58% of that.
|
|
122
|
+
- The 5-hour window share is worked out from each run's cost: 12.27 points per $1 for GPT-6.1 Sol, 9.90 for GPT-6 Astra.
|
|
123
|
+
- From 0.6.1 Codex is woken by its Stop hook instead of polling with `dsh_wait`, and 0.6.2 cuts the MCP instructions to one sentence, so Codex reads a few thousand to over ten thousand fewer new input tokens per hand-over.
|
|
117
124
|
|
|
118
|
-
|
|
125
|
+
</details>
|
|
119
126
|
|
|
120
|
-
|
|
127
|
+
## How it works
|
|
121
128
|
|
|
122
129
|
<p align="center">
|
|
123
|
-
<img src="./
|
|
130
|
+
<img src="./assets/readme/en/architecture.svg" width="100%" alt="MCP clients call dsh-baton-mcp over stdio; it reaches the dsh-baton-host plugin inside Harness through an owner-only local socket; the plugin drives Harness sessions live, with dsh headless as fallback">
|
|
124
131
|
</p>
|
|
125
132
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
| 5-hour window | 86% | about 16% (−81%) |
|
|
132
|
-
| Acceptance tests | 55/55, first try | 55/55, first try |
|
|
133
|
-
| Time | about 31 min | about 87 min |
|
|
134
|
-
| Lines of code | 1,876 | 9,953 |
|
|
133
|
+
<p align="center">
|
|
134
|
+
<img src="./assets/readme/en/comparison.svg" width="100%" alt="Plugin vs headless: only the plugin shows runs live in the Harness UI, continues desktop sessions and uses desktop agent presets; both reuse the desktop sign-in">
|
|
135
|
+
</p>
|
|
136
|
+
|
|
137
|
+
**Pick a model:** just say which one, for example "hand it to DeepSeek, V4 Pro". `dsh_send` passes it as `model` (and `reasoning_effort`), matched against the models Harness lists, so a model Harness adds later works as soon as it shows up. It applies to that session only; Harness's default model stays as it was. `dsh_list_models` shows what is available.
|
|
135
138
|
|
|
136
|
-
|
|
139
|
+
Nothing to configure for waiting; if you want to know what each client does while a run is going:
|
|
137
140
|
|
|
138
141
|
<details>
|
|
139
|
-
<summary>
|
|
142
|
+
<summary>How each client waits</summary>
|
|
140
143
|
|
|
141
|
-
- **
|
|
142
|
-
-
|
|
143
|
-
-
|
|
144
|
-
- Prices are list prices. DeepSeek is counted at its peak price, the most conservative case; the real DeepSeek bill came to about 58% of that.
|
|
145
|
-
- Every run: [run log](docs/benchmarks/token-usage-2026-10/LOG.md) (section "0.6.2 hand-over rerun"); raw data in `docs/benchmarks/token-usage-2026-10/results/*-n062*.jsonl`; method and earlier builds in the [full report](docs/benchmarks/token-usage-2026-10.md).
|
|
144
|
+
- **Claude Code** waits for a run in the background (the `wait_command` that `dsh_send` returns) and is woken once, when the run ends.
|
|
145
|
+
- **Codex** with the dsh-baton Stop hook trusted (from 0.6.1): after `dsh_send` it ends its turn; the hook waits for the run outside the model and, when the run ends or asks something, continues the same conversation with the result. One model request per run instead of one every 270 s.
|
|
146
|
+
- **Codex** without the hook waits inside `dsh_wait`, up to 270 s per call, just under its 300 s tool timeout. An unfinished wait returns only the state, so Codex calls again without summarising. Codex gets no `wait_command`: its terminal cannot wake it, so it would poll.
|
|
146
147
|
|
|
147
148
|
</details>
|
|
148
149
|
|
|
150
|
+
### How a hand-over is checked
|
|
151
|
+
|
|
152
|
+
You don't have to take DeepSeek's word for it. Before the run, dsh-baton notes what is in the folder. When the run ends it shows exactly which files were created, changed or deleted, and runs your project's own tests itself (`npm test`, or whatever the client passes as `checks`). A second, read-only DeepSeek agent then goes through the request point by point and ends with a table: requirement, met or not, and where (file:line). Claude or GPT reads just that, sends anything missing back in one message, and opens code only where a row says "not met". Unless `cwd` names another folder, the new Harness session starts in the folder you're already working in.
|
|
153
|
+
|
|
154
|
+
<p align="center">
|
|
155
|
+
<img src="./docs/benchmarks/token-usage-2026-10/charts/evidence-chain.svg" width="100%" alt="Evidence chain of one real hand-over: the user asks, Codex writes the spec and checks, dsh-baton snapshots the folder, DeepSeek Harness writes 11 files, dsh-baton lists the changed files and runs npm test itself, Codex sends one fix back on its own, dsh-baton checks again, Codex accepts, and the benchmark's own test passes 13 of 13">
|
|
156
|
+
</p>
|
|
157
|
+
|
|
149
158
|
## Tools
|
|
150
159
|
|
|
151
160
|
<p align="center">
|
|
152
|
-
<img src="./assets/readme/tools.svg" width="100%" alt="Ten tools in the tool list: list workspaces, list sessions, read session, list models, send, status, wait, answer, cancel, call. Six less used tools are reached through dsh_call: create and rename workspace, session activity, rename session, update queued, list runs">
|
|
161
|
+
<img src="./assets/readme/en/tools.svg" width="100%" alt="Ten tools in the tool list: list workspaces, list sessions, read session, list models, send, status, wait, answer, cancel, call. Six less used tools are reached through dsh_call: create and rename workspace, session activity, rename session, update queued, list runs">
|
|
153
162
|
</p>
|
|
154
163
|
|
|
155
164
|
<p align="center">
|
|
156
|
-
<img src="./assets/readme/states.svg" width="100%" alt="Run states: queued, running, waiting, completed, failed, cancelled, and interrupted when Harness restarts mid-run. A busy session takes followup (queued) or steer (interjects) messages">
|
|
165
|
+
<img src="./assets/readme/en/states.svg" width="100%" alt="Run states: queued, running, waiting, completed, failed, cancelled, and interrupted when Harness restarts mid-run. A busy session takes followup (queued) or steer (interjects) messages">
|
|
157
166
|
</p>
|
|
158
167
|
|
|
168
|
+
When you want the exact parameters and return fields of each tool:
|
|
169
|
+
|
|
159
170
|
<details>
|
|
160
171
|
<summary>Full tool reference</summary>
|
|
161
172
|
|
|
@@ -168,7 +179,7 @@ The server's MCP instructions are a single sentence (from 0.6.2); how to hand ov
|
|
|
168
179
|
| `dsh_list_sessions` | Sessions newest first, with title, folder, running state and `model` (what its next request uses); filter by folder or text. Archived sessions are left out |
|
|
169
180
|
| `dsh_list_models` | The models Harness can run (provider, id, name, reasoning efforts) and its default model |
|
|
170
181
|
| `dsh_read_session` | Recent prompts and replies of one session (with tool names); `archived: true` for an archived session |
|
|
171
|
-
| `dsh_send` | Send a message: `session_id` continues that session; otherwise a new one starts in `cwd` or `workspace_id`, or, with neither, in the folder the conversation works in (a `cwd` that is not a workspace yet is registered first; a `cwd` outside the conversation's folder is allowed and noted in `folder_note`). `checks`: test commands dsh-baton runs itself when the run ends (see How a hand-over is checked). Returns a `run_id` at once. In Harness the message is marked "Sent by Claude / ChatGPT from another chat". An archived session is refused: unarchive it in Harness, or start a new session. To a busy session, `mode` `followup` queues the message after the current turn and `steer` adds it to the running turn; left out, it does what Harness's own setting says (Settings → sending while busy: queue or steer), and `followup` when that cannot be read. `mode_source` says which applied. `model` only when the user names one ("DeepSeek-V4 Pro", "v4-pro"; `reasoning_effort` likewise): it is matched against Harness's own model list, ignoring case, spaces, hyphens and a leading DeepSeek, so a model Harness adds later (V4.1 Pro) works as soon as Harness lists it; a name that matches nothing or several fails the send, before anything is created, with the list (`provider/model` picks one). It is set on that session before the message goes in, and Harness's default model is put back as it was (Harness saves a session's pick as the default too). Left out, a continued session keeps its own model and a new one uses Harness's default. The reply says which in `model` and `model_source` |
|
|
182
|
+
| `dsh_send` | Send a message: `session_id` continues that session; otherwise a new one starts in `cwd` or `workspace_id`, or, with neither, in the folder the conversation works in (a `cwd` that is not a workspace yet is registered first; a `cwd` outside the conversation's folder is allowed and noted in `folder_note`). `checks`: test commands dsh-baton runs itself when the run ends (see [How a hand-over is checked](#how-a-hand-over-is-checked)). Returns a `run_id` at once. In Harness the message is marked "Sent by Claude / ChatGPT from another chat". An archived session is refused: unarchive it in Harness, or start a new session. To a busy session, `mode` `followup` queues the message after the current turn and `steer` adds it to the running turn; left out, it does what Harness's own setting says (Settings → sending while busy: queue or steer), and `followup` when that cannot be read. `mode_source` says which applied. `model` only when the user names one ("DeepSeek-V4 Pro", "v4-pro"; `reasoning_effort` likewise): it is matched against Harness's own model list, ignoring case, spaces, hyphens and a leading DeepSeek, so a model Harness adds later (V4.1 Pro) works as soon as Harness lists it; a name that matches nothing or several fails the send, before anything is created, with the list (`provider/model` picks one). It is set on that session before the message goes in, and Harness's default model is put back as it was (Harness saves a session's pick as the default too). Left out, a continued session keeps its own model and a new one uses Harness's default. The reply says which in `model` and `model_source` |
|
|
172
183
|
| `dsh_status` | Run progress: state, recent text and tool calls, final reply; `waiting_on` when the turn is over but sub-agents or jobs still run; `human_interjections` when someone typed in the Harness UI mid-run; state `interrupted` for a run cut short by a Harness restart; `model` the run was sent with |
|
|
173
184
|
| `dsh_wait` | Codex with the Stop hook trusted does not need it. Wait for the run to end; a finished run carries `evidence` (files changed, check results) when the message was sent with `checks` (or until it stops to ask a person, see `dsh_answer`): up to 270 s per call for Codex, 50 s for other clients. Still going: returns `timed_out` with just the state; call it again. Clients that can wait in the background use `dsh_send`'s `wait_command` instead. `dsh_status` is a one-off look, not a way to wait |
|
|
174
185
|
| `dsh_answer` | Answer the questions a run stopped to ask (Harness's ask_user_question): per question, an option number or label, free text, or skip; one at a time or all at once. `dsh_wait` and `wait_command` return as soon as a run asks, with the questions (`needs_input`, `question`; the option Harness's model would pick is in `harness_suggested`, apart from the labels). The assistant answers by its own judgment only when you told it to decide, and then passes `reason` and tells you what it chose and why; otherwise it brings you the questions, starting with its own recommendation and reason. You can also answer in the Harness window: whoever answers first wins |
|
|
@@ -182,7 +193,12 @@ The server's MCP instructions are a single sentence (from 0.6.2); how to hand ov
|
|
|
182
193
|
|
|
183
194
|
</details>
|
|
184
195
|
|
|
185
|
-
##
|
|
196
|
+
## Command line and configuration
|
|
197
|
+
|
|
198
|
+
When you want to drive Harness from a terminal or a script instead of a chat:
|
|
199
|
+
|
|
200
|
+
<details>
|
|
201
|
+
<summary>CLI commands</summary>
|
|
186
202
|
|
|
187
203
|
```bash
|
|
188
204
|
dsh-baton workspaces
|
|
@@ -199,13 +215,20 @@ dsh-baton rename session-xxxx "New title"
|
|
|
199
215
|
dsh-baton rename-workspace ~/code/new-app "New name"
|
|
200
216
|
```
|
|
201
217
|
|
|
202
|
-
|
|
218
|
+
(From source, use `node src/cli.mjs …`.)
|
|
219
|
+
|
|
220
|
+
</details>
|
|
221
|
+
|
|
222
|
+
When the defaults don't fit, for example a different socket path or forcing headless mode:
|
|
223
|
+
|
|
224
|
+
<details>
|
|
225
|
+
<summary>Environment variables</summary>
|
|
203
226
|
|
|
204
227
|
| Variable | Default | Meaning |
|
|
205
228
|
|---|---|---|
|
|
206
229
|
| `DSH_BATON_BACKEND` | `auto` | `auto`, `host` or `headless` |
|
|
207
230
|
| `DSH_BATON_HOME` | `~/.dsh-baton` | Socket and headless run records |
|
|
208
|
-
| `DSH_BATON_SOCKET` | `$DSH_BATON_HOME/host.sock` | Plugin socket path (must match on both sides); on Windows mapped to a per-user named pipe |
|
|
231
|
+
| `DSH_BATON_SOCKET` | `$DSH_BATON_HOME/host.sock` | Plugin socket path (must match on both sides); on Windows mapped to a per-user named pipe, see [WINDOWS.md](WINDOWS.md) |
|
|
209
232
|
| `DSH_BATON_WAIT_S` | `270` for Codex, else `50` | How long one `dsh_wait` call waits; set it only if you raised the client's tool timeout, and keep it below that |
|
|
210
233
|
| `DSH_BATON_CODEX_HOOK` | `1` | `0` (set for Harness) stops the plugin adding the Codex Stop hook |
|
|
211
234
|
| `DSH_BIN` | the `dsh` inside the macOS app, else `PATH` | Harness CLI for headless mode |
|
|
@@ -213,6 +236,10 @@ dsh-baton rename-workspace ~/code/new-app "New name"
|
|
|
213
236
|
| `DSH_BATON_ACCOUNT` | `1` | `0` uses the API-key route in headless mode |
|
|
214
237
|
| `DSH_BATON_PATCHES` | empty | Extra headless `--patch` overlays, `:`-separated |
|
|
215
238
|
|
|
239
|
+
</details>
|
|
240
|
+
|
|
241
|
+
When the Harness plugin is not reachable:
|
|
242
|
+
|
|
216
243
|
<details>
|
|
217
244
|
<summary>Headless fallback details</summary>
|
|
218
245
|
|
|
@@ -222,6 +249,8 @@ When the plugin is unreachable, the MCP server falls back to Harness's `dsh head
|
|
|
222
249
|
|
|
223
250
|
## Install details
|
|
224
251
|
|
|
252
|
+
When you update or remove the plugin, come from dsh-bridge, run from source, or the plugin could not register:
|
|
253
|
+
|
|
225
254
|
<details>
|
|
226
255
|
<summary>What the plugin does, updating, removing, upgrading from dsh-bridge, installing from source, Windows</summary>
|
|
227
256
|
|
|
@@ -229,7 +258,7 @@ When the plugin is unreachable, the MCP server falls back to Harness's `dsh head
|
|
|
229
258
|
|
|
230
259
|
**The Codex Stop hook.** The plugin adds one `[[hooks.Stop]]` block, marked `# dsh-baton`, to `~/.codex/config.toml`; Codex shows it as **dsh-baton: waiting for the Harness run** and runs it only once you trust it (dsh-baton never trusts it for you). The block only names a small script, `~/.dsh-baton/bin/codex-stop-hook` (`codex-stop-hook.cmd` on Windows), and everything that can move, Node.js and the server, lives in that script. So the block itself never changes: updates, same-version refreshes and Harness restarts leave it as it is, and your trust stays. If the block is ever rewritten, dsh-baton drops the old trust record, and Codex asks you to trust it again. Windows uses a `.cmd` because Codex runs hooks through `PowerShell -Command`, which cannot start a quoted program path. Without a Node.js of your own, the script runs Harness's own app in Node mode (`ELECTRON_RUN_AS_NODE=1`), as the server does; the plugin rewrites it on each start, so it follows Harness updates. Not added without Codex, with `DSH_BATON_CODEX_HOOK=0`, or when Codex's dsh-baton entry names no server folder (a bare `npx` command); the first hand-over in a Codex conversation then says why. `dsh-baton setup --codex-hook` adds it by hand, `setup --remove` takes it out, `DSH_BATON_CODEX_HOOK=0` for Harness stops the plugin adding it.
|
|
231
260
|
|
|
232
|
-
**Updates need no restart.** When a newer version lands in `~/.dsh-baton/app` (a plugin update, `setup`, or a copy by hand), or the same version with changed files, conversations already open in Claude Code and Codex use it from their next `dsh_*` call: the server checks the folder before each call, starts the new version, and says once in that call's result that it was updated. A `dsh_wait` that is already running finishes on the old version first. If the new version cannot start, the old one keeps working and the result says why. A tool added in the new version shows up in Claude Code at once (the server tells the client its tool list changed); Codex keeps the tool list a conversation started with, so it does not show a new tool until the next conversation;
|
|
261
|
+
**Updates need no restart.** When a newer version lands in `~/.dsh-baton/app` (a plugin update, `setup`, or a copy by hand), or the same version with changed files, conversations already open in Claude Code and Codex use it from their next `dsh_*` call: the server checks the folder before each call, starts the new version, and says once in that call's result that it was updated. A `dsh_wait` that is already running finishes on the old version first. If the new version cannot start, the old one keeps working and the result says why. A tool added in the new version shows up in Claude Code at once (the server tells the client its tool list changed); Codex keeps the tool list a conversation started with, so it does not show a new tool until the next conversation; it can still use one at once through `dsh_call`, which the update note points to, and the tools it already has run the new code right away.
|
|
233
262
|
|
|
234
263
|
**`npx -y dsh-baton-mcp setup`** does the same registration from a terminal: for a plugin that could not register, or when you come from an MCP directory. With the plugin already registered it changes nothing, then points you to the plugin if it is missing.
|
|
235
264
|
|
|
@@ -243,6 +272,10 @@ When the plugin is unreachable, the MCP server falls back to Harness's `dsh head
|
|
|
243
272
|
|
|
244
273
|
**Windows.** Same steps; [WINDOWS.md](WINDOWS.md) covers what differs (a named pipe instead of a socket file) and fixes for a Plugins page that cannot install or uninstall.
|
|
245
274
|
|
|
275
|
+
</details>
|
|
276
|
+
|
|
277
|
+
When you register by hand, or want dsh-baton in the Claude desktop chat app (not Claude Code):
|
|
278
|
+
|
|
246
279
|
<details>
|
|
247
280
|
<summary>Manual registration and the Claude desktop app</summary>
|
|
248
281
|
|
|
@@ -263,22 +296,18 @@ codex mcp add dsh-baton -- "$(command -v node)" "$PWD/dsh-baton/src/server.mjs"
|
|
|
263
296
|
|
|
264
297
|
</details>
|
|
265
298
|
|
|
266
|
-
</details>
|
|
267
|
-
|
|
268
299
|
## Security and limits
|
|
269
300
|
|
|
270
301
|
<p align="center">
|
|
271
|
-
<img src="./assets/readme/security.svg" width="100%" alt="Owner-only channel (socket 0600 on macOS and Linux, per-user named pipe on Windows), no network port, Harness sandbox and approvals apply">
|
|
302
|
+
<img src="./assets/readme/en/security.svg" width="100%" alt="Owner-only channel (socket 0600 on macOS and Linux, per-user named pipe on Windows), no network port, Harness sandbox and approvals apply">
|
|
272
303
|
</p>
|
|
273
304
|
|
|
274
305
|
<p align="center">
|
|
275
|
-
<img src="./assets/readme/limits.svg" width="100%" alt="Good to know: a message typed in the UI mid-run is flagged on the run as a human interjection, run records survive a Harness restart (cut-short runs show interrupted), Claude Code and Codex are woken once when a run ends">
|
|
306
|
+
<img src="./assets/readme/en/limits.svg" width="100%" alt="Good to know: a message typed in the UI mid-run is flagged on the run as a human interjection, run records survive a Harness restart (cut-short runs show interrupted), Claude Code and Codex are woken once when a run ends">
|
|
276
307
|
</p>
|
|
277
308
|
|
|
278
309
|
Any process running as your OS user can drive Harness through the socket, the same as running `dsh` directly. dsh-baton relies on Harness 0.2.1-series internal service APIs, so a major Harness update may need a follow-up release.
|
|
279
310
|
|
|
280
|
-
<p align="center"><a href="docs/demos.md">More demos →</a></p>
|
|
281
|
-
|
|
282
311
|
## License
|
|
283
312
|
|
|
284
313
|
MIT
|
package/README.zh-CN.md
CHANGED
|
@@ -1,42 +1,24 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<
|
|
3
|
-
</p>
|
|
4
|
-
|
|
5
|
-
<p align="center">
|
|
6
|
-
<a href="README.md">English</a> | 中文
|
|
7
|
-
</p>
|
|
8
|
-
|
|
9
|
-
<p align="center">
|
|
10
|
-
macOS · Windows(<a href="WINDOWS.md">说明</a>)· 不用装 Node.js · 零依赖 · MIT
|
|
2
|
+
<a href="README.md">English</a> | 中文 · macOS · Windows(<a href="WINDOWS.md">说明</a>)· 不用装 Node.js · 零依赖 · MIT
|
|
11
3
|
</p>
|
|
12
4
|
|
|
13
|
-
|
|
5
|
+
# dsh-baton
|
|
14
6
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
<p align="center">
|
|
18
|
-
<img src="./assets/readme/zh/features.svg" width="100%" alt="新建工作区和对话、续接指定对话、界面实时可见并标明发送方、Claude Code 和 Codex 在运行结束时被唤醒、不用另配 key 且可选 Harness 里的任意模型、macOS 和 Windows 零依赖">
|
|
19
|
-
</p>
|
|
20
|
-
|
|
21
|
-
<p align="center">
|
|
22
|
-
<img src="./assets/readme/zh/flow.gif" width="100%" alt="典型流程:新建工作区、派发任务、不轮询地等待(Claude Code 和 Codex 在运行结束时被唤醒)、在同一个对话里续接追问">
|
|
23
|
-
</p>
|
|
7
|
+
把 Claude Code 或 Codex 的编码任务交给本机的 DeepSeek Harness:任务在 Harness 界面里实时跑,跑完自动唤醒主 Agent,并带回改了哪些文件、项目自己的测试结果,以及逐条需求对照表。
|
|
24
8
|
|
|
25
|
-
|
|
9
|
+
- **大任务更省**:Claude Code 连 DeepSeek 一起算,总花费少 51–78%。
|
|
10
|
+
- **少用 5 小时额度**:Codex 长任务,ChatGPT Plus 5 小时额度用 GPT-6 Astra 从 86.01% 降到 16.01%,用 GPT-6.1 Sol 从 13.71% 降到 2.67%。
|
|
11
|
+
- **不用盯、不用轮询**:跑完只唤醒一次,结果自带改动文件、测试输出和需求对照。
|
|
26
12
|
|
|
27
|
-
|
|
13
|
+
需要 DeepSeek Harness ≥ 0.2.1-rc.2(已登录)和 Claude Code 或 Codex;macOS / Windows,零依赖。[快速上手 →](#快速上手)
|
|
28
14
|
|
|
29
15
|
<p align="center">
|
|
30
|
-
<img src="./assets/
|
|
16
|
+
<img src="./assets/readme/zh/hero.gif" width="100%" alt="dsh-baton:让 Claude Code 或 Codex 把接力棒交给 DeepSeek Harness,界面实时可见,跑完那一刻就交回给你">
|
|
31
17
|
</p>
|
|
32
18
|
|
|
33
|
-
|
|
19
|
+
## 这是什么
|
|
34
20
|
|
|
35
|
-
|
|
36
|
-
<img src="./assets/demos/s8-relay-zh-zoom.gif" width="100%" alt="Harness 对话里 Codex 和 Claude 的消息交替出现,每条都带“由 ChatGPT / Claude 从另一个聊天发送”的标签">
|
|
37
|
-
</p>
|
|
38
|
-
|
|
39
|
-
<p align="center"><a href="docs/demos.zh-CN.md">更多演示 →</a></p>
|
|
21
|
+
dsh-baton 是一个 MCP 服务器加一个 Harness 小插件。主 Agent(Claude Code 或 Codex)写好任务,用一次工具调用交出去。任务在一个普通的 Harness 对话里跑,你可以在 Harness 界面里实时看着,也可以随时插话。跑完时主 Agent 只被唤醒一次,不用轮询。它拿回的是证据而不是一段总结:新建、修改、删除了哪些文件,项目自己的测试输出,以及对照需求的逐条检查。和普通的 MCP 桥接另一个模型相比,区别在于整个过程在 Harness 界面里看得见,证据随结果一起带回来。
|
|
40
22
|
|
|
41
23
|
## 快速上手
|
|
42
24
|
|
|
@@ -44,108 +26,135 @@ Codex 和 Claude 轮流往同一个 Harness 对话发消息,每条都标明是
|
|
|
44
26
|
<img src="./assets/readme/zh/install.svg" width="100%" alt="第一步:Harness → 插件 → 添加插件 → 输入 dsh-baton-host → 安装 → 立即启用,它会为 Claude Code 和 Codex 注册 dsh-baton。第二步(仅 Codex,一次):设置 → 钩子 → 信任 dsh-baton: waiting for the Harness run。第三步:新开对话,让它调用 dsh_list_workspaces">
|
|
45
27
|
</p>
|
|
46
28
|
|
|
47
|
-
|
|
29
|
+
别的都不用装:不用 Node.js,也不用开终端。macOS 和 Windows 步骤完全相同。
|
|
48
30
|
|
|
49
31
|
**1. 在 Harness 里添加插件:** 插件 → 添加插件 → 输入 `dsh-baton-host` → 安装 → 立即启用。
|
|
50
32
|
|
|
51
33
|
插件启用后会为 Claude Code 和 Codex 注册 dsh-baton(对所有文件夹生效),Harness 里会弹出提示。
|
|
52
34
|
|
|
53
|
-
**2. 仅 Codex,只需一次**:Codex **设置 → 钩子(Hooks)** → **信任** **dsh-baton: waiting for the Harness run
|
|
35
|
+
**2. 仅 Codex,只需一次**:Codex **设置 → 钩子(Hooks)** → **信任** **dsh-baton: waiting for the Harness run**。
|
|
36
|
+
|
|
37
|
+
列表里还没有这个钩子,或者想知道信任它会改变什么时,展开看:
|
|
54
38
|
|
|
55
|
-
|
|
39
|
+
<details>
|
|
40
|
+
<summary>Codex 钩子</summary>
|
|
41
|
+
|
|
42
|
+
钩子要插件 0.6.0 以上才有(没有 Node.js 时要 0.6.2 以上);装好或更新插件后,先重启一次 Harness,再重启一次 Codex,它才会出现在这个列表里。不需要 Node.js:没有的话它和服务器一样用 Harness 自带运行环境。之后 Codex 在任务跑完时会被自动唤醒,不用再每 270 秒查一次。只需信任一次:以后升级、热更新、重启 Harness 都不用再信任。不信任也能用,Codex 会改用 `dsh_wait` 等,第一次交接时会提醒你。
|
|
43
|
+
|
|
44
|
+
</details>
|
|
45
|
+
|
|
46
|
+
**3. 检查**:在 Claude Code 或 Codex 里**新开**一个对话,让它调用 `dsh_list_workspaces`,会列出你的 Harness 工作区。Codex 调用 MCP 工具时会请你批准,批准 `dsh-baton` 的工具即可(无人值守的 `codex exec` 加上 `--approve-for-me`)。
|
|
56
47
|
|
|
57
48
|
就这些。如果 Harness 提示没能注册,在终端运行 `npx -y dsh-baton-mcp setup`(这需要 Node.js ≥ 22.15),它做的是同样的注册。更新、卸载和 Windows 说明在[后面](#安装细节)。
|
|
58
49
|
|
|
59
|
-
##
|
|
50
|
+
## 看它跑
|
|
60
51
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
52
|
+
*Claude Code 和 Codex 各派一个任务给 DeepSeek Harness,它的子代理用 1 小时 41 分搭出了这条雨夜街道。40 秒,建议开声音。*
|
|
53
|
+
|
|
54
|
+
https://github.com/user-attachments/assets/ed931637-acb3-4c16-8c52-72298ea90485
|
|
64
55
|
|
|
65
56
|
<p align="center">
|
|
66
|
-
<img src="./assets/readme/zh/
|
|
57
|
+
<img src="./assets/readme/zh/flow.gif" width="100%" alt="典型流程:新建工作区、派发任务、不轮询地等待(Claude Code 和 Codex 在运行结束时被唤醒)、在同一个对话里续接追问">
|
|
67
58
|
</p>
|
|
68
59
|
|
|
69
|
-
|
|
70
|
-
- **Claude Code** 在后台等待任务(`dsh_send` 返回的 `wait_command`),任务结束时只被唤醒一次。
|
|
71
|
-
- **Codex**(0.6.1 起)信任了 dsh-baton 的 Stop 钩子后:`dsh_send` 之后直接结束这一轮;钩子在模型之外等任务,任务结束或停下来提问时,带着结果继续同一个对话。每个任务只唤醒模型一次,而不是每 270 秒一次。
|
|
72
|
-
- **Codex** 没有钩子时在 `dsh_wait` 里等待,每次最多 270 秒,刚好低于它 300 秒的工具超时。没做完时只返回状态,Codex 会直接再调用,不会去总结进度。Codex 不会收到 `wait_command`:它的终端无法在命令结束时唤醒它,只能轮询。
|
|
73
|
-
- Codex 调用 MCP 工具时会请你批准,批准 `dsh-baton` 的工具即可;无人值守的 `codex exec` 加上 `--approve-for-me`。
|
|
74
|
-
|
|
75
|
-
**指定模型**(0.6.1 起):直接说用哪个就行,比如「交给 DeepSeek,用 V4 Pro」。`dsh_send` 会把它作为 `model`(以及 `reasoning_effort`)传过去,按 Harness 自己的模型列表匹配,所以 Harness 以后新加的模型一出现就能用。只对这个对话生效,Harness 的默认模型保持不变。`dsh_list_models` 可以看有哪些模型。
|
|
60
|
+
一个 Codex 对话里完成新建、排队、插队和等待:
|
|
76
61
|
|
|
77
|
-
|
|
62
|
+
<p align="center">
|
|
63
|
+
<img src="./assets/demos/s3-queue-zh.gif" width="100%" alt="左边 Codex 给 DeepSeek 派任务,再排队一条追问、插队修改正在跑的任务并等待;右边 DeepSeek Harness 实时执行">
|
|
64
|
+
</p>
|
|
78
65
|
|
|
79
|
-
|
|
66
|
+
Codex 和 Claude 轮流往同一个 Harness 对话发消息,每条都标明是谁发的:
|
|
80
67
|
|
|
81
68
|
<p align="center">
|
|
82
|
-
<img src="./
|
|
69
|
+
<img src="./assets/demos/s8-relay-zh-zoom.gif" width="100%" alt="Harness 对话里 Codex 和 Claude 的消息交替出现,每条都带“由 ChatGPT / Claude 从另一个聊天发送”的标签">
|
|
83
70
|
</p>
|
|
84
71
|
|
|
72
|
+
<p align="center"><a href="docs/demos.zh-CN.md">更多演示 →</a></p>
|
|
73
|
+
|
|
85
74
|
## Token 用量实测
|
|
86
75
|
|
|
87
|
-
|
|
76
|
+
**一句话:大活、长活交给它**。Claude Code 连 DeepSeek 一起算,总花费少 51–78%;Codex 用 GPT-6 Astra 少 47–81%,分阶段的看板应用占 ChatGPT Plus 5 小时额度从 86.01% 降到 16.01%。
|
|
88
77
|
|
|
89
|
-
|
|
78
|
+
**三个测试案例**,都按普通用户的口吻提需求(原文可以直接点开看):
|
|
79
|
+
- **案例 1 · 命令行工具**:用 Node 写一个命令行任务管理工具,零依赖,分模块,带测试。[需求原文](docs/benchmarks/token-usage-2026-10/natural/medium.md)
|
|
80
|
+
- **案例 2 · 任务管理网页**:Node 服务器加前端页面,REST 接口、统计、测试和 README。[需求原文](docs/benchmarks/token-usage-2026-10/natural/large.md)
|
|
81
|
+
- **案例 3 · 看板应用(分阶段)**:只用 Node 内置模块和原生前端的看板应用,三个阶段在同一个对话里依次给:① 主体(REST 接口、持久化、前端拖拽);② 标签、截止日期、搜索语法、归档、撤销重做、活动日志、导入导出;③ 版本冲突检测、WIP 上限、批量操作、SSE 实时同步、统计。事先写好 55 项验收测试,两组用同一套。需求原文:[阶段 1](docs/benchmarks/token-usage-2026-10/xxl/seed/docs/phase1.md) · [阶段 2](docs/benchmarks/token-usage-2026-10/xxl/seed/docs/phase2.md) · [阶段 3](docs/benchmarks/token-usage-2026-10/xxl/seed/docs/phase3.md)
|
|
90
82
|
|
|
91
|
-
|
|
83
|
+
| 案例 3 | 自己做 | 转交 |
|
|
84
|
+
|---|---|---|
|
|
85
|
+
| Opus 5.5 high | 55/55,39 分钟 | 55/55,93 分钟 |
|
|
86
|
+
| GPT-6.1 Sol high | 55/55,18 分钟 | 55/55,101 分钟 |
|
|
87
|
+
| GPT-6 Astra high | 55/55,约 31 分钟 | 55/55,约 87 分钟 |
|
|
88
|
+
|
|
89
|
+
每个任务的花费,按标价计,DeepSeek V4.1 Flash 1017 按高峰价。Opus 5.5 跑在 Claude Code 里,GPT-6.1 Sol 和 GPT-6 Astra 跑在 Codex 里,推理强度都是 high:
|
|
90
|
+
|
|
91
|
+
| 案例 | 模型 | 自己做 | 转交:主 Agent | 转交:DeepSeek | 转交合计 | 合计变化 | 主 Agent 变化 | 5 小时额度,自己做 → 转交 |
|
|
92
|
+
|---|---|---|---|---|---|---|---|---|
|
|
93
|
+
| 案例 1 · 命令行工具 | Opus 5.5 high | $1.07 | $0.42 | $0.10 | $0.52 | −51.2% | −60.8% | — |
|
|
94
|
+
| | GPT-6.1 Sol high | $0.17 | $0.06 | $0.13 | $0.20 | +15.3% | −62.9% | 2.08% → 0.77% |
|
|
95
|
+
| | GPT-6 Astra high | $0.79 | $0.30 | $0.12 | $0.42 | −47.4% | −62.0% | 7.83% → 2.98% |
|
|
96
|
+
| 案例 2 · 任务管理网页 | Opus 5.5 high | $2.13 | $0.47 | $0.13 | $0.60 | −71.7% | −77.7% | — |
|
|
97
|
+
| | GPT-6.1 Sol high | $0.41 | $0.06 | $0.18 | $0.25 | −40.2% | −84.5% | 5.08% → 0.79% |
|
|
98
|
+
| | GPT-6 Astra high | $2.27 | $0.30 | $0.13 | $0.42 | −81.4% | −86.9% | 22.43% → 2.94% |
|
|
99
|
+
| 案例 3 · 看板应用(分阶段) | Opus 5.5 high | $14.18 | $2.32 | $0.84 | $3.16 | −77.7% | −83.6% | — |
|
|
100
|
+
| | GPT-6.1 Sol high | $1.12 | $0.22 | $0.81 | $1.03 | −8.1% | −80.5% | 13.71% → 2.67% |
|
|
101
|
+
| | GPT-6 Astra high | $8.69 | $1.62 | $0.74 | $2.35 | −72.9% | −81.4% | 86.01% → 16.01% |
|
|
92
102
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
103
|
+
<p align="center">
|
|
104
|
+
<img src="./docs/benchmarks/token-usage-2026-10/charts/cost.zh.svg" width="100%" alt="每个任务花了多少钱(美元),自己做对转交,按模型分组。Opus 5.5 high:$1.07 对 $0.52,$2.13 对 $0.60,$14.18 对 $3.16。GPT-6.1 Sol high:$0.17 对 $0.20,$0.41 对 $0.25,$1.12 对 $1.03。GPT-6 Astra high:$0.79 对 $0.42,$2.27 对 $0.42,$8.69 对 $2.35">
|
|
105
|
+
</p>
|
|
96
106
|
|
|
97
107
|
<p align="center">
|
|
98
|
-
<img src="./docs/benchmarks/token-usage-2026-10/charts/
|
|
108
|
+
<img src="./docs/benchmarks/token-usage-2026-10/charts/quota.zh.svg" width="100%" alt="Codex 占 ChatGPT Plus 5 小时额度,自己做对转交,按模型分组。GPT-6.1 Sol high:2.08% 对 0.77%,5.08% 对 0.79%,13.71% 对 2.67%。GPT-6 Astra high:7.83% 对 2.98%,22.43% 对 2.94%,86.01% 对 16.01%">
|
|
99
109
|
</p>
|
|
100
110
|
|
|
101
|
-
|
|
111
|
+
**适合转交**:大任务、长任务,以及 5 小时额度快用完的时候。每个案例、每个模型,主 Agent 自己的花费都少了 61–87%。
|
|
102
112
|
|
|
103
|
-
|
|
104
|
-
|---|---|---|---|---|
|
|
105
|
-
| Claude Code · Opus 5.5 high | 案例 1 · 命令行工具 | $1.07 | $0.42(−61%) | $0.52(−51%) |
|
|
106
|
-
| Claude Code · Opus 5.5 high | 案例 2 · 任务管理网页 | $2.13 | $0.47(−78%) | $0.60(−72%) |
|
|
107
|
-
| Codex · GPT-6.1 Sol high | 案例 1 · 命令行工具 | $0.17 | $0.06(−63%) | $0.20(+15%) |
|
|
108
|
-
| Codex · GPT-6.1 Sol high | 案例 2 · 任务管理网页 | $0.41 | $0.06(−84%) | $0.25(−40%) |
|
|
109
|
-
| Codex · GPT-6 Astra high | 案例 1 · 命令行工具 | $0.79 | $0.30(−62%) | $0.42(−47%) |
|
|
110
|
-
| Codex · GPT-6 Astra high | 案例 2 · 任务管理网页 | $2.27 | $0.30(−87%) | $0.42(−81%) |
|
|
113
|
+
**适合自己做**:Codex 用 GPT-6.1 Sol 的小任务。Sol 本身够便宜,转交时 DeepSeek 那部分要占到三分之二以上,抵掉了 Sol 省下的钱。
|
|
111
114
|
|
|
112
|
-
<
|
|
113
|
-
|
|
114
|
-
</p>
|
|
115
|
+
<details>
|
|
116
|
+
<summary>测了什么、怎么测的</summary>
|
|
115
117
|
|
|
116
|
-
|
|
118
|
+
- **转交组唯一的区别**,是在需求末尾多一句:交给 DeepSeek Harness 去做。
|
|
119
|
+
- 两组用同一个模型、同样的推理强度;每次都用同一套验收脚本检查,模型看不到这个脚本。
|
|
120
|
+
- 案例 1、2:每格 3 次取平均。案例 3:每格 1 次。全部通过验收,没有返工。
|
|
121
|
+
- 价格按标价算。DeepSeek 按高峰价;实际 DeepSeek 账单大约是这个数的 58%。
|
|
122
|
+
- 5 小时额度按每次运行的花费换算:GPT-6.1 Sol 每 $1 合 12.27 个百分点,GPT-6 Astra 合 9.90 个。
|
|
123
|
+
- 0.6.1 起 Codex 由 Stop 钩子唤醒,不再用 `dsh_wait` 轮询;0.6.2 把 MCP 说明缩成一句话,Codex 每次转交少读几千到一万多个新输入 token。
|
|
117
124
|
|
|
118
|
-
|
|
125
|
+
</details>
|
|
119
126
|
|
|
120
|
-
|
|
127
|
+
## 工作原理
|
|
121
128
|
|
|
122
129
|
<p align="center">
|
|
123
|
-
<img src="./
|
|
130
|
+
<img src="./assets/readme/zh/architecture.svg" width="100%" alt="MCP 客户端通过 stdio 调用 dsh-baton-mcp,再经仅当前用户可访问的本地套接字连到 Harness 里的 dsh-baton-host 插件,插件驱动 Harness 的对话并实时显示;插件不可用时退回 dsh headless">
|
|
124
131
|
</p>
|
|
125
132
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
| 5 小时额度 | 86% | 约 16%(−81%) |
|
|
132
|
-
| 验收测试 | 55/55,一次通过 | 55/55,一次通过 |
|
|
133
|
-
| 用时 | 约 31 分钟 | 约 87 分钟 |
|
|
134
|
-
| 代码量 | 1,876 行 | 9,953 行 |
|
|
133
|
+
<p align="center">
|
|
134
|
+
<img src="./assets/readme/zh/comparison.svg" width="100%" alt="插件和 headless 对比:只有插件能在 Harness 界面实时可见、续接桌面版的对话、使用桌面版智能体预设;两者都沿用桌面版登录">
|
|
135
|
+
</p>
|
|
136
|
+
|
|
137
|
+
**指定模型**:直接说用哪个就行,比如「交给 DeepSeek,用 V4 Pro」。`dsh_send` 会把它作为 `model`(以及 `reasoning_effort`)传过去,按 Harness 自己的模型列表匹配,所以 Harness 以后新加的模型一出现就能用。只对这个对话生效,Harness 的默认模型保持不变。`dsh_list_models` 可以看有哪些模型。
|
|
135
138
|
|
|
136
|
-
|
|
139
|
+
等待不需要任何配置;想知道任务跑着时各客户端在做什么,展开看:
|
|
137
140
|
|
|
138
141
|
<details>
|
|
139
|
-
<summary
|
|
142
|
+
<summary>各客户端怎么等待</summary>
|
|
140
143
|
|
|
141
|
-
-
|
|
142
|
-
-
|
|
143
|
-
-
|
|
144
|
-
- 价格按标价算。DeepSeek 按高峰价,是最保守的算法;实际 DeepSeek 账单大约是这个数的 58%。
|
|
145
|
-
- 每一次运行:[运行日志](docs/benchmarks/token-usage-2026-10/LOG.md)(「0.6.2 hand-over rerun」一节);原始数据在 `docs/benchmarks/token-usage-2026-10/results/*-n062*.jsonl`;方法和更早的版本见[完整报告](docs/benchmarks/token-usage-2026-10.md)。
|
|
144
|
+
- **Claude Code** 在后台等待任务(`dsh_send` 返回的 `wait_command`),任务结束时只被唤醒一次。
|
|
145
|
+
- **Codex**(0.6.1 起)信任了 dsh-baton 的 Stop 钩子后:`dsh_send` 之后直接结束这一轮;钩子在模型之外等任务,任务结束或停下来提问时,带着结果继续同一个对话。每个任务只唤醒模型一次,而不是每 270 秒一次。
|
|
146
|
+
- **Codex** 没有钩子时在 `dsh_wait` 里等待,每次最多 270 秒,刚好低于它 300 秒的工具超时。没做完时只返回状态,Codex 会直接再调用,不会去总结进度。Codex 不会收到 `wait_command`:它的终端无法在命令结束时唤醒它,只能轮询。
|
|
146
147
|
|
|
147
148
|
</details>
|
|
148
149
|
|
|
150
|
+
### 转交之后怎么验收
|
|
151
|
+
|
|
152
|
+
不用光听 DeepSeek 自己说做完了。运行前,dsh-baton 先记下文件夹里有什么;运行结束后,它会列出到底新建、修改、删除了哪些文件,并亲自跑一遍项目自己的测试(`npm test`,或者客户端在 `checks` 里给的命令)。接着,另一个只读的 DeepSeek 子智能体会对照需求逐条检查,最后给一张表:哪条需求、做到没有、依据在哪个文件第几行。Claude 或 GPT 只看这些,没做到的一次性发回去改,只有表里写着「没做到」的地方才去翻代码。不用 `cwd` 指定的话,新对话就开在你当前工作的文件夹里。
|
|
153
|
+
|
|
154
|
+
<p align="center">
|
|
155
|
+
<img src="./docs/benchmarks/token-usage-2026-10/charts/evidence-chain.zh.svg" width="100%" alt="一次真实转交的证据链:用户提需求,Codex 写规格和验收命令,dsh-baton 给文件夹拍快照,DeepSeek Harness 写出 11 个文件,dsh-baton 自己列出改动并跑 npm test,Codex 主动打回一处,dsh-baton 再次验收,Codex 通过,基准测试自己的验收 13/13 通过">
|
|
156
|
+
</p>
|
|
157
|
+
|
|
149
158
|
## 工具
|
|
150
159
|
|
|
151
160
|
<p align="center">
|
|
@@ -156,6 +165,8 @@ Codex 和 Claude 轮流往同一个 Harness 对话发消息,每条都标明是
|
|
|
156
165
|
<img src="./assets/readme/zh/states.svg" width="100%" alt="运行状态:queued、running、waiting、completed、failed、cancelled,Harness 中途重启则为 interrupted。对话正忙时,followup 进排队栏,steer 在下一步插话">
|
|
157
166
|
</p>
|
|
158
167
|
|
|
168
|
+
需要查每个工具的具体参数和返回字段时:
|
|
169
|
+
|
|
159
170
|
<details>
|
|
160
171
|
<summary>完整工具说明</summary>
|
|
161
172
|
|
|
@@ -168,7 +179,7 @@ Codex 和 Claude 轮流往同一个 Harness 对话发消息,每条都标明是
|
|
|
168
179
|
| `dsh_list_sessions` | 列出对话(最新在前),带标题、目录、是否在跑和 `model`(下一次请求用的模型);可按目录或关键字过滤。已归档的对话不会列出 |
|
|
169
180
|
| `dsh_list_models` | 列出 Harness 能用的模型(provider、id、名称、推理强度)和它的默认模型 |
|
|
170
181
|
| `dsh_read_session` | 读某个对话最近的消息和回复(含用到的工具名);已归档的对话会带 `archived: true` |
|
|
171
|
-
| `dsh_send` | 发消息。带 `session_id` 续接那个对话;否则新开对话,建在 `cwd` 或 `workspace_id` 指定的位置,都不带时建在当前对话所在的文件夹(`cwd` 还不是工作区时会先登记;指定的 `cwd` 在当前对话文件夹之外也可以,结果里的 `folder_note` 会提醒)。`checks`:运行结束后 dsh-baton
|
|
182
|
+
| `dsh_send` | 发消息。带 `session_id` 续接那个对话;否则新开对话,建在 `cwd` 或 `workspace_id` 指定的位置,都不带时建在当前对话所在的文件夹(`cwd` 还不是工作区时会先登记;指定的 `cwd` 在当前对话文件夹之外也可以,结果里的 `folder_note` 会提醒)。`checks`:运行结束后 dsh-baton 自己跑的测试命令(见[转交之后怎么验收](#转交之后怎么验收))。立即返回 `run_id`。Harness 里这条消息会标注「由 Claude / ChatGPT 从另一个聊天发送」。已归档的对话会被拒绝:先在 Harness 里取消归档,或新开对话。对话正忙时,`mode` 为 `followup` 会排在当前回合之后,`steer` 会插进正在跑的回合;不传时跟随 Harness 自己的设置(设置 → 繁忙时的发送行为:排队发送或插话发送),读不到设置时按 `followup`。返回里的 `mode_source` 说明用的是哪种。`model` 只在用户点名模型时传(如“DeepSeek-V4 Pro”“v4-pro”;`reasoning_effort` 同理):按 Harness 自己的模型列表匹配,不分大小写,忽略空格、连字符和开头的 DeepSeek,所以 Harness 以后上线的模型(如 V4.1 Pro)一出现在列表里就能用;匹配不到或匹配到多个时,发送直接失败、什么都不创建,并列出可用模型(用 `provider/model` 指定其一)。模型在消息发出前设到该对话上,Harness 的默认模型会恢复原样(Harness 会把对话里选的模型同时存成默认)。不传时,续接的对话沿用它自己的模型,新对话用 Harness 默认模型。返回值的 `model` 和 `model_source` 写明用的是哪个 |
|
|
172
183
|
| `dsh_status` | 查看运行进度:状态、最近的文字和工具调用、最终回复;回合结束但子智能体/后台任务还在跑时给出 `waiting_on`;中途有人在 Harness 界面里发消息时给出 `human_interjections`;被 Harness 重启打断的任务状态为 `interrupted`;以及这次运行发送时用的 `model` |
|
|
173
184
|
| `dsh_wait` | 信任了 Stop 钩子的 Codex 用不到它。等运行结束;发消息时带了 `checks` 的,结束后会附上 `evidence`(改了哪些文件、测试结果)(或者等到它停下来问人,见 `dsh_answer`):Codex 每次最多等 270 秒,其他客户端 50 秒。没做完会带 `timed_out`,只返回状态,再调用一次即可。能在后台等待的客户端改用 `dsh_send` 返回的 `wait_command`。`dsh_status` 只用来看一眼,不用来等待 |
|
|
174
185
|
| `dsh_answer` | 回答运行中途提出的问题(Harness 的 ask_user_question):每道题可以填选项编号或选项文字、自由填写,或者跳过;可以逐题回答,也可以一次答完。运行一提问,`dsh_wait` 和 `wait_command` 就会立刻返回,并带上题目(`needs_input`、`question`;Harness 那边模型推荐的选项单独放在 `harness_suggested` 里,不再写在选项文字中)。只有你说过让它自己决定时,助手才会按自己的判断回答,这时它会带上 `reason`,并告诉你选了什么、为什么;否则它会把题目转给你,开头先写它自己的建议和理由。你也可以直接在 Harness 窗口里回答,谁先答就算谁的 |
|
|
@@ -182,7 +193,12 @@ Codex 和 Claude 轮流往同一个 Harness 对话发消息,每条都标明是
|
|
|
182
193
|
|
|
183
194
|
</details>
|
|
184
195
|
|
|
185
|
-
##
|
|
196
|
+
## 命令行和配置
|
|
197
|
+
|
|
198
|
+
想在终端或脚本里直接驱动 Harness,而不是通过对话时:
|
|
199
|
+
|
|
200
|
+
<details>
|
|
201
|
+
<summary>命令行</summary>
|
|
186
202
|
|
|
187
203
|
```bash
|
|
188
204
|
dsh-baton workspaces
|
|
@@ -201,7 +217,12 @@ dsh-baton rename-workspace ~/code/new-app 新名字
|
|
|
201
217
|
|
|
202
218
|
(从源码运行时用 `node src/cli.mjs …`。)
|
|
203
219
|
|
|
204
|
-
|
|
220
|
+
</details>
|
|
221
|
+
|
|
222
|
+
默认值不合适时,比如要换套接字路径或强制用 headless 模式:
|
|
223
|
+
|
|
224
|
+
<details>
|
|
225
|
+
<summary>环境变量</summary>
|
|
205
226
|
|
|
206
227
|
| 变量 | 默认值 | 作用 |
|
|
207
228
|
|---|---|---|
|
|
@@ -215,6 +236,10 @@ dsh-baton rename-workspace ~/code/new-app 新名字
|
|
|
215
236
|
| `DSH_BATON_ACCOUNT` | `1` | headless 模式下设为 `0` 则改用 API key 路线 |
|
|
216
237
|
| `DSH_BATON_PATCHES` | 空 | headless 模式额外的 `--patch` 覆盖文件,用 `:` 分隔 |
|
|
217
238
|
|
|
239
|
+
</details>
|
|
240
|
+
|
|
241
|
+
Harness 插件连不上时:
|
|
242
|
+
|
|
218
243
|
<details>
|
|
219
244
|
<summary>headless 后备模式说明</summary>
|
|
220
245
|
|
|
@@ -224,6 +249,8 @@ dsh-baton rename-workspace ~/code/new-app 新名字
|
|
|
224
249
|
|
|
225
250
|
## 安装细节
|
|
226
251
|
|
|
252
|
+
更新或卸载插件、从 dsh-bridge 升级、从源码运行,或者插件没能注册时:
|
|
253
|
+
|
|
227
254
|
<details>
|
|
228
255
|
<summary>插件做了什么、更新、卸载、从 dsh-bridge 升级、从源码安装、Windows</summary>
|
|
229
256
|
|
|
@@ -231,7 +258,7 @@ dsh-baton rename-workspace ~/code/new-app 新名字
|
|
|
231
258
|
|
|
232
259
|
**Codex 的 Stop 钩子**。插件会在 `~/.codex/config.toml` 里加一段带 `# dsh-baton` 标记的 `[[hooks.Stop]]`,Codex 里显示为 **dsh-baton: waiting for the Harness run**,你信任之后才会运行(dsh-baton 不会替你信任)。这一段只写了一个小脚本的路径 `~/.dsh-baton/bin/codex-stop-hook`(Windows 上是 `codex-stop-hook.cmd`),Node.js 和服务器这些会变的路径都放在脚本里。所以这一段本身永远不变:升级、同版本刷新、重启 Harness 都不会动它,信任一直有效。万一这一段被改写,dsh-baton 会清掉旧的信任记录,Codex 会请你重新信任。Windows 上用 `.cmd`,是因为 Codex 通过 `PowerShell -Command` 运行钩子,启动不了带引号的程序路径。没有自己的 Node.js 时,脚本和服务器一样以 Node 模式(`ELECTRON_RUN_AS_NODE=1`)运行 Harness 程序本身;插件每次启动都会重写它,所以 Harness 更新后路径也会跟上。只有没装 Codex、设了 `DSH_BATON_CODEX_HOOK=0`、或 Codex 里的 dsh-baton 条目没指向服务器目录(比如裸的 `npx` 命令)时才不加;这时 Codex 对话里第一次交接会说明原因。手动添加用 `dsh-baton setup --codex-hook`,`setup --remove` 会删掉它,给 Harness 设置 `DSH_BATON_CODEX_HOOK=0` 则插件不再添加。
|
|
233
260
|
|
|
234
|
-
**更新不用重启**。新版本装进 `~/.dsh-baton/app` 后(插件更新、`setup` 或手动复制),或者同一版本但文件有改动时,Claude Code 和 Codex 里已经开着的对话,下一次调用 `dsh_*` 工具时就会用上:服务器每次调用前检查这个文件夹,启动新版本,并在这次调用的结果里提示一次已经更新。正在进行的 `dsh_wait` 会先在旧版本里跑完。新版本启动失败时,旧版本继续工作,结果里会说明原因。新版本新增的工具,Claude Code 里马上就能用(服务器会通知客户端工具列表变了);Codex
|
|
261
|
+
**更新不用重启**。新版本装进 `~/.dsh-baton/app` 后(插件更新、`setup` 或手动复制),或者同一版本但文件有改动时,Claude Code 和 Codex 里已经开着的对话,下一次调用 `dsh_*` 工具时就会用上:服务器每次调用前检查这个文件夹,启动新版本,并在这次调用的结果里提示一次已经更新。正在进行的 `dsh_wait` 会先在旧版本里跑完。新版本启动失败时,旧版本继续工作,结果里会说明原因。新版本新增的工具,Claude Code 里马上就能用(服务器会通知客户端工具列表变了);Codex 一直用对话开始时的工具列表,新工具要到下一个新对话才会显示;不过旧对话里也能通过 `dsh_call` 马上用上新功能(更新提示里会写明),已有的工具也会立刻用上新代码。
|
|
235
262
|
|
|
236
263
|
**`npx -y dsh-baton-mcp setup`** 在终端里做同样的注册:用于插件没能注册的情况,或者从 MCP 目录过来的用户。插件已经注册过时它什么都不改;插件没装的话,它会提示去添加。
|
|
237
264
|
|
|
@@ -245,6 +272,10 @@ dsh-baton rename-workspace ~/code/new-app 新名字
|
|
|
245
272
|
|
|
246
273
|
**Windows**。步骤相同;[WINDOWS.md](WINDOWS.md) 说明了不同之处(用命名管道代替套接字文件),以及插件页装不上或卸不掉时的修法。
|
|
247
274
|
|
|
275
|
+
</details>
|
|
276
|
+
|
|
277
|
+
手动注册,或者想在 Claude 桌面聊天应用(不是 Claude Code)里用 dsh-baton 时:
|
|
278
|
+
|
|
248
279
|
<details>
|
|
249
280
|
<summary>手动注册与 Claude 桌面应用</summary>
|
|
250
281
|
|
|
@@ -265,8 +296,6 @@ codex mcp add dsh-baton -- "$(command -v node)" "$PWD/dsh-baton/src/server.mjs"
|
|
|
265
296
|
|
|
266
297
|
</details>
|
|
267
298
|
|
|
268
|
-
</details>
|
|
269
|
-
|
|
270
299
|
## 安全与限制
|
|
271
300
|
|
|
272
301
|
<p align="center">
|
|
@@ -279,8 +308,6 @@ codex mcp add dsh-baton -- "$(command -v node)" "$PWD/dsh-baton/src/server.mjs"
|
|
|
279
308
|
|
|
280
309
|
任何能以你的身份运行程序的进程都能通过这个套接字驱动 Harness,这和直接运行 `dsh` 的权限一样。本项目依赖 Harness 0.2.1 系列的内部服务接口,Harness 大版本更新后可能需要跟进。
|
|
281
310
|
|
|
282
|
-
<p align="center"><a href="docs/demos.zh-CN.md">更多演示 →</a></p>
|
|
283
|
-
|
|
284
311
|
## 许可证
|
|
285
312
|
|
|
286
313
|
MIT
|
package/host/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<img src="https://raw.githubusercontent.com/JialaoLiu/dsh-baton/main/assets/readme/hero.gif" width="100%" alt="dsh-baton: pass the baton from Claude Code or Codex to DeepSeek Harness, watch it run live, and get it handed back when it's done">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/JialaoLiu/dsh-baton/main/assets/readme/en/hero.gif" width="100%" alt="dsh-baton: pass the baton from Claude Code or Codex to DeepSeek Harness, watch it run live, and get it handed back when it's done">
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
5
|
<p align="center">
|
|
@@ -17,13 +17,13 @@ The DeepSeek Harness plugin for [dsh-baton](https://github.com/JialaoLiu/dsh-bat
|
|
|
17
17
|
DeepSeek Harness 插件,配合 [dsh-baton](https://github.com/JialaoLiu/dsh-baton/blob/main/README.zh-CN.md) 使用。它运行在 Harness 内部,只开一个当前用户才能访问的本地套接字,让 MCP 客户端能新建工作区和对话、续接指定对话、等待任务完成。所有操作都走 Harness 官方会话服务,界面实时可见。
|
|
18
18
|
|
|
19
19
|
<p align="center">
|
|
20
|
-
<img src="https://raw.githubusercontent.com/JialaoLiu/dsh-baton/main/assets/readme/features.svg" width="100%" alt="Create workspaces and sessions, continue a session, run live in the Harness UI labelled with the sender, get woken when it is done, no extra API key, macOS and Windows with zero dependencies">
|
|
20
|
+
<img src="https://raw.githubusercontent.com/JialaoLiu/dsh-baton/main/assets/readme/en/features.svg" width="100%" alt="Create workspaces and sessions, continue a session, run live in the Harness UI labelled with the sender, get woken when it is done, no extra API key, macOS and Windows with zero dependencies">
|
|
21
21
|
</p>
|
|
22
22
|
|
|
23
23
|
## Install / 安装
|
|
24
24
|
|
|
25
25
|
<p align="center">
|
|
26
|
-
<img src="https://raw.githubusercontent.com/JialaoLiu/dsh-baton/main/assets/readme/install.svg" width="100%" alt="Step 1: in Harness open Plugins, Add plugin, enter dsh-baton-host, Install, Enable now; it registers dsh-baton for Claude Code and Codex. Step 2: in a new chat, ask for dsh_list_workspaces">
|
|
26
|
+
<img src="https://raw.githubusercontent.com/JialaoLiu/dsh-baton/main/assets/readme/en/install.svg" width="100%" alt="Step 1: in Harness open Plugins, Add plugin, enter dsh-baton-host, Install, Enable now; it registers dsh-baton for Claude Code and Codex. Step 2: in a new chat, ask for dsh_list_workspaces">
|
|
27
27
|
</p>
|
|
28
28
|
|
|
29
29
|
Harness → Plugins / 插件 → Add plugin / 添加插件 → `dsh-baton-host` → Install / 安装 → Enable now / 立即启用
|
|
@@ -39,7 +39,7 @@ Remove / 卸载: uninstall, then `npx -y dsh-baton-mcp setup --remove` / 卸载
|
|
|
39
39
|
## How it works / 工作原理
|
|
40
40
|
|
|
41
41
|
<p align="center">
|
|
42
|
-
<img src="https://raw.githubusercontent.com/JialaoLiu/dsh-baton/main/assets/readme/architecture.svg" width="100%" alt="MCP clients call dsh-baton-mcp over stdio; it reaches the dsh-baton-host plugin inside Harness through an owner-only local socket; the plugin drives Harness sessions live, with dsh headless as fallback">
|
|
42
|
+
<img src="https://raw.githubusercontent.com/JialaoLiu/dsh-baton/main/assets/readme/en/architecture.svg" width="100%" alt="MCP clients call dsh-baton-mcp over stdio; it reaches the dsh-baton-host plugin inside Harness through an owner-only local socket; the plugin drives Harness sessions live, with dsh headless as fallback">
|
|
43
43
|
</p>
|
|
44
44
|
|
|
45
45
|
## Socket protocol / 套接字协议
|
package/host/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-baton-host",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.3",
|
|
4
4
|
"description": "DeepSeek Harness plugin for dsh-baton: adding it is the whole install. Switched on, it registers the dsh-baton MCP server for Claude Code and Codex, then takes the baton over a local, owner-only control socket, runs it in a specific session through the host's own session controller, and hands the result back when the turn is done.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"LICENSE"
|
|
21
21
|
],
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"dsh-baton-mcp": "0.6.
|
|
23
|
+
"dsh-baton-mcp": "0.6.3"
|
|
24
24
|
},
|
|
25
25
|
"license": "MIT",
|
|
26
26
|
"dsh": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-baton-mcp",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.3",
|
|
4
4
|
"mcpName": "io.github.JialaoLiu/dsh-baton",
|
|
5
5
|
"description": "Pass the baton from Claude Code, Codex or any MCP client to a local DeepSeek Harness: an MCP server that hands off tasks, continues a specific session, shows the run live in Harness and hands the result back when it is done.",
|
|
6
6
|
"type": "module",
|
package/server.json
CHANGED
|
@@ -7,12 +7,12 @@
|
|
|
7
7
|
"url": "https://github.com/JialaoLiu/dsh-baton",
|
|
8
8
|
"source": "github"
|
|
9
9
|
},
|
|
10
|
-
"version": "0.6.
|
|
10
|
+
"version": "0.6.3",
|
|
11
11
|
"packages": [
|
|
12
12
|
{
|
|
13
13
|
"registryType": "npm",
|
|
14
14
|
"identifier": "dsh-baton-mcp",
|
|
15
|
-
"version": "0.6.
|
|
15
|
+
"version": "0.6.3",
|
|
16
16
|
"transport": {
|
|
17
17
|
"type": "stdio"
|
|
18
18
|
}
|
package/src/codex-hook.mjs
CHANGED
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
// Codex feed reason back to the model in the same turn. `dsh-baton codex-stop-hook` is such
|
|
4
4
|
// a hook: after dsh_send the model ends its turn, the hook waits for the run without any
|
|
5
5
|
// model requests, and returns the result as dsh_wait would. One wake instead of a dsh_wait
|
|
6
|
-
// call every few minutes.
|
|
6
|
+
// call every few minutes.
|
|
7
7
|
import fs from 'node:fs'
|
|
8
|
+
import { execFileSync } from 'node:child_process'
|
|
8
9
|
import path from 'node:path'
|
|
9
10
|
import * as core from './core.mjs'
|
|
10
11
|
import { codexConfigPath, stableNodePath, codexHookEntry, codexHookStatus, hookResultFile, HOOK_TIMEOUT_S } from './register.mjs'
|
|
@@ -94,9 +95,123 @@ export function noteOnce(thread) {
|
|
|
94
95
|
return true
|
|
95
96
|
}
|
|
96
97
|
|
|
97
|
-
//
|
|
98
|
-
//
|
|
99
|
-
|
|
98
|
+
// The language of Codex's own interface. A language picked in Codex's settings is written as
|
|
99
|
+
// localeOverride in its config ([desktop]) and wins, English included. Without one, Codex
|
|
100
|
+
// (Electron) follows the system's preferred language, not its region format:
|
|
101
|
+
// AppleLanguages (then AppleLocale) on macOS, the user interface language that Node's ICU
|
|
102
|
+
// reports on Windows, LC_ALL / LC_MESSAGES / LANG elsewhere (C and POSIX say nothing).
|
|
103
|
+
// 'zh' when that starts with zh, else 'en' (also when nothing can be read). The hook runs
|
|
104
|
+
// at every Codex Stop, so the system is asked only without an override, once per process.
|
|
105
|
+
const defaults = (key) => execFileSync('defaults', ['read', '-g', key], { encoding: 'utf8', timeout: 1500, stdio: ['ignore', 'pipe', 'ignore'] })
|
|
106
|
+
const SYSTEM = {
|
|
107
|
+
platform: process.platform,
|
|
108
|
+
readAppleLanguages: () => /^\(\s*"?([^",\s)]+)/.exec(defaults('AppleLanguages'))?.[1],
|
|
109
|
+
readAppleLocale: () => defaults('AppleLocale').trim(),
|
|
110
|
+
intlLocale: () => Intl.DateTimeFormat().resolvedOptions().locale,
|
|
111
|
+
}
|
|
112
|
+
const attempt = (read) => { try { return read() || '' } catch { return '' } }
|
|
113
|
+
export function systemLocale(env = process.env, sys = SYSTEM) {
|
|
114
|
+
const { platform, readAppleLanguages, readAppleLocale, intlLocale } = { ...SYSTEM, ...sys }
|
|
115
|
+
if (platform === 'darwin') return attempt(readAppleLanguages) || attempt(readAppleLocale)
|
|
116
|
+
if (platform === 'win32') return attempt(intlLocale)
|
|
117
|
+
return [env.LC_ALL, env.LC_MESSAGES, env.LANG].find((v) => v && !/^(C|POSIX)([._@]|$)/.test(v)) ?? ''
|
|
118
|
+
}
|
|
119
|
+
let systemCached
|
|
120
|
+
export function codexLang(env = process.env, sys) {
|
|
121
|
+
let picked
|
|
122
|
+
try {
|
|
123
|
+
picked = fs.readFileSync(codexConfigPath(env), 'utf8').match(/^\s*localeOverride\s*=\s*["']([^"']*)["']/m)?.[1]
|
|
124
|
+
} catch { /* no config: Codex follows the system */ }
|
|
125
|
+
const locale = picked || (sys ? systemLocale(env, sys) : (systemCached ??= systemLocale(env)))
|
|
126
|
+
return /^zh/i.test(locale) ? 'zh' : 'en'
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Codex desktop shows reason as is and folds the rest of a long message away, so it opens
|
|
130
|
+
// with a few lines a person reads at a glance: per run, its state, how long it took, and
|
|
131
|
+
// the files and checks dsh-baton saw (only what the result already holds).
|
|
132
|
+
const WORDS = {
|
|
133
|
+
en: {
|
|
134
|
+
state: { completed: 'completed', failed: 'failed', cancelled: 'cancelled', interrupted: 'interrupted' },
|
|
135
|
+
asks: (q, n) => `waiting for an answer${n > 1 ? ` (${n} questions)` : ''}${q ? `: "${q}"` : ''}`,
|
|
136
|
+
approval: 'waiting for an approval in Harness',
|
|
137
|
+
took: (t) => ` in ${t}`, units: ['h', 'm', 's'], join: ' ',
|
|
138
|
+
files: (n, more) => `${n}${more ? '+' : ''} file${n === 1 && !more ? '' : 's'} changed`,
|
|
139
|
+
checks: (ok, bad, skipped, all) => (skipped
|
|
140
|
+
? `checks ${ok}/${all} passed${bad ? `, ${bad} failed` : ''}, ${skipped} skipped`
|
|
141
|
+
: `checks ${bad ? 'failed' : 'passed'} (${ok}/${all})`),
|
|
142
|
+
many: (n) => `dsh-baton: ${n} Harness runs are back`,
|
|
143
|
+
running: (ids) => `Still running: ${ids.join(', ')}`,
|
|
144
|
+
sep: ': ', dot: ' · ',
|
|
145
|
+
},
|
|
146
|
+
zh: {
|
|
147
|
+
state: { completed: '已完成', failed: '失败', cancelled: '已取消', interrupted: '被中断' },
|
|
148
|
+
asks: (q, n) => `在等回答${n > 1 ? `(共 ${n} 个问题)` : ''}${q ? `:「${q}」` : ''}`,
|
|
149
|
+
approval: '在等你在 Harness 里批准',
|
|
150
|
+
took: (t) => `,用时 ${t}`, units: [' 小时', ' 分', ' 秒'], join: ' ',
|
|
151
|
+
files: (n, more) => `改动 ${n}${more ? '+' : ''} 个文件`,
|
|
152
|
+
checks: (ok, bad, skipped, all) => (skipped
|
|
153
|
+
? `项目测试 ${ok}/${all} 通过${bad ? `,${bad} 个未通过` : ''},${skipped} 个超时未跑`
|
|
154
|
+
: `项目测试${bad ? '未通过' : '通过'}(${ok}/${all})`),
|
|
155
|
+
many: (n) => `dsh-baton:${n} 个 Harness 任务回来了`,
|
|
156
|
+
running: (ids) => `还在运行:${ids.join('、')}`,
|
|
157
|
+
sep: ':', dot: ' · ',
|
|
158
|
+
},
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function took(r, w) {
|
|
162
|
+
const ms = Date.parse(r.ended_at) - Date.parse(r.started_at)
|
|
163
|
+
if (!(ms >= 0)) return null
|
|
164
|
+
const s = Math.round(ms / 1000)
|
|
165
|
+
const [h, m, sec] = [Math.floor(s / 3600), Math.floor((s % 3600) / 60), s % 60]
|
|
166
|
+
const parts = h ? [[h, 0], [m, 1]] : m ? [[m, 1], [sec, 2]] : [[sec, 2]]
|
|
167
|
+
return parts.filter(([v], i) => v || i === 0).map(([v, u]) => `${v}${w.units[u]}`).join(w.join)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// By characters, not UTF-16 units, so an emoji is never cut in half.
|
|
171
|
+
const clip = (text, n = 100) => {
|
|
172
|
+
const t = Array.from(String(text ?? '').replace(/\s+/g, ' ').trim())
|
|
173
|
+
return t.length > n ? `${t.slice(0, n).join('')}…` : t.join('')
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function runLine(r, w) {
|
|
177
|
+
if (r.needs_input) {
|
|
178
|
+
const qs = r.question?.questions ?? []
|
|
179
|
+
return r.needs_input === 'harness-ui' && !qs.length ? w.approval : w.asks(clip(qs[0]?.question), qs.length)
|
|
180
|
+
}
|
|
181
|
+
const t = took(r, w)
|
|
182
|
+
const bits = [`${w.state[r.state] ?? r.state}${t ? w.took(t) : ''}`]
|
|
183
|
+
const ch = r.evidence?.changes
|
|
184
|
+
if (ch) bits.push(w.files((ch.created?.length ?? 0) + (ch.modified?.length ?? 0) + (ch.deleted?.length ?? 0), ch.truncated))
|
|
185
|
+
// A check skipped for lack of time did not fail: it is counted apart.
|
|
186
|
+
const checks = r.evidence?.checks
|
|
187
|
+
if (checks?.length) {
|
|
188
|
+
const ok = checks.filter((c) => c.passed).length
|
|
189
|
+
const skipped = checks.filter((c) => c.skipped).length
|
|
190
|
+
bits.push(w.checks(ok, checks.length - ok - skipped, skipped, checks.length))
|
|
191
|
+
}
|
|
192
|
+
return bits.join(w.dot)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export function glanceFor(results, running = [], lang = 'en') {
|
|
196
|
+
const w = WORDS[lang] ?? WORDS.en
|
|
197
|
+
const lines = results.length === 1
|
|
198
|
+
? [`dsh-baton${w.dot}${results[0].run_id}${w.sep}${runLine(results[0], w)}`]
|
|
199
|
+
: [w.many(results.length), ...results.map((r) => `- ${r.run_id}${w.sep}${runLine(r, w)}`)]
|
|
200
|
+
if (running.length) lines.push(w.running(running))
|
|
201
|
+
return lines.join('\n')
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// What the model is told: a glance for the person first, then each run's result as
|
|
205
|
+
// dsh_wait returns it, and which runs of the same batch are still going (when one came
|
|
206
|
+
// back early to ask something or failed).
|
|
207
|
+
// Should the glance fail on an odd result, the model still gets everything.
|
|
208
|
+
function reasonFor(results, running, lang) {
|
|
209
|
+
let glance = ''
|
|
210
|
+
try { glance = `${glanceFor(results, running, lang)}\n\n` } catch { /* results only */ }
|
|
211
|
+
return `${glance}${modelPart(results, running)}`
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function modelPart(results, running) {
|
|
100
215
|
const one = results.length === 1 ? results[0] : null
|
|
101
216
|
const what = one
|
|
102
217
|
? `the Harness run ${one.run_id} you handed over with dsh_send has ${one.needs_input ? 'stopped to ask a question' : `ended (${one.state})`}`
|
|
@@ -109,13 +224,20 @@ function reasonFor(results, running) {
|
|
|
109
224
|
}
|
|
110
225
|
|
|
111
226
|
// What Codex shows the user for the wake (systemMessage), instead of the JSON in reason:
|
|
112
|
-
// a short line per run with its id and state. The model reads reason only.
|
|
113
|
-
export function summaryFor(results, running = []) {
|
|
114
|
-
|
|
227
|
+
// a short line per run with its id and state, in Codex's language. The model reads reason only.
|
|
228
|
+
export function summaryFor(results, running = [], lang = 'en') {
|
|
229
|
+
if (lang === 'zh') {
|
|
230
|
+
const one = (r) => r.needs_input ? `${r.run_id} 在等回答` : `${r.run_id}(${r.state})`
|
|
231
|
+
const head = results.length === 1
|
|
232
|
+
? (results[0].needs_input ? `dsh-baton:Harness 任务 ${one(results[0])},问题已交给 Codex` : `dsh-baton:Harness 任务 ${one(results[0])}已结束,结果已交回 Codex`)
|
|
233
|
+
: `dsh-baton:${results.length} 个 Harness 任务已交回 Codex:${results.map(one).join(',')}`
|
|
234
|
+
return running.length ? `${head};还在运行 ${running.length} 个` : head
|
|
235
|
+
}
|
|
236
|
+
const one = (r) => r.needs_input ? `${r.run_id} waiting for an answer` : `${r.run_id} (${r.state})`
|
|
115
237
|
const head = results.length === 1
|
|
116
|
-
? (results[0].needs_input ? `dsh-baton
|
|
117
|
-
: `dsh-baton
|
|
118
|
-
return running.length ? `${head}
|
|
238
|
+
? (results[0].needs_input ? `dsh-baton: Harness run ${one(results[0])}, question passed to Codex` : `dsh-baton: Harness run ${one(results[0])} ended, result passed to Codex`)
|
|
239
|
+
: `dsh-baton: ${results.length} Harness runs passed to Codex: ${results.map(one).join(', ')}`
|
|
240
|
+
return running.length ? `${head}; ${running.length} still running` : head
|
|
119
241
|
}
|
|
120
242
|
|
|
121
243
|
// The JSON Codex writes to the hook's stdin, or null. A leading BOM is dropped (seen on
|
|
@@ -146,7 +268,7 @@ function ranAs() {
|
|
|
146
268
|
// last of them ends, with all their results, so it reviews the parallel parts together.
|
|
147
269
|
// A run that stops to ask a question or fails wakes it at once (with whatever else has
|
|
148
270
|
// ended by then); the rest keep waiting for the next Stop.
|
|
149
|
-
export async function runHook(input, { shape, wait = core.api.wait, now = Date.now, chunk_s = 10, self = ranAs() } = {}) {
|
|
271
|
+
export async function runHook(input, { shape, wait = core.api.wait, now = Date.now, chunk_s = 10, self = ranAs(), lang } = {}) {
|
|
150
272
|
fs.mkdirSync(dir(), { recursive: true, mode: 0o700 })
|
|
151
273
|
fs.writeFileSync(aliveFile(), JSON.stringify({ at: now(), ...self }))
|
|
152
274
|
const thread = input?.session_id
|
|
@@ -176,9 +298,12 @@ export async function runHook(input, { shape, wait = core.api.wait, now = Date.n
|
|
|
176
298
|
if (st.needs_input || st.state !== 'completed') urgent = true
|
|
177
299
|
}
|
|
178
300
|
if (ended.size && (urgent || !running.length)) {
|
|
179
|
-
|
|
301
|
+
// Marked delivered only once the message exists, so a failure here loses no result.
|
|
180
302
|
const results = [...ended].map(([run_id, st]) => ({ run_id, ...shape(st) }))
|
|
181
|
-
|
|
303
|
+
const language = lang ?? codexLang()
|
|
304
|
+
const out = { decision: 'block', reason: reasonFor(results, running, language), systemMessage: summaryFor(results, running, language) }
|
|
305
|
+
for (const [runId, st] of ended) delivered(runId, st)
|
|
306
|
+
return out
|
|
182
307
|
}
|
|
183
308
|
if (!running.length) return null
|
|
184
309
|
if (now() > deadline) return null
|