修复 execDbWrite 写死 python 导致 WSL 停用/恢复全失效 + toast 死循环
- execDbWrite spawn 可执行名跨平台:win32 用 python,其余用 python3(与 readCatalog/readCatalogTime 一致) WSL Ubuntu 24.04 无 python,spawn-fail -> 停用/恢复出厂全失效(截图实锤) - reconcile 重放失败(spawn-fail/unknown op/Traceback/rc>=1)时不写成功 flush: 此前每 60s force 对账轮发现回潮 -> 重放失败 -> 照写 flush -> TUI 每 60s reload+toast 死循环
This commit is contained in:
+29
-6
@@ -303,7 +303,12 @@ sys.stdout.buffer.write(row[0].encode("utf-8"))
|
||||
// 执行 DB 写(fork Python 子进程,独立 SQLite 连接)
|
||||
async function execDbWrite(op: string, payload: any): Promise<string> {
|
||||
return new Promise((resolve) => {
|
||||
const r = spawn("python", [DB_EXEC, op, JSON.stringify(payload)], {
|
||||
// ★ 跨平台可执行名:WSL(Ubuntu 24.04) 无 "python" 只有 "python3",
|
||||
// 写死 "python" 会 spawn-fail(Executable not found in $PATH)→ 停用/恢复全失效,
|
||||
// 且 outbox 条目永不清队 → daemon 每 5s 重试失败 → 反复写 flush → TUI 反复 toast。
|
||||
// 与 readCatalog / readCatalogTime 的分流保持一致(2026-10-20 截图实锤)。
|
||||
const py = process.platform === "win32" ? "python" : "python3"
|
||||
const r = spawn(py, [DB_EXEC, op, JSON.stringify(payload)], {
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
windowsHide: true, // 防 Windows 弹黑色控制台窗
|
||||
})
|
||||
@@ -709,9 +714,10 @@ let recTicks = 0
|
||||
async function reconcile(ctx: any): Promise<void> {
|
||||
ensureCfg()
|
||||
const stopList = readStopList()
|
||||
const removed = loadRemoved() // 2026-10-20:已删除厂商也纳入压制(修 agnes 残留在 catalog)
|
||||
const mirror = loadMirror()
|
||||
const hasMirror = Object.values(mirror).some((ids) => ids.length > 0)
|
||||
if (stopList.length === 0 && !hasMirror) return
|
||||
if (stopList.length === 0 && removed.length === 0 && !hasMirror) return
|
||||
|
||||
recTicks++
|
||||
const t = await readCatalogTime()
|
||||
@@ -733,9 +739,10 @@ async function reconcile(ctx: any): Promise<void> {
|
||||
const p = body[id] || {}
|
||||
const name = p.name || ""
|
||||
const models = p.models || {}
|
||||
// cfg 停用列表命中 → 真删除厂商节点(不能只清空 models:OC 刷新 catalog 会把
|
||||
// provider 节点连同模型一起加回来,只有摘掉节点才算真删除)
|
||||
if (stopList.length && stopListHits(stopList, id, name)) {
|
||||
// cfg 停用列表 或 removed.json 命中 → 真删除厂商节点(不能只清空 models:
|
||||
// OC 刷新 catalog 会把 provider 节点连同模型一起加回来,只有摘掉节点才算真删除)
|
||||
const inRemoved = removed.includes(id)
|
||||
if ((stopList.length && stopListHits(stopList, id, name)) || inRemoved) {
|
||||
plan.push({ op: "remove_provider", payload: { provider: id } })
|
||||
continue
|
||||
}
|
||||
@@ -749,10 +756,26 @@ async function reconcile(ctx: any): Promise<void> {
|
||||
}
|
||||
if (plan.length === 0) return
|
||||
|
||||
fileLog(`回潮检测:OC 刷新 catalog 使 ${plan.length} 项停用失效,开始重放压制(cfg=${stopList.length} 项 provider 级 + 镜像 modelID 级)`)
|
||||
fileLog(`回潮检测:OC 刷新 catalog 使 ${plan.length} 项压制失效,开始重放(cfg=${stopList.length} + removed=${removed.length} + 镜像 modelID 级)`)
|
||||
let okCount = 0
|
||||
for (const step of plan) {
|
||||
const r = await execDbWrite(step.op, step.payload)
|
||||
fileLog(`重放 ${step.op} ${JSON.stringify(step.payload)}: ${r}`)
|
||||
// ★ 堵假成功(2026-10-20 截图实锤):重放失败绝不能写成功 flush。
|
||||
// 否则 WSL 侧 spawn-fail(无 python)→ catalog 从未真改 → 每 60s force 轮
|
||||
// 又发现"回潮" → 又失败 → 又 flush → TUI 每 60s reload + toast 死循环。
|
||||
const rs = String(r)
|
||||
const stepFailed =
|
||||
rs.startsWith("unknown op") ||
|
||||
rs.startsWith("spawn-fail") ||
|
||||
rs.startsWith("Traceback") ||
|
||||
/^rc=[1-9]/.test(rs)
|
||||
if (!stepFailed) okCount++
|
||||
}
|
||||
if (okCount === 0) {
|
||||
fileLog("✗ reconcile 重放全部失败(DB 一个都没改成),不写 flush、不触发 TUI 重载")
|
||||
recLastTime = await readCatalogTime() // 防止下一轮立刻重试刷屏;等 catalog 真变化再试
|
||||
return
|
||||
}
|
||||
recLastTime = await readCatalogTime()
|
||||
await finishBatch(ctx, "reconcile")
|
||||
|
||||
Reference in New Issue
Block a user