dsh-baton-mcp 0.5.1 → 0.5.2
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 +8 -4
- package/README.zh-CN.md +4 -4
- package/WINDOWS.md +2 -2
- package/host/lib/index.js +6 -4
- package/host/package.json +2 -2
- package/package.json +1 -1
- package/server.json +2 -2
- package/src/register.mjs +21 -8
- package/src/server.mjs +4 -2
- package/src/setup.mjs +13 -3
package/README.md
CHANGED
|
@@ -7,9 +7,13 @@
|
|
|
7
7
|
</p>
|
|
8
8
|
|
|
9
9
|
<p align="center">
|
|
10
|
-
macOS · Windows (<a href="WINDOWS.md">guide</a>) · Node.js
|
|
10
|
+
macOS · Windows (<a href="WINDOWS.md">guide</a>) · no Node.js needed · zero dependencies · MIT
|
|
11
11
|
</p>
|
|
12
12
|
|
|
13
|
+
Claude Code and Codex each hand a task to DeepSeek Harness, and its subagents build this rainy-night street in 1 h 41 min (40 s, sound on):
|
|
14
|
+
|
|
15
|
+
https://github.com/user-attachments/assets/72e2fdff-275e-48bc-8e5a-39ffb025a0a8
|
|
16
|
+
|
|
13
17
|
<p align="center">
|
|
14
18
|
<img src="./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">
|
|
15
19
|
</p>
|
|
@@ -40,7 +44,7 @@ Codex and Claude take turns in the same Harness session, and every message is la
|
|
|
40
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: in a new chat, ask for dsh_list_workspaces">
|
|
41
45
|
</p>
|
|
42
46
|
|
|
43
|
-
Requires DeepSeek Harness ≥ 0.2.1-rc.2 (signed in)
|
|
47
|
+
Requires DeepSeek Harness ≥ 0.2.1-rc.2 (signed in) and Claude Code or Codex. Nothing else: no Node.js, no terminal. The steps are the same on macOS and Windows.
|
|
44
48
|
|
|
45
49
|
**1. Add the plugin in Harness:** **Plugins** (插件) → **Add plugin** (添加插件) → type `dsh-baton-host` → **Install** (安装) → **Enable now** (立即启用).
|
|
46
50
|
|
|
@@ -48,7 +52,7 @@ Switched on, the plugin registers dsh-baton for Claude Code and Codex, for every
|
|
|
48
52
|
|
|
49
53
|
**2. Check:** in a **new** Claude Code or Codex conversation, ask it to call `dsh_list_workspaces`. It lists your Harness workspaces.
|
|
50
54
|
|
|
51
|
-
That is all. If Harness says it could not register
|
|
55
|
+
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).
|
|
52
56
|
|
|
53
57
|
## How it works
|
|
54
58
|
|
|
@@ -134,7 +138,7 @@ When the plugin is unreachable, the MCP server falls back to Harness's `dsh head
|
|
|
134
138
|
|
|
135
139
|
## Install details
|
|
136
140
|
|
|
137
|
-
**What the plugin does when switched on.** It copies the MCP server to `~/.dsh-baton/app`, a folder that stays put, and writes the MCP entry into `~/.claude.json` and `~/.codex/config.toml` directly, keeping a `*.bak-dsh-baton` backup of each. It
|
|
141
|
+
**What the plugin does when switched on.** It copies the MCP server to `~/.dsh-baton/app`, a folder that stays put, and writes the MCP entry into `~/.claude.json` and `~/.codex/config.toml` directly, keeping a `*.bak-dsh-baton` backup of each. It starts the server with your own Node.js ≥ 22.15 when it finds one on `PATH`; without one, with Harness's own runtime (the Harness app in Node mode, `ELECTRON_RUN_AS_NODE=1`), so nothing else has to be installed. Either way it works with only the desktop apps installed, when `claude` / `codex` are not on your `PATH`. It runs again on each Harness start: an entry that is already right, or a working one that starts another copy of dsh-baton, is left alone. In Harness, the source switches to the npm registry ("Official npm registry", 「npm 官方源」) by itself once you type the package name, and no restart is needed. To turn the registration off, set `DSH_BATON_AUTO_REGISTER=0` for Harness.
|
|
138
142
|
|
|
139
143
|
**`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.
|
|
140
144
|
|
package/README.zh-CN.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
</p>
|
|
8
8
|
|
|
9
9
|
<p align="center">
|
|
10
|
-
macOS · Windows(<a href="WINDOWS.md">说明</a>)· Node.js
|
|
10
|
+
macOS · Windows(<a href="WINDOWS.md">说明</a>)· 不用装 Node.js · 零依赖 · MIT
|
|
11
11
|
</p>
|
|
12
12
|
|
|
13
13
|
<p align="center">
|
|
@@ -40,7 +40,7 @@ Codex 和 Claude 轮流往同一个 Harness 对话发消息,每条都标明是
|
|
|
40
40
|
<img src="./assets/readme/zh/install.svg" width="100%" alt="第一步:Harness → 插件 → 添加插件 → 输入 dsh-baton-host → 安装 → 立即启用,它会为 Claude Code 和 Codex 注册 dsh-baton。第二步:新开对话,让它调用 dsh_list_workspaces">
|
|
41
41
|
</p>
|
|
42
42
|
|
|
43
|
-
需要:DeepSeek Harness ≥ 0.2.1-rc.2
|
|
43
|
+
需要:DeepSeek Harness ≥ 0.2.1-rc.2 并已登录;装了 Claude Code 或 Codex。别的都不用装,不用 Node.js,也不用开终端。macOS 和 Windows 步骤完全相同。
|
|
44
44
|
|
|
45
45
|
**1. 在 Harness 里添加插件:** 插件 → 添加插件 → 输入 `dsh-baton-host` → 安装 → 立即启用。
|
|
46
46
|
|
|
@@ -48,7 +48,7 @@ Codex 和 Claude 轮流往同一个 Harness 对话发消息,每条都标明是
|
|
|
48
48
|
|
|
49
49
|
**2. 检查:**在 Claude Code 或 Codex 里**新开**一个对话,让它调用 `dsh_list_workspaces`,会列出你的 Harness 工作区。
|
|
50
50
|
|
|
51
|
-
就这些。如果 Harness
|
|
51
|
+
就这些。如果 Harness 提示没能注册,在终端运行 `npx -y dsh-baton-mcp setup`(这需要 Node.js ≥ 22.15),它做的是同样的注册。更新、卸载和 Windows 说明在[后面](#安装细节)。
|
|
52
52
|
|
|
53
53
|
## 工作原理
|
|
54
54
|
|
|
@@ -136,7 +136,7 @@ dsh-baton rename-workspace ~/code/new-app 新名字
|
|
|
136
136
|
|
|
137
137
|
## 安装细节
|
|
138
138
|
|
|
139
|
-
**插件启用时做了什么。**它把 MCP 服务器复制到 `~/.dsh-baton/app` 这个固定的文件夹,再直接把 MCP 注册写进 `~/.claude.json` 和 `~/.codex/config.toml`,各留一份 `*.bak-dsh-baton`
|
|
139
|
+
**插件启用时做了什么。**它把 MCP 服务器复制到 `~/.dsh-baton/app` 这个固定的文件夹,再直接把 MCP 注册写进 `~/.claude.json` 和 `~/.codex/config.toml`,各留一份 `*.bak-dsh-baton` 备份。电脑上有 Node.js ≥ 22.15(在 `PATH` 里能找到)就用它启动服务器;没有的话,就用 Harness 自带的运行环境(Harness 应用本身以 Node 模式运行,`ELECTRON_RUN_AS_NODE=1`),不用另外安装任何东西。两种情况下,只装了桌面应用、`claude` / `codex` 不在 `PATH` 里也能用。每次 Harness 启动都会再检查一遍:已经正确的注册、或者指向另一份可用 dsh-baton 的注册,都不会改动。在 Harness 里输入包名后,安装源会自动切到「npm 官方源」(英文界面是 "Official npm registry"),不用另外选择,也不用重启。不想让插件自动注册,给 Harness 设置 `DSH_BATON_AUTO_REGISTER=0`。
|
|
140
140
|
|
|
141
141
|
**`npx -y dsh-baton-mcp setup`** 在终端里做同样的注册:用于插件没能注册的情况,或者从 MCP 目录过来的用户。插件已经注册过时它什么都不改;插件没装的话,它会提示去添加。
|
|
142
142
|
|
package/WINDOWS.md
CHANGED
|
@@ -22,7 +22,7 @@ Verified on Windows 11 with DeepSeek Harness 0.2.1-rc.2 (desktop app), Node.js 2
|
|
|
22
22
|
|
|
23
23
|
Exactly as in the [README](README.md#quick-start): in Harness add the plugin `dsh-baton-host` and switch it on. Updating and removing are the same too ([Install details](README.md#install-details)). On Windows:
|
|
24
24
|
|
|
25
|
-
- Switched on, the plugin copies the MCP server to `%USERPROFILE%\.dsh-baton\app` and writes `%USERPROFILE%\.claude.json` and `%USERPROFILE%\.codex\config.toml` with the absolute path of `node.exe` (from `PATH`, `%ProgramFiles%\nodejs` or nvm-windows' `NVM_SYMLINK
|
|
25
|
+
- Switched on, the plugin copies the MCP server to `%USERPROFILE%\.dsh-baton\app` and writes `%USERPROFILE%\.claude.json` and `%USERPROFILE%\.codex\config.toml` with the absolute path of `node.exe` (from `PATH`, `%ProgramFiles%\nodejs` or nvm-windows' `NVM_SYMLINK`). Without Node.js it registers `DeepSeek Harness.exe` itself with `ELECTRON_RUN_AS_NODE=1` (Node mode, no window), so nothing else needs installing, so the desktop apps work even though their `claude.exe` / `codex.exe` are usually not on `PATH`. If it reports that it could not register, run `npx -y dsh-baton-mcp setup` in PowerShell.
|
|
26
26
|
- Claude Code runs the background `wait_command` in Git Bash.
|
|
27
27
|
- With the hoisted profile layout, uninstalling a plugin can leave a `node_modules\dsh-baton-host` (or old `dsh-bridge-host`) link in `%USERPROFILE%\.dsh\profiles\desktop`; `setup --remove` deletes it once the profile no longer lists the plugin.
|
|
28
28
|
|
|
@@ -96,6 +96,6 @@ The suite runs on Windows with named pipes. Two POSIX-only checks are skipped: f
|
|
|
96
96
|
- **套接字改为命名管道。** Windows 上 Node 不能监听 Unix 套接字文件,所以同一路径被映射成按用户区分的命名管道 `\\.\pipe\dsh-baton-<哈希>`。插件和 MCP 两边算法一致,`DSH_BATON_SOCKET` 仍然可用,Harness 退出后管道自动消失。
|
|
97
97
|
- **headless 用 Harness 自带的 CLI。** Harness ≥ 0.2.1 自带 `resources\runtime\cli\bin\dsh.cmd`;更早的版本只有打包在 `app.asar` 里的 CLI,由 Harness 自己的 exe 以 Node 模式运行。会按顺序自动查找:`DSH_BIN`、`PATH` 里的 `dsh`、默认安装位置,以及注册表里的自定义安装目录。
|
|
98
98
|
- **安装和 macOS 完全相同。** 在 Harness 插件页添加 `dsh-baton-host` 并启用,详见 [README](README.zh-CN.md#快速上手)。
|
|
99
|
-
- **插件启用时自动注册 MCP。** 它把 MCP 服务器复制到 `%USERPROFILE%\.dsh-baton\app`,再直接写 Claude Code 和 Codex 的用户级配置(用 `PATH`、`%ProgramFiles%\nodejs` 或 nvm-windows 里找到的 `node.exe`
|
|
99
|
+
- **插件启用时自动注册 MCP。** 它把 MCP 服务器复制到 `%USERPROFILE%\.dsh-baton\app`,再直接写 Claude Code 和 Codex 的用户级配置(用 `PATH`、`%ProgramFiles%\nodejs` 或 nvm-windows 里找到的 `node.exe` 绝对路径;没装 Node.js 时,改用 `DeepSeek Harness.exe` 本身以 Node 模式运行,不弹窗口,不用另外安装任何东西),桌面版的 `claude` / `codex` 不在 `PATH` 里也没关系。之后新开对话就有 `dsh_*` 工具;Codex 每次最多等 270 秒,不用再配超时。没能注册时,在 PowerShell 里运行 `npx -y dsh-baton-mcp setup`。
|
|
100
100
|
- **插件页卸不掉或装不上时**(`ERR_PNPM_UNEXPECTED_STORE`,或提示"该插件已安装"):是旧版 Harness 装 profile 时用了另一个 pnpm store。一次性修好:从菜单退出 Harness,把 `%USERPROFILE%\.dsh\profiles\desktop\node_modules` 改名备份,用 Harness 自带的 pnpm 运行 `install --frozen-lockfile`(不加 `--store-dir`;`$app` 改成你的安装目录,比如 `E:\Software\DeepSeek Harness`),再启动 Harness,插件页的卸载和添加就正常了;如果插件被关掉了,记得重新打开。
|
|
101
101
|
- **headless 需要独立凭据。** 要么登录账户,要么设置 `DEEPSEEK_API_KEY`。桌面版里保存的 Key,headless CLI 读不到。
|
package/host/lib/index.js
CHANGED
|
@@ -663,8 +663,10 @@ export function apply(ctx) {
|
|
|
663
663
|
setImmediate(guard('register', async () => {
|
|
664
664
|
const root = serverRoot()
|
|
665
665
|
if (!root) { registration = { state: 'done', ok: false, error: 'no-server', clients: [] }; return }
|
|
666
|
-
const { autoRegister } = await import(pathToFileURL(path.join(root, 'src', 'register.mjs')).href)
|
|
667
|
-
|
|
666
|
+
const { autoRegister, harnessRuntime } = await import(pathToFileURL(path.join(root, 'src', 'register.mjs')).href)
|
|
667
|
+
// Inside the desktop app, its own binary can stand in for a missing Node.js.
|
|
668
|
+
const fallback = process.versions.electron && harnessRuntime ? harnessRuntime(process.execPath) : null
|
|
669
|
+
const result = autoRegister({ root, harnessDir: harnessDir(), fallback })
|
|
668
670
|
registration = { state: 'done', ...result }
|
|
669
671
|
const summary = result.clients.map((c) => `${c.label}: ${c.state}${c.error ? ` (${c.error})` : ''}`).join(', ')
|
|
670
672
|
ctx.logger?.[result.ok ? 'info' : 'error']?.(`dsh-baton-host MCP registration: ${summary || result.error}`)
|
|
@@ -678,9 +680,9 @@ export function apply(ctx) {
|
|
|
678
680
|
kind: 'exact',
|
|
679
681
|
path: '/dsh-baton/status',
|
|
680
682
|
handler: (request, response) => {
|
|
681
|
-
const { state, ok, error, version, clients = [] } = registration
|
|
683
|
+
const { state, ok, error, version, runtime, clients = [] } = registration
|
|
682
684
|
response.writeHead(200, { 'content-type': 'application/json', 'cache-control': 'no-store' })
|
|
683
|
-
response.end(JSON.stringify({ version: VERSION, registration: { state, ok, error, version, clients: clients.map(({ id, label, state }) => ({ id, label, state })) } }))
|
|
685
|
+
response.end(JSON.stringify({ version: VERSION, registration: { state, ok, error, version, runtime, clients: clients.map(({ id, label, state }) => ({ id, label, state })) } }))
|
|
684
686
|
},
|
|
685
687
|
}), 'dsh-baton-host status route')
|
|
686
688
|
})
|
package/host/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-baton-host",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.2",
|
|
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.5.
|
|
23
|
+
"dsh-baton-mcp": "0.5.2"
|
|
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.5.
|
|
3
|
+
"version": "0.5.2",
|
|
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.5.
|
|
10
|
+
"version": "0.5.2",
|
|
11
11
|
"packages": [
|
|
12
12
|
{
|
|
13
13
|
"registryType": "npm",
|
|
14
14
|
"identifier": "dsh-baton-mcp",
|
|
15
|
-
"version": "0.5.
|
|
15
|
+
"version": "0.5.2",
|
|
16
16
|
"transport": {
|
|
17
17
|
"type": "stdio"
|
|
18
18
|
}
|
package/src/register.mjs
CHANGED
|
@@ -25,7 +25,7 @@ export function editClaudeConfig(text, entry) {
|
|
|
25
25
|
const cfg = text.trim() ? JSON.parse(text) : {}
|
|
26
26
|
const servers = { ...(cfg.mcpServers ?? {}) }
|
|
27
27
|
delete servers[OLD_NAME]
|
|
28
|
-
if (entry) servers[NAME] = { type: 'stdio', command: entry.command, args: entry.args, env: {} }
|
|
28
|
+
if (entry) servers[NAME] = { type: 'stdio', command: entry.command, args: entry.args, env: entry.env ?? {} }
|
|
29
29
|
else delete servers[NAME]
|
|
30
30
|
return JSON.stringify({ ...cfg, mcpServers: servers }, null, 2) + '\n'
|
|
31
31
|
}
|
|
@@ -55,6 +55,8 @@ export function editCodexConfig(text, entry) {
|
|
|
55
55
|
let out = kept.join('\n').replace(/\s+$/, '')
|
|
56
56
|
if (entry) {
|
|
57
57
|
out += `${out ? '\n\n' : ''}[mcp_servers.${NAME}]\ncommand = ${tomlString(entry.command)}\nargs = [${entry.args.map(tomlString).join(', ')}]`
|
|
58
|
+
const env = Object.entries(entry.env ?? {})
|
|
59
|
+
if (env.length) out += `\n\n[mcp_servers.${NAME}.env]\n${env.map(([k, v]) => `${k} = ${tomlString(v)}`).join('\n')}`
|
|
58
60
|
}
|
|
59
61
|
return out ? out + '\n' : ''
|
|
60
62
|
}
|
|
@@ -185,6 +187,11 @@ export function compareVersions(a, b) {
|
|
|
185
187
|
return (String(b).includes('-') ? 1 : 0) - (String(a).includes('-') ? 1 : 0)
|
|
186
188
|
}
|
|
187
189
|
|
|
190
|
+
// Without a Node.js of the user's own, the server runs on Harness's: the app binary in
|
|
191
|
+
// Node mode, the way Harness runs its own scripts. Its path is the app's own (it moves
|
|
192
|
+
// only if the app does), and if it ever breaks, the next Harness start rewrites it.
|
|
193
|
+
export const harnessRuntime = (exe) => ({ command: exe, env: { ELECTRON_RUN_AS_NODE: '1' } })
|
|
194
|
+
|
|
188
195
|
// Run by the Harness plugin when it is switched on (and on every Harness start, so a
|
|
189
196
|
// plugin update brings the server along). `root` holds the MCP server package: the repo
|
|
190
197
|
// checkout the plugin was added from, or the dsh-baton-mcp package installed with it.
|
|
@@ -192,9 +199,11 @@ export function compareVersions(a, b) {
|
|
|
192
199
|
// as is, like `setup` from a clone) or that copy is already as new.
|
|
193
200
|
// - An entry that already starts this server, or a working one that starts another copy
|
|
194
201
|
// (a clone, say), is left alone; a missing or broken one is (re)written.
|
|
202
|
+
// - It starts the server with the user's Node.js when there is a new enough one, else
|
|
203
|
+
// with `fallback` (Harness's own runtime, see harnessRuntime).
|
|
195
204
|
// Never throws: the result says what happened to each client.
|
|
196
|
-
export function autoRegister({ root, home = os.homedir(), env = process.env, node = null, harnessDir = null, find = findNode, app = appDir(batonHome(env, home)) } = {}) {
|
|
197
|
-
const result = { ok: false, version: versionOf(root), clients: [], error: null, node: null }
|
|
205
|
+
export function autoRegister({ root, home = os.homedir(), env = process.env, node = null, harnessDir = null, fallback = null, find = findNode, app = appDir(batonHome(env, home)) } = {}) {
|
|
206
|
+
const result = { ok: false, version: versionOf(root), clients: [], error: null, node: null, runtime: null }
|
|
198
207
|
try {
|
|
199
208
|
let serverRoot = root
|
|
200
209
|
if (!fs.existsSync(path.join(root, '.git')) && path.resolve(root) !== path.resolve(app)) {
|
|
@@ -205,7 +214,7 @@ export function autoRegister({ root, home = os.homedir(), env = process.env, nod
|
|
|
205
214
|
const script = path.join(serverRoot, 'src', 'server.mjs')
|
|
206
215
|
const works = (e) => e && fs.existsSync(e.command) && e.args?.[0] && fs.existsSync(e.args[0])
|
|
207
216
|
const list = clients(env, home)
|
|
208
|
-
let
|
|
217
|
+
let launch = node ? { command: node } : null
|
|
209
218
|
for (const c of list) {
|
|
210
219
|
if (!c.present) { result.clients.push({ id: c.id, label: c.label, state: 'not-installed' }); continue }
|
|
211
220
|
try {
|
|
@@ -216,15 +225,19 @@ export function autoRegister({ root, home = os.homedir(), env = process.env, nod
|
|
|
216
225
|
result.clients.push({ id: c.id, label: c.label, state: same ? 'unchanged' : 'kept', script: current.args[0] })
|
|
217
226
|
continue
|
|
218
227
|
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
228
|
+
if (!launch) {
|
|
229
|
+
const found = find({ env, exclude: [harnessDir] })
|
|
230
|
+
launch = found ? { command: found } : fallback
|
|
231
|
+
}
|
|
232
|
+
if (!launch) { result.clients.push({ id: c.id, label: c.label, state: 'failed', error: 'no-node' }); continue }
|
|
233
|
+
rewrite(c.file, (t) => c.edit(t, { ...launch, args: [script] }))
|
|
222
234
|
result.clients.push({ id: c.id, label: c.label, state: 'registered' })
|
|
223
235
|
} catch (e) {
|
|
224
236
|
result.clients.push({ id: c.id, label: c.label, state: 'failed', error: e.message })
|
|
225
237
|
}
|
|
226
238
|
}
|
|
227
|
-
result.node =
|
|
239
|
+
result.node = launch?.command ?? null
|
|
240
|
+
result.runtime = !launch ? null : launch === fallback ? 'harness' : 'node'
|
|
228
241
|
const present = result.clients.filter((c) => c.state !== 'not-installed')
|
|
229
242
|
result.ok = present.length > 0 && present.every((c) => c.state !== 'failed')
|
|
230
243
|
if (!present.length) result.error = 'no-clients'
|
package/src/server.mjs
CHANGED
|
@@ -36,10 +36,12 @@ const ARCHIVED = 'Sessions not in dsh_list_sessions are archived or gone: do not
|
|
|
36
36
|
// A shell command that blocks until a run ends and prints its reply. Run in the
|
|
37
37
|
// background by a client that wakes the model when the command exits (Claude Code),
|
|
38
38
|
// it waits without spending any turns. Plain "<node>" "<cli>" wait <id> form: it runs
|
|
39
|
-
// in POSIX shells and in Git Bash, which Claude Code uses on Windows too.
|
|
39
|
+
// in POSIX shells and in Git Bash, which Claude Code uses on Windows too. Registered on
|
|
40
|
+
// Harness's own binary (no Node.js installed), the command keeps it in Node mode.
|
|
40
41
|
const quote = (p) => `"${p}"`
|
|
42
|
+
const nodeMode = process.versions.electron ? 'ELECTRON_RUN_AS_NODE=1 ' : ''
|
|
41
43
|
const waitCommand = (runId) =>
|
|
42
|
-
/^[\w.-]+$/.test(runId ?? '') ? `${quote(stableNodePath())} ${quote(path.join(core.ROOT, 'src', 'cli.mjs'))} wait ${runId}` : undefined
|
|
44
|
+
/^[\w.-]+$/.test(runId ?? '') ? `${nodeMode}${quote(stableNodePath())} ${quote(path.join(core.ROOT, 'src', 'cli.mjs'))} wait ${runId}` : undefined
|
|
43
45
|
|
|
44
46
|
// Wait in short chunks, reporting progress between them (clients that support it can
|
|
45
47
|
// show it or reset their timer). An unfinished wait returns only the state, not the run's
|
package/src/setup.mjs
CHANGED
|
@@ -4,7 +4,7 @@ import fs from 'node:fs'
|
|
|
4
4
|
import os from 'node:os'
|
|
5
5
|
import path from 'node:path'
|
|
6
6
|
import { execFileSync } from 'node:child_process'
|
|
7
|
-
import { ROOT, config, hostAvailable, hostVersionSeen as versionOfHost, versionWarning } from './core.mjs'
|
|
7
|
+
import { ROOT, VERSION, config, hostAvailable, hostVersionSeen as versionOfHost, versionWarning } from './core.mjs'
|
|
8
8
|
import { appDir, installApp, rewrite, stableNodePath, claudeConfigPath, codexConfigPath, editClaudeConfig, editCodexConfig } from './register.mjs'
|
|
9
9
|
|
|
10
10
|
export { appDir, installApp, stableNodePath, claudeConfigPath, codexConfigPath, editClaudeConfig, editCodexConfig }
|
|
@@ -94,11 +94,21 @@ const pluginFrom = (source, hostDir) => source === `link:${hostDir}` || source =
|
|
|
94
94
|
// time. setup then copies it to a stable folder and registers that copy instead.
|
|
95
95
|
export const inNpxCache = (root = ROOT) => root.split(/[\\/]/).includes('_npx')
|
|
96
96
|
|
|
97
|
+
export function sameVersionAt(dir, version = VERSION) {
|
|
98
|
+
try {
|
|
99
|
+
return JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf8')).version === version && fs.existsSync(path.join(dir, 'src', 'server.mjs'))
|
|
100
|
+
} catch {
|
|
101
|
+
return false
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
97
105
|
export async function setup({ remove = false } = {}) {
|
|
98
|
-
|
|
106
|
+
// The same version is already in place (the Harness plugin copied it, say): leave it.
|
|
107
|
+
const copy = !remove && inNpxCache() && !sameVersionAt(appDir())
|
|
108
|
+
const root = !remove && inNpxCache() ? (copy ? installApp(ROOT) : appDir()) : ROOT
|
|
99
109
|
const entry = remove ? null : { command: stableNodePath(), args: [path.join(root, 'src', 'server.mjs')] }
|
|
100
110
|
const lines = []
|
|
101
|
-
if (root !== ROOT) lines.push(`Installed dsh-baton to ${root}`)
|
|
111
|
+
if (root !== ROOT) lines.push(copy ? `Installed dsh-baton to ${root}` : `dsh-baton ${VERSION} is already in ${root}`)
|
|
102
112
|
const register = (label, file, present, edit) => {
|
|
103
113
|
if (!remove && !present) return lines.push(`- ${label}: not installed, skipped`)
|
|
104
114
|
try {
|