新增 WSL 插件批量同步工具 tools/sync_wsl.py;TUI 插件为 mcpcfg.txt 增加实时监测与丢失自动重建防御
This commit is contained in:
@@ -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())
|
||||||
@@ -582,6 +582,48 @@ export default Plugin.define({
|
|||||||
return parts.join("|")
|
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))
|
const presentDirs = skillDirs.filter((d) => existsSync(d))
|
||||||
let lastSkillSig = skillSignature()
|
let lastSkillSig = skillSignature()
|
||||||
log(
|
log(
|
||||||
@@ -634,6 +676,8 @@ export default Plugin.define({
|
|||||||
|
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
try {
|
try {
|
||||||
|
// 一手防御:mcpcfg.txt 被 OC 更新删除时自动重建(健康时仅一次 existsSync,开销可忽略)
|
||||||
|
ensureMcpcfgAlive()
|
||||||
const sig = skillSignature()
|
const sig = skillSignature()
|
||||||
if (sig === lastSkillSig) return
|
if (sig === lastSkillSig) return
|
||||||
lastSkillSig = sig
|
lastSkillSig = sig
|
||||||
|
|||||||
Reference in New Issue
Block a user