dshmarket 1.2.4 → 1.3.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 fkysly and dsh-market contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -32,6 +32,7 @@ Restart `dsh web`, then open **Settings → Plugin Market**.
32
32
  - **One-click install** — confirm the source, watch live progress; most plugins go live after a page refresh, no restart
33
33
  - **Updates** — per-plugin update checks (npm version or pinned commit vs HEAD), one-click update, or update everything at once; the market updates itself the same way
34
34
  - **Uninstall** — two-step confirm; plugins installed this session are removed live
35
+ - **Restart when needed** — changes that cannot hot-load show a one-click restart beside the pending-change banner; the action is restricted to same-origin loopback requests
35
36
  - **Zero jargon** — if a component is missing (pnpm), the market detects it and offers a one-click automatic setup
36
37
  - **Log export** — one click produces a sanitized plain-text log for bug reports (home paths and credential shapes are masked; nothing is ever sent anywhere)
37
38
 
@@ -45,8 +46,15 @@ Installs prefer npm tarballs over full-repo GitHub downloads whenever a plugin p
45
46
  - Build scripts stay blocked by default (pnpm ≥10); allowing one is your explicit per-package choice
46
47
  - Terminal/CLI-surface plugins are flagged before you install them into the web profile
47
48
  - The install endpoint accepts same-origin POST only; the market never phones home
49
+ - The restart endpoint additionally requires a direct loopback client (forwarded requests are rejected) and relaunches the exact DSH entry, arguments, environment, and working directory
50
+ - One-click restart launches a detached replacement. If DSH is managed by systemd, launchd, pm2, or another supervisor, set the plugin option `allowRestart: false` and let the supervisor own restarts instead; the pending-change notice remains visible but the button is hidden
51
+ - For terminal-attached launches, the detached replacement keeps running after the original terminal closes
48
52
  - Listing ≠ endorsement: plugins are third-party code, install sources you trust
49
53
 
54
+ ## Submit your plugin
55
+
56
+ **This repo is the market app, not the catalog.** The plugin list comes from the curated [awesome-dsh-plugin](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin) registry — to get your plugin listed in the market, open a PR **there** (one entry in the list; the site and this market pick it up automatically, usually within a day). Please don't PR plugin entries against this repo.
57
+
50
58
  ## Data source
51
59
 
52
60
  Live from [awesome-dsh-plugin.com/plugins.json](https://awesome-dsh-plugin.com/plugins.json) — curated entries, npm mapping, and star counts refreshed daily by CI — with a bundled snapshot as offline fallback.
package/README.zh.md CHANGED
@@ -32,6 +32,7 @@ dsh plugin --profile web add dshmarket
32
32
  - **一键安装**——确认来源,实时进度;多数插件刷新页面即可用,无需重启
33
33
  - **更新**——逐插件检测(npm 版本或锁定 commit 对比 HEAD),一键更新或全部更新;市场自己也走同一通道升级
34
34
  - **卸载**——两步确认防误触;本次会话装的插件即点即卸
35
+ - **按需重启**——无法热加载的变更会在待重启提示旁显示一键重启;操作仅接受本机同源请求
35
36
  - **零术语**——缺组件(pnpm)时市场自己发现、一键自动装好,全程不见命令行
36
37
  - **导出日志**——一键生成脱敏纯文本日志方便反馈(home 路径与密钥形状已打码;任何数据都不会被上传)
37
38
 
@@ -45,8 +46,15 @@ dsh plugin --profile web add dshmarket
45
46
  - 构建脚本默认禁止执行(pnpm ≥10),放行与否由你按包显式决定
46
47
  - 终端/命令行类插件装进网页版前会被明确提醒
47
48
  - 安装接口只接受同源 POST;市场不会向任何地方上报数据
49
+ - 重启接口还要求客户端直接来自环回地址(拒绝代理转发请求),并使用原入口、参数、环境和工作目录重新启动 DSH
50
+ - 一键重启会启动脱离终端的替代进程。若 DSH 由 systemd、launchd、pm2 等进程管理器托管,请设置插件选项 `allowRestart: false`,交由管理器负责重启;待重启提示仍会显示,但按钮会隐藏
51
+ - 从终端启动时,替代进程脱离原终端,关闭原终端后仍会继续运行
48
52
  - 收录 ≠ 背书:插件是第三方代码,请只安装你信任的来源
49
53
 
54
+ ## 提交你的插件
55
+
56
+ **这个仓库是市场应用本身,不是插件目录。** 市场里的插件列表来自精选列表 [awesome-dsh-plugin](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin)——想让你的插件上架,请去**那边**提 PR(在列表里加一条即可,站点和本市场会自动收录,通常一天内生效)。请不要往本仓库提插件条目。
57
+
50
58
  ## 数据源
51
59
 
52
60
  实时来自 [awesome-dsh-plugin.com/plugins.json](https://awesome-dsh-plugin.com/plugins.json)——精选条目、npm 映射、star 数由 CI 每日刷新——内置快照做离线兜底。
package/client/client.js CHANGED
@@ -43,6 +43,10 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
43
43
  busyWait: "已有操作正在进行,请等它结束(同一时间只执行一个安装/更新/卸载)",
44
44
  approveBuilds: "放行构建脚本并重试",
45
45
  buildsSkipped: "该插件需要运行构建脚本才能工作,出于安全默认被拦下。点击下方按钮为它放行并重装:",
46
+ restartNow: "立即重启",
47
+ restarting: "正在重启…",
48
+ restartFail: "重启失败",
49
+ restartTimeout: "等待 DeepSeek Harness 启动超时",
46
50
  updateNow: "立即更新",
47
51
  updateFail: "更新失败",
48
52
  upToDate: "已是最新",
@@ -107,6 +111,10 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
107
111
  busyWait: "Another operation is already running — please wait for it to finish (one install/update/uninstall at a time)",
108
112
  approveBuilds: "Allow build scripts and retry",
109
113
  buildsSkipped: "This plugin needs its build scripts to run; they are blocked by default for safety. Click below to allow them and reinstall:",
114
+ restartNow: "Restart now",
115
+ restarting: "Restarting…",
116
+ restartFail: "Restart failed",
117
+ restartTimeout: "Timed out waiting for DeepSeek Harness to start",
110
118
  updateNow: "Update now",
111
119
  updateFail: "Update failed",
112
120
  upToDate: "Up to date",
@@ -227,10 +235,10 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
227
235
  return matchInstalledName(plugin, installed) !== null;
228
236
  }
229
237
  /**
230
- * The brand mark (assets/logo.svg shared block-grid mark with
231
- * awesome-dsh-plugin), inlined so the header needs no extra request.
238
+ * The header brand mark now lives in MarketSection.tsx as an inline SVG
239
+ * (official-style monochrome glyph, fill="currentColor") so it follows the
240
+ * active theme; the colored assets/logo.svg tile is no longer inlined here.
232
241
  */
233
- const LOGO_URI = "data:image/svg+xml," + encodeURIComponent("<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 128 128\"><rect width=\"128\" height=\"128\" rx=\"28\" fill=\"#f6f2ea\"/><g transform=\"translate(15.7 16.7) scale(0.3) translate(-112 -78)\"><g fill=\"#2b2620\"><rect x=\"112\" y=\"112\" width=\"88\" height=\"88\" rx=\"14\"/><rect x=\"212\" y=\"112\" width=\"88\" height=\"88\" rx=\"14\"/><rect x=\"112\" y=\"212\" width=\"88\" height=\"88\" rx=\"14\"/><rect x=\"212\" y=\"212\" width=\"88\" height=\"88\" rx=\"14\"/><rect x=\"112\" y=\"312\" width=\"88\" height=\"88\" rx=\"14\"/><rect x=\"212\" y=\"312\" width=\"88\" height=\"88\" rx=\"14\"/><rect x=\"312\" y=\"212\" width=\"88\" height=\"88\" rx=\"14\"/><rect x=\"312\" y=\"312\" width=\"88\" height=\"88\" rx=\"14\"/></g><rect x=\"346\" y=\"78\" width=\"88\" height=\"88\" rx=\"14\" fill=\"#c0392b\" transform=\"rotate(9 390 122)\"/></g></svg>");
234
242
  /** Four representative colors for a theme card's preview strip. */
235
243
  function themeSwatch(def) {
236
244
  const tk = def.tokens || {};
@@ -285,61 +293,61 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
285
293
  }
286
294
  var Market_module_css_default = {
287
295
  "warnBtn": "eGUBIq_warnBtn",
288
- "grow": "eGUBIq_grow",
289
- "sentinel": "eGUBIq_sentinel",
290
- "body": "eGUBIq_body",
291
- "titleRow": "eGUBIq_titleRow",
292
- "tag": "eGUBIq_tag",
293
- "sect": "eGUBIq_sect",
294
- "catsRow": "eGUBIq_catsRow",
295
- "dangerArmed": "eGUBIq_dangerArmed",
296
- "catsCollapsed": "eGUBIq_catsCollapsed",
297
- "head": "eGUBIq_head",
298
296
  "star": "eGUBIq_star",
299
- "swatches": "eGUBIq_swatches",
300
- "card": "eGUBIq_card",
301
- "grid": "eGUBIq_grid",
302
- "root": "eGUBIq_root",
297
+ "catsWrap": "eGUBIq_catsWrap",
298
+ "row1": "eGUBIq_row1",
299
+ "owner": "eGUBIq_owner",
300
+ "spin": "eGUBIq_spin",
301
+ "empty": "eGUBIq_empty",
302
+ "sub": "eGUBIq_sub",
303
+ "dangerArmed": "eGUBIq_dangerArmed",
304
+ "warnLine": "eGUBIq_warnLine",
305
+ "tabs": "eGUBIq_tabs",
306
+ "title": "eGUBIq_title",
307
+ "sort": "eGUBIq_sort",
303
308
  "tab": "eGUBIq_tab",
304
- "av": "eGUBIq_av",
305
- "searchInline": "eGUBIq_searchInline",
306
- "desc": "eGUBIq_desc",
307
- "foot": "eGUBIq_foot",
308
309
  "themesGrid": "eGUBIq_themesGrid",
309
- "src": "eGUBIq_src",
310
+ "root": "eGUBIq_root",
311
+ "grow": "eGUBIq_grow",
312
+ "staleAction": "eGUBIq_staleAction",
313
+ "cmdDetails": "eGUBIq_cmdDetails",
314
+ "cancelBtn": "eGUBIq_cancelBtn",
315
+ "nm": "eGUBIq_nm",
316
+ "desc": "eGUBIq_desc",
317
+ "catsToggle": "eGUBIq_catsToggle",
318
+ "irow": "eGUBIq_irow",
319
+ "loading": "eGUBIq_loading",
320
+ "cmd": "eGUBIq_cmd",
321
+ "restart": "eGUBIq_restart",
322
+ "sect": "eGUBIq_sect",
323
+ "titleRow": "eGUBIq_titleRow",
324
+ "catsRow": "eGUBIq_catsRow",
325
+ "dangerBtn": "eGUBIq_dangerBtn",
310
326
  "dot": "eGUBIq_dot",
311
- "spin": "eGUBIq_spin",
312
- "descTight": "eGUBIq_descTight",
313
- "row1": "eGUBIq_row1",
327
+ "cats": "eGUBIq_cats",
314
328
  "progress": "eGUBIq_progress",
315
- "err": "eGUBIq_err",
316
- "title": "eGUBIq_title",
317
- "restart": "eGUBIq_restart",
318
- "cmdSummary": "eGUBIq_cmdSummary",
319
- "loading": "eGUBIq_loading",
320
329
  "sp": "eGUBIq_sp",
330
+ "err": "eGUBIq_err",
321
331
  "spec": "eGUBIq_spec",
322
- "staleAction": "eGUBIq_staleAction",
323
- "cancelBtn": "eGUBIq_cancelBtn",
324
- "tabs": "eGUBIq_tabs",
332
+ "pct": "eGUBIq_pct",
333
+ "tag": "eGUBIq_tag",
334
+ "catsCollapsed": "eGUBIq_catsCollapsed",
335
+ "src": "eGUBIq_src",
336
+ "cmdSummary": "eGUBIq_cmdSummary",
325
337
  "on": "eGUBIq_on",
326
- "top": "eGUBIq_top",
327
- "sub": "eGUBIq_sub",
338
+ "sentinel": "eGUBIq_sentinel",
339
+ "searchInline": "eGUBIq_searchInline",
340
+ "foot": "eGUBIq_foot",
341
+ "swatches": "eGUBIq_swatches",
328
342
  "modalNote": "eGUBIq_modalNote",
329
- "cmdDetails": "eGUBIq_cmdDetails",
330
- "owner": "eGUBIq_owner",
343
+ "body": "eGUBIq_body",
344
+ "descTight": "eGUBIq_descTight",
345
+ "av": "eGUBIq_av",
346
+ "top": "eGUBIq_top",
331
347
  "okState": "eGUBIq_okState",
332
- "empty": "eGUBIq_empty",
333
- "cmd": "eGUBIq_cmd",
334
- "pct": "eGUBIq_pct",
335
- "nm": "eGUBIq_nm",
336
- "sort": "eGUBIq_sort",
337
- "warnLine": "eGUBIq_warnLine",
338
- "irow": "eGUBIq_irow",
339
- "cats": "eGUBIq_cats",
340
- "catsToggle": "eGUBIq_catsToggle",
341
- "dangerBtn": "eGUBIq_dangerBtn",
342
- "catsWrap": "eGUBIq_catsWrap"
348
+ "card": "eGUBIq_card",
349
+ "head": "eGUBIq_head",
350
+ "grid": "eGUBIq_grid"
343
351
  };
344
352
  //#endregion
345
353
  //#region src/client/MarketSection.tsx
@@ -368,6 +376,31 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
368
376
  });
369
377
  }
370
378
  /**
379
+ * Official-style market glyph: the shared block-grid brand mark converted to
380
+ * the official monochrome icon form (16×16, fill="currentColor") so it
381
+ * follows the active theme. Mirrors the settings-nav glyph used for the
382
+ * "market" section id.
383
+ */
384
+ function MarketLogo({ size = 16, style }) {
385
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
386
+ width: size,
387
+ height: size,
388
+ viewBox: "0 0 16 16",
389
+ fill: "none",
390
+ xmlns: "http://www.w3.org/2000/svg",
391
+ "aria-hidden": "true",
392
+ style,
393
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
394
+ fill: "currentColor",
395
+ d: "M2.35 1.75H4.95A0.6 0.6 0 0 1 5.55 2.35V4.95A0.6 0.6 0 0 1 4.95 5.55H2.35A0.6 0.6 0 0 1 1.75 4.95V2.35A0.6 0.6 0 0 1 2.35 1.75ZM6.7 1.75H9.3A0.6 0.6 0 0 1 9.9 2.35V4.95A0.6 0.6 0 0 1 9.3 5.55H6.7A0.6 0.6 0 0 1 6.1 4.95V2.35A0.6 0.6 0 0 1 6.7 1.75ZM2.35 6.1H4.95A0.6 0.6 0 0 1 5.55 6.7V9.3A0.6 0.6 0 0 1 4.95 9.9H2.35A0.6 0.6 0 0 1 1.75 9.3V6.7A0.6 0.6 0 0 1 2.35 6.1ZM6.7 6.1H9.3A0.6 0.6 0 0 1 9.9 6.7V9.3A0.6 0.6 0 0 1 9.3 9.9H6.7A0.6 0.6 0 0 1 6.1 9.3V6.7A0.6 0.6 0 0 1 6.7 6.1ZM11.05 6.1H13.65A0.6 0.6 0 0 1 14.25 6.7V9.3A0.6 0.6 0 0 1 13.65 9.9H11.05A0.6 0.6 0 0 1 10.45 9.3V6.7A0.6 0.6 0 0 1 11.05 6.1ZM2.35 10.45H4.95A0.6 0.6 0 0 1 5.55 11.05V13.65A0.6 0.6 0 0 1 4.95 14.25H2.35A0.6 0.6 0 0 1 1.75 13.65V11.05A0.6 0.6 0 0 1 2.35 10.45ZM6.7 10.45H9.3A0.6 0.6 0 0 1 9.9 11.05V13.65A0.6 0.6 0 0 1 9.3 14.25H6.7A0.6 0.6 0 0 1 6.1 13.65V11.05A0.6 0.6 0 0 1 6.7 10.45ZM11.05 10.45H13.65A0.6 0.6 0 0 1 14.25 11.05V13.65A0.6 0.6 0 0 1 13.65 14.25H11.05A0.6 0.6 0 0 1 10.45 13.65V11.05A0.6 0.6 0 0 1 11.05 10.45Z"
396
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
397
+ fill: "currentColor",
398
+ d: "M11.05 1.75H13.65A0.6 0.6 0 0 1 14.25 2.35V4.95A0.6 0.6 0 0 1 13.65 5.55H11.05A0.6 0.6 0 0 1 10.45 4.95V2.35A0.6 0.6 0 0 1 11.05 1.75Z",
399
+ transform: "rotate(9 12.35 3.65)"
400
+ })]
401
+ });
402
+ }
403
+ /**
371
404
  * Module-scope caches so re-entering the section renders instantly instead
372
405
  * of refetching and rebuilding from a spinner (#30 by @StarsTom). Module
373
406
  * state survives section switches; a background refetch keeps it current.
@@ -396,6 +429,8 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
396
429
  const [cat, setCat] = (0, react.useState)("all");
397
430
  const [confirming, setConfirming] = (0, react.useState)(null);
398
431
  const [busyUrl, setBusyUrl] = (0, react.useState)(null);
432
+ /** Consecutive idle polls with a pending install that never landed (#32). */
433
+ const idleStrikes = (0, react.useRef)(0);
399
434
  const [doneUrls, setDoneUrls] = (0, react.useState)([]);
400
435
  const [installError, setInstallError] = (0, react.useState)(null);
401
436
  const [updates, setUpdates] = (0, react.useState)({});
@@ -429,6 +464,9 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
429
464
  const [envFixing, setEnvFixing] = (0, react.useState)(false);
430
465
  const [envFailed, setEnvFailed] = (0, react.useState)(false);
431
466
  const [bootId, setBootId] = (0, react.useState)(null);
467
+ /** One-click restart (#14 by @ysyyhhh): server capability + in-flight state. */
468
+ const [restartEnabled, setRestartEnabled] = (0, react.useState)(false);
469
+ const [restarting, setRestarting] = (0, react.useState)(false);
432
470
  const [showTop, setShowTop] = (0, react.useState)(false);
433
471
  const bodyRef = (0, react.useRef)(null);
434
472
  const [sort, setSort] = (0, react.useState)("hot");
@@ -453,6 +491,7 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
453
491
  fetch("/dsh-market/status", { cache: "no-store" }).then((res) => res.json()).then((status) => {
454
492
  setEnvReady(status.pnpm !== false);
455
493
  if (typeof status.boot === "string") setBootId(status.boot);
494
+ setRestartEnabled(status.restart === true);
456
495
  }).catch(() => {});
457
496
  refreshInstalled();
458
497
  }, [refreshInstalled]);
@@ -492,7 +531,10 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
492
531
  body: "{}"
493
532
  }).then((res) => res.json()).then((body) => {
494
533
  if (body.ok) setEnvReady(true);
495
- else setEnvFailed(true);
534
+ else {
535
+ setEnvFailed(true);
536
+ if (typeof body.error === "string") setInstallError(body.error);
537
+ }
496
538
  }).catch(() => setEnvFailed(true)).finally(() => setEnvFixing(false));
497
539
  }, []);
498
540
  (0, react.useEffect)(() => {
@@ -519,9 +561,15 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
519
561
  setInstalled(status.installed || {});
520
562
  if (readSession("dshm-pending") !== null && busyUrl !== null) {
521
563
  if (data !== null && data.plugins.some((p) => p.url === busyUrl && isInstalled(p, status.installed || {}))) {
564
+ idleStrikes.current = 0;
522
565
  sessionStorage.removeItem("dshm-pending");
523
566
  setDoneUrls((urls) => urls.includes(busyUrl) ? urls : urls.concat(busyUrl));
524
567
  setBusyUrl(null);
568
+ } else if (++idleStrikes.current >= 2) {
569
+ idleStrikes.current = 0;
570
+ sessionStorage.removeItem("dshm-pending");
571
+ setBusyUrl(null);
572
+ setInstallError(t("installFail") + " — " + t("exportLog"));
525
573
  }
526
574
  }
527
575
  }
@@ -602,6 +650,57 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
602
650
  setInstallError(t("installFail") + ": " + String(error));
603
651
  }).finally(() => setBusyUrl(null));
604
652
  }, [refreshInstalled, t]);
653
+ /**
654
+ * Restart the host and reload once the boot id changes (#14 by @ysyyhhh).
655
+ * The 202 races the process's SIGTERM, so network errors on the initial
656
+ * request are expected and treated as "restart under way".
657
+ */
658
+ const doRestart = (0, react.useCallback)(() => {
659
+ if (bootId === null || restarting) return;
660
+ const previousBoot = bootId;
661
+ setRestarting(true);
662
+ setInstallError(null);
663
+ const awaitNewBoot = () => {
664
+ const deadline = Date.now() + 6e4;
665
+ const poll = () => {
666
+ fetch("/dsh-market/status", { cache: "no-store" }).then((res) => res.json()).then((next) => {
667
+ if (typeof next.boot === "string" && next.boot !== previousBoot) {
668
+ location.reload();
669
+ return;
670
+ }
671
+ retry();
672
+ }).catch(retry);
673
+ };
674
+ const retry = () => {
675
+ if (Date.now() > deadline) {
676
+ setRestarting(false);
677
+ setInstallError(t("restartTimeout"));
678
+ return;
679
+ }
680
+ setTimeout(poll, 1500);
681
+ };
682
+ poll();
683
+ };
684
+ fetch("/dsh-market/restart", {
685
+ method: "POST",
686
+ headers: { "content-type": "application/json" },
687
+ body: "{}"
688
+ }).then((res) => res.json().then((body) => ({
689
+ status: res.status,
690
+ body
691
+ }))).then(({ status, body }) => {
692
+ if (status !== 202 || body.ok !== true) {
693
+ setRestarting(false);
694
+ setInstallError(t("restartFail") + ": " + String(body.error || "HTTP " + String(status)));
695
+ return;
696
+ }
697
+ awaitNewBoot();
698
+ }).catch(awaitNewBoot);
699
+ }, [
700
+ bootId,
701
+ restarting,
702
+ t
703
+ ]);
605
704
  /** Cancel the running plugin command (#6 by @qichuang321). */
606
705
  const doCancel = (0, react.useCallback)(() => {
607
706
  fetch("/dsh-market/cancel", {
@@ -951,15 +1050,9 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
951
1050
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
952
1051
  className: Market_module_css_default.titleRow,
953
1052
  children: [
954
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
955
- src: LOGO_URI,
956
- width: 22,
957
- height: 22,
958
- alt: "",
959
- style: {
960
- borderRadius: "5px",
961
- flexShrink: 0
962
- }
1053
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MarketLogo, {
1054
+ size: 22,
1055
+ style: { flexShrink: 0 }
963
1056
  }),
964
1057
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
965
1058
  className: Market_module_css_default.title,
@@ -1092,6 +1185,13 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
1092
1185
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1093
1186
  title: t("restartHint"),
1094
1187
  children: "ℹ️"
1188
+ }),
1189
+ restartEnabled && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1190
+ variant: "primary",
1191
+ size: "sm",
1192
+ disabled: restarting || busyUrl !== null || updatingName !== null || removingName !== null,
1193
+ onClick: doRestart,
1194
+ children: restarting ? t("restarting") : t("restartNow")
1095
1195
  })
1096
1196
  ]
1097
1197
  })