dsh-calibguard 0.3.4 → 0.3.5
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.en.md +6 -2
- package/README.md +6 -2
- package/docs/configuration.md +1 -1
- package/docs/troubleshooting.md +1 -0
- package/lib/client.js +91 -9
- package/lib/engine.js +125 -0
- package/lib/routes.js +29 -0
- package/package.json +1 -1
package/README.en.md
CHANGED
|
@@ -4,7 +4,7 @@ English | [中文](README.md)
|
|
|
4
4
|
|
|
5
5
|

|
|
6
6
|

|
|
7
|
-

|
|
8
8
|
[](https://www.npmjs.com/package/dsh-calibguard)
|
|
9
9
|
|
|
10
10
|
Plugs the [CalibGuard](https://github.com/P-436/CG) file-security auditing engine into
|
|
@@ -85,6 +85,10 @@ checks — use at your own risk) or `node setup.mjs --cpu` (force CPU), or the e
|
|
|
85
85
|
> The CPU and CUDA builds of torch **cannot coexist in one venv** (same package name; installing one replaces
|
|
86
86
|
> the other). The CUDA build is a full torch and falls back to CPU when no GPU is available, so coexistence is
|
|
87
87
|
> unnecessary.
|
|
88
|
+
>
|
|
89
|
+
> **Machines that already have the CPU build**: with ≥0.3.5 the panel header detects "NVIDIA … but torch is the
|
|
90
|
+
> CPU build" and offers a **one-click "Reinstall GPU torch (~2.5 GB)"** button (uninstalls the CPU build, installs
|
|
91
|
+
> CUDA, then restarts the bridge). CLI equivalent: `node "<plugin dir>/setup.mjs" --reinstall-torch --force-cuda`.
|
|
88
92
|
|
|
89
93
|
**Where to watch progress**: the panel shows a "First run: preparing engine and dependencies" card with
|
|
90
94
|
the current step and the tail of the installer output; host logs show `[calibguard]` lines. The audit
|
|
@@ -295,7 +299,7 @@ dsh-CalibGuard/
|
|
|
295
299
|
├── engine/ Bundled engine core copied from CG_GUI (code / tokenizer / calibration, no weights)
|
|
296
300
|
├── cordis.patch.yml Bundle patch (inserts the loader row)
|
|
297
301
|
├── scripts/ link-dsh (dev-time junction), bridge-selftest (engine self-check)
|
|
298
|
-
├── test/ node --test (
|
|
302
|
+
├── test/ node --test (103 cases; auto-skip when the engine env is missing)
|
|
299
303
|
├── .github/workflows/ publish.yml: npm trusted publishing (OIDC, optional)
|
|
300
304
|
└── docs/ configuration, performance, troubleshooting, plugin contract, design notes
|
|
301
305
|
```
|
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|

|
|
6
6
|

|
|
7
|
-

|
|
8
8
|
[](https://www.npmjs.com/package/dsh-calibguard)
|
|
9
9
|
|
|
10
10
|
把 [CalibGuard](https://github.com/P-436/CG) 的文件安全审计引擎接进 **DeepSeek Harness(DSH)**:
|
|
@@ -78,6 +78,10 @@ GitHub 直连失败会自动换 gh-proxy / ghfast / ghproxy 镜像;pip 依赖
|
|
|
78
78
|
|
|
79
79
|
> CPU 版与 CUDA 版 torch **不能同时存在于同一个 venv**(同名包会互相覆盖);
|
|
80
80
|
> 但 CUDA 版 torch 本身就是完整 torch,没有 GPU 时会自动用 CPU,所以不需要共存。
|
|
81
|
+
>
|
|
82
|
+
> **已经有 CPU 版的机器**:升级到 ≥0.3.5 后,面板顶部会显示「检测到 NVIDIA … 但当前 torch 是 CPU 版」
|
|
83
|
+
> 并提供 **「一键重装 GPU 版 torch(约 2.5 GB)」** 按钮(会先卸载 CPU 版再装 CUDA 版,完成后自动重启桥接);
|
|
84
|
+
> 也可以用命令行:`node "<插件目录>/setup.mjs" --reinstall-torch --force-cuda`。
|
|
81
85
|
|
|
82
86
|
**进度在哪看**:面板顶部会出现「首次运行:正在自动准备引擎与依赖」卡片(含当前步骤与输出尾巴);
|
|
83
87
|
宿主日志里是 `[calibguard]` 开头的行。准备期间审计按钮不可用,完成后点「刷新状态」即可。
|
|
@@ -282,7 +286,7 @@ dsh-CalibGuard/
|
|
|
282
286
|
├── engine/ 从 CG_GUI 复制来的引擎核心(代码/词表/校准参数,不含 475 MB 权重)
|
|
283
287
|
├── cordis.patch.yml 组合包 patch(把插件行插进 profile 配置树)
|
|
284
288
|
├── scripts/ link-dsh(开发期挂包)、bridge-selftest(引擎自检)
|
|
285
|
-
├── test/ node --test(
|
|
289
|
+
├── test/ node --test(103 个用例;缺引擎环境时自动 skip)
|
|
286
290
|
├── .github/workflows/ publish.yml:npm 可信发布(OIDC,可选)
|
|
287
291
|
└── docs/ 配置全表、性能、排错、插件契约、实现决策
|
|
288
292
|
```
|
package/docs/configuration.md
CHANGED
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
|
|
31
31
|
> **GPU 定档**:安装器检测到 NVIDIA 且 CUDA ≥ 12.8、算力 ≥ 7.5 时自动装 CUDA(cu128)版 torch,
|
|
32
32
|
> 否则装 CPU 版。已经装成 CPU 版时:在插件目录运行 `node setup.mjs --reinstall-torch`。
|
|
33
|
-
> 手动覆盖:`--force-cuda` / `--cpu`(或环境变量)决定**安装哪个 build
|
|
33
|
+
> 手动覆盖:`--force-cuda` / `--cpu`(或环境变量)决定**安装哪个 build**(面板顶部也有「一键重装 GPU 版 torch」按钮,走 `POST /install-torch`);插件配置 `device`
|
|
34
34
|
> (auto/cpu/cuda)决定**运行时用哪个设备**。CPU 版与 CUDA 版 torch 不能共存于同一个 venv
|
|
35
35
|
>(同名包互斥);CUDA 版在无 GPU 时也能跑 CPU。
|
|
36
36
|
|
package/docs/troubleshooting.md
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
| **审计结果在哪里 / 怎么清空** | 引擎 `reports/` 目录(默认 `<engineRoot>/reports`):每次审计生成 JSON + Markdown;面板结果区显示路径并有「清空结果」,工具返回 `report_md_path` / `report_json_path` |
|
|
14
14
|
| **每次审计都提示"首次会加载模型"** | 0.3.3 起仅在模型未加载时提示;加载后不再出现,顶部状态显示「模型 已加载」 |
|
|
15
15
|
| **有 NVIDIA 显卡但引擎显示 CPU** | 装的是 CPU 版 torch。升级到 ≥0.3.3(面板顶部会提示),然后在插件目录运行 `node setup.mjs --reinstall-torch`(约 2.5 GB)装 CUDA 版;装完重启 DSH。`DSH_CALIBGUARD_CPU_ONLY=1` 可反向强制 CPU |
|
|
16
|
+
| **已经装了 CPU 版 torch,想换成 GPU 版** | 升级到 ≥0.3.5:面板顶部点「一键重装 GPU 版 torch(约 2.5 GB)」(先卸载再装,完成自动重启桥接);或命令行 `node "<插件目录>/setup.mjs" --reinstall-torch --force-cuda` |
|
|
16
17
|
| **想强制装 GPU / CPU 版 torch** | 安装时加 `--force-cuda` / `--cpu`(可配合 `--reinstall-torch` 覆盖已装版本);CPU 版与 CUDA 版不能共存于同一 venv,CUDA 版在无 GPU 时会自动回落 CPU |
|
|
17
18
|
| **首次安装特别慢 / 提示某个源不可用** | 模型默认 4 路并发分段下载(`DSH_CALIBGUARD_DOWNLOAD_CONNECTIONS` 可调),GitHub 失败自动换 gh-proxy/ghfast/ghproxy 镜像;pip 自动在 清华/阿里/中科大/腾讯/官方 之间回退。实在慢可手动下载 `calibguard-model-1.0.0.zip` 放到缓存目录(见下一条) |
|
|
18
19
|
| **模型下载失败 / 想离线** | 手动下载 `calibguard-model-1.0.0.zip`(Release `model-v1.0.0`),解压出 `model.safetensors` 放到 `~/.dsh/calibguard-tools/model-cache/model.safetensors`(或 `<engineRoot>/models/codebert_finetuned/`),重跑安装即会校验 SHA 并跳过下载 |
|
package/lib/client.js
CHANGED
|
@@ -345,6 +345,11 @@ window.__ModuleLoader__.load({
|
|
|
345
345
|
var credName = credNameState[0]
|
|
346
346
|
var setCredName = credNameState[1]
|
|
347
347
|
|
|
348
|
+
// torch 重装提示(面板按钮/结果)
|
|
349
|
+
var torchMsgState = useState('')
|
|
350
|
+
var torchMsg = torchMsgState[0]
|
|
351
|
+
var setTorchMsg = torchMsgState[1]
|
|
352
|
+
|
|
348
353
|
var busyState = useState(false)
|
|
349
354
|
var busy = busyState[0]
|
|
350
355
|
var setBusy = busyState[1]
|
|
@@ -382,6 +387,15 @@ window.__ModuleLoader__.load({
|
|
|
382
387
|
loadSettings()
|
|
383
388
|
}, [])
|
|
384
389
|
|
|
390
|
+
useEffect(function () {
|
|
391
|
+
if (!torchRunning) return undefined
|
|
392
|
+
var timer = setInterval(refreshStatus, 3000)
|
|
393
|
+
return function () { clearInterval(timer) }
|
|
394
|
+
}, [torchRunning])
|
|
395
|
+
useEffect(function () {
|
|
396
|
+
if (torchState === 'done') refreshStatus()
|
|
397
|
+
}, [torchState])
|
|
398
|
+
|
|
385
399
|
function applyStatus(payload) {
|
|
386
400
|
var data = (payload && payload.status) || {}
|
|
387
401
|
var llm = data.llm || {}
|
|
@@ -554,6 +568,19 @@ window.__ModuleLoader__.load({
|
|
|
554
568
|
})
|
|
555
569
|
}
|
|
556
570
|
|
|
571
|
+
function installTorch(mode) {
|
|
572
|
+
setTorchMsg('已启动重装 torch(后台进行,面板自动刷新进度)…')
|
|
573
|
+
apiFetch('/install-torch', {
|
|
574
|
+
method: 'POST',
|
|
575
|
+
headers: { 'content-type': 'application/json' },
|
|
576
|
+
body: JSON.stringify({ mode: mode }),
|
|
577
|
+
}).then(function () {
|
|
578
|
+
refreshStatus()
|
|
579
|
+
}).catch(function (err) {
|
|
580
|
+
setTorchMsg('重装启动失败:' + String((err && err.message) || err))
|
|
581
|
+
})
|
|
582
|
+
}
|
|
583
|
+
|
|
557
584
|
function clearResult() {
|
|
558
585
|
setResult(null)
|
|
559
586
|
setReport(null)
|
|
@@ -610,9 +637,15 @@ window.__ModuleLoader__.load({
|
|
|
610
637
|
|| ''
|
|
611
638
|
var devicePolicy = (engineInfo && engineInfo.devicePolicy) || null
|
|
612
639
|
var setupScript = (status && status.setupScript) || '<插件目录>/setup.mjs'
|
|
613
|
-
|
|
640
|
+
var torchJob = (status && status.torchJob) || null
|
|
641
|
+
var torchRunning = Boolean(torchJob && torchJob.state === 'running')
|
|
642
|
+
var torchState = (torchJob && torchJob.state) || 'idle'
|
|
643
|
+
var gpuInfo = (status && status.gpuTorch) || null
|
|
644
|
+
// 有 NVIDIA 显卡、但 torch 没启用 CUDA(装的是 CPU 版)→ 提示并允许一键重装
|
|
614
645
|
var gpuTorchHint = engineInfo && engineInfo.cudaAvailable === false
|
|
615
|
-
&&
|
|
646
|
+
&& ((gpuInfo && gpuInfo.gpuName) || (devicePolicy && devicePolicy.gpuName))
|
|
647
|
+
? ((gpuInfo && gpuInfo.gpuName) || (devicePolicy && devicePolicy.gpuName))
|
|
648
|
+
: ''
|
|
616
649
|
var engineReady = Boolean(engineInfo)
|
|
617
650
|
var envBusy = Boolean(envInfo) && !engineReady && envInfo.state !== 'failed'
|
|
618
651
|
var envFailed = Boolean(envInfo) && !engineReady && envInfo.state === 'failed'
|
|
@@ -646,13 +679,62 @@ window.__ModuleLoader__.load({
|
|
|
646
679
|
+ ' | ' + devicePolicy.reason,
|
|
647
680
|
})
|
|
648
681
|
: null,
|
|
649
|
-
gpuTorchHint
|
|
650
|
-
?
|
|
651
|
-
style: S.
|
|
652
|
-
children:
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
682
|
+
gpuTorchHint && torchState !== 'running'
|
|
683
|
+
? jsxs('div', {
|
|
684
|
+
style: S.card,
|
|
685
|
+
children: [
|
|
686
|
+
jsx('div', { style: S.cardTitle, children: '检测到 NVIDIA ' + gpuTorchHint + ',但当前 torch 是 CPU 版' }),
|
|
687
|
+
jsx('div', {
|
|
688
|
+
style: S.muted,
|
|
689
|
+
children: 'CPU 版与 GPU 版 torch 不能共存于同一个 venv,重装会先卸载当前的再装 CUDA(cu128)版'
|
|
690
|
+
+ '(约 2.5 GB,下载走 PyTorch 官方源)。装完桥接会自动重启,无需重启 DSH。',
|
|
691
|
+
}),
|
|
692
|
+
jsxs('div', {
|
|
693
|
+
style: S.row,
|
|
694
|
+
children: [
|
|
695
|
+
jsx('button', {
|
|
696
|
+
style: S.buttonPrimary,
|
|
697
|
+
onClick: function () { installTorch('cuda') },
|
|
698
|
+
children: '一键重装 GPU 版 torch(约 2.5 GB)',
|
|
699
|
+
}),
|
|
700
|
+
jsx('button', {
|
|
701
|
+
style: S.button,
|
|
702
|
+
onClick: function () { installTorch('auto') },
|
|
703
|
+
children: '按自动定档重装',
|
|
704
|
+
}),
|
|
705
|
+
torchMsg ? jsx('span', { style: S.muted, children: torchMsg }) : null,
|
|
706
|
+
],
|
|
707
|
+
}),
|
|
708
|
+
torchState === 'done'
|
|
709
|
+
? jsx('div', { style: { color: C.allow, fontSize: 12, marginTop: 6 }, children: torchMsg || 'torch 重装完成(如仍显示 CPU,点「刷新状态」)' })
|
|
710
|
+
: null,
|
|
711
|
+
torchState === 'failed'
|
|
712
|
+
? jsx('div', {
|
|
713
|
+
style: { color: C.error, fontSize: 12, whiteSpace: 'pre-wrap', marginTop: 6 },
|
|
714
|
+
children: '重装失败:' + ((torchJob && (torchJob.error || torchJob.message)) || '详见日志')
|
|
715
|
+
+ '\n手动方式:node "' + setupScript + '" --reinstall-torch --force-cuda',
|
|
716
|
+
})
|
|
717
|
+
: null,
|
|
718
|
+
jsx('div', {
|
|
719
|
+
style: S.note,
|
|
720
|
+
children: '不想用面板也可以手动运行:node "' + setupScript + '" --reinstall-torch --force-cuda',
|
|
721
|
+
}),
|
|
722
|
+
],
|
|
723
|
+
})
|
|
724
|
+
: null,
|
|
725
|
+
torchRunning
|
|
726
|
+
? jsxs('div', {
|
|
727
|
+
style: S.card,
|
|
728
|
+
children: [
|
|
729
|
+
jsx('div', { style: S.cardTitle, children: '正在重装 torch…' }),
|
|
730
|
+
jsx('div', { style: S.muted, children: (torchJob && (torchJob.step || torchJob.message)) || '准备中…' }),
|
|
731
|
+
torchJob && torchJob.tail && torchJob.tail.length > 0
|
|
732
|
+
? jsx('pre', {
|
|
733
|
+
style: Object.assign({}, S.pre, { maxHeight: 140, marginTop: 8 }),
|
|
734
|
+
children: torchJob.tail.slice(-5).join('\n'),
|
|
735
|
+
})
|
|
736
|
+
: null,
|
|
737
|
+
],
|
|
656
738
|
})
|
|
657
739
|
: null,
|
|
658
740
|
|
package/lib/engine.js
CHANGED
|
@@ -32,6 +32,8 @@ import { EnvironmentBootstrap, venvPythonPath } from './bootstrap.js'
|
|
|
32
32
|
|
|
33
33
|
/** 桥接脚本相对本文件的位置(spawn 需要真实路径,不能直接把 URL 对象交给子进程)。 */
|
|
34
34
|
const BRIDGE_SCRIPT = fileURLToPath(new URL('../bridge/calibguard_bridge.py', import.meta.url))
|
|
35
|
+
/** 安装器脚本(面板「重装 GPU 版 torch」会调用它)。 */
|
|
36
|
+
const SETUP_SCRIPT = fileURLToPath(new URL('../setup.mjs', import.meta.url))
|
|
35
37
|
|
|
36
38
|
/** 单次 status 请求的默认超时(毫秒):只做文件探测与 torch 导入,很快。 */
|
|
37
39
|
const STATUS_TIMEOUT_MS = 60_000
|
|
@@ -71,6 +73,9 @@ export class CalibGuardEngine {
|
|
|
71
73
|
this.statusCache = null
|
|
72
74
|
/** 最近一次凭据解析结果(只用于诊断,不缓存值给下次复用)。 */
|
|
73
75
|
this.lastCredential = { source: 'none', configured: false }
|
|
76
|
+
/** 面板「重装 torch」任务状态(CPU 版 → GPU 版一键替换用)。 */
|
|
77
|
+
this.torchJob = { state: 'idle', step: '', message: '', tail: [], startedAt: null, finishedAt: null, error: null }
|
|
78
|
+
this.torchChild = null
|
|
74
79
|
/**
|
|
75
80
|
* 首次运行自动准备环境(对齐 CG_GUI 的 launcher 首启装环境):
|
|
76
81
|
* 缺 `<engineRoot>/.venv` 时在后台跑 setup.mjs(内置引擎 + npm 模型分片 → 建 venv → 装依赖)。
|
|
@@ -156,6 +161,9 @@ export class CalibGuardEngine {
|
|
|
156
161
|
const ref = this.effectiveCredentialRef()
|
|
157
162
|
if (!ref) {
|
|
158
163
|
this.lastCredential = { source: 'none', configured: false }
|
|
164
|
+
/** 面板「重装 torch」任务状态(CPU 版 → GPU 版一键替换用)。 */
|
|
165
|
+
this.torchJob = { state: 'idle', step: '', message: '', tail: [], startedAt: null, finishedAt: null, error: null }
|
|
166
|
+
this.torchChild = null
|
|
159
167
|
return { key: undefined, source: 'none' }
|
|
160
168
|
}
|
|
161
169
|
|
|
@@ -274,6 +282,121 @@ export class CalibGuardEngine {
|
|
|
274
282
|
)
|
|
275
283
|
}
|
|
276
284
|
|
|
285
|
+
/**
|
|
286
|
+
* GPU/CPU torch 状态(面板据此决定是否显示「重装 GPU 版」)。
|
|
287
|
+
* 数据来自最近一次 status 快照:gpuName 仅当检测到 NVIDIA;cudaAvailable=torch 是否启用 CUDA。
|
|
288
|
+
*/
|
|
289
|
+
gpuTorchStatus() {
|
|
290
|
+
const status = this.statusCache
|
|
291
|
+
if (!status) return { gpuName: null, cudaAvailable: null, device: null, policyMode: null, reason: '' }
|
|
292
|
+
const policy = status.devicePolicy || {}
|
|
293
|
+
return {
|
|
294
|
+
gpuName: policy.gpuName ?? null,
|
|
295
|
+
cudaAvailable: status.cudaAvailable ?? null,
|
|
296
|
+
device: status.device ?? null,
|
|
297
|
+
policyMode: policy.mode ?? null,
|
|
298
|
+
reason: policy.reason ?? '',
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/** 重装 torch 任务快照。 */
|
|
303
|
+
torchJobSnapshot() {
|
|
304
|
+
const job = this.torchJob
|
|
305
|
+
return {
|
|
306
|
+
state: job.state,
|
|
307
|
+
step: job.step,
|
|
308
|
+
message: job.message,
|
|
309
|
+
startedAt: job.startedAt,
|
|
310
|
+
finishedAt: job.finishedAt,
|
|
311
|
+
error: job.error,
|
|
312
|
+
tail: [...job.tail],
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* 重装 torch(CPU 版 ↔ GPU 版互斥,先卸载再装),完成后停掉桥接让下次请求用新的 torch。
|
|
318
|
+
* @param {'auto'|'cuda'|'cpu'} [mode] auto=按自动定档;cuda=强制 CUDA 版(跳过兼容检查);cpu=强制 CPU 版
|
|
319
|
+
*/
|
|
320
|
+
async installTorch(mode = 'auto') {
|
|
321
|
+
if (this.torchJob.state === 'running') return this.torchJobSnapshot()
|
|
322
|
+
if (!['auto', 'cuda', 'cpu'].includes(mode)) throw new Error('mode 必须是 auto / cuda / cpu')
|
|
323
|
+
if (!existsSync(venvPythonPath(this.config.engineRoot))) {
|
|
324
|
+
throw new Error('环境还没准备好(缺 .venv):请先完成首次环境准备再重装 torch。')
|
|
325
|
+
}
|
|
326
|
+
this.torchJob = {
|
|
327
|
+
state: 'running', step: '', message: '正在重装 torch…', tail: [],
|
|
328
|
+
startedAt: Date.now(), finishedAt: null, error: null,
|
|
329
|
+
}
|
|
330
|
+
const extra = ['--reinstall-torch']
|
|
331
|
+
if (mode === 'cuda') extra.push('--force-cuda')
|
|
332
|
+
if (mode === 'cpu') extra.push('--cpu')
|
|
333
|
+
const args = [SETUP_SCRIPT, '--dest', this.config.engineRoot, '--skip-verify', ...extra]
|
|
334
|
+
this.logger?.info?.(`[calibguard] 重装 torch:node setup.mjs ${args.slice(1).join(' ')}`)
|
|
335
|
+
|
|
336
|
+
const finish = new Promise((resolvePromise) => {
|
|
337
|
+
const child = spawn(process.execPath, args, {
|
|
338
|
+
cwd: this.config.engineRoot,
|
|
339
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
340
|
+
windowsHide: true,
|
|
341
|
+
})
|
|
342
|
+
this.torchChild = child
|
|
343
|
+
const onLine = (line) => {
|
|
344
|
+
const text = String(line).trim()
|
|
345
|
+
if (!text) return
|
|
346
|
+
this.torchJob.tail.push(text)
|
|
347
|
+
if (this.torchJob.tail.length > 20) this.torchJob.tail.shift()
|
|
348
|
+
const stepMatch = /^==\s*(.+?)\s*==$/.exec(text)
|
|
349
|
+
if (stepMatch) {
|
|
350
|
+
this.torchJob.step = stepMatch[1]
|
|
351
|
+
this.torchJob.message = `${stepMatch[1]}…`
|
|
352
|
+
return
|
|
353
|
+
}
|
|
354
|
+
this.torchJob.message = text
|
|
355
|
+
}
|
|
356
|
+
const wire = (stream) => {
|
|
357
|
+
let buffer = ''
|
|
358
|
+
stream.setEncoding('utf8')
|
|
359
|
+
stream.on('data', (chunk) => {
|
|
360
|
+
buffer += chunk
|
|
361
|
+
const lines = buffer.split(/\r?\n/)
|
|
362
|
+
buffer = lines.pop() ?? ''
|
|
363
|
+
for (const line of lines) onLine(line)
|
|
364
|
+
})
|
|
365
|
+
stream.on('end', () => { if (buffer) onLine(buffer) })
|
|
366
|
+
}
|
|
367
|
+
wire(child.stdout)
|
|
368
|
+
wire(child.stderr)
|
|
369
|
+
child.on('error', (error) => {
|
|
370
|
+
this.torchJob.state = 'failed'
|
|
371
|
+
this.torchJob.error = String(error?.message ?? error)
|
|
372
|
+
this.torchJob.message = `重装失败:${this.torchJob.error}`
|
|
373
|
+
this.torchJob.finishedAt = Date.now()
|
|
374
|
+
this.torchChild = null
|
|
375
|
+
resolvePromise(this.torchJobSnapshot())
|
|
376
|
+
})
|
|
377
|
+
child.on('close', async (code) => {
|
|
378
|
+
this.torchChild = null
|
|
379
|
+
this.torchJob.finishedAt = Date.now()
|
|
380
|
+
if (code === 0) {
|
|
381
|
+
this.torchJob.state = 'done'
|
|
382
|
+
this.torchJob.step = ''
|
|
383
|
+
this.torchJob.message = 'torch 重装完成,桥接将重启以使用新的 torch'
|
|
384
|
+
this.statusCache = null
|
|
385
|
+
try { await this.stop('torch-reinstalled') } catch { /* 忽略 */ }
|
|
386
|
+
this.logger?.info?.('[calibguard] torch 重装完成,桥接已停止(下次调用重启)')
|
|
387
|
+
} else {
|
|
388
|
+
this.torchJob.state = 'failed'
|
|
389
|
+
this.torchJob.error = `setup.mjs 退出码 ${code}`
|
|
390
|
+
this.torchJob.message = `重装失败(${this.torchJob.error}):${this.torchJob.tail.slice(-3).join(' | ')}`
|
|
391
|
+
this.logger?.warn?.(`[calibguard] ${this.torchJob.message}`)
|
|
392
|
+
}
|
|
393
|
+
resolvePromise(this.torchJobSnapshot())
|
|
394
|
+
})
|
|
395
|
+
})
|
|
396
|
+
this.torchJob.promise = finish
|
|
397
|
+
return finish
|
|
398
|
+
}
|
|
399
|
+
|
|
277
400
|
// ============================== 进程生命周期 ==============================
|
|
278
401
|
|
|
279
402
|
/** 启动常驻桥接进程(已启动则直接返回)。子进程环境经过凭据清洗。 */
|
|
@@ -503,6 +626,8 @@ export class CalibGuardEngine {
|
|
|
503
626
|
credentialRef: this.effectiveCredentialRef() || null,
|
|
504
627
|
credentialWritable: typeof this.credentials?.set === 'function',
|
|
505
628
|
environment: this.bootstrap.snapshot(),
|
|
629
|
+
torchJob: this.torchJobSnapshot(),
|
|
630
|
+
gpuTorch: this.gpuTorchStatus(),
|
|
506
631
|
settings: this.settings(),
|
|
507
632
|
stderrTail: [...this.stderrTail],
|
|
508
633
|
}
|
package/lib/routes.js
CHANGED
|
@@ -149,6 +149,8 @@ export function createCalibGuardRoutes({ engine, config, logger, settingsStore }
|
|
|
149
149
|
credentialRef: diagnostics.credentialRef ?? null,
|
|
150
150
|
credentialWritable: Boolean(diagnostics.credentialWritable),
|
|
151
151
|
setupScript,
|
|
152
|
+
torchJob: typeof engine.torchJobSnapshot === 'function' ? engine.torchJobSnapshot() : null,
|
|
153
|
+
gpuTorch: typeof engine.gpuTorchStatus === 'function' ? engine.gpuTorchStatus() : null,
|
|
152
154
|
settings: engine.settings(),
|
|
153
155
|
settingsFile: settingsStore?.file ?? null,
|
|
154
156
|
})
|
|
@@ -180,6 +182,8 @@ export function createCalibGuardRoutes({ engine, config, logger, settingsStore }
|
|
|
180
182
|
credentialRef: diagnostics.credentialRef ?? null,
|
|
181
183
|
credentialWritable: Boolean(diagnostics.credentialWritable),
|
|
182
184
|
setupScript,
|
|
185
|
+
torchJob: typeof engine.torchJobSnapshot === 'function' ? engine.torchJobSnapshot() : null,
|
|
186
|
+
gpuTorch: typeof engine.gpuTorchStatus === 'function' ? engine.gpuTorchStatus() : null,
|
|
183
187
|
settings: engine.settings(),
|
|
184
188
|
settingsFile: settingsStore?.file ?? null,
|
|
185
189
|
})
|
|
@@ -216,6 +220,30 @@ export function createCalibGuardRoutes({ engine, config, logger, settingsStore }
|
|
|
216
220
|
}
|
|
217
221
|
}
|
|
218
222
|
|
|
223
|
+
/** POST /install-torch —— 面板「重装 GPU/CPU 版 torch」。启动后台任务,进度走 /status 的 torchJob。 */
|
|
224
|
+
async function handleInstallTorch(req, res) {
|
|
225
|
+
if (req.method !== 'POST') return sendJson(res, 405, { error: '仅支持 POST' })
|
|
226
|
+
try {
|
|
227
|
+
const body = await readBody(req, 1024 * 1024)
|
|
228
|
+
let payload
|
|
229
|
+
try {
|
|
230
|
+
payload = JSON.parse(body.toString('utf8') || '{}')
|
|
231
|
+
} catch {
|
|
232
|
+
return sendJson(res, 400, { error: '请求体必须是合法 JSON' })
|
|
233
|
+
}
|
|
234
|
+
const mode = typeof payload?.mode === 'string' ? payload.mode : 'auto'
|
|
235
|
+
if (typeof engine.installTorch !== 'function') {
|
|
236
|
+
return sendJson(res, 503, { error: '当前插件版本不支持重装 torch' })
|
|
237
|
+
}
|
|
238
|
+
engine.installTorch(mode).catch((error) => {
|
|
239
|
+
logger?.warn?.(`[calibguard] 重装 torch 失败:${error?.message ?? error}`)
|
|
240
|
+
})
|
|
241
|
+
sendJson(res, 200, { ok: true, started: true, mode })
|
|
242
|
+
} catch (error) {
|
|
243
|
+
sendJson(res, 400, { error: String(error?.message ?? error) })
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
219
247
|
/** POST /audit */
|
|
220
248
|
async function handleAudit(req, res) {
|
|
221
249
|
if (req.method !== 'POST') return sendJson(res, 405, { error: '仅支持 POST' })
|
|
@@ -308,6 +336,7 @@ export function createCalibGuardRoutes({ engine, config, logger, settingsStore }
|
|
|
308
336
|
state,
|
|
309
337
|
routes: [
|
|
310
338
|
{ kind: 'exact', path: `${ROUTE_PREFIX}/status`, handler: handleStatus },
|
|
339
|
+
{ kind: 'exact', path: `${ROUTE_PREFIX}/install-torch`, handler: handleInstallTorch },
|
|
311
340
|
{ kind: 'exact', path: `${ROUTE_PREFIX}/audit`, handler: handleAudit },
|
|
312
341
|
{ kind: 'exact', path: `${ROUTE_PREFIX}/report`, handler: handleReport },
|
|
313
342
|
{ kind: 'exact', path: `${ROUTE_PREFIX}/settings`, handler: async (req, res) => {
|