@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 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()
@@ -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
- if (typeof p === 'string' && p.startsWith('~/')) {
27
- return path.join(os.homedir(), p.slice(2))
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
  }
@@ -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
  }
@@ -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
- saveStatsSync(customPath)
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.0",
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",