dsh-zen-remote 2.3.3 → 2.3.4
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/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/docs/plugin-bridge.md +14 -7
- package/docs/remote-access.en.md +8 -8
- package/docs/remote-access.md +10 -10
- package/lib/adapters/dsh-better-sidebar/index.js +21 -8
- package/lib/adapters/dsh-better-sidebar/index.js.map +1 -1
- package/lib/adapters/dsh-better-sidebar/surface.js +79 -0
- package/lib/adapters/dsh-better-sidebar/surface.js.map +1 -0
- package/lib/client.js +1 -1
- package/lib/index.js +103 -10
- package/lib/index.js.map +1 -1
- package/lib/intercept.js +23 -1
- package/lib/intercept.js.map +1 -1
- package/lib/merge-streams.js +29 -2
- package/lib/merge-streams.js.map +1 -1
- package/lib/plugin-adapter-core.js +69 -13
- package/lib/plugin-adapter-core.js.map +1 -1
- package/lib/plugin-bridge.js +179 -12
- package/lib/plugin-bridge.js.map +1 -1
- package/lib/plugin-route-intercept.js +631 -79
- package/lib/plugin-route-intercept.js.map +1 -1
- package/lib/plugin-tunnel.js +84 -12
- package/lib/plugin-tunnel.js.map +1 -1
- package/lib/relay-server.js +65 -47
- package/lib/relay-server.js.map +1 -1
- package/lib/types/adapters/dsh-better-sidebar/index.d.ts.map +1 -1
- package/lib/types/adapters/dsh-better-sidebar/surface.d.ts +23 -0
- package/lib/types/adapters/dsh-better-sidebar/surface.d.ts.map +1 -0
- package/lib/types/adapters/types.d.ts +14 -3
- package/lib/types/adapters/types.d.ts.map +1 -1
- package/lib/types/index.d.ts +2 -2
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/intercept.d.ts +19 -0
- package/lib/types/intercept.d.ts.map +1 -1
- package/lib/types/merge-streams.d.ts +12 -0
- package/lib/types/merge-streams.d.ts.map +1 -1
- package/lib/types/plugin-adapter-core.d.ts +24 -11
- package/lib/types/plugin-adapter-core.d.ts.map +1 -1
- package/lib/types/plugin-bridge.d.ts +93 -5
- package/lib/types/plugin-bridge.d.ts.map +1 -1
- package/lib/types/plugin-route-intercept.d.ts +52 -3
- package/lib/types/plugin-route-intercept.d.ts.map +1 -1
- package/lib/types/plugin-tunnel.d.ts +27 -3
- package/lib/types/plugin-tunnel.d.ts.map +1 -1
- package/lib/types/relay-server.d.ts +2 -25
- package/lib/types/relay-server.d.ts.map +1 -1
- package/lib/types/upload-limits.d.ts +67 -0
- package/lib/types/upload-limits.d.ts.map +1 -0
- package/lib/types/wizard/proxy-snippets.d.ts +4 -3
- package/lib/types/wizard/proxy-snippets.d.ts.map +1 -1
- package/lib/upload-limits.js +67 -0
- package/lib/upload-limits.js.map +1 -0
- package/lib/wizard/proxy-snippets.js +5 -4
- package/lib/wizard/proxy-snippets.js.map +1 -1
- package/package.json +2 -2
- package/scripts/check-upload-limits.mjs +167 -0
- package/src/adapters/dsh-better-sidebar/index.ts +21 -9
- package/src/adapters/dsh-better-sidebar/surface.ts +90 -0
- package/src/adapters/types.ts +14 -3
- package/src/client-data/wizard.ts +1 -1
- package/src/index.ts +99 -12
- package/src/intercept.ts +47 -1
- package/src/merge-streams.ts +41 -1
- package/src/plugin-adapter-core.ts +69 -14
- package/src/plugin-bridge.ts +228 -14
- package/src/plugin-route-intercept.ts +677 -82
- package/src/plugin-tunnel.ts +95 -13
- package/src/relay-server.ts +60 -48
- package/src/upload-limits.ts +69 -0
- package/src/wizard/proxy-snippets.ts +5 -4
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one home of every upload/body ceiling this plugin enforces (the
|
|
3
|
+
* upload unification, 2026-10-28). Three paths move bytes through the
|
|
4
|
+
* plugin, each with its own number, and the numbers used to live beside
|
|
5
|
+
* their enforcement sites — where they drifted: the docs still said
|
|
6
|
+
* 100 MiB, the nginx snippet capped at 128m under a 500 MiB relay, and
|
|
7
|
+
* the better-sidebar adapter hand-copied the relay budget (kept in step
|
|
8
|
+
* only by a paired test). Defining them here once makes each number
|
|
9
|
+
* greppable to exactly one place; scripts/check-upload-limits.mjs pins
|
|
10
|
+
* the rest — no second MiB-scale constant definition anywhere in src/,
|
|
11
|
+
* and the docs / nginx snippet stay in sync with MAX_UPLOAD_BYTES.
|
|
12
|
+
*
|
|
13
|
+
* Pure constants, ZERO imports (the virtual-id.ts precedent): the relay
|
|
14
|
+
* server, the phone-upload route, the sub-client's fetch-route intercept,
|
|
15
|
+
* the adapter core and the adapters themselves all import from here — an
|
|
16
|
+
* adapter may only lean on pure utility modules, so anything less pure
|
|
17
|
+
* would have kept the hand-copies alive.
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* Body ceiling for the binary upload channel alone (T51): a shared session's
|
|
21
|
+
* non-image file attachments. DSH's own upload route has NO byte cap — the
|
|
22
|
+
* host stores the stream verbatim (`saveFileStreamVerbatim` →
|
|
23
|
+
* `publishImmutableObjectStream`, RT dsh-attachment-local lib/index.js
|
|
24
|
+
* ~699-716; the 20 MiB `maxImageBytes` there covers INLINE images only), so
|
|
25
|
+
* there is no host number to mirror and this relay picks its own: 500 MiB
|
|
26
|
+
* per upload, checked first against `Content-Length` (refused before a byte
|
|
27
|
+
* moves) and then counted while the stream is pumped (a chunked body has no
|
|
28
|
+
* length header — the sub-client's undici forbids hand-set length headers,
|
|
29
|
+
* so the streaming count is the path its uploads normally take). Past the
|
|
30
|
+
* cap the forward is aborted, the rest of the request is drained and the
|
|
31
|
+
* answer is 413 `payload-too-large`.
|
|
32
|
+
*
|
|
33
|
+
* SLOW-LINK BOUND (T51-fix, for the docs): a single legitimate upload is
|
|
34
|
+
* also bounded in TIME. The sub-client's own round-trip budget now caps at
|
|
35
|
+
* 15 minutes (relay-client.ts, the T31-fix rule), but the HOST's HTTP server
|
|
36
|
+
* keeps Node's default 300 s request deadline — that is the binding limit:
|
|
37
|
+
* a 500 MiB body needs roughly 14 Mbps sustained end to end, and a slower
|
|
38
|
+
* link fails the call (the client-side refusal keeps the connection state)
|
|
39
|
+
* rather than hanging. Raising the cap past what that deadline can carry
|
|
40
|
+
* would only move the failure, so 500 MiB is the honest ceiling for the
|
|
41
|
+
* current transport.
|
|
42
|
+
*/
|
|
43
|
+
export const MAX_UPLOAD_BYTES = 500 * 1024 * 1024;
|
|
44
|
+
/**
|
|
45
|
+
* Body cap when the plugin row sets no `maxUploadBytes`. This governs ONLY
|
|
46
|
+
* the phone composer's attachment upload (`/_dsh/mobile-nav/upload`) — the
|
|
47
|
+
* remote relay channel reads {@link MAX_UPLOAD_BYTES} and must never be
|
|
48
|
+
* sized from this: `maxUploadBytes` is a user-facing knob for phone picks
|
|
49
|
+
* (default 20 MB), and wiring it into the relay would drop remote
|
|
50
|
+
* attachments from 500 MiB back down to it (a regression the README's
|
|
51
|
+
* 500 MiB promise forbids).
|
|
52
|
+
*/
|
|
53
|
+
export const DEFAULT_MAX_UPLOAD_BYTES = 20 * 1024 * 1024;
|
|
54
|
+
/**
|
|
55
|
+
* Body ceiling for the relay's invoke route alone: the prompt routes carry
|
|
56
|
+
* inline images in their content blocks (`session/prompt` / `subagents/prompt`
|
|
57
|
+
* take base64 `image` parts; DSH admits one image up to 20 MiB by default —
|
|
58
|
+
* `maxImageBytes`, dsh-attachment-local — which base64 inflates to ~27 MiB),
|
|
59
|
+
* so one full-size inline image plus its envelope needs 32 MiB. It is NOT
|
|
60
|
+
* sized for `fileUploads/upload`: the UI's real file uploads ride the HTTP
|
|
61
|
+
* route `api/session/uploadFileBinary` (T41b), not this JSON relay. The
|
|
62
|
+
* buffer only ever grows to the REAL body size; the cap decides
|
|
63
|
+
* accept/refuse, and a body that outgrows it is drained and answered 413
|
|
64
|
+
* like any other oversized read.
|
|
65
|
+
*/
|
|
66
|
+
export const MAX_INVOKE_BODY_BYTES = 32 * 1024 * 1024;
|
|
67
|
+
//# sourceMappingURL=upload-limits.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upload-limits.js","sourceRoot":"","sources":["../src/upload-limits.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,GAAG,GAAG,IAAI,GAAG,IAAI,CAAA;AAEjD;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAA;AAExD;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAA"}
|
|
@@ -32,9 +32,10 @@ function validatedParts(input) {
|
|
|
32
32
|
* nginx: a complete server block. The WebSocket upgrade map sits outside
|
|
33
33
|
* the server (http-level context) — the snippet says so in a comment, the
|
|
34
34
|
* user pastes it into the http/server scope their setup offers. Long
|
|
35
|
-
* idle tunnels need the 3600s read/send timeouts, uploads the
|
|
36
|
-
* client body cap
|
|
37
|
-
*
|
|
35
|
+
* idle tunnels need the 3600s read/send timeouts, uploads the 512m
|
|
36
|
+
* client body cap (nginx's `m` is MiB — 512m covers the relay's 500 MiB
|
|
37
|
+
* upload ceiling with headroom), and streaming the buffering switch — all
|
|
38
|
+
* three are the shapes DSH's relay traffic actually needs.
|
|
38
39
|
*/
|
|
39
40
|
export function nginxSnippet(input) {
|
|
40
41
|
const parts = validatedParts(input);
|
|
@@ -62,7 +63,7 @@ export function nginxSnippet(input) {
|
|
|
62
63
|
' proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;',
|
|
63
64
|
' proxy_set_header X-Forwarded-Proto $scheme;',
|
|
64
65
|
' proxy_buffering off;',
|
|
65
|
-
' client_max_body_size
|
|
66
|
+
' client_max_body_size 512m;',
|
|
66
67
|
' proxy_read_timeout 3600s;',
|
|
67
68
|
' proxy_send_timeout 3600s;',
|
|
68
69
|
' }',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"proxy-snippets.js","sourceRoot":"","sources":["../../src/wizard/proxy-snippets.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AAgBrC,mCAAmC;AACnC,SAAS,MAAM,CAAC,KAAc;IAC5B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI,KAAK,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAA;AACjH,CAAC;AAED,SAAS,cAAc,CAAC,KAAmB;IACzC,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAA;IACnC,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,YAAY,EAAE,EAAE,CAAA;IAC9E,MAAM,QAAQ,GAAG,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,CAAA;IAC3C,IAAI,QAAQ,KAAK,SAAS;QAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,mBAAmB,EAAE,EAAE,CAAA;IACvF,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,CAAA;IACvC,IAAI,IAAI,KAAK,SAAS;QAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,mBAAmB,EAAE,EAAE,CAAA;IACnF,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAA;AACnC,CAAC;AAED
|
|
1
|
+
{"version":3,"file":"proxy-snippets.js","sourceRoot":"","sources":["../../src/wizard/proxy-snippets.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-zen-remote",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.4",
|
|
4
4
|
"description": "一个插件、两种角色:主服务端把 DeepSeek Harness 变成手机能安全访问的 PWA(移动端界面、配对码网关、Web Push),并把选定会话共享给已配对的桌面端;子客户端在另一台 DSH 桌面端的侧边栏里远程使用这些会话。One DSH plugin, two roles: the host turns DeepSeek Harness into a phone-reachable PWA (mobile UI, pairing-code gateway behind your own reverse proxy, Web Push) and shares chosen sessions to paired desktops; the client uses those sessions remotely from another DSH desktop app's sidebar.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -160,7 +160,7 @@
|
|
|
160
160
|
"scripts": {
|
|
161
161
|
"build": "tsc -p tsconfig.json && tsc -p tsconfig.client.json && node scripts/build-client.mjs",
|
|
162
162
|
"verify": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.client.json --noEmit",
|
|
163
|
-
"test": "node --test \"test/*.test.cjs\" && node scripts/check-sunk-viewport.mjs && node scripts/check-keyboard-avoid.mjs && node scripts/check-attach-upload.mjs && node scripts/check-share-image.mjs && node scripts/check-upload-endpoint.mjs && node scripts/check-host-shadow.mjs && node scripts/check-compat-store.mjs && node scripts/check-settings-form.mjs && node scripts/check-shares.mjs && node scripts/check-plugin-bridge-ui.mjs && node scripts/check-remote-drop.mjs && node scripts/check-client-externals.mjs && node scripts/check-info-sheet-stacking.mjs && node scripts/check-wizard-ui.mjs && node scripts/sync-doc-version.mjs --check",
|
|
163
|
+
"test": "node --test \"test/*.test.cjs\" && node scripts/check-sunk-viewport.mjs && node scripts/check-keyboard-avoid.mjs && node scripts/check-attach-upload.mjs && node scripts/check-share-image.mjs && node scripts/check-upload-endpoint.mjs && node scripts/check-host-shadow.mjs && node scripts/check-compat-store.mjs && node scripts/check-settings-form.mjs && node scripts/check-shares.mjs && node scripts/check-plugin-bridge-ui.mjs && node scripts/check-remote-drop.mjs && node scripts/check-client-externals.mjs && node scripts/check-info-sheet-stacking.mjs && node scripts/check-wizard-ui.mjs && node scripts/check-upload-limits.mjs && node scripts/sync-doc-version.mjs --check",
|
|
164
164
|
"version": "node scripts/sync-doc-version.mjs && git add README.md README.zh-CN.md"
|
|
165
165
|
}
|
|
166
166
|
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The upload-limits drift check (the upload unification, 2026-10-28).
|
|
3
|
+
* The plugin's upload ceilings live in exactly one place —
|
|
4
|
+
* src/upload-limits.ts — and this script keeps the rest of the repo in
|
|
5
|
+
* step with it, the way sync-doc-version.mjs keeps the READMEs in step
|
|
6
|
+
* with package.json: every target below is REQUIRED, and a missing
|
|
7
|
+
* marker exits non-zero rather than quietly passing, because a silent
|
|
8
|
+
* no-op is exactly the failure each pin exists to prevent.
|
|
9
|
+
*
|
|
10
|
+
* a. Both languages' docs state the `/v1/upload` route's cap, and every
|
|
11
|
+
* MiB number on that table row equals MAX_UPLOAD_BYTES (the docs
|
|
12
|
+
* drifted to "100 MiB" once already).
|
|
13
|
+
* b. Every nginx `client_max_body_size <N>m;` the repo hands out — the
|
|
14
|
+
* wizard's generated snippet and the docs' example block — is at
|
|
15
|
+
* least MAX_UPLOAD_BYTES in MiB (nginx's `m` is MiB; the snippet sat
|
|
16
|
+
* at 128m under a 500 MiB relay once already). The Chinese doc must
|
|
17
|
+
* carry the example; the English doc is checked wherever it appears.
|
|
18
|
+
* c. A static scan: no upload-limit VALUE may reappear as a constant
|
|
19
|
+
* definition anywhere else in the repo (the better-sidebar adapter
|
|
20
|
+
* hand-copied the relay budget once already, kept honest only by a
|
|
21
|
+
* paired test). The scan matches `const X = <N> * 1024 * 1024`
|
|
22
|
+
* definitions for the three shipped values, so unrelated MiB
|
|
23
|
+
* constants (the 64 MiB bridge body cap, the 512 MiB download cap,
|
|
24
|
+
* buffer sizes) stay untouched — and so do test-side literal
|
|
25
|
+
* arguments, which are assertions, not definitions.
|
|
26
|
+
*
|
|
27
|
+
* Imports the BUILT lib/upload-limits.js — run after `pnpm build` (the
|
|
28
|
+
* test script already is). The scan reads the values at run time, so
|
|
29
|
+
* raising a limit in upload-limits.ts moves every pin with it.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import { readdirSync, readFileSync } from 'node:fs'
|
|
33
|
+
import { dirname, join, relative } from 'node:path'
|
|
34
|
+
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
35
|
+
|
|
36
|
+
const root = join(dirname(fileURLToPath(import.meta.url)), '..')
|
|
37
|
+
const limits = await import(pathToFileURL(join(root, 'lib', 'upload-limits.js')).href)
|
|
38
|
+
const capMib = limits.MAX_UPLOAD_BYTES / (1024 * 1024)
|
|
39
|
+
|
|
40
|
+
const failures = []
|
|
41
|
+
|
|
42
|
+
// --- a. the docs' `/v1/upload` row states the relay cap ---------------------
|
|
43
|
+
|
|
44
|
+
const DOCS = ['docs/remote-access.md', 'docs/remote-access.en.md']
|
|
45
|
+
|
|
46
|
+
for (const file of DOCS) {
|
|
47
|
+
const row = readFileSync(join(root, file), 'utf8')
|
|
48
|
+
.split('\n')
|
|
49
|
+
.find((line) => /^\|\s*`\/v1\/upload`\s*\|/.test(line))
|
|
50
|
+
if (row === undefined) {
|
|
51
|
+
failures.push(`${file}: no \`/v1/upload\` table row found — the docs moved, update this check`)
|
|
52
|
+
continue
|
|
53
|
+
}
|
|
54
|
+
const mibs = [...row.matchAll(/(\d+(?:\.\d+)?)\s*MiB/g)].map((match) => Number(match[1]))
|
|
55
|
+
if (mibs.length === 0) {
|
|
56
|
+
failures.push(`${file}: the \`/v1/upload\` row states no MiB cap — the docs moved, update this check`)
|
|
57
|
+
continue
|
|
58
|
+
}
|
|
59
|
+
for (const mib of mibs) {
|
|
60
|
+
if (mib !== capMib) failures.push(`${file}: the \`/v1/upload\` row says ${mib} MiB but MAX_UPLOAD_BYTES is ${capMib} MiB`)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// --- a2. the READMEs' marketing claim ("up to 500 MiB file uploads") rides the
|
|
65
|
+
// same number (no `/v1/upload` table there — the mention just has to exist
|
|
66
|
+
// and match, so raising the cap drags the READMEs along) ---------------------
|
|
67
|
+
|
|
68
|
+
const READMES = ['README.md', 'README.zh-CN.md']
|
|
69
|
+
|
|
70
|
+
for (const file of READMES) {
|
|
71
|
+
const text = readFileSync(join(root, file), 'utf8')
|
|
72
|
+
const mibs = [...text.matchAll(/(\d+)\s*MiB/g)].map((match) => Number(match[1]))
|
|
73
|
+
if (!mibs.includes(capMib)) {
|
|
74
|
+
failures.push(`${file}: no "${capMib} MiB" mention found — the upload-cap claim drifted from MAX_UPLOAD_BYTES`)
|
|
75
|
+
}
|
|
76
|
+
for (const mib of new Set(mibs)) {
|
|
77
|
+
if (mib !== capMib && mib > 100) failures.push(`${file}: mentions ${mib} MiB — a stale upload-cap claim? (only ${capMib} is the relay number)`)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// --- b. every nginx body cap we hand out covers the relay -------------------
|
|
82
|
+
|
|
83
|
+
const NGINX_CAP = /client_max_body_size\s+(\d+)m\s*;/g
|
|
84
|
+
|
|
85
|
+
const nginxCapsOf = (text) => [...text.matchAll(NGINX_CAP)].map((match) => Number(match[1]))
|
|
86
|
+
|
|
87
|
+
const { nginxSnippet } = await import(pathToFileURL(join(root, 'lib', 'wizard', 'proxy-snippets.js')).href)
|
|
88
|
+
const snippet = nginxSnippet({ domain: 'dsh.example.com', upstreamHost: '127.0.0.1', upstreamPort: 4088 })
|
|
89
|
+
if (!snippet.ok) {
|
|
90
|
+
failures.push('wizard nginxSnippet refused a plainly valid input — the generator broke')
|
|
91
|
+
} else {
|
|
92
|
+
const caps = nginxCapsOf(snippet.text)
|
|
93
|
+
if (caps.length === 0) {
|
|
94
|
+
failures.push('wizard nginx snippet: no `client_max_body_size` found — the snippet moved, update this check')
|
|
95
|
+
}
|
|
96
|
+
for (const cap of caps) {
|
|
97
|
+
if (cap < capMib) failures.push(`wizard nginx snippet: client_max_body_size ${cap}m is below the relay cap (${capMib}m)`)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
for (const file of DOCS) {
|
|
102
|
+
const caps = nginxCapsOf(readFileSync(join(root, file), 'utf8'))
|
|
103
|
+
if (file === 'docs/remote-access.md' && caps.length === 0) {
|
|
104
|
+
failures.push(`${file}: no nginx \`client_max_body_size\` example found — the docs moved, update this check`)
|
|
105
|
+
}
|
|
106
|
+
for (const cap of caps) {
|
|
107
|
+
if (cap < capMib) failures.push(`${file}: client_max_body_size ${cap}m is below the relay cap (${capMib}m)`)
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// --- c. the one-definition scan ----------------------------------------------
|
|
112
|
+
|
|
113
|
+
/** The value each upload-limit constant ships, by name — the scan keys on
|
|
114
|
+
* the VALUES so raising a limit in upload-limits.ts moves the pin too. */
|
|
115
|
+
const LIMIT_VALUES = Object.entries({
|
|
116
|
+
MAX_UPLOAD_BYTES: limits.MAX_UPLOAD_BYTES,
|
|
117
|
+
DEFAULT_MAX_UPLOAD_BYTES: limits.DEFAULT_MAX_UPLOAD_BYTES,
|
|
118
|
+
MAX_INVOKE_BODY_BYTES: limits.MAX_INVOKE_BODY_BYTES,
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
const GUARDED = 'src/upload-limits.ts'
|
|
122
|
+
const SKIP_DIRS = new Set(['node_modules', 'lib', 'dist'])
|
|
123
|
+
const SCAN_EXT = /\.(ts|mts|cts|js|mjs|cjs)$/
|
|
124
|
+
|
|
125
|
+
const files = []
|
|
126
|
+
const walk = (dir) => {
|
|
127
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
128
|
+
const full = join(dir, entry.name)
|
|
129
|
+
if (entry.isDirectory()) {
|
|
130
|
+
if (!SKIP_DIRS.has(entry.name)) walk(full)
|
|
131
|
+
} else if (SCAN_EXT.test(entry.name)) {
|
|
132
|
+
files.push(full)
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
walk(join(root, 'src'))
|
|
137
|
+
walk(join(root, 'test'))
|
|
138
|
+
walk(join(root, 'scripts'))
|
|
139
|
+
walk(join(root, 'pwa'))
|
|
140
|
+
for (const entry of readdirSync(root, { withFileTypes: true })) {
|
|
141
|
+
if (entry.isFile() && SCAN_EXT.test(entry.name)) files.push(join(root, entry.name))
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
for (const full of files) {
|
|
145
|
+
const rel = relative(root, full).split('\\').join('/')
|
|
146
|
+
if (rel === GUARDED) continue
|
|
147
|
+
const text = readFileSync(full, 'utf8')
|
|
148
|
+
for (const [name, bytes] of LIMIT_VALUES) {
|
|
149
|
+
const mib = bytes / (1024 * 1024)
|
|
150
|
+
const defines = new RegExp(
|
|
151
|
+
`\\b(?:const|let|var)\\s+[A-Za-z_$][\\w$]*\\s*(?::[^=\\n]+)?=\\s*${mib}\\s*\\*\\s*1024\\s*\\*\\s*1024\\b`,
|
|
152
|
+
)
|
|
153
|
+
if (defines.test(text)) {
|
|
154
|
+
failures.push(`${rel}: defines a ${mib} MiB upload-limit literal — import ${name} from src/upload-limits.ts instead`)
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// -----------------------------------------------------------------------------
|
|
160
|
+
|
|
161
|
+
if (failures.length > 0) {
|
|
162
|
+
console.error(`check-upload-limits: drift against src/upload-limits.ts:\n ${failures.join('\n ')}`)
|
|
163
|
+
process.exit(1)
|
|
164
|
+
}
|
|
165
|
+
console.log(
|
|
166
|
+
`check-upload-limits: docs state ${capMib} MiB, nginx covers it, one definition (src/upload-limits.ts) — ok`,
|
|
167
|
+
)
|
|
@@ -31,15 +31,9 @@
|
|
|
31
31
|
|
|
32
32
|
import type { BridgeAdapter } from '../types.js'
|
|
33
33
|
import { isVirtual } from '../../virtual-id.js'
|
|
34
|
+
import { MAX_UPLOAD_BYTES } from '../../upload-limits.js'
|
|
34
35
|
import { SIDEBAR_METHODS, sidebarMethodNameOf } from './methods.js'
|
|
35
36
|
|
|
36
|
-
/** The relay's upload budget (`MAX_UPLOAD_BYTES` in lib/relay-server.js).
|
|
37
|
-
* Inlined rather than imported — an adapter may only lean on pure utility
|
|
38
|
-
* modules — and pinned to the original by a test in
|
|
39
|
-
* test/adapter-better-sidebar.test.cjs, which fails the pair apart when
|
|
40
|
-
* either side moves. */
|
|
41
|
-
const RELAY_UPLOAD_CAP_BYTES = 500 * 1024 * 1024
|
|
42
|
-
|
|
43
37
|
/** better-sidebar's own JSON body bound (lib/index.js:863
|
|
44
38
|
* `MAX_BODY_BYTES = 1 << 20`) — the adapter forwards at most what the
|
|
45
39
|
* backend would accept anyway. */
|
|
@@ -74,6 +68,24 @@ export const dshBetterSidebarAdapter: BridgeAdapter = {
|
|
|
74
68
|
// and only then a client `cwd` hint — so the server hook's cwd stripping
|
|
75
69
|
// below stays correct (if anything, the client hint matters less).
|
|
76
70
|
versions: ['0.25.0', '0.26.0', '0.26.1', '0.28.0-alpha.0', '0.28.0', '0.28.1', '0.28.2'],
|
|
71
|
+
// PB-19 (ADR-0003): the wire-surface whitelist. Keys are digest16 over the
|
|
72
|
+
// PB-18 extractor's reduction (`surface.ts` beside this file) of the npm
|
|
73
|
+
// builds' lib/index.js, computed off /tmp/bs-versions/{version}/lib/index.js:
|
|
74
|
+
// d383e5af5b95e456 — the 0.25.0 generation
|
|
75
|
+
// 96465143373a31a7 — the 0.26.0 generation (0.26.1 hashes identically)
|
|
76
|
+
// 6ed29a896101e681 — the 0.28.0 generation (0.28.0-alpha.0 identically)
|
|
77
|
+
// 65a0acaeb56f87d9 — the 0.28.1 generation (0.28.2 identically)
|
|
78
|
+
// An installed version outside `versions` still registers when its build
|
|
79
|
+
// hashes into one of these (a wire-identical patch release bridges without
|
|
80
|
+
// a zen-remote release); anything else fails closed to the unlisted-version
|
|
81
|
+
// refusal — including an upstream edit that touches only a path-mentioning
|
|
82
|
+
// comment, which the extractor counts. NOT part of the route-table digest.
|
|
83
|
+
surfaces: {
|
|
84
|
+
d383e5af5b95e456: 'the 0.25.0 generation',
|
|
85
|
+
'96465143373a31a7': 'the 0.26.0 generation (0.26.1 identical)',
|
|
86
|
+
'6ed29a896101e681': 'the 0.28.0 generation (0.28.0-alpha.0 identical)',
|
|
87
|
+
'65a0acaeb56f87d9': 'the 0.28.1 generation (0.28.2 identical)',
|
|
88
|
+
},
|
|
77
89
|
routes: [
|
|
78
90
|
{
|
|
79
91
|
// The JSON API: the method name rides the path tail, the session
|
|
@@ -110,13 +122,13 @@ export const dshBetterSidebarAdapter: BridgeAdapter = {
|
|
|
110
122
|
{ path: '/sidebar/archive', match: 'exact', methods: ['GET'], locator: { in: 'query', name: 'sessionId' } },
|
|
111
123
|
// The raw upload (lib/index.js:6905-6948): binary bytes, capped by the
|
|
112
124
|
// relay's own upload budget.
|
|
113
|
-
{ path: '/sidebar/upload', match: 'exact', methods: ['POST'], maxBody:
|
|
125
|
+
{ path: '/sidebar/upload', match: 'exact', methods: ['POST'], maxBody: MAX_UPLOAD_BYTES, locator: { in: 'query', name: 'sessionId' } },
|
|
114
126
|
// The raw text write (0.26.0, lib/index.js:7875-7919): the document IS
|
|
115
127
|
// the POST body (text/plain — no JSON escaping layer), the session id,
|
|
116
128
|
// path and a client cwd ride the QUERY. The plugin caps the body at
|
|
117
129
|
// WRITE_BODY_GUARD_BYTES = 8 MiB (lib/index.js:6854); the adapter takes
|
|
118
130
|
// 10 MiB — headroom over the plugin's own guard, under the relay's
|
|
119
|
-
//
|
|
131
|
+
// 500 MiB upload budget.
|
|
120
132
|
{ path: '/sidebar/write', match: 'exact', methods: ['POST'], maxBody: 10 * 1024 * 1024, locator: { in: 'query', name: 'sessionId' } },
|
|
121
133
|
// The HTML preview (lib/index.js:7026+): the session id is the FIRST
|
|
122
134
|
// path segment after the prefix; the rest of the segments are the
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The better-sidebar wire-surface extractor (PB-18, ADR-0003 local draft).
|
|
3
|
+
*
|
|
4
|
+
* The adapter's `versions` list pins EXACT upstream versions, so a wire-identical
|
|
5
|
+
* patch release (0.26.1, 0.28.2 — both shipped with the routes/methods/WebSockets
|
|
6
|
+
* byte-identical to their predecessor) still refuses to bridge until zen-remote
|
|
7
|
+
* itself releases. The `surfaces` table (PB-19) closes that gap: the wiring reads
|
|
8
|
+
* the INSTALLED plugin's `lib/index.js`, this pure function reduces it to the
|
|
9
|
+
* set of `/sidebar/*` routes and `ns.method` dispatch keys it serves, and a
|
|
10
|
+
* digest over that reduction whitelists every version whose WIRE SURFACE matches
|
|
11
|
+
* a blessed one — regardless of the version string.
|
|
12
|
+
*
|
|
13
|
+
* What the extraction is NOT: a general static analyzer, nor a pure wire
|
|
14
|
+
* oracle. It knows better-sidebar's two shapes — the `"x.y": async (…)`
|
|
15
|
+
* dispatch-table entries inside its bundled lib/index.js (the same recognition
|
|
16
|
+
* test/adapter-better-sidebar.test.cjs layer 1 has pinned since PB-12), and
|
|
17
|
+
* every `/sidebar/…` string literal the bundle carries. The npm build KEEPS
|
|
18
|
+
* comments (0.28.2 ships ~470 `/**` blocks, some quoting path strings), and the
|
|
19
|
+
* routes set below therefore collects registered routes, prefix checks and
|
|
20
|
+
* comment mentions alike — the digest is stable exactly as long as ALL those
|
|
21
|
+
* literals are (PB-18 review, verified with comment add/remove probes). That
|
|
22
|
+
* is STRICTER than "wire unchanged": an upstream release that only edits a
|
|
23
|
+
* path-mentioning comment changes the digest and fails closed to the
|
|
24
|
+
* unlisted-version behavior of today — never an unsafe forward, just a smaller
|
|
25
|
+
* auto-bridge hit rate than a pure wire comparison would give. A bundler that
|
|
26
|
+
* stops emitting the literals at all fails the same closed direction.
|
|
27
|
+
*
|
|
28
|
+
* Pure by the adapters rule: node:crypto only, no fs, no DSH imports.
|
|
29
|
+
*/
|
|
30
|
+
import { createHash } from 'node:crypto'
|
|
31
|
+
|
|
32
|
+
/** The recognized wire surface: sorted route paths + sorted dispatch keys. */
|
|
33
|
+
export interface BetterSidebarSurface {
|
|
34
|
+
routes: string[]
|
|
35
|
+
methods: string[]
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** The dispatcher namespaces the plugin has used across 0.25.0–0.28.2. The
|
|
39
|
+
* same object-key regex also matches unrelated tables in the bundle, so only
|
|
40
|
+
* these prefixes count (the PB-12 test's filter, verbatim). */
|
|
41
|
+
const NAMESPACE_PATTERN = /^(session|fs|git|changes|subagents|workflows|teams|settings|open|archive|sidechat)\./u
|
|
42
|
+
|
|
43
|
+
/** A healthy build of any supported generation yields at least this many
|
|
44
|
+
* dispatch keys (0.25.0 already serves 45+). Fewer means the extractor lost
|
|
45
|
+
* the table — the wiring logs that distinctly from "surface changed". */
|
|
46
|
+
export const MIN_SURFACE_METHODS = 45
|
|
47
|
+
|
|
48
|
+
/** Every `/sidebar/…` literal in the bundle — registered routes (the upload
|
|
49
|
+
* prefix, the deeper media route) plus prefix checks and comment mentions.
|
|
50
|
+
* Lowercase only on purpose: the routes themselves are lowercase, and a
|
|
51
|
+
* case-insensitive scan would swallow uppercase prose noise for no gain
|
|
52
|
+
* (PB-18 review nit — the `i` flag is deliberately absent). */
|
|
53
|
+
const ROUTE_PATTERN = /\/sidebar\/[a-z0-9./:-]+/gu
|
|
54
|
+
|
|
55
|
+
/** `"ns.method":` dispatch-table entries (`async` optional after the colon). */
|
|
56
|
+
const METHOD_PATTERN = /"([a-zA-Z]+\.[a-zA-Z-]+)":\s*(?:async\s*)?\(/gu
|
|
57
|
+
|
|
58
|
+
/** Reduce one build's lib/index.js source to its surface fingerprint.
|
|
59
|
+
* Deterministic: same source in, same sorted arrays out — the digest below is
|
|
60
|
+
* a pure function of the surface. Empirically, wire-identical patch releases
|
|
61
|
+
* also hash identically (0.26.1's perf work, 0.28.2's cwd fix — both kept
|
|
62
|
+
* every extracted literal); see the header for why that is a fact about those
|
|
63
|
+
* releases, not a structural guarantee. */
|
|
64
|
+
export function extractBetterSidebarSurface(source: string): BetterSidebarSurface {
|
|
65
|
+
const routes = new Set<string>()
|
|
66
|
+
for (const match of source.matchAll(ROUTE_PATTERN)) {
|
|
67
|
+
// A literal matched through a sentence like "/sidebar/api/foo." would drag
|
|
68
|
+
// a sentence-period into the set — trim one trailing dot so prose mentions
|
|
69
|
+
// normalize to the same literal the code registers.
|
|
70
|
+
routes.add(match[0].replace(/\.$/u, ''))
|
|
71
|
+
}
|
|
72
|
+
const methods = new Set<string>()
|
|
73
|
+
for (const match of source.matchAll(METHOD_PATTERN)) {
|
|
74
|
+
if (NAMESPACE_PATTERN.test(match[1])) methods.add(match[1])
|
|
75
|
+
}
|
|
76
|
+
return { routes: [...routes].sort(), methods: [...methods].sort() }
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Whether an extraction looks like a real better-sidebar build at all (the
|
|
80
|
+
* "extractor found nothing" tell — logged apart from digest mismatches). */
|
|
81
|
+
export function surfaceLooksHealthy(surface: BetterSidebarSurface): boolean {
|
|
82
|
+
return surface.methods.length >= MIN_SURFACE_METHODS && surface.routes.length > 0
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** The 16-hex digest a `surfaces` table key carries: sha256 over the canonical
|
|
86
|
+
* JSON of the surface. Same surface across versions → same digest → bridged. */
|
|
87
|
+
export function betterSidebarSurfaceDigest(surface: BetterSidebarSurface): string {
|
|
88
|
+
const canonical = JSON.stringify({ routes: surface.routes, methods: surface.methods })
|
|
89
|
+
return createHash('sha256').update(canonical, 'utf8').digest('hex').slice(0, 16)
|
|
90
|
+
}
|
package/src/adapters/types.ts
CHANGED
|
@@ -21,15 +21,26 @@ export type { AdapterRoute, AdapterRequestContext, AdapterResponseContext, Adapt
|
|
|
21
21
|
/** One pluggable adapter. `versions` lists the EXACT plugin versions this
|
|
22
22
|
* adapter was written against — the installed version must be among them
|
|
23
23
|
* or the adapter does not register at all (a best-guess rewrite against a
|
|
24
|
-
* moving schema is how ids get corrupted).
|
|
25
|
-
*
|
|
24
|
+
* moving schema is how ids get corrupted). One exception (PB-19): a version
|
|
25
|
+
* outside the list still registers when the installed build's WIRE-SURFACE
|
|
26
|
+
* digest appears in {@link BridgeAdapter.surfaces} — a wire-identical patch
|
|
27
|
+
* release bridges without a zen-remote release. A plugin that registers its
|
|
28
|
+
* OWN cooperative declaration always wins; the adapter yields. */
|
|
26
29
|
export interface BridgeAdapter {
|
|
27
30
|
/** The target plugin's npm package name. */
|
|
28
31
|
plugin: string
|
|
29
32
|
/** The checked exact versions; an installed version outside this list
|
|
30
|
-
* means "not adapted"
|
|
33
|
+
* means "not adapted" — unless its wire surface is whitelisted in
|
|
34
|
+
* {@link BridgeAdapter.surfaces}. */
|
|
31
35
|
versions: readonly string[]
|
|
32
36
|
routes: readonly AdapterRoute[]
|
|
37
|
+
/** The wire-surface whitelist (PB-19, ADR-0003): digest16 of the
|
|
38
|
+
* adapter's surface-extractor reduction of the installed `lib/index.js`
|
|
39
|
+
* (routes + dispatch keys) → a remark naming the generation that produced
|
|
40
|
+
* it. Consulted ONLY when the installed version misses `versions`; never
|
|
41
|
+
* part of the route-table digest — this is an admission credential, not a
|
|
42
|
+
* wire contract. */
|
|
43
|
+
surfaces?: Readonly<Record<string, string>>
|
|
33
44
|
/** The sub-client half: rewrite or refuse on the way OUT to the relay,
|
|
34
45
|
* and map the answer's real session ids back to virtual form. */
|
|
35
46
|
client?: {
|
|
@@ -401,7 +401,7 @@ export function nginxSnippetText(input: SnippetInput): string | null {
|
|
|
401
401
|
' proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;',
|
|
402
402
|
' proxy_set_header X-Forwarded-Proto $scheme;',
|
|
403
403
|
' proxy_buffering off;',
|
|
404
|
-
' client_max_body_size
|
|
404
|
+
' client_max_body_size 512m;',
|
|
405
405
|
' proxy_read_timeout 3600s;',
|
|
406
406
|
' proxy_send_timeout 3600s;',
|
|
407
407
|
' }',
|