@goodandready/dsh-clinebot 0.5.0 → 0.5.1
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/CHANGELOG.md +7 -0
- package/lib/index.js +4 -4
- package/lib/provider-sync.js +15 -2
- package/lib/routes/proxy.js +1 -1
- package/lib/stats-storage.js +25 -7
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,13 @@ All notable changes to `@goodandready/dsh-clinebot` will be documented in this f
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.5.1] - 2026-09-28
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- **Host Process Crash on Stats Save (Issue #141, GitHub #9)**: Safely unwrap volatile Schemastery `{ get }` references, getter functions, and non-string inputs in `resolvePath`. Wrapped `saveStatsSync` in `try/catch` inside `queueSaveStats` timer callback to guarantee disk persistence failures never become uncaught exceptions terminating the DSH host process. Updated `lib/index.js` to pass `publicConfig(live()).statsPath`.
|
|
12
|
+
- **Large Context / Multimodal Proxy Rejection (Issue #142, GitHub #10)**: Increased request body reading limit on `/dsh-clinebot/v1/chat/completions` from 256 KB to 64 MB, preventing HTTP 400 `"body too large"` errors on long conversational sessions and multimodal requests.
|
|
13
|
+
- **Model Cache Path Resolution (Issue #143)**: Added safe volatile reference and getter unwrapping to `resolvePathWithHome` in `lib/provider-sync.js`, preventing `TypeError [ERR_INVALID_ARG_TYPE]` on disk cache operations.
|
|
14
|
+
|
|
8
15
|
## [0.5.0] - 2026-09-27
|
|
9
16
|
|
|
10
17
|
### Security
|
package/lib/index.js
CHANGED
|
@@ -210,7 +210,7 @@ export function apply(ctx, config) {
|
|
|
210
210
|
totalTokens: promptTokens + completionTokens,
|
|
211
211
|
isError: true,
|
|
212
212
|
is429,
|
|
213
|
-
statsPath: live().statsPath,
|
|
213
|
+
statsPath: publicConfig(live()).statsPath,
|
|
214
214
|
})
|
|
215
215
|
if (is429 || isExceeded) {
|
|
216
216
|
handleStream429()
|
|
@@ -237,7 +237,7 @@ export function apply(ctx, config) {
|
|
|
237
237
|
promptTokens,
|
|
238
238
|
completionTokens,
|
|
239
239
|
totalTokens: promptTokens + completionTokens,
|
|
240
|
-
statsPath: live().statsPath,
|
|
240
|
+
statsPath: publicConfig(live()).statsPath,
|
|
241
241
|
})
|
|
242
242
|
}
|
|
243
243
|
}
|
|
@@ -256,7 +256,7 @@ export function apply(ctx, config) {
|
|
|
256
256
|
promptTokens,
|
|
257
257
|
completionTokens,
|
|
258
258
|
totalTokens: promptTokens + completionTokens,
|
|
259
|
-
statsPath: live().statsPath,
|
|
259
|
+
statsPath: publicConfig(live()).statsPath,
|
|
260
260
|
})
|
|
261
261
|
}
|
|
262
262
|
} catch (err) {
|
|
@@ -279,7 +279,7 @@ export function apply(ctx, config) {
|
|
|
279
279
|
totalTokens: promptTokens + completionTokens,
|
|
280
280
|
isError: true,
|
|
281
281
|
is429: status === 429,
|
|
282
|
-
statsPath: live().statsPath,
|
|
282
|
+
statsPath: publicConfig(live()).statsPath,
|
|
283
283
|
})
|
|
284
284
|
if (status === 429 || isQuotaExceededError(msg)) {
|
|
285
285
|
handleStream429()
|
package/lib/provider-sync.js
CHANGED
|
@@ -23,8 +23,21 @@ import { resolveKeyValue } from './credential-refs.js'
|
|
|
23
23
|
import { resolveAccountPool, getLastRotation } from './account-pool.js'
|
|
24
24
|
|
|
25
25
|
export function resolvePathWithHome(p) {
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
let guard = 0
|
|
27
|
+
while (guard < 5 && p) {
|
|
28
|
+
if (typeof p === 'function') {
|
|
29
|
+
try { p = p() } catch { p = ''; break }
|
|
30
|
+
guard += 1
|
|
31
|
+
} else if (typeof p === 'object' && typeof p.get === 'function') {
|
|
32
|
+
try { p = p.get() } catch { p = ''; break }
|
|
33
|
+
guard += 1
|
|
34
|
+
} else {
|
|
35
|
+
break
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (typeof p !== 'string') return ''
|
|
39
|
+
if (p.startsWith('~/') || p === '~') {
|
|
40
|
+
return path.join(os.homedir(), p.slice(1))
|
|
28
41
|
}
|
|
29
42
|
return p
|
|
30
43
|
}
|
package/lib/routes/proxy.js
CHANGED
|
@@ -35,7 +35,7 @@ export function registerProxyRoutes(ctx, { live, getSettingsApi, syncProviderSta
|
|
|
35
35
|
|
|
36
36
|
let bodyBuf
|
|
37
37
|
try {
|
|
38
|
-
bodyBuf = await readBody(req)
|
|
38
|
+
bodyBuf = await readBody(req, 64 * 1024 * 1024)
|
|
39
39
|
} catch (err) {
|
|
40
40
|
return writeJson(res, 400, { ok: false, error: String(err?.message || err) })
|
|
41
41
|
}
|
package/lib/stats-storage.js
CHANGED
|
@@ -41,6 +41,21 @@ export function createEmptyStats() {
|
|
|
41
41
|
* Resolve '~' in paths to user's home directory.
|
|
42
42
|
*/
|
|
43
43
|
export function resolvePath(targetPath) {
|
|
44
|
+
let unwrapGuard = 0
|
|
45
|
+
while (unwrapGuard < 5 && targetPath) {
|
|
46
|
+
if (typeof targetPath === 'function') {
|
|
47
|
+
try { targetPath = targetPath() } catch { targetPath = ''; break }
|
|
48
|
+
unwrapGuard += 1
|
|
49
|
+
} else if (typeof targetPath === 'object' && typeof targetPath.get === 'function') {
|
|
50
|
+
try { targetPath = targetPath.get() } catch { targetPath = ''; break }
|
|
51
|
+
unwrapGuard += 1
|
|
52
|
+
} else {
|
|
53
|
+
break
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (typeof targetPath !== 'string') {
|
|
57
|
+
targetPath = ''
|
|
58
|
+
}
|
|
44
59
|
if (!targetPath) {
|
|
45
60
|
return path.join(homedir(), '.dsh', 'clinebot-stats.json')
|
|
46
61
|
}
|
|
@@ -57,10 +72,9 @@ export function resolvePath(targetPath) {
|
|
|
57
72
|
* @returns {Object} Loaded stats
|
|
58
73
|
*/
|
|
59
74
|
export function loadStats(customPath) {
|
|
60
|
-
const filePath = resolvePath(customPath)
|
|
61
|
-
activeStatsPath = filePath
|
|
62
|
-
|
|
63
75
|
try {
|
|
76
|
+
const filePath = resolvePath(customPath)
|
|
77
|
+
activeStatsPath = filePath
|
|
64
78
|
if (existsSync(filePath)) {
|
|
65
79
|
const raw = readFileSync(filePath, 'utf8')
|
|
66
80
|
const parsed = JSON.parse(raw)
|
|
@@ -91,10 +105,10 @@ export function loadStats(customPath) {
|
|
|
91
105
|
* @param {string} [customPath]
|
|
92
106
|
*/
|
|
93
107
|
export function saveStatsSync(customPath) {
|
|
94
|
-
const filePath = resolvePath(customPath || activeStatsPath)
|
|
95
|
-
activeStatsPath = filePath
|
|
96
|
-
|
|
97
108
|
try {
|
|
109
|
+
const filePath = resolvePath(customPath || activeStatsPath)
|
|
110
|
+
activeStatsPath = filePath
|
|
111
|
+
|
|
98
112
|
const dir = path.dirname(filePath)
|
|
99
113
|
if (!existsSync(dir)) {
|
|
100
114
|
mkdirSync(dir, { recursive: true })
|
|
@@ -123,7 +137,11 @@ function queueSaveStats(customPath, debounceMs = 500) {
|
|
|
123
137
|
}
|
|
124
138
|
saveTimer = setTimeout(() => {
|
|
125
139
|
saveTimer = null
|
|
126
|
-
|
|
140
|
+
try {
|
|
141
|
+
saveStatsSync(customPath)
|
|
142
|
+
} catch {
|
|
143
|
+
// Best-effort disk persistence, never crash host process
|
|
144
|
+
}
|
|
127
145
|
}, debounceMs)
|
|
128
146
|
}
|
|
129
147
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-clinebot",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "DeepSeek Harness companion for ClineBot / ClinePass: dynamic subscription models sync, quota exhaustion warnings, session metrics, dedicated settings page, live usage limits, and /cline slash-command.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|