@goodandready/dsh-clinebot 0.6.22 → 0.6.23

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.6.23] - 2026-10-28
9
+
10
+ ### Fixed
11
+ - **Profile-file path guard** (#269): data paths must be `.json`, must stay inside `$DSH_HOME`, and cannot point at `profiles/**/package.json`, settings, credentials or TypeScript configs.
12
+ - **Explicit credential rollback failure** (#272): if settings persistence and credential rollback both fail, the API now reports `partial`/`credentialOrphan` with the env name and keeps the key available for cleanup/retry instead of hiding it.
13
+ - **Settings merge on account add** (#280): account additions use `settingsApi.update({ accounts })` and a fresh config read, preserving concurrent `timeoutMs` and other setting changes.
14
+
8
15
  ## [0.6.22] - 2026-10-28
9
16
 
10
17
  ### Fixed
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  <h3>Native ClineBot / ClinePass Provider Companion for DeepSeek Harness</h3>
6
6
 
7
- <p><b>0.6.22</b> hardens updater credentials, provider failover, concurrent account changes, price-catalog/path access, CSV export and stream limits. It keeps automatic model/free/dated catalog backfill, DSH <code>0.2.1-rc.4</code>, model-scoped credit cooldowns and 402/413 errors. Chat runs on the native <code>ctx.llm</code> adapter; the legacy <code>llm-pi-ai</code>, loopback proxy and <code>llm/stream</code> path are removed.</p>
7
+ <p><b>0.6.23</b> closes residual path/rollback/settings-merge races: profile files cannot be targeted through data paths, orphan credentials are reported explicitly, and account additions merge with concurrent settings. It keeps automatic model/free/dated catalog backfill, DSH <code>0.2.1-rc.4</code>, model-scoped credit cooldowns and 402/413 errors. Chat runs on the native <code>ctx.llm</code> adapter; the legacy <code>llm-pi-ai</code>, loopback proxy and <code>llm/stream</code> path are removed.</p>
8
8
 
9
9
  <p align="center">
10
10
  <a href="https://www.npmjs.com/package/@goodandready/dsh-clinebot"><img src="https://img.shields.io/npm/v/@goodandready/dsh-clinebot.svg?style=for-the-badge&color=6366f1&labelColor=1e1b4b" alt="npm version"></a>
package/README.ru.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  <h3>Нативное подключение провайдера ClineBot / ClinePass для DeepSeek Harness</h3>
6
6
 
7
- <p><b>0.6.22</b> ужесточает работу с credentials в updater, failover провайдера, конкурентные изменения аккаунтов, доступ к URL/путям, CSV-экспорт и лимиты потоков. Сохраняются автодобор каталога моделей/FREE/dated, DSH <code>0.2.1-rc.4</code>, cooldown недостатка кредитов для конкретной модели и ошибки 402/413. Чат работает через нативный адаптер <code>ctx.llm</code>; старые <code>llm-pi-ai</code>, loopback-прокси и <code>llm/stream</code> удалены.</p>
7
+ <p><b>0.6.23</b> закрывает остаточные гонки путей, rollback и настроек: файлы профиля нельзя выбрать через data paths, orphan-credential явно репортится, а добавление аккаунта мержится с параллельными настройками. Сохраняются автодобор каталога моделей/FREE/dated, DSH <code>0.2.1-rc.4</code>, cooldown недостатка кредитов для конкретной модели и ошибки 402/413. Чат работает через нативный адаптер <code>ctx.llm</code>; старые <code>llm-pi-ai</code>, loopback-прокси и <code>llm/stream</code> удалены.</p>
8
8
 
9
9
  <p align="center">
10
10
  <a href="https://www.npmjs.com/package/@goodandready/dsh-clinebot"><img src="https://img.shields.io/npm/v/@goodandready/dsh-clinebot.svg?style=for-the-badge&color=6366f1&labelColor=1e1b4b" alt="npm version"></a>
package/README.zh.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  <h3>适用于 DeepSeek Harness 的 ClineBot / ClinePass 原生模型提供商伴侣插件</h3>
6
6
 
7
- <p><b>0.6.22</b> 加强更新器凭据隔离、提供方故障转移、并发账户变更、目录 URL/路径访问、CSV 导出和流限制。保留模型/免费/日期变体自动补齐、DSH <code>0.2.1-rc.4</code>、按模型隔离的余额不足冷却和明确的 402/413 错误。聊天使用原生 <code>ctx.llm</code> 适配器;旧的 <code>llm-pi-ai</code>、回环代理和 <code>llm/stream</code> 已删除。</p>
7
+ <p><b>0.6.23</b> 修复剩余的路径、回滚和设置合并竞态:数据路径不能指向配置文件,回滚失败会明确报告孤立凭据,添加账户会与并发设置合并。保留模型/免费/日期变体自动补齐、DSH <code>0.2.1-rc.4</code>、按模型隔离的余额不足冷却和明确的 402/413 错误。聊天使用原生 <code>ctx.llm</code> 适配器;旧的 <code>llm-pi-ai</code>、回环代理和 <code>llm/stream</code> 已删除。</p>
8
8
 
9
9
  <p align="center">
10
10
  <a href="https://www.npmjs.com/package/@goodandready/dsh-clinebot"><img src="https://img.shields.io/npm/v/@goodandready/dsh-clinebot.svg?style=for-the-badge&color=6366f1&labelColor=1e1b4b" alt="npm version"></a>
@@ -13,5 +13,11 @@ export function isSafeDataPath(value) {
13
13
  ? path.resolve(process.env.HOME || os.homedir(), raw.slice(1))
14
14
  : path.resolve(raw)
15
15
  const home = dshHome()
16
- return resolved === home || resolved.startsWith(`${home}${path.sep}`)
16
+ if (resolved === home || !resolved.startsWith(`${home}${path.sep}`)) return false
17
+ const relative = path.relative(home, resolved).toLowerCase()
18
+ const segments = relative.split(/[\\/]+/)
19
+ const base = path.basename(resolved).toLowerCase()
20
+ if (segments[0] === 'profiles' && !base.startsWith('clinebot-')) return false
21
+ if (path.extname(base) !== '.json') return false
22
+ return !['package.json', 'package-lock.json', 'tsconfig.json', 'settings.json', '.credentials.json'].includes(base)
17
23
  }
@@ -66,30 +66,46 @@ export function registerAccountsRoutes(ctx, { live, syncProviderState, getSettin
66
66
  try {
67
67
  if (previous.source === 'credentials' && previous.value) {
68
68
  await saveCredentialKey(ctx, targetEnv, previous.value)
69
- } else {
70
- await deleteCredentialKey(ctx, targetEnv)
69
+ return true
71
70
  }
71
+ const result = await deleteCredentialKey(ctx, targetEnv)
72
+ return Boolean(result?.ok)
72
73
  } catch {
73
- /* best-effort rollback */
74
+ return false
74
75
  }
75
76
  }
76
77
 
77
78
  await saveCredentialKey(ctx, targetEnv, apiKey)
78
79
 
79
- const idx = existingAccounts.findIndex((a) => a && a.apiKeyEnv === targetEnv)
80
+ const fresh = plainConfig(live())
81
+ const freshAccounts = Array.isArray(fresh.accounts) ? [...fresh.accounts] : []
82
+ const idx = freshAccounts.findIndex((a) => a && a.apiKeyEnv === targetEnv)
80
83
  const accountEntry = { label: accountLabel, apiKeyEnv: targetEnv }
81
84
  if (idx >= 0) {
82
- existingAccounts[idx] = accountEntry
85
+ freshAccounts[idx] = accountEntry
83
86
  } else {
84
- existingAccounts.push(accountEntry)
87
+ freshAccounts.push(accountEntry)
85
88
  }
86
89
 
87
- const next = plainConfig(Config({ ...curr, accounts: existingAccounts }))
90
+ const next = plainConfig(Config({ ...fresh, accounts: freshAccounts }))
88
91
  const settingsApi = getSettingsApi()
89
92
  try {
90
- await settingsApi.replace(next)
93
+ if (typeof settingsApi.update === 'function') {
94
+ await settingsApi.update({ accounts: freshAccounts })
95
+ } else {
96
+ await settingsApi.replace(next)
97
+ }
91
98
  } catch (err) {
92
- await restoreCredential()
99
+ const restored = await restoreCredential()
100
+ if (!restored) {
101
+ pendingSecretCleanupAccounts.add(targetEnv)
102
+ const partial = new Error(`Account settings were not persisted and credential rollback failed for "${targetEnv}". Use account delete with secret cleanup to retry.`)
103
+ partial.status = 500
104
+ partial.partial = true
105
+ partial.credentialOrphan = true
106
+ partial.apiKeyEnv = targetEnv
107
+ throw partial
108
+ }
93
109
  throw err
94
110
  }
95
111
  await syncProviderState(next)
@@ -150,7 +166,13 @@ export function registerAccountsRoutes(ctx, { live, syncProviderState, getSettin
150
166
  writeJson(res, 200, { ok: true, ...result })
151
167
  } catch (err) {
152
168
  const status = typeof err?.status === 'number' ? err.status : 500
153
- writeJson(res, status, { ok: false, error: String(err?.message || err) })
169
+ writeJson(res, status, {
170
+ ok: false,
171
+ error: String(err?.message || err),
172
+ ...(err?.partial ? { partial: true } : {}),
173
+ ...(err?.credentialOrphan ? { credentialOrphan: true } : {}),
174
+ ...(err?.apiKeyEnv ? { apiKeyEnv: err.apiKeyEnv } : {}),
175
+ })
154
176
  }
155
177
  },
156
178
  }), 'dsh-clinebot: /accounts')
@@ -394,7 +416,13 @@ export function registerAccountsRoutes(ctx, { live, syncProviderState, getSettin
394
416
  writeJson(res, 200, { ok: true, ...result })
395
417
  } catch (err) {
396
418
  const status = typeof err?.status === 'number' ? err.status : 500
397
- writeJson(res, status, { ok: false, error: String(err?.message || err) })
419
+ writeJson(res, status, {
420
+ ok: false,
421
+ error: String(err?.message || err),
422
+ ...(err?.partial ? { partial: true } : {}),
423
+ ...(err?.credentialOrphan ? { credentialOrphan: true } : {}),
424
+ ...(err?.apiKeyEnv ? { apiKeyEnv: err.apiKeyEnv } : {}),
425
+ })
398
426
  }
399
427
  },
400
428
  }), 'dsh-clinebot: /keys/discovered/import')
@@ -9,7 +9,7 @@
9
9
  import { createRequire } from 'node:module'
10
10
 
11
11
  const require = createRequire(import.meta.url)
12
- let packageVersion = '0.6.22'
12
+ let packageVersion = '0.6.23'
13
13
  try {
14
14
  packageVersion = require('../../package.json').version || packageVersion
15
15
  } catch {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goodandready/dsh-clinebot",
3
- "version": "0.6.22",
3
+ "version": "0.6.23",
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",