@michengai/dsh-skills-manager 1.0.0 → 1.0.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/CHANGELOG.md +20 -0
- package/CHANGELOG.zh-CN.md +20 -0
- package/README.md +8 -4
- package/README.zh-CN.md +8 -4
- package/assets/screenshots/delete-plugin.png +0 -0
- package/assets/screenshots/skill-detail.png +0 -0
- package/assets/screenshots/skill-repositories.webp +0 -0
- package/assets/screenshots/skills-manager-en.png +0 -0
- package/assets/screenshots/skills-manager-v2-preview.webp +0 -0
- package/assets/screenshots/skills-manager.png +0 -0
- package/assets/screenshots/skills-public.png +0 -0
- package/assets/screenshots/slash-command.png +0 -0
- package/assets/screenshots/upload-plugin.png +0 -0
- package/lib/client.js +59 -39
- package/lib/core.js +67 -41
- package/lib/index.js +9 -6
- package/lib/plugin-updater.js +2 -1
- package/lib/readonly-discovery.js +2 -1
- package/lib/repositories.js +16 -8
- package/lib/repository-updates.js +2 -1
- package/package.json +11 -6
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
[简体中文](CHANGELOG.zh-CN.md)
|
|
4
4
|
|
|
5
|
+
## 1.0.1 - 2026-09-21
|
|
6
|
+
|
|
7
|
+
This maintenance release improves source development, packaging verification and screenshot privacy while retaining the existing plugin runtime interfaces.
|
|
8
|
+
|
|
9
|
+
### Development and packaging
|
|
10
|
+
|
|
11
|
+
- Migrate ten source modules to strict TypeScript, retaining the existing plugin architecture and JavaScript runtime entry points. Add domain, host and client contracts, plus positive and negative type tests; include type checking in the main test command.
|
|
12
|
+
- Stop tracking `lib` in Git. Run type checks and build automatically before packing, and verify that a clean directory without `lib` produces a package with all runtime files and a loadable server entry point.
|
|
13
|
+
- Cover the exact minimum Node.js version 22.19.0 alongside Node.js 24 in CI. Code changes continue to trigger the supported-host compatibility matrix.
|
|
14
|
+
- Document supported installation via the npm registry or locally built `.tgz` packages; direct Git/GitHub dependency installation is not supported. Align the English and Chinese development instructions.
|
|
15
|
+
|
|
16
|
+
### Fixes
|
|
17
|
+
|
|
18
|
+
- Restore modal focus after an asynchronous form submission disables the focused button, so Escape after an error closes only the plugin dialog instead of the entire host settings page.
|
|
19
|
+
|
|
20
|
+
### Documentation and privacy
|
|
21
|
+
|
|
22
|
+
- Crop all nine screenshots to their feature panels, redact local paths, and remove real filenames and project directories from the background. Reduce total screenshot size by 81% with lossless compression.
|
|
23
|
+
- Correct the English screenshot caption and align image display sizes with the cropped files.
|
|
24
|
+
|
|
5
25
|
## 1.0.0 - 2026-09-21
|
|
6
26
|
|
|
7
27
|
### 1.0 release scope
|
package/CHANGELOG.zh-CN.md
CHANGED
|
@@ -2,6 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
[English](CHANGELOG.md)
|
|
4
4
|
|
|
5
|
+
## 1.0.1 - 2026-09-21
|
|
6
|
+
|
|
7
|
+
本次维护版本完善源码开发、打包验证与截图隐私处理,保留现有插件运行接口。
|
|
8
|
+
|
|
9
|
+
### 开发与打包
|
|
10
|
+
|
|
11
|
+
- 十个源码模块迁移到严格 TypeScript,保留现有插件架构和 JavaScript 运行入口;新增领域、宿主和客户端契约以及类型正反例测试,将类型检查纳入主测试命令。
|
|
12
|
+
- `lib` 不再纳入 Git;打包前自动检查类型并构建,验证没有 `lib` 的干净目录仍可生成包含全部运行产物、服务端入口可加载的安装包。
|
|
13
|
+
- CI 同时覆盖精确最低 Node.js 22.19.0 与 Node.js 24;代码改动继续自动触发受支持宿主兼容矩阵。
|
|
14
|
+
- 明确支持 npm registry 和本地构建 `.tgz` 安装,不支持 Git/GitHub 依赖直装;对齐中英文开发说明。
|
|
15
|
+
|
|
16
|
+
### 修复
|
|
17
|
+
|
|
18
|
+
- 修复异步表单提交禁用按钮后焦点丢失的问题;错误后按 Escape 仅关闭插件弹窗,不再连带关闭宿主设置页。
|
|
19
|
+
|
|
20
|
+
### 文档与隐私
|
|
21
|
+
|
|
22
|
+
- 将全部九张截图裁到功能面板,遮盖本机路径并移除背景中的真实文件名和项目目录;无损压缩后总体积减少 81%。
|
|
23
|
+
- 修正英文截图说明,并按裁切后的尺寸调整文档展示。
|
|
24
|
+
|
|
5
25
|
## 1.0.0 - 2026-09-21
|
|
6
26
|
|
|
7
27
|
### 1.0 正式版范围
|
package/README.md
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
|
|
20
20
|
> DSH Skills Manager is a community-maintained DeepSeek Harness (DSH) plugin, not an official DeepSeek AI product.
|
|
21
21
|
|
|
22
|
-
This README describes **1.0.
|
|
22
|
+
This README describes **1.0.1**. See the [changelog](CHANGELOG.md) and [release notes](https://github.com/MichengAI/dsh-skills-manager/releases/latest).
|
|
23
23
|
|
|
24
24
|
## Features
|
|
25
25
|
|
|
@@ -45,12 +45,14 @@ Skill repositories:
|
|
|
45
45
|
|
|
46
46
|

|
|
47
47
|
|
|
48
|
-
*Screenshots show the pre-release 0.1.54 local build used to prepare 1.0.0. The final version adds refresh animation and progress feedback. Screenshots use lossless WebP
|
|
48
|
+
*Screenshots show the pre-release 0.1.54 local build used to prepare 1.0.0. The final version adds refresh animation and progress feedback. Screenshots show only the feature panels, with real filenames and project directories cropped out of the background, and use lossless WebP.*
|
|
49
49
|
|
|
50
50
|
## Installation
|
|
51
51
|
|
|
52
52
|
Requires a working DeepSeek Harness installation. The commands below use the `web` profile; replace it with yours if needed.
|
|
53
53
|
|
|
54
|
+
Supported plugin installation sources are the npm registry and a locally built `.tgz` package. Direct Git/GitHub dependency installation (such as `github:MichengAI/dsh-skills-manager`) is not supported. For source development, install development dependencies and build the project before loading it into DSH.
|
|
55
|
+
|
|
54
56
|
### Ask an agent to install it
|
|
55
57
|
|
|
56
58
|
Send this to an agent that can use your local terminal:
|
|
@@ -69,7 +71,7 @@ dsh plugin --profile web add @michengai/dsh-skills-manager@latest --registry=htt
|
|
|
69
71
|
|
|
70
72
|
Restart DSH, refresh the page, and open **Settings → Skills**. To update, select **Check for updates** or run the installation command again.
|
|
71
73
|
|
|
72
|
-
- Plugin `1.0.
|
|
74
|
+
- Plugin `1.0.1` is tested with DeepSeek Harness `0.1.0-rc.8`, `0.1.1-rc.2`, `0.1.2-rc.1`, `0.1.5-rc.1`, `0.1.5-rc.2`, `0.1.6-alpha.1`, `0.1.6-alpha.2`.
|
|
73
75
|
|
|
74
76
|
## Usage
|
|
75
77
|
|
|
@@ -142,7 +144,9 @@ For a desktop workbench, download [DSH Codex Desktop](https://github.com/Micheng
|
|
|
142
144
|
|
|
143
145
|
[Open an issue](https://github.com/MichengAI/dsh-skills-manager/issues) for bugs or suggestions. Include your DSH and plugin versions, reproduction steps, and a full screenshot for UI issues.
|
|
144
146
|
|
|
145
|
-
Source code lives in `src
|
|
147
|
+
Source code lives in `src` and uses strict TypeScript while retaining the existing plugin architecture. Run `npm run typecheck` for type checks, `npm run build` to generate runtime files, and `npm run verify` for the full regression and package checks. `lib` is not tracked in Git; packing automatically checks types and builds the JavaScript files required by the npm package. CI runs full verification on Node.js 22.19.0 and 24, and code changes also trigger the supported-host compatibility matrix. Contributions are welcome.
|
|
148
|
+
|
|
149
|
+
To test a source checkout, install dependencies using the pinned pnpm version, run `npm run verify`, then run `npm pack`. Install the resulting `.tgz` with `dsh plugin --profile web add <package.tgz>`, restart DSH and refresh the page. Do not use `--ignore-scripts` when packing: it skips the build hook.
|
|
146
150
|
|
|
147
151
|
## License
|
|
148
152
|
|
package/README.zh-CN.md
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
|
|
20
20
|
> DSH Skills Manager 是社区维护的 DeepSeek Harness(DSH)插件,并非 DeepSeek AI 官方产品。
|
|
21
21
|
|
|
22
|
-
本文对应 **1.0.
|
|
22
|
+
本文对应 **1.0.1**。完整变更见[更新日志](CHANGELOG.zh-CN.md)与[最新发行说明](https://github.com/MichengAI/dsh-skills-manager/releases/latest)。
|
|
23
23
|
|
|
24
24
|
## 功能概览
|
|
25
25
|
|
|
@@ -45,12 +45,14 @@
|
|
|
45
45
|
|
|
46
46
|

|
|
47
47
|
|
|
48
|
-
*截图来自 1.0.0 发布前的 0.1.54
|
|
48
|
+
*截图来自 1.0.0 发布前的 0.1.54 本地测试版,展示全局技能和技能仓库;最终版补充了刷新动画及进度提示。截图仅保留功能面板,已裁去背景中的真实文件名与项目目录,采用无损 WebP。*
|
|
49
49
|
|
|
50
50
|
## 安装
|
|
51
51
|
|
|
52
52
|
需要已安装并能正常运行 DeepSeek Harness。以下命令使用 `web` profile,请按实际环境替换。
|
|
53
53
|
|
|
54
|
+
插件安装支持 npm registry 和本地构建的 `.tgz` 包,不支持直接以 Git/GitHub 依赖安装(例如 `github:MichengAI/dsh-skills-manager`)。从源码开发时,请先安装开发依赖并构建,再加载到 DSH。
|
|
55
|
+
|
|
54
56
|
### 让 Agent 帮你安装
|
|
55
57
|
|
|
56
58
|
把这段话发给能够操作本机终端的 Agent:
|
|
@@ -69,7 +71,7 @@ dsh plugin --profile web add @michengai/dsh-skills-manager@latest --registry=htt
|
|
|
69
71
|
|
|
70
72
|
安装后重启 DSH 并刷新页面,打开「设置 → 技能」即可使用。更新时可点击「检查更新」,或重新执行安装命令。
|
|
71
73
|
|
|
72
|
-
- `1.0.
|
|
74
|
+
- `1.0.1` 已验证兼容 DeepSeek Harness `0.1.0-rc.8`、`0.1.1-rc.2`、`0.1.2-rc.1`、`0.1.5-rc.1`、`0.1.5-rc.2`、`0.1.6-alpha.1`、`0.1.6-alpha.2`。
|
|
73
75
|
|
|
74
76
|
## 使用说明
|
|
75
77
|
|
|
@@ -142,7 +144,9 @@ dsh plugin --profile web add @michengai/dsh-skills-manager@latest --registry=htt
|
|
|
142
144
|
|
|
143
145
|
遇到问题或有建议,欢迎[提交 Issue](https://github.com/MichengAI/dsh-skills-manager/issues)。请附上 DSH 与插件版本、复现步骤;界面问题可附完整截图。
|
|
144
146
|
|
|
145
|
-
源码位于 `src
|
|
147
|
+
源码位于 `src`,使用严格 TypeScript,保留现有插件架构。开发时执行 `npm run typecheck` 检查类型,`npm run build` 生成运行产物,`npm run verify` 执行完整回归与打包验证。`lib` 不纳入 Git 提交;打包前会自动检查类型并构建,npm 安装包仍包含所需 JavaScript 产物。CI 在 Node.js 22.19.0 和 24 上执行完整验证,代码改动也会自动触发受支持宿主兼容矩阵。欢迎提交改进。
|
|
148
|
+
|
|
149
|
+
从源码制作测试包时,使用项目固定版本的 pnpm 安装依赖,执行 `npm run verify`,再执行 `npm pack`。用 `dsh plugin --profile web add <package.tgz>` 安装生成的 `.tgz`,重启 DSH 并刷新页面。打包时不要使用 `--ignore-scripts`,否则会跳过构建钩子。
|
|
146
150
|
|
|
147
151
|
## 许可证
|
|
148
152
|
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/lib/client.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
"use strict";
|
|
1
2
|
(() => {
|
|
2
|
-
// src/repository-ui.
|
|
3
|
+
// src/repository-ui.ts
|
|
3
4
|
var repositoryLocales = {
|
|
4
5
|
zh: {
|
|
5
6
|
"repo.refreshing": "\u6B63\u5728\u68C0\u67E5\u66F4\u65B0\u2026",
|
|
@@ -164,8 +165,9 @@
|
|
|
164
165
|
if (mounted.current) setRepos(data.repositories || []);
|
|
165
166
|
}
|
|
166
167
|
function message(error) {
|
|
167
|
-
|
|
168
|
-
|
|
168
|
+
const item = error;
|
|
169
|
+
if (t("format.locale") === "zh-CN" && item.code === "error.repo.invalid") return item.error || item.message || t(item.code);
|
|
170
|
+
return translateError(t, error) || item.message || String(error);
|
|
169
171
|
}
|
|
170
172
|
function feedbackText(value) {
|
|
171
173
|
return value.payload ? message(value.payload) : (value.key ? t(value.key) : value.text || "") + (value.name ? " \xB7 " + value.name : "");
|
|
@@ -180,7 +182,8 @@
|
|
|
180
182
|
try {
|
|
181
183
|
await task();
|
|
182
184
|
if (reload) await load();
|
|
183
|
-
} catch (
|
|
185
|
+
} catch (caught) {
|
|
186
|
+
const error = caught;
|
|
184
187
|
if (mounted.current) setFeedback({ error: true, payload: error });
|
|
185
188
|
} finally {
|
|
186
189
|
locked.current = false;
|
|
@@ -188,7 +191,7 @@
|
|
|
188
191
|
}
|
|
189
192
|
}
|
|
190
193
|
function post(action, body) {
|
|
191
|
-
return api(
|
|
194
|
+
return api(`/repositories/${action}`, { method: "POST", headers, body: JSON.stringify(body) });
|
|
192
195
|
}
|
|
193
196
|
async function refreshRepo(id) {
|
|
194
197
|
const result = await post("refresh", { id });
|
|
@@ -220,7 +223,8 @@
|
|
|
220
223
|
try {
|
|
221
224
|
const detail = await post("detail", { id: repo.id, path: skill.path });
|
|
222
225
|
if (mounted.current) setModal({ type: "detail", detail, repo });
|
|
223
|
-
} catch (
|
|
226
|
+
} catch (caught) {
|
|
227
|
+
const error = caught;
|
|
224
228
|
if (mounted.current) setModal({ type: "detail", repo, failed: true });
|
|
225
229
|
throw error;
|
|
226
230
|
}
|
|
@@ -250,7 +254,8 @@
|
|
|
250
254
|
setInstallation({ ...target, pending: true, key: "repo.installProgress" });
|
|
251
255
|
try {
|
|
252
256
|
await post("install", target);
|
|
253
|
-
} catch (
|
|
257
|
+
} catch (caught) {
|
|
258
|
+
const error = caught;
|
|
254
259
|
if (mounted.current) setInstallation({ ...target, error: true, payload: error });
|
|
255
260
|
throw error;
|
|
256
261
|
}
|
|
@@ -267,13 +272,15 @@
|
|
|
267
272
|
});
|
|
268
273
|
}
|
|
269
274
|
function update() {
|
|
275
|
+
if (!modal || modal.type !== "review" && modal.type !== "rollback") return;
|
|
270
276
|
const selected = modal, rollback = selected.type === "rollback";
|
|
271
277
|
return perform(async () => {
|
|
272
278
|
const target = { id: selected.repo.id, path: selected.skill.path };
|
|
273
279
|
setInstallation({ ...target, pending: true, key: "repo.updateProgress" });
|
|
274
280
|
try {
|
|
275
281
|
await post(rollback ? "rollback" : "update", { ...target, token: selected.detail.token, overwrite: selected.detail.localModified });
|
|
276
|
-
} catch (
|
|
282
|
+
} catch (caught) {
|
|
283
|
+
const error = caught;
|
|
277
284
|
if (mounted.current) setInstallation({ ...target, error: true, payload: error });
|
|
278
285
|
throw error;
|
|
279
286
|
}
|
|
@@ -350,7 +357,7 @@
|
|
|
350
357
|
"div",
|
|
351
358
|
{ className: "dssm-repo-panel" },
|
|
352
359
|
h("div", { className: "dssm-name" }, modal.skill.name),
|
|
353
|
-
h("div", { className: "dssm-repo-metadata" }, modal.repo.owner + "/" + modal.repo.name + " \xB7 " + modal.detail.commit
|
|
360
|
+
h("div", { className: "dssm-repo-metadata" }, modal.repo.owner + "/" + modal.repo.name + " \xB7 " + modal.detail.commit?.slice(0, 7)),
|
|
354
361
|
modal.detail.localModified ? h("p", { className: "dssm-feedback dssm-warning", role: "alert" }, t("repo.modified")) : null,
|
|
355
362
|
modal.detail.changes.length ? h("ul", { style: { maxHeight: "240px", overflow: "auto", overflowWrap: "anywhere", paddingLeft: "20px" } }, modal.detail.changes.map((change) => h("li", { key: change.path }, t("repo." + (change.kind === "modified" ? "modifiedFile" : change.kind)) + " \xB7 " + change.path))) : h("p", null, t("repo.noChanges")),
|
|
356
363
|
busy ? h("div", { role: "status" }, t("repo.updateProgress")) : null,
|
|
@@ -430,7 +437,7 @@
|
|
|
430
437
|
};
|
|
431
438
|
}
|
|
432
439
|
|
|
433
|
-
// src/plugin-update-ui.
|
|
440
|
+
// src/plugin-update-ui.ts
|
|
434
441
|
var UPDATE_HEADER = "x-michengai-plugin-update";
|
|
435
442
|
var STYLE_ID = "michengai-plugin-update-ui";
|
|
436
443
|
var CSS2 = `
|
|
@@ -512,7 +519,7 @@
|
|
|
512
519
|
...signalOption
|
|
513
520
|
});
|
|
514
521
|
const value = await response.json();
|
|
515
|
-
if (!response.ok || !validPayload(value)) throw new Error(typeof value.error === "string" ? value.error : strings().failed);
|
|
522
|
+
if (!response.ok || !validPayload(value)) throw new Error(value && typeof value === "object" && "error" in value && typeof value.error === "string" ? value.error : strings().failed);
|
|
516
523
|
return value;
|
|
517
524
|
}
|
|
518
525
|
function manualPluginUpdateCommand(profileName, packageName, version) {
|
|
@@ -637,7 +644,7 @@
|
|
|
637
644
|
update.disabled = busy || payload?.canAutoUpdate !== true || payload.updateAvailable !== true;
|
|
638
645
|
if (payload === void 0) setMessage(() => text.checking);
|
|
639
646
|
else if (payload.latestCheckFailed) setMessage(() => text.failed, "error");
|
|
640
|
-
else if (payload.updateAvailable) setMessage(() => `${text.found}: v${payload
|
|
647
|
+
else if (payload.updateAvailable) setMessage(() => `${text.found}: v${payload?.latestVersion ?? text.unknown}`);
|
|
641
648
|
else setMessage(() => text.latest, "success");
|
|
642
649
|
if (payload !== void 0 && !payload.canAutoUpdate && payload.updateAvailable) setMessage(() => text.unavailable);
|
|
643
650
|
};
|
|
@@ -649,7 +656,8 @@
|
|
|
649
656
|
await load();
|
|
650
657
|
setBusy(false);
|
|
651
658
|
render();
|
|
652
|
-
} catch (
|
|
659
|
+
} catch (caught) {
|
|
660
|
+
const error = caught;
|
|
653
661
|
setBusy(false);
|
|
654
662
|
setMessage(error instanceof Error ? error.message : () => text.failed, "error");
|
|
655
663
|
}
|
|
@@ -664,8 +672,9 @@
|
|
|
664
672
|
payload = await requestStatus(options.endpoint, "POST", controller.signal);
|
|
665
673
|
applyControls();
|
|
666
674
|
render();
|
|
667
|
-
setMessage(() => payload
|
|
668
|
-
} catch (
|
|
675
|
+
setMessage(() => payload?.autoReload === true ? text.restarting : text.restart, "success");
|
|
676
|
+
} catch (caught) {
|
|
677
|
+
const error = caught;
|
|
669
678
|
setMessage(error instanceof Error ? error.message : () => text.failed, "error");
|
|
670
679
|
} finally {
|
|
671
680
|
updating = false;
|
|
@@ -748,7 +757,7 @@
|
|
|
748
757
|
};
|
|
749
758
|
}
|
|
750
759
|
|
|
751
|
-
// src/source-select.
|
|
760
|
+
// src/source-select.ts
|
|
752
761
|
function createSourceSelect(react, primitives) {
|
|
753
762
|
const h = react.createElement;
|
|
754
763
|
return function SourceSelect(props) {
|
|
@@ -796,12 +805,12 @@
|
|
|
796
805
|
setOpen(true);
|
|
797
806
|
}
|
|
798
807
|
}
|
|
799
|
-
}, h("span", { className: "dssm-select-value" }, props.action ? props.label : selected?.label || props.label), props.action ? null : h(primitives.IconChevronDownOutline14, { className: "dssm-select-chevron", "aria-hidden": true }))
|
|
808
|
+
}, h("span", { className: "dssm-select-value" }, props.action ? props.label : selected?.label || props.label), props.action ? null : h(primitives.IconChevronDownOutline14, { className: "dssm-select-chevron", ...{ "aria-hidden": true } }))
|
|
800
809
|
});
|
|
801
810
|
};
|
|
802
811
|
}
|
|
803
812
|
|
|
804
|
-
// src/client.
|
|
813
|
+
// src/client.ts
|
|
805
814
|
window.__ModuleLoader__.load({
|
|
806
815
|
id: "@michengai/dsh-skills-manager",
|
|
807
816
|
factory: (require2) => {
|
|
@@ -1287,14 +1296,15 @@
|
|
|
1287
1296
|
function translateError(t, payload) {
|
|
1288
1297
|
if (payload instanceof Error && !payload.code) return payload.message;
|
|
1289
1298
|
if (payload && typeof payload === "object") {
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1299
|
+
const item = payload;
|
|
1300
|
+
if (item.code) {
|
|
1301
|
+
var params = Object.assign({}, item.params || {});
|
|
1302
|
+
if (params.action) params.action = translateOrFallback(t, "action." + params.action, String(params.action));
|
|
1303
|
+
var translated = t(item.code, params);
|
|
1304
|
+
if (typeof translated === "string" && translated !== item.code) return translated;
|
|
1295
1305
|
}
|
|
1296
|
-
if (
|
|
1297
|
-
if (
|
|
1306
|
+
if (item.error !== void 0) return translateError(t, item.error);
|
|
1307
|
+
if (item.message !== void 0) return String(item.message);
|
|
1298
1308
|
}
|
|
1299
1309
|
return String(payload == null ? "" : payload);
|
|
1300
1310
|
}
|
|
@@ -1304,7 +1314,8 @@
|
|
|
1304
1314
|
error.code = "error.proto.nonJson";
|
|
1305
1315
|
error.params = { status: response.status };
|
|
1306
1316
|
throw error;
|
|
1307
|
-
}).then(function(
|
|
1317
|
+
}).then(function(value) {
|
|
1318
|
+
const payload = value;
|
|
1308
1319
|
if (!response.ok || payload.ok === false) throw payload;
|
|
1309
1320
|
return payload.data;
|
|
1310
1321
|
});
|
|
@@ -1314,7 +1325,7 @@
|
|
|
1314
1325
|
}
|
|
1315
1326
|
function isSkillEnabled(skill) {
|
|
1316
1327
|
if (skill.enabled !== void 0) return skill.enabled === true;
|
|
1317
|
-
return skill.invocationPolicyValid && skill.modelInvocable && skill.userInvocable && skill.managerEnabled !== false;
|
|
1328
|
+
return !!(skill.invocationPolicyValid && skill.modelInvocable && skill.userInvocable && skill.managerEnabled !== false);
|
|
1318
1329
|
}
|
|
1319
1330
|
function countKey(key, count) {
|
|
1320
1331
|
return key + (Number(count) === 1 ? ".one" : ".other");
|
|
@@ -1403,7 +1414,7 @@
|
|
|
1403
1414
|
return String(file && (file._dssmPath || file.webkitRelativePath || file.name) || "").replace(/\\/g, "/");
|
|
1404
1415
|
}
|
|
1405
1416
|
function inspectUploadSelection(files) {
|
|
1406
|
-
var list = Array.
|
|
1417
|
+
var list = Array.from(files || []);
|
|
1407
1418
|
if (!list.length) return null;
|
|
1408
1419
|
if (list.length > MAX_UPLOAD_ENTRIES) return { error: { code: "error.upload.tooMany", params: { limit: MAX_UPLOAD_ENTRIES } } };
|
|
1409
1420
|
var relative = list.some(function(file) {
|
|
@@ -1432,7 +1443,7 @@
|
|
|
1432
1443
|
}
|
|
1433
1444
|
function bytesToBase64(buffer) {
|
|
1434
1445
|
var bytes = new Uint8Array(buffer), binary = "", chunk = 32768;
|
|
1435
|
-
for (var i = 0; i < bytes.length; i += chunk) binary += String.fromCharCode
|
|
1446
|
+
for (var i = 0; i < bytes.length; i += chunk) binary += String.fromCharCode(...bytes.subarray(i, Math.min(i + chunk, bytes.length)));
|
|
1436
1447
|
return btoa(binary);
|
|
1437
1448
|
}
|
|
1438
1449
|
function buildUploadPayload(selection) {
|
|
@@ -1460,7 +1471,9 @@
|
|
|
1460
1471
|
if (!batch.length) {
|
|
1461
1472
|
Promise.all(children.map(function(child) {
|
|
1462
1473
|
return readDroppedEntry(child, prefix + entry.name + "/", output);
|
|
1463
|
-
})).then(
|
|
1474
|
+
})).then(function() {
|
|
1475
|
+
resolve();
|
|
1476
|
+
}, reject);
|
|
1464
1477
|
return;
|
|
1465
1478
|
}
|
|
1466
1479
|
children = children.concat(batch);
|
|
@@ -1471,10 +1484,10 @@
|
|
|
1471
1484
|
});
|
|
1472
1485
|
}
|
|
1473
1486
|
function droppedFiles(dataTransfer) {
|
|
1474
|
-
var items = Array.
|
|
1487
|
+
var items = Array.from(dataTransfer && dataTransfer.items || []), entries = items.map(function(item) {
|
|
1475
1488
|
return item.webkitGetAsEntry && item.webkitGetAsEntry();
|
|
1476
|
-
}).filter(
|
|
1477
|
-
if (!entries.length) return Promise.resolve(Array.
|
|
1489
|
+
}).filter((entry) => !!entry);
|
|
1490
|
+
if (!entries.length) return Promise.resolve(Array.from(dataTransfer && dataTransfer.files || []));
|
|
1478
1491
|
var files = [];
|
|
1479
1492
|
return Promise.all(entries.map(function(entry) {
|
|
1480
1493
|
return readDroppedEntry(entry, "", files);
|
|
@@ -1509,11 +1522,17 @@
|
|
|
1509
1522
|
function GithubMark16() {
|
|
1510
1523
|
return h("svg", { viewBox: "0 0 16 16", width: 16, height: 16, "aria-hidden": true, focusable: "false" }, h("path", { fill: "currentColor", d: "M8 0a8 8 0 0 0-2.53 15.59c.4.074.547-.173.547-.385 0-.19-.007-.693-.01-1.36-2.226.484-2.695-1.073-2.695-1.073-.364-.924-.89-1.17-.89-1.17-.726-.496.055-.486.055-.486.803.056 1.225.824 1.225.824.714 1.223 1.872.87 2.328.665.072-.517.28-.87.508-1.07-1.777-.202-3.645-.888-3.645-3.956 0-.874.31-1.588.823-2.148-.083-.202-.357-1.017.078-2.12 0 0 .672-.215 2.2.82A7.65 7.65 0 0 1 8 4.8c.68.003 1.365.092 2.004.27 1.527-1.035 2.197-.82 2.197-.82.437 1.103.162 1.918.08 2.12.513.56.822 1.274.822 2.148 0 3.076-1.872 3.752-3.654 3.95.288.248.544.735.544 1.482 0 1.07-.01 1.932-.01 2.195 0 .214.144.463.55.384A8.001 8.001 0 0 0 8 0Z" }));
|
|
1511
1524
|
}
|
|
1525
|
+
function recoverModalFocus(modal, active, body) {
|
|
1526
|
+
if (modal && active === body) modal.focus();
|
|
1527
|
+
}
|
|
1512
1528
|
function Modal(props) {
|
|
1513
1529
|
var ref = react.useRef(null);
|
|
1514
1530
|
react.useEffect(function() {
|
|
1515
1531
|
if (ref.current) ref.current.focus();
|
|
1516
1532
|
}, []);
|
|
1533
|
+
react.useLayoutEffect(function() {
|
|
1534
|
+
recoverModalFocus(ref.current, document.activeElement, document.body);
|
|
1535
|
+
});
|
|
1517
1536
|
return h("div", { className: "dssm-mask", onMouseDown: function(e) {
|
|
1518
1537
|
if (e.target === e.currentTarget) props.onClose();
|
|
1519
1538
|
} }, h("div", { ref, tabIndex: -1, className: "dssm-modal" + (props.wide ? " dssm-modal-wide" : "") + (props.className ? " " + props.className : ""), role: "dialog", "aria-modal": "true", onKeyDown: function(e) {
|
|
@@ -1542,7 +1561,7 @@
|
|
|
1542
1561
|
function SkillManagerView(props) {
|
|
1543
1562
|
var t = props.t;
|
|
1544
1563
|
function sessionApi(path, options2) {
|
|
1545
|
-
var headers = Object.
|
|
1564
|
+
var headers = Object.fromEntries(new Headers(options2?.headers).entries());
|
|
1546
1565
|
if (props.sessionId) headers["x-dsh-skills-session"] = props.sessionId;
|
|
1547
1566
|
return callApi(path, Object.assign({}, options2, { headers }));
|
|
1548
1567
|
}
|
|
@@ -1655,7 +1674,7 @@
|
|
|
1655
1674
|
}).catch(function() {
|
|
1656
1675
|
});
|
|
1657
1676
|
}
|
|
1658
|
-
var data = snapshot.data || { roots: [], projects: [], trash: [] }, allRoots = data.roots || [], projects = data.projects || [];
|
|
1677
|
+
var data = snapshot.data || { roots: [], projects: [], trash: [], warnings: [] }, allRoots = data.roots || [], projects = data.projects || [];
|
|
1659
1678
|
var activeProject = projects.length === 1 ? projects[0].root : "";
|
|
1660
1679
|
var filterKey = scope === "project" ? "project:" + activeProject : "user", filters = savedFilters[filterKey] || { query: "", source: "", status: "" }, query = filters.query, source = filters.source;
|
|
1661
1680
|
function updateFilter(key, value) {
|
|
@@ -1684,7 +1703,7 @@
|
|
|
1684
1703
|
return item.root && item.root.scope === "project" ? t("root.projectDsh") + " \xB7 " + (item.root.projectName || item.root.projectRoot) : t("root.dsh");
|
|
1685
1704
|
}
|
|
1686
1705
|
var options = [{ value: "", label: t("filter.all") }].concat(roots.map(function(root) {
|
|
1687
|
-
return { value: root.key, label: t("filter.option", { name: rootDisplayName(t, root), count: root.
|
|
1706
|
+
return { value: root.key, label: t("filter.option", { name: rootDisplayName(t, root), count: root.skills.length }) };
|
|
1688
1707
|
}));
|
|
1689
1708
|
function renderFallback(skill) {
|
|
1690
1709
|
if (!skill.fallbackTo) return null;
|
|
@@ -1797,7 +1816,7 @@
|
|
|
1797
1816
|
event.preventDefault();
|
|
1798
1817
|
var next = nextScopeTab(value, event.key);
|
|
1799
1818
|
setScope(next);
|
|
1800
|
-
document.getElementById("dssm-tab-" + next)
|
|
1819
|
+
document.getElementById("dssm-tab-" + next)?.focus();
|
|
1801
1820
|
} }, t("scope." + value), value === "trash" && (data.trash || []).length > 0 ? h("span", { className: "dssm-trash-count" }, (data.trash || []).length) : null);
|
|
1802
1821
|
}));
|
|
1803
1822
|
var skillControls = [h("div", { key: "summary", className: "dssm-summary" }, [["total", "summary.total"], ["enabled", "summary.enabled"], ["disabled", "summary.disabled"], ["invalid", "summary.invalid"]].map(function(item) {
|
|
@@ -1886,7 +1905,7 @@
|
|
|
1886
1905
|
} }, detail ? h(react.Fragment, null, h("div", { className: "dssm-detail-path" }, detail.path), h("div", { className: "dssm-detail-section" }, h("div", { className: "dssm-detail-title" }, t("detail.diagnostics")), detail.diagnostics.length ? detail.diagnostics.map(function(item, index) {
|
|
1887
1906
|
return h("div", { key: index, className: "dssm-diag" }, t(item.code, item.params || {}));
|
|
1888
1907
|
}) : h("div", { className: "dssm-note" }, t("detail.noIssues"))), h("div", { className: "dssm-detail-section" }, h("div", { className: "dssm-detail-title" }, t("detail.body")), h("pre", { className: "dssm-code" }, detail.body || "")), h("div", { className: "dssm-detail-section" }, h("div", { className: "dssm-detail-title" }, t("detail.frontmatter")), h("pre", { className: "dssm-code" }, JSON.stringify(detail.frontmatter, null, 2)))) : h("div", { className: "dssm-empty" }, t("loading"))));
|
|
1889
|
-
if (modal && modal.type === "trash-confirm") content.push(h(Modal, { key: "trash-confirm", title: t("confirm.trash.title"), closeLabel: t("btn.close"), onClose: function() {
|
|
1908
|
+
if (modal && typeof modal === "object" && modal.type === "trash-confirm") content.push(h(Modal, { key: "trash-confirm", title: t("confirm.trash.title"), closeLabel: t("btn.close"), onClose: function() {
|
|
1890
1909
|
setModal(null);
|
|
1891
1910
|
} }, h("p", { className: "dssm-desc" }, t("confirm.trash.desc", { name: modal.name })), h("div", { className: "dssm-modal-actions" }, h("button", { className: "dssm-btn dssm-btn-secondary", onClick: function() {
|
|
1892
1911
|
setModal(null);
|
|
@@ -1896,7 +1915,7 @@
|
|
|
1896
1915
|
}).catch(function() {
|
|
1897
1916
|
});
|
|
1898
1917
|
} }, t("btn.trash")))));
|
|
1899
|
-
if (modal && modal.type === "delete-confirm") content.push(h(Modal, { key: "delete-confirm", title: t("confirm.delete.title"), closeLabel: t("btn.close"), onClose: function() {
|
|
1918
|
+
if (modal && typeof modal === "object" && modal.type === "delete-confirm") content.push(h(Modal, { key: "delete-confirm", title: t("confirm.delete.title"), closeLabel: t("btn.close"), onClose: function() {
|
|
1900
1919
|
setModal(null);
|
|
1901
1920
|
} }, h("p", { className: "dssm-desc" }, t("confirm.delete.desc", { name: modal.name })), h("div", { className: "dssm-modal-actions" }, h("button", { className: "dssm-btn dssm-btn-secondary", onClick: function() {
|
|
1902
1921
|
setModal(null);
|
|
@@ -1940,6 +1959,7 @@
|
|
|
1940
1959
|
module.exports.nextScopeTab = nextScopeTab;
|
|
1941
1960
|
module.exports.currentSessionId = currentSessionId;
|
|
1942
1961
|
module.exports.canToggleSource = canToggleSource;
|
|
1962
|
+
module.exports.recoverModalFocus = recoverModalFocus;
|
|
1943
1963
|
module.exports.trapModalFocus = trapModalFocus;
|
|
1944
1964
|
module.exports.handleModalEscape = handleModalEscape;
|
|
1945
1965
|
module.exports.inspectUploadSelection = inspectUploadSelection;
|
package/lib/core.js
CHANGED
|
@@ -398,7 +398,8 @@ async function browseDirectories(inputPath) {
|
|
|
398
398
|
try {
|
|
399
399
|
canonical = await fs.realpath(target);
|
|
400
400
|
directory = await fs.stat(canonical);
|
|
401
|
-
} catch (
|
|
401
|
+
} catch (caught) {
|
|
402
|
+
const error = caught;
|
|
402
403
|
return {
|
|
403
404
|
ok: false,
|
|
404
405
|
error: `\u65E0\u6CD5\u8BFB\u53D6\u76EE\u5F55: ${target}`,
|
|
@@ -437,7 +438,8 @@ async function browseDirectories(inputPath) {
|
|
|
437
438
|
hidden: item.name.startsWith(".")
|
|
438
439
|
});
|
|
439
440
|
}
|
|
440
|
-
} catch (
|
|
441
|
+
} catch (caught) {
|
|
442
|
+
const error = caught;
|
|
441
443
|
return {
|
|
442
444
|
ok: false,
|
|
443
445
|
error: `\u65E0\u6CD5\u8BFB\u53D6\u76EE\u5F55: ${canonical}`,
|
|
@@ -671,8 +673,9 @@ async function renameWithRetry(source, destination, options = {}) {
|
|
|
671
673
|
for (let attempt = 1; ; attempt++) {
|
|
672
674
|
try {
|
|
673
675
|
return await rename(source, destination);
|
|
674
|
-
} catch (
|
|
675
|
-
|
|
676
|
+
} catch (caught) {
|
|
677
|
+
const error = caught;
|
|
678
|
+
if (!TRANSIENT_RENAME_CODES.has(error && error.code || "") || attempt >= maxAttempts)
|
|
676
679
|
throw error;
|
|
677
680
|
await new Promise(
|
|
678
681
|
(resolvePromise) => setTimeout(resolvePromise, delayMs * attempt)
|
|
@@ -691,7 +694,8 @@ async function movePathWithFallback(source, destination, options = {}) {
|
|
|
691
694
|
try {
|
|
692
695
|
await renameWithRetry(source, destination, options);
|
|
693
696
|
return { copied: false, cleanupError: null };
|
|
694
|
-
} catch (
|
|
697
|
+
} catch (caught) {
|
|
698
|
+
const error = caught;
|
|
695
699
|
if (!error || error.code !== "EXDEV") throw error;
|
|
696
700
|
}
|
|
697
701
|
const quarantine = join(
|
|
@@ -707,14 +711,16 @@ async function movePathWithFallback(source, destination, options = {}) {
|
|
|
707
711
|
verbatimSymlinks: true
|
|
708
712
|
});
|
|
709
713
|
await renameWithRetry(source, quarantine, options);
|
|
710
|
-
} catch (
|
|
714
|
+
} catch (caught) {
|
|
715
|
+
const error = caught;
|
|
711
716
|
await removeMovedPath(destination).catch(() => void 0);
|
|
712
717
|
throw error;
|
|
713
718
|
}
|
|
714
719
|
let cleanupError = null;
|
|
715
720
|
try {
|
|
716
721
|
await removeMovedPath(quarantine);
|
|
717
|
-
} catch (
|
|
722
|
+
} catch (caught) {
|
|
723
|
+
const error = caught;
|
|
718
724
|
cleanupError = error;
|
|
719
725
|
}
|
|
720
726
|
return { copied: true, cleanupError, quarantine };
|
|
@@ -727,7 +733,8 @@ async function writeFileAtomically(path, content) {
|
|
|
727
733
|
try {
|
|
728
734
|
await fs.writeFile(temp, content, "utf8");
|
|
729
735
|
await fs.rename(temp, path);
|
|
730
|
-
} catch (
|
|
736
|
+
} catch (caught) {
|
|
737
|
+
const error = caught;
|
|
731
738
|
await fs.rm(temp, { force: true }).catch(() => void 0);
|
|
732
739
|
throw error;
|
|
733
740
|
}
|
|
@@ -746,11 +753,11 @@ async function resolveEntry(root, name) {
|
|
|
746
753
|
(entry) => entry.name === name
|
|
747
754
|
) || null;
|
|
748
755
|
}
|
|
749
|
-
|
|
756
|
+
const directory = definition ? definition.path : root;
|
|
750
757
|
try {
|
|
751
|
-
const bundlePath = entryPath(
|
|
758
|
+
const bundlePath = entryPath(directory, name);
|
|
752
759
|
if (bundlePath === null) return null;
|
|
753
|
-
const rootPath = resolve(
|
|
760
|
+
const rootPath = resolve(directory);
|
|
754
761
|
const rootStat = await lstatOrNull(rootPath);
|
|
755
762
|
if (!rootStat || !rootStat.isDirectory() || rootStat.isSymbolicLink())
|
|
756
763
|
return null;
|
|
@@ -856,38 +863,38 @@ async function scanEntries(root, options = {}) {
|
|
|
856
863
|
}
|
|
857
864
|
return { ...discovered, entries: entries2 };
|
|
858
865
|
}
|
|
859
|
-
|
|
860
|
-
const rootStat = await lstatOrNull(resolve(
|
|
866
|
+
const directory = definition ? definition.path : root;
|
|
867
|
+
const rootStat = await lstatOrNull(resolve(directory));
|
|
861
868
|
if (!rootStat || !rootStat.isDirectory() || rootStat.isSymbolicLink())
|
|
862
869
|
return { exists: false, entries: [] };
|
|
863
870
|
let items;
|
|
864
871
|
try {
|
|
865
|
-
items = await fs.readdir(
|
|
872
|
+
items = await fs.readdir(directory, { withFileTypes: true });
|
|
866
873
|
} catch {
|
|
867
874
|
return { exists: false, entries: [] };
|
|
868
875
|
}
|
|
869
876
|
const byName = /* @__PURE__ */ new Map();
|
|
870
|
-
const rootReal = await resolvedPath(
|
|
877
|
+
const rootReal = await resolvedPath(directory);
|
|
871
878
|
for (const it of items) {
|
|
872
879
|
try {
|
|
873
880
|
if (it.isSymbolicLink()) continue;
|
|
874
|
-
if (it.isDirectory() && entryPath(
|
|
875
|
-
const docPath = join(
|
|
881
|
+
if (it.isDirectory() && entryPath(directory, it.name) !== null) {
|
|
882
|
+
const docPath = join(directory, it.name, "SKILL.md");
|
|
876
883
|
const st = await lstatOrNull(docPath);
|
|
877
884
|
if (!st || !st.isFile() || st.isSymbolicLink() || !await isInsideResolvedRoot(rootReal, docPath))
|
|
878
885
|
continue;
|
|
879
886
|
const doc = options.metadataOnly ? { map: {}, hasFrontmatter: false } : parseSkillDoc(await fs.readFile(docPath, "utf8"));
|
|
880
887
|
byName.set(it.name, {
|
|
881
888
|
...entryOf(it.name, "bundle", docPath, doc),
|
|
882
|
-
entryPath: join(
|
|
889
|
+
entryPath: join(directory, it.name),
|
|
883
890
|
realDocPath: await fs.realpath(docPath),
|
|
884
|
-
realEntryPath: await fs.realpath(join(
|
|
891
|
+
realEntryPath: await fs.realpath(join(directory, it.name)),
|
|
885
892
|
linked: false
|
|
886
893
|
});
|
|
887
|
-
} else if (it.isFile() && it.name.toLowerCase().endsWith(".md") && it.name.toLowerCase() !== "skill.md" && entryPath(
|
|
894
|
+
} else if (it.isFile() && it.name.toLowerCase().endsWith(".md") && it.name.toLowerCase() !== "skill.md" && entryPath(directory, it.name.slice(0, -3)) !== null) {
|
|
888
895
|
const skillName = it.name.slice(0, -3);
|
|
889
896
|
if (byName.has(skillName)) continue;
|
|
890
|
-
const docPath = join(
|
|
897
|
+
const docPath = join(directory, it.name);
|
|
891
898
|
if (!await isInsideResolvedRoot(rootReal, docPath)) continue;
|
|
892
899
|
const doc = options.metadataOnly ? { map: {}, hasFrontmatter: false } : parseSkillDoc(await fs.readFile(docPath, "utf8"));
|
|
893
900
|
const realDocPath = await fs.realpath(docPath);
|
|
@@ -909,7 +916,7 @@ async function scanEntries(root, options = {}) {
|
|
|
909
916
|
}
|
|
910
917
|
async function scanDeduplicatedRoots(roots = userRoots(), options = {}) {
|
|
911
918
|
const results = await Promise.all(
|
|
912
|
-
roots.map(async (root) => [root, await scanEntries(root,
|
|
919
|
+
roots.map(async (root) => [root, options.metadataOnly ? await scanEntries(root, { metadataOnly: true }) : await scanEntries(root)])
|
|
913
920
|
);
|
|
914
921
|
const scans = /* @__PURE__ */ new Map();
|
|
915
922
|
const groups = /* @__PURE__ */ new Map();
|
|
@@ -1068,7 +1075,8 @@ async function readManagerState() {
|
|
|
1068
1075
|
warning: null,
|
|
1069
1076
|
writable: true
|
|
1070
1077
|
};
|
|
1071
|
-
} catch (
|
|
1078
|
+
} catch (caught) {
|
|
1079
|
+
const error = caught;
|
|
1072
1080
|
if (error && error.code === "ENOENT")
|
|
1073
1081
|
return { state: defaultManagerState(), warning: null, writable: true };
|
|
1074
1082
|
return {
|
|
@@ -1293,8 +1301,9 @@ async function publishTrashStage(stage, finalPath, metadata, renameOptions) {
|
|
|
1293
1301
|
try {
|
|
1294
1302
|
await renameWithRetry(stage, finalPath, renameOptions);
|
|
1295
1303
|
return { fallback: false, cleanupError: null };
|
|
1296
|
-
} catch (
|
|
1297
|
-
|
|
1304
|
+
} catch (caught) {
|
|
1305
|
+
const error = caught;
|
|
1306
|
+
if (!TRANSIENT_RENAME_CODES.has(error && error.code || "")) throw error;
|
|
1298
1307
|
}
|
|
1299
1308
|
await fs.mkdir(finalPath);
|
|
1300
1309
|
try {
|
|
@@ -1312,14 +1321,16 @@ async function publishTrashStage(stage, finalPath, metadata, renameOptions) {
|
|
|
1312
1321
|
`,
|
|
1313
1322
|
"utf8"
|
|
1314
1323
|
);
|
|
1315
|
-
} catch (
|
|
1324
|
+
} catch (caught) {
|
|
1325
|
+
const error = caught;
|
|
1316
1326
|
await fs.rm(finalPath, { recursive: true, force: true }).catch(() => void 0);
|
|
1317
1327
|
throw error;
|
|
1318
1328
|
}
|
|
1319
1329
|
let cleanupError = null;
|
|
1320
1330
|
try {
|
|
1321
1331
|
await fs.rm(stage, { recursive: true, force: true });
|
|
1322
|
-
} catch (
|
|
1332
|
+
} catch (caught) {
|
|
1333
|
+
const error = caught;
|
|
1323
1334
|
cleanupError = error;
|
|
1324
1335
|
}
|
|
1325
1336
|
return { fallback: true, cleanupError };
|
|
@@ -1423,7 +1434,8 @@ async function deleteSkill(root, name, log, options = {}) {
|
|
|
1423
1434
|
);
|
|
1424
1435
|
if (log) log("trash", `\u79FB\u5230\u56DE\u6536\u7AD9 ${name} -> ${finalPath}`);
|
|
1425
1436
|
return { id, name, deletedAt: metadata.deletedAt, root: metadata.root };
|
|
1426
|
-
} catch (
|
|
1437
|
+
} catch (caught) {
|
|
1438
|
+
const error = caught;
|
|
1427
1439
|
const rollbackFailures = [];
|
|
1428
1440
|
for (const item of moved.reverse()) {
|
|
1429
1441
|
try {
|
|
@@ -1432,7 +1444,8 @@ async function deleteSkill(root, name, log, options = {}) {
|
|
|
1432
1444
|
item.path,
|
|
1433
1445
|
options.renameOptions
|
|
1434
1446
|
);
|
|
1435
|
-
} catch (
|
|
1447
|
+
} catch (caught2) {
|
|
1448
|
+
const rollbackError = caught2;
|
|
1436
1449
|
rollbackFailures.push({
|
|
1437
1450
|
path: item.destination,
|
|
1438
1451
|
error: String(
|
|
@@ -1506,7 +1519,8 @@ async function restoreTrash(id, log, options = {}) {
|
|
|
1506
1519
|
await fs.rm(itemRoot, { recursive: true, force: true });
|
|
1507
1520
|
if (log) log("restore", `\u4ECE\u56DE\u6536\u7AD9\u6062\u590D ${metadata.name} -> ${root}`);
|
|
1508
1521
|
return { id, name: metadata.name, root: trashRootMetadata(definition) };
|
|
1509
|
-
} catch (
|
|
1522
|
+
} catch (caught) {
|
|
1523
|
+
const error = caught;
|
|
1510
1524
|
for (const item of moved.reverse())
|
|
1511
1525
|
await movePathWithFallback(
|
|
1512
1526
|
item.destination,
|
|
@@ -1680,7 +1694,8 @@ async function preflightCandidates(pending, conflicts, failed) {
|
|
|
1680
1694
|
const candidate = group[i];
|
|
1681
1695
|
try {
|
|
1682
1696
|
await assertNoSymbolicLinks(candidate.source);
|
|
1683
|
-
} catch (
|
|
1697
|
+
} catch (caught) {
|
|
1698
|
+
const error = caught;
|
|
1684
1699
|
failed.push(
|
|
1685
1700
|
attachCode(
|
|
1686
1701
|
{
|
|
@@ -1704,7 +1719,8 @@ async function copyToTemporary(source, target, isDir) {
|
|
|
1704
1719
|
else await fs.copyFile(source, temp);
|
|
1705
1720
|
await assertNoSymbolicLinks(temp);
|
|
1706
1721
|
return temp;
|
|
1707
|
-
} catch (
|
|
1722
|
+
} catch (caught) {
|
|
1723
|
+
const error = caught;
|
|
1708
1724
|
await fs.rm(temp, { recursive: true, force: true }).catch(() => void 0);
|
|
1709
1725
|
throw error;
|
|
1710
1726
|
}
|
|
@@ -1719,13 +1735,15 @@ async function replaceWithCopy(source, dest, isDir, existing = []) {
|
|
|
1719
1735
|
backups.push({ path, backup });
|
|
1720
1736
|
}
|
|
1721
1737
|
await fs.rename(stage, dest);
|
|
1722
|
-
} catch (
|
|
1738
|
+
} catch (caught) {
|
|
1739
|
+
const error = caught;
|
|
1723
1740
|
await fs.rm(stage, { recursive: true, force: true }).catch(() => void 0);
|
|
1724
1741
|
const rollbackFailures = [];
|
|
1725
1742
|
for (const item of backups.reverse()) {
|
|
1726
1743
|
try {
|
|
1727
1744
|
await fs.rename(item.backup, item.path);
|
|
1728
|
-
} catch (
|
|
1745
|
+
} catch (caught2) {
|
|
1746
|
+
const rollbackError = caught2;
|
|
1729
1747
|
rollbackFailures.push(item.backup);
|
|
1730
1748
|
}
|
|
1731
1749
|
}
|
|
@@ -1743,7 +1761,8 @@ async function replaceWithCopy(source, dest, isDir, existing = []) {
|
|
|
1743
1761
|
for (const item of backups) {
|
|
1744
1762
|
try {
|
|
1745
1763
|
await fs.rm(item.backup, { recursive: true, force: true });
|
|
1746
|
-
} catch (
|
|
1764
|
+
} catch (caught) {
|
|
1765
|
+
const error = caught;
|
|
1747
1766
|
warnings.push({
|
|
1748
1767
|
code: "warning.backupUncleaned",
|
|
1749
1768
|
params: {
|
|
@@ -1787,7 +1806,8 @@ async function importSkill(source, log, options = {}) {
|
|
|
1787
1806
|
} else {
|
|
1788
1807
|
try {
|
|
1789
1808
|
candidates = await collectCandidates(source);
|
|
1790
|
-
} catch (
|
|
1809
|
+
} catch (caught) {
|
|
1810
|
+
const error = caught;
|
|
1791
1811
|
return attachCode(
|
|
1792
1812
|
{
|
|
1793
1813
|
ok: false,
|
|
@@ -1883,7 +1903,8 @@ async function importSkill(source, log, options = {}) {
|
|
|
1883
1903
|
const warnings = await replaceWithCopy(p.source, p.dest, p.isDir);
|
|
1884
1904
|
imported.push({ name: p.name, overwritten: false, warnings });
|
|
1885
1905
|
if (log) log("import", `\u5BFC\u5165 ${p.source} -> ${p.dest}`);
|
|
1886
|
-
} catch (
|
|
1906
|
+
} catch (caught) {
|
|
1907
|
+
const e = caught;
|
|
1887
1908
|
failed.push(
|
|
1888
1909
|
attachCode(
|
|
1889
1910
|
{ source: p.source, error: String(e && e.message ? e.message : e) },
|
|
@@ -1904,7 +1925,8 @@ async function importSkill(source, log, options = {}) {
|
|
|
1904
1925
|
);
|
|
1905
1926
|
imported.push({ name: c.name, overwritten: true, warnings });
|
|
1906
1927
|
if (log) log("import-overwrite", `\u8986\u76D6\u5BFC\u5165 ${c.source} -> ${dest}`);
|
|
1907
|
-
} catch (
|
|
1928
|
+
} catch (caught) {
|
|
1929
|
+
const e = caught;
|
|
1908
1930
|
failed.push(
|
|
1909
1931
|
attachCode(
|
|
1910
1932
|
{ source: c.source, error: String(e && e.message ? e.message : e) },
|
|
@@ -2050,7 +2072,8 @@ async function writeUploadedZip(contentRoot, encoded) {
|
|
|
2050
2072
|
return !normalized.directory;
|
|
2051
2073
|
}
|
|
2052
2074
|
});
|
|
2053
|
-
} catch (
|
|
2075
|
+
} catch (caught) {
|
|
2076
|
+
const error = caught;
|
|
2054
2077
|
if (error && /^error\./.test(String(error.code || ""))) throw error;
|
|
2055
2078
|
throw codedError(
|
|
2056
2079
|
`ZIP \u65E0\u6CD5\u89E3\u538B: ${String(error && error.message ? error.message : error)}`,
|
|
@@ -2095,7 +2118,8 @@ async function importUploadedSkill(input, log, options = {}) {
|
|
|
2095
2118
|
await fs.mkdir(sessionRoot, { recursive: true });
|
|
2096
2119
|
const source = await prepareUploadedSource(sessionRoot, input || {});
|
|
2097
2120
|
return await importSkill(source, log, options);
|
|
2098
|
-
} catch (
|
|
2121
|
+
} catch (caught) {
|
|
2122
|
+
const error = caught;
|
|
2099
2123
|
return uploadError(error);
|
|
2100
2124
|
} finally {
|
|
2101
2125
|
await fs.rm(sessionRoot, { recursive: true, force: true }).catch(() => void 0);
|
|
@@ -2156,7 +2180,8 @@ ${body}
|
|
|
2156
2180
|
`;
|
|
2157
2181
|
await fs.writeFile(join(stage, "SKILL.md"), content, "utf8");
|
|
2158
2182
|
await fs.rename(stage, target);
|
|
2159
|
-
} catch (
|
|
2183
|
+
} catch (caught) {
|
|
2184
|
+
const error = caught;
|
|
2160
2185
|
await fs.rm(stage, { recursive: true, force: true }).catch(() => void 0);
|
|
2161
2186
|
throw error;
|
|
2162
2187
|
}
|
|
@@ -2363,6 +2388,7 @@ async function state(options = {}) {
|
|
|
2363
2388
|
const result = {
|
|
2364
2389
|
roots: [],
|
|
2365
2390
|
projects: [],
|
|
2391
|
+
summary: { total: 0, enabled: 0, disabled: 0, issues: 0 },
|
|
2366
2392
|
trash,
|
|
2367
2393
|
warnings: [
|
|
2368
2394
|
...policyResult.warning ? [policyResult.warning] : [],
|
package/lib/index.js
CHANGED
|
@@ -73,7 +73,8 @@ function readBody(req, limit = 1 << 20) {
|
|
|
73
73
|
const body = raw ? JSON.parse(raw) : {};
|
|
74
74
|
settled = true;
|
|
75
75
|
resolve(body);
|
|
76
|
-
} catch (
|
|
76
|
+
} catch (caught) {
|
|
77
|
+
const e = caught;
|
|
77
78
|
const error = new Error(`invalid JSON body: ${e.message}`);
|
|
78
79
|
error.statusCode = 400;
|
|
79
80
|
error.code = "error.proto.invalidJson";
|
|
@@ -142,7 +143,7 @@ function validateMutationRequest(req, trustedHosts) {
|
|
|
142
143
|
if (contentType !== "application/json") return { statusCode: 415, code: "error.proto.contentType", error: "content-type must be application/json" };
|
|
143
144
|
return null;
|
|
144
145
|
}
|
|
145
|
-
function json(res, code, payload, headOnly) {
|
|
146
|
+
function json(res, code, payload, headOnly = false) {
|
|
146
147
|
if (!res || res.writableEnded || res.destroyed) return;
|
|
147
148
|
res.once("error", () => {
|
|
148
149
|
});
|
|
@@ -156,9 +157,9 @@ function json(res, code, payload, headOnly) {
|
|
|
156
157
|
} catch {
|
|
157
158
|
}
|
|
158
159
|
}
|
|
159
|
-
function run(res, task, afterSuccess, headOnly) {
|
|
160
|
+
function run(res, task, afterSuccess, headOnly = false) {
|
|
160
161
|
return Promise.resolve().then(task).then((r) => {
|
|
161
|
-
if (r && r.ok === false) json(res, 400, r, headOnly);
|
|
162
|
+
if (r && typeof r === "object" && "ok" in r && r.ok === false) json(res, 400, r, headOnly);
|
|
162
163
|
else {
|
|
163
164
|
try {
|
|
164
165
|
if (afterSuccess) afterSuccess();
|
|
@@ -324,7 +325,8 @@ function apply(ctx) {
|
|
|
324
325
|
try {
|
|
325
326
|
const sources = await repositories.sources();
|
|
326
327
|
for (const root of snapshot.roots) if (root.key === "dsh") for (const skill of root.skills || []) skill.installSource = sources[skill.name] || null;
|
|
327
|
-
} catch (
|
|
328
|
+
} catch (caught) {
|
|
329
|
+
const error = caught;
|
|
328
330
|
snapshot.warnings.push({ code: "error.repo.state", error: error.message });
|
|
329
331
|
}
|
|
330
332
|
return snapshot;
|
|
@@ -415,7 +417,8 @@ function apply(ctx) {
|
|
|
415
417
|
json(res, 404, { ok: false, code: "error.proto.unknownAction", error: `unknown action: ${path}` });
|
|
416
418
|
}
|
|
417
419
|
});
|
|
418
|
-
} catch (
|
|
420
|
+
} catch (caught) {
|
|
421
|
+
const e = caught;
|
|
419
422
|
json(res, Number.isInteger(e && e.statusCode) ? e.statusCode : 500, {
|
|
420
423
|
ok: false,
|
|
421
424
|
...e && e.code ? { code: e.code } : {},
|
package/lib/plugin-updater.js
CHANGED
|
@@ -240,7 +240,8 @@ function registerPluginUpdater(ctx, options) {
|
|
|
240
240
|
if (notifyParent) setTimeout(() => {
|
|
241
241
|
process.send?.(PLUGIN_UPDATE_IPC);
|
|
242
242
|
}, 150).unref?.();
|
|
243
|
-
} catch (
|
|
243
|
+
} catch (caught) {
|
|
244
|
+
const error = caught;
|
|
244
245
|
ctx.logger.warn(`plugin updater failed: ${String(error)}`);
|
|
245
246
|
json(response, 503, { error: publicError(error) });
|
|
246
247
|
}
|
|
@@ -34,7 +34,8 @@ async function discoverReadonlyEntries(root) {
|
|
|
34
34
|
let docStat;
|
|
35
35
|
try {
|
|
36
36
|
docStat = await fs.lstat(docPath);
|
|
37
|
-
} catch (
|
|
37
|
+
} catch (caught) {
|
|
38
|
+
const error = caught;
|
|
38
39
|
if (error.code !== "ENOENT") throw error;
|
|
39
40
|
}
|
|
40
41
|
if (docStat?.isFile() && !docStat.isSymbolicLink()) {
|
package/lib/repositories.js
CHANGED
|
@@ -16,7 +16,8 @@ function safePath(value, allowEmpty = false) {
|
|
|
16
16
|
if (parts.length > 64 || parts.some((p) => !p || p === "." || p === ".." || /[\\:<>"|?*\x00-\x1f]/.test(p) || /[. ]$/.test(p) || /^(con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\.|$)/i.test(p))) throw failure("\u4ED3\u5E93\u8DEF\u5F84\u4E0D\u5B89\u5168");
|
|
17
17
|
return value;
|
|
18
18
|
}
|
|
19
|
-
function validateStoredState(
|
|
19
|
+
function validateStoredState(input) {
|
|
20
|
+
const value = input;
|
|
20
21
|
if (!value || value.version !== 1 || !Array.isArray(value.repositories) || value.repositories.length > 30 || !Array.isArray(value.installs)) throw failure("\u4ED3\u5E93\u72B6\u6001\u683C\u5F0F\u65E0\u6548");
|
|
21
22
|
for (const repo of value.repositories) {
|
|
22
23
|
if (!repo || typeof repo.id !== "string" || !Array.isArray(repo.skills) || repo.skills.length > 500) throw failure("\u4ED3\u5E93\u72B6\u6001\u683C\u5F0F\u65E0\u6548");
|
|
@@ -28,7 +29,7 @@ function validateStoredState(value) {
|
|
|
28
29
|
}
|
|
29
30
|
}
|
|
30
31
|
for (const record of value.installs) {
|
|
31
|
-
if (!record || typeof record.id !== "string" || typeof record.complete !== "boolean" || !Array.isArray(record.files) || !record.files.length || record.files.length > 1e3 || !/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/.test(record.commit)) throw failure("\u4ED3\u5E93\u5B89\u88C5\u8BB0\u5F55\u65E0\u6548");
|
|
32
|
+
if (!record || typeof record.id !== "string" || typeof record.complete !== "boolean" || !Array.isArray(record.files) || !record.files.length || record.files.length > 1e3 || !/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/.test(record.commit || "")) throw failure("\u4ED3\u5E93\u5B89\u88C5\u8BB0\u5F55\u65E0\u6548");
|
|
32
33
|
safePath(record.path, true);
|
|
33
34
|
safePath(record.name);
|
|
34
35
|
if (record.name.includes("/")) throw failure("\u4ED3\u5E93\u5B89\u88C5\u540D\u79F0\u65E0\u6548");
|
|
@@ -122,7 +123,8 @@ function createRepositoryManager({ fetchImpl = globalThis.fetch, log } = {}) {
|
|
|
122
123
|
const value = JSON.parse(await fs.readFile(file, "utf8"));
|
|
123
124
|
validateStoredState(value);
|
|
124
125
|
return value;
|
|
125
|
-
} catch (
|
|
126
|
+
} catch (caught) {
|
|
127
|
+
const error = caught;
|
|
126
128
|
if (error.code === "ENOENT") return { version: 1, repositories: [], installs: [] };
|
|
127
129
|
throw failure("\u4ED3\u5E93\u72B6\u6001\u6587\u4EF6\u635F\u574F\u6216\u65E0\u6CD5\u8BFB\u53D6\uFF0C\u8BF7\u4FDD\u7559\u6587\u4EF6\u5E76\u4FEE\u590D\u540E\u91CD\u8BD5", "error.repo.state");
|
|
128
130
|
}
|
|
@@ -151,7 +153,8 @@ function createRepositoryManager({ fetchImpl = globalThis.fetch, log } = {}) {
|
|
|
151
153
|
try {
|
|
152
154
|
const response = await fetchImpl(url, { redirect: "error", signal: AbortSignal.timeout(6e4), headers: { Accept: "application/zip", "User-Agent": "dsh-skills-manager" } });
|
|
153
155
|
return await readResponse(response, LIMIT);
|
|
154
|
-
} catch (
|
|
156
|
+
} catch (caught) {
|
|
157
|
+
const error = caught;
|
|
155
158
|
if (typeof error.code === "string" && error.code.startsWith("error.repo.")) throw error;
|
|
156
159
|
throw failure("\u4ED3\u5E93\u7F51\u7EDC\u8BF7\u6C42\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u7F51\u7EDC\u540E\u91CD\u8BD5", "error.repo.network");
|
|
157
160
|
}
|
|
@@ -199,10 +202,12 @@ function createRepositoryManager({ fetchImpl = globalThis.fetch, log } = {}) {
|
|
|
199
202
|
for (let i = 0; i < refs.length; i++) {
|
|
200
203
|
try {
|
|
201
204
|
return await request(`https://codeload.github.com/${repo.owner}/${repo.name}/zip/${refs[i].split("/").map(encodeURIComponent).join("/")}`);
|
|
202
|
-
} catch (
|
|
205
|
+
} catch (caught) {
|
|
206
|
+
const error = caught;
|
|
203
207
|
if (error.httpStatus !== 404 || i === refs.length - 1) throw error;
|
|
204
208
|
}
|
|
205
209
|
}
|
|
210
|
+
throw failure("\u4ED3\u5E93\u5206\u652F\u4E0D\u5B58\u5728");
|
|
206
211
|
}
|
|
207
212
|
async function matches(record) {
|
|
208
213
|
try {
|
|
@@ -289,14 +294,16 @@ function createRepositoryManager({ fetchImpl = globalThis.fetch, log } = {}) {
|
|
|
289
294
|
}
|
|
290
295
|
const snapshot = await cacheArchive(repo, bytes);
|
|
291
296
|
Object.assign(repo, { skills, commit: snapshot, refreshedAt: (/* @__PURE__ */ new Date()).toISOString(), error: null });
|
|
292
|
-
} catch (
|
|
297
|
+
} catch (caught) {
|
|
298
|
+
const error = caught;
|
|
293
299
|
repo.error = { code: error.code || "error.repo.invalid", error: error.message };
|
|
294
300
|
}
|
|
295
301
|
await write(data);
|
|
296
302
|
if (!repo.error) {
|
|
297
303
|
try {
|
|
298
304
|
await pruneCache(repo);
|
|
299
|
-
} catch (
|
|
305
|
+
} catch (caught) {
|
|
306
|
+
const error = caught;
|
|
300
307
|
if (log) await log("repository.cache.cleanup.failed", { repository: repo.id, error: error.message });
|
|
301
308
|
else console.warn("\u4ED3\u5E93\u65E7\u7F13\u5B58\u672A\u6E05\u7406\uFF1A" + error.message);
|
|
302
309
|
}
|
|
@@ -326,7 +333,8 @@ function createRepositoryManager({ fetchImpl = globalThis.fetch, log } = {}) {
|
|
|
326
333
|
try {
|
|
327
334
|
const result = await importUploadedSkill({ name: skill.name, entries }, log, { conflict: "skip" });
|
|
328
335
|
if (result.ok === false || !result.imported?.length) throw failure(result.error || "\u6280\u80FD\u5B89\u88C5\u5931\u8D25\u6216\u9047\u5230\u540C\u540D\u51B2\u7A81", "error.repo.conflict");
|
|
329
|
-
} catch (
|
|
336
|
+
} catch (caught) {
|
|
337
|
+
const error = caught;
|
|
330
338
|
data.installs = data.installs.filter((item) => item !== record);
|
|
331
339
|
try {
|
|
332
340
|
await write(data);
|
|
@@ -101,7 +101,8 @@ function createRepositoryUpdater({ read, write, repository, download, serialize,
|
|
|
101
101
|
replaced = true;
|
|
102
102
|
data.installs[data.installs.indexOf(record)] = nextRecord;
|
|
103
103
|
await write(data);
|
|
104
|
-
} catch (
|
|
104
|
+
} catch (caught) {
|
|
105
|
+
const error = caught;
|
|
105
106
|
if (moved) {
|
|
106
107
|
try {
|
|
107
108
|
if (replaced) await fs.rename(target, stage);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@michengai/dsh-skills-manager",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "DSH Skills Manager — 在 DeepSeek Harness 中统一加载并安全管理本机 Agent Skills · Load and safely manage local Agent Skills in DSH",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -40,11 +40,13 @@
|
|
|
40
40
|
},
|
|
41
41
|
"scripts": {
|
|
42
42
|
"build": "node scripts/build.mjs",
|
|
43
|
-
"test": "npm run build && node test/build-test.mjs && node test/repositories.test.mjs && node test/repository-ui.test.mjs && node test/theme-select.test.mjs && node test/repository-routes.test.mjs && node test/core-test.mjs && node test/readonly-discovery.test.mjs && node test/project-sources.test.mjs && node test/locale-test.mjs && node test/plugin-update-locale.test.mjs && node test/client-scope.test.mjs && node test/compatibility-options-test.mjs",
|
|
44
|
-
"verify": "node scripts/sync-hosts.mjs && npm test && npm run pack:check && node
|
|
43
|
+
"test": "npm run typecheck && npm run build && node test/build-test.mjs && node test/repositories.test.mjs && node test/repository-ui.test.mjs && node test/theme-select.test.mjs && node test/repository-routes.test.mjs && node test/core-test.mjs && node test/readonly-discovery.test.mjs && node test/project-sources.test.mjs && node test/locale-test.mjs && node --test test/plugin-updater.test.mjs && node test/plugin-update-locale.test.mjs && node test/client-scope.test.mjs && node test/compatibility-options-test.mjs",
|
|
44
|
+
"verify": "node scripts/sync-hosts.mjs && npm test && npm run pack:check && node test/pack-test.mjs",
|
|
45
45
|
"pack:check": "npm pack --dry-run --json",
|
|
46
|
+
"prepack": "npm run typecheck && npm run build",
|
|
46
47
|
"prepublishOnly": "npm run verify",
|
|
47
|
-
"test:compat": "node scripts/test-host-compatibility.mjs"
|
|
48
|
+
"test:compat": "node scripts/test-host-compatibility.mjs",
|
|
49
|
+
"typecheck": "tsc --noEmit"
|
|
48
50
|
},
|
|
49
51
|
"publishConfig": {
|
|
50
52
|
"registry": "https://registry.npmjs.org/",
|
|
@@ -68,12 +70,15 @@
|
|
|
68
70
|
"@deepseek-ai/dsh-client-ui-primitives": "0.1.6-alpha.2",
|
|
69
71
|
"@deepseek-ai/dsh-client-ui-slots": "0.1.6-alpha.2",
|
|
70
72
|
"@deepseek-ai/dsh-host-webserver": "0.1.6-alpha.2",
|
|
71
|
-
"@deepseek-ai/dsh-session": "0.1.6-alpha.2",
|
|
72
73
|
"@deepseek-ai/dsh-scope": "0.1.6-alpha.2",
|
|
74
|
+
"@deepseek-ai/dsh-session": "0.1.6-alpha.2",
|
|
73
75
|
"@deepseek-ai/dsh-skill": "0.1.6-alpha.2",
|
|
74
76
|
"@deepseek-ai/dsh-tools": "0.1.6-alpha.2",
|
|
75
77
|
"@deepseek-ai/dsh-web-app": "0.1.6-alpha.2",
|
|
76
|
-
"
|
|
78
|
+
"@types/node": "22.20.4",
|
|
79
|
+
"@types/react": "18.3.31",
|
|
80
|
+
"esbuild": "0.25.0",
|
|
81
|
+
"typescript": "5.9.3"
|
|
77
82
|
},
|
|
78
83
|
"engines": {
|
|
79
84
|
"node": "^22.19.0 || >=24.0.0"
|