@xuanling-rs/xuanling-dsh-tools 0.2.2 → 0.2.4

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 umbrella22 and XuanLing contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README-ZH.md ADDED
@@ -0,0 +1,28 @@
1
+ # xuanling-dsh-tools
2
+
3
+ [English](README.md) | 简体中文
4
+
5
+ 这是完整 XuanLing MCP catalog 的增量式 DeepSeek Harness bundle。它保留全部 Harness
6
+ 原生工具,并以 `mcp__xuanling__` 前缀增加 XuanLing 工具。
7
+
8
+ ```sh
9
+ dsh plugin --profile web add @xuanling-rs/xuanling-dsh-tools@0.2.4
10
+ ```
11
+
12
+ 该命令会扩展 DSH 内置的 Web profile。内置 Headless profile 应使用 `--profile headless`。
13
+ 未知 profile 名称只包含 base bundle,本身不会提供可运行的 Web 或 Headless 应用。
14
+
15
+ 精确版本的 `@xuanling-rs/xuanling-mcp@0.2.4` runtime 会安装在 profile 内,并通过带校验的 JS launcher
16
+ 启动;不需要也不会使用全局 npm package。只有文件系统 capability root 与 DSH 工作目录不同时,
17
+ 才需要设置 `XUANLING_WORKSPACE_ROOT`。
18
+
19
+ 该 bundle 的 result adapter 会保留完整 Native 文本投影,只删除与 `structuredContent` 完全重复的
20
+ 意外文本块。
21
+
22
+ Adapter 只接受子进程 JSON object frame。输出 malformed 或子进程正常退出时仍有未结算 request 会以非零状态
23
+ 结束;Host 终止信号会被转发,子进程未在 500 ms grace 内退出时会被强制终止。
24
+
25
+ 该包会与 DSH 原生文件、进程和项目工具产生能力重叠。日常配置建议使用目录更小的
26
+ `@xuanling-rs/xuanling-dsh-memory`。不要在同一 profile 内安装多个 XuanLing 工具 bundle。
27
+
28
+ 要求 Node.js 22.14 或更高版本。使用 MIT License。
package/README.md ADDED
@@ -0,0 +1,34 @@
1
+ # xuanling-dsh-tools
2
+
3
+ English | [Simplified Chinese](README-ZH.md)
4
+
5
+ Additive DeepSeek Harness bundle for the complete XuanLing MCP catalog. It
6
+ keeps all Harness-native tools enabled and adds XuanLing tools under the
7
+ `mcp__xuanling__` prefix.
8
+
9
+ ```sh
10
+ dsh plugin --profile web add @xuanling-rs/xuanling-dsh-tools@0.2.4
11
+ ```
12
+
13
+ This command augments DSH's shipped Web profile. Use `--profile headless` for
14
+ the shipped Headless profile. Unknown profile names start with the base bundle
15
+ only and do not provide a runnable Web or Headless application by themselves.
16
+
17
+ The exact `@xuanling-rs/xuanling-mcp@0.2.4` runtime is installed in the profile and started
18
+ through its verified JS launcher. A global npm package is neither required nor
19
+ used. Set `XUANLING_WORKSPACE_ROOT` only when the filesystem capability root
20
+ must differ from the DSH working directory.
21
+
22
+ The bundle's result adapter preserves the complete Native text projection and
23
+ removes only accidental duplicate text blocks that exactly repeat
24
+ `structuredContent`.
25
+
26
+ The adapter accepts only JSON object frames from the child. Malformed output or
27
+ an unresolved request at clean child exit returns a nonzero status; host
28
+ termination is forwarded and force-terminated after a 500 ms grace when needed.
29
+
30
+ This package overlaps with native file, process, and project tools. Prefer
31
+ `@xuanling-rs/xuanling-dsh-memory` for the smaller daily tool surface. Do not install this
32
+ package with another XuanLing tool bundle in the same profile.
33
+
34
+ Node.js 22.14 or newer is required. Licensed under the MIT License.
@@ -0,0 +1,34 @@
1
+ # XuanLing tools for DeepSeek Harness — additive variant.
2
+ #
3
+ # Mounts the xuanling-mcp binary through the local result adapter and the
4
+ # official @deepseek-ai/dsh-mcp-client bridge. Every XuanLing tool becomes a native
5
+ # harness tool named mcp__xuanling__<raw> (for example mcp__xuanling__fs_read_text);
6
+ # the bridge owns reconnect, re-sync, and generation rollback. Built-in tools
7
+ # (read/write/edit/glob/grep/bash/…) stay registered — this variant only ADDS.
8
+ #
9
+ # The bundle resolves its exact result adapter and xuanling-mcp dependency from
10
+ # the DSH profile. The JS launcher then selects and verifies the matching native
11
+ # package; the adapter preserves one complete text projection and structured value.
12
+ #
13
+ # The filesystem capability root defaults to the directory dsh was started
14
+ # from; override with XUANLING_WORKSPACE_ROOT. Memory uses the shared default
15
+ # database (~/.xuanling/memory.db) exactly like the ZCode plugin deployment.
16
+ # The ZCode-only object-parameter compat shim stays OFF everywhere in this
17
+ # integration: DeepSeek forwards model arguments as verbatim JSON.
18
+
19
+ - insert:
20
+ - id: xuanling-tools
21
+ name: '@deepseek-ai/dsh-mcp-client'
22
+ config:
23
+ serverName: xuanling
24
+ transport: stdio
25
+ command: !!js process.execPath
26
+ args:
27
+ - !!js process.getBuiltinModule('node:module').createRequire(baseUrl).resolve('@xuanling-rs/xuanling-dsh-tools/mcp-result-adapter.mjs')
28
+ - '--binary'
29
+ - !!js process.execPath
30
+ - '--'
31
+ - !!js process.getBuiltinModule('node:module').createRequire(baseUrl).resolve('@xuanling-rs/xuanling-mcp/bin/xuanling-mcp.js')
32
+ - '--workspace-root'
33
+ - !!js process.env.XUANLING_WORKSPACE_ROOT ?? process.cwd()
34
+ toolCallTimeoutMs: 120000
@@ -0,0 +1,204 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { isDeepStrictEqual } from "node:util";
4
+ import { spawn } from "node:child_process";
5
+ import readline from "node:readline";
6
+ import { pathToFileURL } from "node:url";
7
+
8
+ const TEXT_RESULT_DUPLICATE_LIMIT = 1;
9
+ const CHILD_TERMINATION_GRACE_MS = 500;
10
+
11
+ function isRecord(value) {
12
+ return typeof value === "object" && value !== null && !Array.isArray(value);
13
+ }
14
+
15
+ export function requestIdKey(id) {
16
+ return `${typeof id}:${JSON.stringify(id)}`;
17
+ }
18
+
19
+ function parseFrame(line) {
20
+ try {
21
+ const frame = JSON.parse(line);
22
+ return isRecord(frame) ? frame : undefined;
23
+ } catch {
24
+ return undefined;
25
+ }
26
+ }
27
+
28
+ function validateChildFrame(line) {
29
+ if (!parseFrame(line)) throw new Error("invalid JSON or malformed child frame");
30
+ }
31
+
32
+ export function requestMethodAndId(line) {
33
+ const frame = parseFrame(line);
34
+ if (!frame || typeof frame.method !== "string" || !Object.hasOwn(frame, "id")) return undefined;
35
+ return { id: requestIdKey(frame.id), method: frame.method };
36
+ }
37
+
38
+ function parsedJsonText(text) {
39
+ if (typeof text !== "string") return { ok: false };
40
+ try {
41
+ return { ok: true, value: JSON.parse(text) };
42
+ } catch {
43
+ return { ok: false };
44
+ }
45
+ }
46
+
47
+ function matchesStructuredContent(block, structuredContent) {
48
+ if (!isRecord(block) || block.type !== "text") return false;
49
+ const parsed = parsedJsonText(block.text);
50
+ return parsed.ok && isDeepStrictEqual(parsed.value, structuredContent);
51
+ }
52
+
53
+ /**
54
+ * DSH keeps the complete text projection for Native/model rendering. If a
55
+ * server or an intermediate bridge accidentally emits the same structured
56
+ * JSON text block more than once, retain one copy and preserve the canonical
57
+ * structured value for Code Mode and output validation.
58
+ */
59
+ export function projectDshCallResult(result) {
60
+ if (!isRecord(result) || !Array.isArray(result.content) || !Object.hasOwn(result, "structuredContent")) {
61
+ return result;
62
+ }
63
+
64
+ const matching = result.content.filter((block) =>
65
+ matchesStructuredContent(block, result.structuredContent),
66
+ );
67
+ if (matching.length <= TEXT_RESULT_DUPLICATE_LIMIT) return result;
68
+
69
+ let retained = false;
70
+ const content = result.content.filter((block) => {
71
+ if (!matchesStructuredContent(block, result.structuredContent)) return true;
72
+ if (retained) return false;
73
+ retained = true;
74
+ return true;
75
+ });
76
+ return { ...result, content };
77
+ }
78
+
79
+ export function projectDshResponseFrame(line, pendingToolCallIds) {
80
+ const frame = parseFrame(line);
81
+ if (
82
+ !frame ||
83
+ !Object.hasOwn(frame, "id") ||
84
+ Object.hasOwn(frame, "method") ||
85
+ (!Object.hasOwn(frame, "result") && !Object.hasOwn(frame, "error"))
86
+ ) return line;
87
+ const key = requestIdKey(frame.id);
88
+ if (!pendingToolCallIds.delete(key)) return line;
89
+ if (!isRecord(frame.result)) return line;
90
+ const projected = projectDshCallResult(frame.result);
91
+ return projected === frame.result ? line : JSON.stringify({ ...frame, result: projected });
92
+ }
93
+
94
+ function usageError(message) {
95
+ throw new Error(`${message}; usage: mcp-result-adapter.mjs --binary <server> -- [args...]`);
96
+ }
97
+
98
+ function parseAdapterArgs(argv) {
99
+ if (argv[0] !== "--binary" || typeof argv[1] !== "string" || argv[1].length === 0) {
100
+ usageError("--binary is required");
101
+ }
102
+ if (argv[2] !== "--") usageError("expected -- before MCP server arguments");
103
+ return { binary: argv[1], childArgs: argv.slice(3) };
104
+ }
105
+
106
+ export function runAdapter(argv = process.argv.slice(2)) {
107
+ const { binary, childArgs } = parseAdapterArgs(argv);
108
+ const child = spawn(binary, childArgs, {
109
+ env: process.env,
110
+ stdio: ["pipe", "pipe", "pipe"],
111
+ windowsHide: true,
112
+ });
113
+ const pendingToolCallIds = new Set();
114
+ let adapterFailed = false;
115
+ let forwardedSignal;
116
+ let terminationTimer;
117
+
118
+ child.stderr.pipe(process.stderr);
119
+
120
+ const clientLines = readline.createInterface({ input: process.stdin, crlfDelay: Infinity });
121
+ const serverLines = readline.createInterface({ input: child.stdout, crlfDelay: Infinity });
122
+
123
+ function childIsRunning() {
124
+ return child.exitCode === null && child.signalCode === null;
125
+ }
126
+
127
+ function terminateChild(signal) {
128
+ if (!childIsRunning()) return;
129
+ child.kill(signal);
130
+ if (terminationTimer !== undefined) return;
131
+ terminationTimer = setTimeout(() => {
132
+ terminationTimer = undefined;
133
+ if (childIsRunning()) child.kill("SIGKILL");
134
+ }, CHILD_TERMINATION_GRACE_MS);
135
+ }
136
+
137
+ function failAdapter(error) {
138
+ if (adapterFailed) return;
139
+ adapterFailed = true;
140
+ process.exitCode = 1;
141
+ process.stderr.write(
142
+ `xuanling-dsh-result-adapter: ${error instanceof Error ? error.message : String(error)}\n`,
143
+ );
144
+ clientLines.close();
145
+ serverLines.close();
146
+ terminateChild("SIGTERM");
147
+ }
148
+
149
+ clientLines.on("line", (line) => {
150
+ const request = requestMethodAndId(line);
151
+ if (request?.method === "tools/call") pendingToolCallIds.add(request.id);
152
+ if (!child.stdin.write(`${line}\n`)) clientLines.pause();
153
+ });
154
+ clientLines.once("close", () => {
155
+ if (!child.stdin.destroyed) child.stdin.end();
156
+ });
157
+ child.stdin.on("drain", () => clientLines.resume());
158
+ child.stdin.on("error", (error) => {
159
+ if (child.exitCode === null && child.signalCode === null) failAdapter(error);
160
+ });
161
+
162
+ serverLines.on("line", (line) => {
163
+ if (adapterFailed) return;
164
+ try {
165
+ validateChildFrame(line);
166
+ const projected = projectDshResponseFrame(line, pendingToolCallIds);
167
+ if (!process.stdout.write(`${projected}\n`)) serverLines.pause();
168
+ } catch (error) {
169
+ failAdapter(error);
170
+ }
171
+ });
172
+ process.stdout.on("drain", () => serverLines.resume());
173
+
174
+ child.once("error", failAdapter);
175
+ child.once("close", (code, signal) => {
176
+ if (terminationTimer !== undefined) clearTimeout(terminationTimer);
177
+ clientLines.close();
178
+ serverLines.close();
179
+ if (adapterFailed) return;
180
+ if (forwardedSignal !== undefined) {
181
+ process.exitCode = 1;
182
+ return;
183
+ }
184
+ if (code !== 0 || signal !== null) {
185
+ process.exitCode = code ?? 1;
186
+ return;
187
+ }
188
+ if (pendingToolCallIds.size > 0) {
189
+ failAdapter(new Error(`child exited with ${pendingToolCallIds.size} unresolved tools/call request(s)`));
190
+ }
191
+ });
192
+
193
+ for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"]) {
194
+ process.once(signal, () => {
195
+ if (forwardedSignal !== undefined) return;
196
+ forwardedSignal = signal;
197
+ clientLines.close();
198
+ terminateChild(signal);
199
+ });
200
+ }
201
+ }
202
+
203
+ const isMain = process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href;
204
+ if (isMain) runAdapter();
package/package.json CHANGED
@@ -1,9 +1,25 @@
1
1
  {
2
2
  "name": "@xuanling-rs/xuanling-dsh-tools",
3
- "version": "0.2.2",
4
- "description": "XuanLing bootstrap placeholder; replaced by the verified 0.2.3 release.",
3
+ "version": "0.2.4",
4
+ "description": "DeepSeek Harness bundle: mount the XuanLing MCP server (xuanling-mcp binary) as native mcp__xuanling__* tools through the official @deepseek-ai/dsh-mcp-client bridge. Additive variant: built-in tools stay enabled.",
5
5
  "license": "MIT",
6
6
  "private": false,
7
+ "dependencies": {
8
+ "@deepseek-ai/dsh-mcp-client": "^0.1.0-rc.5",
9
+ "@xuanling-rs/xuanling-mcp": "0.2.4"
10
+ },
11
+ "files": [
12
+ "LICENSE",
13
+ "README.md",
14
+ "README-ZH.md",
15
+ "cordis.patch.yml",
16
+ "mcp-result-adapter.mjs"
17
+ ],
18
+ "dsh": {
19
+ "bundle": {
20
+ "patch": "./cordis.patch.yml"
21
+ }
22
+ },
7
23
  "engines": {
8
24
  "node": ">=22.14.0"
9
25
  },
@@ -18,6 +34,10 @@
18
34
  },
19
35
  "publishConfig": {
20
36
  "access": "public",
37
+ "provenance": true,
21
38
  "registry": "https://registry.npmjs.org"
39
+ },
40
+ "xuanlingRelease": {
41
+ "sourceCommit": "3fc3e6304a4839de292bf7470f0dbf3f6a7ebe59"
22
42
  }
23
43
  }