@goodandready/dsh-clinebot 0.3.20 → 0.3.22
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 +11 -0
- package/lib/account-pool.js +12 -1
- package/lib/client.js +50 -12
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,17 @@ 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.3.22] - 2026-09-24
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- **SettingsPage Crash on Cordis Strict Context (#67 / GitHub #4)**: Fixed unhandled `TypeError: cannot get property "settingsScope" without inject` thrown when accessing `ctx?.settingsScope` under DSH 0.17+ / Cordis v3+ strict proxy contexts. Replaced bare property access with defensive `ctx.get('lanSettings') || ctx.get('settingsScope')` inside `try/catch` across both `settings-page.js` and `entry.js`.
|
|
12
|
+
- **Standalone Unblocked UI Rendering**: When `settingsScope` / `lanSettings` service is absent or not injected in the client fiber, `SettingsPage` snapshot now defaults to `{ status: 'ready', view: null }` instead of permanently hanging on `status: 'loading'`. The settings page is entirely functional via its own HTTP REST endpoints (`/dsh-clinebot/status`, `/save-key`, `/models/toggle`, etc.).
|
|
13
|
+
|
|
14
|
+
## [0.3.21] - 2026-09-23
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
- **Account Failover Persistence & Cache Invalidation (#61)**: Fixed `rotateToNextAccount` returning `rotated: true` when settings persistence (`settingsApi.replace` or `settings.mutate`) fails. It now returns `{ rotated: false, activeAccount: active, reason: 'failed_to_persist' }` on failure, and defers `clearUsageCache()` and `clearProbeCache()` until settings are confirmed written. This prevents the provider from staying on the previous account while its quota and health telemetry caches are wiped.
|
|
18
|
+
|
|
8
19
|
## [0.3.20] - 2026-09-23
|
|
9
20
|
|
|
10
21
|
### Fixed
|
package/lib/account-pool.js
CHANGED
|
@@ -117,6 +117,17 @@ export async function rotateToNextAccount(ctx, cfg, reason = 'rate_limit', setti
|
|
|
117
117
|
}
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
+
if (!updated) {
|
|
121
|
+
return {
|
|
122
|
+
rotated: false,
|
|
123
|
+
previousAccount: active,
|
|
124
|
+
activeAccount: active,
|
|
125
|
+
reason: 'failed_to_persist',
|
|
126
|
+
message: 'Failed to persist rotated activeAccount to settings',
|
|
127
|
+
updatedSettings: false,
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
120
131
|
// Reset cached quota and host probes so new account immediately revalidates
|
|
121
132
|
clearUsageCache()
|
|
122
133
|
clearProbeCache()
|
|
@@ -126,6 +137,6 @@ export async function rotateToNextAccount(ctx, cfg, reason = 'rate_limit', setti
|
|
|
126
137
|
previousAccount: active,
|
|
127
138
|
activeAccount: nextAcc.apiKeyEnv,
|
|
128
139
|
reason,
|
|
129
|
-
updatedSettings:
|
|
140
|
+
updatedSettings: true,
|
|
130
141
|
}
|
|
131
142
|
}
|
package/lib/client.js
CHANGED
|
@@ -839,13 +839,31 @@ function SettingsPage(props) {
|
|
|
839
839
|
const t = props?.t || makeT(en, en)
|
|
840
840
|
|
|
841
841
|
const scope = React.useMemo(() => {
|
|
842
|
-
|
|
843
|
-
if (!s?.bind) return undefined
|
|
842
|
+
let s = null
|
|
844
843
|
try {
|
|
845
|
-
|
|
844
|
+
if (typeof ctx?.get === 'function') {
|
|
845
|
+
s = ctx.get('lanSettings') || ctx.get('settingsScope')
|
|
846
|
+
}
|
|
847
|
+
} catch (_) {
|
|
848
|
+
s = null
|
|
849
|
+
}
|
|
850
|
+
if (!s) {
|
|
851
|
+
try {
|
|
852
|
+
if (ctx && typeof ctx === 'object') {
|
|
853
|
+
s = ctx.lanSettings || ctx.settingsScope || null
|
|
854
|
+
}
|
|
855
|
+
} catch (_) {
|
|
856
|
+
s = null
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
if (!s) return undefined
|
|
860
|
+
try {
|
|
861
|
+
if (typeof s.bind === 'function') return s.bind({ namespace: NS })
|
|
862
|
+
if (typeof s.get === 'function') return s.get(NS)
|
|
846
863
|
} catch (_) {
|
|
847
864
|
return undefined
|
|
848
865
|
}
|
|
866
|
+
return undefined
|
|
849
867
|
}, [ctx])
|
|
850
868
|
|
|
851
869
|
const subscribe = React.useMemo(() => {
|
|
@@ -860,11 +878,11 @@ function SettingsPage(props) {
|
|
|
860
878
|
}, [scope])
|
|
861
879
|
|
|
862
880
|
const getSnapshot = React.useCallback(() => {
|
|
863
|
-
if (!scope?.getSnapshot) return
|
|
881
|
+
if (!scope?.getSnapshot) return { status: 'ready', view: null }
|
|
864
882
|
try {
|
|
865
|
-
return scope.getSnapshot() ||
|
|
883
|
+
return scope.getSnapshot() || { status: 'ready', view: null }
|
|
866
884
|
} catch (_) {
|
|
867
|
-
return
|
|
885
|
+
return { status: 'ready', view: null }
|
|
868
886
|
}
|
|
869
887
|
}, [scope])
|
|
870
888
|
|
|
@@ -1369,28 +1387,48 @@ function PluginCard(props) {
|
|
|
1369
1387
|
|
|
1370
1388
|
// --- src/client/entry.js ---
|
|
1371
1389
|
function refreshMirrorUntilVisible(ctx) {
|
|
1390
|
+
const getSettingsService = () => {
|
|
1391
|
+
try {
|
|
1392
|
+
if (typeof ctx?.get === 'function') {
|
|
1393
|
+
const s = ctx.get('lanSettings') || ctx.get('settingsScope')
|
|
1394
|
+
if (s) return s
|
|
1395
|
+
}
|
|
1396
|
+
} catch (_) {}
|
|
1397
|
+
try {
|
|
1398
|
+
if (ctx && typeof ctx === 'object') {
|
|
1399
|
+
return ctx.lanSettings || ctx.settingsScope || null
|
|
1400
|
+
}
|
|
1401
|
+
} catch (_) {}
|
|
1402
|
+
return null
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
const s = getSettingsService()
|
|
1406
|
+
if (!s) return
|
|
1407
|
+
|
|
1372
1408
|
const visible = () => {
|
|
1373
1409
|
try {
|
|
1374
|
-
const
|
|
1375
|
-
const view =
|
|
1410
|
+
const current = getSettingsService()
|
|
1411
|
+
const view = current?.describe?.()?.getSnapshot?.()?.view
|
|
1376
1412
|
return !!view && Array.isArray(view.namespaces) && view.namespaces.some((row) => row.ns === NS)
|
|
1377
1413
|
} catch (_) {
|
|
1378
1414
|
return false
|
|
1379
1415
|
}
|
|
1380
1416
|
}
|
|
1381
|
-
if (visible()) return
|
|
1417
|
+
if (visible()) return
|
|
1418
|
+
|
|
1419
|
+
if (typeof setInterval !== 'function') return
|
|
1420
|
+
|
|
1382
1421
|
let tries = 0
|
|
1383
1422
|
const timer = setInterval(() => {
|
|
1384
1423
|
if (visible() || tries >= 15) { clearInterval(timer); return }
|
|
1385
1424
|
tries += 1
|
|
1386
1425
|
try {
|
|
1387
|
-
const
|
|
1388
|
-
|
|
1426
|
+
const current = getSettingsService()
|
|
1427
|
+
current?.describe?.()?.load?.()
|
|
1389
1428
|
} catch (e) {
|
|
1390
1429
|
console.debug?.('[dsh-clinebot] Polling settings mirror:', e)
|
|
1391
1430
|
}
|
|
1392
1431
|
}, 1000)
|
|
1393
|
-
return () => clearInterval(timer)
|
|
1394
1432
|
}
|
|
1395
1433
|
|
|
1396
1434
|
function apply(ctx) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-clinebot",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.22",
|
|
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",
|