@goodandready/dsh-usage-guard 0.1.16 → 0.1.17
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/lib/client.js +14 -4
- package/lib/index.js +49 -35
- package/lib/telemetry.js +5 -16
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -52,6 +52,7 @@ window.__ModuleLoader__.load({
|
|
|
52
52
|
updater_up_to_date: 'Plugin is up to date',
|
|
53
53
|
updater_failed: 'Update check failed',
|
|
54
54
|
updater_success: 'Update successful. Please reload page.',
|
|
55
|
+
request_timeout: 'Request timed out (15s)',
|
|
55
56
|
};
|
|
56
57
|
|
|
57
58
|
const zh = {
|
|
@@ -97,6 +98,7 @@ window.__ModuleLoader__.load({
|
|
|
97
98
|
updater_up_to_date: '已是最新版本',
|
|
98
99
|
updater_failed: '检查更新失败',
|
|
99
100
|
updater_success: '更新成功,请刷新页面。',
|
|
101
|
+
request_timeout: '请求超时(15秒)',
|
|
100
102
|
};
|
|
101
103
|
|
|
102
104
|
function getActiveLocale(c) {
|
|
@@ -248,16 +250,18 @@ window.__ModuleLoader__.load({
|
|
|
248
250
|
const res = await fetch('/api/dsh-usage-guard/telemetry', {
|
|
249
251
|
headers: { accept: 'application/json' },
|
|
250
252
|
cache: 'no-store',
|
|
253
|
+
signal: AbortSignal.timeout(15000),
|
|
251
254
|
});
|
|
252
255
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
253
256
|
const data = await res.json();
|
|
254
257
|
setTelemetry(data);
|
|
255
258
|
} catch (e) {
|
|
256
|
-
|
|
259
|
+
const isTimeout = e?.name === 'TimeoutError' || e?.name === 'AbortError';
|
|
260
|
+
setErr(isTimeout ? t('request_timeout') : (e?.message || String(e)));
|
|
257
261
|
} finally {
|
|
258
262
|
setLoading(false);
|
|
259
263
|
}
|
|
260
|
-
}, []);
|
|
264
|
+
}, [t]);
|
|
261
265
|
|
|
262
266
|
React.useEffect(() => {
|
|
263
267
|
fetchTelemetry();
|
|
@@ -341,12 +345,15 @@ window.__ModuleLoader__.load({
|
|
|
341
345
|
const res = await fetch('/api/dsh-usage-guard/update', {
|
|
342
346
|
headers: { accept: 'application/json' },
|
|
343
347
|
cache: 'no-store',
|
|
348
|
+
signal: AbortSignal.timeout(15000),
|
|
344
349
|
});
|
|
345
350
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
346
351
|
const data = await res.json();
|
|
347
352
|
setStatus(data);
|
|
348
353
|
} catch (e) {
|
|
349
|
-
|
|
354
|
+
const isTimeout = e?.name === 'TimeoutError' || e?.name === 'AbortError';
|
|
355
|
+
const msg = isTimeout ? t('request_timeout') : (e?.message || String(e));
|
|
356
|
+
setFeedback({ text: `${t('updater_failed')}: ${msg}`, ok: false });
|
|
350
357
|
} finally {
|
|
351
358
|
setLoading(false);
|
|
352
359
|
}
|
|
@@ -362,13 +369,16 @@ window.__ModuleLoader__.load({
|
|
|
362
369
|
'x-dsh-plugin-update': '1',
|
|
363
370
|
'content-type': 'application/json',
|
|
364
371
|
},
|
|
372
|
+
signal: AbortSignal.timeout(15000),
|
|
365
373
|
});
|
|
366
374
|
const data = await res.json();
|
|
367
375
|
if (!res.ok) throw new Error(data?.error || `HTTP ${res.status}`);
|
|
368
376
|
setStatus(data);
|
|
369
377
|
setFeedback({ text: t('updater_success'), ok: true });
|
|
370
378
|
} catch (e) {
|
|
371
|
-
|
|
379
|
+
const isTimeout = e?.name === 'TimeoutError' || e?.name === 'AbortError';
|
|
380
|
+
const msg = isTimeout ? t('request_timeout') : (e?.message || String(e));
|
|
381
|
+
setFeedback({ text: msg, ok: false });
|
|
372
382
|
} finally {
|
|
373
383
|
setUpdating(false);
|
|
374
384
|
}
|
package/lib/index.js
CHANGED
|
@@ -25,69 +25,83 @@ export const Config = z.object({
|
|
|
25
25
|
repair: z
|
|
26
26
|
.boolean()
|
|
27
27
|
.default(true)
|
|
28
|
+
.volatile()
|
|
28
29
|
.description('Replace a missing or non-numeric token counter with zero before the harness adds it up. '
|
|
29
30
|
+ 'Off means reporting only: the counters reach the fold as they came, and a broken sample keeps the '
|
|
30
31
|
+ 'session history unreadable.'),
|
|
31
32
|
report: z
|
|
32
33
|
.boolean()
|
|
33
34
|
.default(true)
|
|
35
|
+
.volatile()
|
|
34
36
|
.description('Log a line naming the turn, the step and the raw sample whenever a damaged one arrives. '
|
|
35
37
|
+ 'Each turn, step and field set is reported once, not once per replay.'),
|
|
36
38
|
maxStepTokens: z
|
|
37
39
|
.natural()
|
|
38
40
|
.default(0)
|
|
41
|
+
.volatile()
|
|
39
42
|
.description('Maximum allowed token count for a single turn step. Spikes exceeding this ceiling are clamped to prevent integer overflow and runaway stats. Set to 0 to disable.'),
|
|
40
43
|
})
|
|
41
44
|
|
|
45
|
+
export function plainConfig(val) {
|
|
46
|
+
if (!val || typeof val !== 'object') return val
|
|
47
|
+
if (typeof val.get === 'function') return plainConfig(val.get())
|
|
48
|
+
const out = Array.isArray(val) ? [] : {}
|
|
49
|
+
for (const [k, v] of Object.entries(val)) out[k] = plainConfig(v)
|
|
50
|
+
return out
|
|
51
|
+
}
|
|
52
|
+
|
|
42
53
|
export function apply(ctx, config) {
|
|
43
54
|
let live
|
|
44
55
|
try {
|
|
45
|
-
|
|
56
|
+
const plain = plainConfig(config ?? {})
|
|
57
|
+
live = plainConfig(Config(plain)) ?? plain
|
|
46
58
|
} catch (_) {
|
|
47
59
|
live = Config({}) ?? { repair: true, report: true, maxStepTokens: 0 }
|
|
48
60
|
}
|
|
49
61
|
|
|
50
|
-
|
|
62
|
+
let settingsSvc = null
|
|
63
|
+
const readLive = () => {
|
|
51
64
|
try {
|
|
52
|
-
const
|
|
53
|
-
if (
|
|
54
|
-
|
|
55
|
-
if (typeof sctx.logger?.warn === 'function') sctx.logger.warn(msg)
|
|
56
|
-
else if (typeof ctx.logger?.warn === 'function') ctx.logger.warn(msg)
|
|
57
|
-
return
|
|
65
|
+
const row = settingsSvc?.describe?.()?.find?.((r) => r?.ns === NS)
|
|
66
|
+
if (row?.value) {
|
|
67
|
+
return plainConfig(Config(plainConfig(row.value)))
|
|
58
68
|
}
|
|
59
|
-
|
|
60
|
-
if (typeof
|
|
61
|
-
|
|
62
|
-
const fresh = scope.get()
|
|
63
|
-
if (fresh && typeof fresh === 'object') live = fresh
|
|
64
|
-
} catch (readErr) {
|
|
65
|
-
const msg = `[dsh-usage-guard] failed to read current settings: ${readErr?.message || readErr}; running with initial config`
|
|
66
|
-
if (typeof sctx.logger?.warn === 'function') sctx.logger.warn(msg)
|
|
67
|
-
else if (typeof ctx.logger?.warn === 'function') ctx.logger.warn(msg)
|
|
68
|
-
}
|
|
69
|
-
} else {
|
|
70
|
-
const msg = `[dsh-usage-guard] settings scope has no get() method; running with default config`
|
|
71
|
-
if (typeof sctx.logger?.warn === 'function') sctx.logger.warn(msg)
|
|
72
|
-
else if (typeof ctx.logger?.warn === 'function') ctx.logger.warn(msg)
|
|
69
|
+
} catch (describeErr) {
|
|
70
|
+
if (typeof ctx.logger?.debug === 'function') {
|
|
71
|
+
ctx.logger.debug('[dsh-usage-guard] failed to read settings via describe():', describeErr)
|
|
73
72
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
const msg = `[dsh-usage-guard] settings scope has no watch() method; running with default config without live update subscription`
|
|
81
|
-
if (typeof sctx.logger?.warn === 'function') sctx.logger.warn(msg)
|
|
82
|
-
else if (typeof ctx.logger?.warn === 'function') ctx.logger.warn(msg)
|
|
73
|
+
}
|
|
74
|
+
try {
|
|
75
|
+
return plainConfig(Config(plainConfig(config)))
|
|
76
|
+
} catch (configErr) {
|
|
77
|
+
if (typeof ctx.logger?.debug === 'function') {
|
|
78
|
+
ctx.logger.debug('[dsh-usage-guard] failed to derive config from base:', configErr)
|
|
83
79
|
}
|
|
84
|
-
|
|
85
|
-
const msg = `[dsh-usage-guard] failed to register settings subscription: ${err?.message || err}; running with default config`
|
|
86
|
-
if (typeof sctx.logger?.warn === 'function') sctx.logger.warn(msg)
|
|
87
|
-
else if (typeof ctx.logger?.warn === 'function') ctx.logger.warn(msg)
|
|
80
|
+
return live
|
|
88
81
|
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
ctx.inject(['settings'], (sctx) => {
|
|
85
|
+
settingsSvc = sctx.settings ?? (typeof sctx.get === 'function' ? sctx.get('settings') : null) ?? null
|
|
86
|
+
const fresh = readLive()
|
|
87
|
+
if (fresh) live = fresh
|
|
89
88
|
})
|
|
90
89
|
|
|
90
|
+
if (typeof ctx.effect === 'function') {
|
|
91
|
+
ctx.effect(() => {
|
|
92
|
+
const onVolatileUpdate = () => {
|
|
93
|
+
const fresh = readLive()
|
|
94
|
+
if (fresh) live = fresh
|
|
95
|
+
}
|
|
96
|
+
ctx.on?.('loader/volatile-update', onVolatileUpdate)
|
|
97
|
+
ctx.on?.('settings/document-updated', onVolatileUpdate)
|
|
98
|
+
return () => {
|
|
99
|
+
ctx.off?.('loader/volatile-update', onVolatileUpdate)
|
|
100
|
+
ctx.off?.('settings/document-updated', onVolatileUpdate)
|
|
101
|
+
}
|
|
102
|
+
}, 'dsh-usage-guard: volatile-update')
|
|
103
|
+
}
|
|
104
|
+
|
|
91
105
|
// Register one-click updater and telemetry endpoints if webServer is available
|
|
92
106
|
ctx.inject(['webServer'], (wctx) => {
|
|
93
107
|
try {
|
package/lib/telemetry.js
CHANGED
|
@@ -83,9 +83,6 @@ export function getSnapshot() {
|
|
|
83
83
|
return telemetry.getSnapshot();
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
export function resetTelemetry() {
|
|
87
|
-
telemetry.reset();
|
|
88
|
-
}
|
|
89
86
|
|
|
90
87
|
function header(request, name) {
|
|
91
88
|
const value = request?.headers?.[name.toLowerCase()];
|
|
@@ -104,8 +101,8 @@ export function isTrustedSettingsRequest(request, options = {}) {
|
|
|
104
101
|
if (!request || typeof request !== 'object' || !request.headers) return false;
|
|
105
102
|
|
|
106
103
|
const secFetchSite = header(request, 'sec-fetch-site');
|
|
107
|
-
// Unconditionally reject cross-site requests
|
|
108
|
-
if (secFetchSite === 'cross-site') {
|
|
104
|
+
// Unconditionally reject cross-site and same-site requests (#51)
|
|
105
|
+
if (secFetchSite === 'cross-site' || secFetchSite === 'same-site') {
|
|
109
106
|
return false;
|
|
110
107
|
}
|
|
111
108
|
|
|
@@ -117,9 +114,6 @@ export function isTrustedSettingsRequest(request, options = {}) {
|
|
|
117
114
|
if (host && url.host.toLowerCase() === host.toLowerCase()) {
|
|
118
115
|
return true;
|
|
119
116
|
}
|
|
120
|
-
if (isLoopbackAddress(url.hostname) && isLoopbackAddress(request?.socket?.remoteAddress)) {
|
|
121
|
-
return true;
|
|
122
|
-
}
|
|
123
117
|
return false;
|
|
124
118
|
} catch {
|
|
125
119
|
return false;
|
|
@@ -133,9 +127,6 @@ export function isTrustedSettingsRequest(request, options = {}) {
|
|
|
133
127
|
if (host && url.host.toLowerCase() === host.toLowerCase()) {
|
|
134
128
|
return true;
|
|
135
129
|
}
|
|
136
|
-
if (isLoopbackAddress(url.hostname) && isLoopbackAddress(request?.socket?.remoteAddress)) {
|
|
137
|
-
return true;
|
|
138
|
-
}
|
|
139
130
|
return false;
|
|
140
131
|
} catch {
|
|
141
132
|
return false;
|
|
@@ -168,13 +159,11 @@ export function isTrustedSettingsRequest(request, options = {}) {
|
|
|
168
159
|
}
|
|
169
160
|
}
|
|
170
161
|
|
|
171
|
-
// Same-origin or same-site fetch
|
|
172
|
-
if (secFetchSite === 'same-origin' || secFetchSite === 'same-site') {
|
|
173
|
-
return true;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
162
|
// Loopback connection (curl, local process)
|
|
177
163
|
if (isLoopbackAddress(request?.socket?.remoteAddress)) {
|
|
164
|
+
if (secFetchSite && secFetchSite !== 'same-origin') {
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
178
167
|
return true;
|
|
179
168
|
}
|
|
180
169
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-usage-guard",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.17",
|
|
4
4
|
"description": "Keeps a malformed token-usage sample from taking a whole session history down with it: a missing or non-numeric counter becomes zero before the harness adds it up, so the fold never yields NaN and already-broken sessions open again.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"deepseek-harness",
|