diff --git a/tools/sync_wsl.py b/tools/sync_wsl.py new file mode 100644 index 0000000..5a742f9 --- /dev/null +++ b/tools/sync_wsl.py @@ -0,0 +1,84 @@ +# -*- coding: utf-8 -*- +""" +sync_wsl.py — WSL 插件/配置批量同步(Win 生效份 -> WSL 生效份)。 +用法: python tools/sync_wsl.py [--only 名字 ...] +机制: 每个文件经 `wsl -- bash -c "cat > dst && sha256sum dst"` stdin 管道推送 + (绕 UNC OSError 22), 双侧 sha256 校验, 逐个报告, 任一失败退出码 1。 +同步清单(按「实际加载路径」对齐, 不是源码路径): + compaction-cn.ts / ai-mcp.ts / oc-session-all/tui.js / mcpcfg.txt +""" +import argparse +import hashlib +import io +import os +import subprocess +import sys + +try: + sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace") +except Exception: + pass + +DISTRO = "Ubuntu-24.04" +WIN = os.path.join(os.path.expanduser("~"), ".config", "opencode") +ITEMS = [ + ("compaction-cn", os.path.join(WIN, "plugins", "compaction-cn.ts"), + "/root/.config/opencode/plugins/compaction-cn.ts"), + ("ai-mcp", os.path.join(WIN, "plugins", "ai-mcp.ts"), + "/root/.config/opencode/plugins/ai-mcp.ts"), + ("tui.js", os.path.join(WIN, "plugins", "oc-session-all", "tui.js"), + "/root/.config/opencode/plugins/oc-session-all/tui.js"), + ("mcpcfg", os.path.join(WIN, "mcpcfg.txt"), + "/root/.config/opencode/mcpcfg.txt"), +] + + +def sha256(b): + return hashlib.sha256(b).hexdigest() + + +def sync_one(name, local, remote): + if not os.path.exists(local): + print("[%s] SKIP 本地不存在: %s" % (name, local)) + return False + data = open(local, "rb").read() + local_sha = sha256(data) + cmd = 'cat > "%s" && sha256sum "%s"' % (remote, remote) + try: + r = subprocess.run( + ["wsl.exe", "-d", DISTRO, "--", "bash", "-c", cmd], + input=data, capture_output=True, timeout=120, + ) + except Exception as e: + print("[%s] FAIL 启动 wsl.exe 异常: %s" % (name, e)) + return False + if r.returncode != 0: + err = (r.stderr or b"").decode("utf-8", errors="replace")[:200] + print("[%s] FAIL push rc=%d %s" % (name, r.returncode, err)) + return False + out = r.stdout or b"" + text = out.decode("utf-8", errors="replace") + if "sha256" not in text and len(text.split()) < 2: + text = out.decode("utf-16-le", errors="replace") + parts = text.split() + remote_sha = parts[0].strip() if parts else "" + ok = remote_sha == local_sha + print("[%s] %s %d bytes sha=%s" % (name, "OK " if ok else "FAIL", len(data), local_sha[:12])) + return ok + + +def main(): + ap = argparse.ArgumentParser() + ap.add_argument("--only", nargs="*", default=None, help="只同步指定名字") + args = ap.parse_args() + items = [it for it in ITEMS if not args.only or it[0] in args.only] + ok = 0 + for name, local, remote in items: + if sync_one(name, local, remote): + ok += 1 + print("result: %d/%d OK" % (ok, len(items))) + return 0 if ok == len(items) else 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tui-plugins/oc-session-all.js b/tui-plugins/oc-session-all.js index 239a3e1..b5dd427 100644 --- a/tui-plugins/oc-session-all.js +++ b/tui-plugins/oc-session-all.js @@ -582,6 +582,48 @@ export default Plugin.define({ return parts.join("|") } + // ── 一手防御:mcpcfg.txt 实时监测 + 自动重建(2026-10-12)───────────── + // 背景:OC 升级/更新会删掉 ~/.config/opencode 下的非官方文件(实测 + // mcpcfg.txt、同步脚本都被删过)→ 插件判 mode=off → /mcpon /mcpoff + // 整个不注册。这里在 watcher tick 里做实时兜底:文件丢了立刻按同目录 + // 现存配置重建(模式=自动);重建后需重启 OC 才会重注册命令(toast 提示)。 + const mcpcfgRebuiltAt = { ts: 0 } + function ensureMcpcfgAlive() { + try { + if (existsSync(CFG_TXT)) return true + const active = join(LOG_DIR, "opencode.json") + if (!existsSync(active)) { + log("[oc-session-all] 防御: mcpcfg.txt 与 opencode.json 均缺失,无法自动重建") + return false + } + const has = activeHasMcp(active) + const shadow = healShadowPath(active, "") + if (!shadow || !existsSync(shadow)) { + log("[oc-session-all] 防御: mcpcfg.txt 丢失且未找到影子配置,无法自动重建(shadow=" + shadow + ")") + return false + } + const onPath = has ? active : shadow + const quietPath = has ? shadow : active + if (!writeMcpCfg("auto", onPath, quietPath)) return false + log("[oc-session-all] 防御: mcpcfg.txt 丢失已自动重建(模式=自动 on=" + onPath + " quiet=" + quietPath + ")") + const now = Date.now() + if (now - mcpcfgRebuiltAt.ts > 60000) { + mcpcfgRebuiltAt.ts = now + try { + context.ui.toast.show({ + message: "mcpcfg.txt 已自动重建(重启 OC 后 /mcpon /mcpoff 恢复)", + variant: "warning", + duration: 6000, + }) + } catch (e) {} + } + return true + } catch (e) { + log("[oc-session-all] ensureMcpcfg ERROR: " + e.message) + return false + } + } + const presentDirs = skillDirs.filter((d) => existsSync(d)) let lastSkillSig = skillSignature() log( @@ -634,6 +676,8 @@ export default Plugin.define({ setInterval(() => { try { + // 一手防御:mcpcfg.txt 被 OC 更新删除时自动重建(健康时仅一次 existsSync,开销可忽略) + ensureMcpcfgAlive() const sig = skillSignature() if (sig === lastSkillSig) return lastSkillSig = sig