dsh-zen-remote 1.1.16 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (261) hide show
  1. package/AGENTS.md +54 -9
  2. package/README.md +249 -190
  3. package/README.zh-CN.md +247 -201
  4. package/assets/home.png +0 -0
  5. package/assets/info.png +0 -0
  6. package/assets/pairing.png +0 -0
  7. package/assets/session.png +0 -0
  8. package/assets/sheet.png +0 -0
  9. package/cordis.patch.yml +12 -11
  10. package/cordis.patch.yml.example +24 -9
  11. package/docs/interface.md +57 -2
  12. package/docs/remote-access.en.md +99 -58
  13. package/docs/remote-access.md +99 -42
  14. package/dsh-push.mjs +67 -54
  15. package/lan-gate.mjs +88 -20
  16. package/lib/activity.js +168 -0
  17. package/lib/activity.js.map +1 -0
  18. package/lib/admin-routes.js +526 -0
  19. package/lib/admin-routes.js.map +1 -0
  20. package/lib/client-pairing.js +224 -0
  21. package/lib/client-pairing.js.map +1 -0
  22. package/lib/client-routes.js +674 -0
  23. package/lib/client-routes.js.map +1 -0
  24. package/lib/client.js +11498 -5990
  25. package/lib/config.js +346 -0
  26. package/lib/config.js.map +1 -0
  27. package/lib/fetch-route-intercept.js +418 -0
  28. package/lib/fetch-route-intercept.js.map +1 -0
  29. package/lib/fingerprint.js +365 -0
  30. package/lib/fingerprint.js.map +1 -0
  31. package/lib/http.js +69 -0
  32. package/lib/http.js.map +1 -0
  33. package/lib/index.js +675 -54
  34. package/lib/index.js.map +1 -1
  35. package/lib/intercept-shape.js +81 -0
  36. package/lib/intercept-shape.js.map +1 -0
  37. package/lib/intercept.js +3181 -0
  38. package/lib/intercept.js.map +1 -0
  39. package/lib/lan-gate-server.cjs +532 -121
  40. package/lib/merge-streams.js +1172 -0
  41. package/lib/merge-streams.js.map +1 -0
  42. package/lib/relay-access.js +496 -0
  43. package/lib/relay-access.js.map +1 -0
  44. package/lib/relay-client.js +1303 -0
  45. package/lib/relay-client.js.map +1 -0
  46. package/lib/relay-filter.js +282 -0
  47. package/lib/relay-filter.js.map +1 -0
  48. package/lib/relay-server.js +1813 -0
  49. package/lib/relay-server.js.map +1 -0
  50. package/lib/restart-fields.js +34 -0
  51. package/lib/restart-fields.js.map +1 -0
  52. package/lib/restart-watcher.js +106 -0
  53. package/lib/restart-watcher.js.map +1 -0
  54. package/lib/session-reference.js +157 -0
  55. package/lib/session-reference.js.map +1 -0
  56. package/lib/share-export.js +16 -1
  57. package/lib/share-export.js.map +1 -1
  58. package/lib/share-ops.js +86 -0
  59. package/lib/share-ops.js.map +1 -0
  60. package/lib/share-store.js +294 -0
  61. package/lib/share-store.js.map +1 -0
  62. package/lib/types/activity.d.ts +99 -0
  63. package/lib/types/activity.d.ts.map +1 -0
  64. package/lib/types/admin-routes.d.ts +129 -0
  65. package/lib/types/admin-routes.d.ts.map +1 -0
  66. package/lib/types/client/MobileDrawerFooter.d.ts.map +1 -1
  67. package/lib/types/client/MobileHome.d.ts +1 -1
  68. package/lib/types/client/MobileHome.d.ts.map +1 -1
  69. package/lib/types/client/MobileHomeChips.d.ts +1 -1
  70. package/lib/types/client/MobileHomeChips.d.ts.map +1 -1
  71. package/lib/types/client/MobileSessionHeader.d.ts +13 -2
  72. package/lib/types/client/MobileSessionHeader.d.ts.map +1 -1
  73. package/lib/types/client/MobileSessionInfo.d.ts +20 -3
  74. package/lib/types/client/MobileSessionInfo.d.ts.map +1 -1
  75. package/lib/types/client/RemoteComposerBanner.d.ts +42 -0
  76. package/lib/types/client/RemoteComposerBanner.d.ts.map +1 -0
  77. package/lib/types/client/RemoteHeaderIcon.d.ts +37 -0
  78. package/lib/types/client/RemoteHeaderIcon.d.ts.map +1 -0
  79. package/lib/types/client/RemoteShareMenu.d.ts +28 -0
  80. package/lib/types/client/RemoteShareMenu.d.ts.map +1 -0
  81. package/lib/types/client/RemoteStatusIcon.d.ts +36 -0
  82. package/lib/types/client/RemoteStatusIcon.d.ts.map +1 -0
  83. package/lib/types/client/compat/desktop.d.ts +21 -0
  84. package/lib/types/client/compat/desktop.d.ts.map +1 -0
  85. package/lib/types/client/compat/types.d.ts +57 -0
  86. package/lib/types/client/compat/types.d.ts.map +1 -1
  87. package/lib/types/client/current-session-report.d.ts +37 -0
  88. package/lib/types/client/current-session-report.d.ts.map +1 -0
  89. package/lib/types/client/effects/header-status.d.ts.map +1 -1
  90. package/lib/types/client/effects/keyboard-guard.d.ts.map +1 -1
  91. package/lib/types/client/effects/model-group-side.d.ts +13 -0
  92. package/lib/types/client/effects/model-group-side.d.ts.map +1 -0
  93. package/lib/types/client/effects/permission-sheet.d.ts +39 -0
  94. package/lib/types/client/effects/permission-sheet.d.ts.map +1 -0
  95. package/lib/types/client/effects/remote-session.d.ts +25 -0
  96. package/lib/types/client/effects/remote-session.d.ts.map +1 -0
  97. package/lib/types/client/effects/turn-fold.d.ts +9 -0
  98. package/lib/types/client/effects/turn-fold.d.ts.map +1 -1
  99. package/lib/types/client/index.d.ts.map +1 -1
  100. package/lib/types/client/locales.d.ts +187 -2
  101. package/lib/types/client/locales.d.ts.map +1 -1
  102. package/lib/types/client/remote-fetch.d.ts +51 -0
  103. package/lib/types/client/remote-fetch.d.ts.map +1 -0
  104. package/lib/types/client/remote-group-expansion.d.ts +41 -0
  105. package/lib/types/client/remote-group-expansion.d.ts.map +1 -0
  106. package/lib/types/client/remote-share-css.d.ts +19 -0
  107. package/lib/types/client/remote-share-css.d.ts.map +1 -0
  108. package/lib/types/client/remote-share-register.d.ts +113 -0
  109. package/lib/types/client/remote-share-register.d.ts.map +1 -0
  110. package/lib/types/client/remote-status-css.d.ts +15 -0
  111. package/lib/types/client/remote-status-css.d.ts.map +1 -0
  112. package/lib/types/client/remote-status-register.d.ts +46 -0
  113. package/lib/types/client/remote-status-register.d.ts.map +1 -0
  114. package/lib/types/client/settings/SettingsSection.d.ts +46 -0
  115. package/lib/types/client/settings/SettingsSection.d.ts.map +1 -0
  116. package/lib/types/client/settings/register-settings.d.ts +58 -0
  117. package/lib/types/client/settings/register-settings.d.ts.map +1 -0
  118. package/lib/types/client/settings/settings-css.d.ts +10 -0
  119. package/lib/types/client/settings/settings-css.d.ts.map +1 -0
  120. package/lib/types/client/share/share-preview.d.ts.map +1 -1
  121. package/lib/types/client/styles/composer.css.d.ts +1 -1
  122. package/lib/types/client/styles/composer.css.d.ts.map +1 -1
  123. package/lib/types/client/styles/header.css.d.ts +1 -1
  124. package/lib/types/client/styles/header.css.d.ts.map +1 -1
  125. package/lib/types/client/styles/info.css.d.ts +1 -1
  126. package/lib/types/client/styles/info.css.d.ts.map +1 -1
  127. package/lib/types/client/styles/remote-session.css.d.ts +2 -0
  128. package/lib/types/client/styles/remote-session.css.d.ts.map +1 -0
  129. package/lib/types/client-data/current-session.d.ts +41 -0
  130. package/lib/types/client-data/current-session.d.ts.map +1 -0
  131. package/lib/types/client-data/model-group-side.d.ts +44 -0
  132. package/lib/types/client-data/model-group-side.d.ts.map +1 -0
  133. package/lib/types/client-data/remote-group-expansion.d.ts +76 -0
  134. package/lib/types/client-data/remote-group-expansion.d.ts.map +1 -0
  135. package/lib/types/client-data/remote-status.d.ts +148 -0
  136. package/lib/types/client-data/remote-status.d.ts.map +1 -0
  137. package/lib/types/client-data/settings-form.d.ts +774 -0
  138. package/lib/types/client-data/settings-form.d.ts.map +1 -0
  139. package/lib/types/client-data/shares.d.ts +211 -0
  140. package/lib/types/client-data/shares.d.ts.map +1 -0
  141. package/lib/types/client-pairing.d.ts +114 -0
  142. package/lib/types/client-pairing.d.ts.map +1 -0
  143. package/lib/types/client-routes.d.ts +112 -0
  144. package/lib/types/client-routes.d.ts.map +1 -0
  145. package/lib/types/config.d.ts +200 -0
  146. package/lib/types/config.d.ts.map +1 -0
  147. package/lib/types/fetch-route-intercept.d.ts +156 -0
  148. package/lib/types/fetch-route-intercept.d.ts.map +1 -0
  149. package/lib/types/fingerprint.d.ts +166 -0
  150. package/lib/types/fingerprint.d.ts.map +1 -0
  151. package/lib/types/http.d.ts +41 -0
  152. package/lib/types/http.d.ts.map +1 -0
  153. package/lib/types/index.d.ts +56 -24
  154. package/lib/types/index.d.ts.map +1 -1
  155. package/lib/types/intercept-shape.d.ts +32 -0
  156. package/lib/types/intercept-shape.d.ts.map +1 -0
  157. package/lib/types/intercept.d.ts +442 -0
  158. package/lib/types/intercept.d.ts.map +1 -0
  159. package/lib/types/merge-streams.d.ts +398 -0
  160. package/lib/types/merge-streams.d.ts.map +1 -0
  161. package/lib/types/relay-access.d.ts +310 -0
  162. package/lib/types/relay-access.d.ts.map +1 -0
  163. package/lib/types/relay-client.d.ts +274 -0
  164. package/lib/types/relay-client.d.ts.map +1 -0
  165. package/lib/types/relay-filter.d.ts +103 -0
  166. package/lib/types/relay-filter.d.ts.map +1 -0
  167. package/lib/types/relay-server.d.ts +208 -0
  168. package/lib/types/relay-server.d.ts.map +1 -0
  169. package/lib/types/restart-fields.d.ts +22 -0
  170. package/lib/types/restart-fields.d.ts.map +1 -0
  171. package/lib/types/restart-watcher.d.ts +83 -0
  172. package/lib/types/restart-watcher.d.ts.map +1 -0
  173. package/lib/types/session-reference.d.ts +66 -0
  174. package/lib/types/session-reference.d.ts.map +1 -0
  175. package/lib/types/share-export.d.ts +14 -1
  176. package/lib/types/share-export.d.ts.map +1 -1
  177. package/lib/types/share-ops.d.ts +79 -0
  178. package/lib/types/share-ops.d.ts.map +1 -0
  179. package/lib/types/share-store.d.ts +87 -0
  180. package/lib/types/share-store.d.ts.map +1 -0
  181. package/lib/types/virtual-id.d.ts +52 -0
  182. package/lib/types/virtual-id.d.ts.map +1 -0
  183. package/lib/virtual-id.js +66 -0
  184. package/lib/virtual-id.js.map +1 -0
  185. package/llms.txt +17 -14
  186. package/package.json +60 -50
  187. package/scripts/build-client.mjs +5 -3
  188. package/scripts/check-host-shadow.mjs +19 -2
  189. package/scripts/check-settings-form.mjs +933 -0
  190. package/scripts/check-shares.mjs +397 -0
  191. package/scripts/check-upload-endpoint.mjs +59 -3
  192. package/scripts/sync-doc-version.mjs +8 -15
  193. package/scripts/verify-fingerprint-live.mjs +111 -0
  194. package/src/activity.ts +218 -0
  195. package/src/admin-routes.ts +627 -0
  196. package/src/client/MobileAttachButton.tsx +2 -2
  197. package/src/client/MobileAttachChips.tsx +3 -3
  198. package/src/client/MobileDrawerFooter.tsx +7 -4
  199. package/src/client/MobileHome.tsx +20 -16
  200. package/src/client/MobileHomeChips.tsx +16 -16
  201. package/src/client/MobileNavOverlay.tsx +2 -2
  202. package/src/client/MobileNavToggle.tsx +3 -3
  203. package/src/client/MobileSessionHeader.tsx +63 -42
  204. package/src/client/MobileSessionInfo.tsx +118 -17
  205. package/src/client/RemoteComposerBanner.tsx +76 -0
  206. package/src/client/RemoteHeaderIcon.tsx +103 -0
  207. package/src/client/RemoteShareMenu.tsx +57 -0
  208. package/src/client/RemoteStatusIcon.tsx +91 -0
  209. package/src/client/compat/desktop.ts +22 -0
  210. package/src/client/compat/types.ts +62 -0
  211. package/src/client/current-session-report.ts +164 -0
  212. package/src/client/effects/header-status.ts +21 -17
  213. package/src/client/effects/keyboard-guard.ts +65 -1
  214. package/src/client/effects/model-group-side.ts +132 -0
  215. package/src/client/effects/permission-sheet.ts +149 -0
  216. package/src/client/effects/remote-session.ts +68 -0
  217. package/src/client/effects/turn-fold.ts +52 -0
  218. package/src/client/index.tsx +158 -36
  219. package/src/client/locales.ts +396 -2
  220. package/src/client/remote-fetch.ts +125 -0
  221. package/src/client/remote-group-expansion.ts +190 -0
  222. package/src/client/remote-share-css.ts +54 -0
  223. package/src/client/remote-share-register.ts +244 -0
  224. package/src/client/remote-status-css.ts +64 -0
  225. package/src/client/remote-status-register.ts +107 -0
  226. package/src/client/settings/SettingsSection.tsx +1229 -0
  227. package/src/client/settings/register-settings.ts +116 -0
  228. package/src/client/settings/settings-css.ts +236 -0
  229. package/src/client/share/share-preview.tsx +6 -3
  230. package/src/client/share/share-sheet.tsx +3 -3
  231. package/src/client/styles/composer.css.ts +119 -26
  232. package/src/client/styles/header.css.ts +7 -7
  233. package/src/client/styles/info.css.ts +74 -0
  234. package/src/client/styles/remote-session.css.ts +274 -0
  235. package/src/client-data/current-session.ts +57 -0
  236. package/src/client-data/model-group-side.ts +65 -0
  237. package/src/client-data/remote-group-expansion.ts +145 -0
  238. package/src/client-data/remote-status.ts +374 -0
  239. package/src/client-data/settings-form.ts +1371 -0
  240. package/src/client-data/shares.ts +467 -0
  241. package/src/client-pairing.ts +244 -0
  242. package/src/client-routes.ts +732 -0
  243. package/src/config.ts +447 -0
  244. package/src/fetch-route-intercept.ts +510 -0
  245. package/src/fingerprint.ts +410 -0
  246. package/src/http.ts +68 -0
  247. package/src/index.ts +719 -62
  248. package/src/intercept-shape.ts +87 -0
  249. package/src/intercept.ts +3510 -0
  250. package/src/merge-streams.ts +1279 -0
  251. package/src/relay-access.ts +672 -0
  252. package/src/relay-client.ts +1514 -0
  253. package/src/relay-filter.ts +291 -0
  254. package/src/relay-server.ts +2063 -0
  255. package/src/restart-fields.ts +36 -0
  256. package/src/restart-watcher.ts +140 -0
  257. package/src/session-reference.ts +156 -0
  258. package/src/share-export.ts +26 -1
  259. package/src/share-ops.ts +114 -0
  260. package/src/share-store.ts +373 -0
  261. package/src/virtual-id.ts +74 -0
@@ -0,0 +1,68 @@
1
+ import type { ClientContext, MobileSessionRow } from '../compat/types.ts'
2
+ import { mainSessionIdOf } from '../compat/types.ts'
3
+ import { isVirtual } from '../../virtual-id.ts'
4
+ import { REMOTE_SESSION_CSS } from '../styles/remote-session.css.ts'
5
+
6
+ /**
7
+ * The remote-session marker (T41b): the CURRENT main-view session id, read
8
+ * the way the official uiSession `publishMain` reads it (RT
9
+ * dsh-client-ui-session lib/client.js:279-291), stamped onto <html> as
10
+ * `data-zr-remote-session="1"` while it is a virtual (relay) id and removed
11
+ * otherwise. remote-session.css.ts turns the attribute into the hiding of
12
+ * every open-on-the-server-machine entry, so the decision lives at the one
13
+ * place that knows the session and never inside component DOM.
14
+ *
15
+ * The official tie-break is kept verbatim: when the previous answer is still
16
+ * mainView-retained it STAYS the answer, and only otherwise does the first
17
+ * mainView-retained row in catalog order win — so two simultaneously
18
+ * retained rows are decided by "current", never by iteration order. The
19
+ * current value comes from the uiSession service's own main binding source
20
+ * (`current.value.key`); where the service has not registered yet the scan
21
+ * (compat/types.ts mainSessionIdOf) stands in, and a later list or main
22
+ * change re-decides — which is why BOTH sources are subscribed below.
23
+ *
24
+ * Kept outside React for the same reason the header status dot is: the
25
+ * attribute must track the current session regardless of which panel is
26
+ * mounted, and an attribute + stylesheet survives every re-render untouched.
27
+ */
28
+ export function installRemoteSessionGuard(ctx: ClientContext): void {
29
+ ctx.effect(() => {
30
+ const tag = document.createElement('style')
31
+ tag.dataset.plugin = 'dsh-zen-remote'
32
+ tag.dataset.pluginCss = 'dsh-zen-remote/remote-session.css'
33
+ tag.textContent = REMOTE_SESSION_CSS
34
+ document.head.appendChild(tag)
35
+ return () => {
36
+ tag.remove()
37
+ }
38
+ }, 'dsh-zen-remote: remote-session styles')
39
+
40
+ ctx.effect(() => {
41
+ const apply = (): void => {
42
+ const { byId } = ctx.sessions.list.getSnapshot()
43
+ // publishMain's tie-break (see the module comment): prefer the still-
44
+ // retained current main binding, else the first mainView row.
45
+ const uiSession = ctx.get('uiSession') as { current?: { value?: { key?: unknown }, subscribe?: (listener: () => void) => () => void } } | undefined
46
+ const current = uiSession?.current?.value?.key
47
+ const rows = byId as Readonly<Record<string, MobileSessionRow>>
48
+ const sessionId =
49
+ typeof current === 'string' && (rows[current]?.retainedBy?.mainView ?? 0) > 0
50
+ ? current
51
+ : mainSessionIdOf(byId)
52
+ const root = document.documentElement
53
+ if (sessionId !== undefined && isVirtual(sessionId)) root.setAttribute('data-zr-remote-session', '1')
54
+ else root.removeAttribute('data-zr-remote-session')
55
+ }
56
+ apply()
57
+ const offs: Array<() => void> = [ctx.sessions.list.subscribe(apply)]
58
+ // The main binding can move without a list event (a retention change on
59
+ // the current main re-publishes through uiSession's own retain watcher,
60
+ // RT lib/client.js:293-301); subscribe to that source too when it exists.
61
+ const current = (ctx.get('uiSession') as { current?: { subscribe?: (listener: () => void) => () => void } } | undefined)?.current
62
+ if (typeof current?.subscribe === 'function') offs.push(current.subscribe(apply))
63
+ return () => {
64
+ for (const off of offs) off()
65
+ document.documentElement.removeAttribute('data-zr-remote-session')
66
+ }
67
+ }, 'dsh-zen-remote: remote-session attribute')
68
+ }
@@ -44,6 +44,31 @@ async function desktopConfigured(): Promise<boolean> {
44
44
  * marker, so scoping here is what keeps this effect Chat-only. */
45
45
  const FLOW = '[data-chat-flow]'
46
46
 
47
+ /** DSH 0.1.7's official process-group root (ChatGroupSeat). A group nests
48
+ * its OWN `data-chat-flow` layer whose rows BORN_FOLDED would hide by kind,
49
+ * while {@link groupsOf} only walks the outer column — so a folded tool call
50
+ * inside a group would vanish with no chip to recover it. While the page has
51
+ * any official group the whole fold steps aside (rules too: the stylesheet
52
+ * keys its born-folded block on {@link ACTIVE_ATTR}, which the suppress path
53
+ * removes), and returns when the last group leaves the DOM. */
54
+ const OFFICIAL_GROUP = '[data-chat-group-key]'
55
+
56
+ /**
57
+ * One suppression state transition, given whether an official group is on
58
+ * the page and whether the fold is currently suppressed. `'enter'`: groups
59
+ * appeared — drop every mark and the ACTIVE_ATTR the stylesheet keys on.
60
+ * `'exit'`: the last group left — re-arm and fold again. `'hold'`: nothing
61
+ * to do. Exported pure so the state machine itself is testable off-DOM
62
+ * (test/fold-suppression.test.cjs); {@link scan} applies it.
63
+ */
64
+ export function foldSuppressionTransition(
65
+ officialGroupPresent: boolean,
66
+ suppressed: boolean,
67
+ ): 'enter' | 'exit' | 'hold' {
68
+ if (officialGroupPresent) return suppressed ? 'hold' : 'enter'
69
+ return suppressed ? 'exit' : 'hold'
70
+ }
71
+
47
72
  /** Marker + selector of the injected summary row. */
48
73
  const CHIP = 'turn-fold'
49
74
  const CHIP_SELECTOR = '[data-mobile-nav="turn-fold"]'
@@ -199,6 +224,10 @@ export function installTurnFold(ctx: ClientContext): void {
199
224
  const expanded = new Set<string>()
200
225
  let observer: MutationObserver | null = null
201
226
  let frame = 0
227
+ /** True while an official process group is on the page (see OFFICIAL_GROUP):
228
+ * ACTIVE_ATTR is off and every mark is cleared; the observer stays open so
229
+ * the fold resumes by itself once the last group leaves. */
230
+ let suppressed = false
202
231
 
203
232
  const label = (chip: HTMLElement, count: number, running: boolean): void => {
204
233
  const next = running ? t('turnFoldRunning', { count }) : t('turnFold', { count })
@@ -208,6 +237,20 @@ export function installTurnFold(ctx: ClientContext): void {
208
237
  const scan = (): void => {
209
238
  const flow = document.querySelector(FLOW)
210
239
  if (flow === null) return
240
+ const transition = foldSuppressionTransition(
241
+ document.querySelector(OFFICIAL_GROUP) !== null,
242
+ suppressed,
243
+ )
244
+ if (transition === 'enter') {
245
+ suppressed = true
246
+ document.documentElement.removeAttribute(ACTIVE_ATTR)
247
+ clear()
248
+ return
249
+ }
250
+ if (transition === 'exit') {
251
+ suppressed = false
252
+ document.documentElement.setAttribute(ACTIVE_ATTR, '')
253
+ }
211
254
  // TurnStatus, ChatView's own running banner — present only while the
212
255
  // last turn is still working (lib/client.js:5548).
213
256
  const running = flow.querySelector(':scope > [role="status"]') !== null
@@ -284,6 +327,11 @@ export function installTurnFold(ctx: ClientContext): void {
284
327
  * characterData — the fold verdict only changes when elements do.
285
328
  */
286
329
  const markWholeRows = (records: readonly MutationRecord[]): void => {
330
+ // Suppressed (an official group is on the page): reasoning-only rows in
331
+ // the OUTER column would still get marked here — the synchronous path
332
+ // runs before the next scan flips anything — and a marked row stays
333
+ // hidden with no chip to recover it.
334
+ if (suppressed) return
287
335
  const rows = new Set<Element>()
288
336
  for (const record of records) {
289
337
  const target = record.target
@@ -305,6 +353,9 @@ export function installTurnFold(ctx: ClientContext): void {
305
353
  // to bring it back. It also drops rows detached since the batch.
306
354
  const parent = row.parentElement
307
355
  if (parent === null || !parent.matches(FLOW)) continue
356
+ // A group's inner flow layer also matches FLOW; rows inside an
357
+ // official group are never ours to fold (see OFFICIAL_GROUP).
358
+ if (row.closest(OFFICIAL_GROUP) !== null) continue
308
359
  const thinks = [...row.querySelectorAll(THINK)]
309
360
  if (thinks.length > 0 && foldsWholeRow(row, thinks)) row.setAttribute(FOLD, '')
310
361
  else row.removeAttribute(FOLD)
@@ -366,6 +417,7 @@ export function installTurnFold(ctx: ClientContext): void {
366
417
  if (frame !== 0) cancelAnimationFrame(frame)
367
418
  frame = 0
368
419
  expanded.clear()
420
+ suppressed = false
369
421
  clear()
370
422
  }
371
423
 
@@ -1,4 +1,5 @@
1
- import type { ClientContext, SessionId, WorkspaceId } from './compat/types.ts'
1
+ import type { ClientContext, JobsLike, SessionId, WorkspaceId } from './compat/types.ts'
2
+ import { isDesktopShell } from './compat/desktop.ts'
2
3
  import { MobileNavToggle } from './MobileNavToggle.tsx'
3
4
  import { MobileNavOverlay } from './MobileNavOverlay.tsx'
4
5
  import { MobileDrawerFooter } from './MobileDrawerFooter.tsx'
@@ -18,13 +19,28 @@ import { installGestures } from './effects/gestures.ts'
18
19
  import { installTurnFold } from './effects/turn-fold.ts'
19
20
  import { installModalBack } from './effects/modal-back.ts'
20
21
  import { installModelSheetExtras } from './effects/model-sheet-extras.ts'
22
+ import { installPermissionSheet } from './effects/permission-sheet.ts'
21
23
  import { installNativeTriggerOverlay } from './effects/native-trigger-overlay.ts'
22
24
  import { installWelcomeNoticeOptOut } from './effects/welcome-notice.ts'
23
25
  import { installKeyboardGuard } from './effects/keyboard-guard.ts'
24
26
  import { installKeyboardAvoid } from './effects/keyboard-avoid.ts'
25
27
  import { SharePreview } from './share/share-preview.tsx'
26
- import { NS, en, zh } from './locales.ts'
28
+ import { NS } from './locales.ts'
27
29
  import type { MobileNavKey } from './locales.ts'
30
+ import { SettingsSection } from './settings/SettingsSection.tsx'
31
+ import { registerSettingsPage } from './settings/register-settings.ts'
32
+ import { RemoteHeaderIcon } from './RemoteHeaderIcon.tsx'
33
+ import { RemoteShareMenuItem } from './RemoteShareMenu.tsx'
34
+ import { getSharesStore } from '../client-data/shares.ts'
35
+ import { registerRemoteShareUi } from './remote-share-register.ts'
36
+ import { RemoteStatusIcon } from './RemoteStatusIcon.tsx'
37
+ import { RemoteComposerBanner } from './RemoteComposerBanner.tsx'
38
+ import { registerRemoteStatusUi } from './remote-status-register.ts'
39
+ import { mountCurrentSessionReporter } from './current-session-report.ts'
40
+ import { mountRemoteGroupExpansionKeeper } from './remote-group-expansion.ts'
41
+ import { installRemoteApiFetch } from './remote-fetch.ts'
42
+ import { installRemoteSessionGuard } from './effects/remote-session.ts'
43
+ import { installModelGroupSide } from './effects/model-group-side.ts'
28
44
 
29
45
  declare module '@deepseek-ai/dsh-client-ui-slots' {
30
46
  interface LocaleNamespaceMap {
@@ -37,20 +53,50 @@ declare module '@deepseek-ai/dsh-client-ui-slots' {
37
53
  export const inject = ['slots', 'layout', 'locale', 'sessionLogDownload', 'sessions', 'workspaces']
38
54
 
39
55
  /**
40
- * 0.1.2 把新建会话和归档会话的界面级操作挪到了 uiWorkspace 服务;
41
- * 0.1.1 没有这个服务,那两个操作还在 ctx.workspaces 上。这里按存在探测,
42
- * 不把 uiWorkspace 写进 inject(写了 0.1.1 就永远不激活)。
43
- * 类型用本地最小接口,不 import 那个 dsh-client-ui-workspace 包
44
- * ——0.1.1 装不到。
56
+ * 0.1.2 把新建会话、归档会话与打开会话的界面级操作挪到了 uiWorkspace
57
+ * 服务(0.1.7 删掉了 ctx.sessions.open,这是它唯一的替代入口)。类型用
58
+ * 本地最小接口,不 import 那个 dsh-client-ui-workspace 包——本插件的
59
+ * peerDependencies 不含它,运行时实例由宿主提供,类型自己声明就够。
45
60
  */
46
61
  interface UiWorkspaceLike {
62
+ openSession(target: SessionId): void
47
63
  startSession(workspaceId?: WorkspaceId): void
48
64
  archiveSession(sessionId: SessionId): Promise<void>
49
65
  }
50
66
 
51
- /** 0.1.1 的 ctx.workspaces 上还有 startSession;0.1.2 的类型里没有了。 */
52
- interface LegacyWorkspacesLike {
53
- startSession(workspaceId?: WorkspaceId): void
67
+ /**
68
+ * Activity data both session-header entries read (0.1.7 sources — 0.1.5's
69
+ * per-parent subagent snapshot and per-session job snapshot are gone):
70
+ * - the subagent catalog is read straight off the sessions snapshot's
71
+ * explicit-read store (`projectionsBySession[id].values.subagentCatalog`),
72
+ * exactly like the official SubagentHeaderLineage — no refresh is issued
73
+ * from here: every projection refresh call registers another session in
74
+ * the controller's load table, and all of them are re-read on every
75
+ * reconnect (manager handleConnected), so per-open refreshes would grow
76
+ * into a batch of re-reads forever;
77
+ * - job rows come from the `jobs` client service; the inject face mirrors
78
+ * the official JobListAction registration (`hooks.jobs` observable → the
79
+ * renderer hands the component a `useJobs` hook prop). When the service is
80
+ * absent (a build without the job controller) a fixed empty source keeps
81
+ * the prop present, so the component's hook count never varies — the
82
+ * counts simply read zero instead of the pill silently disappearing.
83
+ */
84
+ const NO_JOBS_SNAPSHOT = Object.freeze({ rows: Object.freeze({}) }) as {
85
+ readonly rows: Readonly<Record<string, readonly never[]>>
86
+ }
87
+ /** Module-level singleton: one stable observable identity across renders. */
88
+ const NO_JOBS_SOURCE = {
89
+ getSnapshot: () => NO_JOBS_SNAPSHOT,
90
+ subscribe: (): (() => void) => () => {},
91
+ }
92
+ const NO_WATCH_ROWS = (): (() => void) => () => {}
93
+
94
+ function activityInject(ctx: ClientContext) {
95
+ const jobs = ctx.get('jobs') as JobsLike | undefined
96
+ return {
97
+ hooks: { jobs: jobs === undefined ? NO_JOBS_SOURCE : jobs.state },
98
+ watchRows: jobs === undefined ? NO_WATCH_ROWS : (id: SessionId) => jobs.watchRows(id),
99
+ }
54
100
  }
55
101
 
56
102
  /**
@@ -60,7 +106,64 @@ interface LegacyWorkspacesLike {
60
106
  * @param ctx - client root context.
61
107
  */
62
108
  export function apply(ctx: ClientContext): void {
63
- ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'dsh-mobile-nav: dictionaries')
109
+ // 2.0.0 设置页:必须在桌面门之前注册——桌面端 App 里手机外壳整体不生效,
110
+ // 但设置页必须生效(主服务端就是桌面端)。字典、设置页样式与设置表单服务
111
+ // 的按需注入都在 registerSettingsPage 里(那个服务名绝不进顶层的 inject
112
+ // 数组,否则没有该服务的环境整个界面半边都加载不了)。
113
+ registerSettingsPage(ctx, SettingsSection)
114
+
115
+ // T33b 会话共享的三个入口里前两个(标题行远程图标、会话右键菜单项):
116
+ // 同样必须在桌面门之前注册——桌面端 App 就是主服务端,图标和菜单恰好
117
+ // 住在它的标题行与会话菜单里。部件自身按数据降级:shares 路由 404
118
+ // (子客户端部署没有这条路由)时渲染为空;手机外壳下样式表把
119
+ // header.actions 全量隐藏(styles/header.css.ts),图标也就不订阅
120
+ // (subscribeIfNotPhoneShell)——订阅是轮询的燃料,不订阅就不发
121
+ // admin/shares 请求。设置页的共享列表在 SettingsSection 内部,随设置
122
+ // 页已在门之前。
123
+ registerRemoteShareUi(ctx, RemoteHeaderIcon, RemoteShareMenuItem)
124
+
125
+ // T34 子客户端的远程状态两件套(标题行连接图标、composer 横幅):同样在
126
+ // 桌面门之前注册——桌面端 App 也能以 client 角色接入服务端。部件自身按
127
+ // 数据降级:只在虚拟 id 会话渲染、首次 GET 应答前不渲染,所以主服务端
128
+ // 的页面上两者都是空,桌面保持逐像素 no-op;订阅本身也按虚拟 id 门控
129
+ // (remote-status.ts 的 subscribeIfVirtual)——本地会话不建立订阅,
130
+ // store 只在有订阅者时才轮询,主服务端的页面不白发 remote-status 请求。
131
+ registerRemoteStatusUi(ctx, RemoteStatusIcon, RemoteComposerBanner)
132
+ // T62 当前会话信号的上报:拦截层跑在后台 Node 进程里,读不到浏览器的
133
+ // localStorage——由这里(唯一读得到宿主选择存储的地方)把「当前打开的
134
+ // 会话」在值变化时 POST 给后台路由。同样在桌面门之前挂载:client 角色
135
+ // 的桌面端 App 正是远程会话所在;只在探测出的子客户端角色下运行,主
136
+ // 服务端的页面探测一次后就静默。
137
+ mountCurrentSessionReporter(ctx)
138
+ // T64 服务端分组展开状态的记忆与恢复:宿主在工作区列表 ready 时会把不在
139
+ // 列表里的分组展开记录清掉(retainAccountKeys),而本地工作区先 ready、
140
+ // 服务端分组后并入,zr~ 键每次加载都被清掉、分组回到折叠。与上报器共用
141
+ // 挂载点与角色判断(同一个 client-config 探测)、同样的 1 秒节拍(自己的
142
+ // 定时器),逻辑在 remote-group-expansion.ts 自己的文件里。
143
+ mountRemoteGroupExpansionKeeper(ctx)
144
+ // T41b 的两个远程会话部件:fetch 改写(改动 / diff 面板的两条 /api GET)与
145
+ // 远程会话标记(data-zr-remote-session + 隐藏「在服务端机器上打开」类入口
146
+ // 的样式)。同样必须在桌面门之前注册——桌面窗口也可以是配对好的子客户端,
147
+ // 改写与隐藏在它里面一样必要。改写只碰「同源 + 精确两条路径 + zr~ 虚拟
148
+ // 会话 id」的 GET(remote-fetch.ts),宿主角色的桌面端(会话 id 永非虚拟)
149
+ // 逐字节原样放行;标记属性只在虚拟会话打开时出现,卸载时还原。
150
+ ctx.effect(() => installRemoteApiFetch(window), 'dsh-zen-remote: remote api fetch')
151
+ installRemoteSessionGuard(ctx)
152
+ // T74 模型选单分组按会话归属显示:0.2.0 的 MenuGroup 的 aria-labelledby 只剩
153
+ // useId(分组 id 不再出现在标记里),旧的 zr~ 前缀选择器失效——改为 effect 给
154
+ // 分组打 data-zr-group 标(分类依据是虚拟分组名开头的不可见标记,见
155
+ // client-data/model-group-side.ts),CSS 按标隐藏。同样必须在桌面门之前挂载:
156
+ // 桌面端子客户端的模型选单一样要分侧,桌面无手机外壳不影响它。
157
+ installModelGroupSide(ctx)
158
+
159
+ // Desktop gate (DSH 0.1.7): the official Electron shell can be dragged
160
+ // down to ~520px wide, where every width-based gate would flip the phone
161
+ // shell on inside the desktop app. Inside that shell this plugin is a
162
+ // complete no-op — no stylesheet, no effects, no slot entries, which is
163
+ // bit-for-bit "not installed" and also retires the desktop-fold knobs
164
+ // there (they exist for narrow desktop *browsers*, which never carry the
165
+ // dshDesktop bridge). See compat/desktop.ts for the marker's provenance.
166
+ if (isDesktopShell()) return
64
167
 
65
168
  ctx.effect(() => {
66
169
  const tag = document.createElement('style')
@@ -111,6 +214,9 @@ export function apply(ctx: ClientContext): void {
111
214
  // Third-party composer entries (speed chip, vision toggle) move into the
112
215
  // model sheet — the row has no width to spare and both are model settings.
113
216
  installModelSheetExtras(ctx)
217
+ // T70: the permission menu portals to body on DSH 0.2.0 — mark it so
218
+ // composer.css section 4 can still make it the bottom sheet.
219
+ installPermissionSheet(ctx)
114
220
 
115
221
  // "内测声明" first-run notice: keep it visible (CSS-hiding it leaked the
116
222
  // dialog's #root inert lock — see effects/welcome-notice.ts) and offer a
@@ -156,6 +262,8 @@ export function apply(ctx: ClientContext): void {
156
262
  id: 'mobile-header-actions',
157
263
  order: 0,
158
264
  locale: NS,
265
+ // Activity-pill data (subagent catalog + job roster), see activityInject.
266
+ inject: () => activityInject(ctx),
159
267
  }, MobileHeaderActions))
160
268
 
161
269
  // Session-info entry (placeholder — S4 owns the sheet) + workbench entry
@@ -180,14 +288,29 @@ export function apply(ctx: ClientContext): void {
180
288
  locale: NS,
181
289
  // The factory's own sessionId param is unused: every function below
182
290
  // takes its own session id explicitly (they're generic action bindings
183
- // reused verbatim, not closures over one particular session).
291
+ // reused verbatim, not closures over one particular session). The
292
+ // spread adds the activity-pill data (subagent catalog + job roster for
293
+ // the info-sheet badges), see activityInject.
184
294
  inject: (_sessionId: SessionId) => ({
295
+ ...activityInject(ctx),
296
+ // T67: the SAME page-wide shares store the header icon and the menu
297
+ // item read — the row subscribes only while the sheet is open, so no
298
+ // second poll loop and no phone-shell polling either.
299
+ shares: getSharesStore(),
185
300
  forkSession: (id: SessionId) => ctx.sessions.fork({ sessionId: id }),
186
- openSession: (id: SessionId) => ctx.sessions.open(id),
301
+ // 0.1.7: ctx.sessions.open is gone; uiWorkspace.openSession is the one
302
+ // navigation entry (also what the official subagent catalog uses).
303
+ // Lazy lookup like every other uiWorkspace binding here: the callback
304
+ // runs on a user tap, by which time the service is registered.
305
+ openSession: (id: SessionId) => {
306
+ const ui = ctx.get('uiWorkspace') as UiWorkspaceLike | undefined
307
+ if (ui === undefined) console.warn('[dsh-zen-remote] uiWorkspace service unavailable; cannot open session', id)
308
+ else ui.openSession(id)
309
+ },
187
310
  renameSession: (id: SessionId, title: string) => ctx.sessions.binding(id)?.session.rename(title),
188
311
  // 0.1.2 的界面级归档在 uiWorkspace(顺带清当前选中);懒查——回调是用户
189
- // 点击才跑,那时服务必已注册。本插件的 inject 不包含 uiWorkspace(0.1.1
190
- // 没有它),所以启动时不能查一次就用:可能早于它注册。
312
+ // 点击才跑,那时服务必已注册。本插件的 inject 不包含 uiWorkspace,
313
+ // 所以启动时不能查一次就用:可能早于它注册。
191
314
  archiveSession: (id: SessionId) => {
192
315
  const ui = ctx.get('uiWorkspace') as UiWorkspaceLike | undefined
193
316
  return ui === undefined ? ctx.workspaces.archiveSession(id) : ui.archiveSession(id)
@@ -242,16 +365,18 @@ export function apply(ctx: ClientContext): void {
242
365
  locale: NS,
243
366
  store: nav,
244
367
  inject: () => ({
245
- openSession: (id: SessionId) => ctx.sessions.open(id),
368
+ // Same uiWorkspace.openSession binding as the session-info sheet above.
369
+ openSession: (id: SessionId) => {
370
+ const ui = ctx.get('uiWorkspace') as UiWorkspaceLike | undefined
371
+ if (ui === undefined) console.warn('[dsh-zen-remote] uiWorkspace service unavailable; cannot open session', id)
372
+ else ui.openSession(id)
373
+ },
246
374
  // uiWorkspace 懒查:回调是用户点击才跑,服务那时必已注册(inject 不含
247
- // uiWorkspace,启动时可能先于它注册,不能启动查一次就用)。0.1.1 上恒
248
- // undefined,退回 ctx.workspaces.startSession——0.1.1 专用分支,0.1.2 的
249
- // 类型里该方法已不存在,故对类型做 LegacyWorkspacesLike cast,运行时走不到。
375
+ // uiWorkspace,启动时可能先于它注册,不能启动查一次就用)。
250
376
  startSession: (workspaceId?: WorkspaceId) => {
251
377
  const ui = ctx.get('uiWorkspace') as UiWorkspaceLike | undefined
252
- return ui === undefined
253
- ? (ctx.workspaces as unknown as LegacyWorkspacesLike).startSession(workspaceId)
254
- : ui.startSession(workspaceId)
378
+ if (ui === undefined) console.warn('[dsh-zen-remote] uiWorkspace service unavailable; cannot start a session')
379
+ else ui.startSession(workspaceId)
255
380
  },
256
381
  // S5 session-log chip — the same service call MobileDrawerFooter uses.
257
382
  downloadSessionLog: (id: SessionId) => ctx.sessionLogDownload.download(id),
@@ -315,22 +440,19 @@ import type {} from '@deepseek-ai/dsh-client-ui-conversation/client'
315
440
  import type {} from '@deepseek-ai/dsh-client-ui-sidebar/client'
316
441
  import type {} from '@deepseek-ai/dsh-client-locale/client'
317
442
  import type {} from '@deepseek-ai/dsh-session-log-export/client'
318
- // 0.1.2-only package: augments GlobalStandardProps with
319
- // useSessions/useSessionPendingInteraction and SessionStandardProps with
320
- // sessionId/useSession/useProjection, and declares the `ctx.uiSession`
321
- // service. 0.1.1 has no such package — but this is `import type`, so the
322
- // build output carries no trace of it and the runtime is unaffected.
443
+ // ui-session: augments GlobalStandardProps with useSessions /
444
+ // useSessionPendingInteraction / useSessionStatus and SessionStandardProps
445
+ // with sessionId/useSession/useProjection, and declares the `ctx.uiSession`
446
+ // service.
323
447
  import type {} from '@deepseek-ai/dsh-client-ui-session/client'
324
- // 0.1.2-only controller packages: their `/client` type entries merge
325
- // `ctx.sessions` / `ctx.workspaces` onto the cordis Context (0.1.1 declared
326
- // both from dsh-client-runtime). Type-only again — nothing to load at
327
- // runtime, so a 0.1.1 installation is unaffected.
448
+ // Controller packages: their `/client` type entries merge `ctx.sessions` /
449
+ // `ctx.workspaces` onto the cordis Context. Type-only — nothing to load at
450
+ // runtime.
328
451
  import type {} from '@deepseek-ai/dsh-api-session-controller/client'
329
452
  import type {} from '@deepseek-ai/dsh-api-workspace-controller/client'
330
- // 0.1.2-only, type-only: the renderer declares `ctx.slots` on the cordis
331
- // Context (the service this plugin registers every slot through) and
332
- // `dsh-subagent/client` types the subagent catalog rows the session header
333
- // reads (SessionListState.subagentsByParent). 0.1.1 had both declared in the
334
- // dsh-client-runtime bundle that no longer exists.
453
+ // Type-only: the renderer declares `ctx.slots` on the cordis Context (the
454
+ // service this plugin registers every slot through) and `dsh-subagent/client`
455
+ // types the subagent catalog projection entries the session header reads
456
+ // (projectionsBySession[].values.subagentCatalog on 0.1.7).
335
457
  import type {} from '@deepseek-ai/dsh-client-ui-renderer/client'
336
458
  import type {} from '@deepseek-ai/dsh-subagent/client'