@mrrisega/dsh-remote 0.7.0-beta.7 → 0.7.0-beta.8
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 +39 -2
- package/package.json +1 -1
- package/packages/dsh-remote-web/package.json +1 -1
- package/packages/dsh-remote-web/runtime/dsh-setup.mjs +39 -2
- package/packages/dsh-remote-web/test/install-profile-targets.test.mjs +3 -1
- package/packages/dsh-remote-web/test/setup-output.test.mjs +23 -0
package/dsh-setup.mjs
CHANGED
|
@@ -194,7 +194,7 @@ function printHelp() {
|
|
|
194
194
|
// 认识的参数(只给 flag 也要能装上去,所以不认识时才拦)。
|
|
195
195
|
const KNOWN_FLAGS = new Set([
|
|
196
196
|
"--api", "--server", "--key", "--profile",
|
|
197
|
-
"--no-autostart", "--no-plugin", "--no-restart", "--uninstall"
|
|
197
|
+
"--no-autostart", "--no-plugin", "--no-restart", "--uninstall", "--force"
|
|
198
198
|
]);
|
|
199
199
|
|
|
200
200
|
// ⚠️ 这一整块必须挡在 ensureRuntimeCopy() **之前**:它会往 ~/.dsh-remote 同步运行时脚本。
|
|
@@ -1139,6 +1139,30 @@ function resolveProfileDir(argv = []) {
|
|
|
1139
1139
|
* `pluginCmd()` 时原本传的是空数组 —— 于是"指定 desktop profile"会被静默忽略、
|
|
1140
1140
|
* 插件仍然装进 Web 版 profile(桌面端用户装了却看不到面板,且极难自查)。
|
|
1141
1141
|
*/
|
|
1142
|
+
/**
|
|
1143
|
+
* DeepSeek Harness **桌面应用**是否正在运行。
|
|
1144
|
+
*
|
|
1145
|
+
* 🔴 2026-10-11 事故:我在应用运行中改写了 `~/.dsh/profiles/desktop/` 里的插件文件,
|
|
1146
|
+
* 应用随即**自己退出/重启**(业主:"怎么又触发了 dsh 的关闭")。安装器自己的提示早就写着
|
|
1147
|
+
* 「请先**完全退出** DeepSeek Harness 应用再安装(应用独占该 profile)」—— 这条必须由程序强制,
|
|
1148
|
+
* 不能靠"我记得"。
|
|
1149
|
+
* 检测方式:macOS 上桌面应用的进程名就是 `DeepSeek`(lsof/pgrep 实测),另加 App 包路径兜底。
|
|
1150
|
+
*/
|
|
1151
|
+
function dshDesktopAppRunning() {
|
|
1152
|
+
// 显式覆盖(测试隔离 / 探测误报时的逃生口):DSH_DESKTOP_APP_RUNNING=0|1
|
|
1153
|
+
const override = String(process.env.DSH_DESKTOP_APP_RUNNING || "").trim();
|
|
1154
|
+
if (override === "0" || override === "1") return override === "1";
|
|
1155
|
+
if (process.platform !== "darwin") return false;
|
|
1156
|
+
try {
|
|
1157
|
+
// 实测(2026-10-11):`pgrep -x DeepSeek` 与 `pgrep -f "...app/Contents/MacOS"` 都匹配不到,
|
|
1158
|
+
// 必须用下面的写法(`Contents/MacOS/DeepSeek` 或短名 `DeepSeek Harness.app` 均可命中)。
|
|
1159
|
+
const a = sh("pgrep -f 'Contents/MacOS/DeepSeek'");
|
|
1160
|
+
if (a.ok && String(a.stdout || "").trim()) return true;
|
|
1161
|
+
const b = sh("lsof -nP -iTCP:19387 -sTCP:LISTEN");
|
|
1162
|
+
return Boolean(b.ok && /LISTEN/.test(String(b.stdout || "")));
|
|
1163
|
+
} catch { return false; }
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1142
1166
|
/**
|
|
1143
1167
|
* 探测本机**已存在**的 profile(可装载 = 同时有 package.json 与 cordis.patch.yml)。
|
|
1144
1168
|
* desktop 排在前面:业主主用的是桌面应用,多个 profile 一起装时它是"首选/权威"那个。
|
|
@@ -2787,14 +2811,27 @@ async function pluginCmd(argv) {
|
|
|
2787
2811
|
}
|
|
2788
2812
|
let last = null;
|
|
2789
2813
|
let ok = 0;
|
|
2814
|
+
let skippedRunning = 0;
|
|
2815
|
+
const force = hasFlag(argv, "--force");
|
|
2790
2816
|
for (const t of targets) {
|
|
2817
|
+
// ⚠️ 桌面应用在运行时**绝不改写它的 profile**:应用会因此退出/重启(2026-10-11 实测事故)
|
|
2818
|
+
if (t.name === "desktop" && !force && dshDesktopAppRunning()) {
|
|
2819
|
+
console.log(`⚠️ 跳过 desktop:DeepSeek Harness 应用正在运行。`);
|
|
2820
|
+
console.log(` 应用独占该 profile,运行中改写会让应用自己退出/重启。请任选其一:`);
|
|
2821
|
+
console.log(` · 完全退出应用后重跑本命令;或`);
|
|
2822
|
+
console.log(` · 在应用内「插件」页安装/更新 dsh-remote-web(官方路径,装完即热加载)。`);
|
|
2823
|
+
console.log(` (确实要强行写入:加 --force)`);
|
|
2824
|
+
skippedRunning += 1;
|
|
2825
|
+
continue;
|
|
2826
|
+
}
|
|
2791
2827
|
if (multi) console.log(`\n── profile: ${t.name}(${t.dir})`);
|
|
2792
2828
|
const r = await pluginApplyTo(t.dir, uninstall, multi);
|
|
2793
2829
|
if (r === "skip") continue;
|
|
2794
2830
|
ok += 1;
|
|
2795
2831
|
if (r !== undefined && r !== null) last = r;
|
|
2796
2832
|
}
|
|
2797
|
-
if (ok === 0) process.exit(1);
|
|
2833
|
+
if (ok === 0 && skippedRunning === 0) process.exit(1);
|
|
2834
|
+
if (skippedRunning > 0) console.log(`ℹ 本次跳过了 ${skippedRunning} 个 profile(应用在运行);桥与运行环境不受影响。`);
|
|
2798
2835
|
return last;
|
|
2799
2836
|
}
|
|
2800
2837
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrrisega/dsh-remote",
|
|
3
|
-
"version": "0.7.0-beta.
|
|
3
|
+
"version": "0.7.0-beta.8",
|
|
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",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-remote-web",
|
|
3
|
-
"version": "0.7.0-beta.
|
|
3
|
+
"version": "0.7.0-beta.8",
|
|
4
4
|
"description": "公网远程控制 DeepSeek Harness(dsh web):安装即得专属加密地址,人在外面也能用手机访问电脑上的 dsh——无需同一局域网/WiFi、无需公网 IP 与内网穿透,全程加密;手机端 100% 还原电脑体验(对话/工具/审批/设置)。技术用户可选自建服务,流量走自己的服务器。Remote control DeepSeek Harness (dsh web) from anywhere over the public internet — install, get an encrypted URL, use it from your phone on any network (dual-half cordis plugin: Settings panel + same-origin /dsh-remote routes). (2026-09 由 dsh-remote-ui 更名 / renamed from dsh-remote-ui; dsh-remote 的 dsh web 插件半,不是纯 UI/皮肤插件)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -194,7 +194,7 @@ function printHelp() {
|
|
|
194
194
|
// 认识的参数(只给 flag 也要能装上去,所以不认识时才拦)。
|
|
195
195
|
const KNOWN_FLAGS = new Set([
|
|
196
196
|
"--api", "--server", "--key", "--profile",
|
|
197
|
-
"--no-autostart", "--no-plugin", "--no-restart", "--uninstall"
|
|
197
|
+
"--no-autostart", "--no-plugin", "--no-restart", "--uninstall", "--force"
|
|
198
198
|
]);
|
|
199
199
|
|
|
200
200
|
// ⚠️ 这一整块必须挡在 ensureRuntimeCopy() **之前**:它会往 ~/.dsh-remote 同步运行时脚本。
|
|
@@ -1139,6 +1139,30 @@ function resolveProfileDir(argv = []) {
|
|
|
1139
1139
|
* `pluginCmd()` 时原本传的是空数组 —— 于是"指定 desktop profile"会被静默忽略、
|
|
1140
1140
|
* 插件仍然装进 Web 版 profile(桌面端用户装了却看不到面板,且极难自查)。
|
|
1141
1141
|
*/
|
|
1142
|
+
/**
|
|
1143
|
+
* DeepSeek Harness **桌面应用**是否正在运行。
|
|
1144
|
+
*
|
|
1145
|
+
* 🔴 2026-10-11 事故:我在应用运行中改写了 `~/.dsh/profiles/desktop/` 里的插件文件,
|
|
1146
|
+
* 应用随即**自己退出/重启**(业主:"怎么又触发了 dsh 的关闭")。安装器自己的提示早就写着
|
|
1147
|
+
* 「请先**完全退出** DeepSeek Harness 应用再安装(应用独占该 profile)」—— 这条必须由程序强制,
|
|
1148
|
+
* 不能靠"我记得"。
|
|
1149
|
+
* 检测方式:macOS 上桌面应用的进程名就是 `DeepSeek`(lsof/pgrep 实测),另加 App 包路径兜底。
|
|
1150
|
+
*/
|
|
1151
|
+
function dshDesktopAppRunning() {
|
|
1152
|
+
// 显式覆盖(测试隔离 / 探测误报时的逃生口):DSH_DESKTOP_APP_RUNNING=0|1
|
|
1153
|
+
const override = String(process.env.DSH_DESKTOP_APP_RUNNING || "").trim();
|
|
1154
|
+
if (override === "0" || override === "1") return override === "1";
|
|
1155
|
+
if (process.platform !== "darwin") return false;
|
|
1156
|
+
try {
|
|
1157
|
+
// 实测(2026-10-11):`pgrep -x DeepSeek` 与 `pgrep -f "...app/Contents/MacOS"` 都匹配不到,
|
|
1158
|
+
// 必须用下面的写法(`Contents/MacOS/DeepSeek` 或短名 `DeepSeek Harness.app` 均可命中)。
|
|
1159
|
+
const a = sh("pgrep -f 'Contents/MacOS/DeepSeek'");
|
|
1160
|
+
if (a.ok && String(a.stdout || "").trim()) return true;
|
|
1161
|
+
const b = sh("lsof -nP -iTCP:19387 -sTCP:LISTEN");
|
|
1162
|
+
return Boolean(b.ok && /LISTEN/.test(String(b.stdout || "")));
|
|
1163
|
+
} catch { return false; }
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1142
1166
|
/**
|
|
1143
1167
|
* 探测本机**已存在**的 profile(可装载 = 同时有 package.json 与 cordis.patch.yml)。
|
|
1144
1168
|
* desktop 排在前面:业主主用的是桌面应用,多个 profile 一起装时它是"首选/权威"那个。
|
|
@@ -2787,14 +2811,27 @@ async function pluginCmd(argv) {
|
|
|
2787
2811
|
}
|
|
2788
2812
|
let last = null;
|
|
2789
2813
|
let ok = 0;
|
|
2814
|
+
let skippedRunning = 0;
|
|
2815
|
+
const force = hasFlag(argv, "--force");
|
|
2790
2816
|
for (const t of targets) {
|
|
2817
|
+
// ⚠️ 桌面应用在运行时**绝不改写它的 profile**:应用会因此退出/重启(2026-10-11 实测事故)
|
|
2818
|
+
if (t.name === "desktop" && !force && dshDesktopAppRunning()) {
|
|
2819
|
+
console.log(`⚠️ 跳过 desktop:DeepSeek Harness 应用正在运行。`);
|
|
2820
|
+
console.log(` 应用独占该 profile,运行中改写会让应用自己退出/重启。请任选其一:`);
|
|
2821
|
+
console.log(` · 完全退出应用后重跑本命令;或`);
|
|
2822
|
+
console.log(` · 在应用内「插件」页安装/更新 dsh-remote-web(官方路径,装完即热加载)。`);
|
|
2823
|
+
console.log(` (确实要强行写入:加 --force)`);
|
|
2824
|
+
skippedRunning += 1;
|
|
2825
|
+
continue;
|
|
2826
|
+
}
|
|
2791
2827
|
if (multi) console.log(`\n── profile: ${t.name}(${t.dir})`);
|
|
2792
2828
|
const r = await pluginApplyTo(t.dir, uninstall, multi);
|
|
2793
2829
|
if (r === "skip") continue;
|
|
2794
2830
|
ok += 1;
|
|
2795
2831
|
if (r !== undefined && r !== null) last = r;
|
|
2796
2832
|
}
|
|
2797
|
-
if (ok === 0) process.exit(1);
|
|
2833
|
+
if (ok === 0 && skippedRunning === 0) process.exit(1);
|
|
2834
|
+
if (skippedRunning > 0) console.log(`ℹ 本次跳过了 ${skippedRunning} 个 profile(应用在运行);桥与运行环境不受影响。`);
|
|
2798
2835
|
return last;
|
|
2799
2836
|
}
|
|
2800
2837
|
|
|
@@ -42,7 +42,9 @@ function makeProfile(home, name, { withPlugin = false } = {}) {
|
|
|
42
42
|
|
|
43
43
|
/** 跑真实的 `plugin` 子进程。**显式清掉 DSH_PROFILE_DIR**:宿主环境天然带着它(会盖掉默认多选)。 */
|
|
44
44
|
function runPlugin(home, args = []) {
|
|
45
|
-
const env = { ...process.env, HOME: home, DSH_PROFILE_DIR: "", DSH_RELAY_DIR: join(home, ".dsh-remote"), DSH_RELAY_SKIP_SERVICE: "1"
|
|
45
|
+
const env = { ...process.env, HOME: home, DSH_PROFILE_DIR: "", DSH_RELAY_DIR: join(home, ".dsh-remote"), DSH_RELAY_SKIP_SERVICE: "1",
|
|
46
|
+
// 假 HOME 与真机无关:显式声明"桌面应用未运行",否则会命中"应用在跑就别写 desktop"的保护
|
|
47
|
+
DSH_DESKTOP_APP_RUNNING: "0" };
|
|
46
48
|
const r = spawnSync(process.execPath, [SETUP, "plugin", ...args], { encoding: "utf8", env, timeout: 120000 });
|
|
47
49
|
return { out: String(r.stdout || ""), err: String(r.stderr || ""), code: r.status };
|
|
48
50
|
}
|
|
@@ -226,3 +226,26 @@ test("★ 自启动配置里可以钉住上游(多 profile 时代,各宿主
|
|
|
226
226
|
const svcAt = src.indexOf("svc = installAutostart()");
|
|
227
227
|
assert.ok(pinAt > 0 && svcAt > 0 && pinAt < svcAt, "钉定必须发生在 installAutostart() 之前");
|
|
228
228
|
});
|
|
229
|
+
|
|
230
|
+
test("★ 桌面应用在运行时,绝不改写它的 profile(否则应用会自己退出/重启)", () => {
|
|
231
|
+
// 2026-10-11 事故:在应用运行中改写 ~/.dsh/profiles/desktop/ 的插件文件 →
|
|
232
|
+
// 应用随即自己退出/重启(业主:"怎么又触发了 dsh 的关闭")。安装器文案早就写了"先完全退出应用",
|
|
233
|
+
// 但必须由程序强制,不能靠人记得。
|
|
234
|
+
const src = readFileSync(SETUP, "utf8");
|
|
235
|
+
assert.match(src, /function dshDesktopAppRunning\(/, "要有「桌面应用是否在运行」的探测");
|
|
236
|
+
assert.match(src, /Contents\/MacOS\/DeepSeek/, "探测要匹配 App 包内可执行文件(-x DeepSeek 匹配不到,实测)");
|
|
237
|
+
// 必须在真正写 profile **之前**判断
|
|
238
|
+
const loopAt = src.indexOf("for (const t of targets) {");
|
|
239
|
+
const guardAt = src.indexOf("dshDesktopAppRunning()", loopAt);
|
|
240
|
+
const applyAt = src.indexOf("await pluginApplyTo(", loopAt);
|
|
241
|
+
assert.ok(loopAt > 0 && guardAt > loopAt && applyAt > guardAt,
|
|
242
|
+
"顺序必须是:循环 → 应用在运行?→ 跳过;否则才 pluginApplyTo()");
|
|
243
|
+
assert.match(src, /跳过 desktop:DeepSeek Harness 应用正在运行/, "要如实说明跳过了 desktop");
|
|
244
|
+
assert.match(src, /应用内「插件」页安装/, "要给出官方替代路径(应用内插件页)");
|
|
245
|
+
assert.match(src, /"--force"/, "要留 --force 逃生口");
|
|
246
|
+
// sync-local 脚本同样要保护(本机同步最常用)
|
|
247
|
+
const sync = readFileSync(join(HERE, "..", "..", "..", "scripts", "sync-local.mjs"), "utf8");
|
|
248
|
+
assert.match(sync, /function dshDesktopAppRunning\(/, "本机同步脚本也要有同样的保护");
|
|
249
|
+
assert.match(sync, /跳过插件半/, "要明确说「只跳过插件半」(桥半不受影响)");
|
|
250
|
+
assert.match(sync, /--force-plugin/, "要留逃生口");
|
|
251
|
+
});
|