dsh-mobile-plugin 0.3.1 → 0.4.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/config/agent-presets/standard/agent.cordis.yml +7 -5
- package/dsh-mobile-credentials/lib/credentials.js +8 -8
- package/host.js +108 -39
- package/lib/client.js +29 -82
- package/lib/desktop-view.cjs +5 -10
- package/lib/git/read.js +6 -8
- package/lib/host-compat.cjs +120 -0
- package/lib/host-guard.cjs +9 -39
- package/lib/host-patches.cjs +5 -16
- package/lib/http/routing.js +7 -27
- package/lib/impl.js +6 -8
- package/lib/mirror/pusher.cjs +33 -85
- package/lib/mirror-snapshot.cjs +6 -24
- package/lib/mobile/agent-presets.js +6 -26
- package/lib/mobile/approvals.js +18 -44
- package/lib/mobile/archive-clean.js +10 -19
- package/lib/mobile/attachments.js +11 -36
- package/lib/mobile/channels.js +15 -41
- package/lib/mobile/credential-upgrade.js +7 -16
- package/lib/mobile/credentials.js +5 -21
- package/lib/mobile/directory-browse.js +8 -27
- package/lib/mobile/directory-listing.js +10 -23
- package/lib/mobile/drive-roots.js +59 -0
- package/lib/mobile/envelope.js +4 -15
- package/lib/mobile/events.js +35 -65
- package/lib/mobile/extensions.js +5 -8
- package/lib/mobile/git-repos.js +6 -15
- package/lib/mobile/git.js +11 -19
- package/lib/mobile/history-derive.js +23 -41
- package/lib/mobile/host-bridge/endpoints.js +4 -6
- package/lib/mobile/host-bridge/event-synthesis.js +21 -35
- package/lib/mobile/host-bridge/shims.js +12 -22
- package/lib/mobile/host-bridge.js +8 -20
- package/lib/mobile/host-settings.js +9 -19
- package/lib/mobile/inbox-clean.js +7 -24
- package/lib/mobile/local-images.js +11 -17
- package/lib/mobile/monitor.js +8 -17
- package/lib/mobile/pairing.js +7 -19
- package/lib/mobile/plugin-self.js +5 -22
- package/lib/mobile/plugin-update.js +8 -27
- package/lib/mobile/plugins.js +6 -24
- package/lib/mobile/questions.js +15 -40
- package/lib/mobile/references.js +11 -31
- package/lib/mobile/relay-control.js +6 -27
- package/lib/mobile/rpc.js +5 -13
- package/lib/mobile/schedule.js +8 -33
- package/lib/mobile/self-check.cjs +9 -36
- package/lib/mobile/session-cache.js +13 -19
- package/lib/mobile/session-file.js +8 -21
- package/lib/mobile/session-flags.js +7 -13
- package/lib/mobile/session-mirror.js +8 -15
- package/lib/mobile/session-models.js +3 -3
- package/lib/mobile/session-schedule-mark.js +8 -14
- package/lib/mobile/session-send.js +13 -21
- package/lib/mobile/session-snapshot.js +4 -6
- package/lib/mobile/sessions.js +7 -23
- package/lib/mobile/stats-collect.cjs +10 -24
- package/lib/mobile/stats-ledger.cjs +13 -20
- package/lib/mobile/stream.js +12 -23
- package/lib/mobile/subagents.js +5 -18
- package/lib/mobile/uploads.js +8 -17
- package/lib/mobile/workspaces.js +20 -8
- package/lib/panel/index.html +7 -7
- package/lib/read-receipt.cjs +10 -21
- package/lib/relay/lan-network.cjs +14 -28
- package/lib/relay/lan-proxy.js +14 -21
- package/lib/relay/pairing-policy.js +6 -9
- package/lib/relay/preview.js +8 -25
- package/lib/relay/protocol.js +6 -13
- package/lib/relay/status.js +5 -8
- package/lib/relay/tunnel.js +9 -14
- package/lib/routes/control-plane.js +28 -45
- package/lib/routes/mobile-api.js +19 -32
- package/lib/session-cleanup.cjs +16 -27
- package/lib/shared/credentials-entry.js +4 -11
- package/lib/shared/dshtoken.js +4 -4
- package/lib/shared/e2ee-request.js +1 -1
- package/lib/shared/e2ee.js +8 -14
- package/lib/shared/fresh-request.js +6 -22
- package/lib/shared/http-util.js +3 -4
- package/lib/shared/list-revision.js +5 -14
- package/lib/shared/session-signal.js +7 -16
- package/lib/shared/sse-write.js +14 -20
- package/lib/shared/workspace-guard.js +7 -15
- package/lib/shell/device-ota.js +4 -6
- package/lib/shell/manager.js +12 -27
- package/lib/shell/ota-package.js +7 -12
- package/lib/shell/runtime.js +8 -16
- package/lib/shell/self-check-after-swap.js +9 -23
- package/lib/shell/updater.js +13 -26
- package/lib/sidecar-facade-worker.cjs +20 -33
- package/lib/sidecar-facade.cjs +7 -7
- package/lib/sidecar-lifecycle.cjs +7 -9
- package/lib/update-install.cjs +14 -59
- package/lib/update-registry.cjs +6 -18
- package/package.json +8 -5
package/lib/shell/updater.js
CHANGED
|
@@ -1,29 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* 更新编排:查与装都归实现自己,装完就地换代(不重启 DSH)。
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* 面板那颗「更新」按钮只覆盖「用户正开着页面点一下」;自动更新的定义是没人看着也要发生,
|
|
5
|
+
* 所以轮询与「装完切换」都必须在宿主进程里做。
|
|
5
6
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
7
|
+
* 查问 npm 注册表、装在 profile 里跑包管理器(`update-registry.cjs` / `update-install.cjs`)。
|
|
8
|
+
* 外壳只从盘上那一代把自装模块取进来跑,所以换装法不用等重启。下面那套市场实现是兜底,只在
|
|
9
|
+
* 盘上那一代还没有自装模块时走到;市场装完会报 `restartRequired`,但本插件有外壳可以自己
|
|
10
|
+
* 换代,所以那条路里故意不调它的重启。
|
|
8
11
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* 从前「查」和「装」都是请 DSH 插件市场代劳的(打它的 HTTP 接口)。现在两件都归实现自己:
|
|
12
|
-
* 查问 npm 注册表、装在 profile 里跑它自己的包管理器(`lib/update-registry.cjs` /
|
|
13
|
-
* `lib/update-install.cjs`)。外壳**只从盘上那一代**把自装模块取进来跑(`installedInstaller`),
|
|
14
|
-
* 所以换装法、换判定都不用等重启 —— 盘上换了,下一次自动更新就走新的。
|
|
15
|
-
*
|
|
16
|
-
* 下面那套市场实现是**兜底**,只在「盘上那一代还没有自装模块」时才走到(老一代、刚回滚到
|
|
17
|
-
* 旧代)。自动更新宁可暂时还走老路,也不能直接坏掉;市场装完会报 `restartRequired: true`,
|
|
18
|
-
* 而本插件有外壳可以自己换代,所以那条路里**故意不调它的重启**。
|
|
19
|
-
*
|
|
20
|
-
* 装完新一代是**新目录**(包管理器换链接),外壳照着新 realpath 重新 import 一代即可,见
|
|
21
|
-
* `lib/shell/manager.js` 与 `lib/shell/runtime.js`。
|
|
22
|
-
*
|
|
23
|
-
* ## 兜底那条路不做的事
|
|
24
|
-
*
|
|
25
|
-
* 不下载、不解包、不校验、不写 profile —— 全是市场的职责。它只回答四个问题:有没有新版、
|
|
26
|
-
* 请它装、装完了没、盘上现在是什么版本。
|
|
12
|
+
* 兜底那条路不下载、不解包、不校验、不写 profile,只回答「有没有新版、装完了没、盘上是什么
|
|
13
|
+
* 版本」。
|
|
27
14
|
*/
|
|
28
15
|
|
|
29
16
|
import fs from 'node:fs'
|
|
@@ -40,7 +27,7 @@ const { dshHome } = require_('../shared/paths.js')
|
|
|
40
27
|
/**
|
|
41
28
|
* 盘上那一代的「自己装」模块(`lib/update-install.cjs`)。
|
|
42
29
|
*
|
|
43
|
-
*
|
|
30
|
+
* 「装」这件事归实现自己:在 profile 里跑它自己的包管理器,不再借道 DSH
|
|
44
31
|
* 插件市场。外壳只负责把盘上那一代的模块取进来跑 —— 从**盘上**(不是外壳自己的目录)取,
|
|
45
32
|
* 是为了「装法改了不用等重启」:市场以外的方式换掉那一代之后,下一次自动更新就走新装法。
|
|
46
33
|
*
|
|
@@ -229,12 +216,12 @@ const TERMINAL = new Set(['succeeded', 'failed', 'cancelled', 'rolled-back'])
|
|
|
229
216
|
*/
|
|
230
217
|
export async function installIfNeeded(base, { name = PACKAGE_NAME, force = false, auto = false, onEvent = () => {}, packageManager = () => null, agents = () => null, beforeInstall = null, now = Date.now } = {}) {
|
|
231
218
|
const before = installedVersion()
|
|
232
|
-
//
|
|
219
|
+
// 装归实现自己:盘上那一代里有 `update-install.cjs` 就走它。判定(有没有
|
|
233
220
|
// 新版、`link:` 来源不碰、有 agent 在跑就先别换文件)也一并交给它,别在这里抄第二份 ——
|
|
234
221
|
// 外壳的自动更新与面板那颗按钮必须是同一套判定,否则会出现「手动说有新版、自动说没有」。
|
|
235
222
|
//
|
|
236
|
-
// `auto`
|
|
237
|
-
//
|
|
223
|
+
// `auto` 也透下去:自动那一轮跳过「有对话在跑就先别装」那道闸(取舍见
|
|
224
|
+
// `update-install.cjs` 文件头),但没有新版时它照样什么都不做。
|
|
238
225
|
const selfInstall = await installedInstaller()
|
|
239
226
|
if (selfInstall) {
|
|
240
227
|
const outcome = await selfInstall({
|
|
@@ -3,19 +3,16 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* sidecar worker:真正持有「中转隧道 + 局域网代理」的那个子进程。
|
|
5
5
|
*
|
|
6
|
-
* 宿主不自己开 WebSocket、也不自己监听局域网端口,而是 fork 出这个 worker,用环回
|
|
7
|
-
*
|
|
8
|
-
* 失败都只死一个子进程,宿主仍能如实把状态报给 /api/relay/status。
|
|
6
|
+
* 宿主不自己开 WebSocket、也不自己监听局域网端口,而是 fork 出这个 worker,用环回 HTTP +
|
|
7
|
+
* Bearer 令牌对话。隧道崩了、原生模块加载失败都只死一个子进程,宿主仍能如实回报状态。
|
|
9
8
|
*
|
|
10
9
|
* 三条不能忘的语义:
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* 3. 请求体一律先量后读(readBody + 显式上限):`/configure` 与镜像帧都有字节上限,
|
|
18
|
-
* 超限立刻 413,别让一个畸形请求把内存吃光。
|
|
10
|
+
* 1. 状态既要在 `/status` 上可查,也要在变化那一刻主动 `process.send` 给宿主:健康轮询是
|
|
11
|
+
* 10s 一次,只靠轮询的话隧道真断后宿主最多 10s 仍以为连着;「断又连」落在两次轮询之间时
|
|
12
|
+
* 那次全量补推也会漏掉。
|
|
13
|
+
* 2. `/tunnel/send` 先把快照留在本地再尝试真发:局域网直连的手机读本地这份,隧道断了也不
|
|
14
|
+
* 至于读到过期镜像。
|
|
15
|
+
* 3. 请求体一律先量后读:`/configure` 与镜像帧都有字节上限,超限立刻 413。
|
|
19
16
|
*/
|
|
20
17
|
|
|
21
18
|
const http = require('node:http')
|
|
@@ -98,15 +95,12 @@ function authorized(req) { return typeof token === 'string' && token.length >= 6
|
|
|
98
95
|
class BodyTooLarge extends Error {}
|
|
99
96
|
/** /tunnel/send 的请求体上限。
|
|
100
97
|
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
* 旧的 1MB 上限是在把有用的东西挡在门外。
|
|
98
|
+
* 这个接口收桌面推给移动端的镜像:整份会话列表、工作区、当前会话的完整消息与统计。桌面一次
|
|
99
|
+
* 推送就超过 1MB,旧的 1MB 上限把有用的东西挡在门外。
|
|
104
100
|
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
* 丢一次镜像可以恢复,隧道被关掉要等重连、期间手机端全瞎——所以宁可拒也不能赌。
|
|
109
|
-
* 留 64KB 余量给帧头。推得再大也只是这一次 413,隧道安然无恙。
|
|
101
|
+
* 依据是中转那侧的帧上限(默认 8MB):一帧超过它,中转会以 1009 'frame too large' 直接关掉
|
|
102
|
+
* 隧道。丢一次镜像可以恢复,隧道被关掉要等重连、期间手机端全瞎。所以留 64KB 余量给帧头,
|
|
103
|
+
* 推得再大也只是这一次 413。
|
|
110
104
|
*/
|
|
111
105
|
const TUNNEL_SEND_MAX_BODY = 8 * 1024 * 1024 - 64 * 1024
|
|
112
106
|
|
|
@@ -464,22 +458,15 @@ process.once('SIGTERM', shutdown); process.once('SIGINT', shutdown)
|
|
|
464
458
|
/**
|
|
465
459
|
* 父进程(宿主)没了就自己退出:否则这里会变成一具握着隧道的尸体。
|
|
466
460
|
*
|
|
467
|
-
*
|
|
468
|
-
*
|
|
469
|
-
*
|
|
470
|
-
* 隧道、继续如实回答 `/api/mobile/v1/status`(它知道的宿主身份还是那一份),中转的
|
|
471
|
-
* `instances` 里那条记录就永不删除 —— 手机侧栏的设备「在线」从此再也灭不掉,正文
|
|
472
|
-
* 镜像也停在最后一帧。2026-10-10 真机故障,根因就在这里。
|
|
461
|
+
* 宿主被 Cmd+Q、被系统清掉或被 SIGKILL 时,没有人会给这里发 SIGTERM,IPC 也只是静默 EOF。
|
|
462
|
+
* worker 继续活着、继续握着那条注册成功过的隧道,中转的 instances 里那条记录就永不删除,
|
|
463
|
+
* 手机侧栏的设备「在线」再也灭不掉。
|
|
473
464
|
*
|
|
474
|
-
*
|
|
475
|
-
*
|
|
476
|
-
* - ppid 巡检:宿主被 SIGKILL 时**不发** disconnect(实测只看到 ppid 变成 1),
|
|
477
|
-
* 只能靠定时对账。启动时记下 ppid,之后对不上就退出;间隔与宿主那边的健康轮询
|
|
478
|
-
* 同量级(10s),2s 只是让「关掉桌面 → 手机显示离线」快一点。
|
|
465
|
+
* 两道判据互相兜底:`disconnect` 在宿主正常退出时立刻触发(最快);ppid 巡检兜住 SIGKILL
|
|
466
|
+
* (实测只看到 ppid 变成 1,不发 disconnect),启动时记下 ppid,之后对不上就退出。
|
|
479
467
|
*
|
|
480
|
-
*
|
|
481
|
-
*
|
|
482
|
-
* 最快的收敛路径(也让 `ps` 里不再留下一行认不出归属的 node 进程)。
|
|
468
|
+
* 整进程退出而不是只 `stopServices()`:隧道一关,中转要等心跳超时才删实例,这期间手机仍
|
|
469
|
+
* 显示在线;孤儿进程没有存在的理由,退出是最快的收敛路径。
|
|
483
470
|
*/
|
|
484
471
|
const PARENT_WATCH_INTERVAL_MS = 2000
|
|
485
472
|
const parentPidAtStart = process.ppid
|
package/lib/sidecar-facade.cjs
CHANGED
|
@@ -3,14 +3,14 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* SidecarFacade:宿主 ⇄ sidecar worker 的环回 HTTP 客户端 + 生命周期看守。
|
|
5
5
|
*
|
|
6
|
-
* fork worker、等 ready、把它的 stdout/stderr 转发到宿主 stderr
|
|
7
|
-
*
|
|
8
|
-
* 两条上报通道都是「宿主必须立刻知道」的事:onDown(worker 意外退出,否则宿主的隧道/
|
|
9
|
-
* 局域网快照会永远冻在临死前那一帧,比状态难看危险得多)与 onState(worker 在状态
|
|
10
|
-
* 变化那一刻主动推的一帧,比 10s 轮询早得多)。
|
|
6
|
+
* fork worker、等 ready、把它的 stdout/stderr 转发到宿主 stderr(诊断只有这一条通道),之后
|
|
7
|
+
* 按需 request('/status' | '/configure' | '/tunnel/send' | '/revoke-all')。
|
|
11
8
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
9
|
+
* 两条上报通道都是「宿主必须立刻知道」的事:onDown(worker 意外退出,否则隧道/局域网快照会
|
|
10
|
+
* 永远冻在临死前那一帧)、onState(worker 在状态变化那一刻主动推的一帧)。
|
|
11
|
+
*
|
|
12
|
+
* 刻意不做重试:超时就是超时,由调用方决定是否重启;偷偷重试会把「隧道已死」拖成
|
|
13
|
+
* 「偶尔能通」,更难诊断。
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
const http = require('node:http')
|
|
@@ -3,15 +3,13 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* sidecar 子进程的完整生命周期:起停、配置下发、自重启、健康轮询。
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
* 一个 200 行的镜像推送调度器。它们共享同一批可变量(sidecarState / sidecarSnapshot /
|
|
8
|
-
* restartInFlight / syncInFlight),拆到一处才看得清「谁改了什么」。
|
|
6
|
+
* 状态变量、配置下发的串行化、自重启共享同一批可变状态,拆到一处才看得清「谁改了什么」。
|
|
9
7
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
8
|
+
* 扩展名是 .cjs:包根声明 type=module,lib/ 下没有就近的 package.json,所以 lib/*.js 全是
|
|
9
|
+
* ESM,只有 .cjs 是 CommonJS。
|
|
12
10
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
11
|
+
* `sidecar` 是会被整体替换的 let(自重启换掉 facade 实例),所以对外一律给取值器,绝不能把
|
|
12
|
+
* 实例本身传出去。
|
|
15
13
|
*/
|
|
16
14
|
|
|
17
15
|
const path = require('node:path');
|
|
@@ -250,8 +248,8 @@ function createSidecarLifecycle(deps) {
|
|
|
250
248
|
* `configure` 完的那个窗口里快照还是上一个号(或还没绑完),此刻去试绑刚下发的号必然
|
|
251
249
|
* 失败——那是自己占着自己,不是被别人占了。只认快照的话,紧接着的第二次同步就会把这个
|
|
252
250
|
* 号判成「被占」、换个内核随手给的端口写回 relay.json,监听也跟着挪走(`/api/relay/config`
|
|
253
|
-
*
|
|
254
|
-
*
|
|
251
|
+
* 改端口那条路由**一次会同步两遍**,第二遍正好落在这个窗口里,落盘端口一路漂,
|
|
252
|
+
* 手机里存的地址就是这么废的)。
|
|
255
253
|
*
|
|
256
254
|
* 代价照实说:真的被别的程序占着时,这里也认这个号。那没关系——worker 自己 bind 失败会
|
|
257
255
|
* 退到内核给的空闲端口并按**实绑值**上报,对外报的地址(getAdvertisedLanPort)跟着它走。
|
package/lib/update-install.cjs
CHANGED
|
@@ -1,68 +1,23 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* 自己装新版:在 profile
|
|
4
|
+
* 自己装新版:在 profile 里跑一次它自己的包管理器,把本插件换到指定版本。
|
|
5
5
|
*
|
|
6
|
-
*
|
|
6
|
+
* 不借道 DSH 插件市场:更新分「查」(update-registry)和「装」,装该由插件自己完成,不该因为
|
|
7
|
+
* 另一个插件没装、太老或坏了就整个失效。
|
|
7
8
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* 不自己实现包管理,只驱动它:profile 的 pnpm-workspace.yaml 已经写好 node-linker、peer 等
|
|
10
|
+
* 关键设置,这里只「cd 到 profile 跑一句 pnpm add <包名>@<版本>」,不传布局类开关。三处必要
|
|
11
|
+
* 的开关:auto-install-peers=false(peer 是运行时注入的,npm 上没有)、CI=1(无终端),以及
|
|
12
|
+
* 撞上 pnpm「新发布先晾一会儿」时带 minimum-release-age=0 重试一次(那道策略查整份 lockfile,
|
|
13
|
+
* 别的插件刚发也会连累我们)。
|
|
12
14
|
*
|
|
13
|
-
*
|
|
15
|
+
* 包管理器优先用宿主发布的那个(profile 是宿主自带 pnpm 装的,PATH 上版本不同会
|
|
16
|
+
* ERR_PNPM_UNEXPECTED_STORE),找不到再退回 PATH。装之前先看有没有 agent 在跑;force 跳过
|
|
17
|
+
* 这道闸并跳过发布晾置期,auto 只跳过这道闸。顺序上先答「有没有新版」,没东西可装时不该拿
|
|
18
|
+
* 「有对话在跑」挡在前面。
|
|
14
19
|
*
|
|
15
|
-
*
|
|
16
|
-
* 关键设置(`node-linker=hoisted`、`auto-install-peers=false`、`minimum-release-age-exclude`
|
|
17
|
-
* 那些按包放行的条目)。所以这里是「cd 到 profile,跑一句 `pnpm add <包名>@<版本>`」——
|
|
18
|
-
* **不传布局类开关**,让它按 profile 的既有配置来,免得把别人的布局改写掉。市场那套
|
|
19
|
-
* (`pnpm-compat.js` + `install.js`)几千行都在处理 pnpm 各版本的旗标拼写、hoist 恢复、
|
|
20
|
-
* 镜像/超时/Windows shim 这些边角;这里刻意**只做我们自己这条路上必须的那两三个开关**,
|
|
21
|
-
* 剩下的交给 profile 自己的配置。
|
|
22
|
-
*
|
|
23
|
-
* ## 用哪个包管理器:**宿主给的那个优先**
|
|
24
|
-
*
|
|
25
|
-
* 2026-10-11 真机踩到:这台机器 PATH 上的 pnpm 是 10.x(store v10),而 profile 是**宿主
|
|
26
|
-
* 自带的** pnpm 11.x(store v11)装的,拿 PATH 那个去装直接 `ERR_PNPM_UNEXPECTED_STORE`。
|
|
27
|
-
* 所以顺序是「宿主发布的调用方式 → PATH 上的 pnpm → npm」,见 `normalizeInvocation`。
|
|
28
|
-
*
|
|
29
|
-
* ## 三个开关,每个都有理由
|
|
30
|
-
*
|
|
31
|
-
* · `--config.auto-install-peers=false`:本插件的 peer 里有一串 `@deepseek-ai/*`
|
|
32
|
-
* (`cordis`、`dsh-tools`、`dsh-client-runtime`…),它们是 **DSH 运行时注入的、npm 上
|
|
33
|
-
* 根本没有**。pnpm 8 起 `auto-install-peers` 默认开,会去 npm 上拉这些 peer 然后 404。
|
|
34
|
-
* profile 里通常已经关掉了,但那是市场帮它写的 —— 换台机器不一定有,所以这里显式带上。
|
|
35
|
-
* · `--config.minimum-release-age=0`:**撞上 pnpm 的「新发布先晾一会儿」策略时才带**。
|
|
36
|
-
* 强制更新(`force`)一开始就带;平时不带,但**失败信息确实是这类策略时会带开关重试一次**
|
|
37
|
-
* —— 那道策略查的是**整份 lockfile**,别的插件刚发不久也会把整份锁文件判成「含未过期
|
|
38
|
-
* 条目」,于是我们自己的更新整整一天都做不了,而原因跟我们这个包毫无关系(2026-10-11
|
|
39
|
-
* 真机撞上的就是 `billion-context@0.1.192`)。市场那边对这种拒绝同样是带开关重试一次。
|
|
40
|
-
* · `CI=1`:没有终端可交互。包管理器在无人值守的子进程里问问题是没法回答的。
|
|
41
|
-
*
|
|
42
|
-
* ## 装之前先看有没有 agent 在跑(但「有没有新版」排在它前面)
|
|
43
|
-
*
|
|
44
|
-
* 装是**就地替换插件文件**,正在干活的对话可能读到半个包。市场那条路有一道这样的闸
|
|
45
|
-
* (`AGENTS_RUNNING`),这里也补上了 —— 判到有人跑就**不动盘**,如实报成「可再试」。
|
|
46
|
-
*
|
|
47
|
-
* 两档能跳过它:`force`(面板上的「强制更新」,用户明说「我知道,装吧」)与 `auto`(宿主
|
|
48
|
-
* 每 10 分钟那一轮自动更新)。后者是有意的取舍:手机那边几乎一直有对话在跑,按着这道闸等,
|
|
49
|
-
* 自动更新实际上永远轮不到 —— 而它的定义就是「没人看着也会发生」。
|
|
50
|
-
*
|
|
51
|
-
* `auto` **只**跳过这道闸:照样要有新版才装(不跳过下面那条判断),也不一开始就带发布时长
|
|
52
|
-
* 开关(撞上再重试一次就够了)。把 `force` 那一档借给自动会在每 10 分钟把同一个版本重装
|
|
53
|
-
* 一遍,那不是自动更新。
|
|
54
|
-
*
|
|
55
|
-
* 顺序上**先答「有没有新版」**:没东西可装时拿一句「有对话在跑」挡在前面是误导,用户会以为
|
|
56
|
-
* 是那个原因,而真正的答案是「已经是最新」。
|
|
57
|
-
*
|
|
58
|
-
* ## 边界(照实说)
|
|
59
|
-
*
|
|
60
|
-
* · **只动 profile 的依赖树**(`pnpm add` 会顺手把 profile 的 `package.json` 里的依赖声明
|
|
61
|
-
* 也更新掉——那正是「装上了」的凭据)。不碰仓库、不碰 `~/.dsh` 里别的东西。
|
|
62
|
-
* · **找不到包管理器就如实失败**,不猜、不回退到「假装装好了」。桌面端由 GUI 拉起时 PATH
|
|
63
|
-
* 往往很干净(LaunchServices 只给 `/usr/bin:/bin` 之类),所以这里照市场的做法补一组
|
|
64
|
-
* 常见 bin 目录再找,见 `toolSearchDirs`。
|
|
65
|
-
* · 装完**以盘上的版本号为准**:`installedVersion` 没变就是没装上,哪怕命令退出码是 0。
|
|
20
|
+
* 只动 profile 的依赖树;找不到包管理器就如实失败;装完以盘上版本号为准,退出码 0 不算装上。
|
|
66
21
|
*/
|
|
67
22
|
|
|
68
23
|
const fs = require('node:fs');
|
|
@@ -491,7 +446,7 @@ async function installLatestIfNeeded(options = {}) {
|
|
|
491
446
|
}
|
|
492
447
|
const check = await checkForUpdate({ installedVersion: beforeVersion, source });
|
|
493
448
|
// 「有没有新版」排在「有没有人在跑」**前面**:没东西可装的时候,拿一句「有对话在跑」挡住
|
|
494
|
-
//
|
|
449
|
+
// 真正的答案(已经是最新)是误导。
|
|
495
450
|
if (!force && !check.updateAvailable) {
|
|
496
451
|
return {
|
|
497
452
|
installed: false,
|
package/lib/update-registry.cjs
CHANGED
|
@@ -3,25 +3,13 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* 「有没有新版」由插件自己回答:直接问 npm 注册表(`<registry>/<name>/latest`)。
|
|
5
5
|
*
|
|
6
|
-
*
|
|
6
|
+
* 更新分「查」和「装」:查不需要市场(公开包一个 GET 就够),装由 `update-install.cjs` 在
|
|
7
|
+
* profile 里自己跑包管理器。绕开市场的理由很具体:市场没装、太老或坏了,「检查更新」就整个
|
|
8
|
+
* 答不出来。
|
|
7
9
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* 的版本」。但**查**不需要市场:这是个公开的 npm 包,一个 GET 就够了。
|
|
12
|
-
*
|
|
13
|
-
* 绕市场的代价很具体:市场(另一个插件)没装、太老或者坏了,「检查更新」就整个答不出来
|
|
14
|
-
* —— 一个插件得另一个插件在场,才能告诉你自己有没有新版。所以这一格拿回来自己做。
|
|
15
|
-
*
|
|
16
|
-
* ## 边界(别把这条读成「更新彻底不依赖市场了」)
|
|
17
|
-
*
|
|
18
|
-
* · 这里**只查不装**:从头到尾不碰 profile、不碰市场、不写盘。装仍然是
|
|
19
|
-
* `/api/relay/update/apply` 那条路(外壳问市场 → 市场装 → 外壳就地换代)。
|
|
20
|
-
* · `source`(npm / 本地 link / `file:` tgz)只有市场知道 —— 它读的是 profile 里的依赖
|
|
21
|
-
* 声明。所以那一格传进来的是外壳**上次**问市场的结果;市场不在时如实为空,本模块
|
|
22
|
-
* **不会**为了这一格去敲市场。
|
|
23
|
-
* · 判据是「线上版本严格大于盘上版本」。线上比盘上旧(回滚、本地构建跑在发布前面)一律
|
|
24
|
-
* 报「没有新版」——「这次更新会降级」必须由版本比较判定,不能由「两个号不相等」判定。
|
|
10
|
+
* 边界:这里只查不装,不碰 profile、不写盘;`source`(npm / link / file:)只有市场知道,传
|
|
11
|
+
* 进来的是上次问到的结果,本模块不会为它去敲市场。判据是「线上版本严格大于盘上版本」,线上
|
|
12
|
+
* 更旧一律报没有新版——「这次更新会降级」必须由版本比较判定。
|
|
25
13
|
*/
|
|
26
14
|
|
|
27
15
|
/** 默认注册表:官方源。私有镜像 / 测试用 `DSHM_NPM_REGISTRY` 覆盖。 */
|
package/package.json
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-mobile-plugin",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Mobile API (/api/mobile/v1) and relay client for the DSH mobile app — desktop profile build (0.2.
|
|
3
|
+
"version": "0.4.1",
|
|
4
|
+
"description": "Mobile API (/api/mobile/v1) and relay client for the DSH mobile app — desktop profile build (dsh 0.2.x, peers resolved by the host)",
|
|
5
5
|
"license": "MIT",
|
|
6
|
+
"engines": {
|
|
7
|
+
"dsh": ">=0.2.0 <0.3.0"
|
|
8
|
+
},
|
|
6
9
|
"type": "module",
|
|
7
10
|
"main": "lib/index.js",
|
|
8
11
|
"bin": {
|
|
@@ -46,8 +49,6 @@
|
|
|
46
49
|
"release:check": "node scripts/preflight-release.mjs"
|
|
47
50
|
},
|
|
48
51
|
"dependencies": {
|
|
49
|
-
"@deepseek-ai/dsh-tool-ask-user": "0.2.1-rc.2",
|
|
50
|
-
"@deepseek-ai/dsh-tool-present": "0.2.1-rc.2",
|
|
51
52
|
"qrcode": "1.5.4",
|
|
52
53
|
"ws": "8.18.0"
|
|
53
54
|
},
|
|
@@ -73,7 +74,9 @@
|
|
|
73
74
|
}
|
|
74
75
|
},
|
|
75
76
|
"devDependencies": {
|
|
76
|
-
"@deepseek-ai/dsh-host-directory-picker-browse": "0.
|
|
77
|
+
"@deepseek-ai/dsh-host-directory-picker-browse": "^0.2.1-rc.3",
|
|
78
|
+
"@deepseek-ai/dsh-tool-ask-user": "^0.2.1-rc.3",
|
|
79
|
+
"@deepseek-ai/dsh-tool-present": "^0.2.1-rc.3",
|
|
77
80
|
"yaml": "2.9.1"
|
|
78
81
|
}
|
|
79
82
|
}
|