dsh-mobile 0.8.7 → 0.8.8
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 +9 -2
- package/CONTRIBUTORS.md +6 -0
- package/README.en.md +8 -7
- package/README.md +8 -7
- package/apps/mobile/README.md +11 -3
- package/apps/mobile/README.zh-CN.md +11 -3
- package/apps/mobile/release.json +3 -3
- package/docs/WEB_PUSH.en.md +1 -1
- package/docs/WEB_PUSH.md +1 -1
- package/lib/client.js +156 -6
- package/lib/client.js.map +1 -1
- package/lib/index.d.mts +25 -3
- package/lib/index.mjs +288 -103
- package/lib/index.mjs.map +1 -1
- package/lib/mobile-layout.js +312 -3
- package/lib/mobile-layout.js.map +1 -1
- package/package.json +2 -1
- package/packages/question-fixes/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
Notable changes are recorded here. GitHub Releases provide the downloadable packages and generated commit notes.
|
|
4
4
|
|
|
5
|
+
## 0.8.8 - 2026-11-12
|
|
6
|
+
|
|
7
|
+
- Restore Web Push on DSH 0.2.2-rc.2, which removed the `roots`/`isOwnedBy` ownership walk the public push host required: when the registry no longer exposes it, ownership comes from the durable Session header instead — `origin: 'subagent'` plus its `parentSession` ancestry, bounded and stopping at a missing parent — while hosts that still expose the walk keep the previous path and the same root-targeting and child-suppression rules. The packed push lanes and the Web Push guides cover rc.2 again. Thanks @chintoleung for the report in [#407](https://github.com/saya-ch/dsh-mobile/issues/407) and the fix in [#408](https://github.com/saya-ch/dsh-mobile/pull/408).
|
|
8
|
+
- Carry a bounded title and body with task-completion events: the Session display title and the turn's first non-empty assistant line, each bounded by code point (80 and 200) and dropped as a pair when either half looks like a credential, so the App never receives one without the other. The page keeps its own notification unless the installed App reports `enabled`, `connected` and `canPost` at the same time, so an older App, a disconnected service or a blocked notification channel never swallows the alert; questions stay page-only. The App-side halves (announced text, one notification per Session and turn sharing a tag and id, service-owned delivery) ship with the next Android release. Thanks @owenzhao for the measurement and proposal in [#400](https://github.com/saya-ch/dsh-mobile/issues/400).
|
|
9
|
+
- Add an opt-in, off-by-default reading mode to the dedicated Mobile layout ([#194](https://github.com/saya-ch/dsh-mobile/issues/194)) — thanks [@rushpixy](https://github.com/rushpixy) for the report and [@lnuxe](https://github.com/lnuxe) for the implementation in [#384](https://github.com/saya-ch/dsh-mobile/pull/384): scrolling down a long conversation collapses the composer editor so the reading area is unobstructed, while the card keeps the upstream Send/Stop row. Tapping the card, focusing the composer, opening the software keyboard, or scrolling back up about 80px restores it, and it never collapses while a draft exists, a submission is in flight, or a reply is streaming. It samples the conversation scroller `[data-conversation-scroll]` rather than the document, and the Settings → General switch is shared between the settings and layout bundles through the persisted flag, so it applies without a reload.
|
|
10
|
+
- Stop arming a push wake timer while every concurrent send slot is busy: a completion callback re-kicks delivery as slots free, so a saturated queue no longer wakes the device for sends it cannot start. Thanks @chintoleung for [#402](https://github.com/saya-ch/dsh-mobile/pull/402).
|
|
11
|
+
|
|
5
12
|
## 0.8.7 - 2026-11-11
|
|
6
13
|
|
|
7
14
|
- Add an opt-in, off-by-default session prefetch for the dedicated Mobile layout. After a session switch settles, the page issues one bounded, read-only `session/projections` request for the session the user just left (the most likely switch-back target), so a follow-back hits DSH session-query's prepared-session cache warm instead of re-parsing the whole log. Guarded by a per-session cooldown and a bounded timeout; failures are silent and are not retried. It stays disabled until the new "Prefetch the session you just left" switch in Settings → General is turned on; the choice is persisted and applies without a reload. Thanks @eeeggplant for the measurement and proposal in [#291](https://github.com/saya-ch/dsh-mobile/issues/291), and @lnuxe for the implementation in [#383](https://github.com/saya-ch/dsh-mobile/pull/383).
|
|
@@ -12,13 +19,13 @@ Notable changes are recorded here. GitHub Releases provide the downloadable pack
|
|
|
12
19
|
- Declare a plugin icon so DSH shows the DSH Mobile picture in its plugin metadata instead of the default plugin glyph. The shipped `assets/brand/plugin-icon.png` is package-relative, within DSH's 256 KiB icon limit, and read without evaluating plugin code.
|
|
13
20
|
- The page-side halves of the background-reminder status and in-place notification navigation ship in this plugin release; their native halves are prepared as Android 0.7.4 below and released separately. Android 0.7.3 remains the published App until then.
|
|
14
21
|
|
|
15
|
-
## Android 0.7.4 -
|
|
22
|
+
## Android 0.7.4 - 2026-11-11
|
|
16
23
|
|
|
17
24
|
- Cache the revision-addressed mobile page payload on the device, independently of the WebView's own HTTP cache. For a revalidated boot batch the App sends `If-None-Match` and serves the stored body after a 304; a revision-named plugin combo bundle is addressed by its ordered module list and revision and needs no round-trip. A body is stored only after its size and SHA-256 match the fetched bytes, a cache miss or any integrity, epoch or ownership failure falls back to the ordinary request, documents, API responses, credentials and non-revisioned assets never qualify, and clearing site data or removing the paired device clears the store. Thanks @LUgABeEEE for the implementation and the LAN measurements in [#350](https://github.com/saya-ch/dsh-mobile/pull/350).
|
|
18
25
|
- Report the background reminder's real phase in its persistent notification — connecting, connected or not connected — and publish `connected`, `lastEventAt` and a bounded `lastError` stage through `notification.background.get`, so the page and a `DshMobileBg` logcat line can separate a credential-renewal, stream-open or notification-delivery failure without recording tokens or addresses.
|
|
19
26
|
- Give background-service completion notifications a `dsh-task-bg-` tag so their origin is identifiable while page notifications keep `dsh-task-done-`, rebind the service to the newly active paired computer instead of retrying the previous Origin, and withdraw the opt-in when the active record cannot serve it, including an iroh pairing.
|
|
20
27
|
- Open a warm notification tap in the live page instead of reloading it: the App invokes the page's in-place Session hook first and only falls back to route navigation when the page is not ready or cannot handle the route.
|
|
21
|
-
-
|
|
28
|
+
- Released independently of the plugin: plugin 0.8.7 carries the page-side halves and shipped while Android 0.7.3 was the current App, and this App release is published as `android-v0.7.4` with the established signer. The plugin release notes keep the App that was published when they shipped; the download links in this repository point at the verified 0.7.4 APKs.
|
|
22
29
|
|
|
23
30
|
## 0.8.6 - 2026-11-10
|
|
24
31
|
|
package/CONTRIBUTORS.md
CHANGED
|
@@ -4,6 +4,12 @@ GitHub 的 [Contributors 图表](https://github.com/saya-ch/dsh-mobile/graphs/co
|
|
|
4
4
|
|
|
5
5
|
GitHub's Contributors graph counts commits on the default branch; issue reports and PR proposals cannot be manually added to it. This page credits community members by how they contributed. The repository maintainer and automated dependency updates remain visible in GitHub's history.
|
|
6
6
|
|
|
7
|
+
## 下一版本贡献 / Contributions for the next release
|
|
8
|
+
|
|
9
|
+
感谢 [@chintoleung](https://github.com/chintoleung) 在 [#407](https://github.com/saya-ch/dsh-mobile/issues/407) 报告 DSH 0.2.2-rc.2 下 Web Push 不可用,并提供 [#408](https://github.com/saya-ch/dsh-mobile/pull/408) 的兼容修复与 [#402](https://github.com/saya-ch/dsh-mobile/pull/402) 的推送调度修复;感谢 [@owenzhao](https://github.com/owenzhao) 在 [#400](https://github.com/saya-ch/dsh-mobile/issues/400) 提供实测与落点;感谢 [@rushpixy](https://github.com/rushpixy) 在 [#194](https://github.com/saya-ch/dsh-mobile/issues/194) 提出阅读体验问题,[@lnuxe](https://github.com/lnuxe) 提供 [#384](https://github.com/saya-ch/dsh-mobile/pull/384) 的阅读模式实现。
|
|
10
|
+
|
|
11
|
+
Thanks [@chintoleung](https://github.com/chintoleung) for reporting Web Push being unavailable on DSH 0.2.2-rc.2 in [#407](https://github.com/saya-ch/dsh-mobile/issues/407), and for the compatibility fix in [#408](https://github.com/saya-ch/dsh-mobile/pull/408) and the push-scheduling fix in [#402](https://github.com/saya-ch/dsh-mobile/pull/402); thanks [@owenzhao](https://github.com/owenzhao) for the measurement and points in [#400](https://github.com/saya-ch/dsh-mobile/issues/400); thanks [@rushpixy](https://github.com/rushpixy) for the reading-experience report in [#194](https://github.com/saya-ch/dsh-mobile/issues/194), and [@lnuxe](https://github.com/lnuxe) for the reading-mode implementation in [#384](https://github.com/saya-ch/dsh-mobile/pull/384).
|
|
12
|
+
|
|
7
13
|
## 0.8.7 贡献 / Contributions in 0.8.7
|
|
8
14
|
|
|
9
15
|
感谢 [@eeeggplant](https://github.com/eeeggplant) 在 [#291](https://github.com/saya-ch/dsh-mobile/issues/291) 提供会话切换重复加载的测量与预取提案,[@lnuxe](https://github.com/lnuxe) 通过 [#383](https://github.com/saya-ch/dsh-mobile/pull/383) 实现可选的会话预取;原作者提交保留,维护者补充独立开关、跨 bundle 的偏好同步和网关参数契约。感谢 [@owenzhao](https://github.com/owenzhao) 在 [#390](https://github.com/saya-ch/dsh-mobile/issues/390) 报告后台提醒不投递并持续提供设备侧排查信息,在 [#394](https://github.com/saya-ch/dsh-mobile/issues/394) 指出点击任务通知会整页重载;两个问题仍在确认中,本版只提供诊断与页面侧准备,不表示已修复。感谢 [@LUgABeEEE](https://github.com/LUgABeEEE) 在 [#350](https://github.com/saya-ch/dsh-mobile/pull/350) 提供网关到期连接正常关闭、认证失败分类,以及 App 端按修订号缓存页面资源的实现与测量。感谢 [@pakeh2866](https://github.com/pakeh2866) 在 [#389](https://github.com/saya-ch/dsh-mobile/issues/389) 报告文件预览后无法返回,促成第三方全屏宿主的返回键契约文档;宿主自身的修复不在本仓库。
|
package/README.en.md
CHANGED
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
<a href="#compatibility">Compatibility</a> ·
|
|
29
29
|
<a href="docs/README.md">Documentation</a> ·
|
|
30
30
|
<a href="#contributors">Contributors</a> ·
|
|
31
|
-
<a href="https://github.com/saya-ch/dsh-mobile/releases/tag/v0.8.
|
|
31
|
+
<a href="https://github.com/saya-ch/dsh-mobile/releases/tag/v0.8.8">Release notes</a> ·
|
|
32
32
|
<a href="README.md">简体中文</a>
|
|
33
33
|
</p>
|
|
34
34
|
|
|
@@ -36,14 +36,14 @@
|
|
|
36
36
|
>
|
|
37
37
|
> **Using an iPhone or iPad?** Use Safari or [add DSH Mobile to your Home Screen as a web app (PWA)](<#iphone-and-ipad-home-screen-web-app-pwa>)—no native iOS download needed.
|
|
38
38
|
>
|
|
39
|
-
> **Current stable release: 0.8.
|
|
39
|
+
> **Current stable release: 0.8.8**. Restore Web Push on DSH 0.2.2-rc.2 after its AgentRegistry ownership change, carry the Session title and a bounded summary with task-completion events and yield page notifications only when the App reports it can post, add an opt-in reading mode, and stop arming push wake timers while the send queue is at capacity. Plugin and Android App releases remain independent; Android App 0.7.4 remains the published app. [Plugin release notes and downloads](https://github.com/saya-ch/dsh-mobile/releases/tag/v0.8.8) · [Full changelog](CHANGELOG.md).
|
|
40
40
|
>
|
|
41
41
|
> **Version policy**: from plugin **0.6.2** the Android app and plugin ship as independent releases (the app was still 0.6.1 then), and the app has used its own version numbers since app **0.6.2**. Their numbers do not need to match, and a plugin update does not imply an app update. [Compatibility notes](#compatibility).
|
|
42
42
|
|
|
43
43
|
<p align="center">
|
|
44
|
-
<a href="https://github.com/saya-ch/dsh-mobile/releases/download/android-v0.7.
|
|
45
|
-
<a href="https://github.com/saya-ch/dsh-mobile/releases/download/android-v0.7.
|
|
46
|
-
<sub><a href="https://github.com/saya-ch/dsh-mobile/releases/download/android-v0.7.
|
|
44
|
+
<a href="https://github.com/saya-ch/dsh-mobile/releases/download/android-v0.7.4/dsh-mobile-android-v0.7.4.apk"><img src="https://raw.githubusercontent.com/saya-ch/dsh-mobile/main/assets/brand/app-icon-rounded.svg" alt="DSH Mobile Android app icon" width="72" height="72"></a><br>
|
|
45
|
+
<a href="https://github.com/saya-ch/dsh-mobile/releases/download/android-v0.7.4/dsh-mobile-android-v0.7.4.apk"><strong>Download Android app 0.7.4 (compatibility)</strong></a><br>
|
|
46
|
+
<sub><a href="https://github.com/saya-ch/dsh-mobile/releases/download/android-v0.7.4/dsh-mobile-android-v0.7.4-iroh.apk">64-bit full APK (experimental iroh)</a> · <a href="https://github.com/saya-ch/dsh-mobile/releases/tag/android-v0.7.4">Release notes and checksums</a></sub>
|
|
47
47
|
</p>
|
|
48
48
|
|
|
49
49
|
DSH Mobile is a DeepSeek Harness plugin that lets a mobile browser or the Android app connect over a protected LAN or an optional Tailscale Funnel, cpolar, cloudflared, self-hosted FRP, or own reverse-proxy remote path. The full Android App also supports experimental iroh encrypted direct connections with relay fallback. The phone reaches the computer's sessions, Workspaces, messages and tools. The computer manages LAN and remote switches and pairing authorizations separately; the Android app lists paired computers together. The plugin does not modify DeepSeek Harness source.
|
|
@@ -395,7 +395,7 @@ flowchart LR
|
|
|
395
395
|
DSH -->|"same workspace, sessions and event stream"| Phone
|
|
396
396
|
```
|
|
397
397
|
|
|
398
|
-
Three layers: the Host face for discovery, pairing, HTTPS, loopback proxying, and extension registration; the Client face for the dedicated mobile layout and extension SDK; and the Android app for a narrow native bridge. The bridge uses `androidx.webkit` WebMessage, which verifies the exact top-level origin and main frame on every bounded message, and never uses `addJavascriptInterface`. The plugin does not rewrite source or webpage files in the DeepSeek Harness installation. Its mobile gateway serves the startup compatibility layer and dedicated layout per request; installation and removal use the plugin mechanism. The upstream DSH Web port defaults to 3080 and is configurable through `dsh web --port`.
|
|
398
|
+
Three layers: the Host face for discovery, pairing, HTTPS, loopback proxying, and extension registration; the Client face for the dedicated mobile layout and extension SDK; and the Android app for a narrow native bridge. The bridge uses `androidx.webkit` WebMessage, which verifies the exact top-level origin and main frame on every bounded message, and never uses `addJavascriptInterface`. The plugin does not rewrite source or webpage files in the DeepSeek Harness installation. Its mobile gateway serves the startup compatibility layer and dedicated layout per request; installation and removal use the plugin mechanism. The upstream DSH Web port defaults to 3080 and is configurable through `dsh web --port`. Reading mode (opt-in, off by default) collapses the composer editor while scrolling down a long conversation: the card and its Send/Stop row stay, and tapping the card, focusing the composer, opening the software keyboard, or scrolling back up about 80px restores it. It never collapses while a draft exists, a submission is in flight, or a reply is streaming. Enable it from Settings → General.
|
|
399
399
|
|
|
400
400
|
Switching sessions tears down the client Session instance and its live stream. The dedicated layout ships an opt-in, off-by-default session prefetch ([#291](https://github.com/saya-ch/dsh-mobile/issues/291)): after a switch settles, it issues one bounded, read-only `session/projections` request for the session the user just left, so a follow-back hits DSH session-query's prepared cache instead of re-parsing the whole log. Guarded by a per-session cooldown and a bounded timeout, failures are silent and never disturb normal requests or live streams; the behavior stays disabled until the "Prefetch the session you just left" switch in Settings → General is turned on (off by default; the choice is persisted).
|
|
401
401
|
|
|
@@ -449,7 +449,8 @@ On macOS, local network, cloudflared, self-hosted FRP and the own reverse proxy
|
|
|
449
449
|
|
|
450
450
|
| DSH Mobile plugin | Verified DeepSeek Harness version |
|
|
451
451
|
| --- | --- |
|
|
452
|
-
| `0.8.
|
|
452
|
+
| `0.8.8` | On the `0.2.2-rc.2` target, restore Web Push after the AgentRegistry ownership change, carry the Session title and a bounded summary with task-completion events and yield page notifications only when the App reports it can post, add an opt-in reading mode, and stop arming push wake timers while the send queue is at capacity. The Android App is 0.7.4 |
|
|
453
|
+
| `0.8.7` | On the `0.2.2-rc.1` packed target, add an opt-in session prefetch, close an expiring Gateway WebSocket normally with clearer authentication-failure categories, and prepare the background-reminder status and in-place notification open for Android 0.7.4; also update the plugin icon and iroh troubleshooting notes. The Android App is 0.7.4, with the device-side page cache and real background-reminder status |
|
|
453
454
|
| `0.8.6` | On the `0.2.2-rc.1` packed target, forward paired third-party plugin `x-dsh-*` headers, keep the push subscription after a Disable, expose the trusted Host push-text service, and fix Windows firewall verification, iroh directory diagnostics and the LAN diagnostic scope. The Android App is 0.7.3 with opt-in background reminders |
|
|
454
455
|
| `0.8.5` | Maintenance and performance update on the `0.2.2-rc.1` packed-DSH target: catalog cache, Desktop update targeting, CGNAT LAN discovery, firewall protection and a macOS permission notice. The Android App remains 0.7.2 |
|
|
455
456
|
| `0.8.4` | Plugin maintenance update. The packed DSH test target remains `0.2.2-rc.1`, alongside existing compatibility checks. The upstream rc.1 npm CLI requires Node.js 24.9 or later |
|
package/README.md
CHANGED
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
<a href="#兼容性">兼容性</a> ·
|
|
29
29
|
<a href="docs/README.md">文档</a> ·
|
|
30
30
|
<a href="#贡献者">贡献者</a> ·
|
|
31
|
-
<a href="https://github.com/saya-ch/dsh-mobile/releases/tag/v0.8.
|
|
31
|
+
<a href="https://github.com/saya-ch/dsh-mobile/releases/tag/v0.8.8">更新记录</a> ·
|
|
32
32
|
<a href="README.en.md">English</a>
|
|
33
33
|
</p>
|
|
34
34
|
|
|
@@ -36,14 +36,14 @@
|
|
|
36
36
|
>
|
|
37
37
|
> **使用 iPhone 或 iPad?** 可以用 Safari 访问,或[添加到主屏幕作为 Web App(PWA)使用](<#iphone-与-ipad主屏幕-web-apppwa>),无需下载原生 iOS App。
|
|
38
38
|
>
|
|
39
|
-
> **当前正式版本:0.8.
|
|
39
|
+
> **当前正式版本:0.8.8**。恢复 DSH 0.2.2-rc.2 下的 Web 推送(AgentRegistry ownership 变更),任务完成事件携带会话标题与有界摘要、并在 App 报告可投递时让出页面通知,新增默认关闭的阅读模式,推送调度不再在容量满时唤醒定时器。插件与 Android App 独立更新;Android App 0.7.4 仍为当前正式版。[插件更新与下载](https://github.com/saya-ch/dsh-mobile/releases/tag/v0.8.8) · [完整更新记录](CHANGELOG.md)。
|
|
40
40
|
>
|
|
41
41
|
> **版本说明**:自插件 **0.6.2** 起,Android App 与插件改为独立发布(当时 App 仍为 0.6.1);App 自 0.6.2 起使用独立编号,版本号无需一致;插件升级不代表 App 需要同步升级。[兼容说明](#兼容性)。
|
|
42
42
|
|
|
43
43
|
<p align="center">
|
|
44
|
-
<a href="https://github.com/saya-ch/dsh-mobile/releases/download/android-v0.7.
|
|
45
|
-
<a href="https://github.com/saya-ch/dsh-mobile/releases/download/android-v0.7.
|
|
46
|
-
<sub><a href="https://github.com/saya-ch/dsh-mobile/releases/download/android-v0.7.
|
|
44
|
+
<a href="https://github.com/saya-ch/dsh-mobile/releases/download/android-v0.7.4/dsh-mobile-android-v0.7.4.apk"><img src="https://raw.githubusercontent.com/saya-ch/dsh-mobile/main/assets/brand/app-icon-rounded.svg" alt="DSH Mobile 安卓应用图标" width="72" height="72"></a><br>
|
|
45
|
+
<a href="https://github.com/saya-ch/dsh-mobile/releases/download/android-v0.7.4/dsh-mobile-android-v0.7.4.apk"><strong>下载 Android App 0.7.4(兼容版)</strong></a><br>
|
|
46
|
+
<sub><a href="https://github.com/saya-ch/dsh-mobile/releases/download/android-v0.7.4/dsh-mobile-android-v0.7.4-iroh.apk">64 位完整版(含实验性 iroh)</a> · <a href="https://github.com/saya-ch/dsh-mobile/releases/tag/android-v0.7.4">版本说明与校验文件</a></sub>
|
|
47
47
|
</p>
|
|
48
48
|
|
|
49
49
|
DSH Mobile 是一个 DeepSeek Harness 插件,让手机浏览器或 Android App 通过局域网,或可选的 Tailscale Funnel、cpolar、cloudflared、自建 FRP、自有反向代理远程通道连接电脑;Android 完整版还支持实验性 iroh 加密直连与中继。手机可以继续使用电脑上的会话、工作区、消息和工具。电脑端分别启停局域网与远程访问、分别管理配对授权;Android App 统一显示已配对电脑。插件不修改 DeepSeek Harness 源码。
|
|
@@ -395,7 +395,7 @@ flowchart LR
|
|
|
395
395
|
DSH -->|"同一工作区、会话和事件流"| Phone
|
|
396
396
|
```
|
|
397
397
|
|
|
398
|
-
插件包含三层:Host face 负责发现、配对、HTTPS、回环代理和扩展注册表;Client face 提供独立的移动布局与扩展 SDK;Android App 提供受限的原生 Bridge。Bridge 使用 `androidx.webkit` WebMessage,每条消息都校验精确顶层 Origin 和主 Frame,并限制消息大小;不使用 `addJavascriptInterface`。插件不改写 DeepSeek Harness 安装目录中的源码或网页文件;移动网关按请求提供启动兼容层与专用布局,安装和卸载通过插件机制完成。DSH Web 上游端口默认为 3080,可通过 `dsh web --port`
|
|
398
|
+
插件包含三层:Host face 负责发现、配对、HTTPS、回环代理和扩展注册表;Client face 提供独立的移动布局与扩展 SDK;Android App 提供受限的原生 Bridge。Bridge 使用 `androidx.webkit` WebMessage,每条消息都校验精确顶层 Origin 和主 Frame,并限制消息大小;不使用 `addJavascriptInterface`。插件不改写 DeepSeek Harness 安装目录中的源码或网页文件;移动网关按请求提供启动兼容层与专用布局,安装和卸载通过插件机制完成。DSH Web 上游端口默认为 3080,可通过 `dsh web --port` 修改。阅读模式(可选,默认关闭):向下滚动阅读长会话时收起输入栏的编辑区,输入框卡片与发送/停止按钮保留;点按卡片、聚焦输入框、打开软键盘或向上回滚约 80px 即恢复。有草稿、提交中或回复生成中时不会收起。可在「设置 → 通用」中开启。
|
|
399
399
|
|
|
400
400
|
会话切换后会重建客户端 Session 与实时流。插件提供默认关闭的会话预取(对应 [#291](https://github.com/saya-ch/dsh-mobile/issues/291)):切换稳定后,仅对刚离开的会话发起一次有界、只读的 `session/projections` 预热,让切回命中 DSH session-query 的已准备缓存,避免重新解析整个会话日志。该行为受每会话冷却与超时约束,失败静默,且绝不打扰正常请求或实时流;在「设置 → 通用」中的「预取刚离开的会话」开关显式开启后才生效(默认关闭,选择会持久化)。
|
|
401
401
|
|
|
@@ -449,7 +449,8 @@ macOS 上局域网、cloudflared、自建 FRP 与自有反向代理可用;Funn
|
|
|
449
449
|
|
|
450
450
|
| DSH Mobile 插件 | 已验证的 DeepSeek Harness 版本与范围 |
|
|
451
451
|
| --- | --- |
|
|
452
|
-
| `0.8.
|
|
452
|
+
| `0.8.8` | 在 `0.2.2-rc.2` 上恢复 Web 推送(AgentRegistry ownership 变更),任务完成事件携带会话标题与有界摘要并在 App 报告可投递时让出页面通知,新增默认关闭的阅读模式,推送调度不再在容量满时唤醒定时器。Android App 为 0.7.4 |
|
|
453
|
+
| `0.8.7` | 在 `0.2.2-rc.1` 打包目标上加入默认关闭的会话预取、网关到期连接正常关闭与认证失败分类,并为 Android 0.7.4 准备后台提醒真实状态与通知就地打开;同时更新插件图标与 iroh 排障文档。Android App 为 0.7.4,新增设备端页面缓存与后台提醒真实状态 |
|
|
453
454
|
| `0.8.6` | 在 `0.2.2-rc.1` 打包目标上透传已配对第三方插件的 `x-dsh-*` 请求头、修正网页推送停用语义、向受信 Host 插件开放推送文案接口,并修复 Windows 防火墙校验、iroh 目录诊断与局域网诊断口径。Android App 为 0.7.3,新增可选后台提醒 |
|
|
454
455
|
| `0.8.5` | 插件维护与性能更新,继续使用 `0.2.2-rc.1` 的 DSH 打包验证目标;新增目录缓存、Desktop 更新目标修复、CGNAT 局域网识别、防火墙保护与 macOS 权限提示。Android App 仍为 0.7.2 |
|
|
455
456
|
| `0.8.4` | 插件维护更新,继续使用 `0.2.2-rc.1` 的 DSH 打包验证目标,并保留既有版本检查。使用 rc.1 的 npm CLI 时,按上游要求使用 Node.js 24.9 或更新版本 |
|
package/apps/mobile/README.md
CHANGED
|
@@ -4,14 +4,14 @@
|
|
|
4
4
|
|
|
5
5
|
DeepSeek Harness is the display name of this community-maintained Android WebView app. It opens the plugin's authenticated HTTPS page rather than bundling another DSH frontend. The app and mobile browsers receive the same DSH features and computer-side mobile customizations.
|
|
6
6
|
|
|
7
|
-
The current stable app is **0.7.
|
|
7
|
+
The current stable app is **0.7.4** (build 89). The npm plugin and Android App have independent versions: a plugin update does not automatically require another APK. See the [0.7.4 changes](#074-update).
|
|
8
8
|
|
|
9
9
|
This APK supports Android 10 or later. iPhone and iPad use the [Safari Home Screen web app and optional Web Push](../../docs/WEB_PUSH.en.md), not this Android package; no native iOS app is distributed.
|
|
10
10
|
|
|
11
11
|
## Install and pair
|
|
12
12
|
|
|
13
13
|
1. Install and enable the plugin using the [project quick start](../../README.en.md#quick-start). Complete LAN setup from the command line or the computer's **Mobile Access** panel.
|
|
14
|
-
2. Install a signed Android APK from [GitHub Releases](https://github.com/saya-ch/dsh-mobile/releases/tag/android-v0.7.
|
|
14
|
+
2. Install a signed Android APK from [GitHub Releases](https://github.com/saya-ch/dsh-mobile/releases/tag/android-v0.7.4). Choose the compatibility `.apk` for existing HTTPS channels, or the full `-iroh.apk` for experimental iroh on arm64-v8a/x86_64. Both have the same package ID and official signer; install only one and upgrade in place to retain pairings. A differently signed Debug APK cannot replace it; do not uninstall or clear data just to bypass that mismatch.
|
|
15
15
|
3. On the computer, generate a pairing key/link under **Mobile Access → Local network**, or configure a remote provider and generate its remote pairing QR code. In the app, choose the matching connection mode and scan or paste the link.
|
|
16
16
|
4. Name the computer. The app stores each LAN or remote pairing as its own record. The phone does not need a cpolar, Cloudflare or Tailscale app.
|
|
17
17
|
|
|
@@ -36,6 +36,10 @@ Page zoom changes the whole page and is separate from per-address text preferenc
|
|
|
36
36
|
|
|
37
37
|
Deleting a local record removes its saved token and pinned CA, not the computer's authorization or every short-lived WebView cookie. Use **Mobile Access** on the computer to revoke access immediately. A full Android clear-app-data reset removes all pairings and web data and is not a normal reconnection step.
|
|
38
38
|
|
|
39
|
+
## 0.7.4 update
|
|
40
|
+
|
|
41
|
+
Cache the revision-addressed mobile page payload on the device, independently of the WebView's HTTP cache: a revalidated boot batch can revalidate with `If-None-Match` and serve the stored body after a 304, and a revision-named plugin combo bundle is addressed by its ordered module list and revision. A body is stored only after its size and SHA-256 match, and a cache miss or any integrity, epoch or ownership failure falls back to the ordinary request; documents, API responses, credentials and non-revisioned assets never qualify, and clearing site data or removing the paired device clears the store. The persistent background-reminder notification now reports the real phase — connecting, connected or not connected — and publishes `connected`, `lastEventAt` and a bounded `lastError` stage through `notification.background.get` and a `DshMobileBg` logcat line, background-service completion notifications carry their own `dsh-task-bg-` tag, the service rebinds to the newly active paired computer instead of retrying the previous Origin, and the opt-in is withdrawn when the active record cannot serve it, including an iroh pairing. A tapped task notification now opens the notified Session inside the live page through the page's in-place hook, falling back to route navigation when the page is not ready or cannot handle the route. Both APK variants retain the established signer and existing pairings; the page-side halves need plugin 0.8.7 or later.
|
|
42
|
+
|
|
39
43
|
## 0.7.3 update
|
|
40
44
|
|
|
41
45
|
Open the matching conversation after a task-notification tap by carrying a bounded same-origin `dsh-session` route through the native notification, and add an explicit, opt-in `dataSync` foreground service that keeps the current paired-computer event stream alive for completion alerts after Android suspends the page. The service shows a persistent notification, refuses iroh pairings, does not replay events across disconnection, and remains subject to Android foreground-service time limits and battery policy. Both APK variants retain the established signer and existing pairings; the background stream needs plugin 0.8.6 or later.
|
|
@@ -138,6 +142,8 @@ DSH records audio through `getUserMedia` and sends it to the configured computer
|
|
|
138
142
|
|
|
139
143
|
Task reminders use Host completion events and pending-input cards. While the page is alive it sends notifications through the native bridge; **Settings → General → Task notifications** can explicitly enable **background reminders**, a foreground service that can be turned off in Settings and keeps the active paired-computer connection alive after Android suspends the page. The service remains subject to foreground-service time limits, battery policy and connectivity, displays a persistent notification, does not yet support iroh pairings, and does not replay events across disconnection. A tap may carry a bounded same-origin `dsh-session` route, which opens the matching conversation after a cold or warm start; any other target falls back to the app root. Cloudflare Quick Tunnels do not support the event stream; see [channel limits](../../docs/CLOUDFLARE_TUNNEL.en.md#quick-tunnel-feature-limits).
|
|
140
144
|
|
|
145
|
+
Android's own battery management can still stop delivery, and the persistent notification does not prove the stream is alive: it stays while the service runs, whatever the connection is doing. Settings → General reports the service's real phase — connected, not connected, or the bounded failure stage it last recorded — and a `DshMobileBg` logcat line records the same transitions for `adb logcat` diagnosis. On an OEM build that freezes a background process (for example Xiaomi HyperOS's app battery saver), no App-side change keeps the stream open: allow battery use without restriction for this App, or accept that reminders arrive when the App is next foregrounded. iOS is unaffected because it uses the Web Push path instead.
|
|
146
|
+
|
|
141
147
|
The authenticated page may use the `dshMobile` bridge for `files.pick`, `camera.capture`, `share`, clipboard read/write, task notifications/settings, background reminders (`notification.background.get`/`set`), computer switching and page scaling. The separate extension `files.pick` returns one file, follows its requested MIME types and limits bridge-returned file bytes to 8 MiB. This is not the standard DSH attachment selector's limit.
|
|
142
148
|
|
|
143
149
|
Bridge file/camera operations are serialized with a five-minute deadline. Cancellation, document/session changes or WebView teardown reject stale results and clean up owned resources. Browser media selection waits for an actual result, explicit cancellation, abort or deadline; returning focus is not itself cancellation. Unsupported capabilities return `unsupported`.
|
|
@@ -182,10 +188,12 @@ Native builds also include the JVM TLS verifier matching `native/Cargo.lock`, fr
|
|
|
182
188
|
|
|
183
189
|
Current-source Debug builds use package `io.github.sayach.dshmobile.debug` with a `-debug` version suffix. They install beside the official App with separate storage and pairings. They do not upgrade or migrate an older unsuffixed Debug installation; preserve that app and its data rather than uninstalling or clearing it to switch packages. Release builds keep `io.github.sayach.dshmobile` and the established signer.
|
|
184
190
|
|
|
185
|
-
The verified stable release is [Android 0.7.
|
|
191
|
+
The verified stable release is [Android 0.7.4](https://github.com/saya-ch/dsh-mobile/releases/tag/android-v0.7.4), build 89. The compatibility and full APKs each have a matching `.sha256` asset. Both public downloads retain the established release signer and package `io.github.sayach.dshmobile`. Earlier release history belongs in [CHANGELOG](../../CHANGELOG.md).
|
|
186
192
|
|
|
187
193
|
## Acceptance and known limits
|
|
188
194
|
|
|
195
|
+
- Both public App 0.7.4 APKs passed checksum, manifest, established-signer and size checks; the compatibility APK contains no native libraries and the full APK carries only the two iroh transport ABIs. There is no new physical-device or public-network run for 0.7.4: the device cache, background-reminder status and in-place notification tap paths are covered by the Android unit tests and CI release builds only, and the Debug-device evidence below belongs to 0.7.3.
|
|
196
|
+
|
|
189
197
|
- A current-source Debug build (0.7.3/build 88) paired over a cloudflared remote origin received a real completed-turn notification while the app was backgrounded and the screen locked, and tapping it opened the `dsh-session` route to the matching conversation; disabling the opt-in stopped the service and removed the notification. This was the Debug signer on one test device over a public tunnel; it does not establish the published APK, iroh, or every network and OEM behavior.
|
|
190
198
|
|
|
191
199
|
- Both public App 0.7.1 APKs passed checksum, manifest, established-signer, notice and alignment checks. The full APK additionally passed ABI and retained JVM TLS/JNI entry checks; compatibility contains no native libraries. There is no new physical-device or public-relay run for 0.7.1. Earlier arm64 development traffic remains historical evidence, not proof that a current connection uses the relay.
|
|
@@ -4,14 +4,14 @@
|
|
|
4
4
|
|
|
5
5
|
DeepSeek Harness 是这个社区维护的 Android WebView App 的显示名称。它直接打开插件提供的已认证 HTTPS 页面,不打包另一套 DSH 前端;App 与手机浏览器获得相同的 DSH 功能,以及电脑端配置的移动页面定制。
|
|
6
6
|
|
|
7
|
-
当前正式 App 为 **0.7.
|
|
7
|
+
当前正式 App 为 **0.7.4**;构建号为 89。npm 插件与 Android App 独立编号、独立更新;插件升级不代表需要重新安装 APK。此次改进见 [0.7.4 更新](#074-更新)。
|
|
8
8
|
|
|
9
9
|
此 APK 支持 Android 10 及以上系统。iPhone 和 iPad 使用 [Safari 主屏幕 Web App 与可选网页推送](../../docs/WEB_PUSH.md),不安装 Android 包;本项目不分发原生 iOS App。
|
|
10
10
|
|
|
11
11
|
## 安装与配对
|
|
12
12
|
|
|
13
13
|
1. 按[项目快速开始](../../README.md#快速开始)安装并启用插件。局域网配置可通过命令行或电脑端的 **移动访问** 面板完成。
|
|
14
|
-
2. 从 [GitHub Releases](https://github.com/saya-ch/dsh-mobile/releases/tag/android-v0.7.
|
|
14
|
+
2. 从 [GitHub Releases](https://github.com/saya-ch/dsh-mobile/releases/tag/android-v0.7.4) 安装已签名 APK。原有 HTTPS 通道可选兼容版 `.apk`;arm64-v8a/x86_64 设备使用实验性 iroh 时选完整版 `-iroh.apk`。两包使用相同包名和正式签名,选一个原位升级即可保留配对;不同签名的 Debug 包不能直接覆盖正式版,不要为绕过签名不匹配而卸载或清除数据。
|
|
15
15
|
3. 在电脑 **移动访问 → 局域网访问** 中生成密钥/配对链接,或配置远程通道并生成远程配对二维码。在 App 选择对应方式,扫码或粘贴链接。
|
|
16
16
|
4. 为电脑命名。每条局域网或远程配对单独保存;手机无需安装 cpolar、Cloudflare 或 Tailscale App。
|
|
17
17
|
|
|
@@ -36,6 +36,10 @@ DeepSeek Harness 是这个社区维护的 Android WebView App 的显示名称。
|
|
|
36
36
|
|
|
37
37
|
删除本机记录会移除保存的设备令牌和固定 CA,不等于撤销电脑授权或清除所有 WebView 短期 Cookie。需要立即终止访问时,请在电脑的 **移动访问** 中撤销设备。Android“清除应用数据”会删除全部配对和网页数据,不是普通重连的处理步骤。
|
|
38
38
|
|
|
39
|
+
## 0.7.4 更新
|
|
40
|
+
|
|
41
|
+
把按修订号寻址的移动端页面载荷缓存在设备上,独立于 WebView 自身的 HTTP 缓存:经重新校验的启动批次可用 `If-None-Match` 校验,并在 304 后直接返回已存正文;按修订号命名的插件组合包按有序模块列表与修订号寻址。只有大小与 SHA-256 都与取回字节一致后才写入缓存;缓存未命中或任何完整性、epoch、归属校验失败都会回退到普通请求;文档、API 响应、凭据与未按修订号命名的资源不参与缓存;清除站点数据或移除已配对设备会清空该存储。后台提醒的常驻通知现在显示真实阶段(连接中、已连接、未连接),并通过 `notification.background.get` 与 `DshMobileBg` logcat 行发布 `connected`、`lastEventAt` 和有界的 `lastError` 阶段;后台服务的完成通知使用独立的 `dsh-task-bg-` 标签,服务会重新绑定到新激活的已配对电脑而不是继续重试原 Origin,活动记录无法提供服务(包括 iroh 配对)时撤回该选项。点击任务通知现在先由页面内的就地钩子在活动页面中打开对应会话,页面未就绪或无法处理该路由时回退到路由跳转。两种 APK 均沿用正式签名,可原位升级并保留已有配对;页面侧的一半需要插件 0.8.7 或更高版本。
|
|
42
|
+
|
|
39
43
|
## 0.7.3 更新
|
|
40
44
|
|
|
41
45
|
任务通知点击可携带经校验的同源 `dsh-session` 路由,冷启动或热启动后打开对应会话;并新增可显式开启的 `dataSync` 前台服务,在 Android 挂起页面后继续接收当前已配对电脑的完成事件。该服务显示常驻通知,不支持 iroh 配对,断连期间不补发,仍受 Android 前台服务时限与电池策略限制。两种 APK 均沿用正式签名,可原位升级并保留已有配对;后台事件流需要插件 0.8.6 或更高版本。
|
|
@@ -138,6 +142,8 @@ DSH 通过 `getUserMedia` 录音并交给电脑已配置的语音提供方,不
|
|
|
138
142
|
|
|
139
143
|
任务提醒依赖 Host 完成事件和待确认卡片。页面仍存活时由网页走原生 Bridge 发通知;在**设置 → 通用设置 → 任务通知**下可显式开启**后台提醒**,由可关闭的前台服务保持与当前已配对电脑的连接,页面被系统挂起后仍可收到完成事件。该服务会显示常驻通知,受 Android 前台服务时限、电池策略和网络影响;iroh 配对暂不支持,断连期间不补发。点击通知可携带有界的同源 `dsh-session` 路由,冷启动或热启动后打开对应会话;其他目标一律回退到 App 根入口。Cloudflare 快速隧道不支持事件流,详见[通道限制](../../docs/CLOUDFLARE_TUNNEL.md#快速隧道的功能限制)。
|
|
140
144
|
|
|
145
|
+
Android 自身的电池管理仍可能中断投递,而且常驻通知**不代表**事件流是活的:服务一在跑它就在,与连接状态无关。设置 → 通用设置会显示服务的真实阶段(已连接、未连接或最后一次记录到的有界失败阶段),并写入 `DshMobileBg` logcat 行,便于用 `adb logcat` 定位。在会冻结后台进程的厂商系统上(例如小米 HyperOS 的应用电池省电策略),App 侧无法维持这条流:请为该 App 关闭电池限制,或接受提醒在下一次回到前台时才出现。iOS 走网页推送通道,不受此限。
|
|
146
|
+
|
|
141
147
|
认证页面可通过 `dshMobile` Bridge 调用 `files.pick`、`camera.capture`、分享、剪贴板读写、任务通知/设置、后台提醒(`notification.background.get`/`set`)、切换电脑及页面缩放。扩展单独调用的 `files.pick` 返回一个文件,遵循声明的 MIME 类型,通过 Bridge 返回的文件最多 8 MiB;这不是 DSH 标准附件选择器的大小限制。
|
|
142
148
|
|
|
143
149
|
Bridge 文件/相机操作串行执行,最长等待五分钟。取消、文档/会话变化或 WebView 销毁会拒绝旧结果并清理资源。浏览器媒体选择等待实际结果、明确取消、中止或超时,恢复焦点不直接视为取消;不支持的能力返回 `unsupported`。
|
|
@@ -182,10 +188,12 @@ Set-Location apps/mobile/android
|
|
|
182
188
|
|
|
183
189
|
当前源码的 Debug 构建使用包名 `io.github.sayach.dshmobile.debug` 与 `-debug` 版本后缀,可与正式 App 并存,存储和配对彼此独立。它不会升级或迁移旧版无后缀 Debug 应用;切换包名时应保留旧应用和数据,不要为此卸载或清除。Release 构建继续使用 `io.github.sayach.dshmobile` 与既定正式签名。
|
|
184
190
|
|
|
185
|
-
已核验的正式版本为 [Android 0.7.
|
|
191
|
+
已核验的正式版本为 [Android 0.7.4](https://github.com/saya-ch/dsh-mobile/releases/tag/android-v0.7.4),构建 89。兼容版与完整版分别附有对应的 `.sha256` 校验文件;两个公开下载均沿用正式发布签名及包名 `io.github.sayach.dshmobile`。旧版本历史集中在 [CHANGELOG](../../CHANGELOG.md)。
|
|
186
192
|
|
|
187
193
|
## 验收与已知限制
|
|
188
194
|
|
|
195
|
+
- App 0.7.4 的两份公开 APK 均通过校验值、Manifest、既定正式签名与体积检查;兼容版不含原生库,完整版只携带两个 iroh 传输 ABI。本版未新增真机或公网回归:设备端缓存、后台提醒状态与通知就地打开仅由 Android 单元测试与 CI 发布构建覆盖,下方的真机 Debug 证据属于 0.7.3。
|
|
196
|
+
|
|
189
197
|
- 当前源码 Debug 构建(0.7.3/构建 88)经 cloudflared 远程入口配对后,在 App 退到后台且锁屏时收到了真实回合完成通知;点击后打开对应会话的 `dsh-session` 路由,关闭该选项后服务停止并移除通知。这是单台设备、公网隧道和 Debug 签名的验证,不代表正式 APK、iroh 或所有网络与厂商行为。
|
|
190
198
|
|
|
191
199
|
- App 0.7.1 的两份公开 APK 均通过校验值、Manifest、既定正式签名、许可文本和对齐检查;完整版另检查两个 ABI 及保留的 JVM TLS/JNI 入口,兼容版确认没有原生库。本版未新增真机或公网中继回归。较早 arm64 开发构建的链路记录仍属历史证据,不能据此断言当前连接的数据已经过中继。
|
package/apps/mobile/release.json
CHANGED
package/docs/WEB_PUSH.en.md
CHANGED
|
@@ -14,7 +14,7 @@ Home Screen installation does not provide offline DSH: the computer, DSH and its
|
|
|
14
14
|
|
|
15
15
|
- A capable iPhone/iPad Home Screen web app, opened in standalone mode, with the page-level `window.pushManager` API. The plugin detects capabilities rather than trusting a browser version string. Ordinary Safari tabs and unsupported browsers do not get a service-worker fallback.
|
|
16
16
|
- A **stable, trusted HTTPS origin** and an existing DSH Mobile browser pairing. HTTP and continuing through a certificate warning are not supported push configurations. The Android app's private-CA pin does not make an iPhone browser trust that CA.
|
|
17
|
-
- Plugin **0.8.0 or later** on the computer, with DSH and the Mobile Gateway for the exact HTTPS origin running. Packed push automation covers DSH **0.2.1-rc.2 through 0.2.2-rc.
|
|
17
|
+
- Plugin **0.8.0 or later** on the computer, with DSH and the Mobile Gateway for the exact HTTPS origin running. Packed push automation covers DSH **0.2.1-rc.2 through 0.2.2-rc.2** (rc.2, rc.3, rc.5, 0.2.2-alpha.3, 0.2.2-rc.1, 0.2.2-rc.2); other versions depend on the required public services. Missing services disable push without disabling ordinary Mobile access.
|
|
18
18
|
- Outbound access to Apple's push infrastructure from the host and phone. No Apple developer membership, notification SaaS account or new public inbound port is required. This feature does not create a VPN, change certificates, open a firewall or configure a proxy.
|
|
19
19
|
|
|
20
20
|
A publicly trusted private HTTPS hostname is architecturally eligible. The phone must still reach that origin to install, enable, authenticate and open a conversation. Delivery over a private/Tailscale origin is **unverified**; do not expose a private backend just to try push. Random tunnel hostnames are unsuitable for stable installation consent: an origin change never migrates authorization or subscriptions automatically. For persistent addresses, see [Cloudflare named tunnels](<CLOUDFLARE_TUNNEL.en.md>) or [your own HTTPS reverse proxy](<SELF_HOSTED_ORIGIN.en.md>); configuring an address alone does not prove push delivery.
|
package/docs/WEB_PUSH.md
CHANGED
|
@@ -14,7 +14,7 @@ DSH Mobile 插件运行在**电脑**上。iPhone/iPad 用户用 **Safari** 打
|
|
|
14
14
|
|
|
15
15
|
- 支持页面级 `window.pushManager` 的 iPhone/iPad 主屏幕 Web App,并以独立模式打开。插件检查实际能力,不根据浏览器版本字符串放行;普通 Safari 标签页和不支持的浏览器没有 Service Worker 降级方案。
|
|
16
16
|
- **固定、受信任的 HTTPS Origin**,以及现有 DSH Mobile 浏览器配对。HTTP 和绕过证书警告不属于受支持的推送配置;Android App 固定私有 CA,不等于 iPhone 浏览器信任该 CA。
|
|
17
|
-
- 电脑端已更新到插件 **0.8.0 或更高版本**,DSH 和对应 HTTPS 地址的 Mobile Gateway 正在运行。推送的打包自动化检查覆盖 DSH **0.2.1-rc.2 至 0.2.2-rc.
|
|
17
|
+
- 电脑端已更新到插件 **0.8.0 或更高版本**,DSH 和对应 HTTPS 地址的 Mobile Gateway 正在运行。推送的打包自动化检查覆盖 DSH **0.2.1-rc.2 至 0.2.2-rc.2**(rc.2、rc.3、rc.5、0.2.2-alpha.3、0.2.2-rc.1、0.2.2-rc.2);其他版本仍取决于所需公共服务是否可用,缺少服务只关闭推送,不关闭普通移动访问。
|
|
18
18
|
- 主机和手机能出站连接 Apple 推送基础设施。不需要 Apple 开发者会员、通知 SaaS 账号或新增公网入站端口。此功能不创建 VPN、不改证书/防火墙,也不配置代理。
|
|
19
19
|
|
|
20
20
|
使用公开受信任证书的私有 HTTPS 主机名在架构上可以使用。安装、启用、认证和打开会话时,手机仍须能访问该 Origin。私有/Tailscale Origin 的投递**尚未实测确认**;不要为了尝试推送把私有后端直接暴露到公网。随机隧道域名不适合作为长期安装地址:Origin 改变后,不自动迁移授权或订阅。需要长期地址时,可参考 [Cloudflare 命名隧道](<CLOUDFLARE_TUNNEL.md>)或[自有 HTTPS 反向代理](<SELF_HOSTED_ORIGIN.md>);仅完成地址配置不代表推送已验证送达。
|
package/lib/client.js
CHANGED
|
@@ -3864,22 +3864,46 @@ window.__ModuleLoader__.load({
|
|
|
3864
3864
|
return true;
|
|
3865
3865
|
}
|
|
3866
3866
|
/**
|
|
3867
|
+
* Whether the App's background service reports that it will actually post a
|
|
3868
|
+
* task-completion notification. All three facts must be explicit: the service
|
|
3869
|
+
* is enabled, its event stream is connected, and Android currently allows the
|
|
3870
|
+
* task channel to post (`canPost`: notifications enabled and channel importance
|
|
3871
|
+
* not NONE). The App records `notify:blocked` and does not touch
|
|
3872
|
+
* `connected`/`lastEventAt` when `canPost` is false, so treating enabled+
|
|
3873
|
+
* connected alone as a promise would let the page yield to a silent App and
|
|
3874
|
+
* drop the notification entirely.
|
|
3875
|
+
*/
|
|
3876
|
+
function backgroundNotificationsWillPost(report) {
|
|
3877
|
+
if (report === null || typeof report !== "object" || Array.isArray(report)) return false;
|
|
3878
|
+
const record = report;
|
|
3879
|
+
return record.enabled === true && record.connected === true && record.canPost === true;
|
|
3880
|
+
}
|
|
3881
|
+
/**
|
|
3867
3882
|
* Deliver one notification through the native bridge when available.
|
|
3868
3883
|
* Returns false when there is no bridge (desktop browsers keep their own
|
|
3869
3884
|
* UI) so callers can fall back; a denial flips the module sticky flag via
|
|
3870
3885
|
* onDenied for the session-lifetime nag guard.
|
|
3886
|
+
*
|
|
3887
|
+
* For task-completion notifications the page first asks the App whether its
|
|
3888
|
+
* background service will post the same event. When it will, the page yields:
|
|
3889
|
+
* one completed turn then produces exactly one notification, authored by the
|
|
3890
|
+
* plugin's Host-side event text and rendered by the App. Old Apps (no
|
|
3891
|
+
* `notification.background.get` report, no `connected` state, or no `canPost`
|
|
3892
|
+
* state) keep the previous behavior and the page still notifies.
|
|
3871
3893
|
*/
|
|
3872
3894
|
function fireTaskNotifyEvent(event, onDenied) {
|
|
3873
3895
|
const bridge = readNativeBridge();
|
|
3874
3896
|
if (bridge === void 0) return false;
|
|
3897
|
+
const deliver = () => bridge.invoke("notification.notify", {
|
|
3898
|
+
title: event.title,
|
|
3899
|
+
body: event.body,
|
|
3900
|
+
tag: event.tag,
|
|
3901
|
+
...event.navigate === void 0 ? {} : { navigate: event.navigate }
|
|
3902
|
+
});
|
|
3875
3903
|
Promise.resolve().then(() => bridge.capabilities()).then((capabilities) => {
|
|
3876
3904
|
if (!capabilities.includes("notification.notify")) return;
|
|
3877
|
-
|
|
3878
|
-
|
|
3879
|
-
body: event.body,
|
|
3880
|
-
tag: event.tag,
|
|
3881
|
-
...event.navigate === void 0 ? {} : { navigate: event.navigate }
|
|
3882
|
-
});
|
|
3905
|
+
if (event.kind !== "done" || !capabilities.includes("notification.background.get")) return deliver();
|
|
3906
|
+
return bridge.invoke("notification.background.get", {}).then((report) => backgroundNotificationsWillPost(report) ? void 0 : deliver(), () => deliver());
|
|
3883
3907
|
}).then(() => void 0, (error) => {
|
|
3884
3908
|
const code = typeof error === "object" && error !== null && "code" in error ? String(error.code) : "";
|
|
3885
3909
|
if ((code === "permission_denied" || code === "denied") && onDenied !== void 0) onDenied();
|
|
@@ -7115,6 +7139,124 @@ window.__ModuleLoader__.load({
|
|
|
7115
7139
|
}
|
|
7116
7140
|
}, copy.reset)));
|
|
7117
7141
|
}
|
|
7142
|
+
// src/composer-collapse.ts
|
|
7143
|
+
/**
|
|
7144
|
+
* Storage key for the opt-in switch.
|
|
7145
|
+
*
|
|
7146
|
+
* The setting row lives in the client bundle while the tracker lives in the
|
|
7147
|
+
* layout bundle, and tsdown.config.ts emits those as two independent CJS modules
|
|
7148
|
+
* that each inline this file. A module-level singleton therefore cannot carry the
|
|
7149
|
+
* value across: the row would flip its own copy while the layout kept the
|
|
7150
|
+
* boot-time value, and the toggle would only apply after a reload. The persisted
|
|
7151
|
+
* flag plus the two events below are the shared state instead, mirroring the
|
|
7152
|
+
* session-prefetch opt-in.
|
|
7153
|
+
*/
|
|
7154
|
+
const COMPOSER_COLLAPSE_STORAGE_KEY = "dsh-mobile.composer-collapse.enabled";
|
|
7155
|
+
const COMPOSER_COLLAPSE_EVENT = "dsh-mobile:composer-collapse-change";
|
|
7156
|
+
/**
|
|
7157
|
+
* Last value written in this page when storage refused the write.
|
|
7158
|
+
*
|
|
7159
|
+
* This is a per-bundle fallback for the blocked-storage case only: it is not the
|
|
7160
|
+
* cross-bundle transport (that is the persisted flag plus the events above). It
|
|
7161
|
+
* exists so a switch that cannot be persisted still reads back as flipped in the
|
|
7162
|
+
* same page, instead of the row snapping back while the layout obeys the event.
|
|
7163
|
+
*/
|
|
7164
|
+
let unpersistedEnabled;
|
|
7165
|
+
/** Read the persisted opt-in. Defaults to off; unreadable storage keeps it off. */
|
|
7166
|
+
function readComposerCollapseEnabled(storage) {
|
|
7167
|
+
try {
|
|
7168
|
+
const stored = (storage === void 0 ? globalThis.localStorage : storage)?.getItem("dsh-mobile.composer-collapse.enabled") ?? null;
|
|
7169
|
+
if (stored !== null) return stored === "true";
|
|
7170
|
+
return storage === void 0 ? unpersistedEnabled ?? false : false;
|
|
7171
|
+
} catch {
|
|
7172
|
+
return storage === void 0 ? unpersistedEnabled ?? false : false;
|
|
7173
|
+
}
|
|
7174
|
+
}
|
|
7175
|
+
/** Persist the opt-in and notify the layout bundle in the same page. */
|
|
7176
|
+
function writeComposerCollapseEnabled(enabled, storage) {
|
|
7177
|
+
try {
|
|
7178
|
+
const resolved = storage === void 0 ? globalThis.localStorage : storage;
|
|
7179
|
+
if (resolved === null) throw new Error("storage unavailable");
|
|
7180
|
+
resolved.setItem(COMPOSER_COLLAPSE_STORAGE_KEY, String(enabled));
|
|
7181
|
+
if (storage === void 0) unpersistedEnabled = void 0;
|
|
7182
|
+
} catch {
|
|
7183
|
+
if (storage === void 0) unpersistedEnabled = enabled;
|
|
7184
|
+
}
|
|
7185
|
+
try {
|
|
7186
|
+
globalThis.dispatchEvent?.(new CustomEvent(COMPOSER_COLLAPSE_EVENT, { detail: { enabled } }));
|
|
7187
|
+
} catch {}
|
|
7188
|
+
}
|
|
7189
|
+
/**
|
|
7190
|
+
* Subscribe to opt-in changes coming from either bundle. The listener is not
|
|
7191
|
+
* called with the initial value; read it once with readComposerCollapseEnabled().
|
|
7192
|
+
*/
|
|
7193
|
+
function subscribeComposerCollapseEnabled(listener) {
|
|
7194
|
+
const onStorage = (event) => {
|
|
7195
|
+
if (event.key !== null && event.key !== "dsh-mobile.composer-collapse.enabled") return;
|
|
7196
|
+
listener(readComposerCollapseEnabled());
|
|
7197
|
+
};
|
|
7198
|
+
const onCustom = (event) => {
|
|
7199
|
+
const detail = event.detail;
|
|
7200
|
+
listener(detail?.enabled ?? readComposerCollapseEnabled());
|
|
7201
|
+
};
|
|
7202
|
+
globalThis.addEventListener?.("storage", onStorage);
|
|
7203
|
+
globalThis.addEventListener?.(COMPOSER_COLLAPSE_EVENT, onCustom);
|
|
7204
|
+
return () => {
|
|
7205
|
+
globalThis.removeEventListener?.("storage", onStorage);
|
|
7206
|
+
globalThis.removeEventListener?.(COMPOSER_COLLAPSE_EVENT, onCustom);
|
|
7207
|
+
};
|
|
7208
|
+
}
|
|
7209
|
+
// src/composer-collapse-settings.ts
|
|
7210
|
+
const COMPOSER_COLLAPSE_COPY = {
|
|
7211
|
+
en: {
|
|
7212
|
+
title: "Reading mode: collapse composer",
|
|
7213
|
+
desc: "Collapse the composer editor while scrolling down a long conversation; tap it, focus it, or scroll back up to restore. It never collapses while a draft or a running reply is present.",
|
|
7214
|
+
on: "On",
|
|
7215
|
+
off: "Off"
|
|
7216
|
+
},
|
|
7217
|
+
zh: {
|
|
7218
|
+
title: "阅读模式:收起输入栏",
|
|
7219
|
+
desc: "向下滚动阅读长会话时收起输入框编辑区;点按、聚焦或向上回滚即恢复。有草稿或回复生成中时不会收起。",
|
|
7220
|
+
on: "已开启",
|
|
7221
|
+
off: "已关闭"
|
|
7222
|
+
},
|
|
7223
|
+
it: {
|
|
7224
|
+
title: "Modalità lettura: comprimi il campo",
|
|
7225
|
+
desc: "Comprime l’editor del campo mentre scorri una conversazione lunga; toccalo, mettilo a fuoco o scorri verso l’alto per ripristinarlo. Non si comprime con una bozza o una risposta in corso.",
|
|
7226
|
+
on: "Attivo",
|
|
7227
|
+
off: "Disattivo"
|
|
7228
|
+
}
|
|
7229
|
+
};
|
|
7230
|
+
/** Browser-only settings row; toggling it only persists the flag. */
|
|
7231
|
+
function ComposerCollapseSettings({ locale, read, write, subscribe }) {
|
|
7232
|
+
const readEnabled = read ?? (() => readComposerCollapseEnabled());
|
|
7233
|
+
const writeEnabled = write ?? ((enabled) => {
|
|
7234
|
+
writeComposerCollapseEnabled(enabled);
|
|
7235
|
+
});
|
|
7236
|
+
const enabled = (0, react.useSyncExternalStore)(subscribe ?? ((listener) => subscribeComposerCollapseEnabled(listener)), readEnabled, () => false);
|
|
7237
|
+
const copy = COMPOSER_COLLAPSE_COPY[locale];
|
|
7238
|
+
return (0, react.createElement)("label", {
|
|
7239
|
+
className: "dsh-mobile-settings_row",
|
|
7240
|
+
lang: locale === "zh" ? "zh-CN" : locale,
|
|
7241
|
+
"data-dsh-mobile-composer-collapse": enabled ? "on" : "off"
|
|
7242
|
+
}, (0, react.createElement)("div", { className: "dsh-mobile-settings_rowText" }, (0, react.createElement)("div", { className: "dsh-mobile-settings_title" }, copy.title), (0, react.createElement)("div", {
|
|
7243
|
+
className: "dsh-mobile-settings_desc",
|
|
7244
|
+
"aria-live": "polite"
|
|
7245
|
+
}, copy.desc)), (0, react.createElement)("input", {
|
|
7246
|
+
type: "checkbox",
|
|
7247
|
+
role: "switch",
|
|
7248
|
+
className: "dsh-mobile-settings_switchInput",
|
|
7249
|
+
checked: enabled,
|
|
7250
|
+
"aria-label": copy.title,
|
|
7251
|
+
"aria-checked": enabled ? "true" : "false",
|
|
7252
|
+
onChange: (event) => {
|
|
7253
|
+
writeEnabled(event.currentTarget.checked);
|
|
7254
|
+
}
|
|
7255
|
+
}), (0, react.createElement)("span", {
|
|
7256
|
+
className: "dsh-mobile-settings_selector",
|
|
7257
|
+
"aria-hidden": "true"
|
|
7258
|
+
}, enabled ? copy.on : copy.off));
|
|
7259
|
+
}
|
|
7118
7260
|
// src/mobile-display-scale.ts
|
|
7119
7261
|
/** Native App page scaling in DSH General settings, independent from local typography. */
|
|
7120
7262
|
/** Validate the native reply before displaying or reusing its values in a request. */
|
|
@@ -18004,6 +18146,11 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
18004
18146
|
preference: localFont.preference,
|
|
18005
18147
|
locale: selectedMobileControlLocale()
|
|
18006
18148
|
}));
|
|
18149
|
+
const removeComposerCollapse = ctx.slots.register({
|
|
18150
|
+
name: "settings.general.item",
|
|
18151
|
+
id: "dsh-mobile-composer-collapse",
|
|
18152
|
+
order: 15
|
|
18153
|
+
}, () => (0, react.createElement)(ComposerCollapseSettings, { locale: selectedMobileControlLocale() }));
|
|
18007
18154
|
const removeDisplayScale = ctx.slots.register({
|
|
18008
18155
|
name: "settings.general.item",
|
|
18009
18156
|
id: "dsh-mobile-display-scale",
|
|
@@ -18029,6 +18176,7 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
18029
18176
|
removeTaskNotifications();
|
|
18030
18177
|
removeSwitchComputer();
|
|
18031
18178
|
removeDisplayScale();
|
|
18179
|
+
removeComposerCollapse();
|
|
18032
18180
|
removeFontSize();
|
|
18033
18181
|
};
|
|
18034
18182
|
});
|
|
@@ -18150,6 +18298,7 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
18150
18298
|
exports.parseMobileExtensionManifest = parseMobileExtensionManifest;
|
|
18151
18299
|
exports.parseMobileRemoteProvider = parseMobileRemoteProvider;
|
|
18152
18300
|
exports.publishAuthoritativeExtensionIds = publishAuthoritativeExtensionIds;
|
|
18301
|
+
exports.readComposerCollapseEnabled = readComposerCollapseEnabled;
|
|
18153
18302
|
exports.reconcileRemovedExtensions = reconcileRemovedExtensions;
|
|
18154
18303
|
exports.registerUniqueDisposable = registerUniqueDisposable;
|
|
18155
18304
|
exports.renderDiagnosticPayloadSafely = renderDiagnosticPayloadSafely;
|
|
@@ -18162,6 +18311,7 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
18162
18311
|
exports.startLifecycleRefreshScheduler = startLifecycleRefreshScheduler;
|
|
18163
18312
|
exports.trustAuthenticatedGatewayConnection = trustAuthenticatedGatewayConnection;
|
|
18164
18313
|
exports.validateDiagnosticChecks = validateDiagnosticChecks;
|
|
18314
|
+
exports.writeComposerCollapseEnabled = writeComposerCollapseEnabled;
|
|
18165
18315
|
return module.exports;
|
|
18166
18316
|
}
|
|
18167
18317
|
});
|