@marktrue/dsh-mobile-remote 3.2.2-rc.1 → 3.2.3-alpha.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 +137 -35
- package/README.md +2 -1
- package/docs/03-api.md +2 -2
- package/docs/04-security.md +1 -1
- package/docs/05-test-cases.md +53 -14
- package/docs/07-user-manual.md +4 -0
- package/docs/09-compatibility.md +6 -2
- package/lib/index.js +182 -40
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,35 +1,8 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## v3.2.
|
|
3
|
+
## v3.2.3-alpha.1(build 27,issue #39)— NPM-only 自动更新(develop 收口)
|
|
4
4
|
|
|
5
|
-
> 插件 `3.2.
|
|
6
|
-
|
|
7
|
-
### 随上游并入的改动(main v3.2.2 基线,`09dd2934`)
|
|
8
|
-
|
|
9
|
-
本分支基于 upstream/main 的 v3.2.2 发布提交,以下上游已交付内容一并保留:
|
|
10
|
-
|
|
11
|
-
#### 审批兜底(真机验收修复)
|
|
12
|
-
|
|
13
|
-
- 下游返回 `unavailable` 不再抢先应答:仅在桌面给出**真实答复**时才收手机卡;下游 unavailable 时保留手机条目,等 `/respond` 或手机侧 fail-close 超时给出最终结果。
|
|
14
|
-
- 回归覆盖:下游立即 unavailable 时手机仍可作答,且结果以手机为准。
|
|
15
|
-
|
|
16
|
-
#### 对话轮次导航(PR #42)
|
|
17
|
-
|
|
18
|
-
- 一期:对话页右侧刻度轨按轮次跳转;长会话定位修复(尺寸缓存、有符号估算、尝试次数上界、尾部安全网)。
|
|
19
|
-
- 二期:接入宿主 `turnOutline` 投影,未加载轮次以短淡刻度显示,点它按 200 条/页有界跨页加载后定位;用户主动滚动即取消,四种终止结果均有明确说明。
|
|
20
|
-
- 新增只读端点 `GET /m/api/turn-outline?sessionId=`:四态(`available` / `empty` / `capability-missing` / `read-failed`)+ 稳定错误码、5 秒有界等待、冷折叠串行(并发 1)+ 有界队列(8)、按体积 256 KB 截断并显式标 `truncated`。
|
|
21
|
-
- 能力位 `capabilities.turnOutline` 按宿主是否真的挂载投影判定;旧插件/旧 App 双方都能降级到既有行为;长会话跨页跳转的限制如实标注。
|
|
22
|
-
|
|
23
|
-
#### App 在线本地通知(#46)
|
|
24
|
-
|
|
25
|
-
- SSE 在线且 App **不在前台**时,`approval/requested` 与 `question/requested` 直接弹系统本地通知(Android 13+ 首次启动请求通知权限);点通知直达该会话的待办卡。
|
|
26
|
-
- 同一 `rpcId` 5 分钟内只提醒一次(SSE 重连/回放会重放未结算帧);回到前台自动清掉系统通知;`pushUrls` 退为「App 被杀/离线」时的兜底。
|
|
27
|
-
- Android 构建开启 core library desugaring(`desugar_jdk_libs 2.1.5`),满足通知库要求。
|
|
28
|
-
|
|
29
|
-
#### 二维码地址优选(#49)
|
|
30
|
-
|
|
31
|
-
- 新增纯函数 `orderQrAddresses`:默认仍是「局域网私有地址优先」;**仅当机器上没有任何 `10./192.168.x/172.16-31` 地址时**,才把 Tailscale CGNAT(`100.64/10`)提到最前。
|
|
32
|
-
- 7 项回归覆盖排序与端到端载荷选择;`docs/03-api.md` 同步。
|
|
5
|
+
> 插件 `3.2.3-alpha.1` / App `3.2.3+27` / 载体 `3.2.3-alpha.1-app`。develop 已合并 issue #39 实现;本条目为收口后的开发快照。
|
|
33
6
|
|
|
34
7
|
### 更新源与安全
|
|
35
8
|
|
|
@@ -48,18 +21,147 @@
|
|
|
48
21
|
- 历史失败/未确认事务不再阻塞 App 更新;APK 按 sha 复用,安装未完成给出可执行指引。
|
|
49
22
|
- 桌面「连接移动端」同步显示阶段文本与最新输出。
|
|
50
23
|
|
|
51
|
-
### 其他随分支并入
|
|
52
|
-
|
|
53
|
-
- Git 标签页设置顺序与 SSE 取消回归覆盖补齐。
|
|
54
|
-
|
|
55
24
|
### 平台
|
|
56
25
|
|
|
57
26
|
- Windows:npm/pnpm 经 shell 调用,缺失客户端给出稳定原因;桌面原生自更新仍受 `restart-unsupported` 限制(Linux/WSL 宿主可完成自更新)。
|
|
58
27
|
|
|
59
28
|
|
|
60
|
-
### 上游原文(v3.2.2
|
|
29
|
+
### 上游原文(main v3.2.2 / v3.2.3,截至 `a4fb7a3`)
|
|
30
|
+
|
|
31
|
+
以下为上游 main 已发布条目的原文(标题层级已下移以适配本节),按 main 顺序保留:
|
|
32
|
+
|
|
33
|
+
#### v3.2.3(2026-10-31,build 30 重发,issue #55 / PR #56)— 后台提醒保活 + 通知判据与精确撤销
|
|
34
|
+
|
|
35
|
+
> 版本:插件 `3.2.3` / App `3.2.3+30`。对 v3.2.3(build 29)的**同版本号重发**,Release 资产替换;构建号 29 → 30。
|
|
36
|
+
> 验收:`flutter analyze` 0 issue、`flutter test` 593、`node --test test/*.mjs`(审批相关单跑 13/13)、真机 9 项(见下)。
|
|
37
|
+
|
|
38
|
+
##### 真机验收(build 29 的实现,Xiaomi 17 Pro Max / Android 16,2026-10-31)
|
|
39
|
+
|
|
40
|
+
- 常驻通知模式启动:`KeepAliveService` 前台运行、通道 `dsh_keep_alive`、通知 ID 1041,`ONGOING|ONLY_ALERT_ONCE|NO_CLEAR`。
|
|
41
|
+
- 与桌面宠物互斥:切常驻通知 → 宠物服务停止;切回桌面宠物 → 常驻服务停止。
|
|
42
|
+
- 选「关闭」→ 两个服务都停。
|
|
43
|
+
- `force-stop` 后重开 App → 按持久化模式自动恢复常驻服务。
|
|
44
|
+
- 按 HOME 退到后台 190 秒:进程 PID 与 ServiceRecord 不变,服务存活。
|
|
45
|
+
- 通知权限被拒时选「常驻通知」→ 弹「需要通知权限」且不启动服务。
|
|
46
|
+
- 端到端:后台收到真实审批帧 → 系统通知出现(`dsh_attention`);`/respond` 结算后通知自动撤销。
|
|
47
|
+
|
|
48
|
+
> build 30 并入 PR #56 后,原生保活服务与通知判据换成了其实现(通道 `dsh_keepalive_v2`、ID 3515、启动前 `resolveService` 校验),PR 作者在 MIUI / Android 14 上验过;本机 build 30 的复测待设备重新连上后补。
|
|
49
|
+
|
|
50
|
+
##### 通知判据与身份(PR #56,合并后以此实现为准)
|
|
51
|
+
|
|
52
|
+
- 新增 `lib/attention.dart`:把「要不要弹、弹哪条、怎么撤」抽成纯逻辑层(`AttentionEvent` / `decideAttention` / `AttentionLedger`),key = `kind|sessionId|approvalId 或 rpcId`,通知 id 用稳定哈希,跨重连与重启都不重复、不撤错。
|
|
53
|
+
- 进会话即已读:打开会话时该会话未读自动标已读并撤掉对应横幅;横幅按会话合并,全局一条,点击直达。
|
|
54
|
+
- `notify_diag.dart`:通知链路诊断页(前台/后台、权限、渠道、服务状态与心跳)。
|
|
55
|
+
- 设置页增加「系统省电设置」入口:MIUI/HyperOS 默认会冻结后台应用(实测连前台服务也照冻,需设为「不受限制」)。
|
|
56
|
+
- 原生启动前用 `resolveService` 校验组件可解析——此前 manifest 漏声明时 `startForegroundService` 不抛异常,UI 会假成功。
|
|
57
|
+
- 单测新增 `attention_test.dart`(38 条)与通知测试补充,总数 593。
|
|
58
|
+
|
|
59
|
+
##### 后台提醒:三选一模式(#55 步骤 4)
|
|
60
|
+
|
|
61
|
+
- 设置 → 后台提醒:`关闭 / 常驻通知 / 桌面宠物` 三选一,互斥(切换时先停另一个服务,不会出现两条常驻通知)。
|
|
62
|
+
- 新增 `KeepAliveService`(`specialUse` 前台服务):一条低干扰常驻通知把进程钉住,让 Dart 侧 SSE 在后台不被冻结;不自己连 SSE,也不碰宠物渲染。
|
|
63
|
+
- 默认关闭;启动时按上次模式恢复。常驻通知不可见(用户拒绝 `POST_NOTIFICATIONS`)时不启动——否则插件会误判手机在线,把等待从 300s 压到 120s。
|
|
64
|
+
- `onTaskRemoved → stopSelf()`:划掉最近任务时诚实退出,避免"通知还在、进程已死"的假在线。
|
|
65
|
+
- 切到「桌面宠物」沿用原悬浮窗权限引导与失败提示。
|
|
66
|
+
|
|
67
|
+
##### 通知身份与撤销(#55 步骤 1)
|
|
68
|
+
|
|
69
|
+
- 审批通知的 id 与去重键改用 `approvalId`(问题帧退回 `rpcId`):SSE 重连重放同一审批帧不再堆叠多条通知。
|
|
70
|
+
- `approval/resolved` / `question/resolved` 到达时按 id 精确撤销对应系统通知。
|
|
71
|
+
- 回到前台不再 `cancelAll()`——旧行为会把仍未处理的审批提醒一起抹掉。
|
|
72
|
+
- 插件:`approval/resolved` 帧补 `sessionId` 与 `outcome`,`question/resolved` 补 `sessionId`。
|
|
73
|
+
|
|
74
|
+
##### 插件(#55 步骤 3)
|
|
75
|
+
|
|
76
|
+
- `pendingTimeoutMs` 改为可配置(默认仍 120s),`offlineHoldMs` 保持原默认 300s。
|
|
77
|
+
- `hello` 与 `/api/bootstrap` 的 `capabilities` 增加 `hold: { onlineMs, offlineMs }`,手机端只用它展示剩余时间,结算仍以服务端为准。
|
|
78
|
+
|
|
79
|
+
##### 未包含(按 issue 约定后置)
|
|
80
|
+
|
|
81
|
+
- 首次连接后的保活引导弹窗、进会话即已读与横幅合并(issue 步骤 2),下一版做。
|
|
82
|
+
|
|
83
|
+
#### v3.2.3(2026-10-30,issue #50 / #52,PR #51 / #53 / #54)— 工作区归属修复 + 大文件流式下载 + Markdown 预览
|
|
84
|
+
|
|
85
|
+
> 版本:插件 `3.2.3` / App `3.2.3+28`。
|
|
86
|
+
> 验收门禁:`node --test test/*.mjs`(208 通过 + 1 平台跳过,`interaction-settlement` 单跑 13/13)、`flutter analyze`(0 issue)、`flutter test`(555)、`flutter build apk --debug` 通过。
|
|
87
|
+
|
|
88
|
+
##### 建会话前确保 cwd 有归属工作区(#50)
|
|
89
|
+
|
|
90
|
+
- **问题**:App 目录选择器可选到任意目录(含新建文件夹),若该目录不在 `workspaceRegistry` 里,会话会带着「无归属工作区」的 `cwd` 落盘。内核 `WorkspaceEntity.attachSession()` 要求 `header.cwd` 与工作区根**完全相等**,于是:
|
|
91
|
+
- 本次 attach 走「向上找祖先工作区」分支必然抛错,且原实现 `workspace?.attachSession(...)` **未 await**,异常逃过 `try/catch`;
|
|
92
|
+
- 会话成为孤儿,**下次 dsh 启动** bootstrap 再 attach 时抛错 → `dsh: fatal load failure` → 进程 exit 1(web/SSH/IM 全部入口下线,反代侧 502)。
|
|
93
|
+
- **修复**:`POST /m/api/sessions` 在建会话**之前**解析工作区归属——已注册则复用,未注册则 `workspaceRegistry.create(cwd)` 注册成新工作区,并用工作区的 realpath 规范路径作为 `cwd`;`create` 抛错(目录不存在/非目录)时返回 `400 cwd-not-a-directory`,不再产生孤儿会话。
|
|
94
|
+
- **兼容**:旧宿主未暴露 `registry.create` 时保留原「逐级向上找最近已注册工作区」的尽力而为路径,且不改写 `cwd`。
|
|
95
|
+
- **顺带**:attach 改为 `await` + `catch` 记 warn,避免未处理的 Promise rejection。
|
|
96
|
+
- **新增回归**:`test/issue50-cwd-workspace.test.mjs`(未注册→注册并 attach;已注册→复用;目录不可用→400 且不建会话;旧宿主→回落且不改写 cwd)。
|
|
97
|
+
|
|
98
|
+
##### 插件:`/files` 支持 Range / HEAD / ETag(#52 / PR #53)
|
|
99
|
+
|
|
100
|
+
- 单区间 `Range: bytes=...` → `206` + `Content-Range`;越界或格式错 → `416` + `bytes */size`(多区间仍不支持,避免响应放大)。
|
|
101
|
+
- `ETag`(size + mtime)与 `Last-Modified`,`If-None-Match` 命中回 `304`。
|
|
102
|
+
- `HEAD` 只回头(content-length / etag / accept-ranges),不读文件体;客户端断开时销毁读流,避免 fd 泄漏。
|
|
103
|
+
- `/directories` 增加 `fileEntries: [{name,size,mtimeMs,mime}]`,`files` 仍保持纯字符串数组,旧 App 不受影响。
|
|
104
|
+
- `hello` 与 `/api/bootstrap` 的 `capabilities` 增加 `fileRange`;旧插件不声明时 App 自动退回整文件下载。
|
|
105
|
+
|
|
106
|
+
##### App:流式下载(#52 / PR #54)
|
|
107
|
+
|
|
108
|
+
- 下载改为 `StreamedResponse` → 临时文件:进度回调、可取消、`Range` 续传(旧插件返回 200 时自动从头写);取消/失败清理半成品。
|
|
109
|
+
- 完成后由原生按路径保存到 `Download/DSH-Remote`(MediaStore `IS_PENDING`,I/O 不在主线程),完成弹窗给「查看 / 关闭」两个出口。
|
|
110
|
+
- 失败文案人话化(401/403、404、5xx、断网、超时、中断、证书、保存失败各有表述),原始异常折叠进可复制的技术详情。
|
|
111
|
+
- `MainActivity` 新增 `saveFileFromPath` / `openFile` / `shareFile` / `decodeText`;旧保存路径不再写死包名,改用 `packageName`。
|
|
112
|
+
|
|
113
|
+
##### App:预览编码识别 + Markdown 渲染(#52 / PR #54)
|
|
114
|
+
|
|
115
|
+
- 预览只取前 256 KiB:`fileRange` 可用时发 `Range`,否则读满主动断开。
|
|
116
|
+
- 编码判定改为「BOM → 无 BOM UTF-16 双向试解打分 → 严格 UTF-8(容忍采样末尾被截断的多字节字符)→ GB18030」,GB18030 走原生 `Charset` 解码;修掉真 UTF-8 中文被误判成 GB18030 的整篇乱码。
|
|
117
|
+
- `.md` / `.markdown` 默认渲染:按块边界分片(围栏内不切)+ `ListView.builder` 懒渲染,顶部可切「预览 / 源码」;源码模式也改为按行懒加载 + 自动换行。
|
|
118
|
+
- 修 `isWithinRoot` 的分隔符判断(Windows 下 `root\sub` 曾被判成越界)。
|
|
119
|
+
- 测试:`flutter test` 555(基线 509 + 新增 46:download_stream、download_error、session_files_logic、session_files_widget)。
|
|
120
|
+
|
|
121
|
+
#### v3.2.2(2026-10-29,build 27 重发)— 外观:三套皮肤 + 8 款图标 + 宠物联动
|
|
122
|
+
|
|
123
|
+
> 版本:插件 `3.2.2` / App `3.2.2+27`。对已发布 v3.2.2(build 25)的**同版本号重发**,Release 资产替换;构建号 25 → 27(26 为本地产物,从未发布)。
|
|
124
|
+
> 插件 `lib/` 无逻辑改动;重新打包只为修掉 `package.json` 的 UTF-8 BOM(带 BOM 的 tgz 安装会被 npm 拒绝)。
|
|
125
|
+
> 验收:`flutter analyze` 0 issue、`flutter test` 515、`node --test test/*.mjs` 200 通过 + 1 平台跳过、`gradlew :app:compileDebugKotlin` 通过。
|
|
126
|
+
|
|
127
|
+
##### 外观:三套皮肤(默认仍是原 DSH)
|
|
128
|
+
|
|
129
|
+
- `设置 → 外观` 新增皮肤选择:**深潜夜航**(墨绿黑底 / 声呐环 / 冷光气泡 / 潮汐波浪 / 薄荷绿主色 + 深色字)、**海盐汽水**(奶油底 / 可乐蓝按钮 / 光束与气泡)、**鲸落星海**(靛蓝底 / 星尘星座 / 月盘 / 金色主色 + 深色字)。
|
|
130
|
+
- 每套皮肤 × 明暗 = 8 组完整令牌,挂在 `ThemeExtension`(`DshPalette`)上;页面统一走 `DshColors.xxx(context)`,不再逐层传 skin。默认皮肤令牌与旧版逐值一致,老用户视觉不变。
|
|
131
|
+
- 首页在这三套皮肤下换成大肥鱼 Hero:左侧一句问候(按皮肤切换文案),右侧固定 132dp 角色,下方动态背景(30fps 上限、不可见即停、减少动效时只留静态渐变)。
|
|
132
|
+
|
|
133
|
+
##### 外观:8 款 App 图标
|
|
134
|
+
|
|
135
|
+
- 新增 `深海头像 / 贝壳徽章 / 月夜潮线 / 奶油贴纸 / 鲸邮票 / 霓虹灯牌 / 鲸尾剪影`,加原图标共 8 款,`设置 → 外观 → App 图标` 四列网格选择。
|
|
136
|
+
- 实现用 8 个 `activity-alias`(`PackageManager` 一次只启用一个),`MainActivity` 不再直接持有 `LAUNCHER`;默认 `IconDefault` 的图标与原 `ic_launcher` 逐像素一致(仅重编码差异),**默认外观不变**。
|
|
137
|
+
- 选择持久化在 `dsh_mr_app_icon`,启动时 `syncIcon` 对账;原生侧已是目标 alias 时不重复切换,避免每次启动触发桌面刷新。
|
|
138
|
+
- 已知限制:Android 13 主题图标(monochrome 层)与 `roundIcon` 未提供,下个版本补。
|
|
139
|
+
|
|
140
|
+
##### 首页角色互动
|
|
141
|
+
|
|
142
|
+
- 点头部摸头、点身体戳一下、点尾部甩尾,各有对应动作与台词气泡(1.8 秒回落);长按打开迷你面板(运行中会话 / 最近通知 / 桌面宠物状态 / 用量入口)。
|
|
143
|
+
- 角色区域外滑动不被拦截(`GestureDetector` 只吃自己那块);`减少动效` 或系统「移除动画」时退回静态帧。
|
|
144
|
+
|
|
145
|
+
##### 桌面宠物与 App 联动(行为变化)
|
|
146
|
+
|
|
147
|
+
- **App 在前台时收起桌面宠物**(默认开,可关):`MainActivity.onStart/onStop` 把前后台状态告诉服务,宠物根视图置 `GONE`,同时收掉状态气泡与迷你面板。解决两件事——悬浮宠物与首页角色同屏重复;移动窗口盖住「+ 新建会话」等按钮时**吞掉触摸**。
|
|
148
|
+
- 用 `onStart/onStop` 而非 `onResume/onPause`(权限弹窗不算离开);`onStop` 而非 `onDestroy`(进程被杀时后者不保证回调)。服务与 Activity 同进程,用静态标记而非 `startService` 广播,规避 Android 12+ 后台启动前台服务的限制。
|
|
149
|
+
- 恢复时回到「贴边完全露出」再走 5 秒自动缩进,不改用户拖过的纵向位置。
|
|
150
|
+
|
|
151
|
+
##### 「减少动态」改为「减少动效」并挪位
|
|
152
|
+
|
|
153
|
+
- 该开关实际同时管桌面宠物、首页角色与首页背景三处,原来只写在桌面宠物卡里、文案也只提宠物。现改名 **减少动效**,移入 `设置 → 外观`,副标题写明覆盖范围与「系统级移除动画始终优先」。
|
|
154
|
+
- 行为不变:`MediaQuery.disableAnimations`(系统无障碍)优先级最高且不可被 App 覆盖,App 开关只能在此基础上再关。
|
|
155
|
+
|
|
156
|
+
##### 图标入口迁移提示
|
|
157
|
+
|
|
158
|
+
- 升级后首次启动弹一次说明:启动入口从 `com.dsh.remote.MainActivity` 换成 `com.dsh.remote.IconDefault`,**桌面上的旧快捷方式可能点不开一次**,删掉后从抽屉重新拖一个即可;此后切换图标不再出现。
|
|
159
|
+
- 只在 Android 提示(其它平台不涉及 alias 迁移),结果记在 `dsh_mr_icon_migration_tip`,不会重复打扰。
|
|
160
|
+
|
|
161
|
+
##### 其它
|
|
61
162
|
|
|
62
|
-
|
|
163
|
+
- 对话页发送按钮的硬编码白色改为 `DshColors.onBrand(context)`,薄荷/金色底上会变深色图标,符合对比度要求。
|
|
164
|
+
- APK 体积:宠物动画(原生 14.1 MB)+ 首页动画(9.6 MB)。实测 release 包约 99 MB(上一版 71.8 MB),如需瘦身可把首页那 4 组 webp 降采样或改静态帧。
|
|
63
165
|
|
|
64
166
|
#### v3.2.2(2026-10-27,issue #46 / #49,PR #42)— 在线本地通知 + 轮次导航 + 二维码组网地址优选 + 审批兜底修复
|
|
65
167
|
|
package/README.md
CHANGED
|
@@ -29,7 +29,8 @@
|
|
|
29
29
|
- 🏢 **模型提供商互通**:PC 端「设置 → 模型」与手机同一配置通道——37 个内置提供商(anthropic / openai / google / groq 等)手机可见可配(baseURL + API Key 即激活),模型选择器按提供商分组,设置页「模型提供商」可搜索/编辑/探测模型
|
|
30
30
|
- 👀 **过程可见性**:活动条(思考中 / 正在调用工具实时状态行)+ 可折叠思考面板(默认只显示状态)
|
|
31
31
|
- 🛠️ **会话工具**:运行中的后台任务实时卡片(可取消)+ 三页签弹层——任务 / 子代理(查看+中断)/ 目标(查看、创建、暂停、继续、标记完成,与 PC 端 goal 服务同源)
|
|
32
|
-
-
|
|
32
|
+
- 🐋 **桌面宠物(大肥鱼)**:MIT 鲸尾女仆常驻桌面——任务/通知/余额驱动 16 组动作(思考、处理、等待、完成、出错、待机微动作);点头部/身体/尾巴互动,双击摸头,拖动悬空+落地,长按打开旧迷你面板(运行中会话 / 通知 / 用量与额度 / 余额 / 打开 App / 去充值);贴边 5 秒缩进 45% 并虚化,完成/等待/出错自动探头;支持三档大小与减少动效;**App 在前台时自动收起**(不与首页角色同屏、不挡按钮);错过的通知也会补提示(气泡/角标/横幅)
|
|
33
|
+
- 🎨 **外观:皮肤与图标**:四套皮肤(默认 / 深潜夜航 / 海盐汽水 / 鲸落星海,各含明暗两套)——三套大肥鱼皮肤下首页换成角色 Hero + 动态背景(气泡/声呐环/星尘月盘的 30fps 上限动画),角色可点头部/身体/尾巴互动、长按开面板;App 图标 8 款(原图标 / 深海头像 / 贝壳徽章 / 月夜潮线 / 奶油贴纸 / 鲸邮票 / 霓虹灯牌 / 鲸尾剪影),用 `activity-alias` 切换,默认与原图标逐像素一致
|
|
33
34
|
- 💾 **体验打磨**:Agent 状态 bootstrap 同步(按钮/活动条即时反映 PC 状态)、下拉刷新收集地址(蒲公英/Tailscale 自动进候选)、聊天草稿保留(退出重进自动恢复)、首页改版(官方 logo / 新欢迎语 / 最近会话卡片滑动)、语言切换(中/英)
|
|
34
35
|
- 📶 **扫码连接**:桌面 dsh 设置页出现「连接移动端设备」二维码 → 手机 App 扫码即连(免输 IP/口令)
|
|
35
36
|
- 🩺 **环境诊断**:一键查看当前环境各项能力(含问询/审批桥状态),升级/排障一目了然
|
package/docs/03-api.md
CHANGED
|
@@ -298,7 +298,7 @@
|
|
|
298
298
|
| `session/context` | `{ sessionId, contextWindow }`——模型上下文窗口(`request/context` 事件,PC 端圆环同源) |
|
|
299
299
|
| `agent/status` | `{ agentId, sessionId, status, child }`——`status` 只有 `running` / `idle` 两值(内核 `AgentStatus` 即为二元联合);`sessionId` 为去 `session:` 前缀的会话 id,`child` 标记子代理会话(v2.7.2 起携带后两字段)。**不存在 `waiting`**:等待用户审批/作答时 agent 仍为 `running`,该事实由 `mobile/frame` 的挂起问询/审批承载 |
|
|
300
300
|
| `notifications/changed` | 通知记录增删(如移动端删除后),客户端刷新列表与角标 |
|
|
301
|
-
| `mobile/notify` | `{ notification: { id, kind, sessionId, title, detail, time } }`——插件"真结束"判定后推送的通知(completed / failed / needs-answer
|
|
301
|
+
| `mobile/notify` | `{ notification: { id, kind, sessionId, title, detail, time } }`——插件"真结束"判定后推送的通知(completed / failed / needs-answer),桌面宠物/App 与通知中心同源渲染(v2.7.2) |
|
|
302
302
|
| `mobile/frame` | 内核瞬态帧(问询/审批)。`frame` 字段为 `question/requested`(含 `rpcId`、`questions[]`)、`question/resolved`(`questionRpcId`)、`approval/requested`(`rpcId`、`approvalId`、`toolName`、`reason?`)、`approval/resolved`(`approvalId`)。**App 断线重连时服务端补发挂起的待答帧**(`pendingFrames` 回放) |
|
|
303
303
|
| `mobile/queue` | `{ sessionId, rows: [{ id, text, placement }] }`——内核队列快照(`agent/inbox/spliced` 即时镜像,v3.0.2):认领/删除/编辑实时反映,App 端 dock 以此为权威源(`placement`: `queued` / `steering` / `context`,与 GET /queue 同款形状);断线重连时 mux 回放当前队列 |
|
|
304
304
|
| `git/changed`(规划中) | 设计目标为 `{ repositoryId }`;当前版本未实现 Git provider,也不会推送此帧 |
|
|
@@ -820,7 +820,7 @@ fetch/push 序列化 common domain,pull/sync/abort 同时序列化 common 与
|
|
|
820
820
|
- 查询快照在电脑进程内缓存 60 秒并共享并发请求;手动 `refresh=1` 的连点在 2 秒内复用快照;不写手机或电脑持久化文件。配额窗口不相加,有限 Credits 和个人消费上限分别展示。
|
|
821
821
|
- HTTP 错误只返回通用 `502 account-usage-failed`(单来源失败不会使整体请求失败)。
|
|
822
822
|
|
|
823
|
-
|
|
823
|
+
**桌面宠物面板消费语义(沿用 v3.1.5 的 ADR 0008)**:原生桌面宠物面板把本端点当作「展开时按需读取」的快照——每次展开面板最多触发一次查询(客户端 2 分钟节流,不并入面板打开期间的 5 秒轮询,也不做后台周期轮询);成功快照在进程内保留,失败时沿用旧值并标注相对时间;投影不可用(旧插件 404 / 未配置 / 全部失败)时,面板整体降级为原有的单行 DeepSeek 余额。面板按「一个来源一行」渲染:金额来源保留文字(CNY 优先),配额来源只显示主 bucket 窗口的细条与颜色、不出百分比数字,每个进度条上方居中显示窗口短标签(5h / 每周 / 每月,与详情页窗口语义一致);附加 bucket(服务端命名的 `名称 · 5h`)与账户身份不出现 overlay 面板,详情页仍是完整信息入口(`displayName`/`maskedEmail`、Credits、个人消费上限、重置时间)。
|
|
824
824
|
|
|
825
825
|
### 6.17 文件传输(v3.1.2,B站 csborbbnc 反馈)
|
|
826
826
|
|
package/docs/04-security.md
CHANGED
|
@@ -85,7 +85,7 @@
|
|
|
85
85
|
- **后续**:是否将文件传输收紧到工作区包含 + descriptor-relative 句柄保护,见 GitLab `ahedu/dsh-mobile-remote#16`;在决定前,本文件如实描述当前实现,不描述未落地的保护。
|
|
86
86
|
- **第三方推送通道脱敏(v2.6)**:Server酱/ntfy/Bark/generic 等推送默认只收到「事件类型 + 会话短码」(`pushContent: minimal`),会话标题/错误详情等核心内容默认不出本机;仅显式配置 `pushContent: standard` 后外发——第三方服务不可信。
|
|
87
87
|
- **用量与额度投影(v3.1.5)**:`/m/api/account-usage` 只返回成功来源的余额/配额、脱敏 Codex 账户标签和汇总失败数;DeepSeek/OpenCode Go 密钥与 Codex OAuth token 永不进入响应、App 日志或持久化文件。OpenCode Go 只请求固定官方 HTTPS 端点;Codex 启用代理但代理不可用时拒绝直连。服务端只保留 60 秒进程内快照,单来源失败不回退到过期数据。
|
|
88
|
-
-
|
|
88
|
+
- **桌面宠物 overlay 面板脱敏(沿用 ADR 0008)**:桌面宠物是 **`TYPE_APPLICATION_OVERLAY` 系统级浮层**,可能出现在锁屏、他人可见、或覆盖其它应用之上。因此面板的用量与额度区块只展示来源标题、金额文字与配额细条/颜色;**不显示账户身份(displayName/maskedEmail)**、不显示任何聚合或换算数值;配额窗口永不相加。数据仅按需获取(展开面板时,客户端节流)、失败沿用旧值并标注相对时间,不新增后台轮询或持久化。
|
|
89
89
|
## 7. 安全测试要点(并入 05-test-cases.md)
|
|
90
90
|
1. 口令启用后:未认证访问 bootstrap/send/events/history 返回 401;错误口令 401。
|
|
91
91
|
2. 口令关闭时:以上端点返回 200。
|
package/docs/05-test-cases.md
CHANGED
|
@@ -166,8 +166,8 @@
|
|
|
166
166
|
|
|
167
167
|
| 项目 | 内容 |
|
|
168
168
|
|---|---|
|
|
169
|
-
| 步骤 | 带 token 请求 `/m/api/bootstrap` → 检查 agents/sessions 条目;App
|
|
170
|
-
| 预期 | ① agents/sessions 条目含 `title`(`sessionTitleOf ?? shortSessionId` 兜底);②
|
|
169
|
+
| 步骤 | 带 token 请求 `/m/api/bootstrap` → 检查 agents/sessions 条目;App 回桌面点桌面宠物看「运行中的会话」面板 |
|
|
170
|
+
| 预期 | ① agents/sessions 条目含 `title`(`sessionTitleOf ?? shortSessionId` 兜底);② 桌面宠物面板显示会话标题(超宽省略号截断),而非 session id 短码;③ 旧版插件(无 title)时 App 回退显示 id 短码 |
|
|
171
171
|
| 实测 | ✅ 端到端通过(真机:面板显示会话标题) |
|
|
172
172
|
|
|
173
173
|
### F-22 思维链折叠状态持久化(v3.1.0 候选,App 真机)
|
|
@@ -227,22 +227,22 @@
|
|
|
227
227
|
| 步骤 | 快速连续进入设置和详情页;点击详情页顶部刷新;观察服务端上游请求与 App 显示 |
|
|
228
228
|
| 预期 | 普通进入请求复用 60 秒成功快照且并发请求共享一轮上游查询;手动刷新请求 `?refresh=1` 并发查询全部来源;不产生本地持久化额度文件,不周期轮询上游 |
|
|
229
229
|
|
|
230
|
-
### F-29
|
|
230
|
+
### F-29 桌面宠物面板三来源区块(#18 用量与额度)
|
|
231
231
|
|
|
232
232
|
| 项目 | 内容 |
|
|
233
233
|
|---|---|
|
|
234
|
-
| 前置 |
|
|
235
|
-
| 步骤 |
|
|
234
|
+
| 前置 | 插件含 `/account-usage`,App 开启桌面宠物,DeepSeek / Codex / OpenCode Go 至少各一个来源可用 |
|
|
235
|
+
| 步骤 | 展开桌面宠物面板,等待用量与额度区块渲染 |
|
|
236
236
|
| 预期 | 出现「用量与额度」区块(含「详情 ▸」);每来源一行:DeepSeek 出金额文字(CNY ¥),Codex / OpenCode Go 配额行每个进度条上方居中显示窗口短标签(Codex:5h / 每周;OpenCode Go:5h / 每周 / 每月),条只出细条与颜色、不出百分比数字;主 bucket 窗口按 5h → 周 → 月排列;金额行在低余额时变红;点击区块或「详情 ▸」打开 App 用量页;底部「去充值」保留 |
|
|
237
237
|
|
|
238
|
-
### F-30
|
|
238
|
+
### F-30 桌面宠物展开时按需获取与节流
|
|
239
239
|
|
|
240
240
|
| 项目 | 内容 |
|
|
241
241
|
|---|---|
|
|
242
|
-
| 步骤 |
|
|
242
|
+
| 步骤 | 反复快速展开/收起桌面宠物面板;观察服务端 account-usage 调用次数 |
|
|
243
243
|
| 预期 | 每次展开最多触发一次查询;2 分钟节流窗内重复展开复用在途/缓存结果,不反复打上游;首次展开区块先显示「查询中…」再异步就地更新;App 被杀后展开面板仍能拉到数据 |
|
|
244
244
|
|
|
245
|
-
### F-31
|
|
245
|
+
### F-31 桌面宠物面板降级与陈值(#18 用量与额度)
|
|
246
246
|
|
|
247
247
|
| 项目 | 内容 |
|
|
248
248
|
|---|---|
|
|
@@ -250,13 +250,14 @@
|
|
|
250
250
|
| 步骤 | 展开面板,分别观察 A / B / C |
|
|
251
251
|
| 预期 | A/B/C 下区块整体不出现、不显示任何报错,原有单行余额原位显示且点击仍=去充值(A 不退化现有功能);C 曾成功过 → 区块保留旧值并以小灰字标注相对时间(超过 10 分钟才标注);C 从未成功过 → 退回单行余额;不产生本地持久化额度文件 |
|
|
252
252
|
|
|
253
|
-
### F-32
|
|
253
|
+
### F-32 桌面宠物面板隐私边界(#18 用量与额度)
|
|
254
254
|
|
|
255
255
|
| 项目 | 内容 |
|
|
256
256
|
|---|---|
|
|
257
|
-
| 步骤 |
|
|
258
|
-
| 预期 | 区块不显示 Codex 账户身份(displayName/maskedEmail
|
|
259
|
-
|
|
257
|
+
| 步骤 | 展开面板,检查区块、宠物与操作说明弹窗 |
|
|
258
|
+
| 预期 | 区块不显示 Codex 账户身份(displayName/maskedEmail);宠物自身不常驻金额;配额窗口与来源永不相加、不换算;附加 Codex bucket(服务端命名「名称 · 5h」)不出现在面板;设置 → 桌面宠物操作说明的面板内容已包含「用量与额度」|
|
|
259
|
+
|
|
260
|
+
### F-33 对话时间线能力与历史/实时一致(PR #24 时间线)
|
|
260
261
|
|
|
261
262
|
| 项目 | 内容 |
|
|
262
263
|
|---|---|
|
|
@@ -328,6 +329,43 @@
|
|
|
328
329
|
| 预期 | 顺序稳定不抖动(以 `id` 为次级键) |
|
|
329
330
|
| 单测 | `flutter test`:排序键回退链、双方有消息时间时打开不改序、旧 App 更新缺失消息时间会话活跃时间的兼容例外、等值稳定性 |
|
|
330
331
|
|
|
332
|
+
### F-39 桌面宠物:动作 / 互动 / 贴边
|
|
333
|
+
|
|
334
|
+
| 项目 | 内容 |
|
|
335
|
+
|---|---|
|
|
336
|
+
| 前置 | App 已授权悬浮窗,设置开启桌面宠物;准备一条可触发完成/失败/审批的任务 |
|
|
337
|
+
| 步骤 | 观察空闲;发消息看思考/处理;触发审批看等待;完成/失败看 pulse;点头部/身体/尾巴、双击、拖拽、长按;静置 5 秒;改大小/减少动效/贴边开关 |
|
|
338
|
+
| 预期 | 动作随状态切换(思考/搜索/命令/等待/完成/出错);完成/失败 pulse 后回到最新状态;互动有台词与 1.2 秒冷却;拖拽播放悬空、松手落地并吸附最近边缘;长按打开旧面板;贴边 5 秒后只露 45% 并降低透明度/模糊,点露出部分滑出;完成/等待/出错自动探头;旧面板保留运行中会话/通知/用量/余额/打开 App/去充值;减少动效固定姿势;三档大小即时生效;App 被杀后宠物仍工作 |
|
|
339
|
+
| 变体 | Android 8 以下无 animated WebP 解码 → 静态首帧,互动与状态文字仍工作 |
|
|
340
|
+
| 单测 | `./gradlew :app:testDebugUnitTest --tests "com.dsh.remote.PetAnimationModelTest" --tests "com.dsh.remote.PetStateReducerTest"`(状态机、pulse、互动冷却、拖拽保护、空闲微动作、SSE 状态收敛) |
|
|
341
|
+
|
|
342
|
+
### F-37 外观:四套皮肤 × 明暗(v3.2.2 build 27)
|
|
343
|
+
|
|
344
|
+
| 项目 | 内容 |
|
|
345
|
+
|---|---|
|
|
346
|
+
| 前置 | 已连接;进入 `设置 → 外观` |
|
|
347
|
+
| 步骤 | 依次切「默认 / 深潜夜航 / 海盐汽水 / 鲸落星海」,每次回首页看主视觉;再在 `设置 → 显示 → 深色模式`(跟随系统 / 深色 / 浅色)各看一遍 |
|
|
348
|
+
| 预期 | ① 默认皮肤:首页仍是原 DSH logo + 「今天打算设计什么?」欢迎语,与原版逐项一致;② 深潜夜航:墨绿黑底、右上声呐环、冷光气泡、底部潮汐波、角色在右侧、主按钮薄荷绿 + 深色字;③ 海盐汽水:奶油底、可乐蓝主按钮、淡气泡与光束;④ 鲸落星海:靛蓝底、右上月亮 + 星尘星座、金色主按钮(字为深色);⑤ 每套 × 明暗共 8 组合下文字/卡片/按钮都可读(无白底白字、无黑底黑字);⑥ 发送按钮在薄荷/金色底上为深色图标 |
|
|
349
|
+
| 变体 | 系统「移除动画」开启 → 首页背景与角色静止,皮肤配色仍生效 |
|
|
350
|
+
| 单测 | `test/skin_home_test.dart`(三套皮肤首页渲染且不溢出 + 默认皮肤保持旧首页)、`test/skin_settings_test.dart`(外观卡在手机宽度不溢出、浅色模式下可切换皮肤) |
|
|
351
|
+
|
|
352
|
+
### F-38 外观:8 款 App 图标(v3.2.2 build 27)
|
|
353
|
+
|
|
354
|
+
| 项目 | 内容 |
|
|
355
|
+
|---|---|
|
|
356
|
+
| 前置 | Android 真机,已安装 build 27;进入 `设置 → 外观 → App 图标` |
|
|
357
|
+
| 步骤 | 依次点选 深海头像 → 贝壳徽章 → 月夜潮线 → 奶油贴纸 → 鲸邮票 → 霓虹灯牌 → 鲸尾剪影 → DSH 原图标;每次回桌面看图标;最后杀进程重启 App |
|
|
358
|
+
| 预期 | ① 每次切换后桌面图标随之变化(个别启动器需回桌面刷新一次,设置页已有小字提示);② 桌面上**始终只有一个** DSH Remote 图标(`activity-alias` 同一时刻只启用一个);③ 重启后保持上次选择;④ 选「DSH 原图标」时与原 `ic_launcher` 外观一致(仅重编码差异);⑤ 非 Android 平台显示「当前平台不支持切换桌面图标」 |
|
|
359
|
+
| 已知限制 | Android 13 主题图标(monochrome 层)与 `roundIcon` 未提供,圆形图标机型会退化为方形 |
|
|
360
|
+
|
|
361
|
+
### F-39 外观:图标入口迁移提示与宠物前台联动(v3.2.2 build 27)
|
|
362
|
+
|
|
363
|
+
| 项目 | 内容 |
|
|
364
|
+
|---|---|
|
|
365
|
+
| 前置 | 从 build 25 或更早版本覆盖安装 build 27;桌面上保留旧快捷方式 |
|
|
366
|
+
| 步骤 | ① 首次启动看是否弹「桌面图标入口已更新」;② 点桌面旧快捷方式;③ 从抽屉打开 App,观察桌面宠物;④ 按 Home 回桌面再切回 App;⑤ 在 `设置 → 桌面宠物` 关闭「App 在前台时收起」后重复③④ |
|
|
367
|
+
| 预期 | ① 首次启动弹一次说明,点「知道了」后不再出现(`dsh_mr_icon_migration_tip`);② 旧快捷方式可能提示未安装——符合预期,删掉从抽屉重拖即可;③ `onStart` 后桌面宠物**收起**,状态气泡与迷你面板一并收起,首页 hero 角色单独可见,宠物不再盖住「+ 新建会话」;④ `onStop` 后宠物恢复,回到贴边完全露出再走 5 秒缩进,纵向位置不被重置;⑤ 关闭该开关后第③步宠物保持显示 |
|
|
368
|
+
|
|
331
369
|
## 3. 安全测试用例
|
|
332
370
|
|
|
333
371
|
### S-01 Host 校验
|
|
@@ -362,5 +400,6 @@
|
|
|
362
400
|
## 4. 回归执行建议
|
|
363
401
|
|
|
364
402
|
- 每次修改插件源码后:`cd C:\Users\<用户>\.dsh\profiles\desktop && corepack pnpm install`(同步 file: 副本)→ 重启 DSH Desktop → 跑 `tools/e2e-check.mjs` 与 `node tools/account-usage-check.mjs` → 手机 App 冒烟(连接/发消息/通知/新建会话/图片发送/用量与额度)。
|
|
365
|
-
- 修改 App 后:`flutter analyze` → `flutter test`(新增 `test/usage_model_test.dart`;另含 `test/api_logic_test.dart`:多地址合并/轮换、回环与链路本地排除,5 用例;`test/md_link_test.dart`:链接 scheme
|
|
366
|
-
-
|
|
403
|
+
- 修改 App 后:`flutter analyze` → `flutter test`(新增 `test/usage_model_test.dart`;另含 `test/api_logic_test.dart`:多地址合并/轮换、回环与链路本地排除,5 用例;`test/md_link_test.dart`:链接 scheme 白名单;外观相关 `test/skin_home_test.dart` / `test/skin_settings_test.dart`)→ `flutter build apk --release` → 覆盖安装。
|
|
404
|
+
- 修改原生桌面宠物后:`cd dsh-mobile-app/android && ./gradlew :app:testDebugUnitTest --tests "com.dsh.remote.PetAnimationModelTest" --tests "com.dsh.remote.PetStateReducerTest" --tests "com.dsh.remote.UsagePanelModelTest"`(动画状态机 / SSE 状态收敛 / 面板模型)→ 真机冒烟(F-29~F-32 面板区块;F-36 动作、互动、贴边与大小;F-37~F-39 皮肤、图标与前台联动)。
|
|
405
|
+
- 修改原生侧(Kotlin/manifest/资源)后至少跑一次 `./gradlew :app:compileDebugKotlin`,确认 alias/资源引用无编译期断裂(图标与前台联动改动即属此类)。
|
package/docs/07-user-manual.md
CHANGED
|
@@ -30,6 +30,10 @@
|
|
|
30
30
|
**新建会话**:首页「+ 新建会话」→ 选模式(标准/PTC/极简/创造)→ 可选工作目录(跨盘浏览、可新建文件夹)→ 创建。
|
|
31
31
|
**修改默认配置**:设置 → 默认 Agent 预设 / 默认权限预设 → 选择(作用于之后新建的会话,与 PC 端同一写入通道)。
|
|
32
32
|
**深色模式**:设置 → 深色模式:跟随系统 / 深色 / 浅色。
|
|
33
|
+
**换皮肤**:设置 → 外观 → 主题皮肤,四选一:默认(原 DSH 令牌)/ 深潜夜航 / 海盐汽水 / 鲸落星海。后三套会把首页换成大肥鱼角色 + 动态背景,角色可点头部(摸头)、点身体(戳一下)、点尾巴(甩尾),长按打开迷你面板。每套皮肤都含明暗两套配色,跟随「深色模式」切换。
|
|
34
|
+
**换图标**:设置 → 外观 → App 图标,8 款任选(默认 / 深海头像 / 贝壳徽章 / 月夜潮线 / 奶油贴纸 / 鲸邮票 / 霓虹灯牌 / 鲸尾剪影)。切换后若桌面没变,回桌面刷新一次即可。升级到这版后**桌面上的旧快捷方式可能失效一次**,删掉从抽屉重拖一个即可。
|
|
35
|
+
**减少动效**:设置 → 外观 → 减少动效,把桌面宠物、首页角色与首页背景动画一起关掉;系统的「移除动画」开关优先级更高。
|
|
36
|
+
**桌面宠物**:设置 → 桌面宠物 → 开启后常驻系统上层;「App 在前台时收起」(默认开)会切回 App 时自动隐藏宠物,返回桌面再出现。
|
|
33
37
|
**外出使用**:电脑和手机都装蒲公英客户端登录同一账号、加入同一组网(电脑保持开机)。首次在 App 里手动填一次电脑的蒲公英虚拟 IP(`http://172.16.x.x:3080`),之后 App 自动切换:出门走蒲公英、回家走局域网(设置 → 电脑地址显示「共 N 个地址自动切换」)。详见 06-install-run.md §5。
|
|
34
38
|
**切换电脑地址**:设置 → 电脑地址 → 点按弹出候选地址列表(当前地址打勾),选择后自动探测可达并切换重连——回家切局域网、出门切蒲公英(v2.5.0 起)。
|
|
35
39
|
**重新配置连接**:设置 → 重新配置连接 → 回到连接页(可重新扫码/输入)。
|
package/docs/09-compatibility.md
CHANGED
|
@@ -15,11 +15,15 @@
|
|
|
15
15
|
| 桌面端 DSH(Harness) | 插件不按宿主版本号硬拦截;可用性取决于宿主提供的 Remote RPC、事件桥等接口。诊断提供 `services` / `checks`,**并自 v3.1.6 起提供宿主能力三态报告**(`checks.hostCapabilities` 的 `ok`/`drift`/`missing` 与 `checks.hostGeneration`);仍**不返回宿主版本号**,因此不要把插件版本推断为宿主能力。见 §2.6;**`0.2.1` 起进程内网关调用强制显式 Peer,由 §2.7 适配(issue #34)** |
|
|
16
16
|
| dsh-mobile-remote 插件 | **v3.1.5**;`/m/api/diagnostics` 提供 `services` / `checks`,并提供 `/m/api/account-usage` |
|
|
17
17
|
| 手机 App(Android) | v3.1.5(与插件同版本便于配对;不同版本按 API 增量兼容);Android 7.0+、64 位机型 |
|
|
18
|
-
| 字段级兼容(v3.1.0) | `reasoning`/`title` 为纯增量字段:新插件+旧 App 无影响(忽略新字段);新 App+旧插件自动回退(不渲染折叠块 /
|
|
18
|
+
| 字段级兼容(v3.1.0) | `reasoning`/`title` 为纯增量字段:新插件+旧 App 无影响(忽略新字段);新 App+旧插件自动回退(不渲染折叠块 / 桌面宠物标题兜底短码)——任意组合均可使用 |
|
|
19
19
|
| 字段级兼容(v3.1.1) | `/m/api/directories` 根视图新增 `sep`(服务端路径分隔符);新插件+旧 App 忽略该字段即可(旧 App 在 WSL 上仍按 `\` 拼接,由服务端`normalizeServerPath` 归一化兜底,浏览/建夹/建会话均可用);新 App+旧插件缺少 `sep` 时按根视图推断分隔符——任意组合均可使用 |
|
|
20
20
|
| 字段级兼容(v3.1.3) | `/m/api/diagnostics` 增加审批策略与远程事件桥状态(`checks.approvalMode` / `checks.remoteEvents`);旧版 App 忽略新增字段,不受影响 |
|
|
21
21
|
| 用量与额度(v3.1.5) | 新插件+新 App 通过 `/m/api/account-usage` 显示 DeepSeek/Codex/OpenCode Go;旧 App 忽略新端点。新 App+旧插件进入该入口会显示“电脑端插件版本过旧”,不影响其它功能;升级插件后无需重新配置凭据 |
|
|
22
|
-
|
|
|
22
|
+
| 桌面宠物面板用量与额度(沿用 ADR 0008) | 新 App+新插件:桌面宠物面板展开时按需展示三来源区块(金额行文字 + 配额行细条/颜色),整块可点进详情页。新 App+旧插件:区块整体降级为原有单行余额(点击仍=去充值),其余面板功能不受影响;旧 App 忽略新端点。无新增服务端契约 |
|
|
23
|
+
| 外观:皮肤与图标(v3.2.2 build 27) | **纯 App 侧,无服务端契约变化**。皮肤是 `ThemeExtension` 令牌,旧插件照常工作;默认皮肤令牌与旧版逐值一致。App 图标用 8 个 `activity-alias`,只在 Android 生效(其它平台隐藏入口) |
|
|
24
|
+
| 图标入口迁移(build 27) | 启动入口从 `com.dsh.remote.MainActivity` 改为 `com.dsh.remote.IconDefault`:**升级后桌面旧快捷方式可能失效一次**(组件名变化,桌面固定的是组件名),首次启动有一次性说明引导重拖。抽屉入口不受影响 |
|
|
25
|
+
| 桌面宠物前台联动(build 27) | `MainActivity.onStart/onStop` → 服务根视图 `GONE`:App 在前台时收起宠物与状态气泡/迷你面板;`设置 → 桌面宠物 → App 在前台时收起` 可关。旧系统不涉及(仅原生层行为) |
|
|
26
|
+
| 图标主题化与圆形图标 | **已知缺口**:`mipmap-anydpi-v26` 未提供 `<monochrome>` 层,Android 13 主题图标不生效;未声明 `android:roundIcon`,圆形图标机型退化为方形。计划在后续版本补齐,不影响功能 |
|
|
23
27
|
| Flutter 构建环境 | Flutter 3.35+(Dart SDK ^3.13) |
|
|
24
28
|
|
|
25
29
|
**声明式兼容范围(`peerDependencies`)**:`@deepseek-ai/dsh-{credentials,llm,sandbox-policy,scope}` 声明 `>=0.1.0-rc.6 <0.3.0`。宿主 `@deepseek-ai/dsh-app-boot` 的 `evaluatePluginCompatibility()` 用 `semver.satisfies(runtimeVersion, range, { includePrerelease: true })` 判定(**prerelease 参与比较**),因此该范围同时覆盖 `0.1.x`、`0.2.0-rc.*` 与 `0.2.x` 正式版,而 `0.3.0+` 会被判为不兼容。上界**只用于声明可安装边界**,功能路径仍按能力探测(ADR 0017),不按版本号做功能门禁。
|
package/lib/index.js
CHANGED
|
@@ -697,6 +697,17 @@ const EVENT_TIMELINE_CAPABILITIES = {
|
|
|
697
697
|
callCorrelation: true,
|
|
698
698
|
};
|
|
699
699
|
|
|
700
|
+
/**
|
|
701
|
+
* 普通文件下载能力:Range / HEAD / ETag(v3.2.3)。
|
|
702
|
+
*
|
|
703
|
+
* App 用它决定能不能"只取前 256 KiB 预览"和"大文件断点续传";不存在该能力位时
|
|
704
|
+
* 自动退回整文件 GET(旧插件行为)。
|
|
705
|
+
*/
|
|
706
|
+
const FILE_RANGE_CAPABILITIES = {
|
|
707
|
+
version: 1,
|
|
708
|
+
supported: true,
|
|
709
|
+
};
|
|
710
|
+
|
|
700
711
|
/**
|
|
701
712
|
* 轮次大纲能力(issue #25 二期):插件把宿主的 `turnOutline` 投影透出为移动端契约,
|
|
702
713
|
* 使刻度轨能画出「整个会话的全部轮次」,而不是只有已加载窗口。
|
|
@@ -4807,7 +4818,7 @@ export function apply(ctx, config, runtime = {}) {
|
|
|
4807
4818
|
"connection": "keep-alive",
|
|
4808
4819
|
});
|
|
4809
4820
|
res.write(": connected\n\n");
|
|
4810
|
-
res.write(`data: ${JSON.stringify({ type: "hello", serverTime: Date.now(), capabilities: { eventTimeline: EVENT_TIMELINE_CAPABILITIES, turnOutline: turnOutlineCapabilityOf(ctx) } })}\n\n`);
|
|
4821
|
+
res.write(`data: ${JSON.stringify({ type: "hello", serverTime: Date.now(), capabilities: { eventTimeline: EVENT_TIMELINE_CAPABILITIES, turnOutline: turnOutlineCapabilityOf(ctx), fileRange: FILE_RANGE_CAPABILITIES, hold: { version: 1, onlineMs: Math.max(0, Number(config.pendingTimeoutMs ?? 120_000) || 0), offlineMs: Math.max(0, Number(config.offlineHoldMs ?? 300_000) || 0) } } })}\n\n`);
|
|
4811
4822
|
connections.add(res);
|
|
4812
4823
|
// 补发断线期间挂起的问询/审批(与 PC 端 GUI 连接时的 pending 回放一致;超 TTL 的僵尸帧先清掉)
|
|
4813
4824
|
for (const [key, f] of pendingFrames) {
|
|
@@ -5547,6 +5558,14 @@ export function apply(ctx, config, runtime = {}) {
|
|
|
5547
5558
|
// 宿主是否**真的**挂了 `turnOutline` 投影单元、且观察接口可用,决定声明是
|
|
5548
5559
|
// "支持"还是"能力缺失"——App 先读声明再决定是否渲染未加载刻度(不按宿主版本推断)。
|
|
5549
5560
|
turnOutline: turnOutlineCapabilityOf(ctx),
|
|
5561
|
+
// v3.2.3:普通文件下载支持 Range/HEAD/ETag(大文件续传 + 前缀预览)。
|
|
5562
|
+
fileRange: FILE_RANGE_CAPABILITIES,
|
|
5563
|
+
// v3.2.4(issue #55):手机端待办等待上限(仅供显示剩余时间,结算仍以服务端为准)。
|
|
5564
|
+
hold: {
|
|
5565
|
+
version: 1,
|
|
5566
|
+
onlineMs: Math.max(0, Number(config.pendingTimeoutMs ?? 120_000) || 0),
|
|
5567
|
+
offlineMs: Math.max(0, Number(config.offlineHoldMs ?? 300_000) || 0),
|
|
5568
|
+
},
|
|
5550
5569
|
},
|
|
5551
5570
|
alignment,
|
|
5552
5571
|
pluginUpdate: publicUpdateStatus(req),
|
|
@@ -6164,6 +6183,49 @@ export function apply(ctx, config, runtime = {}) {
|
|
|
6164
6183
|
}
|
|
6165
6184
|
}
|
|
6166
6185
|
if (!selected?.provider || !selected?.model) return error(res, 400, "no-model-available", "当前部署未配置默认模型,无法创建会话");
|
|
6186
|
+
// v3.2.3(issue #50):建会话前先确认 cwd 有归属工作区。
|
|
6187
|
+
// 内核工作区实体(WorkspaceEntity.attachSession)要求会话 header.cwd 与工作区根**完全相等**,
|
|
6188
|
+
// 而 App 的目录选择器可以选到任意目录(含新建文件夹)。cwd 不属于任何已注册工作区时,
|
|
6189
|
+
// 该会话就成了"孤儿":本次 attach 静默失败,下次 dsh 启动 bootstrap 再 attach 会抛错
|
|
6190
|
+
// → 整个进程 fatal load failure(实测可让 web/SSH/IM 全部入口一起下线,反代侧 502)。
|
|
6191
|
+
// 因此:已注册则复用,未注册则注册成新工作区,并把 cwd 归一为工作区规范路径。
|
|
6192
|
+
let sessionWorkspace = null;
|
|
6193
|
+
const workspaceRegistry = ctx.get("workspaceRegistry");
|
|
6194
|
+
if (workspaceRegistry && cwd) {
|
|
6195
|
+
let exact = null;
|
|
6196
|
+
try {
|
|
6197
|
+
exact = (await workspaceRegistry.resolveByPath?.(cwd)) ?? null;
|
|
6198
|
+
} catch {
|
|
6199
|
+
exact = null; // 路径不可解析(不存在/非目录)→ 交给下面的 create 给出明确错误
|
|
6200
|
+
}
|
|
6201
|
+
if (!exact && typeof workspaceRegistry.create === "function") {
|
|
6202
|
+
try {
|
|
6203
|
+
exact = await workspaceRegistry.create(cwd);
|
|
6204
|
+
} catch {
|
|
6205
|
+
return error(res, 400, "cwd-not-a-directory", `工作目录不可用:${cwd}`);
|
|
6206
|
+
}
|
|
6207
|
+
}
|
|
6208
|
+
if (exact?.path) {
|
|
6209
|
+
// realpath 归一后的工作区路径才是 attach 能匹配的 cwd(符号链接/大小写差异会再次失配)
|
|
6210
|
+
sessionWorkspace = exact;
|
|
6211
|
+
cwd = exact.path;
|
|
6212
|
+
} else if (typeof workspaceRegistry.create !== "function") {
|
|
6213
|
+
// 旧宿主未暴露 create:保留原「逐级向上找最近已注册工作区」的尽力而为路径
|
|
6214
|
+
// (不改写 cwd,attach 仍可能被内核拒绝,但至少不阻断建会话)
|
|
6215
|
+
const sepChar = cwd.includes("\\") ? "\\" : "/";
|
|
6216
|
+
let p = cwd;
|
|
6217
|
+
while (p.includes(sepChar)) {
|
|
6218
|
+
p = p.slice(0, p.lastIndexOf(sepChar));
|
|
6219
|
+
if (!p || p.length <= 2) break; // 到盘符根为止
|
|
6220
|
+
try {
|
|
6221
|
+
sessionWorkspace = (await workspaceRegistry.resolveByPath?.(p)) ?? null;
|
|
6222
|
+
if (sessionWorkspace) break;
|
|
6223
|
+
} catch {
|
|
6224
|
+
break;
|
|
6225
|
+
}
|
|
6226
|
+
}
|
|
6227
|
+
}
|
|
6228
|
+
}
|
|
6167
6229
|
const sessionId = randomUUID();
|
|
6168
6230
|
// v2.7.2 review:danger-full-access 确认校验必须在建会话之前(否则失败留下孤儿会话)
|
|
6169
6231
|
if (typeof body.permissionPreset === "string" && body.permissionPreset === "danger-full-access" && body.confirmDanger !== true) {
|
|
@@ -6187,31 +6249,15 @@ export function apply(ctx, config, runtime = {}) {
|
|
|
6187
6249
|
try { await handle?.dispose?.(); } catch { /* 保留原始配置错误 */ }
|
|
6188
6250
|
return error(res, ...rpcError(err, "session-configuration-failed"));
|
|
6189
6251
|
}
|
|
6190
|
-
//
|
|
6191
|
-
|
|
6192
|
-
|
|
6193
|
-
|
|
6194
|
-
|
|
6195
|
-
|
|
6196
|
-
|
|
6197
|
-
|
|
6198
|
-
const sep = cwd.includes("\\") ? "\\" : "/";
|
|
6199
|
-
let p = cwd;
|
|
6200
|
-
while (p.includes(sep)) {
|
|
6201
|
-
p = p.slice(0, p.lastIndexOf(sep));
|
|
6202
|
-
if (!p || p.length <= 2) break; // 到盘符根为止
|
|
6203
|
-
try {
|
|
6204
|
-
workspace = await registry.resolveByPath?.(p);
|
|
6205
|
-
if (workspace) break;
|
|
6206
|
-
} catch {
|
|
6207
|
-
break;
|
|
6208
|
-
}
|
|
6209
|
-
}
|
|
6210
|
-
}
|
|
6211
|
-
workspace?.attachSession(sessionId);
|
|
6252
|
+
// 归属工作区:建会话前已解析/注册(见上方 v3.2.3 说明),这里只做 attach。
|
|
6253
|
+
// v3.2.3(issue #50):原实现是 `workspace?.attachSession(sessionId)` 且未 await——
|
|
6254
|
+
// 未注册 cwd 走到"向上找祖先工作区"分支时必然抛错,既逃过 try/catch 又留下孤儿会话。
|
|
6255
|
+
if (sessionWorkspace) {
|
|
6256
|
+
try {
|
|
6257
|
+
await sessionWorkspace.attachSession(sessionId);
|
|
6258
|
+
} catch (err) {
|
|
6259
|
+
ctx.logger.warn?.(`mobile-remote: 会话 ${sessionId} 未能附着到工作区 ${sessionWorkspace.path}:${err?.message ?? err}`);
|
|
6212
6260
|
}
|
|
6213
|
-
} catch {
|
|
6214
|
-
// attach 失败不影响会话本身
|
|
6215
6261
|
}
|
|
6216
6262
|
sendJson(res, 200, { ok: true, sessionId, agentId: handle.agent.id, preset });
|
|
6217
6263
|
} catch (err) {
|
|
@@ -7820,7 +7866,23 @@ export function apply(ctx, config, runtime = {}) {
|
|
|
7820
7866
|
.filter((entry) => entry.isFile() && !entry.name.startsWith("."))
|
|
7821
7867
|
.map((entry) => entry.name)
|
|
7822
7868
|
.sort((a, b) => a.localeCompare(b, "zh-CN"));
|
|
7823
|
-
|
|
7869
|
+
// v3.2.3:文件元数据(大小/修改时间/MIME);files 保持纯字符串以兼容旧 App。
|
|
7870
|
+
const fileEntries = entries
|
|
7871
|
+
.filter((entry) => entry.isFile() && !entry.name.startsWith("."))
|
|
7872
|
+
.map((entry) => {
|
|
7873
|
+
let size = null;
|
|
7874
|
+
let mtimeMs = null;
|
|
7875
|
+
try {
|
|
7876
|
+
const st = statSync(join(base, entry.name));
|
|
7877
|
+
size = st.size;
|
|
7878
|
+
mtimeMs = st.mtimeMs;
|
|
7879
|
+
} catch {
|
|
7880
|
+
// 读不到 stat 时保留 null,不影响列表本身
|
|
7881
|
+
}
|
|
7882
|
+
return { name: entry.name, size, mtimeMs, mime: fileMimeOf(entry.name) };
|
|
7883
|
+
})
|
|
7884
|
+
.sort((a, b) => a.name.localeCompare(b.name, "zh-CN"));
|
|
7885
|
+
sendJson(res, 200, { ok: true, path: base, dirs, files, fileEntries });
|
|
7824
7886
|
} catch (err) {
|
|
7825
7887
|
return error(res, 400, "directory-unreadable", failureDetail(err)); // v3.1.5 S7:不再回主机绝对路径
|
|
7826
7888
|
}
|
|
@@ -7830,7 +7892,42 @@ export function apply(ctx, config, runtime = {}) {
|
|
|
7830
7892
|
// ── 文件传输(v3.1.2:B站 csborbbnc 反馈「下载上传」) ──
|
|
7831
7893
|
// 与目录选择器同信任模型(口令鉴权 + 现有限流;路径由手机显式指定),
|
|
7832
7894
|
// 上传默认落到目标会话的工作目录,下载任意可读文件路径。
|
|
7833
|
-
|
|
7895
|
+
/**
|
|
7896
|
+
* 解析 `Range: bytes=...`(v3.2.3)。
|
|
7897
|
+
*
|
|
7898
|
+
* 只支持单区间(多区间用不上,还容易被拿来放大响应);越界/格式错返回
|
|
7899
|
+
* `{ invalid: true }`,调用方回 416 并带 `Content-Range: bytes * /size`。
|
|
7900
|
+
*/
|
|
7901
|
+
const parseByteRange = (value, size) => {
|
|
7902
|
+
const text = String(value ?? "").trim();
|
|
7903
|
+
if (text === "") return null;
|
|
7904
|
+
const match = /^bytes=(\d*)-(\d*)$/.exec(text);
|
|
7905
|
+
if (!match) return { invalid: true };
|
|
7906
|
+
const [, startText, endText] = match;
|
|
7907
|
+
if (startText === "" && endText === "") return { invalid: true };
|
|
7908
|
+
if (startText === "") {
|
|
7909
|
+
const suffix = Number(endText);
|
|
7910
|
+
if (!Number.isSafeInteger(suffix) || suffix <= 0) return { invalid: true };
|
|
7911
|
+
return { start: Math.max(0, size - suffix), end: size - 1 };
|
|
7912
|
+
}
|
|
7913
|
+
const start = Number(startText);
|
|
7914
|
+
if (!Number.isSafeInteger(start) || start >= size) return { invalid: true };
|
|
7915
|
+
const end = endText === "" ? size - 1 : Number(endText);
|
|
7916
|
+
if (!Number.isSafeInteger(end) || end < start) return { invalid: true };
|
|
7917
|
+
return { start, end: Math.min(end, size - 1) };
|
|
7918
|
+
};
|
|
7919
|
+
/** `If-None-Match` 是否命中当前 ETag(支持 `*` 与逗号列表)。 */
|
|
7920
|
+
const ifNoneMatch = (req, etag) => {
|
|
7921
|
+
if (!etag) return false;
|
|
7922
|
+
const raw = requestHeaderText(req, "if-none-match");
|
|
7923
|
+
if (raw === "") return false;
|
|
7924
|
+
return raw
|
|
7925
|
+
.split(",")
|
|
7926
|
+
.map((part) => part.trim())
|
|
7927
|
+
.some((part) => part === "*" || part === etag);
|
|
7928
|
+
};
|
|
7929
|
+
|
|
7930
|
+
if (rest === "/files" && (method === "GET" || method === "HEAD")) {
|
|
7834
7931
|
const path = url.searchParams.get("path");
|
|
7835
7932
|
if (!path || path === "") return error(res, 400, "bad-request", "missing path");
|
|
7836
7933
|
const target = resolve(normalizeServerPath(path));
|
|
@@ -7838,20 +7935,65 @@ export function apply(ctx, config, runtime = {}) {
|
|
|
7838
7935
|
const st = statSync(target);
|
|
7839
7936
|
if (!st.isFile()) return error(res, 400, "not-a-file", "path is not a file");
|
|
7840
7937
|
const name = basename(target);
|
|
7938
|
+
// ETag 由「大小 + mtime」组成:文件被改写就变,够用且不用读内容。
|
|
7939
|
+
const etag = `"${st.size.toString(16)}-${Math.floor(st.mtimeMs).toString(16)}"`;
|
|
7940
|
+
const lastModified = new Date(st.mtimeMs).toUTCString();
|
|
7841
7941
|
guardRes(res);
|
|
7842
|
-
|
|
7942
|
+
if (ifNoneMatch(req, etag)) {
|
|
7943
|
+
res.writeHead(304, { etag, "cache-control": "no-store", connection: "close" });
|
|
7944
|
+
res.end();
|
|
7945
|
+
return;
|
|
7946
|
+
}
|
|
7947
|
+
const range = parseByteRange(requestHeaderText(req, "range"), st.size);
|
|
7948
|
+
if (range && range.invalid) {
|
|
7949
|
+
res.writeHead(416, {
|
|
7950
|
+
"accept-ranges": "bytes",
|
|
7951
|
+
"content-range": `bytes */${st.size}`,
|
|
7952
|
+
"content-length": "0",
|
|
7953
|
+
etag,
|
|
7954
|
+
"cache-control": "no-store",
|
|
7955
|
+
connection: "close",
|
|
7956
|
+
});
|
|
7957
|
+
res.end();
|
|
7958
|
+
return;
|
|
7959
|
+
}
|
|
7960
|
+
const start = range ? range.start : 0;
|
|
7961
|
+
const end = range ? range.end : st.size - 1;
|
|
7962
|
+
const length = st.size === 0 ? 0 : end - start + 1;
|
|
7963
|
+
const headers = {
|
|
7843
7964
|
"content-type": fileMimeOf(name),
|
|
7844
|
-
"content-length":
|
|
7965
|
+
"content-length": String(length),
|
|
7845
7966
|
"content-disposition": `attachment; filename*=UTF-8''${encodeURIComponent(name)}`,
|
|
7846
7967
|
"cache-control": "no-store",
|
|
7847
|
-
|
|
7848
|
-
|
|
7849
|
-
|
|
7850
|
-
//
|
|
7851
|
-
|
|
7852
|
-
"
|
|
7968
|
+
"accept-ranges": "bytes",
|
|
7969
|
+
etag,
|
|
7970
|
+
"last-modified": lastModified,
|
|
7971
|
+
// v3.1.5+39:取完即断(半关连接复用会让手机侧等满超时)
|
|
7972
|
+
connection: "close",
|
|
7973
|
+
...(range ? { "content-range": `bytes ${start}-${end}/${st.size}` } : {}),
|
|
7974
|
+
};
|
|
7975
|
+
res.writeHead(range ? 206 : 200, headers);
|
|
7976
|
+
if (method === "HEAD" || st.size === 0) {
|
|
7977
|
+
res.end();
|
|
7978
|
+
return;
|
|
7979
|
+
}
|
|
7980
|
+
const source = createReadStream(target, { start, end });
|
|
7981
|
+
// 客户端取消/断开时销毁文件流,避免 fd 泄漏。
|
|
7982
|
+
res.on("close", () => {
|
|
7983
|
+
try {
|
|
7984
|
+
source.destroy();
|
|
7985
|
+
} catch {
|
|
7986
|
+
// 已销毁
|
|
7987
|
+
}
|
|
7988
|
+
});
|
|
7989
|
+
source.on("error", () => {
|
|
7990
|
+
try {
|
|
7991
|
+
res.destroy();
|
|
7992
|
+
} catch {
|
|
7993
|
+
// 已销毁
|
|
7994
|
+
}
|
|
7853
7995
|
});
|
|
7854
|
-
|
|
7996
|
+
source.pipe(res);
|
|
7855
7997
|
} catch (err) {
|
|
7856
7998
|
return error(res, 404, "file-not-found", failureDetail(err)); // v3.1.5 S7:不再回主机绝对路径(含进程 cwd)
|
|
7857
7999
|
}
|
|
@@ -8294,8 +8436,8 @@ export function apply(ctx, config, runtime = {}) {
|
|
|
8294
8436
|
// mobile —— v3.1.2 行为:手机在线即由手机独占应答,离线 next() 交桌面 GUI。
|
|
8295
8437
|
// desktop —— 一律 next() 交桌面 GUI(手机不弹审批/问询卡),适合常驻电脑前。
|
|
8296
8438
|
const approvalMode = config.approvalMode ?? "both";
|
|
8297
|
-
/** 手机待办超时 fail-close
|
|
8298
|
-
const PENDING_TIMEOUT_MS = 120_000;
|
|
8439
|
+
/** 手机待办超时 fail-close(默认沿用 120s;v3.2.4 / issue #55 起可配)。 */
|
|
8440
|
+
const PENDING_TIMEOUT_MS = Math.max(0, Number(config.pendingTimeoutMs ?? 120_000) || 0);
|
|
8299
8441
|
/** 手机离线时仍接管审批/问询的等待上限(修复:离线交桌面导致内核悬挂)。 */
|
|
8300
8442
|
const OFFLINE_HOLD_MS = Math.max(0, Number(config.offlineHoldMs ?? 300_000) || 0);
|
|
8301
8443
|
/** 审批/问询推送的宽限期(0 = 立即推)。 */
|
|
@@ -8400,7 +8542,7 @@ export function apply(ctx, config, runtime = {}) {
|
|
|
8400
8542
|
dropAskReplay("a", entry);
|
|
8401
8543
|
if (entry.timer) { clearTimeout(entry.timer); entry.timer = null; }
|
|
8402
8544
|
ctx.logger.info?.(`mobile-remote: approval/request 结算(${entry.phoneId} → ${outcome})`);
|
|
8403
|
-
broadcast({ type: "mobile/frame", frame: { type: "approval/resolved", approvalId: entry.phoneId, rpcId: entry.rpcId } });
|
|
8545
|
+
broadcast({ type: "mobile/frame", frame: { type: "approval/resolved", approvalId: entry.phoneId, rpcId: entry.rpcId, sessionId: entry.sessionId, outcome } });
|
|
8404
8546
|
entry.settle(outcome);
|
|
8405
8547
|
return true;
|
|
8406
8548
|
};
|
|
@@ -8414,7 +8556,7 @@ export function apply(ctx, config, runtime = {}) {
|
|
|
8414
8556
|
dropAskReplay("q", entry);
|
|
8415
8557
|
if (entry.timer) { clearTimeout(entry.timer); entry.timer = null; }
|
|
8416
8558
|
ctx.logger.info?.(`mobile-remote: user-questions/request 结算(${entry.phoneId}${reject ? ",拒绝" : ""})`);
|
|
8417
|
-
broadcast({ type: "mobile/frame", frame: { type: "question/resolved", questionRpcId: entry.rpcId, questionId: entry.phoneId } });
|
|
8559
|
+
broadcast({ type: "mobile/frame", frame: { type: "question/resolved", questionRpcId: entry.rpcId, questionId: entry.phoneId, sessionId: entry.sessionId } });
|
|
8418
8560
|
if (reject) {
|
|
8419
8561
|
// 本地接管条目一定有 reject(见 holdQuestion);远端条目由 postEventsRejection 兜底
|
|
8420
8562
|
if (typeof entry.reject === "function") entry.reject(value);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@marktrue/dsh-mobile-remote",
|
|
3
|
-
"version": "3.2.
|
|
3
|
+
"version": "3.2.3-alpha.1",
|
|
4
4
|
"description": "DSH Mobile:把 DeepSeek Harness 变成随身控制台——手机远程发消息派活、看进度、收通知、审批决策、管理会话;配套 Flutter App(当前 Android 端,iOS 未开发)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"qrcode"
|
|
53
53
|
],
|
|
54
54
|
"optionalDependencies": {
|
|
55
|
-
"@marktrue/dsh-mobile-remote-app": "npm:@marktrue/dsh-mobile-remote@3.2.
|
|
55
|
+
"@marktrue/dsh-mobile-remote-app": "npm:@marktrue/dsh-mobile-remote@3.2.3-alpha.1-app"
|
|
56
56
|
},
|
|
57
57
|
"peerDependencies": {
|
|
58
58
|
"@deepseek-ai/dsh-credentials": ">=0.1.0-rc.6 <0.3.0",
|