dsh-zen-remote 1.1.15 → 2.0.0

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 (272) hide show
  1. package/AGENTS.md +54 -9
  2. package/README.md +249 -190
  3. package/README.zh-CN.md +248 -202
  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 +82 -4
  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 +11268 -5844
  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 +363 -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 +3040 -0
  38. package/lib/intercept.js.map +1 -0
  39. package/lib/lan-gate-server.cjs +532 -121
  40. package/lib/merge-streams.js +1165 -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 +1792 -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 +19 -8
  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/gestures.d.ts.map +1 -1
  90. package/lib/types/client/effects/header-status.d.ts.map +1 -1
  91. package/lib/types/client/effects/keyboard-guard.d.ts.map +1 -1
  92. package/lib/types/client/effects/model-sheet-extras.d.ts.map +1 -1
  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/effects/workbench-ref-close.d.ts +2 -1
  100. package/lib/types/client/effects/workbench-ref-close.d.ts.map +1 -1
  101. package/lib/types/client/index.d.ts.map +1 -1
  102. package/lib/types/client/locales.d.ts +187 -2
  103. package/lib/types/client/locales.d.ts.map +1 -1
  104. package/lib/types/client/remote-fetch.d.ts +51 -0
  105. package/lib/types/client/remote-fetch.d.ts.map +1 -0
  106. package/lib/types/client/remote-group-expansion.d.ts +41 -0
  107. package/lib/types/client/remote-group-expansion.d.ts.map +1 -0
  108. package/lib/types/client/remote-share-css.d.ts +19 -0
  109. package/lib/types/client/remote-share-css.d.ts.map +1 -0
  110. package/lib/types/client/remote-share-register.d.ts +113 -0
  111. package/lib/types/client/remote-share-register.d.ts.map +1 -0
  112. package/lib/types/client/remote-status-css.d.ts +15 -0
  113. package/lib/types/client/remote-status-css.d.ts.map +1 -0
  114. package/lib/types/client/remote-status-register.d.ts +46 -0
  115. package/lib/types/client/remote-status-register.d.ts.map +1 -0
  116. package/lib/types/client/settings/SettingsSection.d.ts +46 -0
  117. package/lib/types/client/settings/SettingsSection.d.ts.map +1 -0
  118. package/lib/types/client/settings/register-settings.d.ts +58 -0
  119. package/lib/types/client/settings/register-settings.d.ts.map +1 -0
  120. package/lib/types/client/settings/settings-css.d.ts +10 -0
  121. package/lib/types/client/settings/settings-css.d.ts.map +1 -0
  122. package/lib/types/client/share/share-preview.d.ts.map +1 -1
  123. package/lib/types/client/sidebar-panels.d.ts +63 -0
  124. package/lib/types/client/sidebar-panels.d.ts.map +1 -0
  125. package/lib/types/client/styles/compat.css.d.ts +1 -1
  126. package/lib/types/client/styles/compat.css.d.ts.map +1 -1
  127. package/lib/types/client/styles/composer.css.d.ts +1 -1
  128. package/lib/types/client/styles/composer.css.d.ts.map +1 -1
  129. package/lib/types/client/styles/header.css.d.ts +1 -1
  130. package/lib/types/client/styles/header.css.d.ts.map +1 -1
  131. package/lib/types/client/styles/info.css.d.ts +1 -1
  132. package/lib/types/client/styles/info.css.d.ts.map +1 -1
  133. package/lib/types/client/styles/layout.css.d.ts +1 -1
  134. package/lib/types/client/styles/layout.css.d.ts.map +1 -1
  135. package/lib/types/client/styles/remote-session.css.d.ts +2 -0
  136. package/lib/types/client/styles/remote-session.css.d.ts.map +1 -0
  137. package/lib/types/client-data/current-session.d.ts +41 -0
  138. package/lib/types/client-data/current-session.d.ts.map +1 -0
  139. package/lib/types/client-data/remote-group-expansion.d.ts +76 -0
  140. package/lib/types/client-data/remote-group-expansion.d.ts.map +1 -0
  141. package/lib/types/client-data/remote-status.d.ts +148 -0
  142. package/lib/types/client-data/remote-status.d.ts.map +1 -0
  143. package/lib/types/client-data/settings-form.d.ts +774 -0
  144. package/lib/types/client-data/settings-form.d.ts.map +1 -0
  145. package/lib/types/client-data/shares.d.ts +211 -0
  146. package/lib/types/client-data/shares.d.ts.map +1 -0
  147. package/lib/types/client-pairing.d.ts +114 -0
  148. package/lib/types/client-pairing.d.ts.map +1 -0
  149. package/lib/types/client-routes.d.ts +112 -0
  150. package/lib/types/client-routes.d.ts.map +1 -0
  151. package/lib/types/config.d.ts +200 -0
  152. package/lib/types/config.d.ts.map +1 -0
  153. package/lib/types/fetch-route-intercept.d.ts +156 -0
  154. package/lib/types/fetch-route-intercept.d.ts.map +1 -0
  155. package/lib/types/fingerprint.d.ts +164 -0
  156. package/lib/types/fingerprint.d.ts.map +1 -0
  157. package/lib/types/http.d.ts +41 -0
  158. package/lib/types/http.d.ts.map +1 -0
  159. package/lib/types/index.d.ts +56 -24
  160. package/lib/types/index.d.ts.map +1 -1
  161. package/lib/types/intercept-shape.d.ts +32 -0
  162. package/lib/types/intercept-shape.d.ts.map +1 -0
  163. package/lib/types/intercept.d.ts +432 -0
  164. package/lib/types/intercept.d.ts.map +1 -0
  165. package/lib/types/merge-streams.d.ts +393 -0
  166. package/lib/types/merge-streams.d.ts.map +1 -0
  167. package/lib/types/relay-access.d.ts +310 -0
  168. package/lib/types/relay-access.d.ts.map +1 -0
  169. package/lib/types/relay-client.d.ts +274 -0
  170. package/lib/types/relay-client.d.ts.map +1 -0
  171. package/lib/types/relay-filter.d.ts +103 -0
  172. package/lib/types/relay-filter.d.ts.map +1 -0
  173. package/lib/types/relay-server.d.ts +208 -0
  174. package/lib/types/relay-server.d.ts.map +1 -0
  175. package/lib/types/restart-fields.d.ts +22 -0
  176. package/lib/types/restart-fields.d.ts.map +1 -0
  177. package/lib/types/restart-watcher.d.ts +83 -0
  178. package/lib/types/restart-watcher.d.ts.map +1 -0
  179. package/lib/types/session-reference.d.ts +66 -0
  180. package/lib/types/session-reference.d.ts.map +1 -0
  181. package/lib/types/share-export.d.ts +14 -1
  182. package/lib/types/share-export.d.ts.map +1 -1
  183. package/lib/types/share-ops.d.ts +79 -0
  184. package/lib/types/share-ops.d.ts.map +1 -0
  185. package/lib/types/share-store.d.ts +87 -0
  186. package/lib/types/share-store.d.ts.map +1 -0
  187. package/lib/types/virtual-id.d.ts +38 -0
  188. package/lib/types/virtual-id.d.ts.map +1 -0
  189. package/lib/virtual-id.js +52 -0
  190. package/lib/virtual-id.js.map +1 -0
  191. package/llms.txt +17 -14
  192. package/package.json +60 -50
  193. package/scripts/build-client.mjs +5 -3
  194. package/scripts/check-host-shadow.mjs +19 -2
  195. package/scripts/check-settings-form.mjs +933 -0
  196. package/scripts/check-shares.mjs +397 -0
  197. package/scripts/check-upload-endpoint.mjs +59 -3
  198. package/scripts/sync-doc-version.mjs +8 -15
  199. package/scripts/verify-fingerprint-live.mjs +111 -0
  200. package/src/activity.ts +218 -0
  201. package/src/admin-routes.ts +627 -0
  202. package/src/client/MobileAttachButton.tsx +2 -2
  203. package/src/client/MobileAttachChips.tsx +3 -3
  204. package/src/client/MobileDrawerFooter.tsx +7 -4
  205. package/src/client/MobileHome.tsx +20 -16
  206. package/src/client/MobileHomeChips.tsx +25 -21
  207. package/src/client/MobileNavOverlay.tsx +2 -2
  208. package/src/client/MobileNavToggle.tsx +3 -3
  209. package/src/client/MobileSessionHeader.tsx +100 -107
  210. package/src/client/MobileSessionInfo.tsx +118 -17
  211. package/src/client/RemoteComposerBanner.tsx +76 -0
  212. package/src/client/RemoteHeaderIcon.tsx +103 -0
  213. package/src/client/RemoteShareMenu.tsx +57 -0
  214. package/src/client/RemoteStatusIcon.tsx +91 -0
  215. package/src/client/compat/desktop.ts +22 -0
  216. package/src/client/compat/types.ts +62 -0
  217. package/src/client/current-session-report.ts +164 -0
  218. package/src/client/effects/gestures.ts +8 -17
  219. package/src/client/effects/header-status.ts +21 -17
  220. package/src/client/effects/keyboard-avoid.ts +2 -2
  221. package/src/client/effects/keyboard-guard.ts +75 -7
  222. package/src/client/effects/model-sheet-extras.ts +8 -2
  223. package/src/client/effects/permission-sheet.ts +149 -0
  224. package/src/client/effects/remote-session.ts +68 -0
  225. package/src/client/effects/turn-fold.ts +52 -0
  226. package/src/client/effects/workbench-ref-close.ts +16 -23
  227. package/src/client/index.tsx +151 -36
  228. package/src/client/locales.ts +396 -2
  229. package/src/client/remote-fetch.ts +125 -0
  230. package/src/client/remote-group-expansion.ts +190 -0
  231. package/src/client/remote-share-css.ts +54 -0
  232. package/src/client/remote-share-register.ts +244 -0
  233. package/src/client/remote-status-css.ts +64 -0
  234. package/src/client/remote-status-register.ts +107 -0
  235. package/src/client/settings/SettingsSection.tsx +1229 -0
  236. package/src/client/settings/register-settings.ts +116 -0
  237. package/src/client/settings/settings-css.ts +236 -0
  238. package/src/client/share/share-preview.tsx +6 -3
  239. package/src/client/share/share-sheet.tsx +3 -3
  240. package/src/client/sidebar-panels.ts +99 -0
  241. package/src/client/styles/compat.css.ts +10 -0
  242. package/src/client/styles/composer.css.ts +147 -36
  243. package/src/client/styles/header.css.ts +15 -11
  244. package/src/client/styles/info.css.ts +74 -0
  245. package/src/client/styles/layout.css.ts +20 -0
  246. package/src/client/styles/remote-session.css.ts +251 -0
  247. package/src/client-data/current-session.ts +57 -0
  248. package/src/client-data/remote-group-expansion.ts +145 -0
  249. package/src/client-data/remote-status.ts +374 -0
  250. package/src/client-data/settings-form.ts +1371 -0
  251. package/src/client-data/shares.ts +467 -0
  252. package/src/client-pairing.ts +244 -0
  253. package/src/client-routes.ts +732 -0
  254. package/src/config.ts +447 -0
  255. package/src/fetch-route-intercept.ts +510 -0
  256. package/src/fingerprint.ts +408 -0
  257. package/src/http.ts +68 -0
  258. package/src/index.ts +719 -62
  259. package/src/intercept-shape.ts +87 -0
  260. package/src/intercept.ts +3362 -0
  261. package/src/merge-streams.ts +1272 -0
  262. package/src/relay-access.ts +669 -0
  263. package/src/relay-client.ts +1514 -0
  264. package/src/relay-filter.ts +291 -0
  265. package/src/relay-server.ts +2025 -0
  266. package/src/restart-fields.ts +36 -0
  267. package/src/restart-watcher.ts +140 -0
  268. package/src/session-reference.ts +156 -0
  269. package/src/share-export.ts +26 -1
  270. package/src/share-ops.ts +114 -0
  271. package/src/share-store.ts +373 -0
  272. package/src/virtual-id.ts +59 -0
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Shared "which sessions have remote enabled" table (2.0.0 remote-session
3
+ * groundwork): pure logic plus a small JSON file under the DSH data dir. The
4
+ * server opens/closes remote per session; a paired desktop client may close
5
+ * but never open; everything else here exists to make one rule survivable
6
+ * across restarts — an idle shared session must put itself away.
7
+ *
8
+ * Three clock rules, all driven by the caller:
9
+ * - `touch()` marks activity (turn start/end, message sent, approval or
10
+ * question answered — opening a session to look at it is NOT activity).
11
+ * - `setBusy()` marks running/waiting; busy sessions never idle out and the
12
+ * idle clock restarts from the moment busy ends.
13
+ * - everything else is derived: `sweep()` closes whatever has been idle past
14
+ * `idleHours`, `remainingMs()` reports the countdown.
15
+ *
16
+ * Accessibility crosses generations: subagent and fork sessions are reachable
17
+ * when any ancestor is shared, without themselves appearing in the table
18
+ * (`isAccessible` walks `parentOf`).
19
+ *
20
+ * Persistence is deliberately dumb: one JSON file, rewritten atomically
21
+ * (tmp + rename) after every operation that changes what the file would say,
22
+ * synchronously, because a crash right after "share" must not silently
23
+ * un-share. `busy` is never persisted — after a restart every session is
24
+ * idle by definition until the caller says otherwise.
25
+ */
26
+ /** One row of the table, as `list()` reports it. */
27
+ export interface ShareEntry {
28
+ sessionId: string;
29
+ /** When remote was first enabled (repeat `share` does not move it). */
30
+ sharedAt: number;
31
+ /** Last activity the caller announced via `touch()` (or busy-end). */
32
+ lastActivityAt: number;
33
+ /** Memory-only: never persisted, always false after a restart. */
34
+ busy: boolean;
35
+ }
36
+ /** Why a session left the table. `manual` = server-side close, `client` =
37
+ * desktop-client close, `idle` = automatic idle shutdown. */
38
+ export type UnshareReason = 'manual' | 'client' | 'idle';
39
+ export type ShareEvent = {
40
+ type: 'shared';
41
+ sessionId: string;
42
+ } | {
43
+ type: 'unshared';
44
+ sessionId: string;
45
+ reason: UnshareReason;
46
+ };
47
+ export interface ShareStoreOptions {
48
+ /** Absolute path of the persistence file. */
49
+ file: string;
50
+ /** Idle budget in hours; must be 0 < x ≤ 8760, anything else falls back
51
+ * to 48. Changeable later via `setIdleHours`. */
52
+ idleHours: number;
53
+ /** Injectable clock; defaults to Date.now. */
54
+ now?: () => number;
55
+ }
56
+ export interface ShareStore {
57
+ /** Enable remote for a session. True when newly shared; false when it was
58
+ * already shared (sharedAt stays put, lastActivityAt refreshes to now). */
59
+ share(sessionId: string): boolean;
60
+ /** Remove a session from the table. True when it was actually removed. */
61
+ unshare(sessionId: string, reason: UnshareReason): boolean;
62
+ /** Whether the session itself is in the table (inheritance NOT applied). */
63
+ isShared(sessionId: string): boolean;
64
+ /** Whether the session is reachable for remote access: itself shared, or
65
+ * some ancestor within 16 `parentOf` hops shared. */
66
+ isAccessible(sessionId: string, parentOf: (id: string) => string | undefined): boolean;
67
+ /** Announce activity. No-op for sessions not in the table. */
68
+ touch(sessionId: string): void;
69
+ /** Announce running/waiting state. No-op for sessions not in the table.
70
+ * busy true→false restarts the idle clock from now. */
71
+ setBusy(sessionId: string, busy: boolean): void;
72
+ /** Close every expired session now. Returns the closed ids, sorted. */
73
+ sweep(): string[];
74
+ /** undefined when not shared; Infinity while busy; otherwise how many ms
75
+ * are left before this session would idle out (never negative). */
76
+ remainingMs(sessionId: string): number | undefined;
77
+ /** Snapshot of the table, sharedAt ascending (ties broken by id). */
78
+ list(): ShareEntry[];
79
+ /** Replace the idle budget. Invalid values are ignored; takes effect
80
+ * immediately for subsequent sweep/remainingMs calls. */
81
+ setIdleHours(hours: number): void;
82
+ /** Observe share/unshare events. Listener errors are swallowed. Returns
83
+ * an unsubscribe function. */
84
+ subscribe(listener: (event: ShareEvent) => void): () => void;
85
+ }
86
+ export declare function createShareStore(options: ShareStoreOptions): ShareStore;
87
+ //# sourceMappingURL=share-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"share-store.d.ts","sourceRoot":"","sources":["../../src/share-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAKH,oDAAoD;AACpD,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,MAAM,CAAA;IACjB,uEAAuE;IACvE,QAAQ,EAAE,MAAM,CAAA;IAChB,sEAAsE;IACtE,cAAc,EAAE,MAAM,CAAA;IACtB,kEAAkE;IAClE,IAAI,EAAE,OAAO,CAAA;CACd;AAED;6DAC6D;AAC7D,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,QAAQ,GAAG,MAAM,CAAA;AAExD,MAAM,MAAM,UAAU,GAClB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GACrC;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,aAAa,CAAA;CAAE,CAAA;AAElE,MAAM,WAAW,iBAAiB;IAChC,6CAA6C;IAC7C,IAAI,EAAE,MAAM,CAAA;IACZ;qDACiD;IACjD,SAAS,EAAE,MAAM,CAAA;IACjB,8CAA8C;IAC9C,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,UAAU;IACzB;+EAC2E;IAC3E,KAAK,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAA;IACjC,0EAA0E;IAC1E,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,GAAG,OAAO,CAAA;IAC1D,4EAA4E;IAC5E,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAA;IACpC;yDACqD;IACrD,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,GAAG,OAAO,CAAA;IACtF,8DAA8D;IAC9D,KAAK,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B;2DACuD;IACvD,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,IAAI,CAAA;IAC/C,uEAAuE;IACvE,KAAK,IAAI,MAAM,EAAE,CAAA;IACjB;uEACmE;IACnE,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAA;IAClD,qEAAqE;IACrE,IAAI,IAAI,UAAU,EAAE,CAAA;IACpB;6DACyD;IACzD,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACjC;kCAC8B;IAC9B,SAAS,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,GAAG,MAAM,IAAI,CAAA;CAC7D;AAgDD,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,iBAAiB,GAAG,UAAU,CA6OvE"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Reversible virtual ids for sessions and workspaces the sub-client surfaces
3
+ * locally while their real records live on a relay server (T23b-1). The UI
4
+ * only ever sees `zr~<serverId>~<original id>`; the interceptor maps those
5
+ * back to the original ids before a call travels, and maps server-returned
6
+ * ids back to virtual form on the way out.
7
+ *
8
+ * The mapping is deliberately a PURE function of (serverId, id), not a
9
+ * session-table lookup: every shared session of one server gets the same
10
+ * virtual id whether or not this process ever opened it, so result rewriting
11
+ * needs no state. Parsing validates the fixed skeleton (`zr~` + the 8 hex
12
+ * chars `loadServerId` mints, relay-server.ts) and keeps the remainder
13
+ * VERBATIM — the original id may contain any characters, including `~`, so
14
+ * nothing after the second separator is interpreted.
15
+ *
16
+ * A local DSH id can never collide with the prefix: real session ids are
17
+ * `session-<uuid>` and workspace ids are bare uuids.
18
+ */
19
+ /** The fixed prefix every virtual id starts with. */
20
+ export declare const VIRTUAL_ID_PREFIX = "zr~";
21
+ /** The two halves {@link fromVirtual} recovers from a virtual id. */
22
+ export interface VirtualIdParts {
23
+ serverId: string;
24
+ id: string;
25
+ }
26
+ /** Build the virtual form of one server-side session or workspace id. */
27
+ export declare function toVirtual(serverId: string, id: string): string;
28
+ /**
29
+ * Parse one virtual id back into its halves, or `undefined` for anything
30
+ * that does not match the skeleton. Strict on the parts that carry meaning:
31
+ * the literal prefix, exactly 8 lowercase hex server id chars, and the
32
+ * separating `~` after them. The remainder is not validated — any string,
33
+ * including the empty one, is a legal original id.
34
+ */
35
+ export declare function fromVirtual(id: unknown): VirtualIdParts | undefined;
36
+ /** True when {@link fromVirtual} would parse this value. */
37
+ export declare function isVirtual(id: unknown): boolean;
38
+ //# sourceMappingURL=virtual-id.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"virtual-id.d.ts","sourceRoot":"","sources":["../../src/virtual-id.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,qDAAqD;AACrD,eAAO,MAAM,iBAAiB,QAAQ,CAAA;AAMtC,qEAAqE;AACrE,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAA;IAChB,EAAE,EAAE,MAAM,CAAA;CACX;AAED,yEAAyE;AACzE,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAE9D;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,EAAE,EAAE,OAAO,GAAG,cAAc,GAAG,SAAS,CASnE;AAED,4DAA4D;AAC5D,wBAAgB,SAAS,CAAC,EAAE,EAAE,OAAO,GAAG,OAAO,CAE9C"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Reversible virtual ids for sessions and workspaces the sub-client surfaces
3
+ * locally while their real records live on a relay server (T23b-1). The UI
4
+ * only ever sees `zr~<serverId>~<original id>`; the interceptor maps those
5
+ * back to the original ids before a call travels, and maps server-returned
6
+ * ids back to virtual form on the way out.
7
+ *
8
+ * The mapping is deliberately a PURE function of (serverId, id), not a
9
+ * session-table lookup: every shared session of one server gets the same
10
+ * virtual id whether or not this process ever opened it, so result rewriting
11
+ * needs no state. Parsing validates the fixed skeleton (`zr~` + the 8 hex
12
+ * chars `loadServerId` mints, relay-server.ts) and keeps the remainder
13
+ * VERBATIM — the original id may contain any characters, including `~`, so
14
+ * nothing after the second separator is interpreted.
15
+ *
16
+ * A local DSH id can never collide with the prefix: real session ids are
17
+ * `session-<uuid>` and workspace ids are bare uuids.
18
+ */
19
+ /** The fixed prefix every virtual id starts with. */
20
+ export const VIRTUAL_ID_PREFIX = 'zr~';
21
+ /** The server id half: exactly the 8 lowercase hex chars relay-server.ts
22
+ * mints (`randomBytes(4).toString('hex')`). */
23
+ const SERVER_ID_PATTERN = /^[0-9a-f]{8}$/;
24
+ /** Build the virtual form of one server-side session or workspace id. */
25
+ export function toVirtual(serverId, id) {
26
+ return `${VIRTUAL_ID_PREFIX}${serverId}~${id}`;
27
+ }
28
+ /**
29
+ * Parse one virtual id back into its halves, or `undefined` for anything
30
+ * that does not match the skeleton. Strict on the parts that carry meaning:
31
+ * the literal prefix, exactly 8 lowercase hex server id chars, and the
32
+ * separating `~` after them. The remainder is not validated — any string,
33
+ * including the empty one, is a legal original id.
34
+ */
35
+ export function fromVirtual(id) {
36
+ if (typeof id !== 'string' || !id.startsWith(VIRTUAL_ID_PREFIX))
37
+ return undefined;
38
+ const rest = id.slice(VIRTUAL_ID_PREFIX.length);
39
+ // The shortest legal remainder is `<8 hex>~` — 9 chars with the separator
40
+ // at index 8. Anything shorter cannot carry a server id.
41
+ if (rest.length < 9 || rest[8] !== '~')
42
+ return undefined;
43
+ const serverId = rest.slice(0, 8);
44
+ if (!SERVER_ID_PATTERN.test(serverId))
45
+ return undefined;
46
+ return { serverId, id: rest.slice(9) };
47
+ }
48
+ /** True when {@link fromVirtual} would parse this value. */
49
+ export function isVirtual(id) {
50
+ return fromVirtual(id) !== undefined;
51
+ }
52
+ //# sourceMappingURL=virtual-id.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"virtual-id.js","sourceRoot":"","sources":["../src/virtual-id.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,qDAAqD;AACrD,MAAM,CAAC,MAAM,iBAAiB,GAAG,KAAK,CAAA;AAEtC;+CAC+C;AAC/C,MAAM,iBAAiB,GAAG,eAAe,CAAA;AAQzC,yEAAyE;AACzE,MAAM,UAAU,SAAS,CAAC,QAAgB,EAAE,EAAU;IACpD,OAAO,GAAG,iBAAiB,GAAG,QAAQ,IAAI,EAAE,EAAE,CAAA;AAChD,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,WAAW,CAAC,EAAW;IACrC,IAAI,OAAO,EAAE,KAAK,QAAQ,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,iBAAiB,CAAC;QAAE,OAAO,SAAS,CAAA;IACjF,MAAM,IAAI,GAAG,EAAE,CAAC,KAAK,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAA;IAC/C,0EAA0E;IAC1E,yDAAyD;IACzD,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,GAAG;QAAE,OAAO,SAAS,CAAA;IACxD,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;IACjC,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,QAAQ,CAAC;QAAE,OAAO,SAAS,CAAA;IACvD,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAA;AACxC,CAAC;AAED,4DAA4D;AAC5D,MAAM,UAAU,SAAS,CAAC,EAAW;IACnC,OAAO,WAAW,CAAC,EAAE,CAAC,KAAK,SAAS,CAAA;AACtC,CAAC"}
package/llms.txt CHANGED
@@ -1,20 +1,23 @@
1
1
  # dsh-zen-remote
2
2
 
3
- > Complete mobile plugin for DeepSeek Harness (DSH), shipped as ONE npm package: app-shell mobile UI (two-level page stack, session-list home, reworked composer, gestures, phone-local attachment upload) + pairing-code/device-token gateway for public-internet access behind your own reverse proxy + installable PWA + offline service worker + real Web Push agent-done notifications.
3
+ > One DeepSeek Harness plugin, two roles, shipped as ONE npm package. The **host** role: app-shell mobile UI (two-level page stack, session-list home, reworked composer, gestures, phone-local attachment upload), pairing-code/device-token gateway for public-internet access behind your own reverse proxy, installable PWA, real Web Push, and per-session sharing to paired desktops. The **client** role: runs inside another DSH desktop app and surfaces the host's remote-enabled sessions in its own sidebar — history, live progress, messages, approvals, terminals; everything executes on the host over zen-remote's relay protocol.
4
4
 
5
5
  ## Key facts
6
- - One npm package `dsh-zen-remote`, MIT. Install: `dsh plugin add dsh-zen-remote`, then restart `dsh web`.
7
- - Declares `dsh.bundle.patch` → `cordis.patch.yml` with three insert rows, all self-contained (no manual profile patching needed):
8
- `dsh-zen-remote` (host upload route + browser client via `dsh.client`), `dsh-zen-remote-gateway` (`lan-gate.mjs`), `dsh-zen-remote-push` (`dsh-push.mjs`).
9
- - Interface half: browser client bundle (`lib/client.js`, registered as `__ModuleLoader__.load({id:"dsh-zen-remote"})`); <768px app shell, 768–1023px drawer, ≥1024px strict no-op. Host route `POST /_dsh/mobile-nav/upload` writes phone-picked files into the session workspace's `.dsh-uploads/`.
10
- - Gateway half: standalone child-process Node gateway (`lib/lan-gate-server.cjs`, Node stdlib + one runtime dep `web-push`), listens on `127.0.0.1:3088`, sits behind a user-run TLS-terminating reverse proxy (nginx/Caddy/Lucky). Device identity = pairing token (cookie `lg_device`), not source IP. Reverse-proxies paired devices to DSH on `127.0.0.1:3080`; DSH webserver untouched. HTML injection adds the PWA manifest link, service-worker bootstrap, `viewport-fit=cover`, shell CSS, randomUUID polyfill.
11
- - Push half: two legs. Event leg (always on, debounce-exempt) fires when a tool is waiting on approval (`approval/asked` with no `approval/decided` within 1.5s) or `ask_user_question` is unanswered; turn end (`agent/turn-stopping`, configurable) is OPT-IN via `DSH_PUSH_TURN_END` and top-level-only. Also registers the model-callable `push_notify` tool. All deliver through the gateway's local `/pwa/push/send` (VAPID + aes128gcm).
12
- - Env: `LAN_GATE_PORT` (3088), `LAN_GATE_HOST` (127.0.0.1), `LAN_GATE_TARGET_PORT` (3080), `LAN_GATE_RATE_LIMIT` (120), `LAN_GATE_TRUSTED_PROXIES`, `LAN_GATE_VAPID_SUBJECT`, `DSH_PUSH_EVENTS`, `DSH_PUSH_DEBOUNCE_MS`, `DSH_PUSH_SUMMARY`, `DSH_PUSH_TOOL`. Same keys (lowerCamelCase) in `~/.dsh/lan-gate.config.json`.
13
- - Admin (local-direct-connection only, 403 otherwise): `http://127.0.0.1:3088/lan-gate/admin` — generate pairing codes, rename/set-kind/revoke devices, trigger a push.
14
- - Security: 8-char one-time pairing code (10 min TTL), 5-wrong-code lockout (15 min), revocable per-device tokens, 120 req/min per resolved client IP, admin surface rejects any request carrying `X-Forwarded-*`.
6
+ - One npm package `dsh-zen-remote`, MIT. Install: DSH Plugins page → Add plugin → `dsh-zen-remote` → Enable now (desktop app and web UI), or `dsh plugin --profile web add dsh-zen-remote` on a headless web service (the CLI requires `--profile` and refuses `--profile desktop`); by hand: one dependency line in `~/.dsh/profiles/desktop|web/package.json` + `pnpm install` + `dsh-zen-remote` appended to that file's `dsh.profile.bundles`. Then restart DSH. The composition layer is a single row `dsh-zen-remote`; the main entry loads the gateway and push sub-plugins itself according to the `role` config. Old `dsh-zen-remote-gateway` / `dsh-zen-remote-push` rows are warned and skipped — do not re-add them (a second gateway would fight over port 3088).
7
+ - Roles: `role: host` (default) runs mobile UI + gateway child + push + session sharing + the relay server; `role: client` runs the relay client and the sidebar/session-page remote parts only — no gateway, no push, no phone UI. Anything that is not exactly `client` degrades to host.
8
+ - Config lives in the DSH Plugins page settings block; per field the first legal value wins: environment variable > plugin row settings > `~/.dsh/lan-gate.config.json` > default. Restart-required fields (`role`, `port`, `host`, `targetPort`, `rateLimit`, `trustedProxies`, `vapidSubject`, `lang`, push fields) reload the plugin row automatically; everything else is volatile and read live.
9
+ - Host sharing: per-session remote toggle from four places (sidebar session "…" menu, title-row icon with confirm, settings list, the phone session info card's remote access switch); idle sleep after 48h without session activity (running/waiting sessions exempt, viewers don't count); auto-share new sessions (opt-in; relay-created sessions always auto-share); subagent/fork sessions follow the parent.
10
+ - Pairing codes are role-bound: web app devices (phones/browsers, see all sessions) redeem on the pairing page; desktop app devices pair from the client's settings page via `/lan-gate/pair/claim-desktop` and only ever reach the relay prefix (everything else 403 `relay-only`).
11
+ - Relay protocol (prefix `/_dsh/zen-remote/relay`): `GET ping`, `POST v1/handshake` (relay protocol version + DSH interface fingerprints — version mismatch refuses; fingerprint mismatch annotates and degrades; the answer carries `serverName`/`deviceName`), `POST v1/invoke`, `POST v1/stream` (NDJSON, ping heartbeats that carry the current `serverName`/`deviceName`, `unshared` error line on close), `POST v1/event-result`, `POST v1/http` (the plain-HTTP long tail: only `changes.summary` / `changes.diff`, GET only, query normalized to `sessionId`/`seq`/`index`, dispatched in-process through the host's shared `/api` handler), `POST v1/upload` (a remote session's non-image attachments: raw byte stream, share-checked, query normalized to `sessionId`/`name`, 100 MiB cap answers 413, 8 concurrent per device answers 429, dispatched in-process through the same shared handler), `POST v1/unshare`, `POST v1/device/name` (gateway-handled rename of the CALLING device's own record — paired desktop clients only, never forwarded to DSH; 1–40 chars). Access control is a per-method allowlist (`src/relay-access.ts` RELAY_METHODS, mirrored by intercept.ts CLIENT_METHOD_FIELDS, pinned identical by test); global reads (`session/control`, `session/list`, `workspace/follow`) and the @-mention candidates are output-filtered (candidates list only shared sessions); `session/modelCatalog` is a parameter-less global read whose `failures` array the server empties; `dsh-session:` references are scanned wherever DSH would inject them — prompt content, queue-edit content, every string of a `commands/execute` call — and a reference naming an unshared session refuses the call. Approval/question events forward first come, first served; the relay never answers on the client's behalf (an unshared session's approval waits for the server UI or the next subscription), and unshared sessions' notification-type events are never forwarded.
12
+ - Remote extras: a remote session can attach non-image files — the sub-client's own backend wraps the host's `fetchRoutes` upload entry and streams the bytes through `v1/upload` (the 100 MiB cap is the relay's own, an over-limit pick is refused locally on the client, slow links are bounded by a 300 s client-side timeout; export is disabled remotely instead — menu item hidden, backend 403 for virtual ids). The model picker in a remote session lists the SERVER's catalog (groups named "server name · group name"); local sessions list only local models, the last server groups survive relay offline, and virtual groups never appear in local settings such as the vision router (boundary: a server-created session that never sent a message shows the local default model).
13
+ - Gateway auth chain: device token (Bearer) → gateway marker headers `x-zen-remote-via/-role/-device` + `x-zen-remote-secret` (per-load shared secret, `LAN_GATE_RELAY_SECRET`, not externally settable, re-verified with timingSafeEqual) → per-method share-table check. Admin API stays loopback-only; forwarded requests may read status but never mutate.
14
+ - Admin surface: the dsh-zen-remote settings block (pairing with role picker, device list with rename/set-role/revoke, push test, shares list). `/lan-gate/admin` is now just a notice pointing there.
15
+ - Trust model: a paired desktop client is a TRUSTED DEVICE — server-user terminal without sandbox/approval gates, file previews not contained to the session dir, session creation in any workspace. "Shared only" constrains session data (lists, history, events are refused/filtered server-side; @ candidates list only shared sessions; references in messages, queue edits and slash commands naming an unshared session refuse the call), not the machine.
16
+ - Env: `LAN_GATE_PORT` (3088), `LAN_GATE_HOST`, `LAN_GATE_TARGET_PORT` (auto-discovered), `LAN_GATE_RATE_LIMIT` (120), `LAN_GATE_TRUSTED_PROXIES`, `LAN_GATE_VAPID_SUBJECT`, `LAN_GATE_LANG`, `LAN_GATE_RELAY_SECRET` (auto), `DSH_PUSH_*`, `DSH_PUSH_APPROVAL_GRACE_MS`. Same keys (lowerCamelCase) in `lan-gate.config.json`.
17
+ - Security: 8-char one-time role-bound pairing code (10 min TTL), 5-wrong-code lockout, revocable per-device tokens (kills connections + push subscription), 120 req/min per client IP for unpaired requests.
18
+ - Known limits: remote sessions disable export (menu item hidden, backend 403 for virtual ids); changes summary/diff work remotely but the "open on the server machine" entries (open / reveal in Finder / open in app) are hidden there; with dsh-better-sidebar installed, its file surfaces (file browser, editor, git, tasks, bottom workbench, floating windows) are hidden in remote sessions because they act on the CLIENT machine's own files — so remote sessions have no sidebar file browser (it takes over the host's "Files" tab) and file links in chat or deliverables open an empty right column; @ candidates are truncated to 50 by the host BEFORE share filtering (a shared session can be missing from the list on a session-heavy server); third-party plugins' non-standard endpoints are not forwarded — their tools run on the server and are unaffected, what breaks is UI calling the plugin's own backend with a session id (better-sidebar, handled as above); both ends need zen-remote 2.0.x; server switch-back or workspace-id reuse needs a page reload; several DSH windows at once (including the desktop backend opened in a browser) can misjudge which session is currently open — closed-remote hiding can fire at the wrong moment and worst case dismiss an open session to the home page. Dev only (see AGENTS.md hard constraints): same-machine testing must reach the gateway over a LAN IP (loopback = local-direct, skips token checks) — normal two-machine use is unaffected.
15
19
 
16
20
  ## Docs
17
- - README.md (English, install + config), README.zh-CN.md (same in Chinese), AGENTS.md (repo guide), LICENSE.
18
- - docs/remote-access.md(.en.md) — gateway detail: reverse proxy, pairing, env, admin API, push, security boundary.
19
- - docs/interface.md — interface detail: breakpoints, debug badge, safe-area system, compatible plugins.
20
- - .ai/ — AI Project OS state, memory, decisions, tasks.
21
+ - README.md (English, install + config + migration + security model), README.zh-CN.md (same in Chinese), AGENTS.md (repo guide), LICENSE.
22
+ - docs/remote-access.md(.en.md) — gateway detail: reverse proxy, roles and pairing, env, admin API, relay protocol, security boundary.
23
+ - docs/interface.md — interface detail: breakpoints, settings block and remote parts, debug badge, safe-area system, compatible plugins.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-zen-remote",
3
- "version": "1.1.15",
4
- "description": "把 DeepSeek Harness 变成手机 App:移动端界面重排(两级页面栈、会话列表主屏、composer 重排、手势、附件上传)+ 配对码认证网关 + PWA 安装 + 真 Web Push。Complete mobile plugin for DeepSeek Harness (DSH): app-shell mobile UI, pairing-code gateway for public-internet access behind your own reverse proxy, installable PWA, offline service worker, and real Web Push agent-done notifications.",
3
+ "version": "2.0.0",
4
+ "description": "一个插件、两种角色:主服务端把 DeepSeek Harness 变成手机能安全访问的 PWA(移动端界面、配对码网关、Web Push),并把选定会话共享给已配对的桌面端;子客户端在另一台 DSH 桌面端的侧边栏里远程使用这些会话。One DSH plugin, two roles: the host turns DeepSeek Harness into a phone-reachable PWA (mobile UI, pairing-code gateway behind your own reverse proxy, Web Push) and shares chosen sessions to paired desktops; the client uses those sessions remotely from another DSH desktop app's sidebar.",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "main": "lib/index.js",
@@ -36,7 +36,10 @@
36
36
  "README.md",
37
37
  "README.zh-CN.md",
38
38
  "LICENSE",
39
- "!docs/push-policy.md"
39
+ "!docs/push-policy.md",
40
+ "!docs/SPEC.md",
41
+ "!docs/spike-relay.md",
42
+ "!docs/images"
40
43
  ],
41
44
  "dsh": {
42
45
  "bundle": {
@@ -78,20 +81,21 @@
78
81
  "web-push": "^3.6.7"
79
82
  },
80
83
  "peerDependencies": {
81
- "@deepseek-ai/cordis": "^4.0.1",
82
- "@deepseek-ai/dsh-client-locale": ">=0.1.0-rc.7 <0.2.0-0 || >=0.1.1-rc.0 <0.2.0-0 || >=0.1.2-rc.0 <0.2.0-0",
83
- "@deepseek-ai/dsh-client-ui-conversation": ">=0.1.0-rc.7 <0.2.0-0 || >=0.1.1-rc.0 <0.2.0-0 || >=0.1.2-rc.0 <0.2.0-0",
84
- "@deepseek-ai/dsh-client-ui-layout": ">=0.1.0-rc.7 <0.2.0-0 || >=0.1.1-rc.0 <0.2.0-0 || >=0.1.2-rc.0 <0.2.0-0",
85
- "@deepseek-ai/dsh-client-ui-primitives": ">=0.1.0-rc.7 <0.2.0-0 || >=0.1.1-rc.0 <0.2.0-0 || >=0.1.2-rc.0 <0.2.0-0",
86
- "@deepseek-ai/dsh-client-ui-sidebar": ">=0.1.0-rc.7 <0.2.0-0 || >=0.1.1-rc.0 <0.2.0-0 || >=0.1.2-rc.0 <0.2.0-0",
87
- "@deepseek-ai/dsh-client-ui-slots": ">=0.1.0-rc.7 <0.2.0-0 || >=0.1.1-rc.0 <0.2.0-0 || >=0.1.2-rc.0 <0.2.0-0",
88
- "@deepseek-ai/dsh-host-webserver": ">=0.1.0-rc.7 <0.2.0-0 || >=0.1.1-rc.0 <0.2.0-0 || >=0.1.2-rc.0 <0.2.0-0",
89
- "@deepseek-ai/dsh-session": ">=0.1.0-rc.7 <0.2.0-0 || >=0.1.1-rc.0 <0.2.0-0 || >=0.1.2-rc.0 <0.2.0-0",
90
- "@deepseek-ai/dsh-session-log-export": ">=0.1.0-rc.7 <0.2.0-0 || >=0.1.1-rc.0 <0.2.0-0 || >=0.1.2-rc.0 <0.2.0-0",
91
- "@deepseek-ai/dsh-session-query": ">=0.1.0-rc.7 <0.2.0-0 || >=0.1.1-rc.0 <0.2.0-0 || >=0.1.2-rc.0 <0.2.0-0",
92
- "react": "^18.2.0",
93
- "@deepseek-ai/dsh-tools": ">=0.0.1-rc.1 <0.1.0 || >=0.1.0-rc.1 <0.2.0-0 || >=0.1.2-rc.0 <0.2.0-0",
94
- "@deepseek-ai/dsh-workspace": ">=0.1.1-rc.0 <0.2.0-0 || >=0.1.2-rc.0 <0.2.0-0"
84
+ "@deepseek-ai/cordis": "*",
85
+ "@deepseek-ai/dsh-client-locale": "^0.1.7-rc.2 || ^0.2.0-rc.1",
86
+ "@deepseek-ai/dsh-client-ui-conversation": "^0.1.7-rc.2 || ^0.2.0-rc.1",
87
+ "@deepseek-ai/dsh-client-ui-layout": "^0.1.7-rc.2 || ^0.2.0-rc.1",
88
+ "@deepseek-ai/dsh-client-ui-primitives": "^0.1.7-rc.2 || ^0.2.0-rc.1",
89
+ "@deepseek-ai/dsh-client-ui-sidebar": "^0.1.7-rc.2 || ^0.2.0-rc.1",
90
+ "@deepseek-ai/dsh-client-ui-slots": "^0.1.7-rc.2 || ^0.2.0-rc.1",
91
+ "@deepseek-ai/dsh-host-webserver": "^0.1.7-rc.2 || ^0.2.0-rc.1",
92
+ "@deepseek-ai/dsh-session": "^0.1.7-rc.2 || ^0.2.0-rc.1",
93
+ "@deepseek-ai/dsh-session-log-export": "^0.1.7-rc.2 || ^0.2.0-rc.1",
94
+ "@deepseek-ai/dsh-session-query": "^0.1.7-rc.2 || ^0.2.0-rc.1",
95
+ "@deepseek-ai/dsh-tools": "^0.1.7-rc.2 || ^0.2.0-rc.1",
96
+ "@deepseek-ai/dsh-workspace": "^0.1.7-rc.2 || ^0.2.0-rc.1",
97
+ "@deepseek-ai/schemastery": "*",
98
+ "react": "^18.2.0"
95
99
  },
96
100
  "peerDependenciesMeta": {
97
101
  "@deepseek-ai/dsh-host-webserver": {
@@ -111,38 +115,44 @@
111
115
  }
112
116
  },
113
117
  "devDependencies": {
114
- "@deepseek-ai/cordis": "4.0.2",
115
- "@deepseek-ai/cosmokit": "^1.8.3",
116
- "@deepseek-ai/dsh-api-remotes": "0.1.2-rc.1",
117
- "@deepseek-ai/dsh-api-session-controller": "0.1.2-rc.1",
118
- "@deepseek-ai/dsh-api-workspace-controller": "0.1.2-rc.1",
119
- "@deepseek-ai/dsh-brand": "0.1.2-rc.1",
120
- "@deepseek-ai/dsh-client-connection": "0.1.2-rc.1",
121
- "@deepseek-ai/dsh-client-locale": "0.1.2-rc.1",
122
- "@deepseek-ai/dsh-client-store": "0.1.2-rc.1",
123
- "@deepseek-ai/dsh-client-ui-conversation": "0.1.2-rc.1",
124
- "@deepseek-ai/dsh-client-ui-layout": "0.1.2-rc.1",
125
- "@deepseek-ai/dsh-client-ui-primitives": "0.1.2-rc.1",
126
- "@deepseek-ai/dsh-client-ui-renderer": "0.1.2-rc.1",
127
- "@deepseek-ai/dsh-client-ui-session": "0.1.2-rc.1",
128
- "@deepseek-ai/dsh-client-ui-sidebar": "0.1.2-rc.1",
129
- "@deepseek-ai/dsh-client-ui-slots": "0.1.2-rc.1",
130
- "@deepseek-ai/dsh-host-webserver": "0.1.2-rc.1",
131
- "@deepseek-ai/dsh-llm": "0.1.2-rc.1",
132
- "@deepseek-ai/dsh-scope": "0.1.2-rc.1",
133
- "@deepseek-ai/dsh-session": "0.1.2-rc.1",
134
- "@deepseek-ai/dsh-session-log-export": "0.1.2-rc.1",
135
- "@deepseek-ai/dsh-session-query": "0.1.2-rc.1",
136
- "@deepseek-ai/dsh-session-stats": "0.1.2-rc.1",
137
- "@deepseek-ai/dsh-subagent": "0.1.2-rc.1",
138
- "@deepseek-ai/dsh-timeout": "0.1.2-rc.1",
139
- "@deepseek-ai/dsh-token-meter": "0.1.2-rc.1",
140
- "@deepseek-ai/dsh-tools": "0.1.2-rc.1",
141
- "@deepseek-ai/dsh-typert-protocol": "0.1.2-rc.1",
142
- "@deepseek-ai/dsh-util-crypto": "0.1.2-rc.1",
143
- "@deepseek-ai/dsh-util-values": "0.1.2-rc.1",
144
- "@deepseek-ai/dsh-workspace": "0.1.2-rc.1",
145
- "@deepseek-ai/schemastery": "^3.18.1",
118
+ "@deepseek-ai/cordis": "4.0.4",
119
+ "@deepseek-ai/cosmokit": "1.8.5",
120
+ "@deepseek-ai/dsh-agent": "0.1.7-rc.2",
121
+ "@deepseek-ai/dsh-api-remotes": "0.1.7-rc.2",
122
+ "@deepseek-ai/dsh-api-session-controller": "0.1.7-rc.2",
123
+ "@deepseek-ai/dsh-api-workspace-controller": "0.1.7-rc.2",
124
+ "@deepseek-ai/dsh-brand": "0.1.7-rc.2",
125
+ "@deepseek-ai/dsh-client-connection": "0.1.7-rc.2",
126
+ "@deepseek-ai/dsh-client-locale": "0.1.7-rc.2",
127
+ "@deepseek-ai/dsh-client-store": "0.1.7-rc.2",
128
+ "@deepseek-ai/dsh-client-ui-conversation": "0.1.7-rc.2",
129
+ "@deepseek-ai/dsh-client-ui-layout": "0.1.7-rc.2",
130
+ "@deepseek-ai/dsh-client-ui-primitives": "0.1.7-rc.2",
131
+ "@deepseek-ai/dsh-client-ui-renderer": "0.1.7-rc.2",
132
+ "@deepseek-ai/dsh-client-ui-session": "0.1.7-rc.2",
133
+ "@deepseek-ai/dsh-client-ui-sidebar": "0.1.7-rc.2",
134
+ "@deepseek-ai/dsh-client-ui-slots": "0.1.7-rc.2",
135
+ "@deepseek-ai/dsh-host-webserver": "0.1.7-rc.2",
136
+ "@deepseek-ai/dsh-invariants": "0.1.7-rc.2",
137
+ "@deepseek-ai/dsh-llm": "0.1.7-rc.2",
138
+ "@deepseek-ai/dsh-ptc-runtime": "0.1.7-rc.2",
139
+ "@deepseek-ai/dsh-sandbox": "0.1.7-rc.2",
140
+ "@deepseek-ai/dsh-sandbox-policy": "0.1.7-rc.2",
141
+ "@deepseek-ai/dsh-scope": "0.1.7-rc.2",
142
+ "@deepseek-ai/dsh-session": "0.1.7-rc.2",
143
+ "@deepseek-ai/dsh-session-log-export": "0.1.7-rc.2",
144
+ "@deepseek-ai/dsh-session-projection": "0.1.7-rc.2",
145
+ "@deepseek-ai/dsh-session-query": "0.1.7-rc.2",
146
+ "@deepseek-ai/dsh-session-stats": "0.1.7-rc.2",
147
+ "@deepseek-ai/dsh-subagent": "0.1.7-rc.2",
148
+ "@deepseek-ai/dsh-timeout": "0.1.7-rc.2",
149
+ "@deepseek-ai/dsh-token-meter": "0.1.7-rc.2",
150
+ "@deepseek-ai/dsh-tools": "0.1.7-rc.2",
151
+ "@deepseek-ai/dsh-typert-protocol": "0.1.7-rc.2",
152
+ "@deepseek-ai/dsh-util-crypto": "0.1.7-rc.2",
153
+ "@deepseek-ai/dsh-util-values": "0.1.7-rc.2",
154
+ "@deepseek-ai/dsh-workspace": "0.1.7-rc.2",
155
+ "@deepseek-ai/schemastery": "3.18.4",
146
156
  "@types/node": "^26.2.0",
147
157
  "@types/react": "~18.3.1",
148
158
  "@types/react-dom": "~18.3.0",
@@ -151,7 +161,7 @@
151
161
  "scripts": {
152
162
  "build": "tsc -p tsconfig.json && tsc -p tsconfig.client.json && node scripts/build-client.mjs",
153
163
  "verify": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.client.json --noEmit",
154
- "test": "node --test \"test/*.test.cjs\" && node scripts/check-sunk-viewport.mjs && node scripts/check-keyboard-avoid.mjs && node scripts/check-attach-upload.mjs && node scripts/check-share-image.mjs && node scripts/check-upload-endpoint.mjs && node scripts/check-host-shadow.mjs && node scripts/check-compat-store.mjs && node scripts/check-client-externals.mjs && node scripts/check-info-sheet-stacking.mjs && node scripts/sync-doc-version.mjs --check",
164
+ "test": "node --test \"test/*.test.cjs\" && node scripts/check-sunk-viewport.mjs && node scripts/check-keyboard-avoid.mjs && node scripts/check-attach-upload.mjs && node scripts/check-share-image.mjs && node scripts/check-upload-endpoint.mjs && node scripts/check-host-shadow.mjs && node scripts/check-compat-store.mjs && node scripts/check-settings-form.mjs && node scripts/check-shares.mjs && node scripts/check-client-externals.mjs && node scripts/check-info-sheet-stacking.mjs && node scripts/sync-doc-version.mjs --check",
155
165
  "version": "node scripts/sync-doc-version.mjs && git add README.md README.zh-CN.md"
156
166
  }
157
167
  }
@@ -49,7 +49,9 @@ const resolveChild = (parent, rel) => {
49
49
  return joined === '.' ? '' : joined
50
50
  }
51
51
 
52
- // Dependency-first topological order from the entry.
52
+ // Dependency-first topological order from the entry. The client tsconfig's
53
+ // rootDir is `src` (so src/client-data can sit beside src/client), which puts
54
+ // the emit under .client-build/client/.
53
55
  const visited = new Set()
54
56
  const order = []
55
57
  const visit = (file) => {
@@ -62,7 +64,7 @@ const visit = (file) => {
62
64
  }
63
65
  order.push(file)
64
66
  }
65
- visit('index.js')
67
+ visit('client/index.js')
66
68
 
67
69
  const modules = order
68
70
  .map((file) => {
@@ -89,7 +91,7 @@ const wrapped = [
89
91
  ' return module.exports;',
90
92
  '}',
91
93
  'var module = { exports: {} };',
92
- '__modules["index.js"](__localRequire, module, module.exports);',
94
+ '__modules["client/index.js"](__localRequire, module, module.exports);',
93
95
  'return module.exports; } });',
94
96
  '',
95
97
  ].join('\n')
@@ -78,6 +78,19 @@ const require = createRequire(import.meta.url)
78
78
  // devDep then.
79
79
  // - npm-published tarballs contain no node_modules, so this shadow only
80
80
  // exists on dev machines where the plugin is link-installed.
81
+ // NOTE (T42-fix): src/fingerprint.ts resolves @deepseek-ai packages at
82
+ // runtime — deliberately through the HOST process entry FIRST
83
+ // (process.argv[1]), with the plugin location as a fallback — so a
84
+ // link-installed plugin no longer fingerprints its own devDependency copies.
85
+ // What argv[1] actually is, per launch shape (T23b2-fix3): under the npm
86
+ // CLI it is a SYMLINK (…/bin/dsh into the real install), and fingerprint.ts
87
+ // realpaths it before anchoring — walking node_modules upward from the bin
88
+ // directory would find nothing and silently fall through to this plugin's
89
+ // copies. Under the desktop App it lives inside app.asar (not exercised in
90
+ // practice). Those resolutions are createRequire(...).resolve()
91
+ // calls whose specifier this file's scan regex cannot see, and the runtime
92
+ // priority makes a plugin-copy shadow the fallback, not the read — so no
93
+ // SHADOW_ALLOWED entry is registered for them.
81
94
  const SHADOW_ALLOWED = ['@deepseek-ai/dsh-tools', '@deepseek-ai/dsh-session']
82
95
 
83
96
  // Packages that must never exist in this plugin's node_modules at all: DSH
@@ -86,8 +99,12 @@ const SHADOW_ALLOWED = ['@deepseek-ai/dsh-tools', '@deepseek-ai/dsh-session']
86
99
  const BANNED_PACKAGES = ['@deepseek-ai/dsh-client-runtime', '@deepseek-ai/dsh-host-apiproxy']
87
100
 
88
101
  // The files the host half actually loads. lib/share-export.js is statically
89
- // imported by lib/index.js, so it is part of the same runtime graph.
90
- const HOST_ENTRY_FILES = ['dsh-push.mjs', 'lan-gate.mjs', 'lib/index.js', 'lib/share-export.js']
102
+ // imported by lib/index.js; lib/fingerprint.js rides the same runtime graph
103
+ // (T42). It is listed here because the scan's whole purpose is watching
104
+ // THIS graph for @deepseek-ai imports: its own package resolutions are
105
+ // createRequire(...).resolve() calls the regex cannot see, but any literal
106
+ // import someone adds there later WILL be caught.
107
+ const HOST_ENTRY_FILES = ['dsh-push.mjs', 'lan-gate.mjs', 'lib/index.js', 'lib/share-export.js', 'lib/fingerprint.js']
91
108
 
92
109
  // Matches the specifier of real module statements only — `... from 'pkg'`,
93
110
  // `import 'pkg'`, `import('pkg')`, `require('pkg')` — so @deepseek-ai names