TUI插件v9:修复dirname导入缺失致自愈静默失效,新增reload前mcp配置推送兜底(双保险)
- 补回 node:path 的 dirname 导入:healShadowPath 引用它,缺导入时 ReferenceError 被自身 catch 吞掉,影子路径自愈静默失效 - 新增 pushMcpConfig:swap+flag 之后、reload 之前把活配置 mcp 推进 运行中 OC(SDK config.update → body 形参 → experimental 三连), 推送成功+reload 双保险,失败降级原单通道 - toast 区分双保险/单通道文案;log/toast 版本标识升级 v9 - 单测新增 S0 回归断言:源文件 path 导入必须含 dirname (prelude 复制的导入曾掩盖源码缺失,15/15 假绿 -> 16/16)
This commit is contained in:
@@ -9,7 +9,7 @@ import {
|
||||
statSync,
|
||||
rmSync,
|
||||
} from "fs"
|
||||
import { join, basename } from "path"
|
||||
import { join, basename, dirname } from "path"
|
||||
import { homedir } from "os"
|
||||
|
||||
// ── 日志(跨平台:Windows C:\Users\... / WSL /root)────────────────────
|
||||
@@ -265,6 +265,53 @@ async function doReload(context) {
|
||||
return false
|
||||
}
|
||||
|
||||
// P2 兜底:reload 之前先把 mcp 配置 PATCH 进运行中的 OC(PATCH /config)。
|
||||
// 这样「配置就位」不再依赖 reload 的读盘时序 —— 推送 + reload 双保险,
|
||||
// 保证 MCP 百分百加载;推送失败也只降级为原有 reload 单通道,不影响对调。
|
||||
async function pushMcpConfig(context, activePath) {
|
||||
try {
|
||||
const j = JSON.parse(readFileSync(activePath, "utf8"))
|
||||
const mcp = j && j.mcp
|
||||
if (!mcp || !mcp.servers || !Object.keys(mcp.servers).length) return false
|
||||
const errs = []
|
||||
const cfgApi = context.client && context.client.config
|
||||
if (cfgApi && typeof cfgApi.update === "function") {
|
||||
// SDK 签名:parameters.config -> body(PATCH /config)
|
||||
try {
|
||||
await cfgApi.update({ config: mcp })
|
||||
log("[oc-session-all] mcp 配置已推送进 OC(client.config.update → PATCH /config)")
|
||||
return true
|
||||
} catch (e) {
|
||||
errs.push("config.update: " + e.message)
|
||||
}
|
||||
try {
|
||||
await cfgApi.update({ body: { mcp } })
|
||||
log("[oc-session-all] mcp 配置已推送进 OC(body 变体)")
|
||||
return true
|
||||
} catch (e) {
|
||||
errs.push("config.update(body): " + e.message)
|
||||
}
|
||||
} else {
|
||||
errs.push("client.config.update 不存在")
|
||||
}
|
||||
const exp = context.client && context.client.experimental && context.client.experimental.config
|
||||
if (exp && typeof exp.update === "function") {
|
||||
try {
|
||||
await exp.update({ mcp })
|
||||
log("[oc-session-all] mcp 配置已推送进 OC(experimental.config.update)")
|
||||
return true
|
||||
} catch (e) {
|
||||
errs.push("experimental: " + e.message)
|
||||
}
|
||||
}
|
||||
log("[oc-session-all] mcp 配置推送未成功(继续走 reload 兜底): " + errs.join("; "))
|
||||
return false
|
||||
} catch (e) {
|
||||
log("[oc-session-all] mcp 配置推送异常(继续走 reload 兜底): " + e.message)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// 切换 MCP 开/关(对调 + reload)。返回 { ok, unchanged?, reloaded?, why }
|
||||
async function setMcp(context, wantOn) {
|
||||
const cfg = readMcpCfg()
|
||||
@@ -289,16 +336,29 @@ async function setMcp(context, wantOn) {
|
||||
try {
|
||||
if (wantOn) writeFileSync(ON_FLAG, String(process.pid))
|
||||
else if (existsSync(ON_FLAG)) rmSync(ON_FLAG)
|
||||
} catch (e) {}
|
||||
} catch (e) {
|
||||
log("[oc-session-all] mcp.on.flag 写入失败: " + e.message)
|
||||
}
|
||||
log("[oc-session-all] config swapped -> " + (wantOn ? "WITH mcp" : "QUIET (no mcp)"))
|
||||
// P2 兜底:开启时先推送再 reload(双保险;关闭无需推送)
|
||||
const pushed = wantOn ? await pushMcpConfig(context, cfg.activePath) : undefined
|
||||
const reloaded = await doReload(context)
|
||||
return { ok: true, reloaded, why: reloaded ? null : "配置已对调,但热重载调用失败(重启 OC 生效)" }
|
||||
return {
|
||||
ok: true,
|
||||
reloaded,
|
||||
pushed,
|
||||
why: reloaded ? null : "配置已对调,但热重载调用失败(重启 OC 生效)",
|
||||
}
|
||||
}
|
||||
|
||||
function toastResult(context, res, actionLabel) {
|
||||
let message
|
||||
if (!res.ok) message = actionLabel + "失败:" + res.why
|
||||
else if (res.unchanged) message = res.why
|
||||
else if (res.reloaded && res.pushed === true)
|
||||
message = actionLabel + "成功(mcp 配置已先送入 OC + 对调 + 热重载,双保险)"
|
||||
else if (res.reloaded && res.pushed === false)
|
||||
message = actionLabel + "成功(已对调并热重载;配置推送未生效,走 reload 兜底)"
|
||||
else if (res.reloaded) message = actionLabel + "成功(配置已对调并热重载)"
|
||||
else message = actionLabel + "成功,但热重载未完成:" + res.why
|
||||
try {
|
||||
@@ -310,7 +370,7 @@ function toastResult(context, res, actionLabel) {
|
||||
export default Plugin.define({
|
||||
id: "oc-session-all",
|
||||
async setup(context) {
|
||||
log("[oc-session-all] v8 loaded — 会话选择器 + MCP配置对调(mcpcfg.txt) + 热重载 + skill热加载")
|
||||
log("[oc-session-all] v9 loaded — 会话选择器 + MCP配置对调(mcpcfg.txt) + 推送兜底 + 热重载 + skill热加载")
|
||||
|
||||
// ── 读取 mcpcfg.txt(键值小白配置)────────────────────────────────
|
||||
const mcfg = readMcpCfg()
|
||||
@@ -724,7 +784,7 @@ export default Plugin.define({
|
||||
try {
|
||||
context.ui.toast.show({
|
||||
message:
|
||||
"oc-session-all v8 已加载 — /sessions-all 浏览全部会话(Ctrl+O)" +
|
||||
"oc-session-all v9 已加载 — /sessions-all 浏览全部会话(Ctrl+O)" +
|
||||
(featureOn ? ",/mcpon 开启 MCP、/mcpoff 关闭 MCP" : ""),
|
||||
variant: "info",
|
||||
duration: 20000,
|
||||
|
||||
@@ -50,6 +50,17 @@ function check(name, cond, extra) {
|
||||
}
|
||||
}
|
||||
|
||||
// ---- 回归:源文件 path 导入必须含 dirname ----
|
||||
// healShadowPath 用了 dirname;源文件缺导入时 ReferenceError 会被它自己的
|
||||
// catch 吞掉 → 影子自愈静默失效。prelude 里复制的导入会掩盖这个缺失
|
||||
// (曾导致 15/15 假绿),所以必须对源文件本体断言。
|
||||
const pathImportLine = (text.match(/^import\s*\{[^}]*\}\s*from\s*["']path["']/m) || [""])[0]
|
||||
check(
|
||||
"S0 源文件 path 导入含 dirname",
|
||||
/dirname/.test(pathImportLine),
|
||||
"import line=" + pathImportLine,
|
||||
)
|
||||
|
||||
// ---- 场景1:键值格式(注释+中文冒号)----
|
||||
const active = join(work, "opencode.json")
|
||||
const shadow = join(work, "opencode.json.2")
|
||||
|
||||
Reference in New Issue
Block a user