@mrrisega/dsh-remote 0.4.4 → 0.4.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dsh-setup.mjs CHANGED
@@ -658,6 +658,91 @@ function declarePluginDep(pkgFile) {
658
658
  fs.writeFileSync(pkgFile, JSON.stringify(pkg, null, 2) + "\n");
659
659
  }
660
660
 
661
+ /** 把 dsh-remote-ui 加入 dsh.profile.bundles(幂等)。返回是否发生变更。 */
662
+ function ensureBundleEntry(pkgFile) {
663
+ const pkg = JSON.parse(fs.readFileSync(pkgFile, "utf8"));
664
+ const bundles = pkg.dsh && pkg.dsh.profile && Array.isArray(pkg.dsh.profile.bundles)
665
+ ? pkg.dsh.profile.bundles
666
+ : null;
667
+ if (bundles && bundles.includes("dsh-remote-ui")) return false;
668
+ pkg.dsh = pkg.dsh || {};
669
+ pkg.dsh.profile = pkg.dsh.profile || {};
670
+ pkg.dsh.profile.bundles = bundles || [];
671
+ pkg.dsh.profile.bundles.push("dsh-remote-ui");
672
+ fs.writeFileSync(pkgFile, JSON.stringify(pkg, null, 2) + "\n");
673
+ return true;
674
+ }
675
+
676
+ /** 从 dsh.profile.bundles 移除 dsh-remote-ui(幂等)。返回是否发生变更。 */
677
+ function removeBundleEntry(pkgFile) {
678
+ const pkg = JSON.parse(fs.readFileSync(pkgFile, "utf8"));
679
+ const bundles = pkg.dsh && pkg.dsh.profile && Array.isArray(pkg.dsh.profile.bundles)
680
+ ? pkg.dsh.profile.bundles
681
+ : null;
682
+ if (!bundles || !bundles.includes("dsh-remote-ui")) return false;
683
+ pkg.dsh.profile.bundles = bundles.filter((b) => b !== "dsh-remote-ui");
684
+ fs.writeFileSync(pkgFile, JSON.stringify(pkg, null, 2) + "\n");
685
+ return true;
686
+ }
687
+
688
+ /**
689
+ * 插件安装(pluginCmd 非卸载分支)收敛策略 —— 2026-09-06「重复 ID 崩溃」根治:
690
+ * dsh-remote-ui 是带 dsh.bundle.patch 的 bundle:package.json 的 dsh.profile.bundles
691
+ * 声明它后,加载器会自动应用插件自带的 cordis.patch.yml(节点半+浏览器半的唯一激活点)。
692
+ * 若用户级 cordis.patch.yml 再手工 insert 同一个 id,dsh web 启动即报“重复 ID”崩溃。
693
+ * 因此本命令【绝不写用户 include】,只负责:让插件以 bundle 形态可解析,
694
+ * 并清理历史遗留的 include 块。市场形态(github:/npm 依赖)则完全交由市场管理,只清理 include。
695
+ */
696
+ function convergePluginActivation(profileDir, pkgFile, patchFile, pluginDir, patch) {
697
+ const pkg = JSON.parse(fs.readFileSync(pkgFile, "utf8"));
698
+ const dep = pkg.dependencies && pkg.dependencies["dsh-remote-ui"];
699
+ const inBundles = !!(pkg.dsh && pkg.dsh.profile && Array.isArray(pkg.dsh.profile.bundles)
700
+ && pkg.dsh.profile.bundles.includes("dsh-remote-ui"));
701
+ const marketManaged = dep && !String(dep).startsWith("file:"); // github:/npm: 等由市场/包管理器管源码
702
+ const managedByUs = !dep || String(dep).startsWith("file:"); // 无依赖或 file: 拷贝 → 我们管
703
+
704
+ const stripInclude = (reason) => {
705
+ const newPatch = stripPluginEntries(patch);
706
+ if (newPatch !== patch) {
707
+ fs.writeFileSync(patchFile, newPatch);
708
+ console.log(`✅ 已移除 ${patchFile} 中的冗余 include(${reason};激活统一走插件自带 bundle patch,避免重复 ID 崩溃)`);
709
+ }
710
+ };
711
+
712
+ if (marketManaged && inBundles) {
713
+ // 插件市场安装形态:依赖与源码归市场管,我们只清历史 include(若旧版曾写过)
714
+ stripInclude("插件市场安装形态无需用户 include");
715
+ console.log("ℹ 插件市场安装形态(bundles+dependency):已保持市场管理的源码不变。");
716
+ return;
717
+ }
718
+
719
+ if (managedByUs) {
720
+ const pluginLocalDir = copyPluginIntoProfile(profileDir, pluginDir);
721
+ const entryFile = path.join(pluginLocalDir, "lib", "index.js");
722
+ if (!fs.existsSync(entryFile)) {
723
+ console.error(`❌ 插件入口缺失:${entryFile}(本包不完整?请用官方源重装:npx --registry=https://registry.npmjs.org @mrrisega/dsh-remote@latest)`);
724
+ process.exit(1);
725
+ }
726
+ console.log(`✅ 插件已拷贝到 ${pluginLocalDir}`);
727
+ declarePluginDep(pkgFile); // file: 依赖(包管理器 install 不误删)
728
+ const addedBundle = ensureBundleEntry(pkgFile); // bundles 声明 → 插件自带 patch 自动激活
729
+ const linked = ensurePluginLinked(profileDir, pluginLocalDir); // 自建 node_modules 链接
730
+ stripInclude("bundle patch 已是唯一激活点"); // 清历史 include
731
+ console.log(addedBundle
732
+ ? "✅ 已加入 dsh.profile.bundles(dsh-remote-ui 自带 patch 自动生效)"
733
+ : "ℹ dsh.profile.bundles 已含 dsh-remote-ui");
734
+ console.log(linked
735
+ ? `✅ 已建立 node_modules/dsh-remote-ui → ${pluginLocalDir}(免包管理器即可解析)`
736
+ : "✅ node_modules/dsh-remote-ui 已就绪");
737
+ console.log(`✅ 插件安装完成(bundle 形态,无用户 include)。配置目录: ${CONFIG_DIR}`);
738
+ return;
739
+ }
740
+
741
+ // 异常形态:有非 file: 依赖但不在 bundles(无法靠 bundle patch 激活)
742
+ console.log(`ℹ 检测到依赖 dsh-remote-ui(${dep}) 但未声明在 dsh.profile.bundles——插件不会激活。`);
743
+ console.log(" 请在 dsh 插件市场重新添加该插件,或先执行 `dsh-remote plugin --uninstall` 再一键安装。");
744
+ }
745
+
661
746
  async function pluginCmd(argv) {
662
747
  const uninstall = hasFlag(argv, "--uninstall");
663
748
  const profileIdx = argv.indexOf("--profile");
@@ -696,35 +781,17 @@ async function pluginCmd(argv) {
696
781
  if (pkg.dependencies) delete pkg.dependencies["dsh-remote-ui"];
697
782
  fs.writeFileSync(pkgFile, JSON.stringify(pkg, null, 2) + "\n");
698
783
  } catch { /* ignore */ }
784
+ // 同步移除 bundles 声明,避免“bundles 引用已删除包 → dsh web 启动报错”
785
+ try {
786
+ if (removeBundleEntry(pkgFile)) console.log("✅ 已从 dsh.profile.bundles 移除 dsh-remote-ui");
787
+ } catch { /* ignore */ }
699
788
  console.log("✅ 卸载完成。重启 dsh web 生效。");
700
789
  return;
701
790
  }
702
791
 
703
- // 安装 = 拷贝完整插件目录 + 写裸名 include + 自建 node_modules 链接 + 声明 file: 依赖。
704
- // 全程不跑 pnpm/npm;但链接与依赖双保险,任何包管理器之后 install 也不会破坏它。
705
- const pluginLocalDir = copyPluginIntoProfile(profileDir, pluginDir);
706
- const entryFile = path.join(pluginLocalDir, "lib", "index.js");
707
- if (!fs.existsSync(entryFile)) {
708
- console.error(`❌ 插件入口缺失:${entryFile}(本包不完整?请用官方源重装:npx --registry=https://registry.npmjs.org @mrrisega/dsh-remote@latest)`);
709
- process.exit(1);
710
- }
711
- console.log(`✅ 插件已拷贝到 ${pluginLocalDir}`);
712
-
713
- // 先清掉旧条目(含旧版无标记条目),再写入带标记的新块,保证不重复。
714
- // 关键:先清除默认的 [] 空文档占位行,否则拼接出的 YAML 非法,dsh web 启动即崩。
715
- const stripped = stripPluginEntries(patch);
716
- const base = normalizePatchBase(stripped);
717
- const block = pluginBlock(CONFIG_DIR);
718
- fs.writeFileSync(patchFile, (base ? base + "\n" : "") + block + "\n");
719
- console.log(`✅ 已写入 ${patchFile}(裸名 include:dsh-remote-ui → 节点半 + 浏览器半均生效)`);
720
-
721
- declarePluginDep(pkgFile); // package.json 声明 file: 依赖(后端各类 install 不误删)
722
- const linked = ensurePluginLinked(profileDir, pluginLocalDir); // 自建 node_modules 链接
723
- console.log(linked
724
- ? `✅ 已建立 node_modules/dsh-remote-ui → ${pluginLocalDir}(免包管理器即可解析)`
725
- : "✅ node_modules/dsh-remote-ui 已就绪");
726
-
727
- console.log(`✅ 插件安装完成。配置目录: ${CONFIG_DIR}`);
792
+ // 安装:收敛到“恰好一处激活”(bundle patch 唯一激活点),绝不与市场/历史 include 并存
793
+ convergePluginActivation(profileDir, pkgFile, patchFile, pluginDir, patch);
794
+
728
795
  console.log(" 打开 dsh web → 设置 → 「远程控制」,注册/登录手机号即可(无需任何命令)。");
729
796
  console.log(" 若从 DeepSeek App/插件市场 安装:请完全退出并重开 App 让插件生效。");
730
797
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrrisega/dsh-remote",
3
- "version": "0.4.4",
3
+ "version": "0.4.6",
4
4
  "description": "手机远程控制 DeepSeek Harness · Remote control DeepSeek Harness (dsh web) from any phone browser — 100% 全功能 App 级体验:发消息、看工具执行、审批权限、改设置、管凭据,含特权操作,免内网穿透。一条命令安装 npx @mrrisega/dsh-remote。Mobile remote control for DSH, self-host or SaaS, no server needed on LAN.",
5
5
  "keywords": [
6
6
  "deepseek-harness",
@@ -362,7 +362,9 @@ function appendLogLine(relayDir, name, line) {
362
362
 
363
363
  /** 插件市场只装了 UI 插件;若桌面缺 dsh-remote 运行环境(dsh-setup.mjs=bridge/自启动),
364
364
  * 由插件在后台自动执行一次 `npx @mrrisega/dsh-remote` 补齐,用户无需手动跑命令。
365
- * 已有环境(包括手动 npx 装过)直接跳过。返回 true=已就绪。 */
365
+ * 已有环境(包括手动 npx 装过)直接跳过。返回 true=已就绪。
366
+ * 注意:默认优先官方源——镜像(npmmirror)可能滞后于刚发布的版本,装到旧版会把
367
+ * 已被 0.4.5 移除的“用户 include”重新写回 profile(历史上造成 dsh web 重复 ID 崩溃)。 */
366
368
  function ensureRuntime(relayDir) {
367
369
  if (existsSync(join(relayDir, "dsh-setup.mjs"))) return true;
368
370
  const marker = join(relayDir, PROVISION_MARKER);
@@ -372,7 +374,7 @@ function ensureRuntime(relayDir) {
372
374
  const log = join(relayDir, AUTO_INSTALL_LOG);
373
375
  const child = spawn(npxCommand(), ["--yes", "@mrrisega/dsh-remote"], {
374
376
  detached: true,
375
- env: spawnEnv(),
377
+ env: spawnEnv({ npm_config_registry: "https://registry.npmjs.org" }),
376
378
  stdio: ["ignore", openSync(log, "a"), openSync(log, "a")]
377
379
  });
378
380
  writeMarker(marker, child.pid); // 记 pid:宿主重启后可立即清理死进程残留
@@ -711,7 +713,7 @@ async function proxyFeedback(relayDir, req, res, pathname) {
711
713
  // ---------- 自管理:版本 / 在线更新 / 彻底卸载(面板内“版本与更新”卡片) ----------
712
714
 
713
715
  /** 插件自身发布版本(与 dsh-remote 根包同步递增)。 */
714
- const PLUGIN_VERSION = "0.4.4";
716
+ const PLUGIN_VERSION = "0.4.6";
715
717
  const UPDATE_LOG = ".dsh-update.log";
716
718
  const UPDATE_MARKER = ".dsh-update-running";
717
719
 
@@ -740,10 +742,12 @@ function spawnUpdater(relayDir, extraEnv) {
740
742
  }
741
743
 
742
744
  /**
743
- * 后台执行在线一键更新:npx @mrrisega/dsh-remote@latest(幂等自愈:补运行环境/更新 bridge/重写 include)。
745
+ * 后台执行在线一键更新:npx @mrrisega/dsh-remote@latest(幂等自愈:补运行环境/更新 bridge/收敛 include)。
744
746
  * 稳健性:
745
747
  * - npx 用绝对路径 + PATH 补全解析(App 拉起的 dsh web PATH 最小化时不再 ENOENT 静默失败);
746
- * - 默认 npx 源(国内常为 npmmirror)未同步到最新版导致失败时,自动用官方 npm 源重试一次;
748
+ * - 【官方源优先】镜像(npmmirror)滞后时会把旧版(如 0.4.4)当成最新安装,旧 pluginCmd 会把
749
+ * 已被移除的“用户 include”重新写回 profile → dsh web 重启重复 ID 崩溃。故先试官方源,
750
+ * 失败(国内网络)才回退用户默认镜像源;
747
751
  * - marker 记录 pid,子进程退出/出错即清理;宿主重启后由 sweepStaleMarkers 立即清掉死进程残留。
748
752
  */
749
753
  function runOnlineUpdate(relayDir) {
@@ -756,16 +760,17 @@ function runOnlineUpdate(relayDir) {
756
760
  let retried = false;
757
761
  const clear = () => { try { rmSync(marker, { force: true }); } catch { /* ignore */ } };
758
762
  const run = () => {
759
- const child = spawnUpdater(relayDir, retried ? { npm_config_registry: "https://registry.npmjs.org" } : {});
763
+ // 第一次:官方 npm 源;失败(exit≠0/网络)才回退用户默认源(通常为国内镜像)
764
+ const child = spawnUpdater(relayDir, retried ? {} : { npm_config_registry: "https://registry.npmjs.org" });
760
765
  writeMarker(marker, child.pid);
761
766
  child.on("exit", (code) => {
762
767
  if (!retried && code !== 0) {
763
768
  retried = true;
764
- appendLogLine(relayDir, UPDATE_LOG, `[update] 默认源安装失败(exit=${code}),改用官方 npm 源重试…`);
769
+ appendLogLine(relayDir, UPDATE_LOG, `[update] 官方源安装失败(exit=${code}),回退默认源(npmmirror )重试…`);
765
770
  run();
766
771
  return;
767
772
  }
768
- appendLogLine(relayDir, UPDATE_LOG, `[update] npx 退出 code=${code ?? "?"}(默认源${retried ? "/官方源" : ""})`);
773
+ appendLogLine(relayDir, UPDATE_LOG, `[update] npx 退出 code=${code ?? "?"}(${retried ? "默认源" : "官方源"})`);
769
774
  clear();
770
775
  });
771
776
  child.on("error", (e) => {