@michengai/dsh-skills-manager 0.1.40 → 0.1.43

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 CHANGED
@@ -6,6 +6,19 @@ The five most recent published versions are listed below.
6
6
 
7
7
  ## Unreleased
8
8
 
9
+ ## 0.1.43 - 2026-09-08
10
+
11
+ - Fixed Settings opening with the Codex Skill list expanded by default. All Skill sources now start collapsed while matching search results still expand automatically.
12
+
13
+ ## 0.1.42 - 2026-09-07
14
+
15
+ - Fixed the release-contract check so valid package version updates no longer block verification and npm publishing.
16
+
17
+ ## 0.1.41 - 2026-09-07
18
+
19
+ - Added independent in-product update checks with automatic updates when a verified DSH update service is available and a profile-specific manual fallback otherwise.
20
+ - Removed the update-button dependency on `react-dom/client` so the client can load on Hosts that do not register that module id.
21
+
9
22
  ## 0.1.40 - 2026-09-05
10
23
 
11
24
  - 修复禁用的外部同名技能遮蔽已启用 DSH 技能的问题,统一管理页与运行时的赢家选择。
@@ -16,10 +29,6 @@ The five most recent published versions are listed below.
16
29
 
17
30
  - Added Cursor as a read-only, locally toggleable Skill source at `%USERPROFILE%\.cursor\skills`, with `DSH_CURSOR_HOME` override and state-file migration.
18
31
 
19
- ## 0.1.37 - 2026-09-03
20
-
21
- - Added compatibility with DeepSeek Harness `0.1.2-rc.1`.
22
-
23
32
  ## 0.1.36 - 2026-09-03
24
33
 
25
34
  - Separated maintained runtime source under `src` from generated `lib` package artifacts, with a reproducible Host and AMD-client build.
@@ -6,6 +6,19 @@
6
6
 
7
7
  ## 未发布
8
8
 
9
+ ## 0.1.43 - 2026-09-08
10
+
11
+ - 修复设置页首次进入时默认展开 Codex 技能列表的问题;现在所有技能来源初始保持折叠,搜索匹配时仍会自动展开相应分组。
12
+
13
+ ## 0.1.42 - 2026-09-07
14
+
15
+ - 修复发布契约校验,合法的包版本升级不再阻断验证和 npm 发布。
16
+
17
+ ## 0.1.41 - 2026-09-07
18
+
19
+ - 新增独立的插件内检查更新能力:仅在已验证的 DSH 更新服务可用时自动更新,其余环境提供与 Profile 对应的手工更新命令。
20
+ - 更新按钮图标不再依赖 `react-dom/client`,避免宿主未注册该模块 id 时客户端无法加载。
21
+
9
22
  ## 0.1.40 - 2026-09-05
10
23
 
11
24
  - 修复禁用的外部同名技能遮蔽已启用 DSH 技能的问题,统一管理页与运行时的赢家选择。
@@ -16,10 +29,6 @@
16
29
 
17
30
  - 新增 Cursor 用户级只读技能来源,默认读取 `%USERPROFILE%\.cursor\skills`,支持 `DSH_CURSOR_HOME` 覆盖,并兼容迁移既有状态文件。
18
31
 
19
- ## 0.1.37 - 2026-09-03
20
-
21
- - 支持 DeepSeek Harness `0.1.2-rc.1`。
22
-
23
32
  ## 0.1.36 - 2026-09-03
24
33
 
25
34
  - 将可维护运行源码统一迁移到 `src`,`lib` 改为可复现生成的 Host 与 AMD 客户端发布产物。
package/README.md CHANGED
@@ -129,6 +129,10 @@ dsh --profile web --dump-config
129
129
 
130
130
  Restart DSH Web and hard-refresh the browser. `dsh plugin ... add .` reads the package metadata and `cordis.patch.yml`; do not install by copying `lib` directly.
131
131
 
132
+ ## Updates
133
+
134
+ The settings title shows the installed version and a **Check for updates** button. When a newer release is available, **Update automatically** runs only when the DSH CLI or Desktop update service is available; otherwise, the dialog provides a profile-specific manual command to copy and run.
135
+
132
136
  ## Usage
133
137
 
134
138
  Open **Settings → Skills**, then use the panel as follows:
package/README.zh-CN.md CHANGED
@@ -129,6 +129,10 @@ dsh --profile web --dump-config
129
129
 
130
130
  完成后重启 DSH Web 并硬刷新浏览器。`dsh plugin ... add .` 会读取当前目录的包信息和 `cordis.patch.yml`;不要改为直接复制 `lib` 目录。
131
131
 
132
+ ## 在线更新
133
+
134
+ 设置标题会显示当前版本和“检查更新”按钮。发现新版后,只有检测到 DSH CLI 或 Desktop 更新服务时才可使用“自动更新”;其他环境会在弹窗中提供可复制、与当前 Profile 对应的手工更新命令。
135
+
132
136
  ## 使用
133
137
 
134
138
  打开「设置 → 技能」,再按下表操作:
package/lib/client.js CHANGED
@@ -1,4 +1,295 @@
1
1
  (() => {
2
+ // src/plugin-update-ui.js
3
+ var UPDATE_HEADER = "x-michengai-plugin-update";
4
+ var STYLE_ID = "michengai-plugin-update-ui";
5
+ var CSS = `
6
+ .mpi-version{margin-left:8px;color:var(--dsw-alias-label-tertiary,#9da1aa);font-family:inherit;font-size:12px;font-weight:500;line-height:18px;letter-spacing:0;white-space:nowrap;vertical-align:baseline}.mpi-check{display:inline-flex;align-items:center;justify-content:center;gap:6px;min-height:28px;padding:0 8px;border:1px solid var(--dsw-alias-border-l2,#4b4d52);border-radius:7px;background:transparent;color:var(--dsw-alias-label-secondary,#b8bbc2);font:inherit;font-size:12px;font-weight:500;line-height:18px;white-space:nowrap;cursor:pointer}.mpi-check:hover{background:var(--dsw-alias-interactive-bg-hover,#3a3b3f);color:var(--dsw-alias-label-primary,#fff)}.mpi-check:focus-visible,.mpi-action:focus-visible,.mpi-dialog-close:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,#4f8cff);outline-offset:2px}.mpi-icon{display:inline-flex;flex:0 0 auto;width:16px;height:16px;align-items:center;justify-content:center;pointer-events:none}.mpi-icon svg{display:block;width:16px;height:16px}
7
+ .mpi-overlay{position:fixed;inset:0;z-index:2147483647;display:flex;align-items:center;justify-content:center;padding:24px;background:rgba(0,0,0,.62)}.mpi-dialog{position:relative;box-sizing:border-box;width:min(680px,100%);max-height:calc(100vh - 48px);overflow:auto;border:1px solid var(--dsw-alias-border-l2,#4b4d52);border-radius:14px;padding:22px;background:var(--dsw-alias-bg-layer-2,var(--dsw-specific-menu,#202124));color:var(--dsw-alias-label-primary,#fff);box-shadow:var(--dsw-shadow-lv3,0 16px 48px rgba(0,0,0,.24));font-family:inherit}.mpi-head{display:flex;align-items:center;justify-content:space-between;gap:12px}.mpi-dialog h2{margin:0;font-size:18px;line-height:26px}.mpi-dialog-close{display:inline-flex;flex:0 0 28px;width:28px;height:28px;align-items:center;justify-content:center;padding:0;border:0;border-radius:8px;background:transparent;color:var(--dsw-alias-label-secondary,#b8bbc2);cursor:pointer}.mpi-dialog-close:hover{background:var(--dsw-alias-interactive-bg-hover,#3a3b3f);color:var(--dsw-alias-label-primary,#fff)}.mpi-intro{margin:8px 0 18px;color:var(--dsw-alias-label-secondary,#c2c4ca);font-size:13px;line-height:20px}.mpi-meta{display:grid;grid-template-columns:max-content minmax(0,1fr);gap:8px 18px;margin:0 0 16px;font-size:12px;line-height:18px}.mpi-meta dt{color:var(--dsw-alias-label-secondary,#c2c4ca)}.mpi-meta dd{margin:0;font-family:ui-monospace,SFMono-Regular,Consolas,monospace}.mpi-status{margin:0 0 18px;border-radius:7px;padding:12px 14px;background:var(--dsw-alias-bg-layer-3,var(--dsw-specific-menu-item-hover,#252527));font-size:13px;font-weight:600;line-height:20px}.mpi-status[data-kind=error]{color:var(--dsw-alias-state-error-primary,#ff6464)}.mpi-status[data-kind=success]{color:var(--dsw-alias-state-success-primary,#36d67a)}.mpi-manual{border-top:1px solid var(--dsw-alias-border-l2,#4b4d52);padding-top:16px}.mpi-manual h3{margin:0 0 6px;font-size:14px;line-height:20px}.mpi-manual p{margin:0 0 10px;color:var(--dsw-alias-label-secondary,#c2c4ca);font-size:12px;line-height:18px}.mpi-command{display:flex;align-items:center;gap:8px;border:1px solid var(--dsw-alias-border-l2,#4b4d52);border-radius:7px;padding:10px;background:var(--dsw-alias-bg-layer-3,var(--dsw-specific-menu-item-hover,#252527))}.mpi-command code{min-width:0;flex:1;overflow:auto;font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:12px;line-height:18px;white-space:nowrap}.mpi-actions{display:flex;align-items:center;justify-content:flex-end;gap:12px;margin-top:18px}.mpi-actions-group{display:flex;gap:8px}.mpi-action{display:inline-flex;align-items:center;justify-content:center;gap:6px;min-height:32px;border:1px solid var(--dsw-alias-border-l2,#4b4d52);border-radius:7px;padding:6px 10px;background:transparent;color:inherit;font:inherit;font-size:12px;cursor:pointer}.mpi-action:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,#414247)}.mpi-action:disabled{cursor:not-allowed;opacity:.55}.mpi-primary{border-color:var(--dsw-alias-state-business-primary,#4f8cff);background:var(--dsw-alias-state-business-primary,#4f8cff);color:#fff}.mpi-progress{height:4px;margin-top:10px;overflow:hidden;border-radius:99px;background:var(--dsw-alias-border-l2,#4b4d52)}.mpi-progress::after{display:block;width:32%;height:100%;background:var(--dsw-alias-state-business-primary,#4f8cff);content:'';animation:mpi-wave 1.15s ease-in-out infinite}@keyframes mpi-wave{from{transform:translateX(-110%)}to{transform:translateX(330%)}}@media(max-width:560px){.mpi-overlay{padding:10px}.mpi-dialog{max-height:calc(100vh - 20px);padding:16px}.mpi-actions{align-items:stretch}.mpi-actions-group{justify-content:flex-end;flex-wrap:wrap}.mpi-meta{grid-template-columns:1fr;gap:2px}.mpi-meta dd{margin-bottom:6px}}
8
+ `;
9
+ var ZH = {
10
+ check: "\u68C0\u67E5\u66F4\u65B0",
11
+ update: "\u66F4\u65B0",
12
+ close: "\u5173\u95ED",
13
+ recheck: "\u91CD\u65B0\u68C0\u67E5",
14
+ auto: "\u81EA\u52A8\u66F4\u65B0",
15
+ updating: "\u6B63\u5728\u66F4\u65B0\u2026",
16
+ copy: "\u590D\u5236\u547D\u4EE4",
17
+ copied: "\u5DF2\u590D\u5236",
18
+ copyFailed: "\u590D\u5236\u5931\u8D25",
19
+ checking: "\u6B63\u5728\u68C0\u67E5\u66F4\u65B0\u2026",
20
+ latest: "\u5DF2\u662F\u6700\u65B0\u7248\u672C",
21
+ found: "\u53D1\u73B0\u65B0\u7248\u672C",
22
+ failed: "\u68C0\u67E5\u66F4\u65B0\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002",
23
+ current: "\u8FD0\u884C\u7248\u672C",
24
+ latestLabel: "\u6700\u65B0\u7248\u672C",
25
+ profile: "\u76EE\u6807 profile",
26
+ unknown: "\u672A\u77E5",
27
+ manual: "\u624B\u5DE5\u66F4\u65B0",
28
+ manualHint: "\u81EA\u52A8\u66F4\u65B0\u5931\u8D25\u65F6\uFF0C\u53EF\u5728\u5F53\u524D DSH \u7EC8\u7AEF\u6267\u884C\u4EE5\u4E0B\u547D\u4EE4\uFF0C\u5B8C\u6210\u540E\u91CD\u542F DSH Web\u3002",
29
+ intro: "\u4EC5\u68C0\u67E5\u5E76\u66F4\u65B0\u5F53\u524D\u63D2\u4EF6\uFF0C\u4E0D\u4F1A\u8054\u52A8\u5B89\u88C5\u5176\u4ED6\u63D2\u4EF6\u3002",
30
+ restart: "\u66F4\u65B0\u5B8C\u6210\uFF0C\u8BF7\u91CD\u542F DSH Web\u3002",
31
+ restarting: "\u66F4\u65B0\u5B8C\u6210\uFF0C\u6B63\u5728\u91CD\u542F DSH Desktop\u2026",
32
+ unavailable: "\u5F53\u524D\u73AF\u5883\u4E0D\u652F\u6301\u81EA\u52A8\u66F4\u65B0\uFF0C\u8BF7\u4F7F\u7528\u624B\u5DE5\u66F4\u65B0\u547D\u4EE4\u3002"
33
+ };
34
+ var EN = {
35
+ check: "Check for updates",
36
+ update: "Update",
37
+ close: "Close",
38
+ recheck: "Check again",
39
+ auto: "Update automatically",
40
+ updating: "Updating\u2026",
41
+ copy: "Copy command",
42
+ copied: "Copied",
43
+ copyFailed: "Copy failed",
44
+ checking: "Checking for updates\u2026",
45
+ latest: "You are up to date",
46
+ found: "New version available",
47
+ failed: "Could not check for updates. Try again later.",
48
+ current: "Running version",
49
+ latestLabel: "Latest version",
50
+ profile: "Target profile",
51
+ unknown: "Unknown",
52
+ manual: "Manual update",
53
+ manualHint: "If automatic update fails, run this command in the current DSH terminal, then restart DSH Web.",
54
+ intro: "Only this plugin is checked and updated. Other plugins are not changed.",
55
+ restart: "Update complete. Restart DSH Web.",
56
+ restarting: "Update complete. Restarting DSH Desktop\u2026",
57
+ unavailable: "Automatic update is unavailable. Use the manual command."
58
+ };
59
+ function strings() {
60
+ const lang = document.documentElement.lang.toLowerCase();
61
+ const settings = document.querySelector('[role="dialog"]')?.textContent ?? "";
62
+ return lang.startsWith("en") || settings.includes("Settings") && !settings.includes("\u8BBE\u7F6E") ? EN : ZH;
63
+ }
64
+ function ensureStyle() {
65
+ if (document.getElementById(STYLE_ID) !== null) return;
66
+ const style = document.createElement("style");
67
+ style.id = STYLE_ID;
68
+ style.textContent = CSS;
69
+ (document.head ?? document.documentElement).append(style);
70
+ }
71
+ function validPayload(value) {
72
+ if (value === null || typeof value !== "object") return false;
73
+ const item = value;
74
+ return typeof item.packageName === "string" && typeof item.currentVersion === "string" && typeof item.updateAvailable === "boolean" && typeof item.profileName === "string" && typeof item.canAutoUpdate === "boolean" && typeof item.latestCheckFailed === "boolean" && (item.latestVersion === void 0 || typeof item.latestVersion === "string");
75
+ }
76
+ async function requestStatus(endpoint, method, signal) {
77
+ const signalOption = signal === void 0 ? {} : { signal };
78
+ const response = await fetch(endpoint, method === "GET" ? { cache: "no-store", ...signalOption } : {
79
+ method: "POST",
80
+ headers: { "content-type": "application/json", [UPDATE_HEADER]: "1" },
81
+ body: "{}",
82
+ ...signalOption
83
+ });
84
+ const value = await response.json();
85
+ if (!response.ok || !validPayload(value)) throw new Error(typeof value.error === "string" ? value.error : strings().failed);
86
+ return value;
87
+ }
88
+ function manualPluginUpdateCommand(profileName, packageName, version) {
89
+ const profile = profileName.trim() === "" ? "" : ` --profile ${profileName.trim()}`;
90
+ return `dsh plugin${profile} add ${packageName}@${version} --registry=https://registry.npmjs.org/`;
91
+ }
92
+ function handlePluginUpdateEscape(event, close) {
93
+ if (event.key !== "Escape") return false;
94
+ event.preventDefault();
95
+ event.stopPropagation();
96
+ event.stopImmediatePropagation();
97
+ close();
98
+ return true;
99
+ }
100
+ function observePluginUpdate(options) {
101
+ if (typeof document === "undefined" || document.body === null) return () => {
102
+ };
103
+ ensureStyle();
104
+ const controller = new AbortController();
105
+ let payload;
106
+ let overlay;
107
+ let frame;
108
+ const setButtonContent = (button, label, iconName) => {
109
+ const icon = options.createIcon(iconName);
110
+ icon.classList.add("mpi-icon");
111
+ icon.setAttribute("aria-hidden", "true");
112
+ const text = document.createElement("span");
113
+ text.dataset.mpiLabel = "";
114
+ text.textContent = label;
115
+ button.replaceChildren(icon, text);
116
+ };
117
+ const setButtonLabel = (button, label) => {
118
+ const text = button.querySelector("[data-mpi-label]");
119
+ if (text === null) button.textContent = label;
120
+ else text.textContent = label;
121
+ };
122
+ const applyControls = () => {
123
+ const row = document.querySelector(options.titleRowSelector);
124
+ if (row === null) return;
125
+ const heading = row.querySelector("h1,h2");
126
+ if (heading !== null && payload !== void 0) {
127
+ let version = heading.querySelector(`.mpi-version[data-package="${options.packageName}"]`);
128
+ if (version === null) {
129
+ version = document.createElement("span");
130
+ version.className = "mpi-version";
131
+ version.dataset.package = options.packageName;
132
+ heading.append(version);
133
+ }
134
+ const versionLabel = `v${payload.currentVersion}`;
135
+ if (version.textContent !== versionLabel) version.textContent = versionLabel;
136
+ }
137
+ const links = row.querySelector(options.linksSelector);
138
+ if (links === null || links.querySelector(`[data-mpi-check="${options.packageName}"]`) !== null) return;
139
+ const button = document.createElement("button");
140
+ button.type = "button";
141
+ button.className = "mpi-check";
142
+ button.dataset.mpiCheck = options.packageName;
143
+ setButtonContent(button, strings().check, "refresh");
144
+ button.addEventListener("click", openDialog);
145
+ links.append(button);
146
+ };
147
+ const load = async () => {
148
+ payload = await requestStatus(options.endpoint, "GET", controller.signal);
149
+ applyControls();
150
+ return payload;
151
+ };
152
+ const closeDialog = () => {
153
+ overlay?.remove();
154
+ overlay = void 0;
155
+ };
156
+ function openDialog() {
157
+ closeDialog();
158
+ const text = strings();
159
+ overlay = document.createElement("div");
160
+ overlay.className = "mpi-overlay";
161
+ const dialog = document.createElement("section");
162
+ dialog.className = "mpi-dialog";
163
+ dialog.setAttribute("role", "dialog");
164
+ dialog.setAttribute("aria-modal", "true");
165
+ dialog.innerHTML = `<header class="mpi-head"><h2></h2><button type="button" class="mpi-dialog-close" data-action="close"></button></header><p class="mpi-intro"></p><dl class="mpi-meta"><dt></dt><dd data-role="current"></dd><dt></dt><dd data-role="latest"></dd><dt></dt><dd data-role="profile"></dd></dl><div class="mpi-status" role="status"></div><div class="mpi-progress" hidden></div><section class="mpi-manual"><h3></h3><p></p><div class="mpi-command"><code></code><button type="button" class="mpi-action" data-action="copy"></button></div></section><footer class="mpi-actions"><div class="mpi-actions-group"><button type="button" class="mpi-action" data-action="check"></button><button type="button" class="mpi-action mpi-primary" data-action="update"></button></div></footer>`;
166
+ const name = document.documentElement.lang.toLowerCase().startsWith("en") ? options.enName : options.zhName;
167
+ dialog.querySelector("h2").textContent = `${name} ${text.update}`;
168
+ dialog.querySelector(".mpi-intro").textContent = text.intro;
169
+ const terms = dialog.querySelectorAll("dt");
170
+ terms[0].textContent = text.current;
171
+ terms[1].textContent = text.latestLabel;
172
+ terms[2].textContent = text.profile;
173
+ dialog.querySelector(".mpi-manual h3").textContent = text.manual;
174
+ dialog.querySelector(".mpi-manual p").textContent = text.manualHint;
175
+ const status = dialog.querySelector(".mpi-status");
176
+ const progress = dialog.querySelector(".mpi-progress");
177
+ const command = dialog.querySelector(".mpi-command code");
178
+ const close = dialog.querySelector("[data-action=close]");
179
+ const check = dialog.querySelector("[data-action=check]");
180
+ const update = dialog.querySelector("[data-action=update]");
181
+ const copy = dialog.querySelector("[data-action=copy]");
182
+ setButtonContent(close, text.close, "close");
183
+ close.querySelector("[data-mpi-label]")?.remove();
184
+ close.setAttribute("aria-label", text.close);
185
+ close.title = text.close;
186
+ setButtonContent(check, text.recheck, "refresh");
187
+ setButtonContent(update, text.auto, "download");
188
+ setButtonContent(copy, text.copy, "copy");
189
+ let busy = false;
190
+ const setMessage = (message, kind = "") => {
191
+ status.textContent = message;
192
+ status.dataset.kind = kind;
193
+ };
194
+ const setBusy = (value) => {
195
+ busy = value;
196
+ check.disabled = value;
197
+ copy.disabled = value;
198
+ update.disabled = value || payload?.canAutoUpdate !== true || payload.updateAvailable !== true;
199
+ progress.hidden = !value;
200
+ };
201
+ const render = () => {
202
+ dialog.querySelector("[data-role=current]").textContent = payload === void 0 ? text.unknown : `v${payload.currentVersion}`;
203
+ dialog.querySelector("[data-role=latest]").textContent = payload?.latestVersion === void 0 ? text.unknown : `v${payload.latestVersion}`;
204
+ dialog.querySelector("[data-role=profile]").textContent = payload?.profileName ?? text.unknown;
205
+ command.textContent = manualPluginUpdateCommand(payload?.profileName ?? "", options.packageName, payload?.latestVersion ?? "latest");
206
+ update.disabled = busy || payload?.canAutoUpdate !== true || payload.updateAvailable !== true;
207
+ if (payload === void 0) setMessage(text.checking);
208
+ else if (payload.latestCheckFailed) setMessage(text.failed, "error");
209
+ else if (payload.updateAvailable) setMessage(`${text.found}: v${payload.latestVersion ?? text.unknown}`);
210
+ else setMessage(text.latest, "success");
211
+ if (payload !== void 0 && !payload.canAutoUpdate && payload.updateAvailable) setMessage(text.unavailable);
212
+ };
213
+ const checkNow = async () => {
214
+ if (busy) return;
215
+ setBusy(true);
216
+ setMessage(text.checking);
217
+ try {
218
+ await load();
219
+ setBusy(false);
220
+ render();
221
+ } catch (error) {
222
+ setBusy(false);
223
+ setMessage(error instanceof Error ? error.message : text.failed, "error");
224
+ }
225
+ };
226
+ const updateNow = async () => {
227
+ if (busy) return;
228
+ setBusy(true);
229
+ setButtonLabel(update, text.updating);
230
+ setMessage(text.updating);
231
+ try {
232
+ payload = await requestStatus(options.endpoint, "POST", controller.signal);
233
+ applyControls();
234
+ render();
235
+ setMessage(payload.autoReload === true ? text.restarting : text.restart, "success");
236
+ } catch (error) {
237
+ setMessage(error instanceof Error ? error.message : text.failed, "error");
238
+ } finally {
239
+ setButtonLabel(update, text.auto);
240
+ setBusy(false);
241
+ }
242
+ };
243
+ close.addEventListener("click", closeDialog);
244
+ check.addEventListener("click", () => {
245
+ void checkNow();
246
+ });
247
+ update.addEventListener("click", () => {
248
+ void updateNow();
249
+ });
250
+ copy.addEventListener("click", () => {
251
+ void navigator.clipboard?.writeText(command.textContent ?? "").then(() => {
252
+ setButtonLabel(copy, text.copied);
253
+ setTimeout(() => {
254
+ setButtonLabel(copy, text.copy);
255
+ }, 1400);
256
+ }).catch(() => {
257
+ setButtonLabel(copy, text.copyFailed);
258
+ });
259
+ });
260
+ overlay.addEventListener("click", (event) => {
261
+ if (event.target === overlay) closeDialog();
262
+ });
263
+ overlay.addEventListener("keydown", (event) => {
264
+ handlePluginUpdateEscape(event, closeDialog);
265
+ }, true);
266
+ overlay.append(dialog);
267
+ document.body.append(overlay);
268
+ render();
269
+ close.focus();
270
+ void checkNow();
271
+ }
272
+ const observer = new MutationObserver(() => {
273
+ if (frame !== void 0) return;
274
+ frame = window.requestAnimationFrame(() => {
275
+ frame = void 0;
276
+ applyControls();
277
+ });
278
+ });
279
+ observer.observe(document.body, { childList: true, subtree: true });
280
+ applyControls();
281
+ void load().catch(() => {
282
+ });
283
+ return () => {
284
+ controller.abort();
285
+ observer.disconnect();
286
+ closeDialog();
287
+ if (frame !== void 0) window.cancelAnimationFrame(frame);
288
+ document.querySelectorAll(`[data-mpi-check="${options.packageName}"],.mpi-version[data-package="${options.packageName}"]`).forEach((node) => node.remove());
289
+ };
290
+ }
291
+
292
+ // src/client.js
2
293
  window.__ModuleLoader__.load({
3
294
  id: "@michengai/dsh-skills-manager",
4
295
  factory: (require2) => {
@@ -7,6 +298,32 @@
7
298
  var react = require2("react");
8
299
  var h = react.createElement;
9
300
  var primitives = require2("@deepseek-ai/dsh-client-ui-primitives");
301
+ var UPDATE_ICON_PATHS = {
302
+ refresh: ["M13.5 5.5V2.5m0 0h-3m3 0-2.1 2.1A5.5 5.5 0 1 0 13.2 12"],
303
+ download: ["M8 2v8m0 0 3-3m-3 3-3-3M3 13v2h10v-2"],
304
+ copy: ["M5 5h8v8H5z", "M3 3h8"],
305
+ close: ["m4 4 8 8M12 4 4 12"]
306
+ };
307
+ function createPluginUpdateIcon(name) {
308
+ var element = document.createElement("span");
309
+ var svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
310
+ svg.setAttribute("viewBox", "0 0 16 16");
311
+ svg.setAttribute("width", "16");
312
+ svg.setAttribute("height", "16");
313
+ svg.setAttribute("fill", "none");
314
+ svg.setAttribute("stroke", "currentColor");
315
+ svg.setAttribute("stroke-width", "1.5");
316
+ svg.setAttribute("stroke-linecap", "round");
317
+ svg.setAttribute("stroke-linejoin", "round");
318
+ svg.setAttribute("aria-hidden", "true");
319
+ UPDATE_ICON_PATHS[name].forEach(function(d) {
320
+ var path = document.createElementNS("http://www.w3.org/2000/svg", "path");
321
+ path.setAttribute("d", d);
322
+ svg.append(path);
323
+ });
324
+ element.append(svg);
325
+ return element;
326
+ }
10
327
  var MUTATION_HEADERS = { "content-type": "application/json", "x-dsh-skills-manager": "1" };
11
328
  var NS = "skills-manager";
12
329
  var DICT = {
@@ -369,7 +686,7 @@
369
686
  "root.cursor": "Cursor"
370
687
  }
371
688
  };
372
- var CSS = `
689
+ var CSS2 = `
373
690
  .dssm-section{box-sizing:border-box;display:flex;width:100%;max-width:820px;min-width:0;margin:0 auto;padding:2px 0 36px;container-type:inline-size;flex-direction:column;gap:14px;color:var(--dsw-alias-label-primary);font-family:inherit}.dssm-head{display:flex;align-items:flex-start;gap:20px}.dssm-title-block{min-width:0}.dssm-title-row{display:flex;align-items:center;gap:8px;min-width:0}.dssm-feedback-links{display:flex;align-items:center;gap:4px}.dssm-title{margin:0;font-size:22px;line-height:30px;font-weight:680;letter-spacing:-.3px}.dssm-feedback-link{display:inline-flex;min-height:28px;align-items:center;gap:5px;padding:0 8px;border:1px solid var(--dsw-alias-border-l2);border-radius:7px;background:transparent;color:var(--dsw-alias-label-secondary);font:inherit;font-size:12px;font-weight:500;line-height:18px;text-decoration:none;white-space:nowrap}.dssm-feedback-link:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.dssm-feedback-link:focus-visible{outline:2px solid var(--dsw-alias-state-success-primary);outline-offset:2px}.dssm-feedback-link svg{flex:none}.dssm-desc{margin:3px 0 0;color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:20px}.dssm-actions{display:flex;gap:8px;margin-left:auto;flex:none}
374
691
  .dssm-btn{box-sizing:border-box;display:inline-flex;min-height:34px;align-items:center;justify-content:center;padding:0 13px;border:1px solid transparent;border-radius:8px;background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground);font:inherit;font-size:13px;font-weight:580;white-space:nowrap;cursor:pointer}.dssm-btn:hover:not(:disabled){filter:brightness(1.08)}.dssm-btn:disabled{opacity:.48;cursor:default}.dssm-btn-secondary,.dssm-btn-quiet{border-color:var(--dsw-alias-border-l2);background:transparent;color:var(--dsw-alias-label-primary)}.dssm-btn-quiet{min-height:28px;padding:0 9px;color:var(--dsw-alias-label-secondary);font-size:12px}.dssm-btn-danger{border-color:var(--dsw-alias-state-error-primary);background:transparent;color:var(--dsw-alias-state-error-primary)}.dssm-btn:focus-visible,.dssm-control:focus-visible,.dssm-select-trigger:focus-visible,.dssm-source-head:focus-visible,.dssm-switch:focus-visible,.dssm-upload-link:focus-visible,.dssm-file-remove:focus-visible{outline:2px solid var(--dsw-alias-state-success-primary);outline-offset:2px}
375
692
  .dssm-summary{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));overflow:hidden;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-alias-bg-layer-2)}.dssm-stat{padding:12px 14px;border-right:1px solid var(--dsw-alias-border-l1);font-size:13px;color:var(--dsw-alias-label-secondary)}.dssm-stat:last-child{border-right:0}.dssm-stat strong{margin-right:5px;color:var(--dsw-alias-label-primary);font-size:17px;font-weight:680}.dssm-filters{display:flex;gap:9px}.dssm-search{flex:1}.dssm-source-filter{width:210px;flex:none}
@@ -620,7 +937,7 @@
620
937
  var bs = react.useState(false), busy = bs[0], setBusy = bs[1];
621
938
  var qs = react.useState(""), query = qs[0], setQuery = qs[1];
622
939
  var fs = react.useState(""), source = fs[0], setSource = fs[1];
623
- var es = react.useState({ codex: true }), expanded = es[0], setExpanded = es[1];
940
+ var es = react.useState({}), expanded = es[0], setExpanded = es[1];
624
941
  var ms = react.useState(null), modal = ms[0], setModal = ms[1];
625
942
  var rs = react.useState(null), result = rs[0], setResult = rs[1];
626
943
  var fms = react.useState({ root: "dsh", name: "", description: "", body: "" }), form = fms[0], setForm = fms[1];
@@ -772,7 +1089,7 @@
772
1089
  })) : h("div", { className: "dssm-empty" }, query ? t("empty.search") : t("empty.source"))) : null);
773
1090
  }
774
1091
  var summary = data.summary || { total: 0, enabled: 0, disabled: 0, issues: 0 };
775
- var content = [h("style", { key: "css" }, CSS), h("div", { key: "head", className: "dssm-head" }, h("div", { className: "dssm-title-block" }, h("div", { className: "dssm-title-row" }, h("h2", { className: "dssm-title" }, t("title")), h("div", { className: "dssm-feedback-links" }, h("a", { className: "dssm-feedback-link", href: "https://github.com/MichengAI/dsh-skills-manager", target: "_blank", rel: "noreferrer", "aria-label": t("link.project") }, h(GithubMark16, null), t("link.project")), h("a", { className: "dssm-feedback-link", href: "https://github.com/MichengAI/dsh-skills-manager/issues", target: "_blank", rel: "noreferrer", "aria-label": t("link.feedback") }, h(primitives.IconListPenOutline16, null), t("link.feedback")))), h("p", { className: "dssm-desc" }, t("desc"))), h("div", { className: "dssm-actions" }, h("button", { type: "button", className: "dssm-btn dssm-btn-secondary", disabled: busy || snapshot.loading, onClick: function() {
1092
+ var content = [h("style", { key: "css" }, CSS2), h("div", { key: "head", className: "dssm-head" }, h("div", { className: "dssm-title-block" }, h("div", { className: "dssm-title-row" }, h("h2", { className: "dssm-title" }, t("title")), h("div", { className: "dssm-feedback-links" }, h("a", { className: "dssm-feedback-link", href: "https://github.com/MichengAI/dsh-skills-manager", target: "_blank", rel: "noreferrer", "aria-label": t("link.project") }, h(GithubMark16, null), t("link.project")), h("a", { className: "dssm-feedback-link", href: "https://github.com/MichengAI/dsh-skills-manager/issues", target: "_blank", rel: "noreferrer", "aria-label": t("link.feedback") }, h(primitives.IconListPenOutline16, null), t("link.feedback")))), h("p", { className: "dssm-desc" }, t("desc"))), h("div", { className: "dssm-actions" }, h("button", { type: "button", className: "dssm-btn dssm-btn-secondary", disabled: busy || snapshot.loading, onClick: function() {
776
1093
  refresh(false);
777
1094
  } }, t("btn.refresh")), h("button", { type: "button", className: "dssm-btn", onClick: openCreate }, t("btn.create")), h("button", { type: "button", className: "dssm-btn dssm-btn-secondary", onClick: function() {
778
1095
  setResult(null);
@@ -893,6 +1210,9 @@
893
1210
  ctx.effect(function() {
894
1211
  return ctx.locale.register(NS, DICT);
895
1212
  });
1213
+ ctx.effect(function() {
1214
+ return observePluginUpdate({ endpoint: "/api/michengai/dsh-skills-manager/update", packageName: "@michengai/dsh-skills-manager", titleRowSelector: ".dssm-title-row", linksSelector: ".dssm-feedback-links", zhName: "\u6280\u80FD", enName: "Skills", createIcon: createPluginUpdateIcon });
1215
+ }, "skills-manager: plugin update ui");
896
1216
  ctx.slots.inject("settings.section", function() {
897
1217
  return ctx.slots.register({ name: "settings.section", id: "skills-manager", order: 17, label: function() {
898
1218
  return ctx.locale.bind(NS)("title");
package/lib/index.js CHANGED
@@ -17,6 +17,7 @@ import {
17
17
  projectRoots,
18
18
  logPath
19
19
  } from "./core.js";
20
+ import { registerPluginUpdater } from "./plugin-updater.js";
20
21
  const name = "skills-manager";
21
22
  const inject = ["webServer", "webRuntime", "skills", "tools", "sessions"];
22
23
  const CLIENT_MARKER_HEADER = "x-dsh-skills-manager";
@@ -252,6 +253,11 @@ function registerAgentSkillProviders(ctx, invalidators) {
252
253
  };
253
254
  }
254
255
  function apply(ctx) {
256
+ ctx.effect(() => registerPluginUpdater(ctx, {
257
+ endpoint: "/api/michengai/dsh-skills-manager/update",
258
+ packageName: "@michengai/dsh-skills-manager",
259
+ manifestUrl: new URL("../package.json", import.meta.url)
260
+ }), "skills-manager: plugin updater");
255
261
  const log = makeLog();
256
262
  const trustedHosts = Array.isArray(ctx.webRuntime.trustedHosts) ? ctx.webRuntime.trustedHosts : [];
257
263
  const roots = userRoots();
@@ -0,0 +1,257 @@
1
+ import { spawn } from "node:child_process";
2
+ import { existsSync, readFileSync } from "node:fs";
3
+ import { readFile } from "node:fs/promises";
4
+ import { homedir } from "node:os";
5
+ import { basename, dirname, isAbsolute, resolve } from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+ const PLUGIN_UPDATE_HEADER = "x-michengai-plugin-update";
8
+ const PLUGIN_UPDATE_IPC = "apply-plugin-updates";
9
+ function header(request, name) {
10
+ const value = request.headers?.[name];
11
+ return Array.isArray(value) ? value[0] : value;
12
+ }
13
+ function isLoopbackAddress(value) {
14
+ const address = value?.toLowerCase().replace(/^\[|\]$/g, "");
15
+ return address === "localhost" || address === "localhost." || address === "::1" || address?.startsWith("127.") === true || address?.startsWith("::ffff:127.") === true;
16
+ }
17
+ function isTrustedUpdateRequest(request) {
18
+ if (header(request, PLUGIN_UPDATE_HEADER) !== "1") return false;
19
+ if (!isLoopbackAddress(request.socket?.remoteAddress)) return false;
20
+ const site = header(request, "sec-fetch-site");
21
+ if (site !== void 0 && site !== "same-origin") return false;
22
+ const origin = header(request, "origin");
23
+ const host = header(request, "host");
24
+ if (origin === void 0 || host === void 0) return false;
25
+ try {
26
+ const url = new URL(origin);
27
+ return (url.protocol === "http:" || url.protocol === "https:") && isLoopbackAddress(url.hostname) && url.host === host;
28
+ } catch {
29
+ return false;
30
+ }
31
+ }
32
+ function validProfileName(value) {
33
+ return typeof value === "string" && value !== "" && value !== "." && value !== ".." && !value.includes("/") && !value.includes("\\") && !/[\0-\x1f\x7f]/.test(value);
34
+ }
35
+ function profileNameFromArgv(argv) {
36
+ for (let index = 2; index < argv.length; index += 1) {
37
+ if (argv[index] === "--profile") return argv[index + 1];
38
+ if (argv[index]?.startsWith("--profile=")) return argv[index].slice("--profile=".length);
39
+ }
40
+ return argv[2] === "web" ? "web" : void 0;
41
+ }
42
+ function isDshCliEntry(entry, manifest, packageRoot) {
43
+ if (typeof manifest !== "object" || manifest === null) return false;
44
+ if (manifest.name !== "@deepseek-ai/dsh") return false;
45
+ const bin = typeof manifest.bin === "string" ? manifest.bin : typeof manifest.bin === "object" && manifest.bin !== null ? manifest.bin.dsh : void 0;
46
+ return typeof bin === "string" && bin !== "" && !isAbsolute(bin) && resolve(packageRoot, bin) === resolve(entry);
47
+ }
48
+ function cliEntry() {
49
+ const value = process.argv[1];
50
+ if (value === void 0 || value === "") return void 0;
51
+ const entry = value.startsWith("file:") ? fileURLToPath(value) : resolve(process.cwd(), value);
52
+ if (!existsSync(entry)) return void 0;
53
+ for (let directory = dirname(entry); ; ) {
54
+ const manifestPath = resolve(directory, "package.json");
55
+ if (existsSync(manifestPath)) {
56
+ try {
57
+ if (isDshCliEntry(entry, JSON.parse(readFileSync(manifestPath, "utf8")), directory)) return entry;
58
+ } catch {
59
+ }
60
+ }
61
+ const parent = dirname(directory);
62
+ if (parent === directory) return void 0;
63
+ directory = parent;
64
+ }
65
+ }
66
+ function runtime(ctx) {
67
+ const profiles = ctx.get?.("desktopProfiles");
68
+ const desktopPnpm = ctx.get?.("desktopPnpm");
69
+ if (profiles?.current !== void 0) {
70
+ const current = profiles.current;
71
+ if (!validProfileName(current.name) || typeof current.dir !== "string" || !isAbsolute(current.dir)) {
72
+ throw new Error("\u5F53\u524D Desktop Profile \u4FE1\u606F\u65E0\u6548\uFF0C\u8BF7\u91CD\u542F\u540E\u91CD\u8BD5\u3002");
73
+ }
74
+ return { profileName: current.name, profileDir: resolve(current.dir), ...typeof desktopPnpm?.runPlugin === "function" ? { desktopPnpm } : {} };
75
+ }
76
+ const profileDir = resolve(process.env.DSH_PROFILE_DIR ?? resolve(homedir(), ".dsh", "profiles", "web"));
77
+ const selected = profileNameFromArgv(process.argv);
78
+ const profileName = validProfileName(selected) ? selected : validProfileName(basename(profileDir)) ? basename(profileDir) : "web";
79
+ const entry = cliEntry();
80
+ return { profileName, profileDir, ...entry === void 0 ? {} : { cliEntry: entry } };
81
+ }
82
+ function parseSemver(value) {
83
+ const match = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/.exec(value);
84
+ if (match === null) return void 0;
85
+ return { core: [Number(match[1]), Number(match[2]), Number(match[3])], prerelease: match[4]?.split(".") ?? [] };
86
+ }
87
+ function isNewerVersion(currentValue, candidateValue) {
88
+ const current = parseSemver(currentValue);
89
+ const candidate = parseSemver(candidateValue);
90
+ if (current === void 0 || candidate === void 0) return false;
91
+ for (let index = 0; index < 3; index += 1) {
92
+ if (candidate.core[index] !== current.core[index]) return candidate.core[index] > current.core[index];
93
+ }
94
+ return comparePrerelease(candidate.prerelease, current.prerelease) > 0;
95
+ }
96
+ function comparePrerelease(left, right) {
97
+ if (left.length === 0 || right.length === 0) return left.length === right.length ? 0 : left.length === 0 ? 1 : -1;
98
+ const length = Math.max(left.length, right.length);
99
+ for (let index = 0; index < length; index += 1) {
100
+ const a = left[index];
101
+ const b = right[index];
102
+ if (a === void 0 || b === void 0) return a === b ? 0 : a === void 0 ? -1 : 1;
103
+ if (a === b) continue;
104
+ const aNumeric = /^\d+$/.test(a);
105
+ const bNumeric = /^\d+$/.test(b);
106
+ if (aNumeric && bNumeric) {
107
+ const aNumber = BigInt(a);
108
+ const bNumber = BigInt(b);
109
+ if (aNumber !== bNumber) return aNumber > bNumber ? 1 : -1;
110
+ continue;
111
+ }
112
+ if (aNumeric !== bNumeric) return aNumeric ? -1 : 1;
113
+ return a > b ? 1 : -1;
114
+ }
115
+ return 0;
116
+ }
117
+ let latestCache;
118
+ async function latestVersion(packageName) {
119
+ if (latestCache?.packageName === packageName && Date.now() < latestCache.expiresAt) return latestCache.version;
120
+ try {
121
+ const response = await fetch(`https://registry.npmjs.org/${encodeURIComponent(packageName)}/latest`, { signal: AbortSignal.timeout(8e3) });
122
+ if (!response.ok) return void 0;
123
+ const value = await response.json();
124
+ if (typeof value.version !== "string" || value.version === "") return void 0;
125
+ latestCache = { packageName, version: value.version, expiresAt: Date.now() + 5 * 6e4 };
126
+ return value.version;
127
+ } catch {
128
+ return void 0;
129
+ }
130
+ }
131
+ async function currentVersion(manifestUrl) {
132
+ const value = JSON.parse(await readFile(manifestUrl, "utf8"));
133
+ if (typeof value.version !== "string" || value.version === "") throw new Error("\u65E0\u6CD5\u8BFB\u53D6\u5F53\u524D\u63D2\u4EF6\u7248\u672C\u3002");
134
+ return value.version;
135
+ }
136
+ async function status(options, target) {
137
+ const current = await currentVersion(options.manifestUrl);
138
+ const latest = await latestVersion(options.packageName);
139
+ return {
140
+ packageName: options.packageName,
141
+ currentVersion: current,
142
+ ...latest === void 0 ? {} : { latestVersion: latest },
143
+ latestCheckFailed: latest === void 0,
144
+ updateAvailable: latest !== void 0 && isNewerVersion(current, latest),
145
+ profileName: target.profileName,
146
+ canAutoUpdate: target.desktopPnpm !== void 0 || target.cliEntry !== void 0
147
+ };
148
+ }
149
+ async function runCliInstall(target, packageSpec) {
150
+ if (target.cliEntry === void 0) throw new Error("\u5F53\u524D\u73AF\u5883\u4E0D\u652F\u6301\u81EA\u52A8\u66F4\u65B0\uFF0C\u8BF7\u4F7F\u7528\u624B\u5DE5\u66F4\u65B0\u547D\u4EE4\u3002");
151
+ await new Promise((resolvePromise, reject) => {
152
+ const child = spawn(process.execPath, [target.cliEntry, "plugin", "--profile", target.profileName, "add", "--config.minimumReleaseAge=0", packageSpec, "--registry=https://registry.npmjs.org/"], {
153
+ cwd: target.profileDir,
154
+ windowsHide: true,
155
+ stdio: ["ignore", "pipe", "pipe"],
156
+ env: { ...process.env, NO_COLOR: "1" }
157
+ });
158
+ let detail = "";
159
+ child.stdout?.on("data", (chunk) => {
160
+ detail = (detail + String(chunk)).slice(-4e3);
161
+ });
162
+ child.stderr?.on("data", (chunk) => {
163
+ detail = (detail + String(chunk)).slice(-4e3);
164
+ });
165
+ const timer = setTimeout(() => {
166
+ child.kill();
167
+ reject(new Error("\u66F4\u65B0\u8D85\u65F6\uFF0C\u8BF7\u6539\u7528\u624B\u5DE5\u66F4\u65B0\u3002"));
168
+ }, 10 * 6e4);
169
+ child.once("error", (error) => {
170
+ clearTimeout(timer);
171
+ reject(error);
172
+ });
173
+ child.once("exit", (code) => {
174
+ clearTimeout(timer);
175
+ if (code === 0) resolvePromise();
176
+ else reject(new Error(detail.trim() || `\u66F4\u65B0\u8FDB\u7A0B\u9000\u51FA\u7801 ${String(code)}`));
177
+ });
178
+ });
179
+ }
180
+ async function install(target, packageSpec) {
181
+ if (target.desktopPnpm === void 0) return runCliInstall(target, packageSpec);
182
+ const handle = target.desktopPnpm.runPlugin(["add", "--config.minimumReleaseAge=0", packageSpec, "--registry=https://registry.npmjs.org/"], target.profileDir);
183
+ const result = await handle.done;
184
+ if (result.exitCode !== 0) throw new Error(`\u66F4\u65B0\u8FDB\u7A0B\u9000\u51FA\u7801 ${String(result.exitCode)}\u3002`);
185
+ }
186
+ function json(response, statusCode, value) {
187
+ response.writeHead(statusCode, { "content-type": "application/json; charset=utf-8", "cache-control": "no-store" });
188
+ response.end(JSON.stringify(value));
189
+ }
190
+ function publicError(error) {
191
+ const message = error instanceof Error ? error.message : "\u66F4\u65B0\u6682\u4E0D\u53EF\u7528\u3002";
192
+ return /[A-Za-z]:[\\/]|\/(?:home|root|Users|var|tmp)\//.test(message) ? "\u66F4\u65B0\u5931\u8D25\uFF0C\u8BF7\u67E5\u770B\u670D\u52A1\u7AEF\u65E5\u5FD7\u3002" : message;
193
+ }
194
+ function registerPluginUpdater(ctx, options) {
195
+ const host = ctx;
196
+ let installing = false;
197
+ return host.webServer.register({
198
+ kind: "exact",
199
+ path: options.endpoint,
200
+ handler: async (request, response) => {
201
+ try {
202
+ const target = runtime(host);
203
+ if (request.method === "GET" || request.method === "HEAD") {
204
+ const payload = await status(options, target);
205
+ response.writeHead(200, { "content-type": "application/json; charset=utf-8", "cache-control": "no-store" });
206
+ response.end(request.method === "HEAD" ? void 0 : JSON.stringify(payload));
207
+ return;
208
+ }
209
+ if (request.method !== "POST") {
210
+ response.writeHead(405, { allow: "GET, HEAD, POST" });
211
+ response.end();
212
+ return;
213
+ }
214
+ if (!isTrustedUpdateRequest(request)) {
215
+ json(response, 403, { error: "\u5DF2\u62D2\u7EDD\u975E\u672C\u673A\u540C\u6E90\u66F4\u65B0\u8BF7\u6C42\u3002" });
216
+ return;
217
+ }
218
+ if (installing) {
219
+ json(response, 409, { error: "\u5F53\u524D\u63D2\u4EF6\u6B63\u5728\u66F4\u65B0\uFF0C\u8BF7\u7A0D\u5019\u3002" });
220
+ return;
221
+ }
222
+ const before = await status(options, target);
223
+ if (before.latestVersion === void 0) {
224
+ json(response, 503, { error: "\u6682\u65F6\u65E0\u6CD5\u83B7\u53D6\u6700\u65B0\u7248\u672C\u3002" });
225
+ return;
226
+ }
227
+ if (!before.updateAvailable) {
228
+ json(response, 200, before);
229
+ return;
230
+ }
231
+ installing = true;
232
+ try {
233
+ await install(target, `${options.packageName}@${before.latestVersion}`);
234
+ } finally {
235
+ installing = false;
236
+ }
237
+ const notifyParent = target.desktopPnpm === void 0 && typeof process.send === "function";
238
+ const autoReload = target.desktopPnpm !== void 0 || notifyParent;
239
+ json(response, 200, { ...before, updatedVersion: before.latestVersion, restartRequired: true, autoReload });
240
+ if (notifyParent) setTimeout(() => {
241
+ process.send?.(PLUGIN_UPDATE_IPC);
242
+ }, 150).unref?.();
243
+ } catch (error) {
244
+ ctx.logger.warn(`plugin updater failed: ${String(error)}`);
245
+ json(response, 503, { error: publicError(error) });
246
+ }
247
+ }
248
+ });
249
+ }
250
+ export {
251
+ PLUGIN_UPDATE_HEADER,
252
+ PLUGIN_UPDATE_IPC,
253
+ isDshCliEntry,
254
+ isNewerVersion,
255
+ isTrustedUpdateRequest,
256
+ registerPluginUpdater
257
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@michengai/dsh-skills-manager",
3
- "version": "0.1.40",
3
+ "version": "0.1.43",
4
4
  "description": "NPM-installable DSH Web plugin for safely loading and managing skills across DSH and common local Agents.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -77,7 +77,7 @@
77
77
  "esbuild": "0.25.0"
78
78
  },
79
79
  "engines": {
80
- "node": ">=20"
80
+ "node": "^22.19.0 || >=24.0.0"
81
81
  },
82
82
  "keywords": [
83
83
  "deepseek-harness",