@michengai/dsh-skills-manager 0.1.3 → 0.1.6
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/lib/client.js +257 -37
- package/lib/core.js +53 -21
- package/lib/index.js +2 -2
- package/package.json +4 -2
package/lib/client.js
CHANGED
|
@@ -9,6 +9,166 @@ window.__ModuleLoader__.load({
|
|
|
9
9
|
var pickDirectory = null;
|
|
10
10
|
var MUTATION_HEADERS = { "content-type": "application/json", "x-dsh-skills-manager": "1" };
|
|
11
11
|
|
|
12
|
+
// 词典命名空间:注册进 DSH locale 服务,渲染机制按该命名空间把 t 注入组件 props。
|
|
13
|
+
var NS = "skills-manager";
|
|
14
|
+
// 双语词典:zh 与现有硬编码文案逐字一致,保证中文用户无感知;en 为通顺英文。
|
|
15
|
+
var DICT = {
|
|
16
|
+
zh: {
|
|
17
|
+
"title": "技能",
|
|
18
|
+
"desc": "DSH 技能可上传、启停和删除;公共 Agent 技能仅供查看。",
|
|
19
|
+
"btn.refresh": "刷新",
|
|
20
|
+
"btn.upload": "上传插件",
|
|
21
|
+
"btn.disable": "停用",
|
|
22
|
+
"btn.enable": "启用",
|
|
23
|
+
"btn.repair.enable": "修复并启用",
|
|
24
|
+
"btn.delete": "删除",
|
|
25
|
+
"btn.cancel": "取消",
|
|
26
|
+
"btn.overwrite.upload": "覆盖上传",
|
|
27
|
+
"btn.delete.confirm": "确认删除",
|
|
28
|
+
"btn.file.pick": "选择本地文件",
|
|
29
|
+
"btn.dir.pick": "选择插件文件夹",
|
|
30
|
+
"btn.uploading": "上传中…",
|
|
31
|
+
"btn.upload.dsh": "上传到 DSH",
|
|
32
|
+
"status.enabled": "已启用",
|
|
33
|
+
"status.disabled": "已停用",
|
|
34
|
+
"status.invalid": "配置异常",
|
|
35
|
+
"status.bundle": "目录插件",
|
|
36
|
+
"status.single": "单文件",
|
|
37
|
+
"status.selected": "已选择",
|
|
38
|
+
"summary.total.one": "个插件",
|
|
39
|
+
"summary.total.other": "个插件",
|
|
40
|
+
"summary.enabled.one": "个已启用",
|
|
41
|
+
"summary.enabled.other": "个已启用",
|
|
42
|
+
"summary.group": "{count} 个",
|
|
43
|
+
"note.missing": "未提供简介",
|
|
44
|
+
"empty.dir.uncreated": "目录尚未创建,上传第一个插件后会自动创建。",
|
|
45
|
+
"empty.dir.missing": "公共目录不存在。",
|
|
46
|
+
"empty.skills.none": "暂未安装插件。",
|
|
47
|
+
"loading": "加载中…",
|
|
48
|
+
"error.action": "操作失败:{error}",
|
|
49
|
+
// 列表连接符由词典侧决定:zh 用顿号/分号,en 用逗号/分号。
|
|
50
|
+
"sep.names": "、",
|
|
51
|
+
"sep.errors": ";",
|
|
52
|
+
"result.failed": "上传失败:{error}",
|
|
53
|
+
"result.partial": "部分上传成功:{names};失败:{errors}",
|
|
54
|
+
"result.failed.only": "上传失败:{errors}",
|
|
55
|
+
"result.done": "上传完成:{names}",
|
|
56
|
+
"result.skipped": "未导入:同名插件已跳过:{names}",
|
|
57
|
+
"result.none": "未导入任何插件。",
|
|
58
|
+
"upload.title": "上传插件",
|
|
59
|
+
"upload.close": "关闭上传弹窗",
|
|
60
|
+
"upload.drop.title": "拖放 SKILL.md 文件到这里",
|
|
61
|
+
"upload.drop.copy": "或点击打开系统原生文件选择窗口",
|
|
62
|
+
"upload.hint": "请选择插件目录中的 SKILL.md;目录内其他文件会一并上传。",
|
|
63
|
+
"upload.picking.dir": "正在打开系统原生目录选择窗口…",
|
|
64
|
+
"select.file.invalid": "请选择插件目录中的 SKILL.md 文件。",
|
|
65
|
+
"select.path.missing": "已选择 SKILL.md,但当前运行环境无法读取文件路径。请点击“选择插件文件夹”继续上传。",
|
|
66
|
+
"select.failed": "选择失败:{error}",
|
|
67
|
+
"dir.selected": "已选择插件目录",
|
|
68
|
+
"dir.service.missing": "当前运行环境无法读取本地文件路径,也未提供原生目录选择服务。",
|
|
69
|
+
"dir.pick.failed": "选择插件目录失败:{error}",
|
|
70
|
+
"confirm.overwrite.title": "发现同名插件",
|
|
71
|
+
"confirm.overwrite.desc": "将覆盖现有插件:{names}",
|
|
72
|
+
"confirm.delete.title": "删除插件?",
|
|
73
|
+
"confirm.delete.desc": "“{name}”将从 DSH 技能目录中永久删除,无法恢复。",
|
|
74
|
+
// Step 2 业务错误码:zh 值保持与 core 原有中文文案逐字一致({action} 由 action.* 映射后替换)。
|
|
75
|
+
"error.root.readonly": "公共 Agent 技能目录不允许{action}",
|
|
76
|
+
"error.skill.notFound": "技能不存在: {name}",
|
|
77
|
+
"error.skill.noFrontmatter": "技能缺少完整 frontmatter,无法{action}: {name}",
|
|
78
|
+
"error.source.notFound": "路径不存在: {path}",
|
|
79
|
+
"error.source.symlink": "不支持包含符号链接的 skill 来源: {path}",
|
|
80
|
+
"error.source.unrecognized": "无法识别的 skill 来源: {path}",
|
|
81
|
+
"error.source.tooDeep": "skill 来源目录层级超过 {depth} 层: {path}",
|
|
82
|
+
"error.import.overlap": "导入来源不能与 DSH 技能目录相同、包含或位于其中",
|
|
83
|
+
"error.import.emptySource": "目录下未找到任何 skill 条目(需含 SKILL.md 的子目录或 .md 文件): {path}",
|
|
84
|
+
"error.import.invalidName": "无法生成合法 kebab-case 名称(原始名: {name})",
|
|
85
|
+
"error.import.duplicateName": "批量来源中存在多个同名插件: {name}",
|
|
86
|
+
"error.import.failed": "导入失败",
|
|
87
|
+
"action.enable": "启用",
|
|
88
|
+
"action.disable": "停用",
|
|
89
|
+
"action.delete": "删除",
|
|
90
|
+
"action.toggle": "启用或停用",
|
|
91
|
+
"root.dsh": "DSH 技能",
|
|
92
|
+
"root.agents": "公共 Agent 技能"
|
|
93
|
+
},
|
|
94
|
+
en: {
|
|
95
|
+
"title": "Skills",
|
|
96
|
+
"desc": "DSH skills can be uploaded, enabled or disabled, and deleted; shared Agent skills are view-only.",
|
|
97
|
+
"btn.refresh": "Refresh",
|
|
98
|
+
"btn.upload": "Upload",
|
|
99
|
+
"btn.disable": "Disable",
|
|
100
|
+
"btn.enable": "Enable",
|
|
101
|
+
"btn.repair.enable": "Repair & enable",
|
|
102
|
+
"btn.delete": "Delete",
|
|
103
|
+
"btn.cancel": "Cancel",
|
|
104
|
+
"btn.overwrite.upload": "Overwrite & upload",
|
|
105
|
+
"btn.delete.confirm": "Delete",
|
|
106
|
+
"btn.file.pick": "Choose local file",
|
|
107
|
+
"btn.dir.pick": "Choose plugin folder",
|
|
108
|
+
"btn.uploading": "Uploading…",
|
|
109
|
+
"btn.upload.dsh": "Upload to DSH",
|
|
110
|
+
"status.enabled": "Enabled",
|
|
111
|
+
"status.disabled": "Disabled",
|
|
112
|
+
"status.invalid": "Invalid config",
|
|
113
|
+
"status.bundle": "Bundle",
|
|
114
|
+
"status.single": "Single file",
|
|
115
|
+
"status.selected": "Selected",
|
|
116
|
+
"summary.total.one": "skill",
|
|
117
|
+
"summary.total.other": "skills",
|
|
118
|
+
"summary.enabled.one": "enabled",
|
|
119
|
+
"summary.enabled.other": "enabled",
|
|
120
|
+
"summary.group": "{count} items",
|
|
121
|
+
"note.missing": "No description provided",
|
|
122
|
+
"empty.dir.uncreated": "The directory has not been created yet; it will be created automatically after the first upload.",
|
|
123
|
+
"empty.dir.missing": "The public directory does not exist.",
|
|
124
|
+
"empty.skills.none": "No plugins installed yet.",
|
|
125
|
+
"loading": "Loading…",
|
|
126
|
+
"error.action": "Action failed: {error}",
|
|
127
|
+
"sep.names": ", ",
|
|
128
|
+
"sep.errors": "; ",
|
|
129
|
+
"result.failed": "Upload failed: {error}",
|
|
130
|
+
"result.partial": "Partially uploaded: {names}; failed: {errors}",
|
|
131
|
+
"result.failed.only": "Upload failed: {errors}",
|
|
132
|
+
"result.done": "Upload complete: {names}",
|
|
133
|
+
"result.skipped": "Not imported: same-name plugins were skipped: {names}",
|
|
134
|
+
"result.none": "Nothing was imported.",
|
|
135
|
+
"upload.title": "Upload plugin",
|
|
136
|
+
"upload.close": "Close upload dialog",
|
|
137
|
+
"upload.drop.title": "Drop SKILL.md file here",
|
|
138
|
+
"upload.drop.copy": "or click to open the native file picker",
|
|
139
|
+
"upload.hint": "Choose a SKILL.md inside the plugin directory; other files in that directory are uploaded too.",
|
|
140
|
+
"upload.picking.dir": "Opening native directory picker…",
|
|
141
|
+
"select.file.invalid": "Please choose the SKILL.md file inside the plugin directory.",
|
|
142
|
+
"select.path.missing": "SKILL.md was selected, but this environment cannot read the file path. Click “Choose plugin folder” to continue uploading.",
|
|
143
|
+
"select.failed": "Selection failed: {error}",
|
|
144
|
+
"dir.selected": "Plugin folder selected",
|
|
145
|
+
"dir.service.missing": "This environment cannot read local file paths and provides no native directory picker.",
|
|
146
|
+
"dir.pick.failed": "Failed to choose plugin folder: {error}",
|
|
147
|
+
"confirm.overwrite.title": "Same-name plugin found",
|
|
148
|
+
"confirm.overwrite.desc": "Will overwrite existing plugin(s): {names}",
|
|
149
|
+
"confirm.delete.title": "Delete plugin?",
|
|
150
|
+
"confirm.delete.desc": "“{name}” will be permanently deleted from the DSH skills directory and cannot be recovered.",
|
|
151
|
+
"error.root.readonly": "The shared Agent skills directory does not allow {action}",
|
|
152
|
+
"error.skill.notFound": "Skill not found: {name}",
|
|
153
|
+
"error.skill.noFrontmatter": "Skill lacks complete frontmatter, cannot {action}: {name}",
|
|
154
|
+
"error.source.notFound": "Path does not exist: {path}",
|
|
155
|
+
"error.source.symlink": "Skill source containing symbolic links is not supported: {path}",
|
|
156
|
+
"error.source.unrecognized": "Unrecognized skill source: {path}",
|
|
157
|
+
"error.source.tooDeep": "Skill source directory depth exceeds the {depth}-level limit: {path}",
|
|
158
|
+
"error.import.overlap": "Import source cannot be the same as, contain, or be inside the DSH skills directory",
|
|
159
|
+
"error.import.emptySource": "No skill entries found in the directory (needs SKILL.md subdirectories or .md files): {path}",
|
|
160
|
+
"error.import.invalidName": "Cannot generate a valid kebab-case name (original name: {name})",
|
|
161
|
+
"error.import.duplicateName": "Batch source contains multiple plugins with the same name: {name}",
|
|
162
|
+
"error.import.failed": "Import failed",
|
|
163
|
+
"action.enable": "enable",
|
|
164
|
+
"action.disable": "disable",
|
|
165
|
+
"action.delete": "deleting",
|
|
166
|
+
"action.toggle": "enabling or disabling",
|
|
167
|
+
"root.dsh": "DSH skills",
|
|
168
|
+
"root.agents": "Shared Agent skills"
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
|
|
12
172
|
var CSS = `
|
|
13
173
|
.dssm-section{display:flex;min-width:0;flex-direction:column;gap:20px;color:var(--dsw-alias-label-primary)}
|
|
14
174
|
.dssm-toolbar{display:flex;align-items:flex-start;gap:16px;padding-bottom:16px;border-bottom:1px solid var(--dsw-alias-border-l2)}
|
|
@@ -27,13 +187,51 @@ window.__ModuleLoader__.load({
|
|
|
27
187
|
function callApi(path, options) {
|
|
28
188
|
return fetch("/api/dsh-skills-manager" + path, options).then(function (response) {
|
|
29
189
|
return response.json().then(function (payload) {
|
|
30
|
-
if (!payload.ok)
|
|
190
|
+
if (!payload.ok) {
|
|
191
|
+
var error = new Error(payload.error || ("HTTP " + response.status));
|
|
192
|
+
// 透传业务错误码与失败明细,供 translateError 按词典翻译。
|
|
193
|
+
if (payload.code) error.code = payload.code;
|
|
194
|
+
if (payload.params) error.params = payload.params;
|
|
195
|
+
if (payload.failed) error.failed = payload.failed;
|
|
196
|
+
throw error;
|
|
197
|
+
}
|
|
31
198
|
return payload.data;
|
|
32
199
|
});
|
|
33
200
|
});
|
|
34
201
|
}
|
|
35
202
|
|
|
36
|
-
|
|
203
|
+
// 翻译业务错误:payload.code 存在则查词典(params 替换占位符),词典 miss 回退原文。
|
|
204
|
+
// 兼容 { code, params, error }、Error 对象、纯字符串三种形态。
|
|
205
|
+
function translateError(t, payload) {
|
|
206
|
+
if (payload && typeof payload === "object") {
|
|
207
|
+
var code = payload.code;
|
|
208
|
+
if (typeof code === "string" && code !== "") {
|
|
209
|
+
var params = payload.params || {};
|
|
210
|
+
// {action} 占位符按当前语言映射(enable/disable/delete → 启用/停用/删除)。
|
|
211
|
+
if (params.action !== undefined) {
|
|
212
|
+
var actionKey = "action." + params.action;
|
|
213
|
+
var actionText = t(actionKey);
|
|
214
|
+
if (actionText !== undefined && actionText !== actionKey) params = Object.assign({}, params, { action: actionText });
|
|
215
|
+
}
|
|
216
|
+
var translated = t(code, params);
|
|
217
|
+
// locale miss 时 t 返回 key 本身(或 undefined/null),此时回退原文。
|
|
218
|
+
if (typeof translated === "string" && translated !== code) return translated;
|
|
219
|
+
}
|
|
220
|
+
if (payload.error !== undefined) return translateError(t, payload.error);
|
|
221
|
+
if (payload.message !== undefined) return String(payload.message);
|
|
222
|
+
}
|
|
223
|
+
return String(payload == null ? "" : payload);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// 词典命中返回翻译,miss(t 返回 key 本身或 undefined/null)回退 fallback。
|
|
227
|
+
function translateOrFallback(t, key, fallback) {
|
|
228
|
+
var value = t(key);
|
|
229
|
+
return typeof value === "string" && value !== key ? value : fallback;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// 渲染机制按 register 的 locale 字段把命名空间绑定的 t 注入组件 props。
|
|
233
|
+
function SkillManagerSection(props) {
|
|
234
|
+
var t = props.t;
|
|
37
235
|
var snapshotState = react.useState({ loading: true, error: null, data: null });
|
|
38
236
|
var snapshot = snapshotState[0];
|
|
39
237
|
var setSnapshot = snapshotState[1];
|
|
@@ -62,7 +260,7 @@ window.__ModuleLoader__.load({
|
|
|
62
260
|
callApi("/state").then(function (data) {
|
|
63
261
|
setSnapshot({ loading: false, error: null, data: data });
|
|
64
262
|
}).catch(function (error) {
|
|
65
|
-
setSnapshot({ loading: false, error:
|
|
263
|
+
setSnapshot({ loading: false, error: error, data: snapshot.data });
|
|
66
264
|
});
|
|
67
265
|
}
|
|
68
266
|
|
|
@@ -91,19 +289,19 @@ window.__ModuleLoader__.load({
|
|
|
91
289
|
refresh();
|
|
92
290
|
}).catch(function (error) {
|
|
93
291
|
setBusy(null);
|
|
94
|
-
setSnapshot({ loading: false, error:
|
|
292
|
+
setSnapshot({ loading: false, error: error, data: snapshot.data });
|
|
95
293
|
});
|
|
96
294
|
}
|
|
97
295
|
|
|
98
296
|
function selectFile(file) {
|
|
99
297
|
if (!file) return;
|
|
100
298
|
if (file.name.toLowerCase() !== "skill.md") {
|
|
101
|
-
setImportResult({ error: "
|
|
299
|
+
setImportResult({ error: t("select.file.invalid") });
|
|
102
300
|
return;
|
|
103
301
|
}
|
|
104
302
|
if (!file.path) {
|
|
105
303
|
setSelected(null);
|
|
106
|
-
setImportResult({ error: "
|
|
304
|
+
setImportResult({ error: t("select.path.missing") });
|
|
107
305
|
return;
|
|
108
306
|
}
|
|
109
307
|
setSelected({ name: file.name, source: file.path });
|
|
@@ -112,7 +310,7 @@ window.__ModuleLoader__.load({
|
|
|
112
310
|
|
|
113
311
|
function selectDirectory() {
|
|
114
312
|
if (!pickDirectory) {
|
|
115
|
-
setImportResult({ error: "
|
|
313
|
+
setImportResult({ error: t("dir.service.missing") });
|
|
116
314
|
return;
|
|
117
315
|
}
|
|
118
316
|
setImportResult(null);
|
|
@@ -120,12 +318,12 @@ window.__ModuleLoader__.load({
|
|
|
120
318
|
pickDirectory().then(function (path) {
|
|
121
319
|
setBusy(null);
|
|
122
320
|
if (path) {
|
|
123
|
-
setSelected({ name: "
|
|
321
|
+
setSelected({ name: t("dir.selected"), source: path });
|
|
124
322
|
setImportResult(null);
|
|
125
323
|
}
|
|
126
324
|
}).catch(function (error) {
|
|
127
325
|
setBusy(null);
|
|
128
|
-
setImportResult({ error: "
|
|
326
|
+
setImportResult({ error: t("dir.pick.failed", { error: String(error && error.message || error) }) });
|
|
129
327
|
});
|
|
130
328
|
}
|
|
131
329
|
|
|
@@ -144,7 +342,7 @@ window.__ModuleLoader__.load({
|
|
|
144
342
|
refresh();
|
|
145
343
|
}).catch(function (error) {
|
|
146
344
|
setBusy(null);
|
|
147
|
-
setImportResult({ error:
|
|
345
|
+
setImportResult({ error: error });
|
|
148
346
|
});
|
|
149
347
|
}
|
|
150
348
|
|
|
@@ -160,7 +358,7 @@ window.__ModuleLoader__.load({
|
|
|
160
358
|
executeImport(selected.source, "skip");
|
|
161
359
|
}).catch(function (error) {
|
|
162
360
|
setBusy(null);
|
|
163
|
-
setImportResult({ error:
|
|
361
|
+
setImportResult({ error: error });
|
|
164
362
|
});
|
|
165
363
|
}
|
|
166
364
|
|
|
@@ -172,70 +370,92 @@ window.__ModuleLoader__.load({
|
|
|
172
370
|
h("div", { className: "dssm-row-main" },
|
|
173
371
|
h("div", { className: "dssm-row-id" },
|
|
174
372
|
h("span", { className: "dssm-row-name" }, skill.name),
|
|
175
|
-
h("span", { className: "dssm-tag" }, skill.kind === "bundle" ? "
|
|
176
|
-
h("span", { className: "dssm-tag " + (policyValid && enabled ? "dssm-tag-on" : "dssm-tag-off") }, policyValid ? (enabled ? "
|
|
177
|
-
h("div", { className: "dssm-note", title: skill.description || "" }, skill.description || "
|
|
178
|
-
root.mutable ? h("button", { className: "dssm-btn dssm-btn-secondary", disabled: !!busy, onClick: function () { action(actionEnabled ? "/disable" : "/enable", { name: skill.name, root: root.key }); } }, actionEnabled ? "
|
|
179
|
-
root.mutable ? h("button", { className: "dssm-btn dssm-btn-danger", disabled: !!busy, onClick: function () { setConfirmDelete(skill); } }, "
|
|
373
|
+
h("span", { className: "dssm-tag" }, skill.kind === "bundle" ? t("status.bundle") : t("status.single")),
|
|
374
|
+
h("span", { className: "dssm-tag " + (policyValid && enabled ? "dssm-tag-on" : "dssm-tag-off") }, policyValid ? (enabled ? t("status.enabled") : t("status.disabled")) : t("status.invalid"))),
|
|
375
|
+
h("div", { className: "dssm-note", title: skill.description || "" }, skill.description || t("note.missing"))),
|
|
376
|
+
root.mutable ? h("button", { className: "dssm-btn dssm-btn-secondary", disabled: !!busy, onClick: function () { action(actionEnabled ? "/disable" : "/enable", { name: skill.name, root: root.key }); } }, actionEnabled ? t("btn.disable") : policyValid ? t("btn.enable") : t("btn.repair.enable")) : null,
|
|
377
|
+
root.mutable ? h("button", { className: "dssm-btn dssm-btn-danger", disabled: !!busy, onClick: function () { setConfirmDelete(skill); } }, t("btn.delete")) : null);
|
|
180
378
|
}
|
|
181
379
|
|
|
182
380
|
var nodes = [h("style", { key: "css" }, CSS)];
|
|
183
|
-
if (snapshot.error) nodes.push(h("div", { key: "error", className: "dssm-error", role: "alert" }, "
|
|
381
|
+
if (snapshot.error) nodes.push(h("div", { key: "error", className: "dssm-error", role: "alert" }, t("error.action", { error: translateError(t, snapshot.error) })));
|
|
184
382
|
if (snapshot.data) {
|
|
185
383
|
var roots = snapshot.data.roots || [];
|
|
186
384
|
var skills = roots.reduce(function (all, root) { return all.concat(root.skills || []); }, []);
|
|
187
385
|
var enabled = skills.filter(function (skill) { return skill.modelInvocable; }).length;
|
|
188
386
|
nodes.push(h("div", { key: "toolbar", className: "dssm-toolbar" },
|
|
189
|
-
h("div", null, h("h2", { className: "dssm-title" }, "
|
|
387
|
+
h("div", null, h("h2", { className: "dssm-title" }, t("title")), h("p", { className: "dssm-desc" }, t("desc"))),
|
|
190
388
|
h("div", { className: "dssm-actions" },
|
|
191
|
-
h("button", { className: "dssm-btn dssm-btn-secondary", disabled: !!busy, onClick: refresh }, "
|
|
192
|
-
h("button", { className: "dssm-btn", disabled: !!busy, onClick: function () { setImportResult(null); setUploadOpen(true); } }, "
|
|
389
|
+
h("button", { className: "dssm-btn dssm-btn-secondary", disabled: !!busy, onClick: refresh }, t("btn.refresh")),
|
|
390
|
+
h("button", { className: "dssm-btn", disabled: !!busy, onClick: function () { setImportResult(null); setUploadOpen(true); } }, t("btn.upload")))));
|
|
391
|
+
// 复数按 en 的 one/other 二元规则选择;zh/en 均适用,扩展多复数语言时需改 CLDR 规则。
|
|
193
392
|
nodes.push(h("div", { key: "summary", className: "dssm-summary" },
|
|
194
|
-
h("span", { className: "dssm-summary-count" }, skills.length), h("span", { className: "dssm-summary-label" }, "
|
|
393
|
+
h("span", { className: "dssm-summary-count" }, skills.length), h("span", { className: "dssm-summary-label" }, t(skills.length === 1 ? "summary.total.one" : "summary.total.other", { count: skills.length })), h("span", { className: "dssm-summary-separator" }), h("span", { className: "dssm-summary-count" }, enabled), h("span", { className: "dssm-summary-label" }, t(enabled === 1 ? "summary.enabled.one" : "summary.enabled.other", { count: enabled }))));
|
|
195
394
|
roots.forEach(function (root) {
|
|
196
395
|
var groupSkills = root.skills || [];
|
|
197
396
|
nodes.push(h("div", { key: root.key, className: "dssm-group" },
|
|
198
|
-
h("div", { className: "dssm-group-head" }, h("h3", { className: "dssm-group-title" }, root.label), h("span", { className: "dssm-count" }, groupSkills.length
|
|
199
|
-
root.exists === false ? h("div", { className: "dssm-empty" }, root.mutable ? "
|
|
397
|
+
h("div", { className: "dssm-group-head" }, h("h3", { className: "dssm-group-title" }, translateOrFallback(t, "root." + root.key, root.label)), h("span", { className: "dssm-count" }, t("summary.group", { count: groupSkills.length })), h("span", { className: "dssm-path", title: root.path }, root.path)),
|
|
398
|
+
root.exists === false ? h("div", { className: "dssm-empty" }, root.mutable ? t("empty.dir.uncreated") : t("empty.dir.missing")) : (groupSkills.length ? groupSkills.map(function (skill) { return skillRow(skill, root); }) : h("div", { className: "dssm-empty" }, t("empty.skills.none")))));
|
|
200
399
|
});
|
|
201
400
|
} else if (snapshot.loading) {
|
|
202
|
-
nodes.push(h("div", { key: "loading", className: "dssm-note" }, "
|
|
401
|
+
nodes.push(h("div", { key: "loading", className: "dssm-note" }, t("loading")));
|
|
203
402
|
}
|
|
204
403
|
if (importResult) {
|
|
404
|
+
var nameSeparator = t("sep.names");
|
|
405
|
+
var errorSeparator = t("sep.errors");
|
|
205
406
|
var importedNames = (importResult.imported || []).map(function (item) { return item.name; });
|
|
206
|
-
|
|
407
|
+
// catch 场景把 core 的失败明细挂在 Error.failed 上,这里一并取用。
|
|
408
|
+
var failedItems = importResult.failed || (importResult.error && importResult.error.failed) || [];
|
|
207
409
|
var skippedNames = (importResult.skipped || []).map(function (item) { return item.name; });
|
|
208
|
-
var failedText = failedItems.map(function (item) { return item
|
|
209
|
-
var resultText
|
|
410
|
+
var failedText = failedItems.map(function (item) { return translateError(t, item); }).join(errorSeparator);
|
|
411
|
+
var resultText;
|
|
412
|
+
if (importResult.error) {
|
|
413
|
+
// 顶层聚合错误优先展示已翻译的失败明细;无明细时由 translateError 兜底(含 Error 对象)。
|
|
414
|
+
resultText = t("result.failed", { error: failedItems.length ? failedText : translateError(t, importResult) });
|
|
415
|
+
} else if (failedItems.length) {
|
|
416
|
+
resultText = importedNames.length ? t("result.partial", { names: importedNames.join(nameSeparator), errors: failedText }) : t("result.failed.only", { errors: failedText });
|
|
417
|
+
} else if (importedNames.length) {
|
|
418
|
+
resultText = t("result.done", { names: importedNames.join(nameSeparator) });
|
|
419
|
+
} else if (skippedNames.length) {
|
|
420
|
+
resultText = t("result.skipped", { names: skippedNames.join(nameSeparator) });
|
|
421
|
+
} else {
|
|
422
|
+
resultText = t("result.none");
|
|
423
|
+
}
|
|
210
424
|
nodes.push(h("div", { key: "import-result", className: importResult.error || failedItems.length ? "dssm-error" : "dssm-note", role: "status" }, resultText));
|
|
211
425
|
}
|
|
212
426
|
if (uploadOpen) {
|
|
213
427
|
nodes.push(h("div", { key: "upload", className: "dssm-mask" }, h("div", { className: "dssm-modal", role: "dialog", "aria-modal": "true", "aria-labelledby": "dssm-upload-title" },
|
|
214
|
-
h("div", { className: "dssm-modal-head" }, h("h3", { id: "dssm-upload-title", className: "dssm-modal-title" }, "
|
|
428
|
+
h("div", { className: "dssm-modal-head" }, h("h3", { id: "dssm-upload-title", className: "dssm-modal-title" }, t("upload.title")), h("button", { className: "dssm-icon-btn", "aria-label": t("upload.close"), onClick: function () { setUploadOpen(false); } }, "×")),
|
|
215
429
|
h("input", { ref: inputRef, className: "dssm-hidden-input", type: "file", accept: ".md", onChange: function (event) { selectFile(event.target.files && event.target.files[0]); event.target.value = ""; } }),
|
|
216
|
-
h("div", { className: "dssm-dropzone", tabIndex: 0, onClick: openNativePicker, onKeyDown: function (event) { if (event.key === "Enter" || event.key === " ") { event.preventDefault(); openNativePicker(); } }, onDragOver: function (event) { event.preventDefault(); }, onDrop: function (event) { event.preventDefault(); selectFile(event.dataTransfer.files && event.dataTransfer.files[0]); } }, h("span", { className: "dssm-dropzone-title" }, "
|
|
217
|
-
selected ? h("div", { className: "dssm-file", title: selected.source }, h("span", { className: "dssm-file-name" }, selected.name), h("span", { className: "dssm-tag dssm-tag-on" }, "
|
|
218
|
-
busy === "pick-directory" ? h("div", { className: "dssm-note", role: "status" }, "
|
|
219
|
-
importResult && importResult.error ? h("div", { className: "dssm-error", role: "alert" }, "
|
|
220
|
-
h("div", { className: "dssm-modal-actions" }, h("button", { className: "dssm-btn dssm-btn-secondary", disabled: !!busy, onClick: openNativePicker }, "
|
|
430
|
+
h("div", { className: "dssm-dropzone", tabIndex: 0, onClick: openNativePicker, onKeyDown: function (event) { if (event.key === "Enter" || event.key === " ") { event.preventDefault(); openNativePicker(); } }, onDragOver: function (event) { event.preventDefault(); }, onDrop: function (event) { event.preventDefault(); selectFile(event.dataTransfer.files && event.dataTransfer.files[0]); } }, h("span", { className: "dssm-dropzone-title" }, t("upload.drop.title")), h("span", { className: "dssm-dropzone-copy" }, t("upload.drop.copy"))),
|
|
431
|
+
selected ? h("div", { className: "dssm-file", title: selected.source }, h("span", { className: "dssm-file-name" }, selected.name), h("span", { className: "dssm-tag dssm-tag-on" }, t("status.selected"))) : h("p", { className: "dssm-desc" }, t("upload.hint")),
|
|
432
|
+
busy === "pick-directory" ? h("div", { className: "dssm-note", role: "status" }, t("upload.picking.dir")) : null,
|
|
433
|
+
importResult && importResult.error ? h("div", { className: "dssm-error", role: "alert" }, t("select.failed", { error: translateError(t, importResult) })) : null,
|
|
434
|
+
h("div", { className: "dssm-modal-actions" }, h("button", { className: "dssm-btn dssm-btn-secondary", disabled: !!busy, onClick: openNativePicker }, t("btn.file.pick")), pickDirectory ? h("button", { className: "dssm-btn dssm-btn-secondary", disabled: !!busy, onClick: selectDirectory }, t("btn.dir.pick")) : null, h("button", { className: "dssm-btn", disabled: !selected || !!busy, onClick: installSelected }, busy === "import" || busy === "import-check" ? t("btn.uploading") : t("btn.upload.dsh"))))));
|
|
221
435
|
}
|
|
222
436
|
if (confirmImport) {
|
|
223
|
-
nodes.push(h("div", { key: "import-confirm", className: "dssm-mask" }, h("div", { className: "dssm-modal", role: "dialog", "aria-modal": "true" }, h("div", { className: "dssm-modal-head" }, h("h3", { className: "dssm-modal-title" }, "
|
|
437
|
+
nodes.push(h("div", { key: "import-confirm", className: "dssm-mask" }, h("div", { className: "dssm-modal", role: "dialog", "aria-modal": "true" }, h("div", { className: "dssm-modal-head" }, h("h3", { className: "dssm-modal-title" }, t("confirm.overwrite.title"))), h("p", { className: "dssm-desc" }, t("confirm.overwrite.desc", { names: confirmImport.conflicts.map(function (item) { return item.name; }).join(t("sep.names")) })), h("div", { className: "dssm-modal-actions" }, h("button", { className: "dssm-btn dssm-btn-secondary", onClick: function () { setConfirmImport(null); } }, t("btn.cancel")), h("button", { className: "dssm-btn", disabled: !!busy, onClick: function () { executeImport(confirmImport.source, "overwrite"); } }, t("btn.overwrite.upload"))))));
|
|
224
438
|
}
|
|
225
439
|
if (confirmDelete) {
|
|
226
|
-
nodes.push(h("div", { key: "delete-confirm", className: "dssm-mask" }, h("div", { className: "dssm-modal", role: "dialog", "aria-modal": "true" }, h("div", { className: "dssm-modal-head" }, h("h3", { className: "dssm-modal-title" }, "
|
|
440
|
+
nodes.push(h("div", { key: "delete-confirm", className: "dssm-mask" }, h("div", { className: "dssm-modal", role: "dialog", "aria-modal": "true" }, h("div", { className: "dssm-modal-head" }, h("h3", { className: "dssm-modal-title" }, t("confirm.delete.title"))), h("p", { className: "dssm-desc" }, t("confirm.delete.desc", { name: confirmDelete.name })), h("div", { className: "dssm-modal-actions" }, h("button", { className: "dssm-btn dssm-btn-secondary", disabled: !!busy, onClick: function () { setConfirmDelete(null); } }, t("btn.cancel")), h("button", { className: "dssm-btn dssm-btn-danger", disabled: !!busy, onClick: function () { setConfirmDelete(null); action("/delete", { name: confirmDelete.name }); } }, t("btn.delete.confirm"))))));
|
|
227
441
|
}
|
|
228
442
|
return h("section", { className: "dssm-section" }, nodes);
|
|
229
443
|
}
|
|
230
444
|
|
|
231
445
|
function apply(ctx) {
|
|
232
446
|
pickDirectory = function () { return ctx.workspaces.pickDirectory(); };
|
|
447
|
+
ctx.effect(function () {
|
|
448
|
+
return ctx.locale.register(NS, DICT);
|
|
449
|
+
}, "skills-manager: dictionaries");
|
|
233
450
|
ctx.slots.inject("settings.section", function () {
|
|
234
|
-
return ctx.slots.register({ name: "settings.section", id: "skills-manager", order: 17, label: "
|
|
451
|
+
return ctx.slots.register({ name: "settings.section", id: "skills-manager", order: 17, label: function () { return ctx.locale.bind(NS)("title"); }, locale: NS }, SkillManagerSection);
|
|
235
452
|
});
|
|
236
453
|
}
|
|
237
454
|
module.exports.apply = apply;
|
|
238
|
-
module.exports.inject = ["slots", "workspaces"];
|
|
455
|
+
module.exports.inject = ["slots", "workspaces", "locale"];
|
|
456
|
+
// 导出词典与翻译函数供零依赖对齐测试读取(宿主只消费 apply/inject,不影响运行时)。
|
|
457
|
+
module.exports.DICT = DICT;
|
|
458
|
+
module.exports.translateError = translateError;
|
|
239
459
|
return module.exports;
|
|
240
460
|
}
|
|
241
461
|
});
|
package/lib/core.js
CHANGED
|
@@ -5,7 +5,10 @@
|
|
|
5
5
|
// - 条目形态:<root>/<name>/SKILL.md(bundle)或 <root>/<name>.md(flat),只扫一层
|
|
6
6
|
// - 前端展示 name、description 与启停状态,不做格式检查或自动修复
|
|
7
7
|
//
|
|
8
|
-
// 所有函数返回普通结果对象,业务校验失败返回 { ok: false, error }
|
|
8
|
+
// 所有函数返回普通结果对象,业务校验失败返回 { ok: false, error, code?, params? };
|
|
9
|
+
// error 保持中文原文(兼容性红线),code 为点分小写业务错误码,params 供前端词典占位符替换;
|
|
10
|
+
// 系统异常(fs ENOENT 等)透传 String(e.message),不加 code。
|
|
11
|
+
// 文件写入错误由路由返回给调用方。
|
|
9
12
|
|
|
10
13
|
import { homedir } from "node:os";
|
|
11
14
|
import { join, basename, dirname, resolve, relative, isAbsolute, sep } from "node:path";
|
|
@@ -16,6 +19,23 @@ const KEBAB_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
|
16
19
|
const WINDOWS_DEVICE_NAME_RE = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\..*)?$/i;
|
|
17
20
|
const MAX_SOURCE_DEPTH = 64;
|
|
18
21
|
|
|
22
|
+
// ── 业务错误码 ────────────────────────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
/** 构造带 code/params 的业务 Error,供导入链路 throw 后透传到失败明细。 */
|
|
25
|
+
function codedError(message, code, params) {
|
|
26
|
+
const error = new Error(message);
|
|
27
|
+
error.code = code;
|
|
28
|
+
error.params = params;
|
|
29
|
+
return error;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** 把业务 Error 的 code/params 附加到失败明细;系统异常(无 code)保持原文。 */
|
|
33
|
+
function attachCode(item, error) {
|
|
34
|
+
if (error && typeof error.code === "string") item.code = error.code;
|
|
35
|
+
if (error && error.params) item.params = error.params;
|
|
36
|
+
return item;
|
|
37
|
+
}
|
|
38
|
+
|
|
19
39
|
// ── 路径解析 ────────────────────────────────────────────────────────────────
|
|
20
40
|
|
|
21
41
|
export function resolveDshHome() {
|
|
@@ -42,6 +62,16 @@ function dshRootPath() {
|
|
|
42
62
|
return userRoots().find((root) => root.key === "dsh").path;
|
|
43
63
|
}
|
|
44
64
|
|
|
65
|
+
/** 公共 Agent 目录的只读拒绝结果;action 为可翻译语义值(toggle/delete)。 */
|
|
66
|
+
function readonlyError(action) {
|
|
67
|
+
return {
|
|
68
|
+
ok: false,
|
|
69
|
+
code: "error.root.readonly",
|
|
70
|
+
params: { action },
|
|
71
|
+
error: action === "delete" ? "公共 Agent 技能目录不允许删除" : "公共 Agent 技能目录不允许启用或停用",
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
45
75
|
/** 判断 child 是否与 parent 相同或位于其内部。跨盘符时 relative 会返回绝对路径。 */
|
|
46
76
|
function isSameOrDescendant(parent, child) {
|
|
47
77
|
const rel = relative(resolve(parent), resolve(child));
|
|
@@ -252,21 +282,21 @@ export async function scanEntries(root) {
|
|
|
252
282
|
|
|
253
283
|
/** enabled=true 恢复模型与 / 手动调用;false 同时停用两种调用入口。 */
|
|
254
284
|
export async function setSkillEnabled(root, name, enabled) {
|
|
255
|
-
if (root !== dshRootPath()) return
|
|
285
|
+
if (root !== dshRootPath()) return readonlyError("toggle");
|
|
256
286
|
const resolved = await resolveEntry(root, name);
|
|
257
|
-
if (resolved === null) return { ok: false, error: `技能不存在: ${name}
|
|
287
|
+
if (resolved === null) return { ok: false, error: `技能不存在: ${name}`, code: "error.skill.notFound", params: { name } };
|
|
258
288
|
const source = await fs.readFile(resolved.docPath, "utf8");
|
|
259
289
|
const updated = updateInvocationPolicy(source, enabled);
|
|
260
|
-
if (updated === null) return { ok: false, error: `技能缺少完整 frontmatter,无法${enabled ? "启用" : "停用"}: ${name}
|
|
290
|
+
if (updated === null) return { ok: false, error: `技能缺少完整 frontmatter,无法${enabled ? "启用" : "停用"}: ${name}`, code: "error.skill.noFrontmatter", params: { name, action: enabled ? "enable" : "disable" } };
|
|
261
291
|
await writeFileAtomically(resolved.docPath, updated);
|
|
262
292
|
return { name, enabled };
|
|
263
293
|
}
|
|
264
294
|
|
|
265
295
|
/** 删除 DSH 根目录中的单个技能。调用方必须先向用户确认。 */
|
|
266
296
|
export async function deleteSkill(root, name, log) {
|
|
267
|
-
if (root !== dshRootPath()) return
|
|
297
|
+
if (root !== dshRootPath()) return readonlyError("delete");
|
|
268
298
|
const resolved = await resolveEntry(root, name);
|
|
269
|
-
if (resolved === null) return { ok: false, error: `技能不存在: ${name}
|
|
299
|
+
if (resolved === null) return { ok: false, error: `技能不存在: ${name}`, code: "error.skill.notFound", params: { name } };
|
|
270
300
|
await fs.rm(resolved.entryPath, { recursive: resolved.kind === "bundle", force: true });
|
|
271
301
|
if (log) log("delete", `删除 ${resolved.entryPath}`);
|
|
272
302
|
return { name };
|
|
@@ -280,9 +310,9 @@ async function analyzeSource(source) {
|
|
|
280
310
|
try {
|
|
281
311
|
st = await fs.lstat(source);
|
|
282
312
|
} catch {
|
|
283
|
-
return { kind: "none", error: `路径不存在: ${source}
|
|
313
|
+
return { kind: "none", error: `路径不存在: ${source}`, code: "error.source.notFound", params: { path: source } };
|
|
284
314
|
}
|
|
285
|
-
if (st.isSymbolicLink()) return { kind: "none", error: `不支持包含符号链接的 skill 来源: ${source}
|
|
315
|
+
if (st.isSymbolicLink()) return { kind: "none", error: `不支持包含符号链接的 skill 来源: ${source}`, code: "error.source.symlink", params: { path: source } };
|
|
286
316
|
if (st.isDirectory()) {
|
|
287
317
|
try {
|
|
288
318
|
const sk = join(source, "SKILL.md");
|
|
@@ -301,14 +331,14 @@ async function analyzeSource(source) {
|
|
|
301
331
|
const rawName = basename(source).slice(0, -3);
|
|
302
332
|
return { kind: "single", rawName, kebab: toKebab(rawName), source, isDir: false, skillFile: source };
|
|
303
333
|
}
|
|
304
|
-
return { kind: "none", error: `无法识别的 skill 来源: ${source}
|
|
334
|
+
return { kind: "none", error: `无法识别的 skill 来源: ${source}`, code: "error.source.unrecognized", params: { path: source } };
|
|
305
335
|
}
|
|
306
336
|
|
|
307
337
|
async function collectCandidates(dir) {
|
|
308
338
|
const items = await fs.readdir(dir, { withFileTypes: true });
|
|
309
339
|
const out = [];
|
|
310
340
|
for (const it of items) {
|
|
311
|
-
if (it.isSymbolicLink()) throw
|
|
341
|
+
if (it.isSymbolicLink()) throw codedError(`不支持包含符号链接的 skill 来源: ${join(dir, it.name)}`, "error.source.symlink", { path: join(dir, it.name) });
|
|
312
342
|
try {
|
|
313
343
|
if (it.isDirectory()) {
|
|
314
344
|
const sk = join(dir, it.name, "SKILL.md");
|
|
@@ -331,14 +361,14 @@ async function assertNoSymbolicLinks(source) {
|
|
|
331
361
|
const pending = [{ path: source, depth: 0 }];
|
|
332
362
|
while (pending.length) {
|
|
333
363
|
const current = pending.pop();
|
|
334
|
-
if (current.depth > MAX_SOURCE_DEPTH) throw
|
|
364
|
+
if (current.depth > MAX_SOURCE_DEPTH) throw codedError(`skill 来源目录层级超过 ${MAX_SOURCE_DEPTH} 层: ${source}`, "error.source.tooDeep", { depth: MAX_SOURCE_DEPTH, path: source });
|
|
335
365
|
const st = await fs.lstat(current.path);
|
|
336
|
-
if (st.isSymbolicLink()) throw
|
|
366
|
+
if (st.isSymbolicLink()) throw codedError(`不支持包含符号链接的 skill 来源: ${current.path}`, "error.source.symlink", { path: current.path });
|
|
337
367
|
if (!st.isDirectory()) continue;
|
|
338
368
|
const items = await fs.readdir(current.path, { withFileTypes: true });
|
|
339
369
|
for (const item of items) {
|
|
340
370
|
const path = join(current.path, item.name);
|
|
341
|
-
if (item.isSymbolicLink()) throw
|
|
371
|
+
if (item.isSymbolicLink()) throw codedError(`不支持包含符号链接的 skill 来源: ${path}`, "error.source.symlink", { path });
|
|
342
372
|
if (item.isDirectory()) pending.push({ path, depth: current.depth + 1 });
|
|
343
373
|
}
|
|
344
374
|
}
|
|
@@ -400,8 +430,8 @@ export async function importSkill(source, log, options = {}) {
|
|
|
400
430
|
const dryRun = options.dryRun === true;
|
|
401
431
|
|
|
402
432
|
const analysis = await analyzeSource(source);
|
|
403
|
-
if (analysis.kind === "none") return { ok: false, error: analysis.error || "无法识别的 skill 来源" };
|
|
404
|
-
if (pathsOverlap(analysis.source, targetRoot)) return { ok: false, error: "导入来源不能与 DSH 技能目录相同、包含或位于其中" };
|
|
433
|
+
if (analysis.kind === "none") return { ok: false, error: analysis.error || "无法识别的 skill 来源", code: analysis.code || "error.source.unrecognized", params: analysis.params };
|
|
434
|
+
if (pathsOverlap(analysis.source, targetRoot)) return { ok: false, error: "导入来源不能与 DSH 技能目录相同、包含或位于其中", code: "error.import.overlap" };
|
|
405
435
|
|
|
406
436
|
let candidates = [];
|
|
407
437
|
if (analysis.kind === "single") {
|
|
@@ -410,9 +440,9 @@ export async function importSkill(source, log, options = {}) {
|
|
|
410
440
|
try {
|
|
411
441
|
candidates = await collectCandidates(source);
|
|
412
442
|
} catch (error) {
|
|
413
|
-
return { ok: false, error: String(error && error.message ? error.message : error) };
|
|
443
|
+
return attachCode({ ok: false, error: String(error && error.message ? error.message : error) }, error);
|
|
414
444
|
}
|
|
415
|
-
if (candidates.length === 0) return { ok: false, error: `目录下未找到任何 skill 条目(需含 SKILL.md 的子目录或 .md 文件): ${source}
|
|
445
|
+
if (candidates.length === 0) return { ok: false, error: `目录下未找到任何 skill 条目(需含 SKILL.md 的子目录或 .md 文件): ${source}`, code: "error.import.emptySource", params: { path: source } };
|
|
416
446
|
}
|
|
417
447
|
|
|
418
448
|
const pending = [];
|
|
@@ -429,7 +459,9 @@ export async function importSkill(source, log, options = {}) {
|
|
|
429
459
|
function failureResult() {
|
|
430
460
|
return {
|
|
431
461
|
ok: false,
|
|
462
|
+
// 聚合失败明细的原文;前端优先展示已翻译的 failed 明细,此处仅作兜底。
|
|
432
463
|
error: failed.map((item) => item.error).join(";"),
|
|
464
|
+
code: "error.import.failed",
|
|
433
465
|
kind: analysis.kind,
|
|
434
466
|
imported,
|
|
435
467
|
skipped,
|
|
@@ -439,11 +471,11 @@ export async function importSkill(source, log, options = {}) {
|
|
|
439
471
|
|
|
440
472
|
for (const c of candidates) {
|
|
441
473
|
if (!c.kebab || !KEBAB_RE.test(c.kebab)) {
|
|
442
|
-
failed.push({ source: c.source, error: `无法生成合法 kebab-case 名称(原始名: ${c.rawName || basename(c.source)}
|
|
474
|
+
failed.push({ source: c.source, error: `无法生成合法 kebab-case 名称(原始名: ${c.rawName || basename(c.source)})`, code: "error.import.invalidName", params: { name: c.rawName || basename(c.source) } });
|
|
443
475
|
continue;
|
|
444
476
|
}
|
|
445
477
|
if (nameCount.get(c.kebab) > 1) {
|
|
446
|
-
failed.push({ source: c.source, error: `批量来源中存在多个同名插件: ${c.kebab}
|
|
478
|
+
failed.push({ source: c.source, error: `批量来源中存在多个同名插件: ${c.kebab}`, code: "error.import.duplicateName", params: { name: c.kebab } });
|
|
447
479
|
continue;
|
|
448
480
|
}
|
|
449
481
|
const dest = c.isDir ? join(targetRoot, c.kebab) : join(targetRoot, `${c.kebab}.md`);
|
|
@@ -475,7 +507,7 @@ export async function importSkill(source, log, options = {}) {
|
|
|
475
507
|
imported.push({ name: p.name, overwritten: false, warnings });
|
|
476
508
|
if (log) log("import", `导入 ${p.source} -> ${p.dest}`);
|
|
477
509
|
} catch (e) {
|
|
478
|
-
failed.push({ source: p.source, error: String(e && e.message ? e.message : e) });
|
|
510
|
+
failed.push(attachCode({ source: p.source, error: String(e && e.message ? e.message : e) }, e));
|
|
479
511
|
}
|
|
480
512
|
}
|
|
481
513
|
|
|
@@ -487,7 +519,7 @@ export async function importSkill(source, log, options = {}) {
|
|
|
487
519
|
imported.push({ name: c.name, overwritten: true, warnings });
|
|
488
520
|
if (log) log("import-overwrite", `覆盖导入 ${c.source} -> ${dest}`);
|
|
489
521
|
} catch (e) {
|
|
490
|
-
failed.push({ source: c.source, error: String(e && e.message ? e.message : e) });
|
|
522
|
+
failed.push(attachCode({ source: c.source, error: String(e && e.message ? e.message : e) }, e));
|
|
491
523
|
}
|
|
492
524
|
}
|
|
493
525
|
} else {
|
package/lib/index.js
CHANGED
|
@@ -145,11 +145,11 @@ function apply(ctx) {
|
|
|
145
145
|
const body = await readBody(req);
|
|
146
146
|
switch (path) {
|
|
147
147
|
case "/api/dsh-skills-manager/enable": {
|
|
148
|
-
run(res, String(body.root || "dsh")
|
|
148
|
+
run(res, setSkillEnabled(rootByKey[String(body.root || "dsh")], String(body.name || ""), true), invalidateSkills);
|
|
149
149
|
return;
|
|
150
150
|
}
|
|
151
151
|
case "/api/dsh-skills-manager/disable": {
|
|
152
|
-
run(res, String(body.root || "dsh")
|
|
152
|
+
run(res, setSkillEnabled(rootByKey[String(body.root || "dsh")], String(body.name || ""), false), invalidateSkills);
|
|
153
153
|
return;
|
|
154
154
|
}
|
|
155
155
|
case "/api/dsh-skills-manager/delete": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@michengai/dsh-skills-manager",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "NPM-installable DSH Web plugin for managing local skills and viewing shared Agent skills safely.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -31,13 +31,14 @@
|
|
|
31
31
|
"inject": [
|
|
32
32
|
"@deepseek-ai/dsh-client-runtime",
|
|
33
33
|
"@deepseek-ai/dsh-client-ui-slots",
|
|
34
|
+
"@deepseek-ai/dsh-client-locale",
|
|
34
35
|
"@deepseek-ai/dsh-client-ui-workspace"
|
|
35
36
|
],
|
|
36
37
|
"platform": "web"
|
|
37
38
|
}
|
|
38
39
|
},
|
|
39
40
|
"scripts": {
|
|
40
|
-
"test": "node test/core-test.mjs",
|
|
41
|
+
"test": "node test/core-test.mjs && node test/locale-test.mjs",
|
|
41
42
|
"pack:check": "npm pack --dry-run --json",
|
|
42
43
|
"prepublishOnly": "npm test"
|
|
43
44
|
},
|
|
@@ -49,6 +50,7 @@
|
|
|
49
50
|
"@deepseek-ai/cordis": ">=4.0.1 <5.0.0",
|
|
50
51
|
"@deepseek-ai/dsh-client-runtime": ">=0.1.0-rc.0 <0.2.0",
|
|
51
52
|
"@deepseek-ai/dsh-client-ui-slots": ">=0.1.0-rc.0 <0.2.0",
|
|
53
|
+
"@deepseek-ai/dsh-client-locale": ">=0.1.0-rc.0 <0.2.0",
|
|
52
54
|
"@deepseek-ai/dsh-host-webserver": ">=0.1.0-rc.5",
|
|
53
55
|
"@deepseek-ai/dsh-skill": ">=0.1.0-rc.5",
|
|
54
56
|
"@deepseek-ai/dsh-client-ui-workspace": ">=0.1.0-rc.5",
|