dsh-plugin-tool-management 0.15.0 → 0.16.5
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/CHANGELOG.md +49 -0
- package/docs/update.md +29 -0
- package/lib/client.js +242 -123
- package/lib/compat/probe.js +207 -19
- package/lib/mcp/manager.js +177 -14
- package/lib/memories/service.js +9 -0
- package/lib/ops/compat.js +19 -3
- package/lib/skills/core.js +183 -9
- package/lib/skills/service.js +5 -1
- package/package.json +24 -23
package/lib/skills/core.js
CHANGED
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
// 文件写入错误由路由返回给调用方。
|
|
14
14
|
import { homedir } from "node:os";
|
|
15
15
|
import { join, basename, dirname, resolve, isAbsolute, } from "node:path";
|
|
16
|
-
import { promises as fs } from "node:fs";
|
|
16
|
+
import { promises as fs, existsSync, mkdirSync, readdirSync, copyFileSync, rmSync, } from "node:fs";
|
|
17
|
+
import { createRequire } from "node:module";
|
|
17
18
|
import { createHash, randomUUID } from "node:crypto";
|
|
18
19
|
import { unzipSync } from "fflate";
|
|
19
20
|
import { discoverReadonlyEntries, validDiscoveryName, } from "./readonly-discovery.js";
|
|
@@ -57,13 +58,180 @@ export function resolveDshHome() {
|
|
|
57
58
|
export function resolveAgentsHome() {
|
|
58
59
|
return process.env.DSH_AGENTS_HOME || join(homedir(), ".agents");
|
|
59
60
|
}
|
|
61
|
+
// ── 官方内置技能来源(随宿主注入会话上下文的内置技能)──────────────────────
|
|
62
|
+
//
|
|
63
|
+
// 桌面版把 4 个官方技能注入每个会话(用户 2026-09-30 实测目录):office-docx /
|
|
64
|
+
// office-pptx / office-xlsx(dsh-skill-office,桌面宿主把 assetRoot 指到
|
|
65
|
+
// `<resources>/runtime/office-skills`)与 diagnose-windows-sandbox-acl
|
|
66
|
+
// (dsh-sandbox-windows-acl,source 'bundled',rank 600)。web 宿主一个都不注入
|
|
67
|
+
// (用户裁定:web 用不上这个功能)。它们或住在官方 asar 里、或由官方 provider
|
|
68
|
+
// 复制进临时目录自管,插件直接扫描/接管有两个坑:宿主子进程里对 asar 的 promises
|
|
69
|
+
// 读法与拦截器解析都出现过时灵时不灵(同机两次启动一组 4/1、一组全 0,实测);
|
|
70
|
+
// 沙盒技能的脚本要交给 PowerShell 子进程,asar 路径读不了。
|
|
71
|
+
//
|
|
72
|
+
// 因此这里把来源**物化**成插件自有的一份真实目录(`$DSH_HOME/tool-management/
|
|
73
|
+
// official-skills/`):每次宿主启动从官方源头整目录覆盖拷贝(与官方 provider 的
|
|
74
|
+
// 「私有资源副本」同一做法),扫描、启停、overlay、脚本执行全部落在真实文件上。
|
|
75
|
+
//
|
|
76
|
+
// 两个边界(都出过事):
|
|
77
|
+
// - 只有桌面版(有 process.resourcesPath)才允许进来。物化目录在 $DSH_HOME 下
|
|
78
|
+
// 与各宿主**共用**,而 web 宿主进程里的官方模块拦截器能把 @deepseek-ai 裸名
|
|
79
|
+
// 解析到宿主副本 —— web 若也进来重建,会把桌面物化好的整目录清成它解析到的
|
|
80
|
+
// 那几个(2026-09-30 实测:桌面物化好 4 个,web 一启动,两份 UI 同时只剩沙盒
|
|
81
|
+
// 1 个)。所以非桌面在碰目录**之前**就退出,一个技能都不列。
|
|
82
|
+
// - 预设参考技能(dsh-agent-preset/skills)不注入会话上下文,不列入(用户裁定)。
|
|
83
|
+
//
|
|
84
|
+
// 单个来源、随 DSH 技能之后排列,来源层语义同默认来源(不可移除、无来源开关)。
|
|
85
|
+
const OFFICIAL_SOURCE_KEY = "official";
|
|
86
|
+
/** electron 的 resources 目录;非桌面宿主(web 等普通 node 进程)没有这个属性。 */
|
|
87
|
+
function resourcesDir() {
|
|
88
|
+
const resources = process.resourcesPath;
|
|
89
|
+
return typeof resources === "string" && resources !== "" ? resources : null;
|
|
90
|
+
}
|
|
91
|
+
/** 桌面版官方 asar 里的包内目录(同步 fs 对 asar 的支持经过实测可靠)。 */
|
|
92
|
+
function desktopPackageDir(packageName, subpath) {
|
|
93
|
+
const resources = resourcesDir();
|
|
94
|
+
if (resources === null)
|
|
95
|
+
return null;
|
|
96
|
+
const dir = join(resources, "app.asar", "dsh", "node_modules", ...packageName.split("/"), ...subpath.split("/"));
|
|
97
|
+
return existsSync(dir) ? dir : null;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* 从插件自身的解析链找官方包内目录(web 等非桌面宿主的途径;宿主进程里的裸名
|
|
101
|
+
* 解析由官方模块拦截器路由到宿主副本)。包不可解析(旧宿主 / 单测环境)返回 null。
|
|
102
|
+
*/
|
|
103
|
+
function resolvedPackageDir(packageName, subpath) {
|
|
104
|
+
try {
|
|
105
|
+
const require = createRequire(import.meta.url);
|
|
106
|
+
let manifest;
|
|
107
|
+
try {
|
|
108
|
+
manifest = require.resolve(`${packageName}/package.json`);
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
// exports 未暴露 ./package.json 的包:从入口文件向上找同名 manifest。
|
|
112
|
+
manifest = "";
|
|
113
|
+
let dir = dirname(require.resolve(packageName));
|
|
114
|
+
for (let i = 0; i < 6; i += 1) {
|
|
115
|
+
const candidate = join(dir, "package.json");
|
|
116
|
+
if (existsSync(candidate)) {
|
|
117
|
+
manifest = candidate;
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
const parent = dirname(dir);
|
|
121
|
+
if (parent === dir)
|
|
122
|
+
break;
|
|
123
|
+
dir = parent;
|
|
124
|
+
}
|
|
125
|
+
if (manifest === "")
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
const dir = join(dirname(manifest), ...subpath.split("/"));
|
|
129
|
+
return existsSync(dir) ? dir : null;
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* office 技能源头。office 只在桌面版由宿主挂载(web 的补丁层没有它),所以只在
|
|
137
|
+
* 桌面环境里找:运行时真实目录优先(官方 assetRoot),其次 asar 包内 assets。
|
|
138
|
+
* 非桌面环境一律 null —— 不把没被注入的技能凭空列出来。
|
|
139
|
+
*/
|
|
140
|
+
function officialOfficeSource() {
|
|
141
|
+
const resources = resourcesDir();
|
|
142
|
+
if (resources === null)
|
|
143
|
+
return null;
|
|
144
|
+
const runtimeDir = join(resources, "runtime", "office-skills");
|
|
145
|
+
if (existsSync(join(runtimeDir, "office-docx")))
|
|
146
|
+
return runtimeDir;
|
|
147
|
+
const asarDir = desktopPackageDir("@deepseek-ai/dsh-skill-office", "assets");
|
|
148
|
+
if (asarDir !== null && existsSync(join(asarDir, "office-docx")))
|
|
149
|
+
return asarDir;
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
/** 沙盒诊断技能源头(桌面版 asar 直连优先,其次本机解析链;仅 Windows 注入)。 */
|
|
153
|
+
function officialSandboxSource() {
|
|
154
|
+
if (process.platform !== "win32")
|
|
155
|
+
return null;
|
|
156
|
+
return (desktopPackageDir("@deepseek-ai/dsh-sandbox-windows-acl", "assets/diagnose-windows-sandbox-acl") ??
|
|
157
|
+
resolvedPackageDir("@deepseek-ai/dsh-sandbox-windows-acl", "assets/diagnose-windows-sandbox-acl"));
|
|
158
|
+
}
|
|
159
|
+
function copyDirectorySync(from, to) {
|
|
160
|
+
mkdirSync(to, { recursive: true });
|
|
161
|
+
for (const entry of readdirSync(from, { withFileTypes: true })) {
|
|
162
|
+
const source = join(from, entry.name);
|
|
163
|
+
const target = join(to, entry.name);
|
|
164
|
+
if (entry.isDirectory())
|
|
165
|
+
copyDirectorySync(source, target);
|
|
166
|
+
else if (entry.isFile())
|
|
167
|
+
copyFileSync(source, target);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* 物化官方内置技能到插件自有目录,返回该目录;无可用源头或拷贝失败返回 null。
|
|
172
|
+
* 每次宿主启动整目录重建(官方包只随宿主更新变化,宿主更新必然重启宿主)。
|
|
173
|
+
*/
|
|
174
|
+
let officialRootCache;
|
|
175
|
+
function materializeOfficialSkills() {
|
|
176
|
+
// 非桌面宿主(web 等普通 node 进程)一个官方技能都不注入,也不许碰共享的物化
|
|
177
|
+
// 目录 —— 否则 web 启动会把它解析到的那几个盖进目录,桌面版物化好的就没了
|
|
178
|
+
// (见上方块注释)。必须在任何 fs 访问之前退出。
|
|
179
|
+
if (resourcesDir() === null)
|
|
180
|
+
return null;
|
|
181
|
+
const office = officialOfficeSource();
|
|
182
|
+
const sandbox = officialSandboxSource();
|
|
183
|
+
if (office === null && sandbox === null)
|
|
184
|
+
return null;
|
|
185
|
+
const root = join(resolveDshHome(), "tool-management", "official-skills");
|
|
186
|
+
try {
|
|
187
|
+
rmSync(root, { recursive: true, force: true });
|
|
188
|
+
// office:整个 assets 根拷进来(office-*/ 与 scripts/ 的相对结构必须保留 ——
|
|
189
|
+
// SKILL.md 用 `<skill-directory>/../scripts/check_office.py` 引用检查脚本)。
|
|
190
|
+
if (office !== null)
|
|
191
|
+
copyDirectorySync(office, root);
|
|
192
|
+
if (sandbox !== null) {
|
|
193
|
+
copyDirectorySync(sandbox, join(root, basename(sandbox)));
|
|
194
|
+
}
|
|
195
|
+
return root;
|
|
196
|
+
}
|
|
197
|
+
catch (error) {
|
|
198
|
+
console.error("[dsh-plugin-tool-management] official skills materialization failed:", String((error && error.message) || error));
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
function officialSkillRoot() {
|
|
203
|
+
if (officialRootCache !== undefined)
|
|
204
|
+
return officialRootCache;
|
|
205
|
+
const dir = materializeOfficialSkills();
|
|
206
|
+
officialRootCache =
|
|
207
|
+
dir === null
|
|
208
|
+
? null
|
|
209
|
+
: {
|
|
210
|
+
key: OFFICIAL_SOURCE_KEY,
|
|
211
|
+
path: dir,
|
|
212
|
+
label: "官方内置",
|
|
213
|
+
localeKey: "official",
|
|
214
|
+
mutable: false,
|
|
215
|
+
deletable: false,
|
|
216
|
+
// 来源层无开关(同默认来源),单个技能可启停;overlay 以 599 压过官方
|
|
217
|
+
// provider 的 600,脚本从物化副本的真实路径执行。
|
|
218
|
+
toggleable: true,
|
|
219
|
+
native: false,
|
|
220
|
+
scope: "user",
|
|
221
|
+
rank: 599,
|
|
222
|
+
};
|
|
223
|
+
return officialRootCache;
|
|
224
|
+
}
|
|
60
225
|
/**
|
|
61
226
|
* DSH 与外部 Agent 的用户级技能目录(4 个来源;外部来源由 manager provider 接入)。
|
|
62
227
|
*
|
|
63
228
|
* rank 与官方 filesystem provider 的用户级优先级衔接:DSH=400、Agents=500。
|
|
64
229
|
* manager provider 以 450 接管公共 Agents(仍低于 DSH),Codex/Claude 依次排在其后。
|
|
230
|
+
* DSH 之后紧随官方内置来源(随宿主注入会话的内置技能,见 materializeOfficialSkills;
|
|
231
|
+
* 无可用源头时自然缺席)。
|
|
65
232
|
*/
|
|
66
233
|
export function userRoots() {
|
|
234
|
+
const official = officialSkillRoot();
|
|
67
235
|
return [
|
|
68
236
|
{
|
|
69
237
|
key: "dsh",
|
|
@@ -78,6 +246,8 @@ export function userRoots() {
|
|
|
78
246
|
native: true,
|
|
79
247
|
rank: 400,
|
|
80
248
|
},
|
|
249
|
+
// 官方内置紧随 DSH 技能之后(用户裁定):同为一眼要看的来源,排在导入技能之前。
|
|
250
|
+
...(official === null ? [] : [official]),
|
|
81
251
|
{
|
|
82
252
|
// v0.4:插件**新建/导入**的技能落到 hub 内(用户要求:插件产生的文件收在
|
|
83
253
|
// $DSH_HOME/tool-management/)。rank 高于 DSH 技能,同名时 hub 版本遮蔽官方目录里的;
|
|
@@ -139,8 +309,8 @@ export function userRoots() {
|
|
|
139
309
|
*
|
|
140
310
|
* 一句话记法:**`mutable` 与 `deletable` 同向,`removable` 与 `mutable` 反向。**
|
|
141
311
|
*/
|
|
142
|
-
const DEFAULT_SOURCE_KEYS = Object.freeze(["dsh", "hub"]);
|
|
143
|
-
/** 是否为默认来源。接受 root 对象或 key 字符串;`dsh` / `hub` 的所有特判都走这里。 */
|
|
312
|
+
const DEFAULT_SOURCE_KEYS = Object.freeze(["dsh", "hub", OFFICIAL_SOURCE_KEY]);
|
|
313
|
+
/** 是否为默认来源。接受 root 对象或 key 字符串;`dsh` / `hub` / `official` 的所有特判都走这里。 */
|
|
144
314
|
export function isDefaultSkillSource(rootOrKey) {
|
|
145
315
|
const key = rootOrKey && typeof rootOrKey === "object"
|
|
146
316
|
? rootOrKey.key
|
|
@@ -582,7 +752,9 @@ function reservedSourceError(root) {
|
|
|
582
752
|
const key = root && root.key ? root.key : "";
|
|
583
753
|
const origin = key === "dsh"
|
|
584
754
|
? "DSH 技能目录是默认来源,必须读取"
|
|
585
|
-
: "
|
|
755
|
+
: key === "hub"
|
|
756
|
+
? "导入技能目录是默认来源、插件新建/导入的落点,必须读取"
|
|
757
|
+
: "官方内置技能目录随宿主发布,不能移除(移除只会让插件失去管理权)";
|
|
586
758
|
return {
|
|
587
759
|
ok: false,
|
|
588
760
|
code: "error.source.reserved",
|
|
@@ -2894,11 +3066,13 @@ export async function listProviderCandidates(options = {}) {
|
|
|
2894
3066
|
provider: "dsh-plugin-tool-management-external",
|
|
2895
3067
|
source: project
|
|
2896
3068
|
? root.kind
|
|
2897
|
-
: root.key === "
|
|
2898
|
-
? "
|
|
2899
|
-
:
|
|
2900
|
-
? "
|
|
2901
|
-
:
|
|
3069
|
+
: root.key === "official"
|
|
3070
|
+
? "bundled"
|
|
3071
|
+
: root.key === "dsh"
|
|
3072
|
+
? "user-dsh"
|
|
3073
|
+
: CUSTOM_ROOT_KEY_RE.test(root.key)
|
|
3074
|
+
? "custom"
|
|
3075
|
+
: `agent-${root.key}`,
|
|
2902
3076
|
rank: project
|
|
2903
3077
|
? root.rank - 1
|
|
2904
3078
|
: root.key === "dsh"
|
package/lib/skills/service.js
CHANGED
|
@@ -410,7 +410,11 @@ export function createSkillsService(ctx) {
|
|
|
410
410
|
console.error('[dsh-plugin-tool-management] agent-scoped provider setup failed:', message(e));
|
|
411
411
|
}
|
|
412
412
|
try {
|
|
413
|
-
const watchPaths = roots
|
|
413
|
+
const watchPaths = roots
|
|
414
|
+
// 官方内置来源是宿主启动时物化的只读副本,不会中途变化,不起 watcher。
|
|
415
|
+
.filter((root) => root.key !== 'official')
|
|
416
|
+
.map((root) => String((root && root.path) || ''))
|
|
417
|
+
.filter(Boolean);
|
|
414
418
|
disposers.push(watchDirectories(watchPaths, invalidateSkills));
|
|
415
419
|
// 自定义目录在状态文件里,注册时读取一次并一并监听(后添加的目录由
|
|
416
420
|
// provider 首次 list 天然覆盖,等插件重载后才有 watcher)。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-plugin-tool-management",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.5",
|
|
4
4
|
"description": "DSH plugin: one settings panel for MCP servers, skills, scenes, memories, subagent personas and AGENTS.md presets — a scene switches the whole environment, and the model can drive it from chat too.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -66,17 +66,17 @@
|
|
|
66
66
|
},
|
|
67
67
|
"peerDependencies": {
|
|
68
68
|
"@deepseek-ai/cordis": "^4.0.2",
|
|
69
|
-
"@deepseek-ai/dsh-agent-preset-registry": "
|
|
70
|
-
"@deepseek-ai/dsh-llm": "
|
|
71
|
-
"@deepseek-ai/dsh-session": "
|
|
72
|
-
"@deepseek-ai/dsh-session-projection-cache": "
|
|
73
|
-
"@deepseek-ai/dsh-settings": "
|
|
74
|
-
"@deepseek-ai/dsh-spill-local": "
|
|
75
|
-
"@deepseek-ai/dsh-storage-domain": "
|
|
76
|
-
"@deepseek-ai/dsh-subagent-fork-in-process": "
|
|
77
|
-
"@deepseek-ai/dsh-subagent-spawn-in-process": "
|
|
78
|
-
"@deepseek-ai/dsh-tools": "
|
|
79
|
-
"@deepseek-ai/dsh-workspace": "
|
|
69
|
+
"@deepseek-ai/dsh-agent-preset-registry": ">=0.1.7-rc.2 || >=0.2.0-rc.1",
|
|
70
|
+
"@deepseek-ai/dsh-llm": ">=0.1.7-rc.2 || >=0.2.0-rc.1",
|
|
71
|
+
"@deepseek-ai/dsh-session": ">=0.1.7-rc.2 || >=0.2.0-rc.1",
|
|
72
|
+
"@deepseek-ai/dsh-session-projection-cache": ">=0.1.7-rc.2 || >=0.2.0-rc.1",
|
|
73
|
+
"@deepseek-ai/dsh-settings": ">=0.1.7-rc.2 || >=0.2.0-rc.1",
|
|
74
|
+
"@deepseek-ai/dsh-spill-local": ">=0.1.7-rc.2 || >=0.2.0-rc.1",
|
|
75
|
+
"@deepseek-ai/dsh-storage-domain": ">=0.1.7-rc.2 || >=0.2.0-rc.1",
|
|
76
|
+
"@deepseek-ai/dsh-subagent-fork-in-process": ">=0.1.7-rc.2 || >=0.2.0-rc.1",
|
|
77
|
+
"@deepseek-ai/dsh-subagent-spawn-in-process": ">=0.1.7-rc.2 || >=0.2.0-rc.1",
|
|
78
|
+
"@deepseek-ai/dsh-tools": ">=0.1.7-rc.2 || >=0.2.0-rc.1",
|
|
79
|
+
"@deepseek-ai/dsh-workspace": ">=0.1.7-rc.2 || >=0.2.0-rc.1",
|
|
80
80
|
"react": "^18.2.0"
|
|
81
81
|
},
|
|
82
82
|
"peerDependenciesMeta": {
|
|
@@ -89,19 +89,20 @@
|
|
|
89
89
|
},
|
|
90
90
|
"devDependencies": {
|
|
91
91
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
92
|
-
"@deepseek-ai/dsh-agent-preset-registry": "^0.
|
|
93
|
-
"@deepseek-ai/dsh-llm": "^0.
|
|
94
|
-
"@deepseek-ai/dsh-session": "^0.
|
|
95
|
-
"@deepseek-ai/dsh-session-projection-cache": "^0.
|
|
96
|
-
"@deepseek-ai/dsh-settings": "^0.
|
|
97
|
-
"@deepseek-ai/dsh-spill-local": "^0.
|
|
98
|
-
"@deepseek-ai/dsh-storage-domain": "^0.
|
|
99
|
-
"@deepseek-ai/dsh-subagent-fork-in-process": "^0.
|
|
100
|
-
"@deepseek-ai/dsh-subagent-spawn-in-process": "^0.
|
|
101
|
-
"@deepseek-ai/dsh-tools": "^0.
|
|
102
|
-
"@deepseek-ai/dsh-workspace": "^0.
|
|
92
|
+
"@deepseek-ai/dsh-agent-preset-registry": "^0.2.0-rc.2",
|
|
93
|
+
"@deepseek-ai/dsh-llm": "^0.2.0-rc.2",
|
|
94
|
+
"@deepseek-ai/dsh-session": "^0.2.0-rc.2",
|
|
95
|
+
"@deepseek-ai/dsh-session-projection-cache": "^0.2.0-rc.2",
|
|
96
|
+
"@deepseek-ai/dsh-settings": "^0.2.0-rc.2",
|
|
97
|
+
"@deepseek-ai/dsh-spill-local": "^0.2.0-rc.2",
|
|
98
|
+
"@deepseek-ai/dsh-storage-domain": "^0.2.0-rc.2",
|
|
99
|
+
"@deepseek-ai/dsh-subagent-fork-in-process": "^0.2.0-rc.2",
|
|
100
|
+
"@deepseek-ai/dsh-subagent-spawn-in-process": "^0.2.0-rc.2",
|
|
101
|
+
"@deepseek-ai/dsh-tools": "^0.2.0-rc.2",
|
|
102
|
+
"@deepseek-ai/dsh-workspace": "^0.2.0-rc.2",
|
|
103
103
|
"@types/js-yaml": "^4.0.9",
|
|
104
104
|
"@types/node": "^24.0.0",
|
|
105
|
+
"react": "^18.2.0",
|
|
105
106
|
"typescript": "^6.0.0"
|
|
106
107
|
},
|
|
107
108
|
"license": "MIT",
|