@goodandready/dsh-clinebot 0.5.13 → 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 +10 -0
- package/lib/index.js +48 -16
- package/lib/routes/settings.js +4 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,16 @@ 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
|
+
|
|
8
18
|
## [0.5.13] - 2026-10-02
|
|
9
19
|
|
|
10
20
|
### Fixed
|
package/lib/index.js
CHANGED
|
@@ -106,25 +106,35 @@ export function apply(ctx, config) {
|
|
|
106
106
|
}
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
-
|
|
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
|
+
|
|
110
131
|
if (typeof ctx?.on === 'function') {
|
|
111
132
|
ctx.on('loader/volatile-update', () => {
|
|
112
133
|
const liveCfg = live()
|
|
113
|
-
const
|
|
114
|
-
if (
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
if (currentConfig && typeof currentConfig === 'object') {
|
|
118
|
-
currentConfig.dynamicModels = []
|
|
119
|
-
currentConfig.planSyncedAt = 0
|
|
120
|
-
}
|
|
121
|
-
} catch {
|
|
122
|
-
/* ignore read-only or frozen */
|
|
123
|
-
}
|
|
124
|
-
overrides.dynamicModels = []
|
|
125
|
-
overrides.planSyncedAt = 0
|
|
126
|
-
clearUsageCache()
|
|
127
|
-
clearProbeCache()
|
|
134
|
+
const currentIdentity = getCatalogIdentity(liveCfg)
|
|
135
|
+
if (currentIdentity !== lastCatalogIdentity) {
|
|
136
|
+
lastCatalogIdentity = currentIdentity
|
|
137
|
+
resetCatalogSnapshot()
|
|
128
138
|
}
|
|
129
139
|
syncStatsStorage(liveCfg)
|
|
130
140
|
})
|
|
@@ -181,7 +191,14 @@ export function apply(ctx, config) {
|
|
|
181
191
|
if (!svc || (typeof svc.replace !== 'function' && typeof svc.update !== 'function')) {
|
|
182
192
|
throw new Error('DSH settings service is unavailable or non-writable')
|
|
183
193
|
}
|
|
194
|
+
const prevIdentity = getCatalogIdentity(live())
|
|
184
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
|
+
}
|
|
185
202
|
const payload = volatileConfig(parsed)
|
|
186
203
|
const rev = getRevision(svc)
|
|
187
204
|
if (typeof svc.replace === 'function') {
|
|
@@ -189,6 +206,10 @@ export function apply(ctx, config) {
|
|
|
189
206
|
} else {
|
|
190
207
|
await svc.update(NS, payload, rev)
|
|
191
208
|
}
|
|
209
|
+
if (identityChanged) {
|
|
210
|
+
lastCatalogIdentity = nextIdentity
|
|
211
|
+
resetCatalogSnapshot()
|
|
212
|
+
}
|
|
192
213
|
applyConfigMutations(currentConfig, parsed)
|
|
193
214
|
syncStatsStorage(live())
|
|
194
215
|
await syncProviderState(live())
|
|
@@ -199,7 +220,14 @@ export function apply(ctx, config) {
|
|
|
199
220
|
if (!svc || (typeof svc.update !== 'function' && typeof svc.replace !== 'function')) {
|
|
200
221
|
throw new Error('DSH settings service is unavailable or non-writable')
|
|
201
222
|
}
|
|
223
|
+
const prevIdentity = getCatalogIdentity(live())
|
|
202
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
|
+
}
|
|
203
231
|
const payload = volatileConfig(merged)
|
|
204
232
|
const rev = getRevision(svc)
|
|
205
233
|
if (typeof svc.update === 'function') {
|
|
@@ -207,6 +235,10 @@ export function apply(ctx, config) {
|
|
|
207
235
|
} else {
|
|
208
236
|
await svc.replace(NS, payload, rev)
|
|
209
237
|
}
|
|
238
|
+
if (identityChanged) {
|
|
239
|
+
lastCatalogIdentity = nextIdentity
|
|
240
|
+
resetCatalogSnapshot()
|
|
241
|
+
}
|
|
210
242
|
applyConfigMutations(currentConfig, merged)
|
|
211
243
|
syncStatsStorage(live())
|
|
212
244
|
await syncProviderState(live())
|
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",
|