@feiyang666/dsh-mobile-remote 1.5.4 → 1.6.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/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # 更新日志 / Changelog
2
2
 
3
+ ## v1.6.1 (2026-08-23)
4
+
5
+ ### 修复 / Fixed
6
+
7
+ - **用量插件等宽表在移动端被压成逐字竖排(可靠兜底)**(`lib/index.js` MOBILE_CSS):`dsh-usage-plugin` 的「消耗表 / 消耗明细 / 每日统计」等 9–11 列宽表依赖其运行时注入的全局样式(`.dsh-usage-table{min-width:720px}` + ≤540px 折叠中间列)来避免列被压到极限宽度;若该样式因 CSP / 加载时序未生效,手机窄屏下每列会被压到约 1 字符宽、内容逐字竖排、无法阅读。本插件(dsh-mobile-remote)的样式是**随 index.html 一起由服务端下发、必定生效**,故在 `<style id="dsh-mobile-remote-css">` 中新增兜底:`html[data-dsh-mobile] .dsh-usage-table { min-width: 720px !important; }`,并在 ≤540px 时对带 `collapse-mobile` 的宽表隐藏第 4 列至倒数第 2 列、只保留前 3 个主标识列与最后合计列。这样即使旧版用量插件未更新或其样式表注入失败,移动端宽表也始终可横向滑动阅读、关键数据一屏看全。价格表等窄表(无 `collapse-mobile`)不折叠,保留横向滚动。
8
+
9
+ ## v1.6.0 (2026-08-23)
10
+
11
+ ### 新增 / New
12
+ - **SSE 实时推送端点**(`lib/index.js`):新增 `GET /__dsh_remote/events` 事件流端点(受远程密码门禁保护),设备心跳上报 / 远程开关切换 / 外网隧道启动停止等状态变化时向订阅者推送 `data: {"type":"changed","at":<时间戳>}`。桌面端数据中心等客户端订阅该流即可即时感知远程设备与隧道状态变化,无需轮询。无订阅者时零开销,插件独立使用不受影响。
13
+
3
14
  ## v1.5.4 (2026-08-23)
4
15
 
5
16
  ### 新增 / New
package/RELEASE_NOTES.md CHANGED
@@ -1,4 +1,4 @@
1
- # dsh-mobile-remote v1.5.4
1
+ # dsh-mobile-remote v1.6.1
2
2
 
3
3
  **DeepSeek Harness 移动端远程控制插件** —— 在 Web UI 设置页内置「远程控制」:连接二维码、一键开关、在线设备数,让手机通过局域网访问并操控电脑上的 DeepSeek Harness。移动端提供上传图片入口、底部抽屉式模型选择、发送状态提示等移动优先体验。
4
4
 
@@ -10,52 +10,25 @@ dsh plugin --profile web add @feiyang666/dsh-mobile-remote
10
10
 
11
11
  安装完成后**重启 dsh web 服务**,打开 `http://127.0.0.1:3080` → **设置 → 远程控制** 即可使用。
12
12
 
13
- ## 本版更新(v1.5.4)
13
+ ## 本版更新(v1.6.1)
14
14
 
15
- ### 🐛 修复移动端无法选择图片(v1.5.0 起就存在的根因)
15
+ ### 🐛 用量插件等宽表在移动端被压成逐字竖排(可靠兜底)
16
16
 
17
- 图片类型校验正则写为 `/^image\//i`,但该代码经 JS **模板字符串**注入 `<script>`,模板字符串中 `\/` 属于非转义序列、反斜杠会被丢弃,注入后变成 `/^image//i`,被解析成「正则 `/^image/` 除以 `i.test(...)`」,运行时报 `TypeError: i.test is not a function`——从 v1.5.0 起手机端选图后输入框一直没有缩略图。本版改用 `indexOf('image/')` 判断图片类型,彻底绕开正则转义问题。
17
+ `dsh-usage-plugin` 的「消耗表 / 消耗明细 / 每日统计」等 9–11 列宽表依赖其运行时注入的全局样式(`.dsh-usage-table{min-width:720px}` + ≤540px 折叠中间列)来避免列被压到极限宽度;若该样式因 CSP / 加载时序未生效,手机窄屏下每列会被压到约 1 字符宽、内容逐字竖排、无法阅读。
18
18
 
19
- 同时配套修复:
20
- - 图片 intake 不再依赖 `new DataTransfer()`(iOS Safari 上 `files` 为空、`getAsFile()` 返回 null),改用普通 JS 对象模拟 `clipboardData` / `dataTransfer` 接口;
21
- - 模拟 `clipboardData` 补齐 DSH `onPaste` 会调用的 `getData()`,避免 React 事件处理器崩溃;
22
- - 隐藏文件选择器改为等 `document.body` 就绪后再挂载,避免部分 iOS WebView 拒绝 `.click()` 打开系统相册。
19
+ 本插件(dsh-mobile-remote)的样式是**随 index.html 一起由服务端下发、必定生效**,故在 `<style id="dsh-mobile-remote-css">` 中新增兜底:
23
20
 
24
- ### 🆕 移动端「上传中 / 发送中」状态指示
21
+ - `html[data-dsh-mobile] .dsh-usage-table { min-width: 720px !important; }`,宽表强制保持 720px 宽;
22
+ - ≤540px 时对带 `collapse-mobile` 的宽表隐藏第 4 列至倒数第 2 列,只保留前 3 个主标识列与最后合计列,关键数据一屏看全;
23
+ - 价格表等窄表(无 `collapse-mobile`)不折叠,保留横向滚动。
25
24
 
26
- 发送图片消息时,DSH 会先 `serializeImages`(读取图片转 base64,移动端大图可能持续数秒)再 `prompt`,期间会话列表不会立刻出现新消息,用户容易误以为「没发出去」。本版监听 composer textarea 的 `data-phase`,进入 `submitting` / `adjudicating` 阶段时在 composer 上方显示带 spinner 的浮层提示:
25
+ 这样即使旧版用量插件未更新或其样式表注入失败,移动端宽表也始终可横向滑动阅读。
27
26
 
28
- - 有附件图片时显示「正在上传图片并发送…」;
29
- - 纯文本时显示「正在发送…」;
30
- - 发送完成(phase 恢复)后自动隐藏。
27
+ ## 历史更新(v1.6.0)
31
28
 
32
- ### 🐛 模型选择弹窗在移动端改为底部抽屉
29
+ ### 🆕 SSE 实时推送端点
33
30
 
34
- ModelSelect 菜单为绝对定位浮层(`right: 0`、宽 240px),窄屏下从右侧锚点向左展开,把 composer 左侧的 + / 上传图片等按钮盖住。本版将带 `aria-label` 的 `[role=menu]`(模型选择菜单)在移动端改为**底部抽屉(bottom sheet)**:贴底、左右留 8px、距底部 24px、宽度自适应屏幕、`max-height: 62vh` 内部滚动,不再遮挡左侧内容;权限选择菜单保持原有浮层 + 水平钳制。
35
-
36
- ## 历史更新(v1.5.0)
37
-
38
- ### 🆕 移动端上传图片(composer 加号旁的「上传图片」按钮)
39
-
40
- DSH 桌面端原生支持图片消息(粘贴 / 拖放,含格式 / 数量 / 大小预检查、缩略图 rail、随消息上传),但移动端没有「选择图片」的入口——工具行的 + 按钮只打开命令菜单。本版在**移动端 composer 工具行、+ 加号按钮旁**注入一个「上传图片」按钮:
41
-
42
- - 点击按钮,直接唤起手机**系统相册 / 文件选择器**(支持一次选多张);
43
- - 选中后把图片喂给 DSH **原生 intake 链路**(`paste` / `drop` 事件),格式 / 数量 / 大小预检查、缩略图预览、随消息上传全部复用官方实现;
44
- - **不依赖 DSH 内部 API**,harness 升级后只要 paste / drop 链路仍在即可正常工作;
45
- - 按钮禁用态跟随输入框(session 移除 / 锁定 / 提交中自动置灰);选完文件自动重置,可重复选同一张图;
46
- - 配套优化:缩略图 rail 删除按钮触控目标加大(18px → 24px),选图失败时自备极简提示气泡。
47
-
48
- > 仅移动端(`<1024px`)生效,桌面端界面不受任何影响。
49
-
50
- ### 🐛 修复启用远程控制后写坏 profile 的 cordis.patch.yml(服务启动即崩溃)
51
-
52
- 在设置页开启「远程控制」并重启服务后,dsh 启动即崩溃。**根因**:官方模板生成的 profile patch 文件内容是 `[]`(空数组占位),插件往文件末尾追加列表块时未先移除占位行,导致同一文件出现两个 YAML 文档且无 `---` 分隔符。**修复**:写入前过滤掉裸 `[]` 占位行(`setPatchEnabled` / `ensureTrustedHost`)。
53
-
54
- ### 🐛 修复客户端插件加载失败(client-modules: loaded without registering)
55
-
56
- client 侧 bundle 中 `__ModuleLoader__.load` 的 `id` 写成了无 scope 的裸包名,改为完整包名 `@feiyang666/dsh-mobile-remote`,与官方插件的 client bundle 写法一致。
57
-
58
- > 已安装旧版本的用户:请先卸载本插件(桌面端「插件管理」或 `dsh plugin --profile web remove @feiyang666/dsh-mobile-remote`),再重新安装本修复版。
31
+ 新增 `GET /__dsh_remote/events` 事件流端点(受远程密码门禁保护),设备心跳上报 / 远程开关切换 / 外网隧道启动停止等状态变化时向订阅者推送 `data: {"type":"changed","at":<时间戳>}`。桌面端数据中心等客户端订阅该流即可**即时**感知远程设备与隧道状态变化,无需轮询。无订阅者时零开销,插件独立使用不受影响。
59
32
 
60
33
  ## 已知事项
61
34
 
package/lib/index.js CHANGED
@@ -59,6 +59,19 @@ const HEARTBEAT_TTL = 90_000
59
59
  /** 心跳清理定时器间隔(毫秒)。 */
60
60
  const HEARTBEAT_SWEEP = 30_000
61
61
 
62
+ // ---- SSE 实时推送 ----
63
+ // 设备心跳 / 远程开关 / 外网隧道状态变化时通知订阅者(订阅端点 /__dsh_remote/events)。
64
+ // 无订阅者时是 no-op,插件独立使用不受影响。
65
+ const sseClients = new Set()
66
+
67
+ function notifyChanged() {
68
+ if (sseClients.size === 0) return
69
+ const payload = 'data: {"type":"changed","at":' + Date.now() + '}\n\n'
70
+ for (const res of sseClients) {
71
+ try { res.write(payload) } catch (e) { sseClients.delete(res) }
72
+ }
73
+ }
74
+
62
75
  /**
63
76
  * 插件入口。webServer 服务就绪后被调用。
64
77
  * @param {import('@deepseek-ai/cordis').Context} ctx
@@ -104,6 +117,8 @@ export function apply(ctx) {
104
117
  ctx.webServer.register({ kind: 'exact', path: '/__dsh_remote/external/stop', handler: authGate(handleExternalStop(ctx, external)) }),
105
118
  // frpc 运行日志读取(供设置页展示外网隧道详细状态)
106
119
  ctx.webServer.register({ kind: 'exact', path: '/__dsh_remote/external/log', handler: authGate(handleExternalLog()) }),
120
+ // —— SSE 实时事件流:设备心跳 / 远程开关 / 外网隧道状态变化时推送 {"type":"changed"} ——
121
+ ctx.webServer.register({ kind: 'exact', path: '/__dsh_remote/events', handler: authGate(handleEvents()) }),
107
122
  // —— 远程访问密码门禁相关(auth 系列不套 authGate,需放行)——
108
123
  ctx.webServer.register({ kind: 'exact', path: '/__dsh_remote/auth-status', handler: handleAuthStatus() }),
109
124
  ctx.webServer.register({ kind: 'exact', path: '/__dsh_remote/auth', handler: handleAuth() }),
@@ -415,6 +430,21 @@ function authGate(handler) {
415
430
  }
416
431
  }
417
432
 
433
+ /** GET /__dsh_remote/events:SSE 实时事件流。设备心跳 / 远程开关 / 外网隧道状态变化时推送。 */
434
+ function handleEvents() {
435
+ return (req, res) => {
436
+ res.writeHead(200, {
437
+ 'Content-Type': 'text/event-stream; charset=utf-8',
438
+ 'Cache-Control': 'no-cache',
439
+ Connection: 'keep-alive',
440
+ 'X-Accel-Buffering': 'no',
441
+ })
442
+ res.write(':ok\n\n')
443
+ sseClients.add(res)
444
+ req.on('close', () => { sseClients.delete(res) })
445
+ }
446
+ }
447
+
418
448
  /** GET /__dsh_remote/auth-status:返回远程访问密码的启用状态与本次请求是否需鉴权。 */
419
449
  function handleAuthStatus() {
420
450
  return async (req, res) => {
@@ -767,6 +797,7 @@ function handleToggle(ctx) {
767
797
  const enabled = body.enabled === true
768
798
  try {
769
799
  const result = setPatchEnabled(ctx, enabled)
800
+ notifyChanged() // 远程开关变化:通知 SSE 订阅者
770
801
  json(res, 200, {
771
802
  ok: true,
772
803
  enabled,
@@ -818,6 +849,7 @@ function handleHeartbeat(devices, tracker) {
818
849
  firstSeen: prev ? prev.firstSeen : now,
819
850
  lastSeen: now,
820
851
  })
852
+ notifyChanged() // 设备状态更新:通知 SSE 订阅者
821
853
  json(res, 200, { ok: true, deviceCount: devices.size })
822
854
  }
823
855
  }
@@ -864,9 +896,11 @@ function handleExternalStart(ctx, external) {
864
896
  try {
865
897
  const patchFile = patchFilePath(ctx)
866
898
  const result = await startExternal(external, body, patchFile, ctx.webServer.port || 3080)
899
+ notifyChanged() // 外网隧道启动:通知 SSE 订阅者
867
900
  json(res, 200, Object.assign({ ok: true }, result))
868
901
  } catch (error) {
869
902
  external.error = String(error?.message || error)
903
+ notifyChanged() // 启动失败也通知(前端可感知错误状态)
870
904
  json(res, 500, { ok: false, error: external.error })
871
905
  }
872
906
  }
@@ -876,6 +910,7 @@ function handleExternalStart(ctx, external) {
876
910
  function handleExternalStop(ctx, external) {
877
911
  return async (req, res) => {
878
912
  stopExternal(external)
913
+ notifyChanged() // 外网隧道停止:通知 SSE 订阅者
879
914
  json(res, 200, { ok: true, enabled: false })
880
915
  }
881
916
  }
@@ -1360,6 +1395,30 @@ html[data-dsh-mobile] [data-composer-card] [role='menu'][aria-label] {
1360
1395
  overflow: hidden !important;
1361
1396
  overscroll-behavior: contain !important;
1362
1397
  }
1398
+
1399
+ /* ----- 移动端插件宽表兜底:防止被压成逐字竖排 -----
1400
+ * dsh-usage-plugin 的「消耗表 / 消耗明细 / 每日统计」等 9–11 列宽表依赖其
1401
+ * 自身在运行时注入的全局样式(.dsh-usage-table{min-width:720px} + ≤540px
1402
+ * 折叠中间列)来避免列被压到极限宽度。若该样式因 CSP 或加载时序未生效,
1403
+ * 手机窄屏下每列会被压到约 1 字符宽、内容逐字竖排、数据无法阅读。
1404
+ * 本插件(dsh-mobile-remote)的样式是随 index.html 一起由服务端下发的,
1405
+ * 必定生效,因此作为可靠兜底:
1406
+ * - 宽表强制 min-width:720px,配合其外层 overflow-x 容器横向滑动,始终可读;
1407
+ * - ≤540px 时对带 collapse-mobile 的宽表隐藏第 4 列至倒数第 2 列,只保留
1408
+ * 前 3 个主标识列 + 最后合计列,手机上一屏即可看全关键数据;
1409
+ * - 价格表等窄表(无 collapse-mobile)不折叠,保留横向滚动。
1410
+ */
1411
+ html[data-dsh-mobile] .dsh-usage-table {
1412
+ min-width: 720px !important;
1413
+ }
1414
+ @media (max-width: 540px) {
1415
+ html[data-dsh-mobile] .dsh-usage-table.collapse-mobile tr > *:not(:nth-child(-n+3)):not(:last-child) {
1416
+ display: none !important;
1417
+ }
1418
+ html[data-dsh-mobile] .dsh-usage-table.collapse-mobile {
1419
+ min-width: 320px !important;
1420
+ }
1421
+ }
1363
1422
  `
1364
1423
 
1365
1424
  /* ------------------------------------------------------------------ */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@feiyang666/dsh-mobile-remote",
3
- "version": "1.5.4",
3
+ "version": "1.6.1",
4
4
  "description": "DeepSeek Harness mobile remote-control plugin: LAN/external remote control (QR / on-off toggle / remote-access password gate), rich telemetry (expandable device details, system CPU/memory/load, DSH app state: version/sessions/workspaces/plugins/models), real external-tunnel status detection (frpc token / reconnect diagnostics), mobile-first UI polish (composer image upload entry via system picker, settings single-column layout) and faster remote loading.",
5
5
  "keywords": [
6
6
  "deepseek",