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.
- package/AGENTS.md +54 -9
- package/README.md +249 -190
- package/README.zh-CN.md +247 -201
- package/assets/home.png +0 -0
- package/assets/info.png +0 -0
- package/assets/pairing.png +0 -0
- package/assets/session.png +0 -0
- package/assets/sheet.png +0 -0
- package/cordis.patch.yml +12 -11
- package/cordis.patch.yml.example +24 -9
- package/docs/interface.md +57 -2
- package/docs/remote-access.en.md +99 -58
- package/docs/remote-access.md +99 -42
- package/dsh-push.mjs +67 -54
- package/lan-gate.mjs +88 -20
- package/lib/activity.js +168 -0
- package/lib/activity.js.map +1 -0
- package/lib/admin-routes.js +526 -0
- package/lib/admin-routes.js.map +1 -0
- package/lib/client-pairing.js +224 -0
- package/lib/client-pairing.js.map +1 -0
- package/lib/client-routes.js +674 -0
- package/lib/client-routes.js.map +1 -0
- package/lib/client.js +10051 -4777
- package/lib/config.js +346 -0
- package/lib/config.js.map +1 -0
- package/lib/fetch-route-intercept.js +418 -0
- package/lib/fetch-route-intercept.js.map +1 -0
- package/lib/fingerprint.js +363 -0
- package/lib/fingerprint.js.map +1 -0
- package/lib/http.js +69 -0
- package/lib/http.js.map +1 -0
- package/lib/index.js +675 -54
- package/lib/index.js.map +1 -1
- package/lib/intercept-shape.js +81 -0
- package/lib/intercept-shape.js.map +1 -0
- package/lib/intercept.js +3040 -0
- package/lib/intercept.js.map +1 -0
- package/lib/lan-gate-server.cjs +532 -121
- package/lib/merge-streams.js +1165 -0
- package/lib/merge-streams.js.map +1 -0
- package/lib/relay-access.js +496 -0
- package/lib/relay-access.js.map +1 -0
- package/lib/relay-client.js +1303 -0
- package/lib/relay-client.js.map +1 -0
- package/lib/relay-filter.js +282 -0
- package/lib/relay-filter.js.map +1 -0
- package/lib/relay-server.js +1792 -0
- package/lib/relay-server.js.map +1 -0
- package/lib/restart-fields.js +34 -0
- package/lib/restart-fields.js.map +1 -0
- package/lib/restart-watcher.js +106 -0
- package/lib/restart-watcher.js.map +1 -0
- package/lib/session-reference.js +157 -0
- package/lib/session-reference.js.map +1 -0
- package/lib/share-export.js +16 -1
- package/lib/share-export.js.map +1 -1
- package/lib/share-ops.js +86 -0
- package/lib/share-ops.js.map +1 -0
- package/lib/share-store.js +294 -0
- package/lib/share-store.js.map +1 -0
- package/lib/types/activity.d.ts +99 -0
- package/lib/types/activity.d.ts.map +1 -0
- package/lib/types/admin-routes.d.ts +129 -0
- package/lib/types/admin-routes.d.ts.map +1 -0
- package/lib/types/client/MobileDrawerFooter.d.ts.map +1 -1
- package/lib/types/client/MobileHome.d.ts +1 -1
- package/lib/types/client/MobileHome.d.ts.map +1 -1
- package/lib/types/client/MobileHomeChips.d.ts +1 -1
- package/lib/types/client/MobileHomeChips.d.ts.map +1 -1
- package/lib/types/client/MobileSessionHeader.d.ts +13 -2
- package/lib/types/client/MobileSessionHeader.d.ts.map +1 -1
- package/lib/types/client/MobileSessionInfo.d.ts +20 -3
- package/lib/types/client/MobileSessionInfo.d.ts.map +1 -1
- package/lib/types/client/RemoteComposerBanner.d.ts +42 -0
- package/lib/types/client/RemoteComposerBanner.d.ts.map +1 -0
- package/lib/types/client/RemoteHeaderIcon.d.ts +37 -0
- package/lib/types/client/RemoteHeaderIcon.d.ts.map +1 -0
- package/lib/types/client/RemoteShareMenu.d.ts +28 -0
- package/lib/types/client/RemoteShareMenu.d.ts.map +1 -0
- package/lib/types/client/RemoteStatusIcon.d.ts +36 -0
- package/lib/types/client/RemoteStatusIcon.d.ts.map +1 -0
- package/lib/types/client/compat/desktop.d.ts +21 -0
- package/lib/types/client/compat/desktop.d.ts.map +1 -0
- package/lib/types/client/compat/types.d.ts +57 -0
- package/lib/types/client/compat/types.d.ts.map +1 -1
- package/lib/types/client/current-session-report.d.ts +37 -0
- package/lib/types/client/current-session-report.d.ts.map +1 -0
- package/lib/types/client/effects/header-status.d.ts.map +1 -1
- package/lib/types/client/effects/keyboard-guard.d.ts.map +1 -1
- package/lib/types/client/effects/permission-sheet.d.ts +39 -0
- package/lib/types/client/effects/permission-sheet.d.ts.map +1 -0
- package/lib/types/client/effects/remote-session.d.ts +25 -0
- package/lib/types/client/effects/remote-session.d.ts.map +1 -0
- package/lib/types/client/effects/turn-fold.d.ts +9 -0
- package/lib/types/client/effects/turn-fold.d.ts.map +1 -1
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/locales.d.ts +187 -2
- package/lib/types/client/locales.d.ts.map +1 -1
- package/lib/types/client/remote-fetch.d.ts +51 -0
- package/lib/types/client/remote-fetch.d.ts.map +1 -0
- package/lib/types/client/remote-group-expansion.d.ts +41 -0
- package/lib/types/client/remote-group-expansion.d.ts.map +1 -0
- package/lib/types/client/remote-share-css.d.ts +19 -0
- package/lib/types/client/remote-share-css.d.ts.map +1 -0
- package/lib/types/client/remote-share-register.d.ts +113 -0
- package/lib/types/client/remote-share-register.d.ts.map +1 -0
- package/lib/types/client/remote-status-css.d.ts +15 -0
- package/lib/types/client/remote-status-css.d.ts.map +1 -0
- package/lib/types/client/remote-status-register.d.ts +46 -0
- package/lib/types/client/remote-status-register.d.ts.map +1 -0
- package/lib/types/client/settings/SettingsSection.d.ts +46 -0
- package/lib/types/client/settings/SettingsSection.d.ts.map +1 -0
- package/lib/types/client/settings/register-settings.d.ts +58 -0
- package/lib/types/client/settings/register-settings.d.ts.map +1 -0
- package/lib/types/client/settings/settings-css.d.ts +10 -0
- package/lib/types/client/settings/settings-css.d.ts.map +1 -0
- package/lib/types/client/share/share-preview.d.ts.map +1 -1
- package/lib/types/client/styles/composer.css.d.ts +1 -1
- package/lib/types/client/styles/composer.css.d.ts.map +1 -1
- package/lib/types/client/styles/header.css.d.ts +1 -1
- package/lib/types/client/styles/header.css.d.ts.map +1 -1
- package/lib/types/client/styles/info.css.d.ts +1 -1
- package/lib/types/client/styles/info.css.d.ts.map +1 -1
- package/lib/types/client/styles/remote-session.css.d.ts +2 -0
- package/lib/types/client/styles/remote-session.css.d.ts.map +1 -0
- package/lib/types/client-data/current-session.d.ts +41 -0
- package/lib/types/client-data/current-session.d.ts.map +1 -0
- package/lib/types/client-data/remote-group-expansion.d.ts +76 -0
- package/lib/types/client-data/remote-group-expansion.d.ts.map +1 -0
- package/lib/types/client-data/remote-status.d.ts +148 -0
- package/lib/types/client-data/remote-status.d.ts.map +1 -0
- package/lib/types/client-data/settings-form.d.ts +774 -0
- package/lib/types/client-data/settings-form.d.ts.map +1 -0
- package/lib/types/client-data/shares.d.ts +211 -0
- package/lib/types/client-data/shares.d.ts.map +1 -0
- package/lib/types/client-pairing.d.ts +114 -0
- package/lib/types/client-pairing.d.ts.map +1 -0
- package/lib/types/client-routes.d.ts +112 -0
- package/lib/types/client-routes.d.ts.map +1 -0
- package/lib/types/config.d.ts +200 -0
- package/lib/types/config.d.ts.map +1 -0
- package/lib/types/fetch-route-intercept.d.ts +156 -0
- package/lib/types/fetch-route-intercept.d.ts.map +1 -0
- package/lib/types/fingerprint.d.ts +164 -0
- package/lib/types/fingerprint.d.ts.map +1 -0
- package/lib/types/http.d.ts +41 -0
- package/lib/types/http.d.ts.map +1 -0
- package/lib/types/index.d.ts +56 -24
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/intercept-shape.d.ts +32 -0
- package/lib/types/intercept-shape.d.ts.map +1 -0
- package/lib/types/intercept.d.ts +432 -0
- package/lib/types/intercept.d.ts.map +1 -0
- package/lib/types/merge-streams.d.ts +393 -0
- package/lib/types/merge-streams.d.ts.map +1 -0
- package/lib/types/relay-access.d.ts +310 -0
- package/lib/types/relay-access.d.ts.map +1 -0
- package/lib/types/relay-client.d.ts +274 -0
- package/lib/types/relay-client.d.ts.map +1 -0
- package/lib/types/relay-filter.d.ts +103 -0
- package/lib/types/relay-filter.d.ts.map +1 -0
- package/lib/types/relay-server.d.ts +208 -0
- package/lib/types/relay-server.d.ts.map +1 -0
- package/lib/types/restart-fields.d.ts +22 -0
- package/lib/types/restart-fields.d.ts.map +1 -0
- package/lib/types/restart-watcher.d.ts +83 -0
- package/lib/types/restart-watcher.d.ts.map +1 -0
- package/lib/types/session-reference.d.ts +66 -0
- package/lib/types/session-reference.d.ts.map +1 -0
- package/lib/types/share-export.d.ts +14 -1
- package/lib/types/share-export.d.ts.map +1 -1
- package/lib/types/share-ops.d.ts +79 -0
- package/lib/types/share-ops.d.ts.map +1 -0
- package/lib/types/share-store.d.ts +87 -0
- package/lib/types/share-store.d.ts.map +1 -0
- package/lib/types/virtual-id.d.ts +38 -0
- package/lib/types/virtual-id.d.ts.map +1 -0
- package/lib/virtual-id.js +52 -0
- package/lib/virtual-id.js.map +1 -0
- package/llms.txt +17 -14
- package/package.json +60 -50
- package/scripts/build-client.mjs +5 -3
- package/scripts/check-host-shadow.mjs +19 -2
- package/scripts/check-settings-form.mjs +933 -0
- package/scripts/check-shares.mjs +397 -0
- package/scripts/check-upload-endpoint.mjs +59 -3
- package/scripts/sync-doc-version.mjs +8 -15
- package/scripts/verify-fingerprint-live.mjs +111 -0
- package/src/activity.ts +218 -0
- package/src/admin-routes.ts +627 -0
- package/src/client/MobileAttachButton.tsx +2 -2
- package/src/client/MobileAttachChips.tsx +3 -3
- package/src/client/MobileDrawerFooter.tsx +7 -4
- package/src/client/MobileHome.tsx +20 -16
- package/src/client/MobileHomeChips.tsx +16 -16
- package/src/client/MobileNavOverlay.tsx +2 -2
- package/src/client/MobileNavToggle.tsx +3 -3
- package/src/client/MobileSessionHeader.tsx +63 -42
- package/src/client/MobileSessionInfo.tsx +118 -17
- package/src/client/RemoteComposerBanner.tsx +76 -0
- package/src/client/RemoteHeaderIcon.tsx +103 -0
- package/src/client/RemoteShareMenu.tsx +57 -0
- package/src/client/RemoteStatusIcon.tsx +91 -0
- package/src/client/compat/desktop.ts +22 -0
- package/src/client/compat/types.ts +62 -0
- package/src/client/current-session-report.ts +164 -0
- package/src/client/effects/header-status.ts +21 -17
- package/src/client/effects/keyboard-guard.ts +65 -1
- package/src/client/effects/permission-sheet.ts +149 -0
- package/src/client/effects/remote-session.ts +68 -0
- package/src/client/effects/turn-fold.ts +52 -0
- package/src/client/index.tsx +151 -36
- package/src/client/locales.ts +396 -2
- package/src/client/remote-fetch.ts +125 -0
- package/src/client/remote-group-expansion.ts +190 -0
- package/src/client/remote-share-css.ts +54 -0
- package/src/client/remote-share-register.ts +244 -0
- package/src/client/remote-status-css.ts +64 -0
- package/src/client/remote-status-register.ts +107 -0
- package/src/client/settings/SettingsSection.tsx +1229 -0
- package/src/client/settings/register-settings.ts +116 -0
- package/src/client/settings/settings-css.ts +236 -0
- package/src/client/share/share-preview.tsx +6 -3
- package/src/client/share/share-sheet.tsx +3 -3
- package/src/client/styles/composer.css.ts +119 -26
- package/src/client/styles/header.css.ts +7 -7
- package/src/client/styles/info.css.ts +74 -0
- package/src/client/styles/remote-session.css.ts +251 -0
- package/src/client-data/current-session.ts +57 -0
- package/src/client-data/remote-group-expansion.ts +145 -0
- package/src/client-data/remote-status.ts +374 -0
- package/src/client-data/settings-form.ts +1371 -0
- package/src/client-data/shares.ts +467 -0
- package/src/client-pairing.ts +244 -0
- package/src/client-routes.ts +732 -0
- package/src/config.ts +447 -0
- package/src/fetch-route-intercept.ts +510 -0
- package/src/fingerprint.ts +408 -0
- package/src/http.ts +68 -0
- package/src/index.ts +719 -62
- package/src/intercept-shape.ts +87 -0
- package/src/intercept.ts +3362 -0
- package/src/merge-streams.ts +1272 -0
- package/src/relay-access.ts +669 -0
- package/src/relay-client.ts +1514 -0
- package/src/relay-filter.ts +291 -0
- package/src/relay-server.ts +2025 -0
- package/src/restart-fields.ts +36 -0
- package/src/restart-watcher.ts +140 -0
- package/src/session-reference.ts +156 -0
- package/src/share-export.ts +26 -1
- package/src/share-ops.ts +114 -0
- package/src/share-store.ts +373 -0
- 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"}
|