dsh-pocket 1.13.0 → 1.13.2

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/README.en.md CHANGED
@@ -48,6 +48,7 @@ What it looks like — the phone shows the exact same UI as your computer, live:
48
48
  | 🧘 Session persistence | Enter the PIN once and you're set for a long time (login is tied to the computer's dsh web process: as long as it stays up, the phone won't ask again; **after a dsh web restart/update, enter it once more**) |
49
49
  | ⚡ Real-time sync | Streaming output passes through WebSocket untouched — what the computer renders, the phone renders live; fully interactive both ways; built-in WS heartbeat keep-alive (defeats silent NAT/battery link drops with auto-reconnect) |
50
50
  | 📱 Mobile-adaptive layout | Narrow screens get a drawer layout automatically (ported from dsh-web-mobile, MIT): sidebar drawer, full-width conversation, safe-area insets, touch optimizations |
51
+ | 📁 File browser | The mobile "Files" entries need a host-side explorer panel (a dsh-web-ui component); on stock DSH without it the entries are auto-hidden instead of doing nothing |
51
52
  | 🗜️ Transfer compression | Large JSON responses are gzip/brotli'd on the fly (17MB session history → ~1MB; brotli quality 6: fast and bandwidth-friendly) — faster loads, less mobile data |
52
53
  | 🔁 Tunnel auto-restore | After a DSH restart the previously-running public tunnel comes back automatically |
53
54
  | 🧩 Zero-dependency install | One npm package, one settings tab — no core/adapter split, no account, no server |
package/README.md CHANGED
@@ -48,6 +48,7 @@ DSH Pocket 就是干这个的:**装上它,手机扫个码,就能实时看
48
48
  | 🧘 会话保持 | 手机输一次密码后**长期免输**(登录状态绑定电脑上的 dsh web 进程:只要它不重启,手机不用再输;**dsh web 重启/更新后需重新输入一次**) |
49
49
  | ⚡ 实时同步 | 流式输出走 WebSocket 全透传——**电脑上在输出,手机上同步在滚**,可双向操作;内置心跳保活(防路由器 NAT/省电机制静默断链,断线自动重连) |
50
50
  | 📱 移动端适配 | 窄屏自动变抽屉布局(移植 dsh-web-mobile,MIT):侧栏抽屉、会话全宽、状态栏安全区、触控优化 |
51
+ | 📁 文件浏览 | 移动端「文件浏览」入口需要宿主提供 explorer 面板(dsh-web-ui 组件);官方 DSH 未内置时入口自动隐藏,不会出现"点了没反应" |
51
52
  | 🗜️ 传输压缩 | 大 JSON 响应自动 gzip/brotli(长会话 17MB → ~1MB,brotli 质量 6:快且省流量),手机加载更快、更省流量 |
52
53
  | 🔁 隧道自动恢复 | DSH 重启后自动重新拉起之前开着的公网隧道,无需手动重开 |
53
54
  | 🧩 零依赖安装 | 一个 npm 包、一个设置页,没有核心/适配器要分开装;无需账号、无需服务器 |
package/client/client.js CHANGED
@@ -205,7 +205,7 @@ function MobileNavOverlay({ toggleSidebar, t }) {
205
205
  if (drawer === null || !drawer.contains(target)) return;
206
206
  if (target.closest('[class*="sessionRow"] button') !== null) return;
207
207
  const navigates = target.closest(
208
- 'button[data-dsh-taskboard-entry], button[data-dsh-ssh-entry], [class*="newSession"], [class*="sessionRow"], [class*="searchResultRow"], [class*="searchResultWorkspace"]'
208
+ 'button[data-dsh-taskboard-entry], button[data-dsh-ssh-entry], [class*="newSession"], [class*="sessionRow"], [class*="searchResultRow"], [class*="searchResultWorkspace"], [data-mobile-nav="files"]'
209
209
  );
210
210
  if (navigates !== null) toggleSidebar();
211
211
  };
@@ -313,6 +313,12 @@ var MOBILE_CSS = `
313
313
  align-items: center;
314
314
  gap: 8px;
315
315
  }
316
+ /* \u5BBF\u4E3B\u6CA1\u6709 aionui explorer \u5217\uFF08\u5B98\u65B9 DSH \u65E0 dsh-web-ui\uFF0Cissue #48\uFF09\u65F6\u9690\u85CF
317
+ \u79FB\u52A8\u7AEF\u300C\u6587\u4EF6\u6D4F\u89C8\u300D\u5165\u53E3\uFF08header \u56FE\u6807 + drawer footer \u9879\uFF09\u2014\u2014\u4E0D\u7136\u70B9\u4E86\u6CA1\u53CD\u5E94\u3002 */
318
+ [data-mobile-nav-explorer="0"] [data-mobile-nav="files"],
319
+ [data-mobile-nav-explorer="0"] [data-mobile-nav="explorer"] {
320
+ display: none !important;
321
+ }
316
322
  [data-mobile-nav="session-log"],
317
323
  [data-mobile-nav="explorer"] {
318
324
  display: inline-flex;
@@ -1190,6 +1196,24 @@ function mobileApply(ctx) {
1190
1196
  document.addEventListener("click", onChevronClick, true);
1191
1197
  return () => document.removeEventListener("click", onChevronClick, true);
1192
1198
  }, "dsh-mobile-nav: aionui explorer close marker");
1199
+ ctx.effect(() => {
1200
+ const narrow = window.matchMedia("(max-width: 1023px)");
1201
+ if (!narrow.matches) return () => {
1202
+ };
1203
+ const frame = () => document.querySelector('[data-mobile-nav="frame"]');
1204
+ const check = () => {
1205
+ const has = document.querySelector("[data-aionui-explorer-col]") !== null;
1206
+ frame()?.setAttribute("data-mobile-nav-explorer", has ? "1" : "0");
1207
+ };
1208
+ check();
1209
+ const timer = window.setTimeout(check, 1500);
1210
+ const observer = new MutationObserver(check);
1211
+ observer.observe(document.body, { childList: true, subtree: true });
1212
+ return () => {
1213
+ window.clearTimeout(timer);
1214
+ observer.disconnect();
1215
+ };
1216
+ }, "dsh-mobile-nav: explorer availability (issue #48)");
1193
1217
  ctx.effect(() => {
1194
1218
  const narrow = window.matchMedia("(max-width: 1023px)");
1195
1219
  if (!narrow.matches) return () => {
@@ -119,8 +119,13 @@ export function MobileNavOverlay({ toggleSidebar, t }: MobileNavOverlayProps) {
119
119
  // menu. Tapping one must NOT count as tapping the row, or the drawer
120
120
  // would close and take the just-opened menu with it.
121
121
  if (target.closest('[class*="sessionRow"] button') !== null) return
122
+ // The drawer footer "Files" action opens the dsh-web-ui explorer sheet,
123
+ // which sits at a LOWER z-index (55) than the open drawer (600) and
124
+ // outside the drawer DOM — leaving the drawer open would let it cover
125
+ // the sheet, and tapping a sheet row would be eaten by the tap-outside
126
+ // close handler. Close the drawer on Files, like navigation.
122
127
  const navigates = target.closest(
123
- 'button[data-dsh-taskboard-entry], button[data-dsh-ssh-entry], [class*="newSession"], [class*="sessionRow"], [class*="searchResultRow"], [class*="searchResultWorkspace"]',
128
+ 'button[data-dsh-taskboard-entry], button[data-dsh-ssh-entry], [class*="newSession"], [class*="sessionRow"], [class*="searchResultRow"], [class*="searchResultWorkspace"], [data-mobile-nav="files"]',
124
129
  )
125
130
  if (navigates !== null) toggleSidebar()
126
131
  }
@@ -107,6 +107,31 @@ export function mobileApply(ctx): void {
107
107
  return () => document.removeEventListener('click', onChevronClick, true)
108
108
  }, 'dsh-mobile-nav: aionui explorer close marker')
109
109
 
110
+ // The mobile "Files" entries (header icon + drawer footer) open the
111
+ // dsh-web-ui **aionui explorer column** — a host-side component that plain
112
+ // DeepSeek Harness does NOT ship (issue #48). Without it the entries are
113
+ // dead buttons (they only toggle a frame attribute; nothing ever renders).
114
+ // Detect whether the host provides the column and mark the frame with
115
+ // `data-mobile-nav-explorer="1|0"` so the stylesheet can hide the entries
116
+ // on hosts without it (dsh-web-ui installs keep the feature).
117
+ ctx.effect(() => {
118
+ const narrow = window.matchMedia('(max-width: 1023px)')
119
+ if (!narrow.matches) return () => {}
120
+ const frame = (): HTMLElement | null => document.querySelector('[data-mobile-nav="frame"]')
121
+ const check = () => {
122
+ const has = document.querySelector('[data-aionui-explorer-col]') !== null
123
+ frame()?.setAttribute('data-mobile-nav-explorer', has ? '1' : '0')
124
+ }
125
+ check()
126
+ const timer = window.setTimeout(check, 1500) // 宿主懒渲染:稍后再查一次
127
+ const observer = new MutationObserver(check)
128
+ observer.observe(document.body, { childList: true, subtree: true })
129
+ return () => {
130
+ window.clearTimeout(timer)
131
+ observer.disconnect()
132
+ }
133
+ }, 'dsh-mobile-nav: explorer availability (issue #48)')
134
+
110
135
  // dsh-web-ui compatibility: the aionui preview column persists its open
111
136
  // tabs in localStorage and restores them on load, which would pop the
112
137
  // preview sheet over the fresh UI after a reload. Gate it like the
@@ -49,6 +49,12 @@ export const MOBILE_CSS = `
49
49
  align-items: center;
50
50
  gap: 8px;
51
51
  }
52
+ /* 宿主没有 aionui explorer 列(官方 DSH 无 dsh-web-ui,issue #48)时隐藏
53
+ 移动端「文件浏览」入口(header 图标 + drawer footer 项)——不然点了没反应。 */
54
+ [data-mobile-nav-explorer="0"] [data-mobile-nav="files"],
55
+ [data-mobile-nav-explorer="0"] [data-mobile-nav="explorer"] {
56
+ display: none !important;
57
+ }
52
58
  [data-mobile-nav="session-log"],
53
59
  [data-mobile-nav="explorer"] {
54
60
  display: inline-flex;
package/lib/proxy.mjs CHANGED
@@ -445,6 +445,9 @@ export function createPocketProxy({ port = 3081, host = '0.0.0.0', upstream = DE
445
445
  if (heartbeat !== false) attachWebSocketHeartbeat(socket, heartbeat ?? {});
446
446
  // 任一端断开都要清理另一端(避免上游残留僵尸连接占用 dsh 连接槽)
447
447
  const teardown = () => { try { proxySocket.destroy(); } catch {} try { socket.destroy(); } catch {} };
448
+ // 上游侧透传 socket 的读错误(如 dsh web 重启/断开时的 ECONNRESET)必须
449
+ // 吞掉并清理对端,否则未处理的 'error' 事件会让整个 dsh web 进程崩溃退出。
450
+ proxySocket.on('error', () => { try { socket.destroy(); } catch {} });
448
451
  proxySocket.on('close', teardown);
449
452
  socket.on('close', teardown);
450
453
  });
package/package.json CHANGED
@@ -76,5 +76,5 @@
76
76
  "access": "public",
77
77
  "registry": "https://registry.npmjs.org/"
78
78
  },
79
- "version": "1.13.0"
79
+ "version": "1.13.2"
80
80
  }