@marktrue/dsh-mobile-remote 3.2.3-rc.1 → 3.2.4-alpha.1-app

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (353) hide show
  1. package/DSH-Remote-v3.2.4.apk +4 -0
  2. package/README.md +6 -254
  3. package/manifest.json +21 -0
  4. package/package.json +12 -65
  5. package/CHANGELOG.md +0 -1362
  6. package/CONTRIBUTING.md +0 -59
  7. package/FAQ.md +0 -111
  8. package/cordis.patch.yml +0 -14
  9. package/docs/00-/345/274/200/345/217/221/346/200/273/347/272/262.md +0 -125
  10. package/docs/01-PRD.md +0 -148
  11. package/docs/02-architecture.md +0 -249
  12. package/docs/03-api.md +0 -930
  13. package/docs/04-security.md +0 -100
  14. package/docs/05-test-cases.md +0 -405
  15. package/docs/06-install-run.md +0 -372
  16. package/docs/07-user-manual.md +0 -70
  17. package/docs/08-/346/211/251/345/261/225/345/274/200/345/217/221/346/214/207/345/215/227.md +0 -86
  18. package/docs/09-compatibility.md +0 -227
  19. package/docs/10-release.md +0 -929
  20. package/docs/screenshots/01-home.png +0 -0
  21. package/docs/screenshots/02-notifications.png +0 -0
  22. package/docs/screenshots/03-sessions.png +0 -0
  23. package/docs/screenshots/04-chat.png +0 -0
  24. package/docs/screenshots/05-settings.png +0 -0
  25. package/docs/screenshots/06-new-session.png +0 -0
  26. package/docs/screenshots/pgy-appstore.jpg +0 -0
  27. package/docs/screenshots/pgy-mobile-app.jpg +0 -0
  28. package/docs/screenshots/pgy-pc-client.jpg +0 -0
  29. package/docs/spec-app-auto-update.md +0 -143
  30. package/lib/account-usage.js +0 -145
  31. package/lib/app-alignment.js +0 -112
  32. package/lib/app-carrier-store.js +0 -1972
  33. package/lib/app-carrier.js +0 -609
  34. package/lib/client.js +0 -1174
  35. package/lib/desktop-form.js +0 -738
  36. package/lib/endpoint-registry.js +0 -165
  37. package/lib/git-read-bundled-provider.js +0 -255
  38. package/lib/git-read-service.js +0 -322
  39. package/lib/index.js +0 -9609
  40. package/lib/npm-update-source.js +0 -226
  41. package/lib/plugin-content-digest.js +0 -80
  42. package/lib/plugin-self-update.js +0 -997
  43. package/lib/plugin-update-controller.js +0 -274
  44. package/lib/plugin-update-log.js +0 -164
  45. package/lib/plugin-update-transaction.js +0 -322
  46. package/lib/plugin-update.js +0 -309
  47. package/lib/runtime-dependency-snapshot.js +0 -345
  48. package/lib/session-title-cache.js +0 -123
  49. package/lib/session-title-refresh.js +0 -261
  50. package/lib/tool-process.js +0 -20
  51. package/lib/update-fetch.js +0 -24
  52. package/lib/update-lock-arbiter.js +0 -170
  53. package/lib/update-supervisor.js +0 -346
  54. package/node_modules/ansi-regex/index.d.ts +0 -37
  55. package/node_modules/ansi-regex/index.js +0 -10
  56. package/node_modules/ansi-regex/license +0 -9
  57. package/node_modules/ansi-regex/package.json +0 -55
  58. package/node_modules/ansi-regex/readme.md +0 -78
  59. package/node_modules/ansi-styles/index.d.ts +0 -345
  60. package/node_modules/ansi-styles/index.js +0 -163
  61. package/node_modules/ansi-styles/license +0 -9
  62. package/node_modules/ansi-styles/package.json +0 -56
  63. package/node_modules/ansi-styles/readme.md +0 -152
  64. package/node_modules/camelcase/index.d.ts +0 -63
  65. package/node_modules/camelcase/index.js +0 -76
  66. package/node_modules/camelcase/license +0 -9
  67. package/node_modules/camelcase/package.json +0 -43
  68. package/node_modules/camelcase/readme.md +0 -99
  69. package/node_modules/cliui/CHANGELOG.md +0 -76
  70. package/node_modules/cliui/LICENSE.txt +0 -14
  71. package/node_modules/cliui/README.md +0 -115
  72. package/node_modules/cliui/index.js +0 -354
  73. package/node_modules/cliui/package.json +0 -65
  74. package/node_modules/color-convert/CHANGELOG.md +0 -54
  75. package/node_modules/color-convert/LICENSE +0 -21
  76. package/node_modules/color-convert/README.md +0 -68
  77. package/node_modules/color-convert/conversions.js +0 -839
  78. package/node_modules/color-convert/index.js +0 -81
  79. package/node_modules/color-convert/package.json +0 -48
  80. package/node_modules/color-convert/route.js +0 -97
  81. package/node_modules/color-name/LICENSE +0 -8
  82. package/node_modules/color-name/README.md +0 -11
  83. package/node_modules/color-name/index.js +0 -152
  84. package/node_modules/color-name/package.json +0 -28
  85. package/node_modules/decamelize/index.js +0 -13
  86. package/node_modules/decamelize/license +0 -21
  87. package/node_modules/decamelize/package.json +0 -38
  88. package/node_modules/decamelize/readme.md +0 -48
  89. package/node_modules/dijkstrajs/.travis.yml +0 -4
  90. package/node_modules/dijkstrajs/CONTRIBUTING.md +0 -8
  91. package/node_modules/dijkstrajs/LICENSE.md +0 -19
  92. package/node_modules/dijkstrajs/README.md +0 -18
  93. package/node_modules/dijkstrajs/dijkstra.js +0 -165
  94. package/node_modules/dijkstrajs/package.json +0 -60
  95. package/node_modules/dijkstrajs/test/dijkstra.test.js +0 -96
  96. package/node_modules/emoji-regex/LICENSE-MIT.txt +0 -20
  97. package/node_modules/emoji-regex/README.md +0 -73
  98. package/node_modules/emoji-regex/es2015/index.js +0 -6
  99. package/node_modules/emoji-regex/es2015/text.js +0 -6
  100. package/node_modules/emoji-regex/index.d.ts +0 -23
  101. package/node_modules/emoji-regex/index.js +0 -6
  102. package/node_modules/emoji-regex/package.json +0 -50
  103. package/node_modules/emoji-regex/text.js +0 -6
  104. package/node_modules/find-up/index.d.ts +0 -137
  105. package/node_modules/find-up/index.js +0 -89
  106. package/node_modules/find-up/license +0 -9
  107. package/node_modules/find-up/package.json +0 -53
  108. package/node_modules/find-up/readme.md +0 -156
  109. package/node_modules/get-caller-file/LICENSE.md +0 -6
  110. package/node_modules/get-caller-file/README.md +0 -41
  111. package/node_modules/get-caller-file/index.d.ts +0 -2
  112. package/node_modules/get-caller-file/index.js +0 -22
  113. package/node_modules/get-caller-file/index.js.map +0 -1
  114. package/node_modules/get-caller-file/package.json +0 -42
  115. package/node_modules/is-fullwidth-code-point/index.d.ts +0 -17
  116. package/node_modules/is-fullwidth-code-point/index.js +0 -50
  117. package/node_modules/is-fullwidth-code-point/license +0 -9
  118. package/node_modules/is-fullwidth-code-point/package.json +0 -42
  119. package/node_modules/is-fullwidth-code-point/readme.md +0 -39
  120. package/node_modules/locate-path/index.d.ts +0 -83
  121. package/node_modules/locate-path/index.js +0 -65
  122. package/node_modules/locate-path/license +0 -9
  123. package/node_modules/locate-path/package.json +0 -45
  124. package/node_modules/locate-path/readme.md +0 -122
  125. package/node_modules/p-limit/index.d.ts +0 -38
  126. package/node_modules/p-limit/index.js +0 -57
  127. package/node_modules/p-limit/license +0 -9
  128. package/node_modules/p-limit/package.json +0 -52
  129. package/node_modules/p-limit/readme.md +0 -101
  130. package/node_modules/p-locate/index.d.ts +0 -64
  131. package/node_modules/p-locate/index.js +0 -52
  132. package/node_modules/p-locate/license +0 -9
  133. package/node_modules/p-locate/package.json +0 -53
  134. package/node_modules/p-locate/readme.md +0 -90
  135. package/node_modules/p-try/index.d.ts +0 -39
  136. package/node_modules/p-try/index.js +0 -9
  137. package/node_modules/p-try/license +0 -9
  138. package/node_modules/p-try/package.json +0 -42
  139. package/node_modules/p-try/readme.md +0 -58
  140. package/node_modules/path-exists/index.d.ts +0 -28
  141. package/node_modules/path-exists/index.js +0 -23
  142. package/node_modules/path-exists/license +0 -9
  143. package/node_modules/path-exists/package.json +0 -39
  144. package/node_modules/path-exists/readme.md +0 -52
  145. package/node_modules/pngjs/.eslintignore +0 -2
  146. package/node_modules/pngjs/.eslintrc.json +0 -19
  147. package/node_modules/pngjs/.prettierignore +0 -3
  148. package/node_modules/pngjs/LICENSE +0 -20
  149. package/node_modules/pngjs/README.md +0 -429
  150. package/node_modules/pngjs/browser.js +0 -16096
  151. package/node_modules/pngjs/coverage/lcov-report/base.css +0 -354
  152. package/node_modules/pngjs/coverage/lcov-report/bitmapper.js.html +0 -881
  153. package/node_modules/pngjs/coverage/lcov-report/bitpacker.js.html +0 -554
  154. package/node_modules/pngjs/coverage/lcov-report/block-navigation.js +0 -77
  155. package/node_modules/pngjs/coverage/lcov-report/chunkstream.js.html +0 -647
  156. package/node_modules/pngjs/coverage/lcov-report/constants.js.html +0 -176
  157. package/node_modules/pngjs/coverage/lcov-report/crc.js.html +0 -200
  158. package/node_modules/pngjs/coverage/lcov-report/favicon.png +0 -0
  159. package/node_modules/pngjs/coverage/lcov-report/filter-pack.js.html +0 -593
  160. package/node_modules/pngjs/coverage/lcov-report/filter-parse-async.js.html +0 -152
  161. package/node_modules/pngjs/coverage/lcov-report/filter-parse-sync.js.html +0 -143
  162. package/node_modules/pngjs/coverage/lcov-report/filter-parse.js.html +0 -611
  163. package/node_modules/pngjs/coverage/lcov-report/format-normaliser.js.html +0 -359
  164. package/node_modules/pngjs/coverage/lcov-report/index.html +0 -426
  165. package/node_modules/pngjs/coverage/lcov-report/interlace.js.html +0 -365
  166. package/node_modules/pngjs/coverage/lcov-report/packer-async.js.html +0 -230
  167. package/node_modules/pngjs/coverage/lcov-report/packer-sync.js.html +0 -248
  168. package/node_modules/pngjs/coverage/lcov-report/packer.js.html +0 -467
  169. package/node_modules/pngjs/coverage/lcov-report/paeth-predictor.js.html +0 -128
  170. package/node_modules/pngjs/coverage/lcov-report/parser-async.js.html +0 -575
  171. package/node_modules/pngjs/coverage/lcov-report/parser-sync.js.html +0 -404
  172. package/node_modules/pngjs/coverage/lcov-report/parser.js.html +0 -950
  173. package/node_modules/pngjs/coverage/lcov-report/png-sync.js.html +0 -116
  174. package/node_modules/pngjs/coverage/lcov-report/png.js.html +0 -662
  175. package/node_modules/pngjs/coverage/lcov-report/prettify.css +0 -101
  176. package/node_modules/pngjs/coverage/lcov-report/prettify.js +0 -1007
  177. package/node_modules/pngjs/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  178. package/node_modules/pngjs/coverage/lcov-report/sorter.js +0 -169
  179. package/node_modules/pngjs/coverage/lcov-report/sync-inflate.js.html +0 -584
  180. package/node_modules/pngjs/coverage/lcov-report/sync-reader.js.html +0 -215
  181. package/node_modules/pngjs/coverage/lcov.info +0 -2177
  182. package/node_modules/pngjs/lib/bitmapper.js +0 -267
  183. package/node_modules/pngjs/lib/bitpacker.js +0 -158
  184. package/node_modules/pngjs/lib/chunkstream.js +0 -189
  185. package/node_modules/pngjs/lib/constants.js +0 -32
  186. package/node_modules/pngjs/lib/crc.js +0 -40
  187. package/node_modules/pngjs/lib/filter-pack.js +0 -171
  188. package/node_modules/pngjs/lib/filter-parse-async.js +0 -24
  189. package/node_modules/pngjs/lib/filter-parse-sync.js +0 -21
  190. package/node_modules/pngjs/lib/filter-parse.js +0 -177
  191. package/node_modules/pngjs/lib/format-normaliser.js +0 -93
  192. package/node_modules/pngjs/lib/interlace.js +0 -95
  193. package/node_modules/pngjs/lib/packer-async.js +0 -50
  194. package/node_modules/pngjs/lib/packer-sync.js +0 -56
  195. package/node_modules/pngjs/lib/packer.js +0 -129
  196. package/node_modules/pngjs/lib/paeth-predictor.js +0 -16
  197. package/node_modules/pngjs/lib/parser-async.js +0 -165
  198. package/node_modules/pngjs/lib/parser-sync.js +0 -108
  199. package/node_modules/pngjs/lib/parser.js +0 -290
  200. package/node_modules/pngjs/lib/png-sync.js +0 -12
  201. package/node_modules/pngjs/lib/png.js +0 -194
  202. package/node_modules/pngjs/lib/sync-inflate.js +0 -168
  203. package/node_modules/pngjs/lib/sync-reader.js +0 -45
  204. package/node_modules/pngjs/package.json +0 -73
  205. package/node_modules/qrcode/README.md +0 -770
  206. package/node_modules/qrcode/bin/qrcode +0 -159
  207. package/node_modules/qrcode/helper/to-sjis-browser.js +0 -2
  208. package/node_modules/qrcode/helper/to-sjis.js +0 -105
  209. package/node_modules/qrcode/lib/browser.js +0 -76
  210. package/node_modules/qrcode/lib/can-promise.js +0 -7
  211. package/node_modules/qrcode/lib/core/alignment-pattern.js +0 -83
  212. package/node_modules/qrcode/lib/core/alphanumeric-data.js +0 -59
  213. package/node_modules/qrcode/lib/core/bit-buffer.js +0 -37
  214. package/node_modules/qrcode/lib/core/bit-matrix.js +0 -65
  215. package/node_modules/qrcode/lib/core/byte-data.js +0 -30
  216. package/node_modules/qrcode/lib/core/error-correction-code.js +0 -135
  217. package/node_modules/qrcode/lib/core/error-correction-level.js +0 -50
  218. package/node_modules/qrcode/lib/core/finder-pattern.js +0 -22
  219. package/node_modules/qrcode/lib/core/format-info.js +0 -29
  220. package/node_modules/qrcode/lib/core/galois-field.js +0 -69
  221. package/node_modules/qrcode/lib/core/kanji-data.js +0 -54
  222. package/node_modules/qrcode/lib/core/mask-pattern.js +0 -234
  223. package/node_modules/qrcode/lib/core/mode.js +0 -167
  224. package/node_modules/qrcode/lib/core/numeric-data.js +0 -43
  225. package/node_modules/qrcode/lib/core/polynomial.js +0 -62
  226. package/node_modules/qrcode/lib/core/qrcode.js +0 -495
  227. package/node_modules/qrcode/lib/core/reed-solomon-encoder.js +0 -56
  228. package/node_modules/qrcode/lib/core/regex.js +0 -31
  229. package/node_modules/qrcode/lib/core/segments.js +0 -330
  230. package/node_modules/qrcode/lib/core/utils.js +0 -63
  231. package/node_modules/qrcode/lib/core/version-check.js +0 -9
  232. package/node_modules/qrcode/lib/core/version.js +0 -163
  233. package/node_modules/qrcode/lib/index.js +0 -12
  234. package/node_modules/qrcode/lib/renderer/canvas.js +0 -63
  235. package/node_modules/qrcode/lib/renderer/png.js +0 -78
  236. package/node_modules/qrcode/lib/renderer/svg-tag.js +0 -81
  237. package/node_modules/qrcode/lib/renderer/svg.js +0 -19
  238. package/node_modules/qrcode/lib/renderer/terminal/terminal-small.js +0 -85
  239. package/node_modules/qrcode/lib/renderer/terminal/terminal.js +0 -49
  240. package/node_modules/qrcode/lib/renderer/terminal.js +0 -9
  241. package/node_modules/qrcode/lib/renderer/utf8.js +0 -71
  242. package/node_modules/qrcode/lib/renderer/utils.js +0 -99
  243. package/node_modules/qrcode/lib/server.js +0 -138
  244. package/node_modules/qrcode/license +0 -10
  245. package/node_modules/qrcode/package.json +0 -77
  246. package/node_modules/require-directory/.jshintrc +0 -67
  247. package/node_modules/require-directory/.npmignore +0 -1
  248. package/node_modules/require-directory/.travis.yml +0 -3
  249. package/node_modules/require-directory/LICENSE +0 -22
  250. package/node_modules/require-directory/README.markdown +0 -184
  251. package/node_modules/require-directory/index.js +0 -86
  252. package/node_modules/require-directory/package.json +0 -40
  253. package/node_modules/require-main-filename/CHANGELOG.md +0 -26
  254. package/node_modules/require-main-filename/LICENSE.txt +0 -14
  255. package/node_modules/require-main-filename/README.md +0 -26
  256. package/node_modules/require-main-filename/index.js +0 -18
  257. package/node_modules/require-main-filename/package.json +0 -35
  258. package/node_modules/set-blocking/CHANGELOG.md +0 -26
  259. package/node_modules/set-blocking/LICENSE.txt +0 -14
  260. package/node_modules/set-blocking/README.md +0 -31
  261. package/node_modules/set-blocking/index.js +0 -7
  262. package/node_modules/set-blocking/package.json +0 -42
  263. package/node_modules/string-width/index.d.ts +0 -29
  264. package/node_modules/string-width/index.js +0 -47
  265. package/node_modules/string-width/license +0 -9
  266. package/node_modules/string-width/package.json +0 -56
  267. package/node_modules/string-width/readme.md +0 -50
  268. package/node_modules/strip-ansi/index.d.ts +0 -17
  269. package/node_modules/strip-ansi/index.js +0 -4
  270. package/node_modules/strip-ansi/license +0 -9
  271. package/node_modules/strip-ansi/package.json +0 -54
  272. package/node_modules/strip-ansi/readme.md +0 -46
  273. package/node_modules/which-module/LICENSE +0 -13
  274. package/node_modules/which-module/README.md +0 -58
  275. package/node_modules/which-module/index.js +0 -9
  276. package/node_modules/which-module/package.json +0 -41
  277. package/node_modules/wrap-ansi/index.js +0 -186
  278. package/node_modules/wrap-ansi/license +0 -9
  279. package/node_modules/wrap-ansi/package.json +0 -61
  280. package/node_modules/wrap-ansi/readme.md +0 -97
  281. package/node_modules/y18n/CHANGELOG.md +0 -35
  282. package/node_modules/y18n/LICENSE +0 -13
  283. package/node_modules/y18n/README.md +0 -109
  284. package/node_modules/y18n/index.js +0 -188
  285. package/node_modules/y18n/package.json +0 -39
  286. package/node_modules/yargs/CHANGELOG.md +0 -420
  287. package/node_modules/yargs/LICENSE +0 -21
  288. package/node_modules/yargs/README.md +0 -140
  289. package/node_modules/yargs/build/lib/apply-extends.d.ts +0 -2
  290. package/node_modules/yargs/build/lib/apply-extends.js +0 -65
  291. package/node_modules/yargs/build/lib/argsert.d.ts +0 -2
  292. package/node_modules/yargs/build/lib/argsert.js +0 -65
  293. package/node_modules/yargs/build/lib/command.d.ts +0 -64
  294. package/node_modules/yargs/build/lib/command.js +0 -416
  295. package/node_modules/yargs/build/lib/common-types.d.ts +0 -36
  296. package/node_modules/yargs/build/lib/common-types.js +0 -25
  297. package/node_modules/yargs/build/lib/completion-templates.d.ts +0 -2
  298. package/node_modules/yargs/build/lib/completion-templates.js +0 -50
  299. package/node_modules/yargs/build/lib/completion.d.ts +0 -21
  300. package/node_modules/yargs/build/lib/completion.js +0 -135
  301. package/node_modules/yargs/build/lib/is-promise.d.ts +0 -1
  302. package/node_modules/yargs/build/lib/is-promise.js +0 -9
  303. package/node_modules/yargs/build/lib/levenshtein.d.ts +0 -1
  304. package/node_modules/yargs/build/lib/levenshtein.js +0 -58
  305. package/node_modules/yargs/build/lib/middleware.d.ts +0 -10
  306. package/node_modules/yargs/build/lib/middleware.js +0 -57
  307. package/node_modules/yargs/build/lib/obj-filter.d.ts +0 -1
  308. package/node_modules/yargs/build/lib/obj-filter.js +0 -14
  309. package/node_modules/yargs/build/lib/parse-command.d.ts +0 -11
  310. package/node_modules/yargs/build/lib/parse-command.js +0 -36
  311. package/node_modules/yargs/build/lib/process-argv.d.ts +0 -2
  312. package/node_modules/yargs/build/lib/process-argv.js +0 -31
  313. package/node_modules/yargs/build/lib/usage.d.ts +0 -49
  314. package/node_modules/yargs/build/lib/usage.js +0 -540
  315. package/node_modules/yargs/build/lib/validation.d.ts +0 -34
  316. package/node_modules/yargs/build/lib/validation.js +0 -330
  317. package/node_modules/yargs/build/lib/yargs.d.ts +0 -274
  318. package/node_modules/yargs/build/lib/yargs.js +0 -1190
  319. package/node_modules/yargs/build/lib/yerror.d.ts +0 -4
  320. package/node_modules/yargs/build/lib/yerror.js +0 -11
  321. package/node_modules/yargs/index.js +0 -40
  322. package/node_modules/yargs/locales/be.json +0 -46
  323. package/node_modules/yargs/locales/de.json +0 -46
  324. package/node_modules/yargs/locales/en.json +0 -51
  325. package/node_modules/yargs/locales/es.json +0 -46
  326. package/node_modules/yargs/locales/fi.json +0 -49
  327. package/node_modules/yargs/locales/fr.json +0 -53
  328. package/node_modules/yargs/locales/hi.json +0 -49
  329. package/node_modules/yargs/locales/hu.json +0 -46
  330. package/node_modules/yargs/locales/id.json +0 -50
  331. package/node_modules/yargs/locales/it.json +0 -46
  332. package/node_modules/yargs/locales/ja.json +0 -51
  333. package/node_modules/yargs/locales/ko.json +0 -49
  334. package/node_modules/yargs/locales/nb.json +0 -44
  335. package/node_modules/yargs/locales/nl.json +0 -49
  336. package/node_modules/yargs/locales/nn.json +0 -44
  337. package/node_modules/yargs/locales/pirate.json +0 -13
  338. package/node_modules/yargs/locales/pl.json +0 -49
  339. package/node_modules/yargs/locales/pt.json +0 -45
  340. package/node_modules/yargs/locales/pt_BR.json +0 -48
  341. package/node_modules/yargs/locales/ru.json +0 -46
  342. package/node_modules/yargs/locales/th.json +0 -46
  343. package/node_modules/yargs/locales/tr.json +0 -48
  344. package/node_modules/yargs/locales/zh_CN.json +0 -48
  345. package/node_modules/yargs/locales/zh_TW.json +0 -47
  346. package/node_modules/yargs/package.json +0 -92
  347. package/node_modules/yargs/yargs.js +0 -14
  348. package/node_modules/yargs-parser/CHANGELOG.md +0 -601
  349. package/node_modules/yargs-parser/LICENSE.txt +0 -14
  350. package/node_modules/yargs-parser/README.md +0 -449
  351. package/node_modules/yargs-parser/index.js +0 -1032
  352. package/node_modules/yargs-parser/lib/tokenize-arg-string.js +0 -40
  353. package/node_modules/yargs-parser/package.json +0 -46
package/CHANGELOG.md DELETED
@@ -1,1362 +0,0 @@
1
- # Changelog
2
-
3
- ## v3.2.3-rc.1(build 28,issue #39 + 上游 v3.2.3)— NPM-only 自动更新 + 上游 v3.2.3 并入
4
-
5
- > 插件 `3.2.3-rc.1` / App `3.2.3+28` / 载体 `3.2.3-rc.1-app`。本 fork 的 RC 发布候选;插件发布到 npm `rc` dist-tag,App build 从 +27 提升到 +28。
6
-
7
- ### 更新源与安全
8
-
9
- - 插件更新唯一来源为宿主有效 npm 配置(`@marktrue:registry` 优先,未设置时用默认 `registry`),手机不直连 npm,registry 凭据不下发。
10
- - 稳定版跟随 `latest`;预发布跟随自身通道(`alpha` / `rc`);carrier 使用精确 `<插件完整版本>-app` 并由 `app` tag 约束。
11
- - 校验 npm `dist.integrity`、插件内容哈希、载体清单、APK 身份与签名;缺 carrier、元数据不一致、降级一律 fail-closed,不改 profile、不触发重启。
12
- - profile 依赖保持精确 npm 版本,不保留长期 `file:` tarball;回滚恢复更新前精确 spec。
13
- - 旧 carrier 缺轻量元数据时:仅用户显式操作后准备并校验 carrier,先展示真实 App build,再要求最终确认;最终 POST 前不改 profile。
14
-
15
- ### 手机 / 桌面
16
-
17
- - 移除 App 更新源菜单;App-only 入口改名「检查 App 更新」。
18
- - 插件更新详情与安装日志合并为可折叠、可复制的脱敏面板,按 opId + 序号回放,重启后仍可查看。
19
- - 新增「更新进度」卡片:六步时间线、当前阶段、已用时、目标版本、最近一行 pnpm 输出;失败直接显示可读原因与稳定码。
20
- - 完成后等待宿主固定 App 发布就绪再显示完成;「检查 App 更新」自动接续,无需回首页刷新。
21
- - 历史失败/未确认事务不再阻塞 App 更新;APK 按 sha 复用,安装未完成给出可执行指引。
22
- - 桌面「连接移动端」同步显示阶段文本与最新输出。
23
-
24
- ### 平台
25
-
26
- - Windows:npm/pnpm 经 shell 调用,缺失客户端给出稳定原因;桌面原生自更新仍受 `restart-unsupported` 限制(Linux/WSL 宿主可完成自更新)。
27
-
28
-
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
- ##### 其它
162
-
163
- - 对话页发送按钮的硬编码白色改为 `DshColors.onBrand(context)`,薄荷/金色底上会变深色图标,符合对比度要求。
164
- - APK 体积:宠物动画(原生 14.1 MB)+ 首页动画(9.6 MB)。实测 release 包约 99 MB(上一版 71.8 MB),如需瘦身可把首页那 4 组 webp 降采样或改静态帧。
165
-
166
- #### v3.2.2(2026-10-27,issue #46 / #49,PR #42)— 在线本地通知 + 轮次导航 + 二维码组网地址优选 + 审批兜底修复
167
-
168
- > 版本:插件 `3.2.2` / App `3.2.2+25`。
169
- > 验收门禁:`node --test test/*.mjs`(200 通过 + 1 平台跳过)、`timeline-contract`(125)、`account-usage`(15)、`account-usage-adversarial`(19)、`flutter analyze`(0 issue)、`flutter test`(509)。
170
-
171
- ##### 审批兜底:下游 unavailable 不再抢答(真机验收发现)
172
-
173
- - `dualRoute` 原来把「下游返回 `unavailable`」也当成先答:没有任何桌面 `$events` 客户端时,内核转发立刻给 `unavailable`,插件会同时收掉手机侧卡片——手机根本来不及作答。
174
- - 现在只在桌面给出**真实答复**时才收手机卡;下游是 `unavailable` 时保留手机条目,等 `/respond` 或手机侧 fail-close 超时给出最终结果。
175
- - 新增回归:下游立即 `unavailable`,手机仍可作答且结果以手机为准;把修复回退该用例即变红。
176
-
177
- ##### 对话轮次导航(PR #42)
178
-
179
- - **一期**:对话页右侧刻度轨按轮次跳转;长会话里的定位修复(尺寸缓存、有符号估算、尝试次数上界、尾部安全网)。
180
- - **二期**:接入宿主 `turnOutline` 投影,已加载轮次正常显示、未加载轮次以短淡刻度显示,点它按 **200 条/页**有界跨页加载后定位;用户主动滚动即取消,四种终止结果(覆盖成功 / 到顶不可达 / 页数上限 / 用户取消)都有明确说明。
181
- - **新增只读端点** `GET /m/api/turn-outline?sessionId=`:四态(`available` / `empty` / `capability-missing` / `read-failed`)+ 稳定错误码、5 秒有界等待、冷折叠串行(并发 1)+ 有界队列(8)、按体积 256 KB 截断并显式标 `truncated`。
182
- - **能力位** `capabilities.turnOutline`:按宿主是否真的挂载投影判定,不按版本号推断;旧插件/旧 App 双方都能降级到既有行为。
183
- - 已知限制(如实标注):轮次特别多的长会话,跨页跳转可能失败或流畅度一般。
184
-
185
- ##### App 在线本地通知(#46)
186
-
187
- - SSE 在线且 App **不在前台**时,`approval/requested` 与 `question/requested` 直接弹系统本地通知(Android 13+ 首次启动请求通知权限);点通知直达该会话的待办卡。
188
- - 文案:`需要授权:<工具名>` + 理由;`需要你回答` + 首个问题;同一 `rpcId` 5 分钟内只提醒一次(SSE 重连/回放会重放未结算帧)。
189
- - 回到前台自动清掉系统通知(App 内已有卡片与横幅,避免重复打扰);`pushUrls` 退为「App 被杀/离线」时的兜底。
190
- - Android 构建开启 core library desugaring(`desugar_jdk_libs 2.1.5`),这是该通知库的硬性要求。
191
-
192
- ##### 二维码地址优选(#49)
193
-
194
- - 新增纯函数 `orderQrAddresses`:默认仍是「局域网私有地址优先」;**仅当机器上没有任何 `10./192.168.x/172.16-31` 地址时**,才把 Tailscale CGNAT(`100.64/10`)提到最前,避免校园网/热点等场景首选到不可达地址。
195
- - 7 项回归覆盖排序与端到端载荷选择;`docs/03-api.md` 同步。
196
-
197
-
198
- ## v3.2.1-alpha.1(2026-10-24)— Desktop 连接、版本偏斜与 npm 载体预发布
199
-
200
- > 插件 `3.2.1-alpha.1` / App `3.2.1+24` / 载体 `3.2.1-alpha.1-app`。主插件发布到 npm `alpha` dist-tag;首发后显式将 `latest` 指向 alpha.1,后续预发布不自动移动 `latest`。
201
-
202
- - 桌面 DSH 未配置时按可信桌面形态自动启用 LAN 桥,默认更新目录可用;桥端点只公告真实可达地址。
203
- - 补齐 DSH 0.2.0/0.2.1 宿主能力探测与 Peer 兼容,失配时明确诊断。
204
- - 会话列表与子代理标题读取支持预算、在途合并、取消及近期标题回退。
205
- - App 提供轮次导航与轮次大纲,并完善与插件版本偏斜时的更新/接续处理。
206
- - 发布链包含 App carrier、配对清单及 npm-drill 预演工具。
207
-
208
- ## v3.2.0(build 58,本机候选构建,未公开发布)— 桌面形态适配与发版演练
209
-
210
- > 本机验收候选:插件语义版本 `3.2.0` / App `3.2.0+58`。仅用于本机与真机验证,**未向公共 npm/GitHub 发布**,不代表正式 release。
211
-
212
- ### 插件:#36 桌面形态 S1 + #37 发版演练
213
-
214
- - 桌面形态未显式配置 `lanBridge` 时自动开启局域网桥:优先手机可达的私有 IPv4、过滤常见虚拟网卡、端口自动选取并跨重启持久化;显式配置仍优先。
215
- - 未配置 `updateDir` 时默认使用 `~/.dsh/mobile-remote/update`,启动自动创建并校验可写。
216
- - 新增本地 npm registry 发版演练(插件/载体安装与更新、失败路径),正式发版前可在不触碰公共 npm 的情况下验证完整链路。
217
-
218
- ### App
219
-
220
- - 本机候选只提高 Android `versionCode` 到 `58`,App 功能代码未变。
221
-
222
- ### 验证
223
-
224
- - develop 合并后 Node 全量 540 tests(536 pass / 0 fail / 4 opt-in skipped);App analyze/test 门禁通过。
225
- - 本机发布演练 20/20 断言通过;真机已用此前的 +57 完成核心链路,本 +58 候选待本机升级验证。
226
-
227
- ## v3.2.0(build 49,issues #32/#33)— 自更新链路加固与 Git 标签排序修复
228
-
229
- > 版本:插件 `3.2.0` / App `3.2.0+49`。本版两端都改:安装新 APK 之外,还需更新插件并重启宿主,应用 #32 自更新与打包链路的后续修正。
230
-
231
- ### 插件:#32 自更新与打包后续修正
232
-
233
- - 修正 bootstrap 中重复的插件条目覆盖,保留插件内容标识;加固自更新事务与并发锁处理。
234
- - 依赖键与实际 scoped package 名对齐,修正 npm scope,并补齐 `dsh.bundle` 声明。
235
- - 发布脚本按 `npm pack` 实际产物名归档;重启后自检不再吞掉失败。
236
-
237
- ### App:Git 标签页设置顺序显示(#33)
238
-
239
- - 设置弹窗的已选标签行即时按上移/下移后的顺序显示;未选标签仍可选,并按默认顺序排在后面。
240
- - 保存后重新加载本地偏好并重开弹窗,实际行顺序与已保存顺序一致。
241
-
242
- ### 验证
243
-
244
- - Flutter 全量测试 592 项通过;develop 集成门禁 5/5 通过。
245
- - ⚠ 真机验收待完成。
246
-
247
- ## v3.2.0(build 48,issue #32)— 两端版本偏斜的兑现机制
248
-
249
- > 版本:插件 `3.2.0` / App `3.2.0+48`。本版**两端都改了**:装新 APK 之外,还需同步插件副本并重启宿主,否则新端点在旧插件上不存在(App 会明确提示而不是静默失败)。
250
- > 内容 = #32(ADR 0019):把 README 早已承诺的「任意组合可用、协议只加不删」从政策变成机制。
251
-
252
- ### 三条判据各司其职(#32)
253
-
254
- - **能力能不能用**看能力声明;**要不要下载新版本**看发布清单里的**内容标识**(`contentSha256`);**要不要提示对齐**看版本号(只决定文案)。
255
- - 新增判据解决一个实测盲区:上游 `v3.2.0` 与 develop 的插件**版本号都是 3.2.0、内容却不同**——用版本号判「是否已最新」永远看不见这类差异。
256
-
257
- ### 插件自更新:手机可直接触发,含无人值守回滚(#32)
258
-
259
- - 手机端触发后走完整链路:下载 → 校验(字节哈希 + 内容标识)→ 解包复核 → 快照当前版本 → 原子落位 → 切依赖 → `pnpm install` → 重启宿主。
260
- - **失败自动回滚一次**:由**外部监督器**在宿主之外确认新实例健康(按"正在运行的内容标识"判定),不健康就用快照恢复旧字节并再重启一次——监督器不依赖新版插件能否加载,因此新版起不来也能救回。
261
- - **重启断线是正常环节**:手机端持久化"更新进行中"并在重连后接续,不把断线当失败。
262
- - 不能自更新的形态(如 Desktop)明确降级为**降级指引**:按实际安装方式给一条可复制的升级命令,不诱导点"一键更新"。
263
-
264
- ### App 侧对齐与编排(#32)
265
-
266
- - 两端不一致时给**温和提示**(PC 端优先,手机端首页横幅/设置页尾注),不阻断使用。
267
- - **插件先行**:App 的更新入口在插件未对齐时先引导更新插件;固定同一份发布清单的产物身份(URL/大小/哈希),不会因为改更新源或上游发新版而"跨版本拼接"。
268
- - 安装器拉起不等于装上:只有本机版本/build 与固定目标一致才收尾。
269
-
270
- ### 发布链(#32)
271
-
272
- - 发布清单 schema 1 同时描述两端产物(App 从主机源取 APK、插件从同一份清单取 tgz);发布脚本产出后**自动校验**,不一致即中止。
273
- - 插件 tgz 自包含运行依赖(`bundleDependencies`),离线/覆盖安装不会缺依赖。
274
-
275
- ### 验证
276
-
277
- - develop 集成门禁 **5/5 全绿**(`flutter-analyze` 0 issue、`flutter-test` **+590 全绿**、`timeline-contract`、`account-usage`、`kotlin-usage-panel`)。
278
- - 六轮代码复核(C1–C9 / W1–W5 / D1–D5 / E1–E3 / F1)逐项修复并留下隔离回归;Node 侧 338 项、App 侧含真实 Store+假 HTTP 的编排回归。
279
- - ⚠ **真实端到端尚未验证**:真实下载、`pnpm install`、宿主重启、故障注入回滚与真机安装留待本次真机测试确认。
280
-
281
- ## v3.2.0(build 47,issue #31)— 长会话里轮次导航定位失败修复
282
-
283
- > 版本:插件 `3.2.0` / App `3.2.0+47`。本版**只改 App**(未改 `lib/index.js`):安装新 APK 即可,无需同步插件副本或重启宿主。
284
- > 内容 = #31(长会话里点刻度轨早期轮次报「未能定位到第 N 轮:多次尝试后仍未进入视图」)。真机会话"轮数少、条目多"(如 10 轮 / 约 3000 条工具与消息)是常态,第 2 轮离底部约 2900 个条目。
285
-
286
- ### 修复:长会话里跳不到早期轮次(#31)
287
-
288
- - **跳转定位改为"查表 + 校正"**:新增**条目尺寸缓存**(按稳定 key 记录已构建条目的实测高度),并据此估算"内容索引 → 偏移"(宿主 Dashboard 虚拟化列表 `getOffsetForIndex` 的等价物)。长跳从"盲二分十几次"降到**一步到几步**:3000 条目的真实分页会话里,冷跳 5 个落点、热跳 **2 个落点**即进入目标轮。
289
- - **估算用 center 相对的有符号坐标**:此前按"从最早条目累加的非负前缀"估算,与 `CustomScrollView.center` 的实际坐标系(更早历史在**负坐标**)整体平移,估算永远通不过实测区间判据;现在用 `prefix(目标) − prefix(center)`,并归一化到布局给出的真实 extent(缓存只影响相对权重)。
290
- - **搜索界按实测持久、布局估计每轮重读**:`minScrollExtent()` 只是懒布局的动态估计,真实分页把更早历史 prepend 进来后它会向负方向大幅扩展;把它当成"永久实测下界"会让区间冻结在错误位置(目标约 −215000,冻结下界却停在 −10 附近)。现在只持久化**实测**界(索引更小者的底部 / 更大者的顶部),布局估计每轮重读。
291
- - **有界性与失败说明**:定位上界改为**尝试次数**(24),墙钟只作 20 秒绝对安全网——固定墙钟会在大量 prepend 后把慢帧误判成停滞、在**正在收敛途中**误报失败;每次落点后允许几帧布局稳定再判定可见性;安全网到时后不再滚动或精调。`notInWindow` / `exhausted` 的区分与"不可达时明确说明原因"保持不变。
292
- - 点未加载刻度仍按 200 条/页跨页加载直到覆盖该轮起始序号;翻页期间该齿脉冲,用户主动滚动即取消(未变)。
293
-
294
- ### 验证
295
-
296
- - develop 集成门禁 **5/5 全绿**(`flutter-analyze` 0 issue、`flutter-test` **+533 全绿**、`timeline-contract` 125 PASS、`account-usage`、`kotlin-usage-panel`)。
297
- - 评审侧独立复跑:严格"初始 50 条 / before 每页 ≤200"的 1000 轮 / 3000 条会话,冷跳 5 个落点、热跳 2 个落点,均与目标轮相交;尾部安全网反例(跨过 20 秒上限不得再精调)通过。
298
- - 新增回归:真实分页后端下的冷/热长跳、真机形状(10 轮 × ~250 条目)中段目标、尺寸前缀纯函数 6 例、尾部安全网 1 例。
299
-
300
- ## v3.2.0(build 46,issue #19 / #21 / #30)— 宿主 0.2.0 兼容:4 处静默失效 + 子代理列表 / Codex 余额
301
-
302
- > 版本:插件 `3.2.0` / App `3.2.0+46`。本版**改动了插件**(`lib/index.js`)——除安装新 APK,**必须同步插件副本并重启宿主**才会生效。
303
- > 内容 = #19(适配 DSH 0.2.0 的四处宿主接口变更,并把能力漂移显式化)、#21(子代理列表与 Codex 余额在 0.2.0 下失效)、#30(上游化交付的 4 项审查修复)。App 侧代码未变,build 号用于与插件版本对齐并提供更新入口。
304
-
305
- ### 修复:宿主 0.2.0 下四处**静默失效**(#19)
306
-
307
- 宿主升级后这四处**都不报错**,只是"看起来正常、实际没反应":
308
-
309
- - **后台任务**:`jobs.list/kill` 的 caller 形状变了(Agent 对象 → SessionId 字符串)。传错不报错——列表恒为空、终止报"任务属于另一个会话"。现按代际给形状。
310
- - **任务事件**:两个回调 `onJobsChanged`/`onJobDone` 被统一的 `events.subscribe` 取代,现两代各走一条路径。
311
- - **设置读取**:`settings.get(ns)` 被移除,改由 `describe()` 返回描述符数组;两处调用点收敛为跨代读取。
312
- - **双端审批通道**:`openWireStream` 的取消信号从第 3 位挪到第 5 位,传错位会让 `approvalMode: both` 的 `$events` 通道**永远不就绪**。
313
- - 另:`$events` 断流改为**退避重连**(3s 起、上限 60s、仅卸载停止;就绪过即单向置位);删除整车 `apiProxy` 死代码;`/m/api/diagnostics` 新增宿主能力**三态**报告(`ok`/`drift`/`missing`)与实际代际路径。
314
-
315
- ### 修复:子代理列表与 Codex 余额(#21)
316
-
317
- - **「会话工具 → 子代理」打不开**:宿主 0.2.0 删除了 `subagents/list` Remote 端点。现改为三级目录来源:宿主持久目录(含**已释放**的子代理)→ 持久化枚举(父会话休眠/归档时入口不消失)→ 会话注册表兜底(标 `catalogDegraded`,**不让不完整列表冒充完整目录**)。
318
- - **手机看不到 Codex 余额**(而桌面网页端正常):0.2.0 移除 `settings.get()` 后,代理配置被**误判为"未启用"**而静默改走直连、最终超时。现区分三态——用户没开代理 / 代理可用 / **读不到宿主设置**(显式失败);失败详情只回稳定错误码与固定文案,**不回传异常原文**(凭据可能内嵌在异常消息里)。
319
-
320
- ### 审查修复(#30)
321
-
322
- - 目录条目的 `label` 可省略:**已释放**的子代理现在会补读持久标题,不再退化成短 ID。
323
- - 目录 `mode: "unknown"` 的不受支持条目保留 `diagnostic/unsupported` 语义,不再伪装成普通闲置子代理。
324
- - `/m/api/diagnostics` 的 `checks.hostCapabilities["settings.read"]` 与真实读取**共享判定**,并新增 `checks.hostCapabilityReasons` 原因码——读取已经失败时诊断不再报 `ok`。
325
- - HTTP 429 限流与登录失效不再被误报为"网络不可达";只接受 allow-list 的错误码与**校验过的**状态码。
326
-
327
- ### 兼容与安装
328
-
329
- - **peer 依赖声明 `>=0.1.0-rc.6 <0.3.0`**:宿主用 `semver.satisfies(runtimeVersion, range, { includePrerelease: true })` 判定,该范围覆盖 `0.1.x`、`0.2.0-rc.*` 与 `0.2.x` 正式版(原 `<0.2.0` 到 `0.2.0` 正式版会被判不兼容)。上界只声明可安装边界,功能路径仍按能力探测。
330
- - `0.1.x` / `0.2.x` 两代宿主 API 都支持,**不按宿主版本号硬拦截**。
331
-
332
- ### 门禁(2026-10-10)
333
-
334
- - develop 集成门禁 **5/5 全绿**(`flutter-analyze` / `flutter-test` / `timeline-contract` / `account-usage` / `kotlin-usage-panel`);全量 node 测试 **207/207 PASS**;`node --check lib/index.js` 通过。
335
- - 验证范围如实标注:只对 **`0.2.x` 真实宿主**做过端到端验证;`0.1.x` 路径由两代假宿主夹具保护(真实宿主未验证)。
336
-
337
- ## v3.2.0(build 45,issue #24 / #25 / #28)— 对话轮次导航(一期+二期)+ 预热 run 级硬截止
338
-
339
- > 版本:插件 `3.2.0` / App `3.2.0+45`。本版**改动了插件**(`lib/index.js`)——除安装新 APK,**必须同步插件副本并重启宿主**才会生效。
340
- > 内容 = issue #28(预热 run 级硬截止,提交 `969bde6` + `e17d2c8`)、#24(轮次导航一期,merge `6711f63`)、#25(二期:宿主轮次大纲端点到刻度轨,merge `d93cf98`)。
341
-
342
- ### 新增:对话轮次导航(#24 一期 + #25 二期)
343
-
344
- 长会话里按轮次定位:对话页右侧悬浮一条**刻度轨**,每轮一个刻度。
345
-
346
- - 默认极细半透明;**上翻离开底部才显形**,与「回到底部」圆钮同进同出;少于 2 轮不渲染(一轮没有导航价值)。
347
- - **点按刻度直接跳转**;沿轨竖直拖动 = **扫掠轮次**(拖动过程出预览、松手落在所指那一轮)。超长轨道在上下沿会**自动滚动**,能一路扫到底,不再有"看不见也点不到"的刻度。
348
- - 预览显示该轮**提示词**(1 行)与**回复摘要**(至多 3 行);提示词为空(纯图片/纯命令轮)时回退「第 N 轮」;非真人注入消息不参与预览。
349
- - 定位是**有界迭代 + 实测几何校正**:懒构建的列表、条目高度不均(长回复)也能收敛;目标不可达或多次尝试仍失败会**明确说明原因**,不静默停下。
350
- - **二期把宿主轮次大纲接到手机**:插件新增只读端点 `GET /m/api/turn-outline?sessionId=`,`/bootstrap` 与 SSE hello 增加 `turnOutline` 能力位(宿主真的挂了该投影才算支持)。刻度轨从此是**整个会话的完整阶梯**:已加载轮次正常显示,未加载轮次以**短淡刻度**区分。
351
- - 点未加载刻度会**按 200 条/页跨页加载**直到覆盖该轮起始序号再定位(不改动既有上翻的 30 条/页);翻页期间该齿脉冲,**用户主动滚动即取消**。
352
- - 大纲获取失败按**四态**分别说明:能力缺失 / 该会话暂时没有大纲 / 阶梯被截断(更早轮次需上翻)/ 读取失败或超时——**空数组不再被当成"没有轮次"**,"不支持"与"该会话没有大纲"也给出不同说法。
353
-
354
- ### 修复:冷启动预热改为 run 级硬截止(#28)
355
-
356
- 预热寿命若被旧 timer 反复收紧会失效——现在收紧时必须先清旧 timer,并把预热寿命提到 run 级硬截止。
357
-
358
- ### 服务端(插件)要点
359
-
360
- - 新端点复用既有路由分派 / 鉴权 / Host 校验 / `sendJson` 约定;**只读**,不改既有路由语义。
361
- - 冷折叠**有界等待 5 秒**(从请求到达起算、含排队时间),队列有界(8)且并发 1;超时或客户端断开都回**明确降级**,真正未结束的观察继续占住执行槽、迟到 lease 释放,**断开后不再向已销毁的响应写入**。
362
-
363
- ### 门禁(2026-10-09)
364
-
365
- - develop 集成门禁 **5/5 全绿**:`flutter-analyze` 0 issue、`flutter-test` **518 项全绿**、`timeline-contract` **125 PASS**、`account-usage` 15/15、`kotlin-usage-panel` PASS。
366
- - 评审侧:三轮评审的全部反例与探针均已转绿(服务端隔离探针 **39 PASS / 0 FAIL**)。
367
-
368
- ## v3.2.0(build 44,issue #27)— 冷启动首次 /sessions 不再 504:枚举与标题预算解耦
369
-
370
- > 版本:插件 `3.2.0` / App `3.2.0+44`。本版**改动了插件**(`lib/index.js`)——除安装新 APK,**必须同步插件副本并重启宿主**才会生效。
371
- > 内容 = issue #27 的修复(提交 `58e0a0f` + `9c0cb78`,merge `8770bf5` / `d5dba5b` @ develop)。
372
-
373
- ### 问题(issue #27,`+43` 部署后实测)
374
-
375
- 宿主重启后最初三次 `/m/api/sessions` 返回 **504 `sessions-timeout`**(1.37–1.47 秒、28 字节),而从第二分钟起为 `200` / 1.37 秒(386 个会话、386 个真实标题);对照部署前是 `200` / **82.45 秒**。即把「永远很慢」修成了「稳态很快」,但**冷启动首次请求从「慢但成功」退化成了「快但失败」**。
376
-
377
- 根因:`listSessionsWithinTitleBudget` 把**枚举本身**(`query.listSessions`)也放进了**标题折叠的 1.5 秒预算**。宿主刚起来时内核语料是冷的(note 793 实测全量 `persistence.stat()` 约 22 秒),枚举必然超过 1.5 秒 → `{timedOut:true}` → 路由 `504`。1.5 秒预算的意图是限制**可延后的标题折叠**,不是核心枚举。
378
-
379
- ### 修法
380
-
381
- - **枚举与标题预算解耦**:新增 `Config.enumerationBudgetMs`(默认 `DEFAULT_ENUMERATION_BUDGET_MS`)作为枚举自己的预算;标题折叠继续 ≤1.5 秒(超预算的会话先用短码兜底、随后后台有界预热)。
382
- - **默认 12 秒——先于客户端超时给出结论**:App 对 `/sessions` 用的是 `getJson` 的默认超时 **15 秒**(`dsh-mobile-app/lib/api.dart`)。若服务端预算更大,冷首轮落在 15–30 秒之间会出现「服务端最终 200、客户端已经先放弃」——用户既拿不到列表、也拿不到明确失败,服务端还在为一个没人等的请求继续烧 CPU。12 秒 = 15 秒 − 3 秒余量(网络往返 + ~110KB JSON 序列化)。
383
- - **防御性默认**:宿主传给 `apply()` 的是**原始** config(schema 默认值不在这里生效),缺字段必须回落到常量,否则 `undefined - elapsed` 得到 NaN、`setTimeout(NaN)` 立即触发 → 冷启动被误判成超时。
384
-
385
- ### 门禁与验收证据(2026-10-09)
386
-
387
- - 插件测试:`node --test test/*.mjs` **134/134 通过**。含新回归——枚举 1.7 秒(慢于标题预算、快于默认预算)→ **200 + N**,且标题为短码兜底(证明标题预算仍生效);该用例刻意**不传** config,同时钉住生产部署路径的防御性默认。
388
- - 变异验证:把枚举预算退回标题预算 → 新回归立刻变红(`504 !== 200`),正是本 issue 报告的缺陷。
389
- - 分支门禁:`flutter-analyze` / `timeline-contract` / `account-usage` / `kotlin-usage-panel` **PASS**;`flutter-test` 为 main 基线既有 3 项红灯(`sse_cancel_window_test.dart`),按基线例外放行——改动只碰插件与 Node 测试。
390
- - **develop 集成门禁 5/5 全绿**(含 `flutter-test`,develop 侧已有该测试的修复):`20-integration-8770bf571bcd.md`、`20-integration-d5dba5b1eef1.md`。
391
- - 维护者代码评审:note 1055「#27 代码评审通过:`58e0a0f`」,未发现 BLOCKING/WARNING。
392
-
393
- ### 仍未验(本版部署后实测)
394
-
395
- note 1055 明确要求「部署后仍需实测真实冷首轮/稳态及 App 15s 超时与服务端预算的匹配」。本版把预算压到 12 秒后需实测:宿主重启后**首次**请求应为 `200 + N`(而非 504),且耗时 <15 秒。
396
-
397
- ## v3.2.0(build 43,issue #20)— 会话列表不再「越用越慢」:revision 缓存 + 每请求预算 + single-flight
398
-
399
- > 版本:插件 `3.2.0` / App `3.2.0+43`。本版**改动了插件**(新增 `lib/session-title-cache.js`、`lib/session-title-refresh.js`,改 `lib/index.js`)——除了安装新 APK,**必须同步插件副本并重启宿主**才会生效。
400
- > 内容 = issue #20 第 2 步在 develop 上的集成(merge `a266e76`,分支交付 SHA `b5f8afc`)。
401
-
402
- ### 问题(issue #20)
403
-
404
- 手机 App 打开就是会话列表。桌面端积累大量会话后,列表要么挂死、要么极慢:`/sessions` 在响应前**同步折叠全部休眠会话的标题**,而折叠要完整读取每个会话日志(多帧 zstd 解压 → JSON 解析 → 折叠校验)。本机 384 个会话实测冷缓存 **81–113 秒**才返回(维护者 note 702 / note 793 已量化根因:354 会话离线全量折叠 108.8 秒;`persistence.stat()` 全量 21.9 秒)。
405
-
406
- ### 第 1 步(更早的 develop 版本已落地)
407
-
408
- 客户端断开即取消**本请求**的枚举与标题折叠:`requestAbort` + 把取消信号传进宿主读取,重试不再叠加。
409
-
410
- ### 第 2 步(本版)
411
-
412
- - **revision 失效的持久化标题缓存**:`~/.dsh/mobile-remote/session-titles.json`,`{ id: { title, revision, at } }`;revision 未变 → **零日志读取**,宿主重启后依然命中(失效键取自不读日志的 `persistence.stat()`)。
413
- - **每请求时间预算(≤1.5s)+ 增量返回**:预算内只折叠新增/变更会话,其余用短码兜底并**立即返回 200**;预算耗尽时仅在客户端仍连接时转为后台有界预热。
414
- - **single-flight + 折叠串行门**:并发请求复用同一个 in-flight 轮次;取消清理期间不叠加第二轮(避免读取宽度翻倍)。
415
- - **有界读取**:整轮 miss **只发一次** `readTitleSnapshots`(其内部读取宽度由宿主 `persistedReadConcurrency`=4 约束,插件不再叠加 per-id 扇出);旧宿主/整批故障的逐个 `readTitleSnapshot` 兜底同样 ≤4。实测 354 会话的标题链全量枚举由 **91 次降到 3 次**且与 N 无关。
416
- - **评审 note 908 BLOCKING 修复**(`b5f8afc`):`mapBounded` 用 `Promise.all` 时**首拒即返回**,会让折叠串行门在其余三路仍在清理时就被释放、重试再开四路 → 实测 `maxActive=7` 突破 ≤4 上限。改为等**全部 worker `allSettled`** 之后再重抛原失败,并在派发前查取消。
417
-
418
- ### 门禁与验收证据(2026-10-09)
419
-
420
- - 分支门禁:`flutter-analyze` / `timeline-contract` / `account-usage` / `kotlin-usage-panel` **PASS**;`flutter-test` 在 main 基线有 3 项既有红灯(`sse_cancel_window_test.dart` 的取消窗口/对照/连续重建),按基线例外放行——本分支只改插件与 Node 测试,**未触及任何 Flutter 代码**。
421
- - **develop 集成门禁 5/5 全绿**(含 `flutter-test`,develop 侧已有该测试的修复,因此无需例外):报告 `20-integration-a266e760b708.md`。
422
- - 合并后 `node --test test/*.mjs` **165/165 通过**(隔离临时 HOME/DSH_HOME)。
423
- - 变异验证:把 `allSettled` 退回 `Promise.all`,note 908 的两条回归**立刻变红**(重试抢跑,`dispatches=12 ≠ 4`)。
424
- - 维护者代码评审:note 1041「#20 最终代码复核通过:`25a320b`」,issue 已按用户授权关闭。
425
-
426
- ### 仍未验(如实标注,勿以本版发布代替)
427
-
428
- - **真实语料的冷首屏计时与生产复核**(note 793 验收第 1、7 条:`/m/api/sessions` ≤2s 返回 200+N、`xinfangyb` 的 4 个会话在 App 可见)——需在本次部署后实测。
429
- - 79 个 seeded 会话的完整冷读失败属另一缺陷(note 788/793 建议单独跟踪)。
430
-
431
- ## v3.2.0(build 42,issue #19 / #21 / #20 / #22)— develop 首次发到上游 v3.2.0 基线
432
-
433
- > 版本:插件 `3.2.0` / App `3.2.0+42`。本次把 develop 推到了上游 v3.2.0 基线(`5832efc` 合并 `github/main`),并带上 develop 独有的 #19 / #21 / #20 / #22 修复。
434
- > **插件副本与已部署副本逐字节一致**(`diff -rq lib/ ~/.dsh/profiles/web/node_modules/dsh-mobile-remote/lib/` 为空),本轮发布**按流程重新执行一次部署与延迟重启以刷新运行中的插件**。
435
-
436
- ### 上游 v3.2.0 带来的内容(合并 `github/main`,此前不在手机版本里)
437
-
438
- - **移动端 Git 只读浏览 + 会话文件浏览**(PR #31 含 PR #28;PR #27 收尾补正):对话操作栏新增 `git` / `files` 入口,浏览根 = 当前会话工作目录;提交详情、文件改动预览、分支图与工作区事实均为只读。
439
- - **会话列表运行状态**(PR #30 覆盖 #29):运行中虚线旋转、等待态静态警示色、子代理可从父会话进入。
440
- - **斜杠命令在提交时真正执行**(#25):此前 `/compact` 被当普通消息投给模型,压缩从未发生;现在命中内核命令语法且名字在命令目录里就走 `POST /m/api/commands`,不插乐观气泡、不进 `/send` 回执对账,并把结果 toast 出来。服务端 `commands.execute` 整条命令超时由 15s 放宽到 **180s**(大会话摘要远不止 15s),客户端 `runCommand` 用 200s 保证服务端先收敛。
441
- - **宽表格横滑不再被消息列表抢走**(#33):把表格子树的拖拽阈值压到 4px,使横向手势按 hit-test 由最内层表格胜出;修复前"按住表格横滑"会触发 `_loadMoreInfinite()`,用户可被一路带到任意早的历史。
442
- - **模型未确定时显式说明**(PR #27 复核补正):强度区域不再静默消失。
443
- - **扫码地址双重挂载路径修复**(PR #32):修 `/m/m` —— LAN 桥与回环都中招。
444
- - **`flutter analyze` 门禁回到 0 issue**:合并后新增的 41 项 info(39 项 `curly_braces_in_flow_control_structures` + 2 项 `use_null_aware_elements`)已用 `dart fix --apply` 机械化修回。
445
-
446
- ### develop 独有修复(上游没有,这次一并随 App 发布)
447
-
448
- - **#19 / #21 宿主 0.2.x 兼容**:子代理列表与 Codex 余额在宿主 0.2.0 下失效——插件改为按能力探测而非按宿主版本号推断,并在缺失/漂移时给出明确原因(三分状态:缺失、语义漂移、可用)。peer 依赖范围去掉上界。
449
- - **#20 请求级取消**:客户端断开即取消会话枚举与标题折叠,止住枚举放大与死队列复用;逐个标题兜底读取在断开时取消在途请求。
450
- - **#22 会话列表刷新的在途守卫**:同一时刻只允许一个列表请求在飞,期间到达的刷新合并为「结束后再补一次」;被合并的刷新等到补发真正结束才返回;切换服务器地址后旧地址未完成的请求不再挡住新地址刷新;owner 收尾窗口不再留下 phantom in-flight。
451
- - **轮次导航**(issue #24,ADR 0018 + `CONTEXT.md` 领域词条):对话页右侧刻度轨按轮次定位,可见轮次可跳转、未构建轮次以弱化刻度区分——本版只随仓库记录决策,**App 侧实现仍在 `feature/turn-navigation` 工作树、未进本次发布**。
452
-
453
- ### 门禁与验收证据(2026-10-08 develop)
454
-
455
- - `flutter analyze` → **0 issue**;`flutter test` → **399 全绿**。
456
- - `node --test test/*.mjs` → **132 pass / 0 fail**;`node tools/timeline-contract-check.mjs` → **77 PASS**;`node tools/account-usage-check.mjs` → **15/15**;`node tools/account-usage-adversarial-check.mjs` → **19/19**。
457
- - Kotlin `UsagePanelModelTest` → **27 项全绿**(用 `:app:cleanTestDebugUnitTest` 强制重跑,核对 `dsh-mobile-app/build/app/test-results/.../TEST-*.xml` 的 mtime,避免把 `UP-TO-DATE` 缓存误判为"门禁空跑")。
458
- - 构建环境:Flutter 3.47.1 / JDK 17 / Android SDK 36;`PUB_HOSTED_URL` 跟随 `pubspec.lock` 的 `https://pub.flutter-io.cn`,构建后 `pubspec.lock` 无改动(88 处源保持镜像)。
459
- - 更新链路:`package-release.sh` → `verify-update-manifest.mjs` **RESULT: PASS**(size/sha256 与 APK 实算一致);`GET /m/api/update/manifest` 返回 `version=3.2.0+42`、`size=75057490`、`sha256=4db924e4…`,`GET /m/api/update/apk` 下载 75057490 字节、实算 sha256 与 manifest 一致。`aapt2 dump badging` 核对 APK 内置 `versionCode='42'` / `versionName='3.2.0'`(**> 手机已装 `+40`**,不会触发「不能降级安装」)。
460
- - 宿主重启与自检:`restart-dsh-web-service.sh <pid> 90` 于 13:02:11 停止旧 pid、13:02:13 新 pid 接管、smoke OK;`tools/postrestart-check.sh` 逐项 PASS——端口由新 pid 接管、`bootstrap` capabilities(`eventTimeline` v1)、`update/manifest` 版本 = `3.2.0+42`、`session-config` 取真实会话(`deepseek-v4.1-flash` / `opencode-go-plus` / `reasoningEffort=high`)、`catalog?refresh=1`。**该脚本自带 20s 超时不适用于本机的 `/m/api/sessions`**(见下条),本次是逐端点手工复核补全的,不是脚本一次全绿。
461
-
462
- ### 观察记录:`/m/api/sessions` 在本机约需 81 秒
463
-
464
- - 本机现有 **384 个会话**,`GET /m/api/sessions` 实测返回 200、约 110KB,但耗时 **81.2s**(`bootstrap`/`update/manifest` 均为毫秒级)——列表按会话逐条读取日志,成本随会话数线性增长。
465
- - 影响:`tools/postrestart-check.sh` 的 `curl -m 20` 取不到 `sessionId`,会判 FAIL;**这是超时口径问题,不是宿主故障**(用 `-m 150` 同一端点正常返回)。手机上该列表也会长时间转圈(#22 的在途守卫保证它不再堆积请求,但单个请求仍慢)。
466
- - 未在本次修改:属性能问题而非本次发布的回归(发布前后插件副本逐字节一致)。建议后续单独立 issue:给列表加缓存/分页,或让 `postrestart-check.sh` 的会话探测复用可配置超时。
467
-
468
- ### 已知未解决:issue #20(缺陷本体在上游核心)
469
-
470
- 插件侧的降级读取(`degraded/current-surface`、`configDegraded`)在 #21 已落地,本版无改动;**缺陷本体在 DSH 核心**——`dsh-session-query.readSession()` 仍走 `Session.create(...)` 快照路径而非 `Session.fromRestore`。上游已加入逃逸口(日志有 `session/end-seed` 且 `data.inherited === true` 时可跳过等长校验),因此是否复现取决于目标会话的日志内容。插件侧降级保留到核心改走 restore 构造路径。
471
-
472
-
473
- ## v3.2.0(2026-10-04,issue #25 / #26 / #33,PR #27 / #28 / #29 / #30 / #31 / #32)— 移动端 Git 与会话文件浏览 + 会话列表状态 + 命令与手势修复
474
-
475
- > 版本:插件 `3.2.0` / App `3.2.0+23`。
476
- > 验收门禁:`node --test test/*.mjs`(94 通过 + 1 平台跳过)、`node tools/timeline-contract-check.mjs`(77)、`node tools/account-usage-check.mjs`(15)、`node tools/account-usage-adversarial-check.mjs`(19)、`flutter analyze`(0 issue)、`flutter test`(366)。
477
-
478
- ### 真机验收补正(2026-10-07,同属本次发布)
479
-
480
- 以下三处是打 tag 后按真机验收结果补的修复,**仍属 v3.2.0**,未单独升版本。
481
-
482
- #### 命令端点对休眠会话直接 404 ——「命令不能用」的真根因(服务端)
483
-
484
- - **现象**:用户在 App 里打开**较旧的会话**后点命令列表,弹出「命令列表加载失败:session not found: session-…」。
485
- - **数据**:本机 `/m/api/sessions` 共 **171** 条会话,其中**只有 1 条已挂载**(当时正在用的那条);App 首页会把全部持久化会话都列出来,所以随手点开的很可能是休眠会话。
486
- - **根因**:`GET/POST /m/api/commands` 用 `agents.get(sessionId)` 判定会话存在性,未命中即 `404 session-not-found`;而 `/send` 会自动 `agents.resume` 唤醒休眠会话——**两条通路的语义不一致**。(服务端本身无故障:同一路径 curl 实测 `200 + 6 条命令`。)
487
- - **修复**:把 `/send` 里的休眠恢复逻辑(`readDormantConfigEvents` + `foldFromEvents` 折叠配置、`agentPresets.mount`、`agents.resume`)抽成 `resumeDormantAgent(sessionId)`,命令端点复用同一条路径。**必须带上折叠出来的模型**:`agents.resume` 会真的挂载 agent,若用默认模型挂载,后续 `/send` 会因 `target` 已存在而跳过折叠,把该会话的模型静默降级。
488
- - **测试**:`test/dormant-session-read.test.mjs` 新增 2 项(GET/POST `/commands` 对休眠会话返回 200 且触发恢复),harness 增加 `commands` 注入。已做变异验证:把 `resumeDormantAgent` 改为恒返回 `null` → **4 条用例失败**(2 条新增 + 2 条原有 `/send` 恢复用例,helper 共用)。
489
-
490
- #### 命令面板:带参数命令点选后不再"看起来没反应"(App)
491
-
492
- - **现象**:打开 ⊕ → 命令 → 点选命令后**没有任何可见反馈**,用户判断为"命令不能用"。
493
- - **原因**:6 条命令里只有 `/compact`、`/export` 是**裸命令**(点选即执行,与 PC 端菜单一致),其余 4 条(`/feedback`、`/goal`、`/permission`、`/plan`)带参数,点选后**只把命令名填入输入框**(对齐 PC 端 `leadingInput` 语义)——而这条路径此前没有任何提示。
494
- - **修复**:填字后补一条 toast「已填入输入框:补参数后点发送才会执行(用法 …)」,用法取自内核的 `input.hint`(如 `/goal` 显示 `[<objective>|clear|edit <objective>|pause|resume]`)。
495
- - **真机验证(执行通路本身正常)**:点选 `/goal` → 输入框出现 `/goal ` 且**日志无执行记录** → 点「排队发送」→ 日志出现 `Chat: 执行命令 → … : /goal`、`Chat: 命令完成 → /goal`,内核同时发出 `command/run`、`command/done` 事件,且**未产生 `/goal` 用户气泡**(符合"命令不进乐观气泡"的设计)。服务端 `POST /m/api/commands` 另经 curl 独立验证:`/goal` 与 `/export` 均返回 `200 + kind:"success"`。
496
-
497
- #### 对话操作栏:白条不再撑满整屏
498
-
499
- - **现象**:右上角「对话操作」打开的右侧图标栏是一条**贯穿整屏的白色长条**,4 个动作只占顶部约 224dp,下方一大片空白。
500
- - **原因**:`_ConversationActionRail` 用 `SizedBox(width: 56, height: double.infinity)` 承载 `ReorderableListView`(列表自顶部排布),栏体因此被拉满整屏高。
501
- - **修复**:高度改为按动作数取高(`56 × 动作数`,当前 4 项 = 224dp),并**挂在右上角顶部**——与触发它的「对话操作」按钮同侧同高(真机实测:首项中心 y=228,与触发按钮一致;整条栏 y=144–816)。左侧加 14dp 圆角。中途曾试过垂直居中,真机反馈"与触发按钮脱节、太丑",已改为顶部对齐。背景变暗(`barrierColor: Colors.black54`)与"覆盖层不挤压内容"属模态交互的既有设计,未改动。
502
-
503
- #### 勘误:初次把「命令列表加载失败」误判为连接竞态
504
-
505
- 本次真机排查中,**我先把一次命令列表加载失败归因为"连接复用 / 半关竞态"——这是错的**。抓到完整提示后确认真实原因是上一节的**休眠会话 404**(`session not found: session-…`),与连接无关;同一路径 curl 始终是 `200 + 6 条命令`。记录在此以免后人被那条推断误导。命令面板的 `catch` 只弹 toast、不写日志,确实是诊断盲点(本次靠 `uiautomator dump` 抓到完整文案才定位),按用户意见未改。
506
-
507
- 本版把 4 个社区 PR 收进主线(其中 PR #30 覆盖 #29、PR #31 覆盖 #28),并修掉 3 个 App 侧缺陷。
508
-
509
- ### 斜杠命令:提交时解析并真正执行(issue #25)
510
-
511
- - 此前在输入框直接发送 `/compact` **不会执行命令**,而是被当作普通用户消息投给模型:会话流出现一条 `/compact` 气泡、模型把它当用户发言回应、压缩从未发生,且没有任何报错——移动端几乎唯一的上下文压缩手段实际不可用。命令面板点选也只是"填字",不完成提交。
512
- - 现在**提交时在客户端解析**:命中内核命令语法(`/` + 小写名 + 边界)且名字在已拉取的命令目录里 → 走 `POST /m/api/commands` 执行;**不插入乐观用户气泡**、不进 `/send` 的 requestId/回执对账;成功后清草稿,并把命令结果文本 toast 出来(`command/run`、`command/done` 在普通模式不渲染,不提示就等于没有反馈)。
513
- - 名字**不在目录里**时按普通消息发送,但先给一条非阻塞提示。这与 issue 原文"未知命令给出提示并保留草稿"不同:照原文实现会让以 `/` 开头的正常消息(如 `/tmp`)永远发不出去,而 PC 端对无法解析的名字同样是回落普通发送。
514
- - ⊕ 命令面板:**裸命令**(无 `input`,如 `/compact`)点选即执行,与 PC 端菜单行为一致;带参数的命令仍填入输入框,并把 `input.hint` 作为副标题显示(此前完全忽略 `input`)。
515
- - 服务端 `commands.execute` 的整条命令上限由 **15s 放宽到 180s**:原值会把 `/compact` 掐死(它要等内核完整跑完一次 LLM 摘要,大会话远不止 15s);客户端 `runCommand` 用 200s,保证先由服务端收敛、客户端不抢跑成"本地失败"。504 时按"已提交、结果未知"提示,不断言失败。
516
- - 新增 `dsh-mobile-app/test/issue25_command_line_test.dart`(命令行判定纯函数 + 提交行为)。
517
-
518
- ### 宽表格横滑不再被消息列表抢走(issue #33)
519
-
520
- - 症状:安卓上看宽表格,手指按住表格横向滑动时页面滑不动,反而从当前对话跳到更早的历史位置。
521
- - 根因:表格的横向 `SingleChildScrollView` 与外层纵向消息列表处在同一手势竞技场,两者都从环境 `MediaQuery.gestureSettings` 取拖拽阈值(Android 实测 ≈8px)。首次移动事件里**纵向分量先越线、横向分量尚未越线**时,只有纵向识别器被接受 → 列表赢下整个手势:表格冻结,列表跟着手指上翻,并因靠近前缘触发 `shouldLoadOlderFromScroll` → `_loadMoreInfinite()`(每页 30 条、可无限重复),用户可以一路被带到任意早的历史。
522
- - 修复:把**表格子树**的拖拽阈值压到 4px,使"横向分量已明显、纵向先越线"的手势在同一事件内两个识别器都越线,按 hit-test 顺序由最内层(表格)胜出。作用域仅限表格:外层消息列表、通知横幅的 `Dismissible`、会话操作栏都不受影响,纯纵向手势仍归列表。
523
- - 既有 `md_table_test.dart` / `md_table_adversarial_test.dart` 把表格放进裸 `Scaffold`、外层没有任何可滚动控件,表格永远赢——这正是缺陷被长期漏掉的原因。新增 `issue33_wide_table_swipe_test.dart` 复刻真实层级(`SelectionArea → 纵向 ListView → 表格`),断言两个 Scrollable 的偏移;已做红/绿双向验证(回退修复即失败)。
524
-
525
- ### 会话文件浏览与移动端 Git 只读浏览(PR #31,含 PR #28 全部提交)
526
-
527
- - 对话操作栏(对话框右上角「三个点」)新增「文件」动作,位于 Git 之后:全屏只读页面,浏览根 = **当前会话的工作目录**;逐级下钻 + 面包屑回跳,夹在会话工作目录内不向上越出;隐藏点开头条目(`.git/` 因此天然不出现);超上限时渲染前 N 条并显示总数。
528
- - 点开文件看等宽 + 行号只读预览;二进制(含 NUL)与超限文件明确说明原因,不伪装成空内容;预览在客户端截断,刷新失败保留旧内容并标注取得时间。纯只读:不新建/重命名/删除/上传/下载/编辑,也不依赖 Git。
529
- - 对话操作栏收纳 `git` / `files` / `session_tools` / `copy` 四项并支持拖动排序;旧设备上持久化的 3 项顺序因长度校验不通过会自动回退到含 `files` 的新默认(只丢一次自定义排序,不会缺项)。
530
- - 移动端 Git 只读浏览(PR #28):工作区浏览、分支图(父泳道/悬线/拓扑起点修复)、标签配置、只读操作安全防护、工作区过期竞态修复、Git 配置过滤器禁用。
531
- - 真机验收中发现并修掉三个问题:打开即报 `No host specified in URI`(误用空 baseUrl 的新实例,改为复用全局单例)、预览返回后偶发 `TimeoutException`(`/files` 缺显式 `connection: close` 与服务端 keep-alive 形成半关竞态,同类流式路由一并处理)、系统返回手势直接退出页面(补 `PopScope` 逐级返回)。
532
- - **本次合并同时修正了该 PR 新增测试的两处可移植性缺陷**(在 Windows 上必红,与业务逻辑无关):`sep` 断言写死为 `"/"`(服务端返回的是宿主真实分隔符 `path.sep`,改为断言 `=== path.sep`);用 `symlinkSync` 构造越界符号链接在 Windows 未开开发者模式/非管理员时抛 `EPERM`(改为识别该宿主权限限制并 `t.skip`)。冲突解决记录见合并提交:`lib/index.js` 卸载清理取两侧并集,`store.dart` 三处取状态归一重构侧(PR 侧会复活内核从不发出的 `waiting` 态,且冲突块外引用了只在该侧定义的变量)。
533
-
534
- ### 会话列表状态与子代理排序(PR #29 / #30)
535
-
536
- - 会话列表新增**运行中标识**(方形虚线指示器:运行时品牌色旋转、空闲不画)、隐藏子代理会话、按 `lastMessageAt`(最近聊了什么)排序;服务端 `/sessions` 透出 `lastMessageAt` / `origin` / `parentSession`。
537
- - 子代理列表改为按 `createdAt` 倒序(与会话列表有意不同:统一用 `lastMessageAt` 会让子代理一干活就跳最前、顺序抖动),并复用会话列表的实时状态标识;副标题由 `[id, status]` 改为「会话 id · 相对创建时间」,「中断」按钮按实时状态出现。两条数据来源分支统一排序,修掉"内核活跃分支升序、休眠分支降序"的顺序相反缺陷。
538
- - 状态归一收敛为共享 helper `normalizeAgentStatus()`:内核 `AgentStatus` 是 `idle | running` **二元**联合(ADR 0013),不存在 `waiting`,未知取值一律归一到 `idle`,避免列表出现"永不消失的第三态"。
539
- - 已知限制(有意保留):活跃父会话分支从内核内存注册表取 `createdAt`,取不到即省略该字段,后果是活跃父会话下**已结束**的子代理会缺时间 → 排到最后且不显示时间;休眠父会话走持久化 header 不受影响。
540
-
541
- ### 模型与推理强度修正(PR #27,修 issue #26)
542
-
543
- - 模型身份与强度改为读内核 `session/control` 的 `modelSelection.next`(**待生效**选择),不再用可能滞后的 `lastUsed`——此前"改强度"会按上一轮实际使用的模型提交,或退回 DeepSeek/默认提供商。
544
- - 目录按模型给出 `reasoning.efforts` / `defaultEffort`(不再用一个全局并集),界面只显示当前模型支持的等级,切换模型不继承旧模型的强度;新建会话用独立草稿(`NewSessionModelDraft`),不借用 `store.sessionConfig`。
545
- - 已持久化但当前未挂载的会话可以通过内核配置接口更新模型选择;确实不存在的会话仍返回明确错误(`session/not-found` → 404)。配置失败显式报错,不再静默改用其他模型或强度。
546
- - **本次复核补正**:`selected == null`(`next` 为 null 或模型不在目录里)时,强度区域此前会**静默消失**——既无控件也无说明。已改为显式说明「尚未确定当前模型,先在上方选择一个模型」,并补 2 项 widget 回归。
547
-
548
- ### 扫码连接修复(PR #32 的 `/m/m` 部分)
549
-
550
- - 缺陷:`/m/api/qr-config` 返回的 `urls` **已自带挂载路径**(服务端两条分支都拼了 `${basePath}`:LAN 桥 `http://ip:3082/m`、回环 `http://ip:3080/m`),而 `lib/client.js` 又拼了一次 `mount`,二维码载荷遂变成 `/m/m`;App 端 `_pathOf()` 把整段路径原样当作请求前缀 → **扫码连接一律 404**。v3.0.0(`7c1291e` 引入 `+ mount`)以来两种模式都中招,不只是原 PR 描述的 LAN 桥模式。
551
- - 修复:仅当目标 url 自身不含路径时才补 `mount`;无法解析成 URL 时退回"直接补 mount"的兜底分支。载荷构造抽出具名纯函数 `buildQrPayload(data)` 并导出,新增 `test/qr-payload.test.mjs` 12 项回归(LAN 桥/回环/裸 url/自定义挂载/尾部斜杠/不可解析兜底/首选地址/空 urls/token 缺失/与 App 端正则兼容),已做变异验证(改回旧实现 7 项失败)。
552
- - **未收该 PR 附带的"二维码首选地址改为优先 Tailscale CGNAT(100.64/10)"**:那是独立的选路策略改动,与 `/m/m` 缺陷无关,且与本仓库既有的明确设计相反——`lib/index.js` 把 Tailscale 排在**最后**并写明理由("在家扫码时必须给手机可达的局域网地址……避免扫到组网 IP 而手机组网未开 → 黑洞"),CHANGELOG 也记录过同类事故。建议单独讨论(例如仅在没有任何局域网私有地址时才优先组网地址)。
553
-
554
- ### 文档与门禁修正
555
-
556
- - `docs/03-api.md` §6.15:命令描述符字段名 **`images` → `attachments`**(内核 `dsh-commands` 与 PC 端客户端均判定 `input.attachments === true`;旧文档写错会让按文档实现者永远读不到该标志)。另补充:GET 缺 `sessionId` 是 `bad-request` 而 POST 是 `missing-sessionId`(不同码);两条命令端点都**要求会话已挂载**,休眠会话 404,与 `/send` 的自动 resume 不同;`commands.execute` 的 15s→180s 整体超时语义。
557
- - `docs/03-api.md` §3.2:修正 `404 session-not-found` 的含义(带 `permissionPreset` 时休眠会话同样 404),并给"同一次 `next`"的保证加上限定条件(仅对已挂载会话成立,休眠会话仍会被 `foldFromEvents` 的 `Object.assign` 覆盖)。
558
- - 合并 #30/#31 后 `flutter analyze` 从基线的 0 issue 变为 41 项 info(39 项 `curly_braces_in_flow_control_structures` + 2 项 `use_null_aware_elements`),已用 `dart fix --apply` 机械化修回 **0 issue**(纯语法包装,不改变行为)。
559
-
560
- ### 已知未解决:issue #20(属上游核心)
561
-
562
- - 插件侧的降级读取(`degraded/current-surface`、`configDegraded`)在 #21 已落地,本版无改动;**缺陷本体在 DSH 核心**。
563
- - 本次复核了当前安装的 DSH 打包产物:`dsh-session-query.readSession()` 仍然调用 `Session.create(...)`(新建快照路径)而不是 `Session.fromRestore`,但上游**已加入逃逸口**——报错文案变为 `seeded session constructor seed must equal its inherited prefix or mark its inherited cut`,即当日志中存在 `session/end-seed` 且 `data.inherited === true` 的标记事件时可以跳过等长校验。因此该缺陷是否复现取决于目标会话的日志里有没有这个标记(旧版本 DSH 写入的会话可能没有)。插件侧这套降级仍需保留,直到核心改走 restore 构造路径。
564
-
565
-
566
- ## v3.1.5(build 40,issue #22)— 会话列表刷新不再堆积,宿主不会再被 App 打到饱和
567
-
568
- > 版本:插件 `3.1.5` / App `3.1.5+40`。本次**只改动了 App**——安装新 APK 即可,**无需同步插件副本,也无需重启宿主**。
569
-
570
- - **修复:手机 App 会把桌面宿主打到持续满载,导致会话列表一直转圈,连其它功能一起卡住。** App 每收到一个会话事件就重新拉一次会话列表,而一次列表请求在宿主上要把历史日志**整个读一遍**(会话攒多后以分钟计)。请求发出去之后既不等结果、也不管上一个是否还在跑,于是越堆越多——实测手机上同时压着 **16–18 个请求**、宿主 CPU 到 **171%**,**谁都完不成**,列表自然永远刷不出来。现在同一时刻只会有一个列表请求在飞;期间到达的刷新会合并成「结束后再补一次」,**既不堆积,也不会漏掉这期间的变化**。
571
- - **顺带修掉两个边界**:① 被合并的刷新现在会**等到补发真正结束**才返回——否则界面会先显示旧数据,随后数据更新了却不再通知,列表停在旧内容上;② **切换服务器地址后,旧地址未完成的请求不再挡住新地址的刷新**(此前换地址后首屏会被旧地址的慢请求卡住)。
572
- - 其余为新增回归测试(请求合并、等待语义、销毁后不再补发、跨地址不阻塞),并**逐条验证过「注入对应缺陷即变红」**,而不是只看它们通过。
573
-
574
- ## v3.1.5(build 39,issue #15)— 修复文件浏览的返回超时与右滑退出
575
-
576
- > 版本:插件 `3.1.5` / App `3.1.5+39`。本次改动了插件,需同步插件副本并重启宿主。
577
-
578
- - **修复:文件预览返回后偶尔报「无法读取目录:TimeoutException」**。文件下载响应是唯一没有显式声明「取完即断」的响应,于是沿用了服务端的连接复用默认值;手机侧连接池的空闲时间比它长,复用那条已被服务端半关的连接时,下一个请求会一直等到超时。预览时正好走的下载通道,返回时的列目录就复用了它留下的连接。现已与其余接口统一为「响应即断」,同类路径(更新包下载、二维码)一并补齐。
579
- - **修复:目录浏览页右滑会直接退出整个页面**,而不是返回上级。现在与同一操作栏里的 Git 页面行为一致:预览中 → 退回目录;子目录 → 回到上级;已在会话工作目录根 → 才退出页面。
580
- - 其余为新增回归测试(下载响应头、四种返回手势路径),并验证过「改回旧实现即变红」。
581
-
582
- ## v3.1.5(build 38,issue #15)— 文件浏览改挂对话操作栏、改为会话级,并修复打不开的问题
583
-
584
- > 版本:插件 `3.1.5` / App `3.1.5+38`。
585
-
586
- - **修正入口位置**:build 37 把「文件」放在了主页抽屉,实际需求是**对话界面右上角「对话操作」⋮ 展开的操作栏**。现改为与 Git 导航并列,抽屉里的入口已移除。
587
- - **修正浏览范围**:改为**当前会话的工作目录**(与同一栏的 Git 导航同源),不再是首页筛选选中的那个工作区。取不到会话工作目录时页面说明原因并可重试,不会退回到别处的目录。
588
- - **修复打开即报错**:build 37 点开「文件」会显示「无法读取目录:No host specified in URI …」。根因是控制器新建了一个 `Api` 实例,而它的服务端地址默认为空、要由启动流程载入——于是请求发到了一个没有主机名的 URL 上。现改为复用已配置的全局实例。
589
- - 操作栏新增动作后,旧设备上保存的图标顺序会自动恢复默认(仅丢一次自定义排序),不会出现缺项。
590
- - 客户端仍是只读浏览;服务端读取边界的加固仍由 GitLab `#16` 单独跟踪,本次未改动。
591
-
592
- ## v3.1.5(build 37,issue #17 #15)— 子代理列表排序与状态 + 侧边栏文件浏览
593
-
594
- > 版本:插件 `3.1.5` / App `3.1.5+37`。
595
- >
596
- > 注:本条中的「侧边栏『文件』入口」在 build 38 已按需求修正为对话操作栏入口,且该版本点开会报 `No host specified in URI`,见上一条。
597
-
598
- - 会话工具 →「子代理」:列表改为按**创建时间由近到远**排列(最新派生的在最上),此前不排序且活跃/休眠父会话顺序相反。
599
- - 子代理行显示**运行状态**:运行时方形虚线旋转、空闲不显示(复用会话列表同一套标识);状态取自实时推送,不再印英文 `running`/`inactive`(后者在休眠父会话下恒为 inactive,会与实际情况矛盾)。
600
- - 子代理行副标题改为「会话 id · 相对创建时间」,让排序依据可见;「中断」按钮只在真的运行时出现。
601
- - 侧边栏新增「文件」入口:只读浏览当前已注册工作区的目录结构与文件内容(issue #15)。
602
-
603
- ## v3.1.5(build 36)— 会话列表排序与运行状态标识
604
-
605
- > 版本:插件 `3.1.5` / App `3.1.5+36`。
606
-
607
- - 首页最近会话与会话列表按最新可见消息时间倒序;打开会话不再改变排序,旧插件缺少 `lastMessageAt` 时回退到 `lastActivity`、`createdAt`。
608
- - 隐藏子代理会话、保留用户 fork 会话,并使计数与可见项目一致。
609
- - 增加运行中与等待审批/问询状态标识;会话转为空闲时停止动效,并尊重系统减弱动态效果设置。
610
-
611
- ## v3.1.5(build 35,issue #13)— 对话操作栏:三个入口收敛为可排序图标栏
612
-
613
- > 版本:插件 `3.1.5` / App `3.1.5+35`。
614
-
615
- - 聊天页 AppBar 的 Git、会话工具、复制三个入口收敛为一个「对话操作」⋮ 入口,打开右侧窄幅图标栏;栏不挤压聊天内容,点外部或按系统返回即关闭。
616
- - 图标栏支持长按拖动排序,顺序按设备本地持久化,跨会话与重启保持;默认顺序为 Git → 会话工具 → 复制。
617
- - 三个动作的目标页与既有行为完全不变:Git 仍进入全屏只读浏览页,会话工具仍弹出原有底部弹层,复制仍导出当前已加载的对话。
618
- - 无会话与空对话的既有兜底行为不变(不跳转、提示「当前没有可复制的对话内容」)。
619
-
620
- ## v3.1.5(build 34,issue #10)— 修复分支图悬线与颜色碰撞
621
-
622
- > 版本:插件 `3.1.5` / App `3.1.5+34`。
623
-
624
- - 未被更近子提交连入的已选分支 tip 不再在节点前绘制悬空 lane;真实父子连线和分页延续保持不变。
625
- - 恢复归档 `app-git-management` 分支的浅色/深色调色板、31-based 引用哈希与碰撞探测,避免已选引用共享相同线色。
626
-
627
- ## v3.1.5(build 33,issue #10 note #379)— 分支图与只读 Git 安全修复
628
-
629
- > 版本:插件 `3.1.5` / App `3.1.5+33`。
630
-
631
- - 分支图修复 lane 拓扑错连与起点上方多余线段;保留真实父子连接、屏幕滚动和跨页延续。
632
- - 只读 Git 浏览禁用仓库配置的 fsmonitor、hooks 与 clean/process filters,清除继承的 trace 写入变量;超限输出明确失败。
633
- - 修复工作区变化期间的异步检查竞态,旧请求不再覆盖新鲜度状态。
634
-
635
- ## v3.1.5(build 32,issue #10 note #379)— 高饱和度分支图
636
-
637
- > 版本:插件 `3.1.5` / App `3.1.5+32`。
638
-
639
- - 提高浅色与深色主题下的分支图颜色饱和度,并保持引用颜色稳定和清晰对比。
640
-
641
- ## v3.1.5(build 31,issue #10 note #379)— 五引用分支图与黑白 Git 图标
642
-
643
- > 版本:插件 `3.1.5` / App `3.1.5+31`。
644
-
645
- - Git 菱形图标保留 45° 底板,浅色主题黑底白线、深色主题白底黑线。
646
- - 分支图允许同时选择最多五个本地/远程引用;默认引用计入上限,超出时阻止并提示。
647
-
648
- ## v3.1.5(build 24,issue #10 note #379)— 工作区浏览与标签配置
649
-
650
- > 版本:插件 `3.1.5` / App `3.1.5+24`。
651
-
652
- - Git 浏览器改为全屏只读页,支持设置 1–3 个有序标签页。
653
- - 新增已暂存、未暂存、未跟踪工作区分组,以及按需、受限的单文件差异预览。
654
- - 保留授权、快照校验、提交图分页和共享 lane 布局;工作区变化仅提示刷新,不提供 Git 写操作。
655
-
656
- ## v3.1.5(build 23,issue #10)— 移动端 Git 只读导航
657
-
658
- > 版本:插件 `3.1.5` / App `3.1.5+23`。
659
-
660
- - 新增会话级 Git 只读浏览:仓库、分支、提交图和提交详情。
661
- - 支持最多三个分支对比、图谱分页、提交文件分页和显式过期刷新。
662
- - 服务端严格校验会话工作区、仓库身份、OID/ref、快照游标及文件路径;不提供 Git 写操作、状态、diff 或 patch。
663
-
664
- ## v3.1.5(2026-09-22,issue #12 / #15 / #19 / #22 / #24,PR #11 / #18 / #21)— 对话时间线 + 用量额度 + 复制对话 + 安全加固
665
-
666
- > 版本:插件 `3.1.5` / App `3.1.5+22`。
667
- > 验收门禁:`node --test test/*.mjs`(28)、`node tools/timeline-contract-check.mjs`(77)、`node tools/account-usage-check.mjs`(15)、`node tools/account-usage-adversarial-check.mjs`(19)、`flutter analyze`(0 issue)、`flutter test`(133)。
668
-
669
- ### 对话时间线(工具调用与事件详情)
670
-
671
- - 对话页新增执行时间线:工具调用按 `callId` 关联调用与结果,合并为单张 Tool activity 卡片(参数/结果/失败态可展开);**未知但用户可见**的事件进入通用事件卡,不再因 App 尚未认识而被静默丢弃。
672
- - 新增**普通 / 调试**两种展示模式(设置页开关,默认普通):普通模式只呈现对话与工具活动;调试模式额外展示原始事件与按需加载的无损详情。**行为变更**:系统注入消息与协议/运行时记录在普通模式下不再占屏(v3.1.4 是折叠块),调试模式下仍可审阅。
673
- - 服务端新增 `GET /m/api/event-detail`(认证与其它 `/m/api/*` 一致):按 durable `seq` 取单事件详情,稳定错误语义 `session-not-found` / `event-not-found` / `session-corrupt` / `event-read-failed` / `event-detail-too-large`(8 MiB);原始异常与主机路径只进服务端日志,不进响应。
674
- - **事件保真与隐私边界(fail-closed)**:`assistant/chunk`、`assistant/attempt`、`request/header`、`request/context`、`session/title-llm-request`、`web/deepseek-search-llm-request`(LLM 请求快照,data 含 system prompt / messages)、`compaction/*`(含压缩摘要正文)、`system/message`(DSH 系统提示词)等内部记录在**历史、实时、详情三处都不下发**;详情面只对显式 allow-list 的可见类型返回原始载荷,其余类型不给 `detail` 指针、`/event-detail` 返回 404。其余未知事件仍在时间线保留(不静默丢弃)。
675
- - 详情正文在 App 侧统一钳制(默认 20000 字 + 截断标注):详情返回的是原始事件,不钳制会把超长工具结果直接送进 markdown 解析与文本布局。
676
- - `/api/history` 的表面过滤由白名单改为 `isTimelineRecord` 黑名单,并新增 `hasMore` durable cursor 分页(`after`/`before`/`limit` 非法值 → 400,与既有参数校验一致);降级(`historyMode: "current-surface"`)语义与 `configDegraded` 保持不变,降级会话的 `hasMore=false` **不代表历史到底**。
677
- - 验收门禁:`node tools/timeline-contract-check.mjs`(含「LLM 请求快照不得外泄」「未命名类型详情 404」两条新增断言)。
678
- - **修复工具卡片标题显示裸 `callId`**:服务端 `tool/result` 摘要在结果事件拿不到工具名时,用 `callId` 兜底写进了 `name`(`callId` 是关联 id,不是工具名);App 侧合并规则 `data['name'] ?? … ?? current?.name` 因此让结果事件覆盖了 `tool/call` 学到的真名。只有带结果的调用在历史里才有 `tool/result`,所以现象恰好是「历史回放=裸 `call_00_...`、进行中=真名」的分裂。修复为服务端不再下发该兜底(名字未知就省略字段),App 侧把「`name` == `callId`」一律视为未知并保留上一次已知工具名(`timelineToolNameOf` 单一实现,四处调用点统一);结果事件携带的真实错误名(如 `UserQuestionError`)与显式工具名照常保留。回归:`tool_name_callid_test.dart`(5 例)+ 契约检查 2 条断言。
679
-
680
- ### 手机用量与额度
681
-
682
- - 设置 → 账户新增“用量与额度”详情页与来源数量摘要。
683
- - 服务端新增 `GET /m/api/account-usage`:DeepSeek CNY 余额、dsh-codex-connect 当前活动 Codex 账户配额、OpenCode Go 5h/周/月套餐窗口;凭据只在电脑端使用,来源独立失败/隐藏。
684
- - Codex Credits 与个人消费上限独立展示;配额显示剩余百分比、重置时间和三档风险颜色;DeepSeek 原充值与金额预警保持不变。
685
- - 新增服务端归一化自检 `node tools/account-usage-check.mjs` 与 App 模型测试 `usage_model_test.dart`。
686
- - 悬浮球面板新增「用量与额度」区块:展开时按需获取 + 客户端节流(2 分钟),不可用(旧插件 / 未配置 / 失败)时整体降级为原有单行余额;金额行保留文字(CNY 优先)、配额行只出细条与颜色、不出数字(每个进度条上方居中显示 5h / 每周 / 每月 窗口短标签);面板不显示账户身份与附加 Codex bucket;配额窗口永不相加、不参与预警。点击区块/「详情 ▸」直达 App 用量页,「去充值」保留。顺带修复悬浮球余额取数非 CNY 优先(与详情页不一致)的问题。新增纯 Kotlin 面板模型 seam(JVM 单测 27 例)。
687
-
688
- ### 安全与健壮性加固(PR #11 存活审计 + 五份全量复核)
689
-
690
- - **修复子代理与目标面板必然失败**:插件发出的 RPC 端点在宿主注册表里不存在(`subagent/list`、`subagent/interrupt`、`goal/*`)→ 改为内核真名与 wire 形状:`subagents/list`(`parentSessionId`)、`subagents/interruptByParent`(`childSessionId,parentSessionId,mode`)、`goals/create|pause|resume|complete`(`{agentId, request{objective,maxGoalRounds?}}` / `{agentId, ref{id,revision}}`)。
691
- - **修复问询取消/超时把 `null` 当答案交给内核**(内核读 `.answers` 抛 TypeError):改为 `{kind:"rejected", error:{name:"UserQuestionError", message, code:"ASK_CANCELLED"}}`,四处调用点全覆盖;`/respond` 新增 `sessionId` 归属校验与 `validateQuestionAnswers` 结构校验(非法 400 且 pending 保留可重试)。
692
- - **修复 `/m/api/defaults` 静默提权**:改为与会话级路径同一守卫(`danger-full-access` 需 `confirmDanger===true`)+ preset 名白名单。
693
- - **修复 LAN 桥可被未鉴权请求崩溃**:不再把 `req.url` 原样拼进上游 URL(absolute-form 会同步抛 `ERR_INVALID_URL` → uncaughtException → 进程退出),改为正规解析 + 非 origin-form 直接 400。
694
- - **新增非 GET 跨站防护**:`Sec-Fetch-Site: cross-site|same-site` 或来源主机不在允许集(回环/本机网卡 IP/请求 Host/trustedHosts)→ 403;`Origin: null` 解析失败 fail-closed。App、curl、桌面原生客户端不受影响。
695
- - **修复浮动 promise 导致宿主退出**(`pushNotification` 未 await 未 catch → unhandled rejection 在宿主 fail-loud 下 = `exit(1)`)。
696
- - **修复持存队列"插队"静默丢消息**:会话无 live agent 时不再返回 200 却零投递,改为 409 `steer-unavailable` 并保留队列行。
697
- - **错误文本路径脱敏**:`/send`、`rpcError`、`/files`、`/directions` 等不再把内核原始 message(含主机绝对路径/cwd)回客户端;`pushContent: "standard"` 的推送正文同样脱敏。
698
- - **上传边界**:指定会话无法解析或会话无 cwd → 404(不再静默写进首个工作区根);文件名黑名单补 `\0`。
699
- - **App 侧**:修复陈旧 agent 状态导致的"发送键变停止 / 消息被静默转排队"(bootstrap 改为全量权威重建)、详情按钮一旦加载成功即永久失效、调试模式 24 万字符原文进 `SelectableText`、缩略图全分辨率解码(OOM)、问询卡换人时的 null-check 崩溃、叠层场景取消问询静默失败。
700
- - **子代理面板行可点(#11 复核补正)**:会话工具 →「子代理」原来只是纯展示(行上没有 `onTap`),现在整行可点 → 跳进该子代理会话,右侧补箭头提示;走统一入口 `openChat`,**返回时恢复原会话**(与「分支」流程同款语义:顺路看一眼,不改主会话)。`showSessionToolsSheet` / `openChat` 增加仅测试用的 `apiClient` 注入位(与 `ChatScreen.apiClient` 同款,生产路径传 null 即用全局 `api`),并新增回归 `subagent_row_navigation_test.dart`(面板不切会话 → 点行切到子会话且推入会话页 → 返回恢复原会话)。
701
- - **SSE 事件流取消窗口的连接泄漏**:`eventsRaw()` 判不出「订阅已取消」—— Dart 的 `StreamController.isClosed` 只反映 `closed` 位、不反映 `canceled` 位(取消后仍为 false,且取消后的 `add()` 不抛错)。取消窗口内晚到的响应于是挂上一条**无人再取消**的响应流:连接永不释放、线性累积;服务端用 `connections.size` 做配额依据(`maxConnections` 默认 16),攒满后手机直接 `503 too-many-connections`,且服务端会误判「手机在线」(审批 fail-close 判据被污染)——症状就是「必须把 App 划掉重开」。修复为显式 `cancelled` 标志;真机触发路径:`resume()`(bootstrap 失败 / 旧流 >45s 无心跳)、`switchBase()` → `disposeBridge()`、`_reconfigure()`。
702
- - **新增 SSE 与 #13 回归测试 5 个文件**:假 SSE 宿主(`bufferOutput=false` + 帧推送 + 条件轮询 + 请求计数)、API 层收帧与分帧、store 连接态与分发、`turn/end` → 无回复条目 → 兜底补拉全量 `/api/history`、取消窗口残留连接(修复前 0/1/3 → 修复后 0/0/0)。
703
- - **排查澄清(#13)**:`/compact` 后首条回复丢失的修复本身没有问题 —— 此前端到端用例跑不通是**测试载体**限制:dart:io `HttpResponse.bufferOutput` 默认 `true` 时,<8KB 的 SSE 帧既不上线也不受显式 `await flush()` 影响(实测 298B/2990B → 0 字节,8990B → 8106B),所以「有时收不到帧」与帧体积相关;关掉该缓冲后三层用例全部稳定通过。
704
- - 真机复验范围(下个版本装机):时间线卡片与模式切换、用量页与悬浮球区块、复制交互、休眠会话降级横幅、子代理/目标面板、问询取消/超时、审批链路。
705
-
706
- ## v3.1.4(2026-09-16,issue #14 / #12 / #13)— 离线待答不再丢 + 任务面板 + 注入折叠 + 压缩后重同步
707
-
708
- > 范围:插件侧三项(离线待答、ntfy 标题、诊断补齐)+ App 侧四项(横滑误触发、注入消息折叠、任务面板、`/compact` 后重同步与轮次兜底)。
709
- > 版本:插件 `3.1.4` / App `3.1.4+21`。回归脚本:`tools/verify-issue14-p0.mjs`(mock 宿主,37 项断言)、`tools/verify-issue14-live.mjs`(真机一键验收)、`dsh-mobile-app/test/issue13_logic_test.dart`(兜底判定单测)。
710
-
711
- ### issue #14 Bug2(同时是 Bug1 的真因):手机离线时审批/问询帧被整个丢弃
712
-
713
- - **现象**:必须在审批发生前就停在那个会话里,手机上才会出现审批卡;事后打开 App 什么都没有(只能在电脑上处理);App 关闭/被杀时收不到任何"需要你回答"提醒。
714
- - **根因**:`onEventsWaterfall` 第一行 `if (connections.size === 0) return;`(v3.1.3 第 3686 行)——手机没连就整段早退,于是 ① 待答**条目**不建、② 回放存储 `pendingFrames` 不写、③ 挂在同一函数里的 `needs-answer` **推送**也一起没了。而回放存储唯一的历史写入点在 `ctx.inject(["apiProxy"], …)` 的 mux 循环里,0.1.2-rc.1+ 内核已移除 apiProxy(实测 0.1.5-rc.2 全量搜 `apiProxy` 0 命中)→ 该通道是死代码,`pendingFrames` 永远是空 Map,`/api/events` 建连时的回放逻辑形同虚设。
715
- - **修复**:无论手机是否在线都建条目 + 写回放存储(`putAskReplay`,键 `a:<phoneId>` / `q:<phoneId>`,与 `/respond` 结算同源);**只在手机在线时才 arm fail-close 超时**(离线时是桌面端在处理审批,不能被手机侧 120s 超时误杀),离线条目改走 `armOfflineReap`(30 分钟后**只清理、不结算**);`holdApproval`/`holdQuestion`(mobile 接管路径)同样写入回放存储,接管期间断线重连也能拿回卡片。
716
- - **成对清理(报告人提醒的坑)**:`finishApproval`/`finishQuestion` 内统一 `dropAskReplay`,覆盖"手机先答 / 120s 超时 / ✕ 取消 / 桌面端先答(cancel 帧)/ 离线到期"五条路径——只修写入会出现"幽灵审批卡"(早已处理的请求被反复回放给 App)。
717
- - **关于推送归因**:报告人推测"`notifyNeedsAnswer` 的唯一调用点挂在 apiProxy 帧桥上",实测不成立——`$events` 瀑布内(审批/问询两个分支)本就有推送调用,真正的拦路者就是上面那行早退;按他的建议"把推送搬到 $events 路径"会搬进同一个函数而依然收不到。
718
-
719
- ### issue #14 Bug3:ntfy 标题丢失、正文变成一坨 JSON
720
-
721
- - **根因**:JSON payload 被 POST 到**主题地址**。ntfy 的 JSON 发布契约是 `POST /`(或自托管 base path 根)且 body 内带 `topic`;发到主题地址时服务端按**纯文本**处理整段 JSON。
722
- - **实测证据**:`POST https://ntfy.sh/<topic>` + `{"title":…,"message":…}` → 响应体无 `title` 字段、`message` 为 JSON 原文;改 `POST https://ntfy.sh/` + body 带 `topic` → `title` 正常返回。本机真实通道(微信 + ntfy)修复前的历史消息同样可见该症状。
723
- - **修复**:新增导出helper `ntfyPublish(url, payload)`(`lib/index.js` 模块级,单测友好)——解析配置里的主题地址,改为 POST 服务器根地址并在 body 内补 `topic`,兼容自托管带 base path 的部署(`https://host/ntfy/topic` → `POST https://host/ntfy/`);schema 注释与实现口径统一(此前文档写 text/plain + X-Title)。配置写法不变。
724
-
725
- ### issue #14 建议 6:诊断补齐可观测性
726
-
727
- - `checks.pendingFrames` 从"仅旧 apiProxy era 输出"改为**无条件输出**,并新增 `checks.pendingApprovals` / `checks.pendingQuestions`——现代内核下也能自查"离线时审批帧有没有被记下来"(正是本次排查现场);App 诊断页对计数字段已有「✅ 0 / ⚠ >0」渲染,无需 App 改动。
728
- - 新增每通道最近一次投递结果 `checks.push:<通道名>`(`ok 21:04:33` / `fail 21:03:10 · HTTP 401: …` / `idle(尚未投递)`)——排障时不必再翻服务端日志确认"到底发没发出去";事件推送与 `/api/push-test` 手动自检**两条入口都记录**(真机验收时正是它暴露了 Server酱通道当天额度已满:`fail · 超过当天的发送次数限制[5]`)。
729
-
730
- ### issue #14(App 侧):代码块 / 表格横滑误触发"加载更早",列表跳回该轮上方
731
-
732
- - **根因**:`_onLiveScroll` 不区分滚动来源——代码块/表格内部的横向 `SingleChildScrollView` 会把 `pixels=0` 的通知**冒泡**给外层 `NotificationListener`,被误判成"滚到视觉顶部"→ 每次横滑都触发 `_loadMoreInfinite()`,列表前插旧内容、视觉跳回。
733
- - **修复**:加 `n.depth != 0`(忽略嵌套滚动)与 `n.metrics.axis != Axis.vertical`(只认纵向)两道过滤——与报告人给出的两行建议一致。
734
-
735
- ### issue #12:系统注入消息改为**可折叠块**(不再当普通气泡铺屏)
736
-
737
- - **结构化判定(报告人建议的 `source` 路线)**:内核 `createUserMessage({ source })` 本就区分来源——真人 `kind: "user"`,注入为 `plugin` / `agent-instructions` / `tool` 等。插件 `summarizeEvent` 的 `user/message` 分支新增透出 `sourceKind`(旧内核无 source 则不下发该字段)。
738
- - **App 渲染**:`sourceKind` 非 `"user"` 的消息渲染成折叠块——一行摘要(类型标签 + 字数)、默认收起、点按展开;展开状态复用「思维链」同一套每消息覆盖存储(键前缀 `inj:`),列表回收重建不丢。原有 3 个关键词黑名单仍直接过滤(PC 端 GUI 也不显示的那三类)。
739
- - **效果**:技能目录、`[SCHEDULE REMINDER]`、压缩摘要、其它插件注入等不再以普通气泡占满屏幕。
740
-
741
- ### 新功能:会话任务清单面板(对齐 PC 端「任务」面板)
742
-
743
- - **数据源与 PC 端同一处**:内核 `dsh-tool-todo` 把整份清单以 `todo/write` 快照写入会话事件,并注册会话投影 `todos`(投影语义:最新快照生效、`turn/start` 清空)。
744
- - **插件**:① `summarizeEvent` 新增 `todo/write` 分支(条数 ≤50、单条 ≤200 字符、status 白名单校验);② 加入 `SURFACE_TYPES` → 历史补拉/重连回放也能拿到快照;③ 新增 `GET /m/api/todos?sessionId=`——直接读内核投影(`sessionProjections.stateOf(session, "todos")`),休眠/旧内核返回 `todos: null`。
745
- - **App**:输入框上方新增折叠面板——收起态一行计数(`1 进行中 · 3 待处理 · 4 已完成`),展开态完整清单(状态图标 + 完成项置灰);实时靠 SSE `todo/write`/`turn/start` 折叠,打开会话/断线重连后用 `/api/todos` 对齐一次(历史只有 50 条窗口,投影读法保证长时间工具链之后仍然准确)。
746
-
747
- ### issue #13:`/compact` 之后首条回复"消失"——真机未能复现 + 按报告人建议加两道兜底
748
-
749
- - **复现尝试(真机实时 + 历史两条路径,各两个会话)**:均未复现。逐条对账证明两端都正常——
750
- - 插件侧:`/compact` 后的 `assistant/message` 确实广播了(`seq=44/64/83`,textLen 63/37/501…),`/api/history` 里也在;
751
- - App 侧:事件被正常处理并渲染(`Chat: build itemCount` 16→19→21 递增),截图上压缩后的长回复(含表格、代码块、链接)完整显示。
752
- - 另核实:`assistant/chunk` 在 0.1.5 内核里**不是会话事件**(不在 `known-event-types`),所以"流式草稿被 `turn/end` 清空"这条路径在当前内核下不可能发生。
753
- - **但借这次排查确认了两处真实隐患,并按报告人的排查建议 2/3 落地兜底**:
754
- 1. **压缩后按新表面重载**:`/compact` 以 `surfaceOp.replace` 重写会话表面,手机此前**不重载** → 继续显示已被 shadow 的旧消息(与桌面端视图分叉)。现在收到 `compaction/end` 立即 `_load(reset: true)` 按当前表面重建。
755
- 2. **轮次兜底补拉**(报告人建议 3):`turn/end` 时若"本轮出现过真人提问、却没有渲染出更晚的回复条目",判定内容被静默吞掉 → 补拉一次历史(10s 节流防抖;判定抽成纯函数 `needsTurnEndResync` 并带单测,见 `test/issue13_logic_test.dart`)。
756
- - 对方环境里的真因仍未定位(已请其提供 App 日志 `Chat: SSE 事件 <type> seq=` 片段);本版两道兜底可让同类"静默丢内容"**自愈**。
757
-
758
- ### 验证
759
-
760
- - `node tools/verify-issue14-p0.mjs`:mock 宿主内跑真实插件代码,**37 项断言全绿**——push-test 自检记账 / 离线记账 / 离线推送 / ntfy 请求形状(本地假 ntfy 断言 URL 与 body)/ 重连回放 / 手机应答清理 / 对端先答清理 / 离线期间不结算 / 问询同契约 / `todo/write` 摘要与 status 白名单 / `sourceKind` 注入标记 / `/api/todos` 端点(缺参 400、未激活 null、投影映射与截断)。
761
- - 真机端到端(LAN 桥 + Android 17 + App 3.1.3+20,2026-09-16):杀掉 App(`mobileOnline=0`)→ 在新建会话里触发一次真实沙箱升级审批 → 诊断 `pendingApprovals=1 / pendingFrames=1`(旧版此处恒 0)→ ntfy 收到**带标题**的「⚠ 需要你回答 · a1bf1abb…a95b」→ 重开 App 出现回放审批卡 → 手机点「允许一次」→ 诊断归零、工具真的执行(探针文件写入成功);审批在手机离线状态下挂起 **>120s 未被 fail-close**(桌面端流程不受手机侧超时干扰)。见证脚本:`tools/verify-issue14-live.mjs`。
762
- - App 3.1.4+21 真机:任务面板 / 注入折叠 / 横滑不跳 三项截图与 logcat 逐项核对(详见 issue #14 / #12 回复)。
763
- - issue #13 兜底(App 3.1.4+21 真机):实时 `/compact` 后 App 日志出现「压缩完成 → 按新表面重载会话」+「重同步会话(按新表面重载)」并按新表面重建条目;正常轮次**不触发**兜底(无「轮次结束但无回复条目」日志),回复照常渲染;`flutter test` **28/28**(含 `needsTurnEndResync` 4 项)。
764
-
765
- ### 升级与验证(真机)
766
-
767
- 1. 电脑端:更新插件包(`dsh-mobile-remote-v3.1.4.tgz`)后**重启 DSH**(LAN 桥持有监听,不建议热重载)。
768
- 2. 手机端:安装 `DSH-Remote-v3.1.4.apk`(覆盖安装,登录态与数据保留)。
769
- 3. 验收:`POST /m/api/push-test`(设置 → 通知 → 发送测试通知)→ ntfy 收到的通知**应有标题**;随后关闭 App → 在电脑端触发一次需要审批的操作 → ntfy 应收到「⚠ 需要你回答」标题的推送,重新打开 App 应看到审批卡并可作答;诊断页 `pendingFrames` / `pendingApprovals` 应随状态归零(不留幽灵卡);会话里跑一次长任务(agent 会调 `todo_write`)→ 输入框上方出现「任务」计数条,点开可看清单。
770
-
771
- ## v3.1.3(2026-09-08,issue #9)— 审批/问询双端呈现(`approvalMode: both` 默认)+ 可配置策略
772
-
773
- ### issue #9:手机 App 在线时桌面端不再弹出审批/问询框(approval/request 被 prepend 接管短路)
774
-
775
- - **现象**:v3.1.2 起(内核 0.1.2-rc.1 审批决策改为单条 Cordis 瀑布),answerer 以 `ctx.on("approval/request", …, { prepend: true, global: true })` 注册——手机在线(SSE `connections.size > 0`)即接管并挂起 promise(120s)且不调用 `next()` → 排在其后的内核"转发桌面 GUI"监听(`dsh-api-remotes` → `$events` 远程事件)不执行 → PC 端不弹卡。手机独占与桌面呈现互斥,桌面用户无法在 PC 审批,只能等手机答或 120s 后 fail-close `unavailable`(issue 报告含源码级技术分析)。
776
- - **修复思路(插件侧,无内核改动)**:0.1.2 的桌面弹卡并不在瀑布监听内联完成——`dsh-api-remotes` 把两个 Agent 作用域瀑布(`approval/request`、`user-questions/request`)继续转成 typertGateway 的 **$events 远程事件广播**:每个 $events 客户端(桌面 GUI 是其一)收到同一事件副本,**任一客户端先回 `$events/result` 即结算**(`settleRemoteEvent`),其余客户端收 **cancel 帧自动收卡**(`finishRemoteEvent`)。因此瀑布监听者只要**不抢先消费(next() 放行)**,审批/问询就会广播到桌面 GUI 与本插件各自的 $events 客户端——插件在 `both` 模式下再挂一个**进程内 $events 客户端**(`ctx.typertGateway.openWireStream("$events")`),即可恢复 v3.1.1 帧桥"两端同显、任一端先答即生效"。
777
- - **新增配置 `approvalMode`(`lib/index.js` schema,cordis.patch.yml 配置,重启生效)**:
778
- - `both`(**默认**)——桌面 GUI 与手机同时收到审批/问询待办,先答生效、另一端自动收卡。需 DSH 0.1.2-rc.1+ / 桌面 v2.0.5+($events 通道);旧宿主自动降级 `mobile`(启动日志 + 诊断 notes 说明);
779
- - `mobile`——v3.1.2 行为:手机在线即由手机独占应答,离线 `next()` 交桌面 GUI(外出远程用);
780
- - `desktop`——一律 `next()` 交桌面 GUI,手机不弹审批/问询卡(常驻电脑前用)。
781
- - **双端结算语义**:手机在场时待办 120s 无应答 fail-close(审批 → `unavailable`、问询 → 跳过 null,与 v3.1.2 一致);任一端口答/超时/取消都会广播 resolved 帧——**其它手机端卡片同步收起**(v3.1.2 只结算不广播,超时/多手机时卡片残留)。
782
- - **`/m/api/respond` 增强(顺手修复 v3.1.2 遗留缺陷)**:① question/approval 条目支持按 `rpcId` 兜底匹配——修复 **v3.1.2 的 question 应答必失败**(App 端只回传 rpcId 不回传 questionId,原查找 miss → 走 apiProxy 降级 → 现代内核 503,问询只能在桌面答);② `kind=cancel` 在现代宿主下结算本地条目(审批 → `cancelled`、问询 → 跳过;v3.1.2 取消悬空、只能等 120s 超时);③ 结算统一广播 resolved 帧(见上)。
783
- - **诊断**:`checks.approvalMode`(生效策略)+ `checks.remoteEvents`($events 双端通道就绪与否)+ `notes` 首行策略说明;both 降级 / desktop 配置均有明确提示。
784
- - **兼容性**:App 协议零破坏(帧只增字段:requested 帧新增 `rpcId`,resolved 帧新增 `rpcId`/`questionId` 冗余字段,旧 App 按 key 取用、忽略未知字段);旧内核(0.1.1-rc.2 及更早)走 apiProxy 帧桥路径不受影响(era 互斥:0.1.2+ 无 apiProxy、旧内核无瀑布/$events,运行时分别探测)。App 侧小改(3.1.3+18):设置 → 环境诊断支持字符串字段与 notes 渲染(v3.1.3 前字符串行会按布尔误显示 ❌)。
785
-
786
- ### 其余
787
-
788
- - **修复(真机验证发现,v3.1.2 遗留)**:问询 `questions` 取值路径错误——内核瀑布值为**顶层 `questions`**(`userQuestions.ask` 传 `{questions, agent, signal}`,桌面 GUI `$on` 亦读 `request.questions`),插件接管与双端广播误取 `req.request.questions` → 手机端 `question/requested` 携带空问题列表,**手机问询在 v3.1.2 不可用**(与 `/respond` 缺 questionId 并列的第二根因)。修复:`holdQuestion` 与 `onEventsWaterfall` 两处改读 `questions`(`lib/index.js`)。
789
- - **修复(桌面端插件树加载崩溃)**:`approvalMode` schema 改用 schemastery `union`/`const` 表达——`z.enum` 不是 `@deepseek-ai/schemastery` 的 API(宿主桌面 v2.0.5 提供的 3.18.2 无此方法),此前桌面端加载插件树即抛 `TypeError: z.enum is not a function` 导致整树失败;改为 `z.union([z.const("both"), z.const("mobile"), z.const("desktop")]).default("both")`,语义(三值集合 + `both` 默认 + 非法值拒绝)与原意图一致。
790
- - **修复(真机验证发现,诊断显示)**:`runtime.form` 判定——桌面启动器未给插件进程置 `DSH_DESKTOP=1`,桌面版恒显示 `cli` 误导诊断;改以 `desktopBrowserAccess` 服务探测兜底(仅桌面版 v2.0.5+ 提供,与 LAN 桥同源判定),桌面版正确显示 `desktop`(新增顶层助手 `runtimeForm(ctx)`)。
791
- - **诊断精简(用户反馈)**:`services.apiProxy` / `checks.respondBridge` / `checks.frameBridge` / `checks.pendingFrames` 是 0.1.1-rc.2 及更早内核(apiProxy 帧桥 era)的探测项——0.1.2-rc.1+ 内核无 apiProxy,此前恒 ❌ 徒增噪音;v3.1.3 起**仅在帧桥实际激活时输出**,现代宿主不再显示(审批/问询状态看 `checks.approvalMode` / `checks.remoteEvents`),FAQ/docs/09 措辞同步。
792
- - **诊断可读化(用户反馈,App 3.1.3+19)**:设置 → 环境诊断的服务/端点实测项改为「中文名(英文 key)」显示——`approvalMode` 带取值说明(both · 双端同卡,先答生效 / mobile · 手机在线独占 / desktop · 仅桌面 GUI)、`remoteEvents` 等新字段有中文名、挂起待办非 0 时附语义提示;英文 key 保留便于复制粘贴排障。旧版 App(≤3.1.2)无此展示(字符串行按布尔误显示 ❌ 属显示限制,以插件日志/诊断 JSON 为准)。
793
- - **实时计数指标(用户反馈,服务端 + App 3.1.3+20)**:`/m/api/diagnostics` 新增 `runtime.metrics`——手机在线连接数(SSE)/ 运行中 Agent / 会话数 / 工作区数 / 推送通道数,替代早期恒真的占位探测(`sessionsList ≥0` 等永远 ✅ 的检查已不再输出);App 诊断页新增「实时指标」区展示;旧版 App 忽略新字段(协议只加不减)。
794
- - 审批/问询 answerer 常量收敛(`PENDING_TIMEOUT_MS` = 120s);超时定时器 `unref`(卸载不再残留句柄)。
795
- - 文档同步:README(功能/配置说明/dsh 版本基线)、FAQ(问询/审批弹窗类新增 issue #9 问答与 approvalMode 配置示例)、docs/09(§1 基线、§2.1 服务/机制表 + approvalMode 语义说明、§5 已知问题 12/13、§6 配置项)。
796
-
797
- ### 真机验证(2026-09-08 深夜,DSH Desktop 2.0.5 / 手机 Xiaomi 2509FPN0BC,App 3.1.2+17,LAN 桥)
798
-
799
- | 场景 | 操作 | 结果 |
800
- |---|---|---|
801
- | 审批双端·手机先答 | 合成审批 → 两端同弹 → 手机「允许一次」 | `allowed-once`;桌面卡自动消失 ✅ |
802
- | 审批双端·桌面先答 | 同上 → 桌面「允许一次」 | `allowed-once`;手机卡自动消失 ✅ |
803
- | 问询双端·手机先答 | ask_user_question → 两端同弹 → 手机回答 | 答案经 `$events/result` 返回;桌面面板自动收起 ✅ |
804
- | 问询双端·桌面先答 | 同上 → 桌面回答 | 手机问询框自动收起 ✅ |
805
- | `mobile` 模式 | 审批/问询仅手机弹(桌面不弹)+ 手机应答 | ✅(approvalMode 切换 + 重启生效) |
806
- | `mobile` 超时 | 手机在线不答 → 恰好 120s | `unavailable` fail-close ✅(手机卡自动收起) |
807
- | `desktop` 模式 | 审批/问询仅桌面弹(手机不弹)+ 桌面应答 | ✅ |
808
- | 取消路径 | 双端同弹 → 手机点 ✕ | `cancelled`;桌面卡自动收起 ✅ |
809
-
810
- 测试中发现并记录的边界:桌面 GUI 单「composer 待办槽」——审批卡悬置时若另一 pending 交互(问询)到达,会顶掉审批卡(客户端 UI 行为,非插件缺陷;真实使用中 agent 串行问询不并发)。
811
-
812
- ## v3.1.2(2026-09-05)— 新建会话权限死锁修复(issue #6)+ 宿主包 peer 化(issue #7)
813
-
814
- ### issue #6:默认权限预设为「完全访问」时,手机端新建会话必失败(risk-confirmation-required)
815
-
816
- - **现象**:设置页把默认权限预设设为 danger-full-access(该路径已带风险确认并成功保存)后,首页「新建会话」必失败——服务端 `lib/index.js:1753` 在建会话前校验 `confirmDanger !== true` 即返回 400,而 App 端 `doCreate` 只传 `permissionPreset`(跟随默认预设)、不传 `confirmDanger`,新建会话弹层也没有权限预设选择/风险确认入口 → 死锁:默认预设设成完全访问后,手机端永远无法新建会话。
817
- - **App(Flutter,3.1.2+17)**(`dsh-mobile-app/lib/screens/sheets.dart`):
818
- - 新建会话弹层新增「权限预设」行:默认跟随设置页默认预设;可逐项选择(danger 先过风险确认弹层,与设置页一致);
819
- - `doCreate` 兜底:目标预设为 danger-full-access 且未经本弹层确认时,先弹风险确认;确认后请求体显式带 `confirmDanger: true`;取消则终止创建(不再出现 400 死锁);
820
- - 风险确认弹层重构为可复用 `_askDangerConfirm`(返回 `Future<bool?>`),设置页路径(`showPermSheet → _showDangerConfirm`)行为不变(取消回权限列表、确认后 `applySessionConfig` 不变)。
821
- - 服务端无改动:契约本就要求显式 `confirmDanger`(docs/03-api §创建会话),App 侧对齐即可。
822
-
823
- ### issue #7:精确钉版 @deepseek-ai/* 共享宿主包 → 双实例(dual-package hazard)
824
-
825
- - `package.json`:`@deepseek-ai/dsh-llm` / `dsh-credentials` / `dsh-sandbox-policy` 从 `dependencies`(精确 `0.1.0-rc.6`)移至 `peerDependencies`(`>=0.1.0-rc.6 <0.2.0`);
826
- - `@deepseek-ai/schemastery` 一并 peer 化(`>=3.18.1`):宿主 0.1.2-rc.1 已是 3.18.2,原精确 `3.18.1` 已过期(与宿主版本分叉);
827
- - 效果:安装后不再在 profile 内产生并存第二份副本,Node 模块解析上溯宿主 bundle(插件 `~/.dsh/profiles/<profile>/node_modules/` → 宿主 `~/.dsh/profiles/node_modules/`),版本与宿主一致,双实例从根上消除;插件市场「可能遮蔽宿主版本」警告随声明方式修正而消失。
828
- - 验证:`pnpm install` 后插件 node_modules 无 `@deepseek-ai` 副本、无 `0.1.0-rc.6` 残留;`require.resolve` 命中宿主 0.1.2-rc.1;`dsh plugin list` 正常。
829
-
830
- ### 兼容性
831
-
832
- - 适配/验证组合:DSH 0.1.2-rc.1(DSH Desktop v2.0.5)——插件加载、LAN 桥(`0.0.0.0:3080 → 127.0.0.1:43120/m/api`)与口令认证均正常(`flutter analyze` 零问题,App 单测 24/24,`node --check` 通过)。
833
- - **DSH Desktop 2.0.5 浏览器门禁适配(真机调试发现)**:2.0.5 给桌面 WebServer 每个路由包了一层 desktop-browser-access 门禁——默认只放行带 `x-dsh-desktop-renderer` 能力头的 Electron 渲染器请求,手机经 LAN 桥的请求(即使 `x-mobile-token` 正确)一律 `403 forbidden`(`dsh-plugin-desktop/lib/webserver.js` → `decideDesktopBrowserAccess`)。修复:桥转发上游时,若同上下文存在 `ctx.desktopBrowserAccess`(桌面启动器提供),补传其 `rendererHeader`;桥的 LAN 面仍由插件 authToken(≥16 位)把关,不依赖「允许浏览器打开」设置,web profile/旧版桌面自动跳过。
834
- - **0.1.2-rc.1 RPC 网关适配(`lib/index.js` apiRpc 重构)**:0.1.2-rc.1 起内核 RPC 契约变化——①端点命名 `namespace.method` → `namespace/method`(`session.models`→`session/modelCatalog`、`session.history`→`session/control`、`goal.create`→`goals/create`、`subagent.*`→`subagents/*`);②载荷按新参数形态适配(多数端点收单参数 `request`,`subagents/interruptByParent` 三参数平铺,`session/modelCatalog`/`session/control` 零参数,`goals/create` 为 `agent`+`request`,`session/prompt` 需补 `requestId`);③桌面 2.0.5 后 `/api` HTTP 通道被浏览器门禁+会话 Cookie 鉴权关闭,插件内 fetch 必 403——统一改走进程内 `ctx.typertGateway.invokeRpc`(@Remote 网关,与宿主同域),旧宿主无网关时保留原 HTTP 路径降级。`readSessionConfig`/图像限额改读 `session/control` 投影(`modelSelection.lastUsed` / `imageLimits`),模型目录改读 `session/modelCatalog`(`groups` 结构不变)。
835
- - 注:DSH Desktop 采用 pnpm `nodeLinker: hoisted`,file: 插件是**物理拷贝**——改源码后必须 `pnpm install --force` 同步到 `~/.dsh/profiles/<profile>/node_modules/` 并重启生效。
836
- - **0.1.2 Session API 变更适配(真机定位)**:0.1.2 的 `Session` 类不再暴露 `.events` 数组,改用 `snapshotEvents()`/`eventAt()`/`seq`;`PermissionPresetService.current()` 也改为接收 session 对象(不再接受事件数组)。插件所有 `session.events` 访问点(`/history`、`sessionTitleOf`、`foldAgentPreset`、`readSessionConfig`、`/usage`)统一收敛到新增 `eventsOf(session)` 助手(兼容休眠快照 `{events}` 形态与旧版宿主),修复 App 打开会话「该会话暂不可用」与 `Cannot read properties of undefined (reading 'length'/'filter')` 崩溃。
837
- - **审批/问询移动端 answerer(0.1.2 机制再适配)**:0.1.2 移除了 apiProxy(旧帧桥失效,手机收不到审批卡);内核改为 Agent 作用域 Cordis 瀑布 `approval/request` / `user-questions/request`,answerer 须以 `global: true` 注册(dispatch 从宿主服务的 fiber 分发并做 agent 作用域过滤,非 global 的根监听不入选)。插件注册 `{ prepend: true, global: true }` 监听:手机在线(SSE)即接管(转发 `mobile/frame` + `/respond` 结算),离线/超时 fail-close(`unavailable`,与内核一致),拒接 `next()` 落回桌面 GUI answerer。与官方 `packages/api/remotes` + `packages/client/ui-approval` 同形态(已对照 deepseek-ai/deepseek-harness dsh-v0.1.2-rc.1 源码逐行验证)。
838
- - **休眠会话自动恢复**:`/send` 遇到休眠会话(桌面重启后)调用 `agents.resume({ resumeSessionId, agentOptions, setup })` 自动重挂 agent(先前必 404 session-not-found);`readSessionConfig` 对休眠会话从日志折叠配置(`model/selection`、`agent-preset/selected`、`permission/preset`),修复重启后聊天页「模型/权限」标签为空。
839
- - **合成审批测试端点**:`POST /m/api/dev/approval-test`(需口令)——复用内核同款 Agent 作用域瀑布(`scopeTarget(agent, agent)`),绕过 turn-enclosed 校验,用于不依赖模型行为的审批链路验证(手机接卡 → 批准/拒绝 → `/respond` 结算 → 瀑布返回 outcome)。新增 peer:`@deepseek-ai/dsh-scope`。
840
- - **B站反馈落地**:
841
- - **文件传输(csborbbnc 反馈)**:服务端新增 `GET /m/api/files?path=`(下载,流式+Content-Disposition,MIME 按扩展名)与 `POST /m/api/files/upload`({sessionId, name, data base64} → 写入会话工作目录,64MB 上限;与目录选择器同信任模型:口令鉴权+现有限流);App 端 composer「⊕ 更多」新增「上传文件」(Android 系统文件选择器,原生通道 `dsh/files`)与「下载文件」(输入电脑路径 → 保存到手机「下载」目录,Android 10+ MediaStore、更早版本应用下载目录,零新依赖)。
842
- - **自由复制(csborbbnc 反馈)**:消息文本本已支持选中复制/操作栏复制——本次补齐**代码块复制按钮**(复制全文 + 行数提示)。
843
- - **干活完提醒可靠性(小小的甜菜 反馈)**:推送超时 10s→15s,并对网络层失败(DNS 抖动/连接重置等)重试一次(HTTP 4xx/5xx 不重试,避免配额错误空转)。
844
- - **微信/IM 提醒通道(v3.1.2 第二波)**:新增**企业微信群机器人 Webhook**推送格式(`format: wecom`,国内稳定、免登录态、约 20 条/分钟限额);新增「**发送测试通知**」(App 设置 → 通知 + `POST /m/api/push-test`——逐通道验证、绕过节流,配置后一键确认通不通);docs/06 §6 通道推荐重构(企业微信机器人 / Server酱 / Bark;ntfy.sh 境内不可直连警示);docs/07 FAQ Q4 补充验证步骤;README 新增「微信入口(可选):dsh-im」推荐章节(IM 对话入口与本插件互补:dsh-im 管对话、本插件管控制台+提醒)。
845
-
846
- ## v3.1.1(2026-08-26)— WSL/类 Unix 平台路径选择修复(issue #5)
847
-
848
- ### 现象
849
- - GitHub issue #5:服务端运行在 WSL(dsh 跑在 Linux 侧)时,移动端「新建会话 → 工作目录」无法正确选择路径——从根目录 `/` 进入 `home` 会拼成 `/\home`,随后报「读取失败」(服务端 `readdir` ENOENT),后面的目录全部无法浏览/选择。
850
-
851
- ### 根因
852
- - 目录选择器按 Windows 习惯硬编码 `\` 拼接子目录(`_openDir`),服务端为 POSIX 时 `/\home` 是非法路径;
853
- - 深一层:工作区路径在 App 侧被 `_normPath` 全量归一成 `\home\user` 形态——WSL 上该形态会被直接当作 cwd 发回服务端(建会话目录不存在),工作区列表展示的也是 `\` 形态(与 PC 端观感不一致)。
854
-
855
- ### 服务端(lib/index.js)
856
- - `GET /m/api/directories` 根视图响应新增 `sep` 字段(服务端真实路径分隔符,纯增量,旧版 App 忽略即可);
857
- - 新增 `normalizeServerPath`:目录浏览/新建文件夹/建会话 `cwd` 的路径参数按当前平台归一化分隔符(POSIX `\`→`/`,Windows `/`→`\`)——旧版 App 在 WSL 上拼出的 `/\home` 也能命中真实目录(兼容矩阵「插件新 + App 旧」成立)。
858
-
859
- ### App(Flutter,3.1.1+16)
860
- - 目录选择器:新增 `joinDirPath`/`dirSepOf` 纯函数,按服务端 `sep` 拼接子目录(WSL `/`、Windows `\`),根视图文案与分组标题随之自适应(「根目录」/「所有盘符」);
861
- - 工作区列表:条目保留服务端原始路径(展示与 cwd 回传用原始形态),规范化只用于匹配比较——`refreshWorkspaces`、主界面工作区弹层、会话页筛选、新建会话默认目录四处同步;
862
- - 新建会话默认目录:匹配改为规范化比较,不再把归一形态当作 cwd 发送。
863
-
864
- ### 测试
865
- - `flutter test test/dirpicker_logic_test.dart`(新增 6 例:joinDirPath 两种分隔符/根视图、dirSepOf 服务端优先/根视图推断/兜底);
866
- - `tools/wsl-path-check.mjs`(新增:normalizeServerPath POSIX/Windows/非字符串,8/8 通过;注:`/\home` 归一为 `//home`,POSIX 下与 `/home` 等价);
867
- - `flutter analyze` 零问题、`node --check` 通过。
868
-
869
- ### 生效
870
- - 服务端改动随 DSH 重启生效(旧版 App 即可获得 WSL 浏览修复);App 修复随新 APK(3.1.1+16)生效。
871
-
872
- ## v3.1.0(2026-08-25)— 思维链折叠 + 流式滚动跟随修复 + 悬浮球会话标题(社区 PR #4,marktrue-ahu)
873
-
874
- ### App(Flutter)
875
- - 对话页新增「思维链折叠」:assistant 回复携带思维链正文时,正文上方渲染可折叠「思维链」块(图标 + 字数 + 箭头,点按展开/收起);单个消息独立切换。
876
- - 设置 → 显示 新增「思维链默认展开」开关(持久化,默认关=折叠),控制思维链块的默认展开状态。
877
- - 修复流式输出不跟随滚动到底的问题:按「停留底部」钉住状态决定自动跟随,替代与增长中的 maxScrollExtent 比距离,大段 chunk 单帧推高内容后不再掉队。
878
- - 悬浮球「运行中的会话」改展示会话标题(超宽省略号截断),替代 session id 短码。
879
-
880
- ### 服务端(lib/index.js)
881
- - `assistant/message` 摘要新增 `reasoning` 字段(思维链正文,仅非空时下发,≤20000 字符),供移动端折叠块使用;历史与 SSE 同步生效。
882
- - `/api/bootstrap` 的 agents/sessions 新增 `title` 字段(会话标题,空则兜底短码),供悬浮球「运行中会话」展示标题。
883
-
884
- ### 审核中发现并修复(维护者)
885
- - **思维链折叠状态丢失**:手动折叠后,列表滚动回收/退出重进/重启会恢复默认展开。修复:折叠状态上移 AppStore 按会话+消息持久化(prefs,每会话 100 条 + 全局 500 条软上限)。
886
- - **新建会话缺模型崩溃**:`POST /sessions` 不传 model 时首轮报 `{{model}}` 组装无值。修复:无显式 model(含仅传 reasoningEffort)统一绑定内核默认模型;失败时 `handle.dispose` 拆除会话并明确报错。
887
- - **技能目录不注入**:插件建会话漏传 `setup`(预设组装挂载),与 PC 端 `session.create` 契约不一致。修复:`agentPresets.resolve` + `mount` 挂载,技能目录恢复注入;resolve 失败硬拒绝。
888
- - 模型探测报错可操作化(401/403、断连、404 中文引导);模型提供商页新增已存密钥提示与官方 DeepSeek「内置可用」标识。
889
- - 诊断接口新增 notes(技能目录状态披露);`clampText` 截断提示计入上限(输出严格 ≤ max)。
890
- - docs/05 新增 F-20~F-22 用例;docs/09 补充字段级兼容与降级说明。
891
-
892
- ## v3.0.0(2026-08-22)— LAN 桥:桌面版局域网直连(无需穿透)(二次 Code Review 落实集成于本版本内)
893
-
894
- ### App 自动更新(2026-08-23,App 3.0.0+8)——双更新源检查 / 下载 / 签名预检 / 安装
895
- - **双更新源**(设置 → 关于,单选持久化,默认 GitHub):**GitHub Releases**(`releases/latest` 取首个 `DSH-Remote-*.apk` 资产直连下载)与 **dsh 运行主机**(插件新增 `updateDir` 配置 + `/api/update/manifest`、`/api/update/apk` 两个带 authToken 鉴权的端点,manifest 为唯一权威)。切换立即生效;GitHub 不可达时明确提示可切主机源。
896
- - **检查触发**:设置页「检查更新」手动按钮(结果三分支:已是最新 / 确认弹窗 / 失败原因)+ 启动连接成功后静默自动检查一次(命中才提示:首页横幅 + 设置页版本行「● 有新版本」常驻,直到安装拉起或版本变更)。
897
- - **版本判定(纯 Dart 模块 `update_core.dart`,14 例单测兜底)**:先比 major.minor.patch 再比 build;远端未显式带 `+build`(如 tag `v3.0.0`)时主段相等即不提示——本地热修 build 不被误判为降级;远端显式 build 更小按防降级异常处理(不更新并提示)。
898
- - **下载与安装**:确认弹窗(版本/说明/大小/来源)→ App 内流式下载(进度可取消,取消即中止流并清理半成品文件)→ 主机源 sha256 校验 → **签名预检**(本机与下载 APK 证书 SHA-256 比对,不一致或读取异常一律取消并明确提示)→ FileProvider 拉起系统安装器。Android 8+ 首次安装由系统引导授权「安装未知应用」。
899
- - **签名预检实现(AGP 9 适配)**:AGP 9 产物为纯 v2 签名(apksigner 实测 v1=false,`getPackageArchiveInfo(GET_SIGNATURES)` 读不到签名)——签名读取改用 **API 28+ `GET_SIGNING_CERTIFICATES`(signingInfo,兼容 v2/v3)**,API<28 回退 GET_SIGNATURES;构建签名配置改用 AGP 9 DSL(`enableV1Signing/enableV2Signing`)。
900
- - **发布链路**:新增 Linux/WSL 发布脚本 `package-release.sh`(与 Windows `package-release.ps1` 等价);manifest.json 由共享生成器 `tools/gen-manifest.js` 统一生成(version 含 build / sha256 / size / notes=CHANGELOG 最新条目全文,JSON 合法且无 BOM),产物可用 `tools/verify-update-manifest.mjs` 校验。部署者把 APK + manifest 放进插件 `updateDir` 即完成主机源发布。
901
-
902
- ### 图像发送 64KB 误限修复(2026-08-23 热修)
903
- - **根因**:`readJson(req, res, limit)` 从不接受第三个参数——`/send` 传入的 `64 * 1024 * 1024` 被静默丢弃,实际按 `readBody` 默认 **64KB** 计;图片 base64 一旦超过 64KB(≈48KB 原图,任何真实照片都超)即触发 `req.destroy()`,响应发不出去——手机端表现为「Connection reset by peer (errno 104)」;经 LAN 桥时桥把上游断连转成 502「upstream webserver unreachable」。
904
- - **修复**:`readJson` 接受并透传 `limit`(其余 20 处调用不变,仍 64KB);`/send` 增加 `content-length` 预检——声明超 64MB 直接回 `413 payload-too-large` JSON(桥可透传),不再让客户端只看到 RST/502 拿不到原因;无 content-length 时仍由 `readBody` 流式兜底。
905
- - **验证**:140KB 图片 payload 实测——修复前桥返回 502「upstream webserver unreachable」;修复后正常解析进 `/send` 语义(agent 不存在 → `404 session-not-found` JSON)。App 端无需改动(服务端热修,无需重装 APK)。
906
-
907
- ### 图像发送两个误报修复(2026-08-23 热修 02)
908
- - **「发送未被接受」误报**:插件图片路径(steer/running 持存/idle 三处)的 200 响应缺 `accepted` 字段,App 端 `r['accepted']==null` → 误弹「发送未被接受」(其实图片已发出,用户以为没发出会重复发送)。修复:图片路径响应补 `accepted: true`;文本路径不受影响。
909
- - **「Declared image type does not match its bytes」**:App 按**文件扩展名**声明 mediaType,微信/浏览器保存的图片常是 WebP 顶 `.jpg/.png` 名字,与真实字节不符 → 内核字节校验拒绝。修复(双保险):
910
- - 服务端 `/send`:对每张图片按字节魔数(PNG/JPEG/GIF/WebP/HEIC)嗅探真实类型,声明不符**自动纠正**(warn 记录),未识别原样交内核裁决;
911
- - App `_sendImages`:发送前按字节嗅探(扩展名只作兜底),嗅到白名单外类型(如 HEIC)给出明确「不支持的图片格式」提示(随下个 APK 版本生效)。
912
- - **验证**:嗅探器单元验证——PNG/JPEG/WebP 前缀识别正确、jpg 名 WebP 字节纠正为 webp、随机字节返回 null;服务端热修随 DSH 重启生效,App 侧修复随下个 APK 生效。
913
- - **限额兜底偏差**:插件 `imageLimitsDefaults.maxMessageImageBytes` 误写 20MB(内核默认 `DEFAULT_MAX_MESSAGE_IMAGE_BYTES = 200MB`),内核 projection 取不到时 App 端总大小会被错误限制在单张额度;已修正为 200MB,App `_sendImages` 兜底同步(下个 APK 生效)。
914
-
915
- ### App 图像发送 UI 两处修复(2026-08-23 热修 03,App 3.0.0+6)
916
- - **气泡图片"显示不全"**:气泡高度按 `(236/ratio).clamp(80, 236)` 计算——竖图(比例≈0.46)被压成 236×236 方形,再配合 `BoxFit.cover` → 只显示图片中间一条(点开全屏才全)。修复:比例上限放宽到 0.3~3.0、高度上限 480,渲染改 `BoxFit.contain`——竖图完整显示,不再裁切。
917
- - **图+文发送后输入框文字残留**:文本路径有 `_inputCtrl.clear()`,图片路径 `_sendImages` 发送成功(含排队持存)后未清空输入框——用户误以为没发出去会重复点发送。修复:accepted 后清空(仅当输入框文字未改动时)。
918
-
919
- ### 发送失败误报与连接复用竞态修复(2026-08-23 热修 04,App 3.0.0+8)
920
- - **现象**:手机发「图片+文字」,会话里消息已出现、agent 已开始应答,但 App 弹「发送失败:Connection reset by peer」,且输入框文字与图片残留——用户会误以为没发出而重复发送。
921
- - **根因两层**:
922
- 1. **App 把「传输层报错」等同于「未送达」**:`/send` 是「服务器收到后才回包」的模型,reset 若发生在响应回程,消息实际已入会话;`_sendImages`/`_send` 的 catch 一律报失败并保留/丢弃草稿,无法区分(已实测复现:服务端 `/send hit` 正常、消息入会话、agent 应答,客户端仍报失败)。
923
- 2. **keep-alive 复用竞态(reset 的主要来源)**:手机 dart:io 连接池 idle 15s 与 Node 服务端 keep-alive 5s 存在半关复用窗口,复用已关 socket 即表现为 reset;LAN 桥还透传上游 keep-alive 响应头并复用上游连接,把竞态窗口又放大了两层。
924
- - **修复**:
925
- - App:发送失败后**对账**(history 近 20 条 user/message 文本+图片数匹配,或队列同文本行)——已送达则清空草稿并提示「已送达:刚才网络波动,请勿重复发送」;真未送达才保留草稿(文本路径恢复输入框、撤回乐观气泡)供重试;`history`/`queue` 支持自定义超时(对账 8s,避免断网时久等)。
926
- - 服务端:所有 JSON 响应与 `/attachment` 强制 `connection: close`(SSE 除外)——关闭复用竞态窗口;LAN 桥禁用上游连接池(`agent: false`)、响应头强制 close(SSE 保持 keep-alive);桥 upstream 错误路径补 warn 日志(此前 reset/销毁全静默,排障无迹可查)。
927
- - **验证**:本机经桥同构重放(2 图 1.26MB + 文本)200/0.48s——服务端链路健康,问题在响应回程与客户端判定;`node --check` 与 `flutter analyze` 通过;服务端修复随 DSH 重启生效,App 修复随下个 APK(3.0.0+8)生效。
928
-
929
- ### Codex 复审 P2 修复(2026-08-24 热修 08,App 3.0.0+14)
930
- - **P2-1 认证/限流等确定性错误不再走"结果未知"回执流程**:`isDefinitiveSendRejection` 白名单补充投递前明确拒绝的 5 个错误码——`auth-required`(401)、`rate-limited`(429)、`host-not-allowed`(403)、`loopback-only`(403)、`method-not-allowed`(405)。命中即判失败并保留草稿;`bridge-unavailable`/`receipt-pending`/网络 reset/超时仍走回执对账(已有测试锁定)。
931
- - **P2-2 回执 TTL 全量清理**:新增顶层纯函数 `pruneReceiptMap`(TTL+上限全量清理、返回是否有删除;恰好 TTL 边界不算过期);`/send` 查重前与 `/send-receipt` 查询前调用 `pruneReceipts()`,有变化才 `persistReceipts()`——未访问的旧回执同样被清理,不再滞留内存与 JSON 文件。
932
- - **测试**:`test/hotfix07_logic_test.dart` 新增认证/限流/Host 拒绝 5 例(18/18 通过);`tools/hotfix07-unit-check.mjs` 新增 pruneReceiptMap 4 例(清过期/保边界/返回标志/上限裁剪,14/14 通过);`flutter analyze` 零问题、`node --check` 通过。
933
- - **验证**:DSH 重启加载新插件后 `DSH_MOBILE_REMOTE_DROP_RESPONSE=1` 钩子场景真机验证(文本/纯图片 → 回程断开 → 回执 `done`、同 requestId 不重复投递);关闭钩子后正常发送验证。requestId 主流程、40MB 上限、图文渲染均未改动。
934
-
935
- ### Codex review 修复(2026-08-24 热修 07,App 3.0.0+12)
936
- - **P1 发送异常不覆盖新输入**:`_send` 三处异常恢复改为 `_restoreDraftIfUntouched`——仅当输入框仍为空(本次发送清空后的预期状态)才回填旧草稿;发送期间用户已输入新内容一律保留。顶层纯函数 `draftAfterFailure` 供单测。
937
- - **P2 回执 TTL 读取时生效**:`receiptExpired` 顶层纯函数(默认 15 分钟);`/send` 查重前与 `/send-receipt` 查询前清理过期回执并持久化——服务闲置 15 分钟后旧回执不再被命中,与文档一致。
938
- - **P2 签名纳入发送语义**:`composerSignature(sessionId, mode, text, imagePaths)` 替代旧签名(会话+最终生效模式+文本+图片路径);`steer` 空闲降级提前到图文分流之前——排队结果未知后改用插队会获得**新 requestId**,插队真正执行而非回放旧排队结果。
939
- - **P2 不误删用户手打 `[图片]`**:占位移除改在**服务端**——`blocksToText` 增加 `imagePlaceholder` 开关,`user/message` 摘要以 `imagePlaceholder:false` 生成文本(图由 `images[]` 图卡渲染);客户端剥离逻辑整体移除,用户原文原样保留。旧会话历史即时按新规则(`/history` 实时重摘要)。
940
- - **测试**:`tools/hotfix07-unit-check.mjs` 10/10(占位开关/手打保留/images 元数据/TTL 边界);`test/hotfix07_logic_test.dart`(草稿恢复决策 + 签名差异 + 明确拒绝白名单 2 例);`flutter analyze` 零问题、`flutter test` 17/17、`node --check` 通过。
941
- - **修正(+13)——桥 502 不误判定失败**:DROP_RESPONSE 真机验收发现——服务端处理完才切断回程,桥会把该切断翻译成 `502 bridge-unavailable` 返回;原代码把**所有** `ApiException`(除 receipt-pending)当"明确拒绝",导致这种"服务端已接收但回程断开"被误报失败、不回执对账。新增 `isDefinitiveSendRejection` 错误码白名单(empty-text/payload-too-large/session-not-found/send-failed/attachment-error/invalid-requestId/bad-request/not-found/no-live-agent/agents-unavailable),仅命中才判失败;其余(bridge-unavailable/receipt-pending/传输层)一律走回执对账 → 弹「已送达,请勿重复发送」。
942
- - **生效边界**:服务端改动随 DSH 重启生效;`64110b3`(requestId 校验顺序)与本次一起在重启后上线;App 修复随 APK 3.0.0+13。
943
-
944
- ### 用户消息图文顺序对齐 PC 端(2026-08-23 热修 06,App 3.0.0+10;修正 +11)
945
- - **现象**:移动端用户气泡先文本、后图片,且文本里带服务端为 image 块生成的「[图片]」占位行(`blocksToText`);PC 端是**图片卡片在前、文本在后,且无占位**——移动端与 PC 观感不一致、占位与图卡重复。
946
- - **修复**(纯展示层,协议零改动):用户气泡重排为 `_ImagesGrid` 在前、`Text` 在后;带图时剥掉独立成行的「[图片]」占位(真实图由图卡渲染),无图消息文本原样(不误删用户手打的 [图片]),纯图消息只剩图卡。服务端不动(占位在队列预览等上下文仍有价值)。
947
- - **修正(+11)**:对照 PC 实际样式进一步对齐——图卡与文本改为**两个独立气泡**(图卡一个卡片、文本一个卡片,垂直相邻),不再共用一个容器,与 PC 端"图片卡 + 文本气泡"分离式渲染一致。
948
- - **验证**:`flutter analyze` 零问题;真机确认图文消息与 PC 同构;历史消息自动按新规则渲染。
949
-
950
- ### 发送回执幂等化与限额统一(2026-08-23 热修 05,App 3.0.0+9)
951
-
952
- - **背景(P0 修正)**:热修 04 的客户端"对账"(按历史文本+图片数/队列文本猜测是否送达)存在**静默丢草稿**风险——空文本图片发送(只发截图)会跳过文本比对、命中任意同图数旧消息即判"已送达"并清空待发图片;同文本旧消息同理。送达确认的正确层级是**协议层回执**,不是客户端启发式判断。
953
- - **服务端(requestId 幂等回执)**:
954
- - `/send` 支持 `requestId`(UUID 形态校验,非法 400);投递**之前**占位 in-progress,处理完成后记录结果快照;同一 `sessionId+requestId` 重复请求**直接返回第一次结果、不再二次投递**(处理中重复请求回 409 `receipt-pending`);
955
- - 新增 `GET /m/api/send-receipt`(只查不投);回执 TTL 15 分钟、上限 2000 条,持久化 `~/.dsh/mobile-remote/send-receipts.json`(重启恢复,处理中状态不跨重启保留);边界文档化:单进程内 + TTL 幂等,超期同 id 重试可能重复投递一次;
956
- - 测试钩子:`DSH_MOBILE_REMOTE_DROP_RESPONSE=1` —— /send 处理后销毁连接不回包,用于模拟"服务端已接收但回程断开"。
957
- - **App**:
958
- - requestId 与**草稿内容绑定**(文本+图片路径签名):失败重试复用同一 id(服务端幂等,不重复投递);草稿被编辑才换新 id;
959
- - 传输层错误(reset/超时)或 409 → 有界轮询回执(4×1.2s):`done` → 清草稿+「已送达,请勿重复发送」;`error` → 明确失败;查不到 → 保留草稿+「发送结果未知:请稍后点重试,重试不会重复发送」;服务端明确拒绝(400/413/404/500)→ 直接失败并重置 requestId;
960
- - 图片发送同一套机制;**移除热修 04 的 `_reconcileSent` 启发式对账**;
961
- - **限额统一**:客户端图片总量上限 40MB(64MB HTTP body 扣 base64 膨胀与 JSON 开销后的安全值)——超限客户端明确提示,不再落到服务端 413;内核侧 200MB 能力不变(PC 端同源)。
962
- - **保留热修 04**:`connection: close` / 桥 `agent: false` / 桥错误日志——缓解半关连接复用,但不作为送达保证。
963
- - **测试**:`tools/hotfix05-check.mjs`(无投递用例:非法 requestId 400、未命中回执 404、缺参 400、>64MB 声明 413;`DSH_MOBILE_LIVE=1` 追加:文本/空文本图片发送+回执 done+同 id 重试结果一致);`flutter analyze` 零问题、`node --check` 通过。
964
- - **验证方法**(如何证明"不重复发送、不静默丢草稿"):① 同 requestId 重试返回相同 messageId(不二次投递);② `DSH_MOBILE_REMOTE_DROP_RESPONSE=1` 重启后真机发送 → App 显示「已送达」且草稿清空(回程断开不影响判定);③ 断网发送 → App 保留草稿提示「结果未知」,恢复网络后点重试仅投递一次。
965
-
966
- ### 图像链路 v2(2026-08-23,App 3.0.0+7)——tool/result 嵌套图片 + GIF 动图
967
- - **tool/result 嵌套图片(对齐 PC 端 contentParts 语义)**:实测内核 `read_image` 等工具结果的图片块**嵌套在 `tool-result.content` 内**(非消息顶层,此前 `imagesOf` 顶层收集漏掉 → 移动端助手消息只有占位/空白)。修复:
968
- - 插件新增 `imagesOfNested()`(递归展开 tool-result.content),`assistant/message` 与 `tool/result` 摘要改用——SSE/history 事件摘要均带出嵌套图片引用(`{attachmentId, mediaType, width?, height?, name?}`,≤20 张);
969
- - `tool/result` 摘要重写:文本跨全部 content 块合并(原仅 content[0])、callId/name/isError 从各块聚合;
970
- - **App 零改动**:助手气泡本就有 `_ImagesGrid` 渲染(与用户图同套组件:宽高比/全屏/重试/LRU),摘要带出来后自动显示——与 PC 端"消息内容图片统一渲染"同构。旧版 App 忽略新字段,无破坏。
971
- - **GIF 动图(结论修正)**:动态播放链路已就绪——Flutter 原生支持(`MultiFrameImageStreamCompleter` 逐帧播放)、App 上传原始字节(日志实锤 24114B = 2×12057B 完整 GIF)、插件字节嗅探正确纠正为 `image/gif`、大图角标已加;**但发送后显示静态**——实测(复刻 PC 内核 RPC 注入同一 GIF):内核附件规范化(`normalizeImage`,`canPassThroughNormalization` 明确把 `image/gif` 排除在直通外)取首帧重编码为静态 PNG(143B),且 PC 与移动端产物为**同一 sha256**——**移动端 = PC 端 = 内核设计**。"发出后动图"需内核(DSH 上游)保留动画附件,本插件/App 无法改内核(适配约束);若无上游支持,v1 边界"GIF 静态展示"即为正确预期。
972
- - **验证**:单元 12/12 PASS——用真实采样的 read_image tool/result 事件(嵌套图)与构造 assistant/message(嵌套+顶层混合)验证摘要 images[] 输出、callId/name 回退、纯文本消息无 images 字段、用户消息顶层收集回归。
973
-
974
- ### 队列"发送出去/删不掉"修复(移动端 ↔ 内核队列一致性)
975
- - **根因三层**:① 内核语义——`followup` 只入 `next-turn`,当前 turn 结束的瞬间 agent 循环即开新 turn 认领(与 PC 端一致);② App 丢弃内核权威 `session/queue` 帧(`store.dart` 只处理 question/approval),dock 全靠 400ms 节流 REST + 20s 轮询,存在陈旧窗口——消息已被认领行仍显示;③ 删除 TOCTOU——被认领后内核返回 `queue-item-not-found`,而 `ApiException` 不带错误码,无法区分语义;④ **移动端与 PC 端观感差异**——PC 端 queued 行只进 Queue Dock、不渲染进对话窗口,移动端则插入乐观气泡,看起来"消息被发送出去了"
976
- - **方案 A(移动端排队语义改造,与 PC 端观感对齐)**:运行中 `followup` **不再进内核 next-turn**(内核会在当前轮结束瞬间自动认领执行 = "被发送出去"的根源)——改为**插件侧持存**(`~/.dsh/mobile-remote/held-queue.json`,重启不丢):
977
- - 排队消息**只进 dock,不渲染进对话窗口**(对齐 PC 端:被认领执行时 user/message 回显才上屏)——`_send` 不再为排队路径插入乐观气泡
978
- - agent 真正空闲(整个任务/目标结束)后按序自动释放为 `followup`(新轮次执行)
979
- - 持存期删除/编辑**永远成功**(插件侧,无"已被认领"竞态);插队=立即 `steer` 注入当前运行(下一步边界执行,与 PC 端一致——**插队不废**)
980
- - 响应 `mode: "queued", note: "held-until-idle"`,App 端提示"已排队:当前任务结束后自动发送"
981
- - **服务端**:mux 把 `session/queue` 归一化为 **`mobile/queue` 帧**(`{sessionId, rows:[{id,text,placement}]}`,与 GET /queue 同款形状,合并持存行),认领/删除/编辑即时镜像;LAN 桥 SSE 空闲超时从"完全取消"收窄为 **60s**(>25s 心跳,防误杀且保留僵尸回收)
982
- - **App**:`store` 新增 `queueBySession` 镜像 + **帧权威**策略(帧永远覆盖,REST 仅无帧可依时兜底,防止旧 REST 快照覆盖新帧);chat dock 以帧为权威源——被认领瞬间行消失;删除/插话/编辑失败按 `queue-item-not-found` / `steer-unavailable` **语义化提示**;`ApiException` 增加 `code` 字段;`api.send` 返回 `(messageId, note)` 记录
983
- - **悬浮球**:SSE `readTimeout 0→50s`(插件 25s 心跳保活,静默死链 50s 内自动重连,通知不再断供);`markNotif` 收敛主线程(消 SSE 线程/主线程并发丢计数);`placePanel` 判空守卫 + onDestroy 取消面板动画/置空 panelParams(修 after-destroy `updateViewLayout(null)` 主线程 NPE 杀进程)
984
-
985
- ### 图像链路(视觉模型移动端跟进——PC 端同 wire、同设计)
986
- - **发送**:`/send` 支持 `images[{mediaType,data(base64),name?}]`,与 PC 端 `session.prompt` 图片通道**完全同形**(`{type:'image',mediaType,data,name?}`——内核限额/降采样/附件落盘同一通路);纯文本仍走 followup 零回归;请求体上限 64MB
987
- - **⊕ 菜单**:拍照 / 从相册选择 / 命令(解决 composer 放不下;命令列表保持原逻辑)
988
- - **不压缩**:`image_picker` 原始字节上传(PC 端浏览器同样只发原文件字节,内核负责超界降采样 8192/64M)
989
- - **限额/能力**:catalog 下发 `imageLimits`(内核 session.history projections 同源数字:20MB/20张/64M 像素/8192 边)与每个模型的 `imageSupported`(inputModalities);App 发送前校验(限额/媒体类型/模型能力),服务端 `attachment-error` 兜底;模型选择器 📷 标注
990
- - **持存(方案A)兼容图片**:运行中排队图片也进插件持存(随 held-queue.json 落盘,重启恢复;插队=立即 prompt steer)
991
- - **渲染**:SSE/history 摘要带 `images[]` 元数据(不放大带宽);新增 `GET /m/api/attachment`(鉴权取图,`x-attachment-meta` 宽高/字节/名字,1h 缓存);App 气泡按宽高比渲染(`attachmentBytes` LRU 64 张),点击全屏(InteractiveViewer),失败点按重试
992
- - **v1 边界**:GIF 静态展示;tool/result 嵌套图片仍显示「[图片]」占位(版本二做)
993
-
994
- ### 二次 Code Review 落实
995
- - **必改**:`md.dart` 列表/表格后段落乱序(`- a\n- b\nprose` 把 prose 渲染到列表上方);`notifications_screen` await 后补 mounted 守卫(pop 后 setState 崩溃);`sheets` 新建会话/文件夹 **busy 锁**(双击双建)+ 文件夹名 Windows 非法字符前置校验
996
- - **连接加固(实测定位)**:探测客户端地址/路径归一化与 save() 同源(`Api.forProbe`,防二维码地址带 `/m` 拼成 `/m/m/api/bootstrap` 404);连接失败提示追加网络原因引导(手机蜂窝/智能网络分流绕过局域网时,表现为同地址间歇 200/404/超时)
997
- - **中低**:`logger` 去每行 fsync(SSE 流式期间掉帧),文件体积改近似记账;`_MsgItem.copyWith` 显式断言(防未来复用静默变用户消息);`agent/status` child 注释缩进
998
- - **文档**:03-api §3.6b 帧表更新(`mobile/queue` 独立行,`mobile/frame` 收窄为问询/审批)
999
-
1000
- ### 验证
1001
- `flutter analyze` 0 issues;`flutter test` 9/9;`node --check` 通过;版本不变(pubspec `3.0.0+5` / package.json `3.0.0`,与既有版本线一致)。
1002
-
1003
- ### 背景
1004
- DSH Desktop(0.1.1-rc.2 / v2.0.2)强制 webserver 只听 `127.0.0.1`——`dsh-plugin-desktop` 在 profile 组装时把 webserver 行替换为 `DesktopWebServer`(构造器对非回环 host 直接 throw,用户 patch 无法覆盖,唯一旋钮是端口),手机无法直连桌面版。旧版(rc.5 web profile)能 `0.0.0.0:3080` 所以手机可连。
1005
-
1006
- ### 新能力:插件内置 LAN 桥(`lanBridge` 配置,默认关闭)
1007
- - 插件在 DSH 进程内**自建第二个 HTTP 监听**(默认 `0.0.0.0:3080`),把 `${path}`(默认 `/m`)前缀请求**流式转发**到回环 webserver——手机填/扫 `http://<电脑局域网IP>:3080/m` 即可使用,**无需穿透、无需装任何工具、不改 DSH**(桌面版/web 版通用)
1008
- - **安全边界**(代码强制):
1009
- - 只转发移动端面(`/m/*`);桌面 `/api` RPC 网关、`/m/api/qr-config`(含 token)、`/m/qr.png`(仅本机语义)一律 404 不转发 —— 桥不扩大桌面攻击面
1010
- - 未配置 **authToken 拒绝启动**(LAN 暴露必须强口令,docs/04-security.md)
1011
- - Host 头重写为回环信任值 → 下游 hostAllowed 自然放行;真实鉴权仍由 token 把关
1012
- - **扫码/自动地址联动**:`lanBridge` 启用时 bootstrap `urls` 与桌面二维码(qr-config)首选地址自动变为桥地址(`http://<IP>:3080/m`),App 扫码即连,无需手动填
1013
- - 诊断:`/m/api/diagnostics` `runtime.lanBridge` 上报 `{enabled, host, port, listening}`;插件日志打印监听状态与错误
1014
- - 清理:插件卸载时桥随 effect 关闭
1015
-
1016
- ### 配置(cordis.patch.yml,用户侧)
1017
- ```yaml
1018
- lanBridge:
1019
- enabled: true
1020
- port: 3080 # 冲突可改;防火墙需放行该端口
1021
- host: 0.0.0.0 # 默认全接口;仅本机测也可用 127.0.0.1
1022
- ```
1023
-
1024
- ### 三路全量 Code Review 修正(2026-08-22)
1025
- - **服务端(23 项,0 CRITICAL/0 HIGH)**:桥加固——只转发 `/m/api*`(收窄原 `/m/*`)、`maxConnections=128`、headersTimeout 15s/requestTimeout 60s、upstream 15s 超时、剥离 `x-forwarded-*`/`via`、在网 socket 随卸载销毁;QR/bootstrap 地址按**实际监听成功**判定(绑定失败回退回环,不指向死端口);`/llm-providers` POST 直调 `settings.mutate/credentials.set` 显式 try/catch(不再裸抛落 500);`/notifications/read|delete` 与 `/sessions/touch` 写盘**去抖**(500ms/10s,卸载前冲刷);`/notifications/delete` ids 限 500;`/history before=0` 语义修正;`/goal maxGoalRounds` 校验 1-10000;`/send` followup/steer try/catch;**authToken 弱口令全局告警 + 桥强制 ≥16 拒启**;lanBridge schema 校验 port/host
1026
- - **App(17 项,1 HIGH)**:**HIGH 修复——页级动作统一 `_mySessionId`**(发送/停止/杀任务/消息操作/用量/命令菜单/工具页,叠层聊天不再发错会话);余额成功清错误态;通知页错误态+store 联动实时刷新;SSE 连接窗口内晚到响应取消(半开 socket 修复);深色模式链接品牌色;气泡解析缓存含亮度;`_decode` 非 JSON 兜底;`commands()` 区分 unavailable;analyze 唯一 info 消除
1027
- - **Kotlin/文档(21 项,0 CRITICAL)**:Android 13+ 运行时请求 POST_NOTIFICATIONS;httpGet 补 readTimeout;Android 14+ 三参 startForeground 显式 SPECIAL_USE;addView 悬浮窗权限兜底;明文 HTTP 残余风险注释;**文档同步**——04-security 新增 §3b LAN 桥安全边界、09-compatibility 更新 0.1.1-rc.2 基线矩阵与配置项、README/06-install-run 版本、03-api 补 LAN 桥地址说明、AGENT-RULES 维护公告更新;版本三处统一(pubspec 3.0.0+5)
1028
-
1029
- ### 未推送说明
1030
- 功能开发、全量评审修正已完成,并通过 PC 侧与真机(App 局域网实测)验证;**待本机全部复验通过后由用户决定推送**。
1031
-
1032
- ## v2.8.2(2026-08-22)— 适配 DSH 0.1.1-rc.2(DSH Desktop v2.0.2)
1033
-
1034
- ### 服务端(lib/index.js)
1035
- - **CRITICAL 修复:`rpcError` 返回改为 `[status, code, message]` 元组**。调用展开 `error(res, ...rpcError(...))` 走迭代器协议,普通对象字面量与 `Object.create(null)` 均不可展开——**2.8.1 起全部 11 处 API 错误路径(队列/归档/fork/取消/模型/子代理/命令/goal)抛 `Spread syntax requires ...iterable[Symbol.iterator]`**,该 TypeError 上抛到 `handleApi` 外层 catch,统一退化为 `500 { error: "internal" }`,真实状态码/内核错误码/详情全部丢失。已实测复现(修复前 `500 internal` → 修复后 `400 session-not-found + detail`)
1036
- - **错误码收窄为仅非空 string**:内核自定义错误码是契约字符串(`session-not-found` / `target-not-found` 等);`DOMException.code` 等数字码不在契约内 → 落 fallbackCode(注释记录依据)
1037
- - **GET `/m/api/commands` 拆分条件**:`commands` 服务未注册 → `200 { ok, commands: [], unavailable: true }`(优雅降级,App 端弹「无可用命令」,不硬 503);服务在而会话不存在 → `404 session-not-found`(不再被空列表掩盖真实原因)
1038
- - **POST `/m/api/commands` 适配内核四参签名**:`commands.execute(agent, line, images, signal)`(0.1.1-rc.2;2.8.1 旧三参调用会把 `AbortSignal` 误传 `images` 槽,已改正);`images` 恒为空数组,`signal` 15s 超时;未知/畸形命令仍 `404 command-not-found`,服务缺失 `503` 附 detail;同样拆分 `!agent` → `404 session-not-found`(与 GET 一致)
1039
- - 文档:03-api.md 补 §6.15 斜杠命令契约与端点总表行(2.8.0 引入时遗漏)
1040
-
1041
- ### 验证
1042
- - 修复后实测(DSH Desktop v2.0.2 / 0.1.1-rc.2,真实会话):archive/fork 假会话 → `400 session-not-found + detail`;goal pause 无目标 → `400 no-active-goal`;feedback none 幂等 200 / positive 不存在消息 → `404 target-not-found`;commands GET 真会话 200 真实列表、假会话 404、POST `/goal` 200 真执行结果、`/bogus` 404、非斜杠 400 —— 全量通过
1043
- - 三路 Code Review(服务端两轮:首轮发现 CRITICAL、复核通过 PASS;App 全量零改动)并修复发现项;App 侧维持 v2.8.1
1044
-
1045
- ## v2.8.1(2026-08-22)— 消息操作栏 + 输入栏重构 + 命令入口 + 反馈增强
1046
-
1047
- ### 消息操作栏(替代长按)
1048
- - 助手消息下方**常驻操作栏**:复制 / 好的回答 / 有问题的回答 / 在新对话中分支——对齐 PC 端 `MessageIconActions`,点即用;**移除长按弹底部面板**
1049
- - 操作逻辑抽为 `_runMessageAction` 统一处理(复制/反馈/分支),无重复代码
1050
-
1051
- ### 反馈增强(对齐 PC 端)
1052
- - 👍/👎 选中态 = **品牌蓝图标 + 浅蓝圆底**(PC 端 `data-active` 同款样式,positive/negative 同色)
1053
- - **toggle 取消**:再点已选评级 = 取消反馈(服务端新增 `rating: "none"` → 内核 `messageFeedback.delete`,与 PC 端一致)
1054
- - 反馈状态按 messageId 同步 live 列表与历史页;同消息提交中防连点竞态
1055
-
1056
- ### 输入栏两层重构(对齐 PC 端 InputBar)
1057
- ```
1058
- 第一层: [输入框························]
1059
- 第二层: [⊕] [模型…] [权限…] [排队发送] [⭕] [发送]
1060
- ```
1061
- - 模型/权限胶囊移到第二层,名称省略显示(超长截断,保证放得下);排队胶囊固定宽度不被挤压;上下文圆环/发送按钮在弹性组外固定,任意窄屏不溢出
1062
- - 深色主题适配(⊕ 图标不再黑压黑)
1063
-
1064
- ### 命令入口
1065
- - 第二层新增 **⊕ 命令按钮**(浅灰圆,对齐 PC 端 command)→ 弹命令列表 → 点选 `/命令名 ` 填入输入框(PC 端 leadingInput 语义,可补参数后发送)
1066
- - 服务端新增 `GET/POST /m/api/commands`(对齐内核 `ctx.commands`:`list(agent)` / `execute(agent, line, signal)`);未知/畸形命令返回 404 `command-not-found`
1067
-
1068
- ### 其他
1069
- - 版本号统一 2.8.1(插件 2.8.1 / App 2.8.1+4)
1070
- - 全部改动经三路 Code Review(服务端/Flutter/Kotlin)并修复发现项;命令功能在测试窗口会话端到端实测通过
1071
-
1072
- ## v2.8.0(2026-08-22)— 代码收敛重构(Phase 0/1/2,行为保持)
1073
-
1074
- ### 服务端(lib/index.js)
1075
- - **Phase 0 公共 helper**:新增 `agentSessionId` / `isLoopback` / `firstAgent` / `shortSessionId` / `notifyTitle` / `guardRes` 六个 helper,统一散落各处的重复写法(会话 id 归一、回环判定、标题兜底短码、响应防崩守卫)
1076
- - **Phase 1 三收敛**:
1077
- - `readJson(req, res)` 收敛 20 处 `JSON.parse(await readBody(req))` + try/catch 样板(失败统一 400/413 响应)
1078
- - `rpcError(err, code)` 收敛 9 处 apiRpc 失败映射(传输层 502 / 超时中止 504 / 内核错误透传 status+code)
1079
- - `requireGet` / `requirePost` 收敛 29 处 405 方法检查;`/events` 保持严格 GET-only(HEAD 会悬挂 SSE 连接)
1080
- - `/sessions` 列表标题统一兜底短码(live 与归档分支一致,标题永不裸 null)
1081
-
1082
- ### 悬浮球(FloatingBubbleService.kt)
1083
- - 新增 `baseUrl` / `httpGet` / `postState` / `openApp` / `roundedRect` / `isActive` / `isBusy` 七个 helper:统一 HTTP 请求骨架(含 finally disconnect)、主线程刷新、跳转、圆角背景、状态判定
1084
- - `mainIntent` 统一前台通知与面板跳转的 Intent 构造(extra 类型守卫:String/Boolean/Int,成对传参)
1085
- - 清理 3 个未使用 import;未读增量检查失败不再消费首次基线(消除瞬时失败误报)
1086
-
1087
- ### App(Flutter)
1088
- - 新建 `lib/fmt.dart`:`relTime` / `fmtTokens` / `permNameOf` 共享格式化(首页/会话页/聊天页/设置页收敛)
1089
- - `toast.dart` 新增 `showToastAt(messenger, msg)`:sheets/settings 的本地 `_toast` 全部收敛
1090
- - `theme.dart` 新增 `DshSwitch`:设置页三处开关统一
1091
- - `openChat`(chat_screen.dart 顶层)统一 7 处打开会话流程(含悬浮球/新建/分支,返回后恢复语义保持)
1092
- - `openNotificationsScreen`(main.dart 顶层)统一 3 处通知页入口;`openProviders`(providers_screen.dart)统一 2 处提供商页入口
1093
- - `_persistPrefs` 收敛 8 个 setter 的 SharedPreferences 样板(不支持类型快速失败);`_isNoiseText` 收敛消息噪声过滤;删除死代码 `api.events()`
1094
- - 行为变化说明:assistant 消息现在与 user 消息一致地过滤 `background job ` 前缀注入帧(与 PC 端 GUI 对称)
1095
-
1096
- ### 其他
1097
- - 版本号统一 2.8.0(package.json / pubspec 2.8.0+3)
1098
- - 纯收敛重构 + 少量 UI 修正,全部改动经多轮 Code Review
1099
- - **UI 行为变化(v2.8.0 修复)**:
1100
- - 对话消息列表统一为普通列表(最旧在顶、最新在底):消息少时内容贴顶、列表占满可滚动——修复旧版"下半部分空白死区 + 滑动消息消失";根治 50/51 条边界列表方向翻转的滚动位置跳变
1101
- - 输入框胶囊行:容器内边距对称、胶囊行与输入框左缘对齐;模型胶囊超长省略号截断(防溢出);插队按钮图标由播放三角改为右向箭头
1102
- - 悬浮球余额自查:JSON 解析兜底 try/catch(200 但畸形响应体不再产生异常噪音)
1103
-
1104
- ## v2.7.2(2026-08-21)— 通知改"真结束"判定 + 悬浮球横屏修复
1105
-
1106
- ### 通知(服务端 + App/悬浮球同源)
1107
- - **只在对话真正结束时通知**:多轮大任务(goal 驱动/连续队列)不再每完成一个子轮次就推"✅ 任务完成"——`turn/end` 只暂存结果,agent 转为 idle 且稳定 `doneGraceMs`(默认 15 秒,插件配置可调)、且无 active goal,才判定"对话真正结束"并通知一次
1108
- - **子代理会话不再单独通知完成/失败**:它是父任务的一部分,父任务结束才通知;「需要你回答」仍立即通知(交互式提问不能等)
1109
- - **通知帧直推 SSE(`mobile/notify`)**:悬浮球/App 与插件通知中心同源渲染,悬浮球不再自行按轮次/job 弹"任务完成"
1110
- - max-tokens 截断的完成通知会标注「max-tokens 截断」,避免误以为任务完整完成
1111
- - **审批/提问提醒补全**:`approval/requested`、`question/requested` 立即生成 `needs-answer` 通知 + 推送桥(App 后台/被杀、悬浮球未开时也能收到);悬浮球提醒统一由 `mobile/notify` 驱动(与弹窗帧去重,不再双弹)
1112
-
1113
- ### 修复
1114
- - **修复崩溃级 bug**:`mobile/notify` 广播中 shorthand `{ time }` 引用未声明变量(漏写 `time: now`)→ 每次通知都抛 ReferenceError,`armDone` 定时器异步回调中的异常在 Node 15+ 默认按 unhandledRejection 抛出 → **整个 DSH 进程崩溃**;已修正并给定时器回调整体加 try/catch 兜底
1115
-
1116
- ### 插队发送
1117
- - `/send` 端点新增 `mode: "steer"` 参数:插队发送(消息插到 agent 下一步执行),适合 team 插件子会话向主会话插队场景;agent 空闲时自动降级排队并在响应中标注
1118
- - 手机端:**长按发送按钮 = 插队发送**;agent 空闲时提示并降级普通发送
1119
-
1120
- ### 悬浮球横屏修复
1121
- - **旋转/跳转后无条件自动贴边**:监听配置变更(`onConfigurationChanged`),按旋转前渲染位置(`lastRenderX`)判断贴左/贴右、保持隐藏状态——竖屏↔横屏、翻转 180°、页面跳转后球都自动回到边上,无需手动拖动
1122
- - **挖孔/安全区偏移适配(关键修复)**:横屏时系统会把 overlay 窗口整体平移(本机挖孔屏 ROTATION_90 时右移 144px),不扣除偏移会导致球被推到屏幕外"消失"(翻转 180° 后更会稳定消失)。实测 `rootWindowInsets` 在 overlay 窗口上返回值不可靠,改用 **display 级权威数据 `defaultDisplay.cutout`**(API 29+)读取安全区,所有贴边/拖动/面板/气泡定位统一按"渲染坐标 = 坐标 + 系统偏移"计算
1123
- - **拖动钳制**:x 限制在 `[-球径+16dp, 屏宽-16dp]`、y 限制在屏内(按渲染坐标钳制)——永远拖不出屏、不会进入无法恢复的位置
1124
- - **自愈兜底**:按下时发现越界先钳回允许范围再开始拖(覆盖部分厂商不回调配置变更的情况)
1125
- - 屏幕尺寸获取 API 30+ 改用 `currentWindowMetrics`(旋转后最可靠)
1126
-
1127
- ### 其他
1128
- - 版本号统一 2.7.2(package.json / pubspec 2.7.2+2)
1129
-
1130
- ## v2.7.1(2026-08-18)— 稳定性修复 + 悬浮球余额预警完善
1131
-
1132
- ### 修复
1133
- - **会话列表慢(归档/取消归档后要几秒才生效)**:休眠会话标题折叠结果**缓存 5 分钟**(折叠需逐个读日志,50+ 会话实测 7 秒)+ App 端**乐观更新**(归档后本地列表立即生效,后台静默校准)——实测归档请求 29ms、列表刷新不再卡
1134
- - **通知逐条保留**:每次轮次完成(主会话/子代理各自)独立生成一条通知,互不合并——已读的消息不再被后续轮次顶掉(去掉旧"同会话同类型聚合覆盖")
1135
- - **Agent 状态串台**:状态改为按会话绑定(`agentStatusMap`,bootstrap 全量同步 + 帧按 agentId 过滤)——切换工作区/回前台/打开会话时状态圆点与停止按钮不再显示成别的会话的;重连 `hello` 帧与重试 bootstrap 也同步状态
1136
- - **悬浮球不再常亮**:余额低不再是"常亮状态",改为事件式提醒(亮 60 秒自动消退 + 30 分钟防抖)
1137
- - **顶部通知横幅彻底重写**:弃用 `OverlayEntry`(新版 Flutter 下 remove 后视觉残留,横幅挂住不消失)——改为 `MaterialApp.builder` 全局渲染(覆盖所有页面,含聊天页/通知页);**回前台/悬浮球跳转时若有未读则强制提醒**("错过的也提醒",绕过防抖);显示后自动更新未读基线不反复弹
1138
- - **新建会话弹层补回「模型与推理强度」入口**:显示当前模型 + 强度,点击弹出模型/推理强度(off/high/max)选择——v2.7.0 首页改版时该入口遗漏
1139
- - **新建会话工作目录自动匹配当前工作区**:改用规范化路径匹配(此前 `api.workspaces()` 原始路径与 workspacePath 大小写/斜杠不一致,永远回退第一个工作区)
1140
-
1141
- ### 悬浮球余额预警(App 端为准)
1142
- - **阈值可配置**:设置 → 账户 → 余额预警行点击选择 ¥5 / ¥10 / ¥20 / ¥50 / 自定义输入,持久化;副标题实时显示当前阈值
1143
- - **开关联动**:悬浮球的报警判定**完全以 App 端开关 + 阈值为准**(开关/阈值变化即时推送,App 启动时同步)——开关关 → 悬浮球不因余额报警/亮起
1144
- - **面板常驻余额行**:两位小数(`余额 ¥12.50`),低余额红字(静默警示,不亮球);点击可去充值
1145
- - 面板打开时顺带刷新余额;30 分钟自查照常更新数值
1146
-
1147
- ### 悬浮球面板与徽标
1148
- - **未读徽标胶囊化**:球上角标与面板通知区徽标均为胶囊形(圆角=高一半),单数字也保持胶囊比例;球上角标骑在球右上外缘(不再偏下)
1149
- - **面板空状态**:无运行中会话 / 无通知时显示灰色占位文案(「暂无运行中的会话」「暂无通知」),面板不再空荡荡
1150
- - **通知区标题行对齐**:最近通知 / 未读徽标 / 查看全部 三者文本视觉中心统一(去字体留白 + 垂直居中)
1151
-
1152
- ## v2.7.0(2026-08-18)— 会话工具(任务/子代理/目标)+ 移动端体验打磨
1153
-
1154
- ### 新增(移动端会话工具,PC 端 GUI 同源数据)
1155
- - **任务进度**:会话运行中的后台任务(jobs)实时推送(SSE `session/jobs` 帧,连接回放 + 订阅更新),对话页活动条下方自动出现任务卡片(状态点/标签/取消);AppBar 新增「会话工具」入口
1156
- - **会话工具弹层**(任务 / 子代理 / 目标 三页签):
1157
- - 任务:列表 + 取消(映射内核 `jobs.kill`,按会话隔离)
1158
- - 子代理:按父会话查询子代理列表(`subagent.list`)+ 中断(`subagent.interrupt`)
1159
- - 目标:查看当前目标(objective / 轮次 / 状态)、创建、暂停 / 继续 / 标记完成(映射内核 goal RPC,`sessionId + ref` 契约一致);受阻时显示原因
1160
- - **插件新端点**:`/m/api/jobs`(GET)、`/m/api/jobs/kill`(POST)、`/m/api/subagents`(GET)、`/m/api/subagents/interrupt`(POST)、`/m/api/goal`(GET/POST);连接回放 `session/jobs` 帧,`onJobsChanged`/`onJobDone` 订阅随插件卸载清理
1161
-
1162
- ### 新增(移动端体验)
1163
- - **Agent 状态 bootstrap 同步**:连接 / 重连 / 下拉刷新时从 bootstrap 同步 agent 状态(思考中 / 工具执行 / 空闲),按钮与活动条即时反映 PC 真实状态
1164
- - **下拉刷新收集地址**:refreshAll 吸收 bootstrap 的 `server.urls`(蒲公英 / Tailscale 等新地址及时进候选表,回环地址过滤)
1165
- - **聊天草稿保留**:会话级缓存,输入内容退出会话后重进自动恢复(仅内存,不落盘)
1166
- - **首页改版**:移除底部快捷输入框(模型/权限选择移至新建会话弹层与会话页);欢迎语改为「今天打算设计什么?」;顶部展示 DeepSeek 官网官方 logo;内容块整体上移居中;最近会话卡片固定 3 行完整显示、超出卡片内滑动
1167
- - **界面语言切换**:设置 → 显示 → 语言(中文 / English),即时生效、持久化;主要界面全量双语(首页 / 会话 / 聊天 / 设置 / 连接 / 通知 / 会话工具弹层 / 提供商管理页等)
1168
- - **悬浮球(设置 → 显示,默认关)**:透明底圆形 DeepSeek 鲸鱼常驻桌面(App 被杀仍工作,自带 SSE)
1169
- - 状态:空闲=灰鲸半透明;有任务/通知/余额低=亮蓝 + 红点角标(60s 自动消退、开面板清零、同类防抖)
1170
- - 交互:单击展开迷你面板(运行中会话 / 最近通知 / 打开 App / 去充值)、拖动贴边 5s 无操作自动缩进、双击开 App、长按退出
1171
- - 面板:四角定位(随球位置向内展开)、点击外部关闭、SSE 事件即时刷新 + 5s 兜底、无内容区块隐藏、浅灰胶囊按钮
1172
- - 动效:按压缩放回弹、滑动吸附、亮暗 180ms 过渡、面板从球方向生长 + 逐项浮现
1173
- - 余额联动:悬浮球每 30 分钟自查余额,低余额亮起 + 气泡 + 按钮变红
1174
- - 面板通知区:最近通知(上限 3 条)+ 红色未读徽标(与 App 铃铛同源)+ 未读蓝点 + 相对时间 + 查看全部入口(点击跳 App 通知页)
1175
- - 操作说明:设置页「悬浮球操作说明」弹窗(状态含义 / 手势 / 面板操作,双语)
1176
- - **余额预警(设置 → 账户,默认关)**:余额低于 ¥10 提醒充值,余额行红色警示
1177
- - **充值入口**:跳转系统浏览器打开官方充值页(恢复 v2.6 原方式;小米不支持标准 Custom Tabs,已移除相关代码)
1178
- - **通知提示(错过的也提醒)**:悬浮球每 60 秒 + App 每次刷新对比未读数增量——重连窗口 / 离线期间新增的通知,重连后**亮红点 + 气泡「有 N 条新通知」+ App 顶部横幅**(点击跳通知页),不只静默更新数据
1179
-
1180
- ### 设计风格与动效
1181
- - **主题令牌统一**:卡片圆角 14 / 弹层 20 / 输入框 10;阴影弱化为单层轻投影(浅底+细线+极轻投影分层)
1182
- - **页面转场**:轻量 iOS 味(新页全宽滑入 + 淡入,旧页静止零重绘——解决 Cupertino 双页面渲染卡顿)
1183
- - **通知铃铛实时刷新**:轮次结束 App 主动拉取通知(不依赖重连/下拉),插件写入通知后广播 `notifications/changed`
1184
-
1185
- ### 修复
1186
- - 任务四端点契约对齐内核 schema(`subagent.list` 需 `parentSessionId`、`subagent.interrupt` 需 `parentSessionId + childSessionId + mode`、goal 变更需 `sessionId + ref`;goal POST 的 agent 解析改用 body 的 sessionId)
1187
- - 目标页「无目标」状态不再卡加载(服务端 `goal: null` 与加载中区分)
1188
- - 目标操作后无论成败都刷新真实状态(轮次驱动可能已改变状态,如轮次耗尽→受阻)
1189
- - 会话工具弹层旧插件降级:端点不存在时显示「加载失败 + 重试」而非白屏
1190
- - **休眠会话标题**:重启 / 重连后历史会话直接显示标题(`sessionQuery.readTitleSnapshot` 从持久化日志折叠),无需点进去才更新
1191
- - **通知聚合重新未读**:同会话同类型通知聚合时从已读集合移除——已读后同会话的后续完成会重新触发未读(修复角标/提示永久不亮)
1192
- - 悬浮球:空闲检测不再被红点卡住(轮次边界驱动亮暗)、连接回放不误报通知、右半屏点击、贴边缩进(`FLAG_LAYOUT_NO_LIMITS`)、气泡按实际测量宽度定位(修复硬编码宽度致窄气泡飘到屏幕中间)、面板实时同步
1193
- - 通知写入后广播 `notifications/changed`(App 铃铛实时更新,无需重开 App)
1194
- - 悬浮球开关语义:`START_NOT_STICKY` + 余额推送仅在服务运行时生效(开关关着不再被系统复活 / 余额刷新拉起)
1195
-
1196
- ### 兼容
1197
- - 新增端点与 SSE 帧:旧 App 忽略新帧;旧插件无新端点(App 弹层提示加载失败,升级插件后可用)
1198
- - 首页移除底部输入框为**行为变更**:首页发消息改为「+新建会话」入口(会话页输入不受影响)
1199
- - 已发布 API 无破坏性变更
1200
-
1201
- ## v2.6.0(2026-08-17)— 安全加固 + 移动端过程可见性 + 模型提供商互通
1202
-
1203
- ### 新增(安全)
1204
- - **登录失败限流**:`rateLimit` 配置(默认 10 次/60s),错误口令按来源 IP 计数,超限返回 `429 rate-limited` + `Retry-After`;认证成功重置计数——防弱口令爆破(仅 `authToken` 启用时生效)
1205
- - **推送内容脱敏(默认开启)**:第三方推送通道(Server酱/ntfy/Bark/generic)默认只推事件类型 + 会话短码,**会话标题、错误详情等核心内容不再外发**;确需完整内容设 `pushContent: "standard"`(旧行为,仅在信任通道时开启)
1206
- - **链接 scheme 白名单(App)**:消息内链接仅 http/https 可点击,`file:`/`intent:`/`tel:` 等一律渲染为纯文本(含单元测试 `test/md_link_test.dart`)
1207
- - **`/m/qr.png` 收口本机**:与 `qr-config` 同策略(Host 校验 + loopback 来源),不再对外提供无认证二维码渲染
1208
- - **口令比较加固**:改为 sha256 定长化 + 常量时间比较,消除长度侧信道
1209
- - **认证未启用显性警示**:插件启动日志告警 + 桌面设置页红色横幅「访问口令未启用」
1210
- - **Android 备份隔离**:`allowBackup="false"`,口令/缓存不进云备份与 ADB 备份
1211
- - **桌面设置页复制口令 60s 自动清剪贴板**(防其他应用读取;尽力而为)
1212
-
1213
- ### 新增(移动端过程可见性)
1214
- - **思考过程实时显示**:agent 思考时对话页出现可折叠「思考中…」面板,实时滚动思考内容(点开/收起),正文开始后显示「已思考 N 字」
1215
- - **活动条**:思考 / 工具执行阶段在输入框上方显示轻量状态行(如「正在调用 read…」),结束即消失——不再"干等无反应"
1216
- - **移除「显示工具调用」开关与工具卡片**:工具过程统一由活动条呈现,设置页与代码同步清理(工具结果细节以 PC 端为准)
1217
- - **「思考内容」开关(设置 → 显示)**:默认关——只显示思考状态(思考中/已思考 N 字),不显示思考原文(deepseek 思考内容为英文,默认隐藏防刷屏;需要时打开)
1218
-
1219
- ### 修复
1220
- - generic 推送格式 `kind is not defined`(存量 bug,仅 generic 通道触发)
1221
- - `z.enum` 不兼容 schemastery 导致插件加载失败(v2.6 新增配置项改用 `z.string` + 运行时校验)
1222
- - **移动端消息重复显示**:SSE 回显先于 send 响应到达(且轮次分隔线已插入)时,乐观消息合并失败 → 同一条消息显示两次;改为全列表查找乐观消息合并
1223
- - **工具阶段空气泡**:多步工具轮中正文为空的中间 assistant 消息不再渲染(过程由活动条呈现)
1224
- - e2e-check:支持 `DSH_MOBILE_BASE` 环境变量、无 agent 实例自动跳过发送
1225
-
1226
- ### 新增(模型提供商互通,PC × 移动端)
1227
- - **模型提供商互通**:PC 端「设置 → 模型」配置的提供商与移动端同一通道,**两端一致、手机修改即时生效**
1228
- - 内核 `llm` 服务的可配置提供商目录全量上手机:除 deepseek-official 外,**37 个 dormant 提供商**(anthropic / openai / google / groq / mistral / nvidia / openrouter / xai / kimi / minimax / moonshotai / qwen / zai / xiaomi 等)在手机可见,配置 baseURL + API Key 即激活
1229
- - **插件新端点**(`/m/api/llm-providers` GET / POST、`/m/api/llm-providers/probe`):
1230
- - 提供商列表(live/dormant、settingsNs、baseURL、密钥状态——密钥引用只读,**不返回密钥本身**)
1231
- - 保存:`ctx.settings.mutate` 写配置 + `ctx.credentials.set` 存密钥(引用派生规则与 PC 端一致:`<PROVIDER>_API_KEY`);仅允许写入配置目录声明的命名空间
1232
- - 探测:优先内核 `discoverModels`;内核未注册模型探测时(rc.5 deepseek 适配器)**回退 OpenAI 兼容 `GET {baseURL}/models`**
1233
- - **App**:
1234
- - 模型选择器**按提供商分组**(组名 = 提供商显示名),dormant 提供商显示「未配置」不可选
1235
- - 设置新增「模型提供商」管理页:列表(已连接/未配置徽标 + baseURL + 密钥状态 + 目录模型数)、编辑(baseURL / API Key / 探测模型 / 清除密钥)
1236
- - catalog 新增 `providers` 元信息;`session-config` 返回当前模型所属 `provider`;无 agent 兜底目录改为遍历全部提供商(不再写死 deepseek-official)
1237
- - `llm.listProviders()` 等为同步方法,原 `.catch()` 链式调用抛错(端点改用 try/catch)
1238
-
1239
- ### 文档
1240
- - 06 新增「HTTPS 反代」章节(Caddy/nginx + 自签证书,可选 TLS 方案)
1241
- - 03 新增 §6.13 模型提供商端点;04/09/00/07/README 同步:限流参数、429 错误码、推送脱敏、链接白名单、备份说明、提供商互通
1242
-
1243
- ### 兼容
1244
- - `pushContent` 默认 minimal 为**行为变更**:升级后推送内容变精简(通道配置无需改动,如需完整内容显式设 standard)
1245
- - 移除「显示工具调用」为**行为变更**:移动端不再显示工具结果卡片,工具过程以活动条呈现
1246
- - 旧 App 忽略 catalog 新增字段;旧插件无新端点(App 管理页提示加载失败,升级插件后可用)
1247
- - 已发布 API 无破坏性变更;`rateLimit` / `pushContent` 均为新增可配置项
1248
-
1249
- ## v2.5.2(2026-08-17)— 抽屉与弹层溢出修复
1250
-
1251
- ### 修复
1252
- - **工作区过多时抽屉挤出「设置」入口**:工作区列表改为封顶屏高 35% 的可滚动区,导航三项(首页/会话/设置)固定在可见区域,不再被顶出屏幕(感谢社区反馈与 PR #1 的思路)
1253
- - **底部弹层条目过多溢出**:通用底部弹层与「新建会话」弹层的列表区改为可滚动,底部按钮固定
1254
- - **切换连接地址弹层溢出**:候选地址随使用动态累积,列表区改为可滚动(同类隐患全库排查后修复)
1255
- - **agent 问询卡片挤压输入框**:问题说明长/选项多时卡片封顶 40% 屏高内部滚动
1256
- - **连接配置页键盘溢出**:表单整体可滚动,小屏 + 键盘弹出时不再溢出
1257
-
1258
- ## v2.5.1(2026-08-17)— 连接自愈提速 + 版本线统一
1259
-
1260
- ### 版本线统一(重要)
1261
- - **App 版本 = 插件版本 = git tag**:插件 2.4.1 对齐为 **2.5.1**,此后每次发布三处一起 bump
1262
- - GitHub Release 每版同时提供 `DSH-Remote-vX.Y.Z.apk`(App)+ `dsh-mobile-remote-vX.Y.Z.tgz`(插件包)
1263
- - 版本差矩阵见 README「版本与兼容」(同版本完美;不同版本可用但"谁旧谁吃亏")
1264
-
1265
- ### 修复/优化
1266
- - **黑洞地址快速故障切换**:手机关组网/隧道断时,从约 72 秒缩短到**约 10 秒**自动切到可用地址(SSE 超时即轮换 + 连接/探测超时 15s→8s)
1267
- - **下拉刷新升级**:改为「探测 → 自愈 → 拉数据」——先探测连通性,不通自动轮换地址并重建连接;失败弹短提示「电脑连接不上,正在自动重连…」(成功静默)
1268
- - 文档:蒲公英手机端保活设置(国产 ROM 杀后台是外出断连头号原因);安装依赖补充 `github:` 快捷方式与按 tag 锁版本
1269
- - APK 本地归档带版本号:`tools/package-release.ps1` → `dist/DSH-Remote-vX.Y.Z.apk` + `dist/dsh-mobile-remote-vX.Y.Z.tgz`
1270
-
1271
- ## v2.5.0 / 插件 v2.4.1(2026-08-17)— 可靠性加固与体验完善
1272
-
1273
- ### 新增
1274
- - **手动切换连接地址**:设置 → 电脑地址点按弹出候选列表(当前打勾),探测可达后切换并重连(回家切局域网 / 出门切组网)
1275
- - **首页最近会话显示所属工作区**:小字 + 文件夹图标,与 PC 端分组同源;不属于任何工作区显示「未分组」
1276
- - **余额缓存兜底**:插件缓存余额 60s,官方 API 慢/失败时返回最近一次成功值
1277
- - **全局提示统一**:短滞留(1.6s,长错误 3s)+ 悬浮样式,不遮挡底部操作区
1278
- - 顶部在线状态点:紧贴抽屉菜单、细描边(视觉微调)
1279
- - 场景化文档:外出访问主推蒲公英(实测截图 + 下载链接),其他方案保留思路
1280
-
1281
- ### 修复
1282
- - **组网地址黑洞连环故障**:SSE HTTP 无超时 → 地址不通但不拒绝时永久卡 connecting、看门狗与轮换全部失效 → SSE 加 15s 超时;重配置保留旧候选地址;二维码首选局域网段
1283
- - **手动切换后界面不刷新**:switchBase 补 notifyListeners + 成功提示
1284
- - **重新配置清空旧配置**:改为新配置保存成功才覆盖,连接页提供「返回(保留原配置)」
1285
- - **SSE 僵尸连接**:半开连接立即清理(error 事件 + 写失败 dropConn)
1286
- - **余额慢链路失效**:官方接口超时 10s→15s,App 侧 15s→25s
1287
- - **引用块渲染 NaN 矩阵**:IntrinsicHeight 修复(内容重叠/无法滑动)
1288
- - Windows 防火墙「公用网络」拦截排查(FAQ 一键命令)
1289
-
1290
- ## v2.4.2(2026-08-17)— 连接可靠性修复
1291
-
1292
- ### 修复
1293
- - **SSE 静默断连卡死**:网络切换/路由器断连时 TCP 静默死亡、流不报错,App 永远显示已连接但实际离线(只能划掉重开)→ 新增心跳看门狗(识别服务器 25s `: ping`,75s 无心跳强制重建连接)+ 回前台时校验旧流活性(>45s 无心跳即重建)
1294
- - **扫码连不上(链路本地地址)**:未登录的 Tailscale/断网虚拟网卡产生 169.254.x 地址并排在地址列表首位,二维码首选这个不可达地址 → 插件排除 169.254/16 并按「局域网私有地址优先」排序;App 地址收集同步排除
1295
- - **环境诊断永远显示旧版本**:旧版只在首次打开时拉取,插件升级后仍显示旧版本号 → 每次打开实时拉取,失败明确显示「检测失败」
1296
- - 充值入口改走服务端配置(`catalog.rechargeUrl`,插件 `rechargeUrl` 为准,缺省回退官方页)
1297
-
1298
- ### 新增
1299
- - `trustedHosts` 配置:显式放行内网穿透中继主机(frp/SakuraFrp 场景,配合强口令)
1300
- - 单元测试 `test/api_logic_test.dart`:多地址合并/轮换/回环与链路本地排除(5 用例)
1301
-
1302
- ## v2.4.1(2026-08-16)— 外出访问:多地址自动切换
1303
-
1304
- ### 新增
1305
- - **多地址自动切换**:App 连接成功后自动从电脑收集全部地址(局域网 IP + Tailscale IP,`/api/bootstrap` 的 `server.urls`),断线重试失败时自动轮换——出门自动切 Tailscale、回家自动切回局域网,全程免手动配置
1306
- - 设置 → 电脑地址显示「共 N 个地址自动切换」
1307
-
1308
- ## v2.4.0(2026-08-16)— 问询/审批弹窗 + 兼容性硬化
1309
-
1310
- ### 新增(移动端补齐"人类交互")
1311
- - **问询弹窗**:agent 用 `ask_user_question` 提问时,手机对话页弹出卡片(单选/多选选项 + 自定义输入),与 PC 端**同一 pending 通道**,任一端回答两端同步消失
1312
- - **权限审批弹窗**:工具越权时手机弹出「权限请求」(工具名 + 原因 + 允许一次/拒绝)
1313
- - 弹窗桥:插件 `ctx.inject(["apiProxy"])` 订阅 mux 队列(PC GUI 同机制),SSE `mobile/frame` 帧转发,断线重连补发挂起待答帧
1314
- - `/m/api/respond` 应答端点(question/approval/cancel),经 `apiProxy.respond` 走内核校验
1315
- - **通知删除**:长按单删 / 垃圾桶批量多选 / 清空全部(`/m/api/notifications/delete`,不影响 PC 端)
1316
- - **微信式无限上翻**:滑到顶部自动加载更早(无断页);「回到底部」浮钮
1317
- - 余额旁独立刷新按钮(移除点击数字刷新的旧交互);应用日志默认 15 天清理
1318
- - 顶部抽屉与标题间电脑在线状态点(点按探测/重连)
1319
- - 诊断探针:`services` 全量服务探测 + `respondBridge`/`frameBridge`/`pendingFrames`
1320
-
1321
- ### 修复
1322
- - **问询/审批桥拿不到 apiProxy**:各插件上下文隔离,`ctx.get` 看不到兄弟插件服务 → 改用 `ctx.inject`(dsh-client-connection 同款)
1323
- - 手机点 ✕ 取消后卡片不消失(本地状态提前清空导致 resolved 帧被跳过)→ 即时收起 + 无条件转发
1324
- - 聊天初始化竞态:SSE 事件与历史加载并发时不再丢失/回退 lastSeq
1325
- - 历史页加载不再污染正在流式生成的草稿
1326
-
1327
- ### 硬化
1328
- - `webServer` 守卫:纯 headless Harness 下插件静默无操作不崩进程
1329
- - Release 签名:正式 keystore(gitignore)+ key.properties 自动回退 debug
1330
- - 渲染后端定版 **Impeller**:此前"小米白屏→回退 Skia"系旧列表实现误判,深滚动在 Impeller 下完全正常(Skia 分段模式保留为 `_infiniteMode=false` 兜底)
1331
-
1332
- ## v2.1.0(2026-08-15)— 开源准备
1333
-
1334
- ### 新增
1335
- - **桌面设置页「连接移动端设备」**:dsh 客户端模块,显示扫码二维码(含地址+口令)+ 连接信息(`/m/api/qr-config`,仅 loopback)
1336
- - **原生 Flutter App**(`dsh-mobile-app/`):扫码连接、首页/对话/会话/通知/设置/新建会话全原生界面,DeepSeek 配色双主题
1337
- - **修改默认配置**:默认 Agent 预设 / 默认权限预设可直接在移动端修改(`POST /m/api/defaults`,与 PC 端同一写入通道)
1338
- - **通知聚合**:同一会话同一类型通知合并,不再按轮次刷满列表
1339
- - **工作区归属**:新建会话 cwd 为已注册工作区子目录时自动归属最近工作区(不再落"未分组")
1340
- - App:深色模式三态切换、环境诊断时间戳、返回键层级处理、长任务排队提示
1341
-
1342
- ### 修复
1343
- - **SSE 解析死循环**(buf 在循环内不更新导致 CPU 100% 卡死)—— 重写为 StringBuffer 增量解析
1344
- - App 通知页黑色背景(独立页面缺 Scaffold)
1345
- - App 消息重复显示(SSE 回显按 messageId 优先去重)
1346
- - App 目录选择器无限加载(初始化未触发)
1347
- - App 通知角标 Positioned 崩溃、HTTP 客户端泄漏、流式渲染风暴
1348
- - 小米设备 Impeller+Vulkan 白屏/卡顿(回退 Skia)
1349
- - 消息文本去重、首页文案、链接复制等细节
1350
-
1351
- ## v2.0.0 — 移动端 v2(新建会话 / 目录 / 通知中心 / 诊断 / 余额)
1352
-
1353
- - 新建会话:Agent 预设 + 模型/推理/权限 + 工作目录(跨盘浏览、新建文件夹)
1354
- - 通知中心:已读持久化(文件存储)、未读角标
1355
- - 环境诊断、余额查询、插件动作区、SSE 事件桥(重连退避/断线补拉)
1356
-
1357
- ## v1.0.0 — 移动端 v1(MVP)
1358
-
1359
- - `/m` 移动页:登录、发消息、SSE 流式、会话历史
1360
- - 访问口令认证(cookie/header)、Host 校验、二维码
1361
- - 推送桥:Server酱 / ntfy / Bark / generic
1362
-