@goodandready/dsh-voice 0.9.25 → 0.9.26
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/README.md +13 -0
- package/README.ru.md +13 -0
- package/README.zh.md +11 -0
- package/lib/client.js +17 -0
- package/lib/updater.js +18 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -223,6 +223,19 @@ Registers `transcribe_audio(file_path, language?)` in `ctx.tools`, allowing agen
|
|
|
223
223
|
* `GET /dsh-voice/config` — Returns live plugin configuration snapshot.
|
|
224
224
|
* `PUT /dsh-voice/config` — Updates and persists plugin configuration across network.
|
|
225
225
|
* `GET/POST /dsh-voice/sensevoice-installer` — SenseVoice 1-click model installation status and trigger (protected by `isTrustedCaller`, model paths redacted).
|
|
226
|
+
* `POST /api/dsh-voice/update` — Triggers plugin self-update to latest compatible npm version (protected by local caller verification and profile lock check).
|
|
227
|
+
* `GET /api/dsh-voice/update` — Returns update check status (current version, latest version, updateAvailable).
|
|
228
|
+
|
|
229
|
+
### 🔒 Profile Lock & Operator Recovery Policy
|
|
230
|
+
When updating or installing plugins, DeepSeek Harness profiles coordinate concurrent operations using `<profile-dir>/package.json.lock`.
|
|
231
|
+
* **Canonical Contender Policy**: The contender process never deletes or unlinks an existing lockfile. Automatic stale lock removal by contenders is prohibited to eliminate TOCTOU (Time-of-Check to Time-of-Use) race conditions and prevent unintended lockfile clobbering.
|
|
232
|
+
* **Diagnostics**: If `<profile-dir>/package.json.lock` is present, update attempts return `409 Conflict` with clear diagnostics:
|
|
233
|
+
- If held by an active process, the PID is reported.
|
|
234
|
+
- If filesystem inspection encounters access errors (`EACCES`, `EIO`), the operation fails closed with the error code preserved.
|
|
235
|
+
* **Operator Recovery**: If a prior installation process was forcefully killed or terminated unexpectedly (e.g. system OOM or power outage), manual operator recovery is required:
|
|
236
|
+
```bash
|
|
237
|
+
rm /home/vadim/.dsh/profiles/web/package.json.lock
|
|
238
|
+
```
|
|
226
239
|
|
|
227
240
|
---
|
|
228
241
|
|
package/README.ru.md
CHANGED
|
@@ -220,6 +220,19 @@ dsh plugin --profile web add @goodandready/dsh-voice
|
|
|
220
220
|
* `POST /dsh-voice/transcribe` — `{ dataBase64, mimeType, mode }` → `{ ok, text, provider, tookMs }`
|
|
221
221
|
* `POST /dsh-voice/polish` — `{ text }` → `{ ok, text }`
|
|
222
222
|
* `GET /dsh-voice/status` — состояние демонов, цепочки, конфигурация SenseVoice и реалтайма.
|
|
223
|
+
* `GET/PUT /dsh-voice/config` — получение и сохранение конфигурации плагина.
|
|
224
|
+
* `GET/POST /dsh-voice/sensevoice-installer` — статус и установка модели SenseVoice в 1 клик.
|
|
225
|
+
* `POST /api/dsh-voice/update` — запуск обновления плагина до актуальной версии из npm (с проверкой прав и блокировки профиля).
|
|
226
|
+
* `GET /api/dsh-voice/update` — проверка наличия обновлений.
|
|
227
|
+
|
|
228
|
+
### 🔒 Политика блокировок профиля и ручное восстановление оператором
|
|
229
|
+
При установке и обновлении плагинов DeepSeek Harness синхронизирует параллельные процессы через `<profile-dir>/package.json.lock`.
|
|
230
|
+
* **Канонический принцип претендента**: Процесс-претендент никогда не удаляет существующий lock-файл. Автоматическое удаление чужих блокировок исключено для предотвращения TOCTOU-гонок.
|
|
231
|
+
* **Диагностика**: При наличии lock-файла запрос на обновление возвращает `409 Conflict` с подробной информацией (PID процесса либо код ошибки доступа к файловой системе `EACCES`/`EIO`).
|
|
232
|
+
* **Восстановление оператором**: Если предыдущий процесс установки аварийно завершился (например, OOM или отключение питания) и оставил зависший lock-файл, снятие блокировки выполняет оператор вручную:
|
|
233
|
+
```bash
|
|
234
|
+
rm /home/vadim/.dsh/profiles/web/package.json.lock
|
|
235
|
+
```
|
|
223
236
|
|
|
224
237
|
---
|
|
225
238
|
|
package/README.zh.md
CHANGED
|
@@ -142,6 +142,17 @@ v0.8.18 质量审查后的修复批次(Gitea #79–#87,PR #88):
|
|
|
142
142
|
* `GET /dsh-voice/config` — 获取插件运行时配置快照
|
|
143
143
|
* `PUT /dsh-voice/config` — 跨网络安全持久化更新插件配置
|
|
144
144
|
* `GET/POST /dsh-voice/sensevoice-installer` — SenseVoice 一键模型安装状态与触发接口(受 `isTrustedCaller` 保护,绝对路径脱敏)
|
|
145
|
+
* `POST /api/dsh-voice/update` — 触发插件自动更新至 npm 最新兼容版本(受本地调用与锁校验保护)
|
|
146
|
+
* `GET /api/dsh-voice/update` — 获取更新检查状态与版本信息
|
|
147
|
+
|
|
148
|
+
### 🔒 配置文件锁与操作员恢复机制
|
|
149
|
+
在安装或更新插件时,DeepSeek Harness 使用 `<profile-dir>/package.json.lock` 协调并发操作。
|
|
150
|
+
* **规范竞争者策略**: 竞争者进程绝不删除现有的锁定文件。禁止竞争者自动清理残留锁,以杜绝 TOCTOU 竞态条件。
|
|
151
|
+
* **诊断信息**: 若存在 lock 文件,更新请求将返回 `409 Conflict` 并附带诊断说明(关联 PID 或文件系统访问错误码 `EACCES`/`EIO`)。
|
|
152
|
+
* **操作员手动恢复**: 若先前安装进程异常终止(如 OOM 或系统断电)留下僵死锁,需由操作员手动清理:
|
|
153
|
+
```bash
|
|
154
|
+
rm /home/vadim/.dsh/profiles/web/package.json.lock
|
|
155
|
+
```
|
|
145
156
|
|
|
146
157
|
---
|
|
147
158
|
|
package/lib/client.js
CHANGED
|
@@ -219,6 +219,9 @@ window.__ModuleLoader__.load({
|
|
|
219
219
|
'updateSuccess': 'Updated successfully! Restart DSH to apply.',
|
|
220
220
|
'updateFailed': 'Update failed. Check logs.',
|
|
221
221
|
'updateCheckFailed': 'Update check failed',
|
|
222
|
+
'profileLockedPid': 'Profile is locked by process (PID {pid}). Operator recovery is required if stale: remove package.json.lock.',
|
|
223
|
+
'profileLockedExisting': 'Profile is locked by an existing lockfile. Operator recovery is required if stale: remove package.json.lock.',
|
|
224
|
+
'profileLockCheckError': 'Profile lock check failed. Filesystem inspection or operator recovery required.',
|
|
222
225
|
'checkingUpdates': 'Checking updates…',
|
|
223
226
|
'updateStatusUnknown': 'Version status unknown',
|
|
224
227
|
'sensevoiceTitle': 'SenseVoice Local ASR (1-Click Setup)',
|
|
@@ -434,6 +437,9 @@ window.__ModuleLoader__.load({
|
|
|
434
437
|
'updateSuccess': '更新成功!请重启 DSH 生效。',
|
|
435
438
|
'updateFailed': '更新失败,请查看日志。',
|
|
436
439
|
'updateCheckFailed': '检查更新失败',
|
|
440
|
+
'profileLockedPid': '配置文件已被进程锁定 (PID {pid})。若残留请由操作员手动处理:删除 package.json.lock。',
|
|
441
|
+
'profileLockedExisting': '配置文件已被现有锁定文件锁定。若残留请由操作员手动处理:删除 package.json.lock。',
|
|
442
|
+
'profileLockCheckError': '检查配置文件锁失败。需要排查文件系统或由操作员恢复。',
|
|
437
443
|
'checkingUpdates': '正在检查更新…',
|
|
438
444
|
'updateStatusUnknown': '版本状态未知',
|
|
439
445
|
'sensevoiceTitle': 'SenseVoice 本地 ASR (一键部署)',
|
|
@@ -3084,6 +3090,17 @@ voice._chainsPromise = null
|
|
|
3084
3090
|
})
|
|
3085
3091
|
const data = await res.json().catch(() => ({}))
|
|
3086
3092
|
if (!res.ok || data.ok === false) {
|
|
3093
|
+
if (data.code) {
|
|
3094
|
+
const base = t('profileLockCheckError')
|
|
3095
|
+
throw new Error(base ? `${base} (${data.code})` : (data.error || `HTTP ${res.status}`))
|
|
3096
|
+
}
|
|
3097
|
+
if (data.locked) {
|
|
3098
|
+
const key = data.pid ? 'profileLockedPid' : 'profileLockedExisting'
|
|
3099
|
+
const translated = t(key)
|
|
3100
|
+
if (translated && translated !== key) {
|
|
3101
|
+
throw new Error(data.pid ? translated.replace('{pid}', String(data.pid)) : translated)
|
|
3102
|
+
}
|
|
3103
|
+
}
|
|
3087
3104
|
throw new Error(data.error || `HTTP ${res.status}`)
|
|
3088
3105
|
}
|
|
3089
3106
|
setUpdaterMsg(data.message || t('updateSuccess'))
|
package/lib/updater.js
CHANGED
|
@@ -208,11 +208,24 @@ export function checkProfileLock(profileDir) {
|
|
|
208
208
|
return { locked: true, pid };
|
|
209
209
|
}
|
|
210
210
|
|
|
211
|
+
export function formatLockDiagnostic(lockStatus) {
|
|
212
|
+
if (!lockStatus?.locked) return null;
|
|
213
|
+
if (lockStatus.error) {
|
|
214
|
+
return `Profile lock check failed with error ${lockStatus.error}. Operator recovery or filesystem inspection required.`;
|
|
215
|
+
}
|
|
216
|
+
if (lockStatus.pid) {
|
|
217
|
+
return `Profile is locked by process (PID ${lockStatus.pid}). If previous installation crashed, operator recovery is required: remove package.json.lock manually.`;
|
|
218
|
+
}
|
|
219
|
+
return 'Profile is locked by an existing lockfile. If previous installation crashed, operator recovery is required: remove package.json.lock manually.';
|
|
220
|
+
}
|
|
221
|
+
|
|
211
222
|
async function installExact(target, packageSpec, options) {
|
|
212
223
|
if (target.cliEntry === undefined) throw new Error('Automatic update is unavailable in this runtime.');
|
|
213
224
|
const lockCheck = checkProfileLock(target.profileDir);
|
|
214
225
|
if (lockCheck.locked) {
|
|
215
|
-
|
|
226
|
+
const err = new Error(formatLockDiagnostic(lockCheck));
|
|
227
|
+
if (lockCheck.error) err.code = lockCheck.error;
|
|
228
|
+
throw err;
|
|
216
229
|
}
|
|
217
230
|
await new Promise((resolvePromise, reject) => {
|
|
218
231
|
const child = spawn(process.execPath, [
|
|
@@ -295,8 +308,11 @@ export function registerPluginUpdater(ctx, options) {
|
|
|
295
308
|
if (installing || lockStatus.locked) {
|
|
296
309
|
json(response, 409, {
|
|
297
310
|
error: lockStatus.locked
|
|
298
|
-
?
|
|
311
|
+
? formatLockDiagnostic(lockStatus)
|
|
299
312
|
: 'This plugin is already updating.',
|
|
313
|
+
code: lockStatus.error,
|
|
314
|
+
locked: Boolean(lockStatus.locked),
|
|
315
|
+
pid: lockStatus.pid ?? null,
|
|
300
316
|
});
|
|
301
317
|
return;
|
|
302
318
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-voice",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.26",
|
|
4
4
|
"description": "Voice input for DeepSeek Harness: dictation chunked by pauses and voice messages, each with its own provider fallback chain (Deepgram, Groq, HuggingFace, local whisper.cpp, plus any OpenAI-compatible API of your own).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|