@goodandready/dsh-clinebot 0.3.24 → 0.3.25
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 +8 -0
- package/lib/client.js +0 -7
- package/lib/config.js +14 -2
- package/lib/index.js +58 -8
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,14 @@ 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.25] - 2026-09-24
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- **SettingsPage "Settings Unavailable" Banner Removal (#96 / GitHub #7)**: Removed the blocking `snapshotStatus === 'unavailable'` render gate in `SettingsPage`. When DSH serves no native settings form or when the page is accessed over non-loopback connections (`persistence === 'memory'`), the page no longer displays the dead-end warning banner and instead smoothly renders its fully functional standalone REST UI from `/dsh-clinebot/status` and `/dsh-clinebot/config`.
|
|
13
|
+
- **Modern DSH `SettingsForms` Adapter**: In `lib/index.js`, implemented a robust adapter for modern DSH `SettingsForms` (`svc.replace` / `svc.update` / `svc.describe`). DSH 0.1.7 removed `sctx.settings.register`, which previously caused `PUT /dsh-clinebot/config` to fail with HTTP 503 `settings not ready` and prevented key changes, account switching, and model toggles from persisting to DSH.
|
|
14
|
+
- **Volatile Field Projection (`volatileConfig`)**: Marked only user-editable settings as `.volatile()`, leaving internal derived fields (`dynamicModels` and deprecated `enabledModels`) non-volatile. Added `volatileConfig` helper to strip non-volatile fields before passing payloads to `SettingsForms.replace`, preventing DSH from rejecting configuration writes with `Config field is not volatile`.
|
|
15
|
+
|
|
8
16
|
## [0.3.24] - 2026-09-24
|
|
9
17
|
|
|
10
18
|
### Fixed
|
package/lib/client.js
CHANGED
|
@@ -1199,13 +1199,6 @@ function SettingsPage(props) {
|
|
|
1199
1199
|
}
|
|
1200
1200
|
|
|
1201
1201
|
if (!status || !draft) {
|
|
1202
|
-
if (snapshotStatus === 'unavailable') {
|
|
1203
|
-
return React.createElement(
|
|
1204
|
-
'div',
|
|
1205
|
-
{ className: 'cb-page' },
|
|
1206
|
-
React.createElement('div', { className: 'cb-banner-warning' }, t('settings.unavailable'))
|
|
1207
|
-
)
|
|
1208
|
-
}
|
|
1209
1202
|
if (err) {
|
|
1210
1203
|
return React.createElement(
|
|
1211
1204
|
'div',
|
package/lib/config.js
CHANGED
|
@@ -38,7 +38,7 @@ export const Config = z.object({
|
|
|
38
38
|
disabledModels: z.array(z.string()).default([])
|
|
39
39
|
.description('List of model IDs explicitly disabled by the user (new models are enabled automatically).').volatile(),
|
|
40
40
|
enabledModels: z.array(z.string()).default([])
|
|
41
|
-
.description('Deprecated: preserved for backwards compatibility with earlier versions.')
|
|
41
|
+
.description('Deprecated: preserved for backwards compatibility with earlier versions.'),
|
|
42
42
|
dynamicModels: z.array(z.object({
|
|
43
43
|
id: z.string(),
|
|
44
44
|
name: z.string(),
|
|
@@ -50,7 +50,7 @@ export const Config = z.object({
|
|
|
50
50
|
isCustom: z.boolean().default(false),
|
|
51
51
|
reasoningEfforts: z.any().default(undefined),
|
|
52
52
|
})).default([])
|
|
53
|
-
.description('Models automatically discovered from the official ClinePass subscription plan.')
|
|
53
|
+
.description('Models automatically discovered from the official ClinePass subscription plan.'),
|
|
54
54
|
timeoutMs: z.number().default(DEFAULT_TIMEOUT_MS)
|
|
55
55
|
.description('HTTP probe timeout in milliseconds.').volatile(),
|
|
56
56
|
smokeTimeoutMs: z.number().default(DEFAULT_SMOKE_TIMEOUT_MS)
|
|
@@ -84,6 +84,18 @@ export function plainConfig(cfg) {
|
|
|
84
84
|
return out
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
export function volatileConfig(cfg) {
|
|
88
|
+
const plain = plainConfig(cfg)
|
|
89
|
+
if (!plain || typeof plain !== 'object') return plain
|
|
90
|
+
const out = {}
|
|
91
|
+
for (const [key, field] of Object.entries(Config.dict || {})) {
|
|
92
|
+
if (field?.meta?.volatile && Object.hasOwn(plain, key)) {
|
|
93
|
+
out[key] = plain[key]
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return out
|
|
97
|
+
}
|
|
98
|
+
|
|
87
99
|
export function publicConfig(cfg) {
|
|
88
100
|
cfg = plainConfig(cfg)
|
|
89
101
|
const dynamic = Array.isArray(cfg?.dynamicModels) ? cfg.dynamicModels : []
|
package/lib/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Config, publicConfig, plainConfig, NS, LLM_PI_AI_NS } from './config.js'
|
|
1
|
+
import { Config, publicConfig, plainConfig, volatileConfig, NS, LLM_PI_AI_NS } from './config.js'
|
|
2
2
|
import { registerPluginUpdater } from './updater.js'
|
|
3
3
|
import {
|
|
4
4
|
sessionStats,
|
|
@@ -29,7 +29,8 @@ export const inject = ['settings', 'webServer', 'credentials']
|
|
|
29
29
|
export { NS, LLM_PI_AI_NS, Config, sessionStats, recordSessionRequest, resetSessionStats, rotateToNextAccount }
|
|
30
30
|
|
|
31
31
|
export function apply(ctx, config) {
|
|
32
|
-
let
|
|
32
|
+
let currentConfig = config
|
|
33
|
+
let getConfig = () => currentConfig
|
|
33
34
|
const live = () => (getConfig() ? Config(structuredClone(plainConfig(getConfig()))) : config)
|
|
34
35
|
let settingsApi
|
|
35
36
|
|
|
@@ -55,14 +56,61 @@ export function apply(ctx, config) {
|
|
|
55
56
|
autoDiscoverPlanModels(ctx, { live, getSettingsApi: () => settingsApi, syncProviderState })
|
|
56
57
|
}
|
|
57
58
|
|
|
59
|
+
const createSettingsAdapter = (svc) => {
|
|
60
|
+
if (!svc) return undefined
|
|
61
|
+
if (typeof svc.replace === 'function' || typeof svc.update === 'function' || typeof svc.write === 'function') {
|
|
62
|
+
const getRevision = () => {
|
|
63
|
+
try {
|
|
64
|
+
return svc.describe?.().find((row) => row.ns === NS)?.revision
|
|
65
|
+
} catch {
|
|
66
|
+
return undefined
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
get: () => live(),
|
|
71
|
+
replace: async (next) => {
|
|
72
|
+
const parsed = Config(structuredClone(plainConfig(next)))
|
|
73
|
+
currentConfig = parsed
|
|
74
|
+
const payload = volatileConfig(parsed)
|
|
75
|
+
if (typeof svc.replace === 'function') {
|
|
76
|
+
await svc.replace(NS, payload, getRevision())
|
|
77
|
+
} else if (typeof svc.update === 'function') {
|
|
78
|
+
await svc.update(NS, payload, getRevision())
|
|
79
|
+
}
|
|
80
|
+
return parsed
|
|
81
|
+
},
|
|
82
|
+
update: async (patch) => {
|
|
83
|
+
const merged = Config({ ...publicConfig(live()), ...plainConfig(patch) })
|
|
84
|
+
currentConfig = merged
|
|
85
|
+
const payload = volatileConfig(merged)
|
|
86
|
+
if (typeof svc.update === 'function') {
|
|
87
|
+
await svc.update(NS, payload, getRevision())
|
|
88
|
+
} else if (typeof svc.replace === 'function') {
|
|
89
|
+
await svc.replace(NS, payload, getRevision())
|
|
90
|
+
}
|
|
91
|
+
return merged
|
|
92
|
+
},
|
|
93
|
+
watch: (cb) => {
|
|
94
|
+
if (typeof svc.watch === 'function') return svc.watch(cb)
|
|
95
|
+
return () => {}
|
|
96
|
+
},
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return undefined
|
|
100
|
+
}
|
|
101
|
+
|
|
58
102
|
if (typeof ctx.inject === 'function') {
|
|
59
103
|
ctx.inject(['settings'], (sctx) => {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
104
|
+
if (typeof sctx.settings?.register === 'function') {
|
|
105
|
+
const scope = sctx.settings.register(NS, Config, { base: config })
|
|
106
|
+
settingsApi = scope
|
|
107
|
+
getConfig = () => (scope?.get?.() ?? config) ?? config
|
|
108
|
+
sctx.effect(() => scope.watch((next) => {
|
|
109
|
+
syncProviderState(live())
|
|
110
|
+
}), 'dsh-clinebot: settings')
|
|
111
|
+
} else {
|
|
112
|
+
settingsApi = createSettingsAdapter(sctx.settings)
|
|
113
|
+
}
|
|
66
114
|
sctx.effect(() => () => {
|
|
67
115
|
getConfig = () => config
|
|
68
116
|
settingsApi = undefined
|
|
@@ -79,6 +127,8 @@ export function apply(ctx, config) {
|
|
|
79
127
|
syncProviderState(live())
|
|
80
128
|
}), 'dsh-clinebot: settings')
|
|
81
129
|
}
|
|
130
|
+
} else {
|
|
131
|
+
settingsApi = createSettingsAdapter(settingsService)
|
|
82
132
|
}
|
|
83
133
|
}
|
|
84
134
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-clinebot",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.25",
|
|
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",
|