From 0f099c88d0d5a42abb437d99aee07a482ab16168 Mon Sep 17 00:00:00 2001 From: ypshidifu Date: Wed, 23 Sep 2026 17:50:26 +0800 Subject: [PATCH] =?UTF-8?q?TUI=E6=8F=92=E4=BB=B6v10=EF=BC=9AMCP=E6=8E=A8?= =?UTF-8?q?=E9=80=81=E5=85=9C=E5=BA=95=E6=94=B9GET+merge=E8=A1=A5shell?= =?UTF-8?q?=E4=B8=89=E8=B7=B3=EF=BC=8CREADME=E5=90=8C=E6=AD=A5=E5=AE=89?= =?UTF-8?q?=E8=A3=85=E8=B7=AF=E5=BE=84=E4=B8=8E=E5=BF=AB=E6=8D=B7=E9=94=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 19 ++++++++++--- tui-plugins/oc-session-all.js | 53 +++++++++++++++++++++++++++-------- 2 files changed, 57 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 254111e..6acb67a 100644 --- a/README.md +++ b/README.md @@ -9,21 +9,25 @@ OpenCode TUI 插件:弹出全项目会话选择器,跨所有项目浏览和 - 过滤掉已归档和子代理会话 - 对调2份`opencode.json`实现拦截mcp报错的toast - 热更新skill,新的skill丢入目录,插件自动发现并更新 +- **MCP 推送兜底(v10)**:`config.get` 取 shell(GET→活文件→null 三级兜底)→ + `config.update({config})` → `({body})` → `experimental.config.update` 三跳推进 + 运行中的 OC,配合 reload 双保险;`/mcpon` `/mcpoff` 可用 ## 安装 -在 `~/.config/opencode/tui.json` 中添加: +在 `~/.config/opencode/tui.json` 中添加(现役源码是 `.js`,部署副本放 `tui-plugins/` 下): ```json { "$schema": "https://opencode.ai/tui.json", "plugin": [ - "D:/桌面/源码/oc_session/tui-plugins/oc-session-all.ts" + "file:///C:/Users/<你>/.config/opencode/tui-plugins/oc-session-all.js" ] } ``` -重启 OpenCode 即可生效。 +WSL 侧同理:`file:///root/.config/opencode/tui-plugins/oc-session-all.js`。 +源码改动后需同步部署副本(本机 + WSL 两处,MD5 应与源码一致),重启 OpenCode 生效。 ## 快捷键 @@ -31,13 +35,20 @@ OpenCode TUI 插件:弹出全项目会话选择器,跨所有项目浏览和 |--------|------|------| | `Ctrl+o` | — | 打开会话选择器 | | `/sessions-all` | `resume-all`, `continue-all` | 斜杠命令 | +| — | `/mcpon` `/mcpoff` | MCP 开关(对调 + 推送兜底 + 热重载) | +| — | 任意位置 `/home` 等 | CLI 菜单(`wsl-round-mcp/oc.py`)跳回顶层,见其 README | ## 项目结构 ``` oc_session/ ├── tui-plugins/ -│ └── oc-session-all.ts ← 插件源码(OC 加载入口) +│ ├── oc-session-all.js ← 现役插件源码(OC 加载入口,v10) +│ ├── oc-session-all.ts ← 旧版留档 +│ ├── oc-session-all-debug.js +│ └── readmcpcfg.test.mjs ← mcpcfg 解析 16 项单测(node readmcpcfg.test.mjs) +├── ai-mcp/ ← 独立 git 仓库(分支 ai-mcp),server 插件 +├── wsl-round-mcp/ ← 独立 git 仓库(分支 wsl-round-mcp),Python CLI ├── package.json ├── node_modules/ ├── .gitignore diff --git a/tui-plugins/oc-session-all.js b/tui-plugins/oc-session-all.js index a76541e..a5c128c 100644 --- a/tui-plugins/oc-session-all.js +++ b/tui-plugins/oc-session-all.js @@ -265,9 +265,11 @@ async function doReload(context) { return false } -// P2 兜底:reload 之前先把 mcp 配置 PATCH 进运行中的 OC(PATCH /config)。 -// 这样「配置就位」不再依赖 reload 的读盘时序 —— 推送 + reload 双保险, -// 保证 MCP 百分百加载;推送失败也只降级为原有 reload 单通道,不影响对调。 +// P2 兜底:reload 之前先把 mcp 配置 PATCH 进运行中的 OC。 +// v10 改为 GET+merge:PATCH 的 zod schema 里 shell 是必填键(string|null), +// 只送 mcp 会 400「Missing key at ["shell"]」—— 就是「配置推送未生效」的根因。 +// 链路:GET 当前 config 取 shell(容错 document 数组 / 扁平对象两形态)→ +// 补 shell(GET 没带则用活文件值,再缺则 null)→ PATCH { shell, mcp }。 async function pushMcpConfig(context, activePath) { try { const j = JSON.parse(readFileSync(activePath, "utf8")) @@ -275,17 +277,46 @@ async function pushMcpConfig(context, activePath) { 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) + // 1) GET 当前 config(进程内 handler)→ 提取 document 的 info 作 base + let base = null + if (cfgApi && typeof cfgApi.get === "function") { try { - await cfgApi.update({ config: mcp }) - log("[oc-session-all] mcp 配置已推送进 OC(client.config.update → PATCH /config)") + const got = await cfgApi.get() + if (Array.isArray(got)) { + const doc = + got.find((d) => d && d.type === "document" && d.info) || + got.find((d) => d && d.info) + base = (doc && doc.info) || null + } else if (got && typeof got === "object") { + base = got.info && typeof got.info === "object" ? got.info : got + } + } catch (e) { + errs.push("config.get: " + e.message) + } + } else { + errs.push("client.config.get 不存在") + } + // 2) 补 shell(必填键):GET 结果 → 活文件 → null,三级兜底 + let shell = base && typeof base === "object" && "shell" in base ? base.shell : undefined + if (shell === undefined) shell = j && "shell" in j ? j.shell : null + if (typeof shell !== "string" && shell !== null) shell = null + const payload = { shell, mcp } + // 3) PATCH(shell 已补齐) + if (cfgApi && typeof cfgApi.update === "function") { + // SDK 签名:parameters.config -> body(进程内直调同一套 zod 校验) + try { + await cfgApi.update({ config: payload }) + log( + "[oc-session-all] mcp 配置已推送进 OC(GET+merge → PATCH /config,shell=" + + JSON.stringify(shell) + + ")", + ) return true } catch (e) { errs.push("config.update: " + e.message) } try { - await cfgApi.update({ body: { mcp } }) + await cfgApi.update({ body: payload }) log("[oc-session-all] mcp 配置已推送进 OC(body 变体)") return true } catch (e) { @@ -297,7 +328,7 @@ async function pushMcpConfig(context, activePath) { const exp = context.client && context.client.experimental && context.client.experimental.config if (exp && typeof exp.update === "function") { try { - await exp.update({ mcp }) + await exp.update(payload) log("[oc-session-all] mcp 配置已推送进 OC(experimental.config.update)") return true } catch (e) { @@ -370,7 +401,7 @@ function toastResult(context, res, actionLabel) { export default Plugin.define({ id: "oc-session-all", async setup(context) { - log("[oc-session-all] v9 loaded — 会话选择器 + MCP配置对调(mcpcfg.txt) + 推送兜底 + 热重载 + skill热加载") + log("[oc-session-all] v10 loaded — 会话选择器 + MCP配置对调(mcpcfg.txt) + 推送兜底(GET+merge补shell) + 热重载 + skill热加载") // ── 读取 mcpcfg.txt(键值小白配置)──────────────────────────────── const mcfg = readMcpCfg() @@ -784,7 +815,7 @@ export default Plugin.define({ try { context.ui.toast.show({ message: - "oc-session-all v9 已加载 — /sessions-all 浏览全部会话(Ctrl+O)" + + "oc-session-all v10 已加载 — /sessions-all 浏览全部会话(Ctrl+O)" + (featureOn ? ",/mcpon 开启 MCP、/mcpoff 关闭 MCP" : ""), variant: "info", duration: 20000,