dsh-remote-web 0.6.4-beta.8 → 0.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/lib/client.js +25 -9
- package/lib/index.js +75 -11
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -1243,6 +1243,9 @@ window.__ModuleLoader__.load({
|
|
|
1243
1243
|
// 是否需要重启 DeepSeek harness(首次安装/在线更新后置顶提醒 + 底部常驻按钮)
|
|
1244
1244
|
var restartInfo = (st && st.restart) || {};
|
|
1245
1245
|
var restartPending = !!restartInfo.pending;
|
|
1246
|
+
// kind === "refresh" = 插件文件在运行中被改写(装完/更新完),只需刷新页面;
|
|
1247
|
+
// 其余情况(极为罕见)才提重启 dsh web。
|
|
1248
|
+
var isRefreshOnly = restartPending && restartInfo.kind === "refresh";
|
|
1246
1249
|
// 连接阶段(node 半下发):轮询拿到的新鲜结果优先,其次用 /status 里带的那一份;
|
|
1247
1250
|
// 旧版 host(响应里没有 connect 字段)→ 回退到原来的 service.running 文案(行为不变)。
|
|
1248
1251
|
var connInfo = conn || (st && st.connect) || null;
|
|
@@ -2446,9 +2449,11 @@ window.__ModuleLoader__.load({
|
|
|
2446
2449
|
);
|
|
2447
2450
|
}
|
|
2448
2451
|
|
|
2449
|
-
// 【自动化】待重启时自动倒计时重启(见 autoRestartCancelled 说明)
|
|
2452
|
+
// 【自动化】待重启时自动倒计时重启(见 autoRestartCancelled 说明);倒计时只在面板可见且无其他操作时推进。
|
|
2453
|
+
// ⚠️ isRefreshOnly(插件更新后只需刷新)时**不**自动重启:刷新是零风险动作,重启 dsh web 会打断
|
|
2454
|
+
// 用户正在进行的会话,不能替他做这个决定 —— 让他点「刷新页面」即可。
|
|
2450
2455
|
useEffect(function () {
|
|
2451
|
-
if (!restartPending || busy !== "" || restartCancelled) { setRestartCountdown(null); return undefined; }
|
|
2456
|
+
if (!restartPending || isRefreshOnly || busy !== "" || restartCancelled) { setRestartCountdown(null); return undefined; }
|
|
2452
2457
|
var at = restartInfo.at || 0;
|
|
2453
2458
|
if (autoRestartCancelled(at)) { setRestartCountdown(null); return undefined; }
|
|
2454
2459
|
var left = Math.round(AUTO_RESTART_DELAY_MS / 1000);
|
|
@@ -2462,7 +2467,7 @@ window.__ModuleLoader__.load({
|
|
|
2462
2467
|
restartHarness(); // 已有实现:重启 + 轮询等它回来 + 自动刷新页面
|
|
2463
2468
|
}, 1000);
|
|
2464
2469
|
return function () { clearInterval(iv); };
|
|
2465
|
-
}, [restartPending, restartInfo.at, busy, restartCancelled]);
|
|
2470
|
+
}, [restartPending, isRefreshOnly, restartInfo.at, busy, restartCancelled]);
|
|
2466
2471
|
|
|
2467
2472
|
/** 取消本次自动重启(按事件持久化:同一 pending 事件不再自动重启)。 */
|
|
2468
2473
|
var cancelAutoRestart = function () {
|
|
@@ -2508,9 +2513,13 @@ window.__ModuleLoader__.load({
|
|
|
2508
2513
|
h("span", { className: "dru-restart-alert-icon" }, "🔔"),
|
|
2509
2514
|
h("div", { style: { flex: "1" } },
|
|
2510
2515
|
h("div", { className: "dru-restart-alert-title" },
|
|
2511
|
-
|
|
2516
|
+
// 插件已改为 patch 热加载:装插件/在线更新都不再需要重启 harness,
|
|
2517
|
+
// 唯一要用户动一下的情形是"插件文件在运行中被改写"→ 刷新页面即可。
|
|
2518
|
+
isRefreshOnly ? "插件已更新,刷新页面即可生效" : "需要重启 dsh web 才能载入新插件"),
|
|
2512
2519
|
h("div", { className: "dru-restart-alert-sub" },
|
|
2513
|
-
|
|
2520
|
+
isRefreshOnly
|
|
2521
|
+
? "刚装好/刚更新的插件已在磁盘上,浏览器里的面板还是旧版本。刷新一下这个页面即可(手机端连接与已登录状态都不受影响)。"
|
|
2522
|
+
: "当前运行中的 dsh web 里还没有载入这个插件。先刷新页面;如果刷新后仍然看不到面板,再重启一次 dsh web。"),
|
|
2514
2523
|
// 【自动化】待重启时自动倒计时重启(用户零操作);倒计时期间可一键取消
|
|
2515
2524
|
restartCountdown !== null && restartCountdown > 0
|
|
2516
2525
|
? h("div", { className: "dru-restart-auto", role: "status" },
|
|
@@ -2524,12 +2533,19 @@ window.__ModuleLoader__.load({
|
|
|
2524
2533
|
? h("div", { className: "dru-restart-auto muted" }, "已取消自动重启 —— 你也可以随时点下面的按钮完成安装")
|
|
2525
2534
|
: null,
|
|
2526
2535
|
h("div", { className: "dru-actions", style: { marginTop: 10 } },
|
|
2536
|
+
isRefreshOnly
|
|
2537
|
+
? h("button", {
|
|
2538
|
+
type: "button",
|
|
2539
|
+
className: "dru-btn dru-btn-primary",
|
|
2540
|
+
onClick: function () { try { location.reload(); } catch (e) { /* 忽略 */ } }
|
|
2541
|
+
}, "刷新页面")
|
|
2542
|
+
: null,
|
|
2527
2543
|
h("button", {
|
|
2528
2544
|
type: "button",
|
|
2529
|
-
className: "dru-btn dru-btn-primary",
|
|
2545
|
+
className: "dru-btn " + (isRefreshOnly ? "dru-btn-ghost" : "dru-btn-primary"),
|
|
2530
2546
|
disabled: busy !== "",
|
|
2531
2547
|
onClick: function () { restartHarness(); }
|
|
2532
|
-
}, busy === "restart" ? "重启中…" : (restartCountdown !== null && restartCountdown > 0 ? "立即重启(不用等)" : "重启
|
|
2548
|
+
}, busy === "restart" ? "重启中…" : (restartCountdown !== null && restartCountdown > 0 ? "立即重启(不用等)" : "重启 dsh web")),
|
|
2533
2549
|
restartCountdown !== null && restartCountdown > 0
|
|
2534
2550
|
? h("button", {
|
|
2535
2551
|
type: "button",
|
|
@@ -2545,13 +2561,13 @@ window.__ModuleLoader__.load({
|
|
|
2545
2561
|
),
|
|
2546
2562
|
// 常驻入口最底部:「重启 DeepSeek harness」按钮始终可达(首次安装/更新/排查都用它)
|
|
2547
2563
|
h("div", { className: "dru-restart-foot" },
|
|
2548
|
-
h("div", { className: "dru-hint" }, "重启
|
|
2564
|
+
h("div", { className: "dru-hint" }, "重启 dsh web:仅在「刷新页面后仍看不到面板」时才需要。日常安装/更新插件已自动热加载,装完刷新页面即可;重启只影响 dsh web 本身,不影响 bridge 与手机端连接。"),
|
|
2549
2565
|
h("button", {
|
|
2550
2566
|
type: "button",
|
|
2551
2567
|
className: "dru-btn " + (restartPending ? "dru-btn-primary" : "dru-btn-ghost"),
|
|
2552
2568
|
disabled: busy !== "",
|
|
2553
2569
|
onClick: function () { restartHarness(); }
|
|
2554
|
-
}, busy === "restart" ? "重启中…" : "🔄 重启
|
|
2570
|
+
}, busy === "restart" ? "重启中…" : "🔄 重启 dsh web")
|
|
2555
2571
|
),
|
|
2556
2572
|
renderCommunityModal()
|
|
2557
2573
|
);
|
package/lib/index.js
CHANGED
|
@@ -677,8 +677,11 @@ function scheduleRuntime(relayDir) {
|
|
|
677
677
|
telemetryRuntimeProbe(relayDir); // 匿名遥测:环境刚补齐 → runtime_ready(每进程一次)
|
|
678
678
|
if (awaitingRestartHint) {
|
|
679
679
|
awaitingRestartHint = false;
|
|
680
|
-
//
|
|
681
|
-
|
|
680
|
+
// 运行环境刚在本进程内补齐 —— **不需要重启 harness**:
|
|
681
|
+
// · bridge 是独立 launchd 进程,补齐后自愈调度会直接把它拉起;
|
|
682
|
+
// · 插件本体走 patch 热加载,装完即生效。
|
|
683
|
+
// 以前这里会 markRestartPending("first-install") 弹出"需要重启 DeepSeek harness"横幅,
|
|
684
|
+
// 在热加载落地后已经变成纯粹的误导(用户实测反馈),故移除。
|
|
682
685
|
}
|
|
683
686
|
const cfg = loadConfig(relayDir);
|
|
684
687
|
const hasAcct = Boolean((cfg.phone || cfg.email) && cfg.password) || Boolean(cfg.local_key);
|
|
@@ -849,7 +852,10 @@ function writeRestartState(relayDir, state) {
|
|
|
849
852
|
} catch { /* 写盘失败不致命:面板退化为不提示 */ }
|
|
850
853
|
}
|
|
851
854
|
|
|
852
|
-
/**
|
|
855
|
+
/**
|
|
856
|
+
* 标记「需要用户动一下」的状态。kind = refresh(插件被运行时改写,刷新页面即可)。
|
|
857
|
+
* 历史 kind(first-install / update)已废弃:热加载落地后,装插件与在线更新都不再需要重启 harness。
|
|
858
|
+
*/
|
|
853
859
|
function markRestartPending(relayDir, kind, reason) {
|
|
854
860
|
const cur = readRestartState(relayDir);
|
|
855
861
|
if (cur.pending && cur.kind === kind) return cur; // 幂等:同一原因不反复重写
|
|
@@ -881,7 +887,15 @@ function pluginInstalledAfterBoot() {
|
|
|
881
887
|
const pkgFile = join(dirname(dirname(here)), "package.json");
|
|
882
888
|
const mtimeMs = statSync(pkgFile).mtimeMs;
|
|
883
889
|
const bootMs = Date.now() - process.uptime() * 1000;
|
|
884
|
-
|
|
890
|
+
if (!(mtimeMs > bootMs + 2000)) return false;
|
|
891
|
+
// ⚠️ 只看时间戳会**误报**:安装器补装/在线更新会把同样的文件重写一遍(哪怕是同一个版本),
|
|
892
|
+
// 时间戳变新 → 被当成"有新插件要装载" → 弹出"需要重启/刷新"横幅(用户实测踩到:
|
|
893
|
+
// 自愈补装把 profile 副本重写了一次,运行中的旧代码就写下了"需要重启")。
|
|
894
|
+
// 真正该提示用户的只有一件事:**磁盘上的插件版本与本进程装载的版本不同**。
|
|
895
|
+
// 版本相同 → 运行中的代码就是目标版本,什么都不用做。
|
|
896
|
+
const diskVersion = String(JSON.parse(readFileSync(pkgFile, "utf8")).version || "");
|
|
897
|
+
const runningVersion = String(PLUGIN_VERSION || "");
|
|
898
|
+
return Boolean(diskVersion && runningVersion && diskVersion !== runningVersion);
|
|
885
899
|
} catch { return false; }
|
|
886
900
|
}
|
|
887
901
|
|
|
@@ -1739,7 +1753,7 @@ const PLUGIN_ID = "dsh-remote-web";
|
|
|
1739
1753
|
const PLUGIN_LEGACY_IDS = ["dsh-remote-ui"];
|
|
1740
1754
|
const PLUGIN_ALL_IDS = [PLUGIN_ID, ...PLUGIN_LEGACY_IDS];
|
|
1741
1755
|
/** 插件自身发布版本(与 dsh-remote 根包同步递增)。 */
|
|
1742
|
-
const PLUGIN_VERSION = "0.6.4
|
|
1756
|
+
const PLUGIN_VERSION = "0.6.4";
|
|
1743
1757
|
const UPDATE_LOG = ".dsh-update.log";
|
|
1744
1758
|
const UPDATE_MARKER = ".dsh-update-running";
|
|
1745
1759
|
|
|
@@ -1764,6 +1778,44 @@ async function npmLatestVersion() {
|
|
|
1764
1778
|
return "";
|
|
1765
1779
|
}
|
|
1766
1780
|
|
|
1781
|
+
/**
|
|
1782
|
+
* 语义化版本比较(只处理本仓库用到的形态:`X.Y.Z` 与 `X.Y.Z-<pre>.<n>`)。
|
|
1783
|
+
* 为什么需要它:面板判断"有没有新版本"不能只用 `latest !== current` ——
|
|
1784
|
+
* 预设版之间(beta.10 → beta.11)无法比较大小,而且预设版在 semver 里**低于**同号正式版
|
|
1785
|
+
* (0.6.4 < 0.6.4),朴素不等判断会把"已经装了预设版"误判成落后。
|
|
1786
|
+
* @returns {number} a>b → 1;a<b → -1;相等 → 0
|
|
1787
|
+
*/
|
|
1788
|
+
function compareVersions(a, b) {
|
|
1789
|
+
const parse = (v) => {
|
|
1790
|
+
const m = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?/.exec(String(v || "").trim());
|
|
1791
|
+
if (!m) return null;
|
|
1792
|
+
return { nums: [Number(m[1]), Number(m[2]), Number(m[3])], pre: m[4] ? m[4].split(".") : null };
|
|
1793
|
+
};
|
|
1794
|
+
const x = parse(a);
|
|
1795
|
+
const y = parse(b);
|
|
1796
|
+
if (!x || !y) return null; // 无法解析 → 交给调用方保守处理
|
|
1797
|
+
for (let i = 0; i < 3; i += 1) {
|
|
1798
|
+
if (x.nums[i] !== y.nums[i]) return x.nums[i] > y.nums[i] ? 1 : -1;
|
|
1799
|
+
}
|
|
1800
|
+
if (!x.pre && !y.pre) return 0;
|
|
1801
|
+
if (!x.pre) return 1; // 有预设版 < 无预设版(semver §11)
|
|
1802
|
+
if (!y.pre) return -1;
|
|
1803
|
+
const len = Math.max(x.pre.length, y.pre.length);
|
|
1804
|
+
for (let i = 0; i < len; i += 1) {
|
|
1805
|
+
const p1 = x.pre[i];
|
|
1806
|
+
const p2 = y.pre[i];
|
|
1807
|
+
if (p1 === undefined) return -1;
|
|
1808
|
+
if (p2 === undefined) return 1;
|
|
1809
|
+
const n1 = /^\d+$/.test(p1) ? Number(p1) : null;
|
|
1810
|
+
const n2 = /^\d+$/.test(p2) ? Number(p2) : null;
|
|
1811
|
+
if (n1 !== null && n2 !== null) { if (n1 !== n2) return n1 > n2 ? 1 : -1; continue; }
|
|
1812
|
+
if (n1 !== null) return -1; // 数字标识符 < 字母标识符
|
|
1813
|
+
if (n2 !== null) return 1;
|
|
1814
|
+
if (p1 !== p2) return p1 > p2 ? 1 : -1;
|
|
1815
|
+
}
|
|
1816
|
+
return 0;
|
|
1817
|
+
}
|
|
1818
|
+
|
|
1767
1819
|
/** 以 detached 子进程执行 `npx --yes <UPDATE_SPEC>`(env 可覆盖 npm 源/更新通道)。 */
|
|
1768
1820
|
function spawnUpdater(relayDir, extraEnv) {
|
|
1769
1821
|
const log = join(relayDir, UPDATE_LOG);
|
|
@@ -1809,9 +1861,12 @@ function runOnlineUpdate(relayDir) {
|
|
|
1809
1861
|
}
|
|
1810
1862
|
appendLogLine(relayDir, UPDATE_LOG, `[update] npx 退出 code=${code ?? "?"}(${retried ? "默认源" : "官方源"})`);
|
|
1811
1863
|
clear();
|
|
1812
|
-
// 在线更新改写了插件/运行环境文件 →
|
|
1813
|
-
|
|
1814
|
-
|
|
1864
|
+
// 在线更新改写了插件/运行环境文件 → 版本对不上时由 pluginInstalledAfterBoot() 自动识别,
|
|
1865
|
+
// 面板会提示"刷新页面"。这里**不再**写"需要重启 DeepSeek harness"的待重启标记:
|
|
1866
|
+
// 插件走 patch 热加载、bridge 是独立进程,重启 harness 已无必要(用户实测反馈)。
|
|
1867
|
+
// 成功不发事件(update_started 已发过,失败才发 update_failed;白名单里没有 update_done,
|
|
1868
|
+
// 硬发只会被服务端静默丢弃)。
|
|
1869
|
+
if (code !== 0) telemetryRecord(relayDir, "update_failed", { fail_code: telemetryFailCodeFromText(readTail(join(relayDir, UPDATE_LOG))) });
|
|
1815
1870
|
});
|
|
1816
1871
|
child.on("error", (e) => {
|
|
1817
1872
|
appendLogLine(relayDir, UPDATE_LOG, `[update] 子进程启动失败: ${e.message}`);
|
|
@@ -2839,7 +2894,12 @@ function registerRoutes(ctx, relayDir) {
|
|
|
2839
2894
|
handler: async (_req, res) => {
|
|
2840
2895
|
const latest = await npmLatestVersion();
|
|
2841
2896
|
const current = PLUGIN_VERSION;
|
|
2842
|
-
|
|
2897
|
+
// 正规比较:注册表版本**严格大于**本地版本才算有更新。
|
|
2898
|
+
// 这样 0.6.4 < 0.6.4(正式版发布后预设版会正确提示升级),
|
|
2899
|
+
// 而 0.6.4-beta.10 < 0.6.4 也能正确判断;解析不了时退回朴素不等(保守提示)。
|
|
2900
|
+
const cmp = compareVersions(latest, current);
|
|
2901
|
+
const outdated = cmp === null ? Boolean(latest && latest !== current) : cmp > 0;
|
|
2902
|
+
sendJson(res, 200, { ok: true, current, latest, outdated });
|
|
2843
2903
|
},
|
|
2844
2904
|
},
|
|
2845
2905
|
{
|
|
@@ -3325,8 +3385,12 @@ export function apply(ctx, config = {}) {
|
|
|
3325
3385
|
ctx.logger?.info?.("dsh-remote-web: 检测到 DeepSeek harness 已重启,撤下「需要重启」提示");
|
|
3326
3386
|
}
|
|
3327
3387
|
if (!readRestartState(relayDir).pending && pluginInstalledAfterBoot()) {
|
|
3328
|
-
|
|
3329
|
-
|
|
3388
|
+
// 唯一还需要用户动一下的情形:插件文件晚于本进程启动才落盘(= 运行中被市场安装/在线更新改写),
|
|
3389
|
+
// 此时本进程里没有本插件的路由与面板。**先让用户刷新页面**(客户端半随之更新);
|
|
3390
|
+
// 只有刷新后仍无面板才需要重启(那时多半是 profile patch 行没被 HMR 读到)。
|
|
3391
|
+
// 不再对"首次安装/在线更新完成"提示重启:插件走 patch 热加载、bridge 是独立进程,重启 harness 无必要。
|
|
3392
|
+
markRestartPending(relayDir, "refresh", "插件已更新,刷新页面即可生效");
|
|
3393
|
+
ctx.logger?.info?.("dsh-remote-web: 插件文件晚于本次进程启动 → 提示刷新页面");
|
|
3330
3394
|
}
|
|
3331
3395
|
ctx.effect(() => registerRoutes(ctx, relayDir), "dsh-remote-web: /dsh-remote routes");
|
|
3332
3396
|
// 0.1.2-rc.1+ 浏览器会话代持:换取 Harness 会话 Cookie 供 bridge 上游携带(手机点设备不再 401 白页)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-remote-web",
|
|
3
|
-
"version": "0.6.4
|
|
3
|
+
"version": "0.6.4",
|
|
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",
|