dsh-baton-mcp 0.3.0 → 0.3.1
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/WINDOWS.md +1 -1
- package/host/lib/index.js +1 -1
- package/host/package.json +1 -1
- package/package.json +1 -1
- package/src/core.mjs +14 -1
- package/src/server.mjs +7 -2
- package/src/setup.mjs +37 -1
package/WINDOWS.md
CHANGED
|
@@ -34,7 +34,7 @@ Nothing else to configure: Claude Code waits for a run in the background (Git Ba
|
|
|
34
34
|
|
|
35
35
|
Remove: `npx -y dsh-baton-mcp setup --remove`, then Harness → Plugins → dsh-baton-host → Uninstall, then `setup --remove` once more to delete the app folder.
|
|
36
36
|
|
|
37
|
-
Upgrading from dsh-bridge: `setup` replaces the old MCP entries and reuses an existing `%USERPROFILE%\.dsh-bridge` (the app then goes to `.dsh-bridge\app`). Uninstall `dsh-bridge-host` in Harness, then add the path `setup` copied. With the hoisted profile layout, uninstalling may leave a `node_modules\dsh-bridge-host` link behind in `%USERPROFILE%\.dsh\profiles\desktop`;
|
|
37
|
+
Upgrading from dsh-bridge: `setup` replaces the old MCP entries and reuses an existing `%USERPROFILE%\.dsh-bridge` (the app then goes to `.dsh-bridge\app`). Uninstall `dsh-bridge-host` in Harness, then add the path `setup` copied. With the hoisted profile layout, uninstalling may leave a `node_modules\dsh-bridge-host` link behind in `%USERPROFILE%\.dsh\profiles\desktop`; `setup --remove` deletes such a leftover link (old or new name) once the profile no longer lists the plugin.
|
|
38
38
|
|
|
39
39
|
From source instead: `git clone https://github.com/JialaoLiu/dsh-baton.git C:\dev\dsh-baton` and `node C:\dev\dsh-baton\src\cli.mjs setup`; the plugin path is then `C:\dev\dsh-baton\host`.
|
|
40
40
|
|
package/host/lib/index.js
CHANGED
|
@@ -15,7 +15,7 @@ import { socketEndpoint, batonHome, socketPath as socketPathSetting } from './so
|
|
|
15
15
|
export const name = 'dsh-baton-host'
|
|
16
16
|
export const inject = ['sessionController', 'workspaceRegistry']
|
|
17
17
|
|
|
18
|
-
const VERSION = '
|
|
18
|
+
const VERSION = JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version
|
|
19
19
|
const MAX_EVENTS = 200
|
|
20
20
|
const MAX_RUNS = 100
|
|
21
21
|
const QUIET_CONFIRM_MS = 2000
|
package/host/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-baton-host",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "DeepSeek Harness plugin for dsh-baton: takes the baton from Claude Code or Codex 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",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-baton-mcp",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"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.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/core.mjs
CHANGED
|
@@ -150,15 +150,28 @@ function hostCallAt(endpoint, method, params = {}, { timeoutMs = 15000 } = {}) {
|
|
|
150
150
|
})
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
+
export const VERSION = JSON.parse(fs.readFileSync(path.join(ROOT, 'package.json'), 'utf8')).version
|
|
154
|
+
|
|
155
|
+
// The plugin version from the last successful ping.
|
|
156
|
+
let hostVersion = null
|
|
157
|
+
|
|
153
158
|
export async function hostAvailable() {
|
|
154
159
|
try {
|
|
155
|
-
await hostCall('ping', {}, { timeoutMs: 2000 })
|
|
160
|
+
hostVersion = (await hostCall('ping', {}, { timeoutMs: 2000 }))?.version ?? null
|
|
156
161
|
return true
|
|
157
162
|
} catch {
|
|
158
163
|
return false
|
|
159
164
|
}
|
|
160
165
|
}
|
|
161
166
|
|
|
167
|
+
// The plugin and the MCP server update separately (Harness and npx), so they can drift.
|
|
168
|
+
// A different major.minor gets a note asking to update the older side; patch releases mix.
|
|
169
|
+
const minorOf = (v) => String(v).split('.').slice(0, 2).join('.')
|
|
170
|
+
export function versionWarning(plugin = hostVersion, server = VERSION) {
|
|
171
|
+
if (!plugin || minorOf(plugin) === minorOf(server)) return null
|
|
172
|
+
return `The Harness plugin dsh-baton-host is ${plugin} but this MCP server is ${server}. Update the older one: the server with \`npx -y dsh-baton-mcp@latest setup\`, the plugin in Harness (Plugins → dsh-baton-host → Uninstall, then Add plugin → dsh-baton-host).`
|
|
173
|
+
}
|
|
174
|
+
|
|
162
175
|
// Who a relayed message is from, shown on it in the Harness chat: DSH_BATON_SENDER if set,
|
|
163
176
|
// else the MCP client's name (claude-code, claude-ai → claude; codex-mcp-client → codex).
|
|
164
177
|
export function senderOf(clientName = setting('SENDER')) {
|
package/src/server.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import path from 'node:path'
|
|
|
5
5
|
import * as core from './core.mjs'
|
|
6
6
|
import { stableNodePath } from './setup.mjs'
|
|
7
7
|
|
|
8
|
-
const SERVER = { name: 'dsh-baton', version:
|
|
8
|
+
const SERVER = { name: 'dsh-baton', version: core.VERSION }
|
|
9
9
|
const PROTOCOLS = ['2025-06-18', '2025-03-26', '2024-11-05']
|
|
10
10
|
|
|
11
11
|
const str = (description) => ({ type: 'string', description })
|
|
@@ -154,6 +154,7 @@ const TOOLS = [
|
|
|
154
154
|
]
|
|
155
155
|
|
|
156
156
|
let clientName = null
|
|
157
|
+
let versionWarned = false
|
|
157
158
|
|
|
158
159
|
function send(msg) {
|
|
159
160
|
process.stdout.write(JSON.stringify(msg) + '\n')
|
|
@@ -184,7 +185,11 @@ async function handle(req) {
|
|
|
184
185
|
const progress = token === undefined ? undefined : (done, message) =>
|
|
185
186
|
send({ jsonrpc: '2.0', method: 'notifications/progress', params: { progressToken: token, progress: done, message } })
|
|
186
187
|
const result = await tool.run(req.params.arguments || {}, { progress })
|
|
187
|
-
|
|
188
|
+
const content = [{ type: 'text', text: JSON.stringify(result, null, 2) }]
|
|
189
|
+
// Said once per server process, the first time a call finds the versions apart.
|
|
190
|
+
const warning = !versionWarned && core.versionWarning()
|
|
191
|
+
if (warning) { versionWarned = true; content.push({ type: 'text', text: `Note for the user: ${warning}` }) }
|
|
192
|
+
return { content }
|
|
188
193
|
} catch (e) {
|
|
189
194
|
return { content: [{ type: 'text', text: e.message }], isError: true }
|
|
190
195
|
}
|
package/src/setup.mjs
CHANGED
|
@@ -85,6 +85,31 @@ function installedPlugins() {
|
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
+
// With pnpm's hoisted layout, uninstalling the plugin can leave its node_modules link
|
|
89
|
+
// behind. Remove such a link once the profile no longer lists the plugin; only the
|
|
90
|
+
// link itself goes, never the folder it points to. Returns the links removed and the
|
|
91
|
+
// ones that could not be.
|
|
92
|
+
export function cleanStalePluginLinks(profile = path.join(config.dshHome, 'profiles', 'desktop')) {
|
|
93
|
+
let deps
|
|
94
|
+
try { deps = JSON.parse(fs.readFileSync(path.join(profile, 'package.json'), 'utf8')).dependencies ?? {} } catch { return { removed: [], failed: [] } }
|
|
95
|
+
const removed = [], failed = []
|
|
96
|
+
for (const name of ['dsh-baton-host', 'dsh-bridge-host']) {
|
|
97
|
+
const link = path.join(profile, 'node_modules', name)
|
|
98
|
+
if (deps[name]) continue
|
|
99
|
+
let st
|
|
100
|
+
try { st = fs.lstatSync(link) } catch { continue }
|
|
101
|
+
if (!st.isSymbolicLink()) continue
|
|
102
|
+
try {
|
|
103
|
+
// A Windows junction is a directory link: rmdir removes the link, not its target.
|
|
104
|
+
try { fs.unlinkSync(link) } catch { fs.rmdirSync(link) }
|
|
105
|
+
removed.push(link)
|
|
106
|
+
} catch {
|
|
107
|
+
failed.push(link)
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return { removed, failed }
|
|
111
|
+
}
|
|
112
|
+
|
|
88
113
|
const pluginFrom = (source, hostDir) => source === `link:${hostDir}` || source === `link:${hostDir.split(path.sep).join('/')}`
|
|
89
114
|
|
|
90
115
|
// The node to register: the one on PATH when it is this same binary, since that path
|
|
@@ -143,13 +168,24 @@ export async function setup({ remove = false } = {}) {
|
|
|
143
168
|
|
|
144
169
|
if (remove) {
|
|
145
170
|
const plugins = installedPlugins()
|
|
146
|
-
|
|
171
|
+
if (plugins.current || plugins.old) {
|
|
172
|
+
lines.push('', `To remove the Harness plugin: Harness → Plugins → ${plugins.current ? 'dsh-baton-host' : 'dsh-bridge-host'} → Uninstall.`)
|
|
173
|
+
} else {
|
|
174
|
+
lines.push('', 'Harness plugin: not installed.')
|
|
175
|
+
const links = cleanStalePluginLinks()
|
|
176
|
+
for (const l of links.removed) lines.push(`Removed the leftover plugin link ${l}.`)
|
|
177
|
+
for (const l of links.failed) lines.push(`Could not remove the leftover plugin link ${l}: quit Harness and delete it.`)
|
|
178
|
+
}
|
|
147
179
|
const app = appDir()
|
|
148
180
|
if (fs.existsSync(app)) {
|
|
149
181
|
// Harness loads the plugin from this folder: delete it only once the plugin is gone.
|
|
150
182
|
if (pluginFrom(plugins.current, path.join(app, 'host'))) lines.push(`Then run this again to delete ${app}.`)
|
|
151
183
|
else { fs.rmSync(app, { recursive: true, force: true }); lines.push(`Deleted ${app}.`) }
|
|
152
184
|
}
|
|
185
|
+
const home = config.batonHome
|
|
186
|
+
if (['runs', 'host-runs'].some((d) => fs.existsSync(path.join(home, d)))) {
|
|
187
|
+
lines.push(`Run records are kept in ${home} (runs, host-runs); delete that folder to remove them too.`)
|
|
188
|
+
}
|
|
153
189
|
return lines.join('\n')
|
|
154
190
|
}
|
|
155
191
|
lines.push('', 'MCP server registered user-wide: new conversations in Claude Code and Codex have the dsh_* tools.')
|