dsh-mobile-plugin 0.1.92 → 0.1.94

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.
Files changed (38) hide show
  1. package/README.md +3 -1
  2. package/config/agent-presets/standard/agent.cordis.yml +7 -4
  3. package/lib/desktop-view.cjs +20 -0
  4. package/lib/impl.js +7 -2
  5. package/lib/mirror/pusher.cjs +40 -0
  6. package/lib/mobile/archive-clean.js +17 -18
  7. package/lib/mobile/attachments.js +23 -6
  8. package/lib/mobile/events.js +28 -3
  9. package/lib/mobile/history-derive.js +53 -7
  10. package/lib/mobile/host-bridge/endpoints.js +0 -1
  11. package/lib/mobile/host-bridge/event-synthesis.js +33 -3
  12. package/lib/mobile/host-bridge/shims.js +28 -4
  13. package/lib/mobile/monitor.js +135 -0
  14. package/lib/mobile/session-flags.js +63 -0
  15. package/lib/mobile/session-models.js +6 -0
  16. package/lib/mobile/session-preview.js +70 -0
  17. package/lib/mobile/session-send.js +8 -2
  18. package/lib/mobile/session-snapshot.js +13 -19
  19. package/lib/mobile/sessions.js +48 -91
  20. package/lib/mobile/stream.js +113 -16
  21. package/lib/mobile/uploads.js +14 -7
  22. package/lib/mobile/workspaces.js +17 -3
  23. package/lib/panel/index.html +7 -7
  24. package/lib/reconnect-scheduler.cjs +31 -4
  25. package/lib/relay/lan-network.cjs +156 -12
  26. package/lib/relay/lan-proxy.js +85 -7
  27. package/lib/relay/protocol.js +3 -3
  28. package/lib/relay/tunnel.js +17 -4
  29. package/lib/routes/control-plane.js +11 -5
  30. package/lib/routes/mobile-api.js +22 -1
  31. package/lib/session-cleanup.cjs +160 -27
  32. package/lib/shared/desktop-view-signal.js +66 -0
  33. package/lib/shared/http-util.js +50 -4
  34. package/lib/shared/list-revision.js +20 -1
  35. package/lib/shared/sse-write.js +52 -2
  36. package/lib/sidecar-facade-worker.cjs +16 -2
  37. package/lib/update-install.cjs +71 -13
  38. package/package.json +2 -2
@@ -138,8 +138,6 @@ function installedVersion(profileNodeModules, name = DEFAULT_NAME) {
138
138
  /**
139
139
  * profile 的依赖声明里这个包是怎么装的(`profileDir` 是 profile 根,不是它的 node_modules)。
140
140
  *
141
- * 只有 `link:` 要拦:那是**故意**把插件指向某个本地目录(开发时),装一次 npm 版会把这个
142
- * 指向悄悄改掉。`file:` 的 tgz 与普通版号都照装(前者会一并换成注册表来源,这是想要的)。
143
141
  * 读不到就说不知道(null),不要瞎猜。
144
142
  */
145
143
  function declaredSource(profileDir, name = DEFAULT_NAME) {
@@ -156,6 +154,43 @@ function declaredSource(profileDir, name = DEFAULT_NAME) {
156
154
  }
157
155
  }
158
156
 
157
+ /** 「只有从注册表装的,才由注册表来更新」——其余装法一律不碰盘。 */
158
+ const REGISTRY_SOURCES = new Set(['npm']);
159
+
160
+ /**
161
+ * 一次安装请求能不能动盘。
162
+ *
163
+ * 判据是 profile 里**声明的来源**,不是「装没装得上」:`link:` 指向本地目录(开发时故意
164
+ * 指过去的)、`file:` 指向本机打出来的 tgz(开发版)、`workspace:` 指向本仓库的包——
165
+ * 这三种都是有人**明确安排**的装法,装一次注册表版会把那个指向悄悄改掉,而这正是开发机
166
+ * 上最不想要的一次覆盖:改完一轮代码,后台自己把 npm 版装回来,然后所有本地改动「明明
167
+ * 改了却没生效」。
168
+ *
169
+ * 所以「能不能点更新」这一格由来源决定,不由外部命令决定:非注册表来源**连试都不试**,
170
+ * 如实回一个稳定错误码,让人知道这台是怎么更新的(插件自己的改动只能由改代码的人来推)。
171
+ *
172
+ * 读不到来源(profile 里没有这条依赖,或文件坏了)不算已知来源:那种现场通常意味着
173
+ * 插件压根不是从 profile 装的,装一次反而是「修复」,所以放行。
174
+ *
175
+ * @param {string|null} source - `declaredSource` 的结果。
176
+ * @returns {{ ok: true } | { ok: false, code: string, message: string }}
177
+ */
178
+ function installAllowedFor(source) {
179
+ if (source === null || REGISTRY_SOURCES.has(source)) return { ok: true };
180
+ const how = source === 'linked' ? 'link: 一个本地目录'
181
+ : source === 'file' ? 'file: 一个本机打出来的包'
182
+ : source === 'workspace' ? 'workspace: 本仓库里的包'
183
+ : `来源 ${source}`;
184
+ return {
185
+ ok: false,
186
+ code: 'LOCAL_SOURCE',
187
+ message:
188
+ `这个插件在 profile 里是 ${how}(${source})装的,是有意为之:改成注册表版会把那个指向换掉,`
189
+ + '本地的改动就再也不生效了。所以这里既不检查也不安装 —— 这一台的插件更新由改代码的人来做'
190
+ + '(同步文件后让宿主就地重载);要换回按版号安装,先在插件市场里按包名重装一次。',
191
+ };
192
+ }
193
+
159
194
  /**
160
195
  * 宿主**发布出来**的包管理器调用方式(DSH 桌面版把它挂在 `profileContext.packageManager`
161
196
  * 上,形状 `{command, args, env?}`)。
@@ -280,6 +315,14 @@ async function installLatest({
280
315
  failure: { code, message },
281
316
  });
282
317
 
318
+ /**
319
+ * 来源那道门排在**挑包管理器之前**:非注册表来源连 `pnpm --version` 都不该跑一次 ——
320
+ * 那一步只是探活,但排在前面会让「这台根本不该装」和「这台装不了」混成同一种失败。
321
+ */
322
+ const source = declaredSource(profileDir, name);
323
+ const gate = installAllowedFor(source);
324
+ if (!gate.ok) return fail(gate.code, gate.message);
325
+
283
326
  /**
284
327
  * 挑一个能用的包管理器:**宿主给的那个先试**(探一次 `--version`,跑不起来就当没有),
285
328
  * 之后才是 PATH 上的 pnpm / npm。返回 `{command, prefix, manager, kind}`。
@@ -313,15 +356,6 @@ async function installLatest({
313
356
  );
314
357
  }
315
358
 
316
- const source = declaredSource(profileDir, name);
317
- if (source === 'linked') {
318
- return fail(
319
- 'LINKED_SOURCE',
320
- '这个插件在 profile 里指向一个本地目录(link:),是有意为之。自己装会把那个指向改掉,' +
321
- '所以这里不动它:要去掉这个指向,先在插件市场里按包名重装一次。',
322
- );
323
- }
324
-
325
359
  const spec = `${name}@${version}`;
326
360
  const releaseAgeFlag = '--config.minimum-release-age=0';
327
361
  const args = picked.manager === 'pnpm'
@@ -394,12 +428,19 @@ async function installLatest({
394
428
  }
395
429
 
396
430
  /**
397
- * 「让我变成最新」:先看有没有人在干活,再查一次,最后决定装不装。
431
+ * 「让我变成最新」:先看这台是不是注册表来源,再看有没有人在干活,最后决定装不装。
398
432
  *
399
433
  * 面板那颗按钮**与外壳的自动更新**都打这个入口 —— 两处走同一套判定,免得出现「手动说有
400
434
  * 新版、自动说没有」这种最难查的偏差。已经是最新(`force` 为假)就什么都不做、如实报
401
435
  * `skipped`。
402
436
  *
437
+ * ## 为什么来源排在第一位
438
+ *
439
+ * 只有从注册表装的包才该由注册表来更新。`link:` / `file:` / `workspace:` 是有人**明确
440
+ * 安排**的装法(开发机就是这么跑的),装一次注册表版会把那个指向悄悄换掉 —— 表现为「明明
441
+ * 改了代码却没生效」,而改代码的人还以为是缓存。`force` 也过不去这一关:这不是「现在不行」,
442
+ * 是「这台不该这么装」。
443
+ *
403
444
  * ## 为什么装之前先看有没有 agent 在跑
404
445
  *
405
446
  * 装会**就地换掉插件文件**(profile 里那一份),而正在干活的对话可能正好在装配/读取插件
@@ -412,7 +453,24 @@ async function installLatest({
412
453
  async function installLatestIfNeeded(options = {}) {
413
454
  const { profileNodeModules, force = false, name = DEFAULT_NAME, agents = null } = options;
414
455
  const beforeVersion = installedVersion(profileNodeModules, name);
415
- const check = await checkForUpdate({ installedVersion: beforeVersion, source: declaredSource(path.dirname(profileNodeModules), name) });
456
+ // 来源那道门排在最前:非注册表来源连「线上最新是几」都不用问 —— 答案再新也不该装,
457
+ // 而且这一条同时管着手动与自动两条路(自动更新永远不带 force,照样过不去)。
458
+ const source = declaredSource(path.dirname(profileNodeModules), name);
459
+ const gate = installAllowedFor(source);
460
+ if (!gate.ok) {
461
+ return {
462
+ installed: false,
463
+ skipped: true,
464
+ beforeVersion,
465
+ installedVersion: beforeVersion,
466
+ source,
467
+ command: null,
468
+ output: '',
469
+ failure: { code: gate.code, message: gate.message },
470
+ check: null,
471
+ };
472
+ }
473
+ const check = await checkForUpdate({ installedVersion: beforeVersion, source });
416
474
  // 「有没有新版」排在「有没有人在跑」**前面**:没东西可装的时候,拿一句「有对话在跑」挡住
417
475
  // 真正的答案(已经是最新)是误导 —— 2026-10-11 真机上就是这么被绊了一下。
418
476
  if (!force && !check.updateAvailable) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-mobile-plugin",
3
- "version": "0.1.92",
3
+ "version": "0.1.94",
4
4
  "description": "Mobile API (/api/mobile/v1) and relay client for the DSH mobile app — desktop profile build (0.2.0-rc.2 compatible, peers resolved by the host)",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -45,7 +45,7 @@
45
45
  "test": "node --test test/*.test.cjs && node test/lan-direct-e2e.cjs && node test/sidecar-facade.cjs"
46
46
  },
47
47
  "dependencies": {
48
- "@deepseek-ai/dsh-tool-ask-user": "0.1.1-rc.2",
48
+ "@deepseek-ai/dsh-tool-ask-user": "0.2.0-rc.2",
49
49
  "qrcode": "1.5.4",
50
50
  "ws": "8.18.0"
51
51
  },