dsh-plugin-notify-sounds 1.6.2 → 1.6.4

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/README.md CHANGED
@@ -123,6 +123,8 @@ For anyone hacking on the code (exact details live in the source comments):
123
123
 
124
124
  ## Changelog
125
125
 
126
+ - **v1.6.4** (2026-10-11) No more scrolling to find the news: an update swaps the plugin's own client file and the page reloads at the top, so the row now scrolls itself back into view when the outcome appears.
127
+ - **v1.6.3** (2026-10-11) The update-success message no longer flashes and vanishes: the Host remembers the install and repeats it with every check, so "Updated, restart DSH to load it" survives the page reload the update itself triggers (it swaps the client half's own file) and stays until the restart. The cached check is invalidated after an install too, so the row can no longer claim a newer version than the one it just wrote.
126
128
  - **v1.6.2** (2026-10-11) Fixed one-click update always failing when the mainland mirror answered the check: the mirror serves its tarballs from its CDN, and the downloader refused every redirect — so the download could never finish (found by the live drill). Redirects are now followed one hop at a time with every hop checked against the trusted hosts (the mirror's CDN is the one legitimate hop); the sha512 check is unchanged. A failure now names the stage it died at (check / download / verify / unpack / replace).
127
129
  - **v1.6.1** (2026-10-11) The README is rebuilt as a release page: a new **Consistent with DSH's own UI** section on how the UI matches DSH's own settings, copy polished throughout, and the upgrade notes corrected — the plugin updates itself, so no uninstall is needed. This release doubles as the live drill for one-click update.
128
130
  - **v1.6.0** (2026-10-11) New: **check for updates and install them in one click**. The settings section can fetch the newest npm version and install it: the download is checked against the registry's published sha512, only whitelisted files are unpacked (symlinks and path traversal are refused), the old files are kept aside, and any failure restores them. Downloaded code is never executed — the new code loads when DSH restarts. Automatic checks run at most once a day and can be turned off, with the mainland-China mirror as fallback.
package/README.zh.md CHANGED
@@ -123,6 +123,8 @@ dsh plugin --profile <你的 profile> add ./dsh-plugin-notify-sounds
123
123
 
124
124
  ## 更新记录
125
125
 
126
+ - **v1.6.4**(2026-10-11)更新完成后不用再手动往下翻:更新会替换插件自身的客户端文件、触发页面重载并回到顶部,现在「更新完成」提示出现时,「更新」行会**平滑滚动回视野**。
127
+ - **v1.6.3**(2026-10-11)更新成功的提示不再一闪而过:更新结果改由**宿主**记住、随每次检查下发——即便更新替换掉了客户端自身文件、页面热重载丢掉本地状态,「更新完成:vX → vY,重启 DSH 后生效」也会一直显示到你重启;同时更新后**作废检查缓存**,不会再出现「明明刚升完却仍显示有新版」。
126
128
  - **v1.6.2**(2026-10-11)修复一键更新在**镜像源**下必然失败:npmmirror 的 tarball 会跳转到它的 CDN,而下载器此前拒绝一切重定向,于是「只要检查由镜像应答,下载就永远到不了」(真机演练实测发现)。现在重定向改为逐跳跟随、每一跳都必须仍落在受信主机上(镜像 CDN 是唯一合法跳转),sha512 校验不变;失败提示会带上失败阶段(下载 / 校验 / 解包 / 替换),便于定位。
127
129
  - **v1.6.1**(2026-10-11)README 重排为发布页风格:补「**与 DSH 界面保持一致**」一节讲清界面取自 DSH 自身设计体系;通篇表达润色,并修正升级说明(本插件自带更新,无需卸载重装)。本版同时是「一键更新」的发布演练。
128
130
  - **v1.6.0**(2026-10-11)新增**检查更新与一键更新**:设置里可查 npm 新版本并一键安装——下载后按 registry 公布的 sha512 校验、只解包白名单文件(拒绝符号链接与路径穿越)、先备份再替换、失败自动还原;下载内容从不执行,新代码在 DSH 重启时生效。自动检查每天最多一次、可关;npm 官方源不通时自动改用国内镜像。
package/lib/client.js CHANGED
@@ -1583,6 +1583,14 @@ window.__ModuleLoader__.load({
1583
1583
  }, []);
1584
1584
  const payload = state.payload;
1585
1585
  const version = (text) => (typeof text === "string" && text !== "" ? `v${text}` : "?");
1586
+ // The Host remembers the last install and repeats it with every
1587
+ // answer, and this row prefers that: an update swaps the client
1588
+ // half's own file, so the page may reload and drop this row's
1589
+ // state mid-message — the news has to survive that. The Host keeps
1590
+ // it until DSH restarts, which is exactly as long as "restart to
1591
+ // load it" is true.
1592
+ const applied = payload?.applied ?? (state.phase === "updated" ? state.result : undefined);
1593
+ const appliedDone = applied !== undefined && (applied.ok === true || typeof applied.to === "string");
1586
1594
  let status = payload === undefined
1587
1595
  ? t("update.idle")
1588
1596
  : payload.updateAvailable === true
@@ -1590,20 +1598,29 @@ window.__ModuleLoader__.load({
1590
1598
  : t("update.uptodate");
1591
1599
  if (state.phase === "checking") status = t("update.checking");
1592
1600
  if (state.phase === "updating") status = t("update.updating");
1593
- if (state.phase === "applyFailed") status = t("update.applyFailed", { stage: String(state.result?.stage ?? "?") });
1594
1601
  let detail = payload === undefined ? status : `${t("update.current")} ${version(payload.current)} · ${status}`;
1595
- if (state.phase === "updated") {
1596
- detail = `${t("update.done")}:${version(state.result?.from)} → ${version(state.result?.to)},${t("update.restart")}`;
1602
+ if (appliedDone) {
1603
+ detail = `${t("update.done")}:${version(applied.from)} → ${version(applied.to)},${t("update.restart")}`;
1604
+ } else if (state.phase === "applyFailed") {
1605
+ detail = t("update.applyFailed", { stage: String(state.result?.stage ?? "?") });
1597
1606
  }
1598
1607
  const busy = state.phase === "checking" || state.phase === "updating";
1599
- return h("div", { className: css.row }, [
1608
+ // An update swaps this very file: DSH reloads the client half and
1609
+ // the settings section is rebuilt from the top, so the news would
1610
+ // land somewhere the user has to scroll to find. Bring the row
1611
+ // back into view instead — once per outcome, not on every render.
1612
+ const rowRef = useRef(null);
1613
+ useEffect(() => {
1614
+ if (appliedDone) rowRef.current?.scrollIntoView?.({ block: "center", behavior: "smooth" });
1615
+ }, [appliedDone]);
1616
+ return h("div", { className: css.row, ref: rowRef }, [
1600
1617
  h("div", { key: "text" }, [
1601
1618
  h("div", { className: css.rowTitle, key: "title" }, t("update.title")),
1602
1619
  h("div", { className: css.rowDescription, key: "description" }, t("update.description")),
1603
1620
  h("div", { className: css.hint, key: "status" }, detail),
1604
1621
  ]),
1605
1622
  h("div", { className: css.stackControl, key: "control" }, [
1606
- payload?.updateAvailable === true && state.phase !== "updated" ? h(Button, {
1623
+ payload?.updateAvailable === true && !appliedDone ? h(Button, {
1607
1624
  key: "apply",
1608
1625
  variant: "outline",
1609
1626
  size: "sm",
package/lib/index.js CHANGED
@@ -468,8 +468,8 @@ const UPDATE_CHECK_SPACING_MS = 15 * 1000;
468
468
  const UPDATE_FETCH_TIMEOUT_MS = 10 * 1000;
469
469
  const UPDATE_MAX_RESPONSE_BYTES = 256 * 1024;
470
470
 
471
- /** Result of the last check, and when the last real request went out. */
472
- const updateCheck = { checkedAt: 0, requestedAt: 0, result: undefined };
471
+ /** Result of the last check, when the last real request went out, and the last successful install. */
472
+ const updateCheck = { checkedAt: 0, requestedAt: 0, result: undefined, applied: undefined };
473
473
 
474
474
  /**
475
475
  * Parse `major.minor.patch` with an optional prerelease tag.
@@ -603,7 +603,7 @@ export async function checkForUpdate(options = {}) {
603
603
  const current = readOwnVersion(options.root);
604
604
  const answer = await fetchRegistryAnswer();
605
605
  if (answer === undefined) {
606
- const result = { ok: false, current, error: "no registry answered" };
606
+ const result = { ok: false, current, error: "no registry answered", applied: updateCheck.applied };
607
607
  updateCheck.checkedAt = now;
608
608
  updateCheck.result = result;
609
609
  return result;
@@ -615,6 +615,7 @@ export async function checkForUpdate(options = {}) {
615
615
  latest: answer.version,
616
616
  updateAvailable: Number.isFinite(ordering) && ordering < 0,
617
617
  checkedAt: now,
618
+ applied: updateCheck.applied,
618
619
  };
619
620
  updateCheck.checkedAt = now;
620
621
  updateCheck.result = result;
@@ -882,6 +883,16 @@ export async function applyUpdate(options = {}) {
882
883
  const files = unpackTar(gunzipSync(bytes));
883
884
  stage = "replace";
884
885
  installUpdate(root, files);
886
+ // The answer that said "update available" is now a lie about this
887
+ // installation: drop the cached check so the next one re-reads the
888
+ // manifest this install just wrote. The outcome is remembered too —
889
+ // the very files that were just swapped include the client half, so
890
+ // the page may reload and lose its own state, and the news that an
891
+ // update landed must survive that. It is kept until DSH restarts,
892
+ // which is exactly as long as "restart to load it" is true.
893
+ updateCheck.checkedAt = 0;
894
+ updateCheck.result = undefined;
895
+ updateCheck.applied = { from: current, to: answer.version, at: Date.now() };
885
896
  log(`update applied: ${current ?? "?"} -> ${answer.version}`);
886
897
  return { ok: true, from: current, to: answer.version, requiresRestart: true };
887
898
  } catch (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-plugin-notify-sounds",
3
- "version": "1.6.2",
3
+ "version": "1.6.4",
4
4
  "description": "Notification sounds for DeepSeek Harness — a soft chime when the agent needs your decision or finishes a task, with a Settings section styled like DSH's own · 需要决策或任务完成时播放提示音,点击系统通知可回到窗口,设置面板可调音量与音效",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",