@goodandready/dsh-clinebot 0.5.12 → 0.5.15
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 +16 -0
- package/lib/index.js +53 -1
- package/lib/routes/accounts.js +1 -4
- package/lib/routes/settings.js +4 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,22 @@ 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.15] - 2026-10-02
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- **Atomic Catalog Snapshot Reset on Settings Persistence (Issue #192)**: In `settingsApi.update` and `settingsApi.replace`, defer invalidating the verified model catalog snapshot (`resetCatalogSnapshot()`) and updating `lastCatalogIdentity` until after persistence to the DSH settings service completes successfully. If settings persistence fails (e.g. revision conflict or rejection), local in-memory dynamic models and plan synchronization timestamp are strictly preserved, preventing catalog loss when endpoint and credentials remain unchanged.
|
|
12
|
+
|
|
13
|
+
## [0.5.14] - 2026-10-02
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
- **Catalog Identity Lifecycle on Endpoint & Primary Credential Switch (Issue #168)**: Unified catalog invalidation under an identity contract: the verified catalog and plan synchronization timestamp strictly belong to the identity tuple (`baseUrl::activeAccount::apiKeyEnv`). Changing primary credential reference (`apiKeyEnv`) or provider endpoint (`baseUrl`) — via native Cosmokit volatile updates or `PUT /dsh-clinebot/config` — immediately resets `dynamicModels = []`, `planSyncedAt = 0`, and purges both usage and probe caches (`clearUsageCache()`, `clearProbeCache()`), preventing foreign or unverified model snapshots from persisting across endpoint or credential changes.
|
|
17
|
+
|
|
18
|
+
## [0.5.13] - 2026-10-02
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
- **Native Volatile Account Switch Catalog & Plan Reset (Issue #168)**: In `loader/volatile-update`, actively detect when `activeAccount` changes via native Cosmokit volatile settings, resetting `dynamicModels = []`, `planSyncedAt = 0`, and clearing usage and probe caches (`clearUsageCache()`, `clearProbeCache()`) to guarantee stale catalogs from previous accounts are never marked verified.
|
|
22
|
+
- **Account Deletion Rollback Automatic Mode Preservation (Issue #187)**: Ensure surgical rollback of account deletion in `lib/routes/accounts.js` unconditionally preserves live `fresh.activeAccount`, preventing unintended restoration of deleted accounts or overriding concurrent selections of automatic mode (`activeAccount: ""`).
|
|
23
|
+
|
|
8
24
|
## [0.5.12] - 2026-10-02
|
|
9
25
|
|
|
10
26
|
### Fixed
|
package/lib/index.js
CHANGED
|
@@ -4,6 +4,8 @@ import {
|
|
|
4
4
|
sessionStats,
|
|
5
5
|
recordSessionRequest,
|
|
6
6
|
resetSessionStats,
|
|
7
|
+
clearUsageCache,
|
|
8
|
+
clearProbeCache,
|
|
7
9
|
} from './cline-client.js'
|
|
8
10
|
import {
|
|
9
11
|
rotateToNextAccount,
|
|
@@ -104,9 +106,37 @@ export function apply(ctx, config) {
|
|
|
104
106
|
}
|
|
105
107
|
}
|
|
106
108
|
|
|
109
|
+
const normalizeEndpoint = (url) => String(url || '').trim().replace(/\/+$/, '')
|
|
110
|
+
const getCatalogIdentity = (cfg) => {
|
|
111
|
+
const pub = publicConfig(cfg)
|
|
112
|
+
return `${normalizeEndpoint(pub.baseUrl)}::${String(pub.activeAccount || '').trim()}::${String(pub.apiKeyEnv || '').trim()}`
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
let lastCatalogIdentity = getCatalogIdentity(live())
|
|
116
|
+
const resetCatalogSnapshot = () => {
|
|
117
|
+
try {
|
|
118
|
+
if (currentConfig && typeof currentConfig === 'object') {
|
|
119
|
+
currentConfig.dynamicModels = []
|
|
120
|
+
currentConfig.planSyncedAt = 0
|
|
121
|
+
}
|
|
122
|
+
} catch {
|
|
123
|
+
/* ignore read-only or frozen */
|
|
124
|
+
}
|
|
125
|
+
overrides.dynamicModels = []
|
|
126
|
+
overrides.planSyncedAt = 0
|
|
127
|
+
clearUsageCache()
|
|
128
|
+
clearProbeCache()
|
|
129
|
+
}
|
|
130
|
+
|
|
107
131
|
if (typeof ctx?.on === 'function') {
|
|
108
132
|
ctx.on('loader/volatile-update', () => {
|
|
109
|
-
|
|
133
|
+
const liveCfg = live()
|
|
134
|
+
const currentIdentity = getCatalogIdentity(liveCfg)
|
|
135
|
+
if (currentIdentity !== lastCatalogIdentity) {
|
|
136
|
+
lastCatalogIdentity = currentIdentity
|
|
137
|
+
resetCatalogSnapshot()
|
|
138
|
+
}
|
|
139
|
+
syncStatsStorage(liveCfg)
|
|
110
140
|
})
|
|
111
141
|
}
|
|
112
142
|
|
|
@@ -161,7 +191,14 @@ export function apply(ctx, config) {
|
|
|
161
191
|
if (!svc || (typeof svc.replace !== 'function' && typeof svc.update !== 'function')) {
|
|
162
192
|
throw new Error('DSH settings service is unavailable or non-writable')
|
|
163
193
|
}
|
|
194
|
+
const prevIdentity = getCatalogIdentity(live())
|
|
164
195
|
const parsed = Config(structuredClone(plainConfig(next)))
|
|
196
|
+
const nextIdentity = getCatalogIdentity(parsed)
|
|
197
|
+
const identityChanged = nextIdentity !== prevIdentity
|
|
198
|
+
if (identityChanged) {
|
|
199
|
+
if (!('dynamicModels' in next)) parsed.dynamicModels = []
|
|
200
|
+
if (!('planSyncedAt' in next)) parsed.planSyncedAt = 0
|
|
201
|
+
}
|
|
165
202
|
const payload = volatileConfig(parsed)
|
|
166
203
|
const rev = getRevision(svc)
|
|
167
204
|
if (typeof svc.replace === 'function') {
|
|
@@ -169,6 +206,10 @@ export function apply(ctx, config) {
|
|
|
169
206
|
} else {
|
|
170
207
|
await svc.update(NS, payload, rev)
|
|
171
208
|
}
|
|
209
|
+
if (identityChanged) {
|
|
210
|
+
lastCatalogIdentity = nextIdentity
|
|
211
|
+
resetCatalogSnapshot()
|
|
212
|
+
}
|
|
172
213
|
applyConfigMutations(currentConfig, parsed)
|
|
173
214
|
syncStatsStorage(live())
|
|
174
215
|
await syncProviderState(live())
|
|
@@ -179,7 +220,14 @@ export function apply(ctx, config) {
|
|
|
179
220
|
if (!svc || (typeof svc.update !== 'function' && typeof svc.replace !== 'function')) {
|
|
180
221
|
throw new Error('DSH settings service is unavailable or non-writable')
|
|
181
222
|
}
|
|
223
|
+
const prevIdentity = getCatalogIdentity(live())
|
|
182
224
|
const merged = Config({ ...publicConfig(live()), ...plainConfig(patch) })
|
|
225
|
+
const nextIdentity = getCatalogIdentity(merged)
|
|
226
|
+
const identityChanged = nextIdentity !== prevIdentity
|
|
227
|
+
if (identityChanged) {
|
|
228
|
+
if (!('dynamicModels' in patch)) merged.dynamicModels = []
|
|
229
|
+
if (!('planSyncedAt' in patch)) merged.planSyncedAt = 0
|
|
230
|
+
}
|
|
183
231
|
const payload = volatileConfig(merged)
|
|
184
232
|
const rev = getRevision(svc)
|
|
185
233
|
if (typeof svc.update === 'function') {
|
|
@@ -187,6 +235,10 @@ export function apply(ctx, config) {
|
|
|
187
235
|
} else {
|
|
188
236
|
await svc.replace(NS, payload, rev)
|
|
189
237
|
}
|
|
238
|
+
if (identityChanged) {
|
|
239
|
+
lastCatalogIdentity = nextIdentity
|
|
240
|
+
resetCatalogSnapshot()
|
|
241
|
+
}
|
|
190
242
|
applyConfigMutations(currentConfig, merged)
|
|
191
243
|
syncStatsStorage(live())
|
|
192
244
|
await syncProviderState(live())
|
package/lib/routes/accounts.js
CHANGED
|
@@ -210,13 +210,10 @@ export function registerAccountsRoutes(ctx, { live, getSettingsApi, syncProvider
|
|
|
210
210
|
if (!freshAccounts.some((a) => a && a.apiKeyEnv === targetEnv)) {
|
|
211
211
|
freshAccounts.push(accountToDelete)
|
|
212
212
|
}
|
|
213
|
-
const shouldRestoreActive = (curr.activeAccount === targetEnv && (!fresh.activeAccount || fresh.activeAccount === targetEnv))
|
|
214
|
-
? targetEnv
|
|
215
|
-
: fresh.activeAccount
|
|
216
213
|
const restoredConfig = plainConfig(Config({
|
|
217
214
|
...fresh,
|
|
218
215
|
accounts: freshAccounts,
|
|
219
|
-
activeAccount:
|
|
216
|
+
activeAccount: fresh.activeAccount,
|
|
220
217
|
}))
|
|
221
218
|
await settingsApi.replace(restoredConfig)
|
|
222
219
|
await syncProviderState(restoredConfig)
|
package/lib/routes/settings.js
CHANGED
|
@@ -93,7 +93,10 @@ export function registerSettingsRoutes(ctx, { live, getSettingsApi, syncProvider
|
|
|
93
93
|
const base = plainConfig(live()) || {}
|
|
94
94
|
delete base.enabledModels
|
|
95
95
|
const merged = { ...base, ...rawPayload }
|
|
96
|
-
|
|
96
|
+
const activeChanged = rawPayload.activeAccount !== undefined && String(rawPayload.activeAccount).trim() !== String(base.activeAccount || "").trim()
|
|
97
|
+
const apiKeyEnvChanged = rawPayload.apiKeyEnv !== undefined && String(rawPayload.apiKeyEnv).trim() !== String(base.apiKeyEnv || "").trim()
|
|
98
|
+
const baseUrlChanged = rawPayload.baseUrl !== undefined && String(rawPayload.baseUrl).trim().replace(/\/+$/, '') !== String(base.baseUrl || "").trim().replace(/\/+$/, '')
|
|
99
|
+
if (activeChanged || apiKeyEnvChanged || baseUrlChanged) {
|
|
97
100
|
if (!("dynamicModels" in rawPayload)) merged.dynamicModels = []
|
|
98
101
|
if (!("planSyncedAt" in rawPayload)) merged.planSyncedAt = 0
|
|
99
102
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-clinebot",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.15",
|
|
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",
|