dsh-zen-remote 1.1.16 → 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 (255) 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 +10051 -4777
  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 +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/permission-sheet.d.ts +39 -0
  92. package/lib/types/client/effects/permission-sheet.d.ts.map +1 -0
  93. package/lib/types/client/effects/remote-session.d.ts +25 -0
  94. package/lib/types/client/effects/remote-session.d.ts.map +1 -0
  95. package/lib/types/client/effects/turn-fold.d.ts +9 -0
  96. package/lib/types/client/effects/turn-fold.d.ts.map +1 -1
  97. package/lib/types/client/index.d.ts.map +1 -1
  98. package/lib/types/client/locales.d.ts +187 -2
  99. package/lib/types/client/locales.d.ts.map +1 -1
  100. package/lib/types/client/remote-fetch.d.ts +51 -0
  101. package/lib/types/client/remote-fetch.d.ts.map +1 -0
  102. package/lib/types/client/remote-group-expansion.d.ts +41 -0
  103. package/lib/types/client/remote-group-expansion.d.ts.map +1 -0
  104. package/lib/types/client/remote-share-css.d.ts +19 -0
  105. package/lib/types/client/remote-share-css.d.ts.map +1 -0
  106. package/lib/types/client/remote-share-register.d.ts +113 -0
  107. package/lib/types/client/remote-share-register.d.ts.map +1 -0
  108. package/lib/types/client/remote-status-css.d.ts +15 -0
  109. package/lib/types/client/remote-status-css.d.ts.map +1 -0
  110. package/lib/types/client/remote-status-register.d.ts +46 -0
  111. package/lib/types/client/remote-status-register.d.ts.map +1 -0
  112. package/lib/types/client/settings/SettingsSection.d.ts +46 -0
  113. package/lib/types/client/settings/SettingsSection.d.ts.map +1 -0
  114. package/lib/types/client/settings/register-settings.d.ts +58 -0
  115. package/lib/types/client/settings/register-settings.d.ts.map +1 -0
  116. package/lib/types/client/settings/settings-css.d.ts +10 -0
  117. package/lib/types/client/settings/settings-css.d.ts.map +1 -0
  118. package/lib/types/client/share/share-preview.d.ts.map +1 -1
  119. package/lib/types/client/styles/composer.css.d.ts +1 -1
  120. package/lib/types/client/styles/composer.css.d.ts.map +1 -1
  121. package/lib/types/client/styles/header.css.d.ts +1 -1
  122. package/lib/types/client/styles/header.css.d.ts.map +1 -1
  123. package/lib/types/client/styles/info.css.d.ts +1 -1
  124. package/lib/types/client/styles/info.css.d.ts.map +1 -1
  125. package/lib/types/client/styles/remote-session.css.d.ts +2 -0
  126. package/lib/types/client/styles/remote-session.css.d.ts.map +1 -0
  127. package/lib/types/client-data/current-session.d.ts +41 -0
  128. package/lib/types/client-data/current-session.d.ts.map +1 -0
  129. package/lib/types/client-data/remote-group-expansion.d.ts +76 -0
  130. package/lib/types/client-data/remote-group-expansion.d.ts.map +1 -0
  131. package/lib/types/client-data/remote-status.d.ts +148 -0
  132. package/lib/types/client-data/remote-status.d.ts.map +1 -0
  133. package/lib/types/client-data/settings-form.d.ts +774 -0
  134. package/lib/types/client-data/settings-form.d.ts.map +1 -0
  135. package/lib/types/client-data/shares.d.ts +211 -0
  136. package/lib/types/client-data/shares.d.ts.map +1 -0
  137. package/lib/types/client-pairing.d.ts +114 -0
  138. package/lib/types/client-pairing.d.ts.map +1 -0
  139. package/lib/types/client-routes.d.ts +112 -0
  140. package/lib/types/client-routes.d.ts.map +1 -0
  141. package/lib/types/config.d.ts +200 -0
  142. package/lib/types/config.d.ts.map +1 -0
  143. package/lib/types/fetch-route-intercept.d.ts +156 -0
  144. package/lib/types/fetch-route-intercept.d.ts.map +1 -0
  145. package/lib/types/fingerprint.d.ts +164 -0
  146. package/lib/types/fingerprint.d.ts.map +1 -0
  147. package/lib/types/http.d.ts +41 -0
  148. package/lib/types/http.d.ts.map +1 -0
  149. package/lib/types/index.d.ts +56 -24
  150. package/lib/types/index.d.ts.map +1 -1
  151. package/lib/types/intercept-shape.d.ts +32 -0
  152. package/lib/types/intercept-shape.d.ts.map +1 -0
  153. package/lib/types/intercept.d.ts +432 -0
  154. package/lib/types/intercept.d.ts.map +1 -0
  155. package/lib/types/merge-streams.d.ts +393 -0
  156. package/lib/types/merge-streams.d.ts.map +1 -0
  157. package/lib/types/relay-access.d.ts +310 -0
  158. package/lib/types/relay-access.d.ts.map +1 -0
  159. package/lib/types/relay-client.d.ts +274 -0
  160. package/lib/types/relay-client.d.ts.map +1 -0
  161. package/lib/types/relay-filter.d.ts +103 -0
  162. package/lib/types/relay-filter.d.ts.map +1 -0
  163. package/lib/types/relay-server.d.ts +208 -0
  164. package/lib/types/relay-server.d.ts.map +1 -0
  165. package/lib/types/restart-fields.d.ts +22 -0
  166. package/lib/types/restart-fields.d.ts.map +1 -0
  167. package/lib/types/restart-watcher.d.ts +83 -0
  168. package/lib/types/restart-watcher.d.ts.map +1 -0
  169. package/lib/types/session-reference.d.ts +66 -0
  170. package/lib/types/session-reference.d.ts.map +1 -0
  171. package/lib/types/share-export.d.ts +14 -1
  172. package/lib/types/share-export.d.ts.map +1 -1
  173. package/lib/types/share-ops.d.ts +79 -0
  174. package/lib/types/share-ops.d.ts.map +1 -0
  175. package/lib/types/share-store.d.ts +87 -0
  176. package/lib/types/share-store.d.ts.map +1 -0
  177. package/lib/types/virtual-id.d.ts +38 -0
  178. package/lib/types/virtual-id.d.ts.map +1 -0
  179. package/lib/virtual-id.js +52 -0
  180. package/lib/virtual-id.js.map +1 -0
  181. package/llms.txt +17 -14
  182. package/package.json +60 -50
  183. package/scripts/build-client.mjs +5 -3
  184. package/scripts/check-host-shadow.mjs +19 -2
  185. package/scripts/check-settings-form.mjs +933 -0
  186. package/scripts/check-shares.mjs +397 -0
  187. package/scripts/check-upload-endpoint.mjs +59 -3
  188. package/scripts/sync-doc-version.mjs +8 -15
  189. package/scripts/verify-fingerprint-live.mjs +111 -0
  190. package/src/activity.ts +218 -0
  191. package/src/admin-routes.ts +627 -0
  192. package/src/client/MobileAttachButton.tsx +2 -2
  193. package/src/client/MobileAttachChips.tsx +3 -3
  194. package/src/client/MobileDrawerFooter.tsx +7 -4
  195. package/src/client/MobileHome.tsx +20 -16
  196. package/src/client/MobileHomeChips.tsx +16 -16
  197. package/src/client/MobileNavOverlay.tsx +2 -2
  198. package/src/client/MobileNavToggle.tsx +3 -3
  199. package/src/client/MobileSessionHeader.tsx +63 -42
  200. package/src/client/MobileSessionInfo.tsx +118 -17
  201. package/src/client/RemoteComposerBanner.tsx +76 -0
  202. package/src/client/RemoteHeaderIcon.tsx +103 -0
  203. package/src/client/RemoteShareMenu.tsx +57 -0
  204. package/src/client/RemoteStatusIcon.tsx +91 -0
  205. package/src/client/compat/desktop.ts +22 -0
  206. package/src/client/compat/types.ts +62 -0
  207. package/src/client/current-session-report.ts +164 -0
  208. package/src/client/effects/header-status.ts +21 -17
  209. package/src/client/effects/keyboard-guard.ts +65 -1
  210. package/src/client/effects/permission-sheet.ts +149 -0
  211. package/src/client/effects/remote-session.ts +68 -0
  212. package/src/client/effects/turn-fold.ts +52 -0
  213. package/src/client/index.tsx +151 -36
  214. package/src/client/locales.ts +396 -2
  215. package/src/client/remote-fetch.ts +125 -0
  216. package/src/client/remote-group-expansion.ts +190 -0
  217. package/src/client/remote-share-css.ts +54 -0
  218. package/src/client/remote-share-register.ts +244 -0
  219. package/src/client/remote-status-css.ts +64 -0
  220. package/src/client/remote-status-register.ts +107 -0
  221. package/src/client/settings/SettingsSection.tsx +1229 -0
  222. package/src/client/settings/register-settings.ts +116 -0
  223. package/src/client/settings/settings-css.ts +236 -0
  224. package/src/client/share/share-preview.tsx +6 -3
  225. package/src/client/share/share-sheet.tsx +3 -3
  226. package/src/client/styles/composer.css.ts +119 -26
  227. package/src/client/styles/header.css.ts +7 -7
  228. package/src/client/styles/info.css.ts +74 -0
  229. package/src/client/styles/remote-session.css.ts +251 -0
  230. package/src/client-data/current-session.ts +57 -0
  231. package/src/client-data/remote-group-expansion.ts +145 -0
  232. package/src/client-data/remote-status.ts +374 -0
  233. package/src/client-data/settings-form.ts +1371 -0
  234. package/src/client-data/shares.ts +467 -0
  235. package/src/client-pairing.ts +244 -0
  236. package/src/client-routes.ts +732 -0
  237. package/src/config.ts +447 -0
  238. package/src/fetch-route-intercept.ts +510 -0
  239. package/src/fingerprint.ts +408 -0
  240. package/src/http.ts +68 -0
  241. package/src/index.ts +719 -62
  242. package/src/intercept-shape.ts +87 -0
  243. package/src/intercept.ts +3362 -0
  244. package/src/merge-streams.ts +1272 -0
  245. package/src/relay-access.ts +669 -0
  246. package/src/relay-client.ts +1514 -0
  247. package/src/relay-filter.ts +291 -0
  248. package/src/relay-server.ts +2025 -0
  249. package/src/restart-fields.ts +36 -0
  250. package/src/restart-watcher.ts +140 -0
  251. package/src/session-reference.ts +156 -0
  252. package/src/share-export.ts +26 -1
  253. package/src/share-ops.ts +114 -0
  254. package/src/share-store.ts +373 -0
  255. package/src/virtual-id.ts +59 -0
@@ -0,0 +1,200 @@
1
+ /**
2
+ * Config resolution for dsh-zen-remote: one flat key space, four layers.
3
+ *
4
+ * 2.0.0 moved the settings surface to the DSH plugin row (the loader-owned
5
+ * `Config` schema below), while long-time users keep their
6
+ * `<DSH_HOME>/lan-gate.config.json`. The agreed contract is resolve-time
7
+ * fallback — nothing is ever written back — and per field the first LEGAL
8
+ * value wins in this order:
9
+ *
10
+ * 1. environment variables (`LAN_GATE_*` / `DSH_PUSH_*`, historic names)
11
+ * 2. the plugin row (what the loader hands `apply()`)
12
+ * 3. `lan-gate.config.json`
13
+ * 4. defaults
14
+ *
15
+ * A value that fails its field's check (type, range, enum) makes that layer
16
+ * transparent, as if it had never been set — a hand-edited `port: "abc"` in
17
+ * the row therefore surfaces the file's port, not an error. Legal ranges are
18
+ * enforced HERE and nowhere else. The loader's `Config` schema below is
19
+ * deliberately not a validator: every field is `z.any()` (secret/volatile
20
+ * markers aside), because a schemastery type mismatch makes the loader reject
21
+ * the WHOLE row and the plugin never loads at all — violating the T11 design
22
+ * that a botched `role` degrades to host instead of breaking the plugin.
23
+ * `Config` only tells the settings form WHICH fields exist, which one is a
24
+ * secret, and which are volatile; `resolveConfig` owns every legality rule.
25
+ *
26
+ * Pure logic: no writes, no `process.env` mutation. The one deliberate read
27
+ * is `os.hostname()` for the `serverName` default, which the DEFAULTS table
28
+ * is specified to embed.
29
+ */
30
+ import z from '@deepseek-ai/schemastery';
31
+ /** Where a resolved field value came from, in resolution order. */
32
+ export type ConfigSource = 'env' | 'row' | 'file' | 'default';
33
+ /** The effective configuration one resolved value set, defaults included. */
34
+ export interface ZenRemoteConfig {
35
+ /** Which half of the plugin runs in this DSH process; anything the layers
36
+ * leave unresolved means `'host'` (see {@link resolveRole}). */
37
+ role: 'host' | 'client';
38
+ /** Port the gateway child listens on. */
39
+ port: number;
40
+ /** Bind address of the gateway child. */
41
+ host: string;
42
+ /** Port of the local DSH Web UI the gateway forwards to; undefined means
43
+ * "discover the host's real listening port at gateway start". */
44
+ targetPort: number | undefined;
45
+ /** Remote requests per minute per pairing before 429. */
46
+ rateLimit: number;
47
+ /** Comma-separated proxy IPs the gateway trusts for X-Forwarded-*. */
48
+ trustedProxies: string;
49
+ /** VAPID subject (a `mailto:` or `https:` URL identifying the push sender). */
50
+ vapidSubject: string;
51
+ /** Notification/UI language: `'auto'` defers to the requester's signal. */
52
+ lang: 'auto' | 'zh' | 'en';
53
+ /** Comma-separated session events the push leg treats as turn end. */
54
+ pushEvents: string;
55
+ /** Minimum milliseconds between two pushes (approval/question legs exempt). */
56
+ pushDebounceMs: number;
57
+ /** Include the turn's final text (or pending question) in push bodies. */
58
+ pushSummary: boolean;
59
+ /** Push when a top-level turn ends (opt-in: finishing is not an alarm). */
60
+ pushTurnEnd: boolean;
61
+ /** Register the model-facing `push_notify` tool. */
62
+ pushTool: boolean;
63
+ /** Server display name, at most {@link SERVER_NAME_MAX} characters. */
64
+ serverName: string;
65
+ /** Idle-sleep window of a remote-enabled session: after this many hours
66
+ * without new activity the remote connection closes on its own; running or
67
+ * waiting sessions never count. (0, 8760]. */
68
+ idleHours: number;
69
+ /** Auto-share every newly created session to the paired phone (T16). */
70
+ autoShareNewSessions: boolean;
71
+ }
72
+ /** Defaults, one per field — exactly what layer 4 hands back. */
73
+ export declare const DEFAULTS: ZenRemoteConfig;
74
+ /**
75
+ * Peel the loader's volatile wrapper off a row value. Schemastery hands
76
+ * `.volatile()` fields to apply() as cosmokit `{ get() }` reference objects,
77
+ * NOT as the value itself — a wrapper run through a validator unchecked would
78
+ * fail every check and the row layer would silently never win for those
79
+ * fields. Anything with a callable `get` is unwrapped; everything else passes
80
+ * through (the same duck-typing the verifier plugin's settingsOf uses, so no
81
+ * dependency on the wrapper's symbol protocol). Later tasks that read live
82
+ * settings at operation time use this too.
83
+ */
84
+ export declare function unwrapVolatile(value: unknown): unknown;
85
+ /**
86
+ * The effective configuration: every field resolved through the four layers.
87
+ * @param row - the plugin row config as `apply()` received it (loosely typed
88
+ * because the loader's parsed shape and hand-edited YAML differ).
89
+ * @param file - the parsed `lan-gate.config.json`, `{}` when absent.
90
+ * @param env - environment carrying the optional `LAN_GATE_*` / `DSH_PUSH_*`
91
+ * variables.
92
+ * @returns the merged values plus, per field, the layer it came from.
93
+ */
94
+ export declare function resolveConfig(row: unknown, file: Record<string, unknown>, env: NodeJS.ProcessEnv): {
95
+ values: ZenRemoteConfig;
96
+ sources: Record<keyof ZenRemoteConfig, ConfigSource>;
97
+ };
98
+ /**
99
+ * Read `<DSH_HOME>/lan-gate.config.json` (DSH_HOME defaulting to `~/.dsh`).
100
+ * Missing file, unparsable JSON, or a non-object root (arrays included) all
101
+ * yield `{}` — a broken file must degrade to defaults, never take the host
102
+ * process down. Never writes.
103
+ * @param env - environment providing DSH_HOME; defaults to `process.env`.
104
+ */
105
+ export declare function readFileConfig(env?: NodeJS.ProcessEnv): Record<string, unknown>;
106
+ /**
107
+ * The normalized role behind a row's `role` knob. Anything but the exact
108
+ * string `'client'` means host, so a typo degrades to the full plugin rather
109
+ * than silently dropping the gateway and push halves. The value is read
110
+ * through {@link unwrapVolatile} first: since T17 every row field arrives as
111
+ * a `{ get() }` live reference, and comparing the wrapper itself against
112
+ * `'client'` would always answer host (T17b). Resolution-order note:
113
+ * {@link resolveConfig} validates each layer against the two-member enum
114
+ * first, so an invalid row role can still be rescued by the file layer; this
115
+ * function answers the final merged value. Kept on the main entry's export
116
+ * surface for test/role-wiring.test.cjs.
117
+ */
118
+ export declare function resolveRole(row: unknown): 'host' | 'client';
119
+ /**
120
+ * The DSH loader's config schema for the plugin row: what the settings form
121
+ * renders and what it hands `apply()`. Every field is `z.any()` on purpose —
122
+ * legality is entirely {@link resolveConfig}'s job. A schemastery type
123
+ * mismatch (`z.number()` meeting `port: "abc"`) makes the loader throw away
124
+ * the whole row and the plugin never loads, which would break the T11
125
+ * promise that a botched `role` degrades to host instead of killing the
126
+ * plugin. So this schema only declares: WHICH fields exist (the form's
127
+ * field list), WHICH one is a secret (`deviceToken`, redacted from every
128
+ * settings wire surface), and that every field is volatile (live).
129
+ *
130
+ * No `.default()` anywhere: a schema default would be written into every row
131
+ * at load and `lan-gate.config.json`'s fallback would never get a turn — the
132
+ * real defaults live in {@link DEFAULTS}.
133
+ *
134
+ * EVERY field is volatile, and that is not an optimization: DSH 0.2.0's
135
+ * settings service builds the form from volatile fields ONLY (its
136
+ * `volatileForm` drops non-volatile ones, and a `mutate` outside the volatile
137
+ * subtree is refused), so a non-volatile field is invisible in the settings
138
+ * page and unsaveable from it — T12 made the restart-required fields
139
+ * non-volatile and the whole settings surface went dark for them. The cost
140
+ * is that the loader hands every volatile field to `apply()` as a `{ get() }`
141
+ * wrapper instead of the value, so EVERY row read goes through
142
+ * {@link unwrapVolatile} at use time (or through {@link resolveConfig},
143
+ * which unwraps per field). "Changed value requires a restart" therefore
144
+ * cannot ride the volatility flag: the fields in `RESTART_FIELDS`
145
+ * (src/restart-fields.ts) are re-resolved the moment the loader announces a
146
+ * volatile-only change (`loader/volatile-update`, T17b) and on a 2-second
147
+ * poll besides, and reload the plugin row when their fingerprint moves — a
148
+ * row reload re-runs `apply()`, which restarts the gateway child and the
149
+ * push half.
150
+ */
151
+ export declare const Config: z<Schemastery.ObjectS<NoInfer<{
152
+ role: z<any, any, "volatile">;
153
+ port: z<any, any, "volatile">;
154
+ host: z<any, any, "volatile">;
155
+ targetPort: z<any, any, "volatile">;
156
+ rateLimit: z<any, any, "volatile">;
157
+ trustedProxies: z<any, any, "volatile">;
158
+ vapidSubject: z<any, any, "volatile">;
159
+ lang: z<any, any, "volatile">;
160
+ pushEvents: z<any, any, "volatile">;
161
+ pushDebounceMs: z<any, any, "volatile">;
162
+ pushSummary: z<any, any, "volatile">;
163
+ pushTurnEnd: z<any, any, "volatile">;
164
+ pushTool: z<any, any, "volatile">;
165
+ serverName: z<any, any, "volatile">;
166
+ idleHours: z<any, any, "volatile">;
167
+ autoShareNewSessions: z<any, any, "volatile">;
168
+ serverUrl: z<any, any, "volatile">;
169
+ deviceToken: z<string, string, "volatile">;
170
+ turnFoldDesktop: z<any, any, "volatile">;
171
+ keyboardLiftRatio: z<any, any, "volatile">;
172
+ keyboardLiftMaxPx: z<any, any, "volatile">;
173
+ keyboardSafetyPadPx: z<any, any, "volatile">;
174
+ maxUploadBytes: z<any, any, "volatile">;
175
+ }>>, Schemastery.ObjectT<NoInfer<{
176
+ role: z<any, any, "volatile">;
177
+ port: z<any, any, "volatile">;
178
+ host: z<any, any, "volatile">;
179
+ targetPort: z<any, any, "volatile">;
180
+ rateLimit: z<any, any, "volatile">;
181
+ trustedProxies: z<any, any, "volatile">;
182
+ vapidSubject: z<any, any, "volatile">;
183
+ lang: z<any, any, "volatile">;
184
+ pushEvents: z<any, any, "volatile">;
185
+ pushDebounceMs: z<any, any, "volatile">;
186
+ pushSummary: z<any, any, "volatile">;
187
+ pushTurnEnd: z<any, any, "volatile">;
188
+ pushTool: z<any, any, "volatile">;
189
+ serverName: z<any, any, "volatile">;
190
+ idleHours: z<any, any, "volatile">;
191
+ autoShareNewSessions: z<any, any, "volatile">;
192
+ serverUrl: z<any, any, "volatile">;
193
+ deviceToken: z<string, string, "volatile">;
194
+ turnFoldDesktop: z<any, any, "volatile">;
195
+ keyboardLiftRatio: z<any, any, "volatile">;
196
+ keyboardLiftMaxPx: z<any, any, "volatile">;
197
+ keyboardSafetyPadPx: z<any, any, "volatile">;
198
+ maxUploadBytes: z<any, any, "volatile">;
199
+ }>>, "plain">;
200
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAKH,OAAO,CAAC,MAAM,0BAA0B,CAAA;AAExC,mEAAmE;AACnE,MAAM,MAAM,YAAY,GAAG,KAAK,GAAG,KAAK,GAAG,MAAM,GAAG,SAAS,CAAA;AAK7D,6EAA6E;AAC7E,MAAM,WAAW,eAAe;IAC9B;oEACgE;IAChE,IAAI,EAAE,MAAM,GAAG,QAAQ,CAAA;IACvB,yCAAyC;IACzC,IAAI,EAAE,MAAM,CAAA;IACZ,yCAAyC;IACzC,IAAI,EAAE,MAAM,CAAA;IACZ;qEACiE;IACjE,UAAU,EAAE,MAAM,GAAG,SAAS,CAAA;IAC9B,yDAAyD;IACzD,SAAS,EAAE,MAAM,CAAA;IACjB,sEAAsE;IACtE,cAAc,EAAE,MAAM,CAAA;IACtB,+EAA+E;IAC/E,YAAY,EAAE,MAAM,CAAA;IACpB,2EAA2E;IAC3E,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAAA;IAC1B,sEAAsE;IACtE,UAAU,EAAE,MAAM,CAAA;IAClB,+EAA+E;IAC/E,cAAc,EAAE,MAAM,CAAA;IACtB,0EAA0E;IAC1E,WAAW,EAAE,OAAO,CAAA;IACpB,2EAA2E;IAC3E,WAAW,EAAE,OAAO,CAAA;IACpB,oDAAoD;IACpD,QAAQ,EAAE,OAAO,CAAA;IACjB,uEAAuE;IACvE,UAAU,EAAE,MAAM,CAAA;IAClB;;kDAE8C;IAC9C,SAAS,EAAE,MAAM,CAAA;IACjB,wEAAwE;IACxE,oBAAoB,EAAE,OAAO,CAAA;CAC9B;AAMD,iEAAiE;AACjE,eAAO,MAAM,QAAQ,EAAE,eAiBtB,CAAA;AAOD;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAKtD;AA0LD;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAC3B,GAAG,EAAE,OAAO,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,GAAG,EAAE,MAAM,CAAC,UAAU,GACrB;IACD,MAAM,EAAE,eAAe,CAAA;IACvB,OAAO,EAAE,MAAM,CAAC,MAAM,eAAe,EAAE,YAAY,CAAC,CAAA;CACrD,CAuBA;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAQ5F;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,GAAG,QAAQ,CAE3D;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,eAAO,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;aA6BjB,CAAA"}
@@ -0,0 +1,156 @@
1
+ /**
2
+ * The exact-fetch-route interception (T51): the sub-client's own DSH process
3
+ * keeps serving its local UI, but the two `/api` routes a REMOTE session's
4
+ * surface reaches must not run against local state:
5
+ *
6
+ * - `/api/session/uploadFileBinary` — a NON-image attachment enters through
7
+ * dsh-client-file-upload's Web Worker (XHR for Blobs, worker-scoped fetch
8
+ * for streams — RT dsh-client-file-upload lib/client.js:72-152), so
9
+ * neither a `window.fetch` wrapper nor the typert interception sees it.
10
+ * What the worker's request DOES hit is the host's shared fetch-handler
11
+ * table: `HostConnectionService.fetchRoutes` is a plain Map the shared
12
+ * handler re-reads per request (`fetchRoutes.get(pathname)` then
13
+ * `route.fetch(request)`, RT dsh-client-connection lib/index.js:558 and
14
+ * ~614-617), and the upload registers `requestBody: "streaming"` with a
15
+ * closure `fetch` (RT dsh-client-file-upload lib/index.js:73, 171-176).
16
+ * Replacing that entry's `fetch` intercepts every upload at the local
17
+ * backend. A virtual `sessionId` rides the relay's binary channel to the
18
+ * server (the staged receipt then resolves under the SAME session the
19
+ * forwarded `session/prompt` runs against); anything else reaches the
20
+ * original function untouched.
21
+ * - `/api/session.export` — a remote session's log lives on the SERVER, and
22
+ * a virtual id on the local route could only fail; the wrap refuses it
23
+ * with 403 `remote-unsupported` before the local route runs (the UI entry
24
+ * is hidden remotely too, remote-session.css.ts).
25
+ *
26
+ * EVERY answer the UPLOAD wrapper produces is HTTP 200 + the failure
27
+ * envelope the browser half parses (T51-fix): dsh-client-file-upload's
28
+ * `FileUploadRuntime.upload` throws a bare English transport error on any
29
+ * non-200 status and never reaches `parseFileUploadResult` (RT
30
+ * lib/client.js:190 vs 272-297) — so offline, mismatch and the local
31
+ * oversize refusal all travel as the host's own failure shape, and the
32
+ * Chinese message lands on the attachment card. Only the EXPORT wrapper
33
+ * keeps a real 403: its UI (the export dialog) reads `response.ok` and
34
+ * prints the status.
35
+ *
36
+ * The wrap is gated on a structural shape check like the typert one
37
+ * (intercept-shape.ts): a future DSH without the fetchRoutes Map must leave
38
+ * this install OFF and local uploads exactly as they were. ENTRY-LEVEL facts
39
+ * attach per route (T51-fix): an entry that is absent at install time —
40
+ * session-log-export may register after us — is retried on a bounded timer
41
+ * and on each call of the OTHER wrapper; an entry that is present but
42
+ * misshaped is refused for ITS route only (present-but-wrong never fixes
43
+ * itself — `registerFetchRoute` throws on a duplicate path), and the rest
44
+ * of the install still runs. Uninstall restores an entry only while it
45
+ * still holds OUR wrapper — a later wrapper chained over us keeps working,
46
+ * and ours goes inert instead (the same posture as intercept.ts).
47
+ */
48
+ import type { RelayClient } from './relay-client.js';
49
+ /** The host's upload route, verbatim from its registration (RT
50
+ * dsh-client-file-upload lib/index.js:73). */
51
+ export declare const FILE_UPLOAD_PATH = "/api/session/uploadFileBinary";
52
+ /** The host's session-log export route (RT dsh-session-log-export
53
+ * lib/index.js:488). */
54
+ export declare const SESSION_EXPORT_PATH = "/api/session.export";
55
+ /** Verdict of {@link checkFetchRouteShape}. STRUCTURAL only: `ok` answers
56
+ * "is this a connection service with a fetch-route table at all"; each
57
+ * route's entry findings travel as `notes` — present/absent/misshaped — and
58
+ * the per-route attach decisions are the install's business (T51-fix). */
59
+ export type FetchRouteShapeCheck = {
60
+ ok: true;
61
+ notes: string[];
62
+ } | {
63
+ ok: false;
64
+ reasons: string[];
65
+ };
66
+ /** One exact-fetch route entry, as `registerFetchRoute` stores it (RT
67
+ * dsh-client-connection lib/index.js:627-631). */
68
+ export interface FetchRouteEntry {
69
+ methods: Set<string>;
70
+ requestBody: string;
71
+ fetch: (request: Request) => Promise<Response>;
72
+ }
73
+ /**
74
+ * Check the RAW connection service instance against the shape the wrap
75
+ * depends on. Pure inspection — nothing is mutated, so a failed check
76
+ * leaves every route exactly as it was. Only the structural facts refuse;
77
+ * the two entries' facts are described in `notes` (they attach lazily).
78
+ */
79
+ export declare function checkFetchRouteShape(connection: unknown): FetchRouteShapeCheck;
80
+ /** One refusal the wrappers answered, in the diagnostics ring. */
81
+ export interface FetchRouteFailureRecord {
82
+ /** ISO timestamp of the moment the refusal was answered. */
83
+ time: string;
84
+ /** Which wrapper answered it. */
85
+ route: 'upload' | 'export';
86
+ code: string;
87
+ }
88
+ /** What the client status route surfaces about this interception: flags,
89
+ * counters and codes only — never a token, never a server address. */
90
+ export interface FetchRouteInterceptDiagnostics {
91
+ installed: boolean;
92
+ shape: FetchRouteShapeCheck;
93
+ /** The upload entry currently holds our wrapper. */
94
+ uploadWrapped: boolean;
95
+ /** The export entry currently holds our wrapper (false while absent or
96
+ * refused). */
97
+ exportWrapped: boolean;
98
+ /** The upload entry was absent at last check and the attach retry is
99
+ * still running (T51-fix). */
100
+ uploadPending: boolean;
101
+ /** The export entry was absent at last check and the attach retry is
102
+ * still running (T51-fix). */
103
+ exportPending: boolean;
104
+ /** The upload entry was present but misshaped — attach refused for THIS
105
+ * route only; the reason is stable diagnostics text (T51-fix). */
106
+ uploadAttachRefused: string | undefined;
107
+ /** Same, for the export entry. */
108
+ exportAttachRefused: string | undefined;
109
+ /** Every upload that entered the wrapper, local passthroughs included. */
110
+ uploadCalls: number;
111
+ /** Uploads forwarded through the relay (virtual ids only). */
112
+ uploadForwarded: number;
113
+ /** Uploads refused locally (oversize, offline, mismatch, relay
114
+ * refusals, relay transport death). */
115
+ uploadRefused: number;
116
+ /** Export requests blocked with `remote-unsupported`. */
117
+ exportBlocked: number;
118
+ /** The most recent refusals, oldest first, capped. */
119
+ recentFailures: FetchRouteFailureRecord[];
120
+ }
121
+ export interface InstallFetchRouteInterceptOptions {
122
+ /** The RAW connection service instance
123
+ * (`ctx.connection[symbols.original]`) — the one whose `fetchRoutes` Map
124
+ * the shared handler reads per request. */
125
+ connection: object;
126
+ /** The client relay client (T23a) the uploads travel through. */
127
+ relay: RelayClient;
128
+ /** The CURRENT handshake's server id, read live per call; `undefined`
129
+ * makes every remote upload a `remote-offline`. */
130
+ getServerId: () => string | undefined;
131
+ /** Progress logging, wired to the context logger by index.ts. */
132
+ log?: (format: string, ...args: unknown[]) => void;
133
+ /** The lazy-attach retry cadence (T51-fix): how often the table is
134
+ * re-probed for entries that were absent at install, and how many probes
135
+ * before giving up. Defaults 3000 ms × 10; tests shrink both. */
136
+ attachRetryMs?: number;
137
+ attachAttempts?: number;
138
+ }
139
+ export interface FetchRouteInterceptHandle {
140
+ /** Restore every attached entry — each ONLY while it still holds our
141
+ * wrapper — and stop the attach retry. Idempotent. After it ran, the
142
+ * wrappers stay installed-but-inert if someone had chained over them
143
+ * (same posture as intercept.ts). */
144
+ uninstall(): void;
145
+ /** The status-route view. */
146
+ diagnostics(): FetchRouteInterceptDiagnostics;
147
+ }
148
+ /**
149
+ * Install the wrappers over the two fetch-route entries. Assumes
150
+ * {@link checkFetchRouteShape} passed (the wiring gates on it — the gate is
151
+ * structural only); each entry attaches on its own facts, immediately when
152
+ * present and attachable, via the bounded retry when absent, never when
153
+ * present-but-misshaped.
154
+ */
155
+ export declare function installFetchRouteIntercept(options: InstallFetchRouteInterceptOptions): FetchRouteInterceptHandle;
156
+ //# sourceMappingURL=fetch-route-intercept.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fetch-route-intercept.d.ts","sourceRoot":"","sources":["../../src/fetch-route-intercept.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAIpD;8CAC8C;AAC9C,eAAO,MAAM,gBAAgB,kCAAkC,CAAA;AAE/D;wBACwB;AACxB,eAAO,MAAM,mBAAmB,wBAAwB,CAAA;AAgBxD;;;0EAG0E;AAC1E,MAAM,MAAM,oBAAoB,GAAG;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,CAAA;AAEnG;kDACkD;AAClD,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IACpB,WAAW,EAAE,MAAM,CAAA;IACnB,KAAK,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;CAC/C;AAMD;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,OAAO,GAAG,oBAAoB,CAe9E;AA0BD,kEAAkE;AAClE,MAAM,WAAW,uBAAuB;IACtC,4DAA4D;IAC5D,IAAI,EAAE,MAAM,CAAA;IACZ,iCAAiC;IACjC,KAAK,EAAE,QAAQ,GAAG,QAAQ,CAAA;IAC1B,IAAI,EAAE,MAAM,CAAA;CACb;AAED;sEACsE;AACtE,MAAM,WAAW,8BAA8B;IAC7C,SAAS,EAAE,OAAO,CAAA;IAClB,KAAK,EAAE,oBAAoB,CAAA;IAC3B,oDAAoD;IACpD,aAAa,EAAE,OAAO,CAAA;IACtB;mBACe;IACf,aAAa,EAAE,OAAO,CAAA;IACtB;kCAC8B;IAC9B,aAAa,EAAE,OAAO,CAAA;IACtB;kCAC8B;IAC9B,aAAa,EAAE,OAAO,CAAA;IACtB;sEACkE;IAClE,mBAAmB,EAAE,MAAM,GAAG,SAAS,CAAA;IACvC,kCAAkC;IAClC,mBAAmB,EAAE,MAAM,GAAG,SAAS,CAAA;IACvC,0EAA0E;IAC1E,WAAW,EAAE,MAAM,CAAA;IACnB,8DAA8D;IAC9D,eAAe,EAAE,MAAM,CAAA;IACvB;2CACuC;IACvC,aAAa,EAAE,MAAM,CAAA;IACrB,yDAAyD;IACzD,aAAa,EAAE,MAAM,CAAA;IACrB,sDAAsD;IACtD,cAAc,EAAE,uBAAuB,EAAE,CAAA;CAC1C;AAED,MAAM,WAAW,iCAAiC;IAChD;;+CAE2C;IAC3C,UAAU,EAAE,MAAM,CAAA;IAClB,iEAAiE;IACjE,KAAK,EAAE,WAAW,CAAA;IAClB;uDACmD;IACnD,WAAW,EAAE,MAAM,MAAM,GAAG,SAAS,CAAA;IACrC,iEAAiE;IACjE,GAAG,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAA;IAClD;;qEAEiE;IACjE,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB;AAED,MAAM,WAAW,yBAAyB;IACxC;;;yCAGqC;IACrC,SAAS,IAAI,IAAI,CAAA;IACjB,6BAA6B;IAC7B,WAAW,IAAI,8BAA8B,CAAA;CAC9C;AAkDD;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,iCAAiC,GAAG,yBAAyB,CAkPhH"}
@@ -0,0 +1,164 @@
1
+ /**
2
+ * DSH interface fingerprints (T42, the second of the three version-tolerance
3
+ * layers). The sub-client's UI code comes from ITS OWN App while the data
4
+ * comes from the relay server's DSH — when the two DSH versions disagree
5
+ * about an interface shape, individual calls fail at runtime. The fingerprint
6
+ * lets both ends NOTICE without refusing the link (that is the relay
7
+ * protocol's job, layer one): each side hashes a few groups of interface
8
+ * definitions, the server puts its map into the handshake, the client
9
+ * compares group by group, and the settings page lists the differing groups.
10
+ * Identical maps mean fully compatible — the DSH version numbers themselves
11
+ * are deliberately NOT compared (docs/spike-relay.md §2.3: 0.1.7 and 0.2.0
12
+ * share byte-identical remote interfaces, so a version gate would misreject).
13
+ *
14
+ * What is hashed, per group, in priority order:
15
+ *
16
+ * 1. REGISTRY TIER — `ctx.typert.local.list()`, the descriptors the running
17
+ * DSH actually registered (dsh-typert-registry's `TypertRegistry.local`:
18
+ * `list()` returns the committed descriptor objects; shape verified against
19
+ * dsh-typert-registry 0.1.7-rc.2 / 0.1.0-rc.6 `DescriptorStore.list`).
20
+ * Descriptors are grouped by their `id` prefix `@deepseek-ai/<package>#`,
21
+ * then canonicalized exactly like the spike's fp.mjs prototype:
22
+ * `sourceLocation` keys are dropped (their line/column drift with unrelated
23
+ * edits), every `create` field (the generated descriptors' codec/result
24
+ * schema producers — the ONLY functions ever called) is projected to JSON
25
+ * Schema with `unrepresentable: 'any'`, object keys are sorted recursively
26
+ * (the schema's `required` arrays alphabetically too — see below), and the
27
+ * JSON is sha256'd. The instance form
28
+ * `schema.toJSONSchema({ unrepresentable: 'any' })` is used instead of
29
+ * importing zod — verified byte-identical to `z.toJSONSchema(schema, …)`,
30
+ * and it spares this package a zod dependency hunt through the host
31
+ * closure. Reproducing the spike's recorded values with the
32
+ * key-sorting-only precursor of this normalization matched exactly
33
+ * (session-controller `be9ab393695a`, 21 endpoints); the recursive schema
34
+ * canonicalization below only makes that equality stronger — field
35
+ * declaration order can no longer move a hash.
36
+ * 2. FILE TIER — the task's documented fallback when no usable typert service
37
+ * (or schema projection) exists: sha256 over the package's
38
+ * `lib/typert.remote-client.js` with every `sourceLocation: {…}` fragment
39
+ * stripped. The file is located through `createRequire` resolving
40
+ * `@deepseek-ai/<package>/package.json`.
41
+ * 3. `FINGERPRINT_UNAVAILABLE` — the group could not be computed here, OR the
42
+ * registry answered with zero descriptors for the group: the typert
43
+ * loader registers package by package asynchronously, so a client that
44
+ * handshakes a just-restarted server would otherwise read a half-empty
45
+ * registry as "this DSH really runs no such interfaces" and publish
46
+ * throwaway hashes. Unavailable is NOT a difference — it only feeds the
47
+ * diagnostics.
48
+ *
49
+ * Every registry value is prefixed `r:`, every file-tier value `f:`. The
50
+ * two tiers hash different material, so a group whose ends normalized
51
+ * through DIFFERENT tiers compares as unavailable — never as a difference.
52
+ *
53
+ * Every group is computed inside its own try/catch, so one broken group never
54
+ * costs the others; the whole map is meant to be recomputed per handshake
55
+ * (~7 ms measured on a full registry — cheap enough to never go stale) and
56
+ * NOT cached: the registry assembles asynchronously, and a cached map would
57
+ * pin whatever a first handshake happened to see. The `events` group has no
58
+ * generated definitions; its input is `dsh-api-remotes`'s exported
59
+ * `API_REMOTE_FORWARDED_EVENTS` (event name + mode, sorted), resolved through
60
+ * the same anchors — unresolvable means unavailable.
61
+ */
62
+ /** The value a group carries when its fingerprint could not be computed. */
63
+ export declare const FINGERPRINT_UNAVAILABLE = "unavailable";
64
+ /** Group name of the forwarded-events fingerprint (no generated definitions). */
65
+ export declare const EVENTS_GROUP = "events";
66
+ /**
67
+ * The interface groups the relay actually exercises, mapped to the DSH
68
+ * package whose descriptors define them (docs/spike-relay.md §4.4). Keys are
69
+ * the wire names both ends exchange — stable ASCII, rendered verbatim by the
70
+ * settings page.
71
+ */
72
+ export declare const FINGERPRINT_GROUP_PACKAGES: Readonly<Record<string, string>>;
73
+ /**
74
+ * The piece of cordis this module needs: the reflection layer's service
75
+ * lookup, used to read `typert` without declaring an inject (a context
76
+ * without the service answers undefined, same as the shares routes' reads).
77
+ * Structural on purpose: the fingerprints compute in compositions this
78
+ * package cannot type against.
79
+ */
80
+ export interface FingerprintContext {
81
+ reflect?: {
82
+ get(name: string): unknown;
83
+ };
84
+ }
85
+ /** Injectable knobs — the resolution anchors, for tests. */
86
+ export interface FingerprintOptions {
87
+ /**
88
+ * Package-resolution anchors, HIGHEST priority first. Each anchor seeds a
89
+ * `createRequire` that walks node_modules upward from it. The default puts
90
+ * the host process entry first (`process.argv[1]`, realpath'd — under the
91
+ * npm CLI it is a symlink into the real install) and this plugin second:
92
+ * a link-installed plugin carries devDependency copies of the DSH
93
+ * packages, and reading those would fingerprint a version the host is not
94
+ * actually running.
95
+ */
96
+ anchors?: readonly string[];
97
+ }
98
+ /** The group-by-group comparison result the relay client stores. */
99
+ export interface RelayCompatVerdict {
100
+ /** Groups whose fingerprints match exactly. */
101
+ identical: string[];
102
+ /** Groups whose fingerprints differ — the yellow-hint material. */
103
+ different: string[];
104
+ /** Groups at least one end could not compute — or whose values came out
105
+ * of different normalization tiers and are therefore not comparable.
106
+ * Never a difference. */
107
+ unavailable: string[];
108
+ }
109
+ /**
110
+ * The canonicalization from the spike's fp.mjs prototype, kept faithful
111
+ * where the spike proved it (sourceLocation dropped, keys sorted, schemas
112
+ * projected with `unrepresentable: 'any'`), tightened in two places: only a
113
+ * field NAMED `create` is treated as a schema producer — the generated
114
+ * descriptors project schemas there and nowhere else, so any other function
115
+ * (`decode`, `encode`, …) collapses to the stable marker `'fn'` WITHOUT
116
+ * being called; and the projection is recursively key-sorted (see
117
+ * {@link canonicalizeJsonSchema}).
118
+ */
119
+ export declare function normalizeDescriptorValue(value: unknown, key?: string): unknown;
120
+ /**
121
+ * One package group's registry-tier fingerprint over its descriptors, shown
122
+ * as a pure helper (the empty-set decision belongs to
123
+ * {@link groupFingerprint}, which turns an empty subset into
124
+ * `unavailable` instead of a hash of nothing).
125
+ */
126
+ export declare function fingerprintDescriptors(descriptors: readonly unknown[]): string;
127
+ /**
128
+ * The file-tier fallback: sha256 over the package's generated remote-client
129
+ * definitions with every `sourceLocation: {…}` fragment stripped. The
130
+ * fragments are flat JSON objects (file/line/column), so the brace-bounded
131
+ * match cannot overeat. Throws when the package is not resolvable — the
132
+ * group degrades instead of guessing.
133
+ */
134
+ export declare function fingerprintPackageFile(packageName: string, anchors?: readonly string[]): string;
135
+ /**
136
+ * The events group's fingerprint over `dsh-api-remotes`'s forwarded-events
137
+ * whitelist: `{event, mode}` rows, sorted by event then mode, hashed. A
138
+ * shape that is not an array of named+moded rows throws — the caller
139
+ * degrades the group rather than hashing a guess.
140
+ */
141
+ export declare function fingerprintForwardedEvents(events: unknown): string;
142
+ /**
143
+ * Compute every group's fingerprint for the DSH this context runs in.
144
+ * Per-group try/catch throughout: a group that cannot be computed carries
145
+ * {@link FINGERPRINT_UNAVAILABLE} and never costs the others. The result is
146
+ * cheap enough (~7 ms against a full registry) to recompute per handshake —
147
+ * deliberately NOT cached, because the typert registry assembles
148
+ * asynchronously and a cached map would pin whatever a first handshake
149
+ * happened to see.
150
+ */
151
+ export declare function computeFingerprints(ctx: FingerprintContext, options?: FingerprintOptions): Promise<Record<string, string>>;
152
+ /**
153
+ * Compare the server's handshake fingerprints against this side's own,
154
+ * group by group over the union of both maps. A group either side could
155
+ * not compute (`FINGERPRINT_UNAVAILABLE`, a missing key, a non-string a
156
+ * hostile server substituted) lands in `unavailable` — never in
157
+ * `different`, so a half-blind comparison stays silent instead of crying
158
+ * wolf. The same goes for a group whose two values carry DIFFERENT
159
+ * algorithm prefixes (`r:` vs `f:`): they were normalized from different
160
+ * material and must not be diffed against each other. Pure; unit-tested
161
+ * against injected maps.
162
+ */
163
+ export declare function compareFingerprints(server: unknown, own: unknown): RelayCompatVerdict;
164
+ //# sourceMappingURL=fingerprint.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fingerprint.d.ts","sourceRoot":"","sources":["../../src/fingerprint.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4DG;AAQH,4EAA4E;AAC5E,eAAO,MAAM,uBAAuB,gBAAgB,CAAA;AAQpD,iFAAiF;AACjF,eAAO,MAAM,YAAY,WAAW,CAAA;AAEpC;;;;;GAKG;AACH,eAAO,MAAM,0BAA0B,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAOvE,CAAA;AAKD;;;;;;GAMG;AACH,MAAM,WAAW,kBAAkB;IACjC,OAAO,CAAC,EAAE;QAAE,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAA;CACzC;AAED,4DAA4D;AAC5D,MAAM,WAAW,kBAAkB;IACjC;;;;;;;;OAQG;IACH,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAC5B;AAED,oEAAoE;AACpE,MAAM,WAAW,kBAAkB;IACjC,+CAA+C;IAC/C,SAAS,EAAE,MAAM,EAAE,CAAA;IACnB,mEAAmE;IACnE,SAAS,EAAE,MAAM,EAAE,CAAA;IACnB;;6BAEyB;IACzB,WAAW,EAAE,MAAM,EAAE,CAAA;CACtB;AA+CD;;;;;;;;;GASG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAqB9E;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,WAAW,EAAE,SAAS,OAAO,EAAE,GAAG,MAAM,CAE9E;AA0CD;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAK/F;AAED;;;;;GAKG;AACH,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,CAYlE;AAyDD;;;;;;;;GAQG;AACH,wBAAsB,mBAAmB,CAAC,GAAG,EAAE,kBAAkB,EAAE,OAAO,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAShI;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,GAAG,kBAAkB,CAwBrF"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Shared HTTP plumbing for the host-side route modules: the one JSON response
3
+ * envelope and the one same-origin gate every state-changing browser route
4
+ * applies. Both lived in the main entry until the admin routes (T14) needed
5
+ * the exact same pair; importing the entry from a route module would be an
6
+ * import cycle, so the definitions live here and the entry re-exports the
7
+ * same-origin gate its public surface has carried since S7.
8
+ */
9
+ import type { IncomingMessage, ServerResponse } from 'node:http';
10
+ /** The one JSON response envelope every host route speaks: no-store (admin
11
+ * surfaces and pairing codes must never sit in a shared cache), nosniff, and
12
+ * a default-src 'none' CSP since no route serves executable content. */
13
+ export declare function responseJson(res: ServerResponse, status: number, body: unknown): void;
14
+ /**
15
+ * Accept a state-changing request only from this DSH Web application's origin.
16
+ *
17
+ * The gateway half rewrites `Origin`/`Host` to the upstream origin
18
+ * before forwarding (lan-gate-server.cjs `cleanHeaders`), so a phone request
19
+ * that already cleared the pairing wall presents here as same-origin; a
20
+ * request with neither header falls back to the Fetch metadata.
21
+ *
22
+ * When BOTH `Origin` and `Sec-Fetch-Site` are absent the request is ACCEPTED
23
+ * (T16-fix): the DSH desktop app's main process forwards window requests to
24
+ * the local backend after verifying `Origin: dsh-app://app` itself, and
25
+ * strips `host`, `origin`, `cookie` and `sec-fetch-site` on the way (its
26
+ * `forwardWebRequest`), so such requests arrive with neither header. A
27
+ * curl-style local tool sends neither either. Neither shape is a
28
+ * browser-direct cross-site POST. What stands in front of an accepted
29
+ * headerless request depends on the route module: the admin routes (T14) and
30
+ * the client routes (T16) run `connection.admit` FIRST, so such a request has
31
+ * still cleared DSH's login wall before this check runs; since T17 the
32
+ * upload and share-export routes run the same `connection.admit` first, so
33
+ * every state-changing route this gate fronts is admission-covered and this
34
+ * acceptance never stands alone. Everything
35
+ * identifiable as cross-site (`Sec-Fetch-Site: cross-site`, or an `Origin`
36
+ * that disagrees with `Host`) is still refused.
37
+ * @param req - the inbound request.
38
+ * @returns true when the request may mutate the workspace.
39
+ */
40
+ export declare function sameOriginPost(req: IncomingMessage): boolean;
41
+ //# sourceMappingURL=http.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/http.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAEhE;;wEAEwE;AACxE,wBAAgB,YAAY,CAAC,GAAG,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,IAAI,CASrF;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAgB5D"}