TUI插件v10:MCP推送兜底改GET+merge补shell三跳,README同步安装路径与快捷键

This commit is contained in:
ypshidifu
2026-09-23 17:50:26 +08:00
parent 6e0ed1743a
commit 0f099c88d0
2 changed files with 57 additions and 15 deletions
+15 -4
View File
@@ -9,21 +9,25 @@ OpenCode TUI 插件:弹出全项目会话选择器,跨所有项目浏览和
- 过滤掉已归档和子代理会话 - 过滤掉已归档和子代理会话
- 对调2份`opencode.json`实现拦截mcp报错的toast - 对调2份`opencode.json`实现拦截mcp报错的toast
- 热更新skill,新的skill丢入目录,插件自动发现并更新 - 热更新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 ```json
{ {
"$schema": "https://opencode.ai/tui.json", "$schema": "https://opencode.ai/tui.json",
"plugin": [ "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` | — | 打开会话选择器 | | `Ctrl+o` | — | 打开会话选择器 |
| `/sessions-all` | `resume-all`, `continue-all` | 斜杠命令 | | `/sessions-all` | `resume-all`, `continue-all` | 斜杠命令 |
| — | `/mcpon` `/mcpoff` | MCP 开关(对调 + 推送兜底 + 热重载) |
| — | 任意位置 `/home` 等 | CLI 菜单(`wsl-round-mcp/oc.py`)跳回顶层,见其 README |
## 项目结构 ## 项目结构
``` ```
oc_session/ oc_session/
├── tui-plugins/ ├── 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 ├── package.json
├── node_modules/ ├── node_modules/
├── .gitignore ├── .gitignore
+42 -11
View File
@@ -265,9 +265,11 @@ async function doReload(context) {
return false return false
} }
// P2 兜底:reload 之前先把 mcp 配置 PATCH 进运行中的 OC(PATCH /config)。 // P2 兜底:reload 之前先把 mcp 配置 PATCH 进运行中的 OC。
// 这样「配置就位」不再依赖 reload 的读盘时序 —— 推送 + reload 双保险, // v10 改为 GET+merge:PATCH 的 zod schema 里 shell 是必填键(string|null),
// 保证 MCP 百分百加载;推送失败也只降级为原有 reload 单通道,不影响对调。 // 只送 mcp 会 400「Missing key at ["shell"]」—— 就是「配置推送未生效」的根因。
// 链路:GET 当前 config 取 shell(容错 document 数组 / 扁平对象两形态)→
// 补 shell(GET 没带则用活文件值,再缺则 null)→ PATCH { shell, mcp }。
async function pushMcpConfig(context, activePath) { async function pushMcpConfig(context, activePath) {
try { try {
const j = JSON.parse(readFileSync(activePath, "utf8")) 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 if (!mcp || !mcp.servers || !Object.keys(mcp.servers).length) return false
const errs = [] const errs = []
const cfgApi = context.client && context.client.config const cfgApi = context.client && context.client.config
if (cfgApi && typeof cfgApi.update === "function") { // 1) GET 当前 config(进程内 handler)→ 提取 document 的 info 作 base
// SDK 签名:parameters.config -> body(PATCH /config) let base = null
if (cfgApi && typeof cfgApi.get === "function") {
try { try {
await cfgApi.update({ config: mcp }) const got = await cfgApi.get()
log("[oc-session-all] mcp 配置已推送进 OC(client.config.update → PATCH /config)") 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 return true
} catch (e) { } catch (e) {
errs.push("config.update: " + e.message) errs.push("config.update: " + e.message)
} }
try { try {
await cfgApi.update({ body: { mcp } }) await cfgApi.update({ body: payload })
log("[oc-session-all] mcp 配置已推送进 OC(body 变体)") log("[oc-session-all] mcp 配置已推送进 OC(body 变体)")
return true return true
} catch (e) { } catch (e) {
@@ -297,7 +328,7 @@ async function pushMcpConfig(context, activePath) {
const exp = context.client && context.client.experimental && context.client.experimental.config const exp = context.client && context.client.experimental && context.client.experimental.config
if (exp && typeof exp.update === "function") { if (exp && typeof exp.update === "function") {
try { try {
await exp.update({ mcp }) await exp.update(payload)
log("[oc-session-all] mcp 配置已推送进 OC(experimental.config.update)") log("[oc-session-all] mcp 配置已推送进 OC(experimental.config.update)")
return true return true
} catch (e) { } catch (e) {
@@ -370,7 +401,7 @@ function toastResult(context, res, actionLabel) {
export default Plugin.define({ export default Plugin.define({
id: "oc-session-all", id: "oc-session-all",
async setup(context) { 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(键值小白配置)──────────────────────────────── // ── 读取 mcpcfg.txt(键值小白配置)────────────────────────────────
const mcfg = readMcpCfg() const mcfg = readMcpCfg()
@@ -784,7 +815,7 @@ export default Plugin.define({
try { try {
context.ui.toast.show({ context.ui.toast.show({
message: message:
"oc-session-all v9 已加载 — /sessions-all 浏览全部会话(Ctrl+O)" + "oc-session-all v10 已加载 — /sessions-all 浏览全部会话(Ctrl+O)" +
(featureOn ? ",/mcpon 开启 MCP、/mcpoff 关闭 MCP" : ""), (featureOn ? ",/mcpon 开启 MCP、/mcpoff 关闭 MCP" : ""),
variant: "info", variant: "info",
duration: 20000, duration: 20000,