diff --git a/docs/02-计划/计划-QMTBridgeMCP内建.md b/docs/02-计划/计划-QMTBridgeMCP内建.md new file mode 100644 index 0000000..f5114f6 --- /dev/null +++ b/docs/02-计划/计划-QMTBridgeMCP内建.md @@ -0,0 +1,92 @@ +# 计划:QMT Bridge MCP 能力内建(神之一手自注册 MCP,去三方插件依赖)(阶段航点) + +> 编号:PLAN-018 | 粒度:阶段航点 | 创建:2026-09-08 | 状态:待实施(拟迭代 18) +> 派生自终极目标:目标-001(DSH 插件形态·复用宿主 MCP 能力)、目标-008(真实交易系统接入) +> 依据需求:**R-020(已定稿,2026-09-08,Q1-Q6 老师拍板)** —— 符合入范围门槛 +> 设计约束:技术约束-003(REST 直连不变)、技术约束-008(连接配置存储/热切换)、产品约束-012(会话头部指示灯体系)、UI约束-002(连接卡片);沿用技术约束-004/005/006/007 + +## 目标 + +让神之一手插件自带 QMT Bridge 的 MCP 能力:插件在自己的 apply 内按 QMT 连接配置动态挂载 **DSH 官方 @deepseek-ai/dsh-mcp-client**(url 自动派生自激活连接 baseUrl + /mcp,serverName 固定 QMT_Bridge_MCP,工具以 mcp__QMT_Bridge_MCP__* 注册给模型;挂载/切换/卸载/状态均由神之一手在插件内管理);连接 CRUD/激活切换与 MCP 实例全生命周期联动;**卸载三方 dsh-skill-mcp-panel 并移除其宿主 cordis.patch.yml 受管块**,不再依赖三方面板加入 MCP 能力。REST 直连架构不变(技术约束-003)。 + +## 范围 + +**做**(对应 R-020 定稿边界,决策编号 Q1-Q6): +1. **依赖与可行性前置(R-020 计划阶段待办 #1,已实证 2026-09-08)**:@deepseek-ai/dsh-mcp-client@0.1.1-rc.2 已加为 one-divine-lot dependencies,从插件 lib 可 import(),模块导出 {name:'mcp-client', inject:['tools'], apply, Config},可用 ctx.plugin() 动态挂载(实证通过); +2. **服务端 MCP 管理器(新增 src/mcp/QmtMcpManager.js)**: + - 唯一 dsh-mcp-client 实例,serverName 固定 `QMT_Bridge_MCP`,url = 激活连接 baseUrl + `/mcp`(自动派生,Q3); + - 启动:解析启动连接(默认→上次激活→第一条,空表回退 cordis qmtBaseUrl)后挂载(对齐 R-004 resolveStartupConnection); + - 生命周期编排:activate/update(地址变)/remove(激活被删切默认)→ 先卸后挂(对齐 qmt-connections API 现有热切换点,Q1 仅激活连接挂载); + - 失败语义(Q5):failOnStartupError:false + reconnect(对齐宿主现有受管块配置:initial 500ms / max 30s / maxAttempts 10); + - 状态缓存:{ state: connected|connecting|disconnected|disabled, serverName, url, toolCount, error, checkedAt },供状态端点与指示灯读取; + - 释放:ctx.effect dispose 时卸载实例; +3. **服务端状态出口(api/qmt-connections.js 或 market.js 扩展)**: + - mcp-status 端点(含 refresh 即时重查)→ 设置页连接卡片 MCP 状态行 + 会话头部指示灯读取; + - sync-status 增加 mcp 域(Q4:头部指示灯数据源合并一处); +4. **设置页「QMT 连接配置」卡片 MCP 状态(UI约束-002 形态内扩展)**:卡片显示 MCP 状态(绿=已连接 + N 工具 / 红=失败原因 / 灰=未启用),「检查 MCP」按钮(手动触发,Q4); +5. **会话头部指示灯新增 MCP 状态灯(产品约束-012 体系扩展)**:SyncIndicators 三灯 → 四灯(QMT连接|持仓数据|行情数据|MCP),MCP 灯三态对齐(绿=已连接 / 黄=重连中 / 红=断开 / 灰=未启用),点击=refresh(Q4); +6. **移除三方 dsh-skill-mcp-panel(Q2,2026-09-08 老师澄清)**:dsh plugin remove dsh-skill-mcp-panel(连带其「技能 / MCP」设置菜单);编辑 ~/.dsh/profiles/web/cordis.patch.yml 删除其受管块(QMT_Bridge_MCP 条目,块外内容逐字节保留)——属宿主组成变更,按 editing-cordis-compositions 技能流程执行 + 老师确认; +7. **构建安装与验收**。 + +**不做**(本期,R-020 边界):通用 MCP 服务器管理面板(Q6,含自建任意 MCP 管理 UI);插件 UI/tab 数据源改走 MCP(技术约束-003);多 serverName 并存;自研 MCP 协议客户端(老师定:官方 dsh-mcp-client 可依赖,不重造);认证/鉴权增强(QMT Bridge 无鉴权)。 + +## 程序结构(新增/改动) + +``` +src/ +├── index.js # 服务端入口(改):注入 mcpManager,启动挂载 + 释放卸载;把 mcpManager 传入 registerApi +├── mcp/ # (新增域目录,技术约束-016 语义:无合适域先讨论,此处讨论定 = 新增 mcp 域) +│ └── QmtMcpManager.js # MCP 实例管理(官方 dsh-mcp-client 动态挂载:唯一实例 + 生命周期编排 + 状态缓存) +├── settings.js # (小改):暴露当前激活连接解析辅助(已有 getActiveQmtConnection 可复用,必要时补暴露) +├── api/ +│ ├── index.js # (改):runtime 增 mcpManager,注册 mcp-status 方法分发 +│ ├── qmt-connections.js # (改):热切换点(activate/update/remove)联动 mcpManager 重挂;新增 mcp-status 处理 +│ └── market.js # (改):sync-status 增加 mcp 域 +└── client/views/ + ├── QmtConnectionChip.jsx # (改):内置 SyncIndicators 传 mcp 状态读取 + ├── SyncIndicators.jsx # (改):三灯 → 四灯(+MCP),轮询 sync-status 读 mcp 域,点击 refresh + └── SettingsSection.jsx # (改):QmtConnectionCard 增 MCP 状态行 + 「检查 MCP」按钮 +package.json # (改,已完成):dependencies + @deepseek-ai/dsh-mcp-client@0.1.1-rc.2 + @modelcontextprotocol/sdk@1.30.0 +~/.dsh/profiles/web/cordis.patch.yml # (改):移除 dsh-skill-mcp-panel 受管块(Q2,宿主配置,独立步骤;插件本体 dsh plugin remove) +``` + +## 实现步骤(建议顺序) + +1. **依赖可行性实证**(任务 1 决策门):声明 peer + 构建后在宿主实测 import 解析;不可解析则停在这里与老师讨论替代通道; +2. **服务端 mcpManager**:QmtMcpManager 骨架 + 启动挂载/卸载 + 状态缓存(先不接 API,日志验证挂载与工具注册); +3. **热切换联动**:qmt-connections activate/update/remove 编排点追加 mcpManager.resync()(复用现有 dataSource.setBaseUrl 同点); +4. **状态端点**:mcp-status + sync-status.mcp 域;本地 curl/HTTP 验证; +5. **设置页卡片**:QmtConnectionCard 增 MCP 状态 + 检查按钮(复用测试连接交互模式与 Toast); +6. **会话头部 MCP 灯**:SyncIndicators 四灯改造;验证与三灯并存样式一致(对齐 IndicatorDot/sep 布局与主题 token); +7. **宿主受管块移除**:编辑 cordis.patch.yml(editing-cordis-compositions 流程)→ 重载宿主 → 验证 mcp__QMT_Bridge_MCP__* 工具仍可用(这次来自神之一手); +8. **构建安装验收**:服务端+客户端构建,dsh plugin add / 重载;逐条核对验收标准。 + +## 验收标准 + +1. 移除宿主 cordis.patch.yml QMT_Bridge_MCP 受管块后,模型仍能使用 mcp__QMT_Bridge_MCP__qmt_* 工具(来源=神之一手动态挂载实例),工具集与移除前一致(同一 serverName/url); +2. 激活连接切换 → MCP 实例 url 跟随切换(工具可用目标随激活配置变化,观察工具调用返回目标数据源变化); +3. 编辑激活连接地址、删除激活连接(自动切默认)→ MCP 实例同步重挂,无残留旧连接工具; +4. 连接列表为空(无激活)→ 不挂 MCP 实例(mcp-status=disabled),插件其它功能不受影响; +5. QMT Bridge 不可达 → MCP 灯=红(断开),插件不崩、其余灯正常;恢复可达后自动重连至绿灯(Q5 语义); +6. 设置页连接卡片显示 MCP 状态(已连接 + N 工具 / 失败原因 / 未启用),「检查 MCP」可手动触发并刷新; +7. 会话头部出现第四个指示灯「MCP」:绿=已连接 / 黄=重连中 / 红=断开 / 灰=未启用,点击触发即时检查(Q4); +8. 现有功能(分仓/策略/交易/设置子 tab/三灯)无回归;typecheck + build 通过。 + +## 验收方法 + +- 移除受管块前先完成 1-6 步并在旧块存在时验证(同名冲突预期:两实例同 serverName 后加载报错——因此**验证顺序必须先移除受管块再加载新实例**,步骤 7 与 1-6 的顺序需在实施时按 HMR 重载节奏小心编排,见实施时核查点); +- 用 qmt 工具的 listTools/实际调用观察目标切换;断开 QMT 观察灯态与自动恢复; +- settings 卡片与头部灯截图/人工核验。 + +> **实施注意(同名冲突时序)**:宿主现有受管块与神之一手新实例同用 serverName QMT_Bridge_MCP,存活实例重复会报错。安全顺序:① 插件侧完整实现并构建 → ② 移除宿主受管块并重载(此窗口工具短暂消失可接受)→ ③ 插件实例接管(工具恢复,来源变为神之一手)。禁止两实例并存重载。 + +## 关联 + +- 需求:R-020(已定稿,2026-09-08)| 参考:mcp_router 动态挂载实证、dsh-skill-mcp-panel 受管块现状 +- 设计约束新增(定稿时补充):技术约束:「插件内建 MCP 客户端注册规范」(唯一实例 serverName 固定 + 随激活连接 url 派生 + 生命周期联动);产品约束:「MCP 状态出口(设置卡片 + 会话头部 MCP 灯)」 + +## 记录 + +| 日期 | 变更 | +|---|---| +| 2026-09-08 | 依据 R-020 定稿(Q1-Q6)创建 PLAN-018 | diff --git a/docs/04-迭代记录/18-QMTBridgeMCP内建/技术实现方案.md b/docs/04-迭代记录/18-QMTBridgeMCP内建/技术实现方案.md new file mode 100644 index 0000000..ecf0ecf --- /dev/null +++ b/docs/04-迭代记录/18-QMTBridgeMCP内建/技术实现方案.md @@ -0,0 +1,68 @@ +# 迭代 18 技术实现方案:QMT Bridge MCP 能力内建 + +> 依据:R-020(2026-09-08 定稿,Q1-Q6 + 老师澄清:移除三方 dsh-skill-mcp-panel、官方 dsh-mcp-client 可依赖)| PLAN-018 +> 日期:2026-09-08 + +## 目标架构 + +神之一手在 apply 内**动态挂载 DSH 官方 @deepseek-ai/dsh-mcp-client**(依赖官方库、管理在插件内),把 QMT Bridge 的 MCP 工具注册给模型;MCP 实例生命周期(挂/切/卸)随 QMT 连接配置的增删改与激活切换联动;状态出口 = 设置页 + 会话头部指示灯。REST 直连架构不变(技术约束-003)。 + +``` +神之一手 apply(ctx) + ├─ settings(QMT 连接配置:list + activeId + defaultId) [已有 R-004] + ├─ dataSource(REST 直连,baseUrl 随激活热切换) [已有] + └─ QmtMcpManager(新增 src/mcp/QmtMcpManager.js) + ├─ 动态 import(@deepseek-ai/dsh-mcp-client) → ctx.plugin(module, config) + │ config = { serverName: QMT_Bridge_MCP, transport: streamable-http, + │ url: 激活 baseUrl + /mcp, failOnStartupError:false, + │ reconnect: {enabled, 500ms→30s, 10次} } ← 与宿主现受管块同参(Q5) + ├─ 生命周期:start(启动挂载激活连接)/ resync(url)(激活·编辑地址·删除联动) + ├─ 状态:probe()(SDK listTools 握手:connected + 工具数 + 延迟 / error) + │ getStatus() → { state, serverName, url, mounted, toolCount, error, latencyMs, checkedAt } + └─ dispose:卸载 fiber(ctx.effect 释放) +``` + +## 关键事实(已实证) + +- `@deepseek-ai/dsh-mcp-client@0.1.1-rc.2` 已加入 dependencies;从插件 lib 可 import,模块导出 {name:mcp-client, inject:[tools], apply, Config},可 ctx.plugin() 动态挂载(mcp_router 同款实证); +- serverName 存活实例内必须唯一 → 移三方受管块前插件实例与宿主实例不可并存(实施时序见 PLAN-018 注意事项); +- dsh-mcp-client 不暴露连接状态查询 API → 状态用**独立 SDK 探测**(@modelcontextprotocol/sdk Client:initialize + tools/list → 工具数/延迟),与 dsh-mcp-client 自管连接互不干扰; +- 宿主现 QMT_Bridge_MCP 受管块 = 三方 dsh-skill-mcp-panel 写入,连接与注册实为官方 dsh-mcp-client。 + +## 改动清单 + +| 文件 | 改动 | +|---|---| +| package.json | dependencies + @deepseek-ai/dsh-mcp-client@0.1.1-rc.2 + @modelcontextprotocol/sdk@1.30.0(已加) | +| src/mcp/QmtMcpManager.js(新增) | MCP 实例管理:动态挂载/卸载 + resync + probe/getStatus + dispose | +| src/index.js | 实例化 QmtMcpManager、start、ctx.effect 释放、传入 registerApi runtime | +| src/api/index.js | runtime 增 mcpManager(分发 handleQmt/handleMarket) | +| src/api/qmt-connections.js | METHODS + mcp-status;activate/update(地址变)/remove 热切换点 + mcpManager.resync(url) | +| src/api/market.js | sync-status 增加 mcp 域(读 mcpManager.getStatus) | +| src/client/views/SyncIndicators.jsx | 三灯 → 四灯:+「MCP」(绿=已连接 / 黄=重连中 / 红=断开 / 灰=未启用;点击刷新) | +| src/client/views/SettingsSection.jsx | QMT 连接配置子 tab 顶部 MCP 状态条 + 「检查 MCP」按钮(手动触发 mcp-status refresh) | + +## MCP 状态语义(对齐产品约束-012 指示灯体系) + +| state | 灯色 | 说明 | +|---|---|---| +| connected | 绿 | 已连接 + N 工具(probe listTools 成功) | +| connecting | 黄 | 挂载中 / 重连中 | +| error | 红 | 不可达 / 失败(含原因) | +| disabled | 灰 | 无激活连接(未启用) | + +## 实现步骤 + +1. QmtMcpManager(挂/卸/resync/probe/getStatus/dispose); +2. index.js 接线 + registerApi runtime; +3. api:mcp-status + sync-status.mcp + 热切换联动; +4. 客户端:SyncIndicators 四灯 + 设置页 MCP 状态条; +5. 构建(pnpm build)验证; +6. 宿主迁移(独立步骤,需老师确认 + editing-cordis-compositions 流程):卸载 dsh-skill-mcp-panel + 移除 cordis.patch.yml 受管块 → 重载 → 验证工具来自神之一手; +7. 验收。 + +## 风险与注意 + +- 同 serverName 冲突:插件实例与宿主受管块不可并存(先插件后移除,或先移除后插件接管,二选一窗口); +- ctx.plugin 子 fiber 需要 tools 服务:one-divine-lot inject 列表评估(对齐 mcp_router inject [webServer, tools]); +- dsh-mcp-client 初始连接失败不抛(failOnStartupError:false),但**重复 serverName 会抛** → 挂载错误必须 catch,插件其余功能不受影响。 diff --git a/docs/04-迭代记录/18-QMTBridgeMCP内建/迭代目标.md b/docs/04-迭代记录/18-QMTBridgeMCP内建/迭代目标.md new file mode 100644 index 0000000..64e74d9 --- /dev/null +++ b/docs/04-迭代记录/18-QMTBridgeMCP内建/迭代目标.md @@ -0,0 +1,19 @@ +# 迭代 18:QMT Bridge MCP 能力内建(去三方插件依赖) + +## 目标(一句话) + +神之一手插件在 apply 内动态挂载 DSH 官方 dsh-mcp-client(serverName 固定 QMT_Bridge_MCP,url 自动派生自激活连接 baseUrl+/mcp,挂载/切换/卸载/状态均由神之一手插件内管理),MCP 能力不再依赖三方 dsh-skill-mcp-panel(卸载其插件 + 移除宿主 cordis.patch.yml 受管块)。 + +## 目标描述 + +- 依据 R-020(2026-09-08 定稿,Q1-Q6)+ PLAN-018; +- Q1 只挂激活连接;Q2 移除三方块由神之一手管理;Q3 url 自动派生;Q4 状态出口=设置页卡片检查 + 会话头部 MCP 指示灯;Q5 失败不崩+重连;Q6 不做通用 MCP 管理; +- REST 直连架构不变(技术约束-003)。 + +## 讨论过程 + +| 日期 | 轮次 | 要点 | +|---|---|---| +| 2026-09-08 | 需求讨论 Q1-Q6 | R-020 定稿(见 R-020.md) | +| 2026-09-08 | 立项 | 老师:「启动,这个不是一个大需求」→ 快速迭代 | +| 2026-09-08 | 指令澄清 | 老师修正:不要的是三方 dsh-skill-mcp-panel;依赖官方 @deepseek-ai/dsh-mcp-client 没问题 → 方案定为官方库动态挂载 + 移除三方面板 | diff --git a/docs/04-迭代记录/18-QMTBridgeMCP内建/验收标准.md b/docs/04-迭代记录/18-QMTBridgeMCP内建/验收标准.md new file mode 100644 index 0000000..92dd47a --- /dev/null +++ b/docs/04-迭代记录/18-QMTBridgeMCP内建/验收标准.md @@ -0,0 +1,10 @@ +# 迭代 18 验收标准:QMT Bridge MCP 能力内建 + +> 验收方法/验收目标见 PLAN-018「验收标准 / 验收方法」逐条。 + +1. 移除宿主 cordis.patch.yml QMT_Bridge_MCP 受管块后,模型仍能用 mcp__QMT_Bridge_MCP__qmt_* 工具(来源=神之一手动态挂载,工具集一致); +2. 激活连接切换 → MCP 实例 url 跟随;编辑/删除激活连接 → 同步重挂无残留; +3. 连接列表为空 → 不挂实例(mcp-status=disabled),其余功能不受影响; +4. QMT 不可达 → 插件不崩、MCP 灯红、恢复后自动重连绿灯; +5. 设置页连接卡片 MCP 状态 + 检查按钮;会话头部 MCP 指示灯四灯齐全; +6. 现有功能无回归;typecheck + build 通过。 diff --git a/docs/05-需求池/R-020.md b/docs/05-需求池/R-020.md new file mode 100644 index 0000000..678db26 --- /dev/null +++ b/docs/05-需求池/R-020.md @@ -0,0 +1,57 @@ +# 需求:R-020 QMT Bridge MCP 能力内建(神之一手自注册 MCP,去三方插件依赖)· 已定稿 + +> 登记:2026-09-08 | 来源:老师指令(2026-09-08 新需求)| 状态:**已定稿(2026-09-08,Q1-Q6 老师逐项确认)** +> 归属:待立项(拟迭代 18)| 计划:待出 | 实现状态:未开始 + +## 需求描述(老师原始诉求) + +> **老师原话(2026-09-08)**:当前我们给 DSH 配置了一个 MCP,用于连接 QMT bridge,这个配置是在另一个插件中配置的(三方插件 dsh-skill-mcp-panel 在 profile cordis.patch.yml 受管块里插入 QMT_Bridge_MCP 实例——管理 UI + 写宿主 patch;真正连接的是 DSH 官方 dsh-mcp-client)。我希望可以在神之一手插件的配置 tab 中,添加了 QMTBridge 的配置后,可以**自动检查 MCP 连接信息**,这样我们**不需要依赖三方插件来加入 MCP 的能力**。 +> +> **2026-09-08 老师澄清(指令修正)**:不要的是**第三方 dsh-skill-mcp-panel**(设置菜单「技能 / MCP」+ 它写的宿主 patch 受管块);**依赖 DSH 官方 @deepseek-ai/dsh-mcp-client 是允许的**。实现 = 神之一手 apply 内动态挂载官方 dsh-mcp-client(参照 mcp_router 实证),**移除三方 dsh-skill-mcp-panel 及其受管块**。 + +## 定稿语义 + +- **MCP 能力内建**:神之一手在自己的 apply 内按 QMT 连接配置动态挂载 DSH 官方 @deepseek-ai/dsh-mcp-client(插件内管理:挂载/切换/卸载/状态都由神之一手控制),QMT Bridge 的 MCP 工具由神之一手自己接入;不再依赖三方 dsh-skill-mcp-panel 的面板与宿主 patch 受管块; +- **REST 直连不变**(技术约束-003):插件 UI/tab 数据仍走 REST,MCP 是插件自带的 Agent 侧能力; +- **MCP url 自动派生**:激活连接 baseUrl + /mcp,零新增配置字段。 + +## 讨论记录(2026-09-08,第一轮,Q1-Q6 老师逐项拍板 → 定稿) + +| # | 问题 | 结论(老师拍板) | +|---|---|---| +| Q1 | MCP 客户端挂载粒度 | **只使用当前激活的那个连接**:仅激活连接挂 1 个 dsh-mcp-client 实例(serverName 固定 QMT_Bridge_MCP,切换激活即热切换 MCP url,工具名 mcp__QMT_Bridge_MCP__* 保持稳定);不逐连接各挂实例 | +| Q2 | 现有三方条目处置 | **现在的移除(2026-09-08 老师澄清:移除对象 = 第三方 dsh-skill-mcp-panel)**:① 卸载/停用三方 dsh-skill-mcp-panel(其「技能 / MCP」设置菜单与宿主 patch 管理职责一并移除);② 删除 cordis.patch.yml 中其受管块(QMT_Bridge_MCP 不再由三方写入);**QMT 的 MCP 连接通过神之一手插件自己管理**——插件 apply 内动态挂载**官方 @deepseek-ai/dsh-mcp-client**(依赖官方库、管理在我们插件内),含连接 CRUD/激活切换的全生命周期联动 | +| Q3 | MCP url 与字段 | **自动来**:默认派生 baseUrl + /mcp,不需要每连接 mcpUrl 覆盖字段 | +| Q4 | 「自动检查 MCP 连接信息」口径 | **两处状态出口**:① 设置页「QMT 连接配置」连接卡片可检查(手动触发);② **对话窗口顶端状态指示灯区新增一个 MCP 状态**(与现有持仓/盘口同步指示灯并列) | +| Q5 | 失败与重连语义 | **采纳建议**:对齐 failOnStartupError:false + reconnect(初始连不上不拒绝插件激活;连接丢失自动指数退避重连;期间保留断开状态不崩插件) | +| Q6 | 范围确认 | **不做通用 MCP 管理**,只做 QMT 的 MCP 内建;通用任意 MCP 服务器管理面板不做(该域属 mcp-router / dsh-skill-mcp-panel,另议) | + +## 定稿边界 + +**做**: +1. 神之一手 apply 内动态挂载/卸载 dsh-mcp-client(唯一实例,serverName=QMT_Bridge_MCP,url=激活连接 baseUrl+/mcp); +2. 生命周期联动:启动(激活默认/上次连接)→ 挂载;添加/编辑/删除/激活连接 → MCP 挂/切/卸(对齐现有 qmt-connections API 热切换编排);激活连接被删自动切默认并重挂; +3. MCP 状态出口 ×2:设置页连接卡片 MCP 检查/状态;对话窗口顶端状态指示灯区新增 MCP 状态(三态风格对齐产品约束-012 指示灯体系); +4. 移除三方 dsh-skill-mcp-panel:卸载其插件 + 移除宿主 cordis.patch.yml 受管块(Q2;属宿主配置变更,按 editing-cordis-compositions 技能流程操作 + 老师确认); +5. 失败/重连语义按 Q5(failOnStartupError:false + reconnect)。 + +**不做(本期)**:通用 MCP 服务器管理面板(Q6,含自建「任意 MCP 管理 UI」——Q6 明确不做);插件 UI/tab 数据源改走 MCP(技术约束-003 REST 直连不变);多 serverName 并存;自研 MCP 协议客户端(老师定:依赖官方 dsh-mcp-client 即可,不重造轮子)。 + +## 计划阶段待办(定稿后第一件事:技术可行性调研) + +1. **依赖解析实证**:one-divine-lot 运行时能否 import(@deepseek-ai/dsh-mcp-client)(宿主 CLI 依赖树 / profile node_modules 解析路径核实,对照 mcp_router 的 peerDependencies 声明与日志实证;确认后在 package.json 补 peer 依赖); +2. **动态挂载生命周期设计**:add/update/remove/activate 连接 → MCP 实例 挂/切/卸 编排(对齐 qmt-connections API 现有热切换逻辑,MCP url 随激活 baseUrl); +3. **MCP 状态查询与展示**:服务端 MCP 状态端点(已连接/断开 + 工具数 + 错误/重连信息)+ 设置页卡片检查 + 会话头部指示灯新增 MCP 状态(对齐产品约束-012/UI约束的指示灯体系); +4. **宿主受管块迁移**:cordis.patch.yml 移除 QMT_Bridge_MCP 块的精确操作步骤(保留块外内容逐字节不动); +5. 前端连接卡片表单是否需提示「MCP 将自动随激活连接启用」(Q4 落点 UI 文案)。 + +## 关联 + +- 终极目标:目标-001(DSH 插件形态·复用宿主 MCP 能力)、目标-008(真实交易系统接入) +- 既有需求:R-004(QMT 连接配置,本需求数据基础:settings.qmtConnections + 激活/默认语义 + 热切换编排);R-015(会话头部指示灯体系,MCP 状态灯并入同一区) +- 设计约束:技术约束-003(REST 直连不变)、技术约束-008(连接配置存储/热切换);定稿后需补新约束条目(技术约束:「插件内建 MCP 客户端注册规范」;产品约束:「MCP 状态出口(设置卡片 + 会话头部指示灯)」) +- 参考实现:mcp_router(ds-plugins/mcp_router)——动态 ctx.plugin(dsh-mcp-client) 实证 + +## 验收 + +**(立项后写入迭代验收标准.md)** diff --git a/docs/05-需求池/需求池索引.md b/docs/05-需求池/需求池索引.md index 601e323..a442ea8 100644 --- a/docs/05-需求池/需求池索引.md +++ b/docs/05-需求池/需求池索引.md @@ -31,6 +31,9 @@ | R-017 | 委托归属候选纳入近期清仓持仓 | 交易记录 tab 归属候选只回当前持仓——清仓后当日委托失去下拉锚点无法补关联(R-016 关联观察立项,2026-09-07 大连热电实际发生:盘中归属后被清为未关联,下拉已无做T候选)。**2026-09-07 定稿(老师指令确认方向)**:候选 = 当前持仓 + 近 7 天清仓持仓(closed 标记,选项文本「已清仓」后缀),下拉 value 改 holdingId 键控(同策略多轮不撞值),排序当前在前/清仓按 closed_at DESC;当日数据修复经 set-attribution 通道完成(非固化功能)。**2026-09-08 R-018 取代标注:7 天清仓候选退役(R-018 T2 拍板)**——归属候选改策略级 attribution-targets(迭代 16 实施),本需求「清仓后补关联」痛点根因(幽灵清仓抢先归档)已由数据域分界消除;holdingId 键控/占位安全逻辑保留。详见 R-017.md | 老师指令(2026-09-07) | P1 | 已定稿 | 2026-09-07 | 15-归属候选纳入清仓持仓 | 已实现(被 R-018 语义取代) | | R-018 | 交易关联驱动持仓份额动态调整(归属 = 账本写操作) | 从交易记录出发给委托关联策略/持仓时自动同步调整该策略下持仓份额(关联即账本写操作):策略级候选 + 统一「份额分配器」(一笔委托拆 0..N 段 × 策略×量,余额默认全量可改小,允许部分关联)+ 买入加仓/建新仓(不复活旧行)、卖出减仓/归零清仓、仓不足自动截断续分、无仓关联失败;撤段逆操作(撤建仓买入=持仓作废第三态不产生假清仓历史、撤加仓=减回归零作废、撤卖出=恢复活动仓+份额加回,粒度=段、改归属原子);**数据域分界(老师拍板):幽灵清仓等同步机制只作用于「全部持仓」对账单域,不再写 strategy_holdings 账本**——账本生命周期只由交易关联 + 手动份额操作驱动,账本转历史唯一途径=卖出单关联 closeHolding;R-017 7 天清仓候选退役(holdingId 键控/占位保留);漏关联软提示(账本有活动行+QMT 无此 code)。影响既有定稿:技术约束-017/产品约束-011/R-014/迭代12 幽灵清仓条款修订。详见 R-018.md | 老师指令(2026-09-08 新优化需求讨论) | P1 | 已定稿 | 2026-09-08 | 16-交易关联驱动持仓份额动态调整 | **已实现(已归档至 已完成/R-018.md,2026-09-08 验收通过)** | +| R-019 | 策略会话:会话以策略当前数据为依据做复盘/分析讨论 | 让 AI 会话以策略为 Workspace(老师原话)→ **已定稿(2026-09-08,F1/F2 拍板)**:改造现有 DSH 对话窗口,对话视图首条消息前提供「讨论策略」选择(仿 DSH workspace chip Choose 体验:依据:做T ▾),选策略即把该策略**当前数据摘要**预注入会话上下文开始聊,会话中可换策略/刷新再注入;**不用 DSH 宿主工作区**(R3);注入 = 当前持仓明细 + 全历史关联交易聚合摘要(含已清仓,F2);只读不开放账本写(Q4=A);产物不落盘(R2-Q4,复盘管理/落库归未来 T-002)。宿主注入通道等技术调研列计划阶段待办。详见 R-019.md | 老师指令(2026-09-08) | P1 | 已定稿 | 2026-09-08 | 17-策略会话 | 已立项(迭代 17 设计阶段) | +| R-020 | QMT Bridge MCP 能力内建(神之一手自注册 MCP,去三方插件依赖) | 当前 DSH 的 QMT Bridge MCP 由三方插件 dsh-skill-mcp-panel 写入 profile cordis.patch.yml 受管块。**2026-09-08 定稿(Q1-Q6 老师逐项拍板)**:神之一手 apply 内动态挂载 **DSH 官方 dsh-mcp-client**(**只挂当前激活连接**,serverName 固定 QMT_Bridge_MCP,url 自动派生 baseUrl+/mcp;挂载/切换/卸载/状态由神之一手插件内管理),MCP 能力内建;**移除对象 = 三方 dsh-skill-mcp-panel**(2026-09-08 老师澄清:卸载其插件 + 移除其宿主 cordis.patch.yml 受管块,官方 dsh-mcp-client 可依赖);MCP 生命周期随连接 CRUD/激活切换联动;状态出口 = 设置页连接卡片检查 + **会话头部指示灯区新增 MCP 状态灯**;失败重连对齐 failOnStartupError:false+reconnect;**不做通用 MCP 管理**。REST 直连不变(技术约束-003)。详见 R-020.md | 老师指令(2026-09-08) | P1 | 已定稿 | 2026-09-08 | -(拟迭代 18) | 未开始 | + ## 渐进明细规划素材 > 2026-08-27 老师确立「渐进明细」:前期目标笼统,先实现周边工具,做中学逐步明确计划。以下周边工具候选为规划素材,**状态为「起草」级**(未定稿,不进入计划/迭代范围),待讨论与实践中逐步明确并转入正式需求。 diff --git a/package.json b/package.json index e9933f1..e5f85a8 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,9 @@ } }, "dependencies": { - "@deepseek-ai/schemastery": "^3.18.1" + "@deepseek-ai/dsh-mcp-client": "0.1.1-rc.2", + "@deepseek-ai/schemastery": "^3.18.1", + "@modelcontextprotocol/sdk": "1.30.0" }, "peerDependencies": { "@deepseek-ai/cordis": "^4.0.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index db8485e..48ba1dc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,12 +17,18 @@ importers: '@deepseek-ai/dsh-client-ui-conversation': specifier: ^0.1.1-rc.2 version: 0.1.1-rc.2(b441692b8d185dbf8cce39ef789ff602) + '@deepseek-ai/dsh-mcp-client': + specifier: 0.1.1-rc.2 + version: 0.1.1-rc.2(b23e1476680e2fc848944102d2a500cd) '@deepseek-ai/dsh-settings': specifier: ^0.1.1-rc.2 version: 0.1.1-rc.2(@deepseek-ai/cordis@4.0.1)(@deepseek-ai/dsh-brand@0.1.1-rc.2(@deepseek-ai/cordis@4.0.1)(@deepseek-ai/dsh-invariants@0.1.1-rc.2(@deepseek-ai/cordis@4.0.1)))(@deepseek-ai/dsh-invariants@0.1.1-rc.2(@deepseek-ai/cordis@4.0.1))(@deepseek-ai/schemastery@3.18.1) '@deepseek-ai/schemastery': specifier: ^3.18.1 version: 3.18.1 + '@modelcontextprotocol/sdk': + specifier: 1.30.0 + version: 1.30.0(zod@4.4.3) devDependencies: '@deepseek-ai/dsh-client-runtime': specifier: 0.1.1-rc.2 @@ -415,6 +421,17 @@ packages: '@deepseek-ai/dsh-invariants': ^0.1.1-rc.2 '@deepseek-ai/dsh-timeout': ^0.1.1-rc.2 + '@deepseek-ai/dsh-mcp-client@0.1.1-rc.2': + resolution: {integrity: sha512-oxNes59UTkXuTmOqQr6+GhZW7u2JtqSYiI5eZyBQ6K+yjPEQESa3/rYnRI+gHGAtx8EFgn6hsdOS5uoEwtFuXg==} + peerDependencies: + '@deepseek-ai/cordis': ^4.0.1 + '@deepseek-ai/dsh-attachment': ^0.1.1-rc.2 + '@deepseek-ai/dsh-invariants': ^0.1.1-rc.2 + '@deepseek-ai/dsh-llm': ^0.1.1-rc.2 + '@deepseek-ai/dsh-subprocess': ^0.1.1-rc.2 + '@deepseek-ai/dsh-timeout': ^0.1.1-rc.2 + '@deepseek-ai/dsh-tools': ^0.1.1-rc.2 + '@deepseek-ai/dsh-message-feedback@0.1.1-rc.2': resolution: {integrity: sha512-GzxDiNvyUVs/bbbm+Hr3MfuU3wM8ErRF5z9XadnSGPYZvipZDAMrvrS5v6JgOUaZt6ApBpEMJ4xVgIel1VnGYQ==} peerDependencies: @@ -750,6 +767,22 @@ packages: '@deepseek-ai/schemastery@3.18.1': resolution: {integrity: sha512-Qn0FCSwCQnpnj6SB31I6i2sIKgKWnkbJM8O0EU91Gv2UsYVvtZTl6IA0sCwk2e2MZf5S8w5hpq9QkeVvK9qwxg==} + '@hono/node-server@2.1.1': + resolution: {integrity: sha512-ELuehkj5VCBdgEw9zs+ivkKwyzzUCSQuE96YmiPvn1ECBoZCczbFXJLeEGMTYjphP6gydh4pHMqEYPVMYUVgQg==} + engines: {node: '>=20'} + peerDependencies: + hono: ^4 + + '@modelcontextprotocol/sdk@1.30.0': + resolution: {integrity: sha512-xKd8OIzlqNzcqcNumGAa6g+PW2kjD5vrpcKOnfldAUPP3j7lnqMPwlTXQm8gF+UwH72z0lqaRbjr9hqGz0eITA==} + engines: {node: '>=18'} + peerDependencies: + '@cfworker/json-schema': ^4.1.1 + zod: ^3.25 || ^4.0 + peerDependenciesMeta: + '@cfworker/json-schema': + optional: true + '@oxc-project/types@0.147.0': resolution: {integrity: sha512-IJ3s6ltHLp45S0bh7phkX+gJO7A1Wuz2EaqpAhb8WjqDwbzMiWKHhyyT42tskaWjEYXtHtVCPpnBJVT9+dcRLg==} @@ -981,6 +1014,21 @@ packages: '@yuku-toolchain/types@0.8.7': resolution: {integrity: sha512-2Z53dNxAJL6UvFoIrDZvYf3zlO8s4VJK4O2hhaB4mXVwwpX/7ajtss3cmfqKvamlNLWyt9FSWs4eoYdlbxpnHA==} + accepts@2.0.0: + resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} + engines: {node: '>= 0.6'} + + ajv-formats@3.0.1: + resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv@8.20.0: + resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} + ansis@4.3.1: resolution: {integrity: sha512-BJ8/l4R5LRE7hW9WdSuGYrLSHi2ynxeFpDFbH0K/CgNeY/tyhk+vO6TYxXC5r5CpUhNVX310xzPsN/H9lCdfOA==} engines: {node: '>=14'} @@ -988,20 +1036,77 @@ packages: argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + body-parser@2.3.0: + resolution: {integrity: sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==} + engines: {node: '>=18'} + + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + cac@7.0.0: resolution: {integrity: sha512-tixWYgm5ZoOD+3g6UTea91eow5z6AAHaho3g0V9CNSNb45gM8SmflpAc+GRd1InC4AqN/07Unrgp56Y94N9hJQ==} engines: {node: '>=20.19.0'} + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + clsx@2.1.1: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} + content-disposition@1.1.0: + resolution: {integrity: sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==} + engines: {node: '>=18'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + + content-type@2.1.0: + resolution: {integrity: sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==} + engines: {node: '>=18'} + + cookie-signature@1.2.2: + resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} + engines: {node: '>=6.6.0'} + + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + + cors@2.8.6: + resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==} + engines: {node: '>= 0.10'} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + defu@6.1.7: resolution: {integrity: sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==} + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + dts-resolver@3.0.0: resolution: {integrity: sha512-1T1f+z+4tl9XD+m+0HBgWoL/nm0bOIffyWaUuUSBlFg/86IWvfx+wjNaO/ybU0AJzG9/Mi5hBUgGV6zCmWEN7Q==} engines: {node: ^22.18.0 || >=24.0.0} @@ -1011,10 +1116,64 @@ packages: oxc-resolver: optional: true + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + empathic@2.0.1: resolution: {integrity: sha512-YGRs8knHhKHVShLkFET/rWAU8kmHbOV5LwN938RHI0pljAJ1Gf6SzXsSmRaEzcXTtOOmVqJ5+WtQPL5uigY50Q==} engines: {node: '>=14'} + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.2: + resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} + engines: {node: '>= 0.4'} + + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + + eventsource-parser@3.1.1: + resolution: {integrity: sha512-EKN1vKAMcZ8MlYMpaNuxN6R9yakzH6uajHcHVTqWJzvu5pWw9DyhbP35HH8MVBQ+dZjAfDxk+A8NiR9KWaXiyQ==} + engines: {node: '>=18.0.0'} + + eventsource@3.0.7: + resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==} + engines: {node: '>=18.0.0'} + + express-rate-limit@8.7.0: + resolution: {integrity: sha512-hOwV7WOxXfjRpAM1DSJWZDXx3GhplwD8IfwuwvogD8i1Qnkgosw/H45s4ZnFAUHDAhPjlY9hLBvJhKmGMyY26g==} + engines: {node: '>= 16'} + peerDependencies: + express: '>= 4.11' + + express@5.2.1: + resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} + engines: {node: '>= 18'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-uri@3.1.7: + resolution: {integrity: sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg==} + fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} engines: {node: '>=12.0.0'} @@ -1027,13 +1186,60 @@ packages: fflate@0.8.3: resolution: {integrity: sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==} + finalhandler@2.1.1: + resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==} + engines: {node: '>= 18.0.0'} + + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + + fresh@2.0.0: + resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} + engines: {node: '>= 0.8'} + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + get-tsconfig@5.0.0-beta.5: resolution: {integrity: sha512-/6gFNr0N04nob252sTQxyFLi3eKFRqIg1I87YcqAMT1i6SQrSF6KujUEQrtrjMV0H/eejTCltLdDSTEMzHbnsQ==} engines: {node: '>=20.20.0'} + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + hasown@2.0.4: + resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} + engines: {node: '>= 0.4'} + + hono@4.13.7: + resolution: {integrity: sha512-c8/gF9ac8Y78/agExVocyLevgR+JlpNB444Py0FSX8pJoPdYUfUzRcXtYEYGwt6l19qIlVZPN5Mfsw9jFShmQQ==} + engines: {node: '>=16.9.0'} + hookable@6.1.1: resolution: {integrity: sha512-U9LYDy1CwhMCnprUfeAZWZGByVbhd54hwepegYTK7Pi5NvqEj63ifz5z+xukznehT7i6NIZRu89Ay1AZmRsLEQ==} + http-errors@2.0.1: + resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} + engines: {node: '>= 0.8'} + + iconv-lite@0.7.3: + resolution: {integrity: sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==} + engines: {node: '>=0.10.0'} + immer@10.2.0: resolution: {integrity: sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==} @@ -1041,6 +1247,26 @@ packages: resolution: {integrity: sha512-NkJQA7oZ4YHQhd2+H3BoRFKF3d/XNsiKpHZCQEMH9pDX27hQQLsTyOocyRgaIVtf8gHX3Nt3LPkR4e5EdtPAGQ==} engines: {node: ^22.18.0 || >=24.0.0} + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ip-address@10.7.0: + resolution: {integrity: sha512-BGFsyJd5mpXp3rK6jIdADLNgpJUK1jnjzvYF8lK+VyDab9JAmqN0YOKDdP17HlgKb2+ehPgDc8EtnRLbGCAMhA==} + engines: {node: '>= 12'} + + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + + is-promise@4.0.0: + resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + jose@6.2.12: + resolution: {integrity: sha512-9NiFmJEex0sy2Dk58j2UGBSHgUs2ypF9eZSu4L6vjOX3Dp96Sw1F3uL+H+D1sx02jZZdzUT0HgvCy59CuvXcWw==} + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -1048,25 +1274,108 @@ packages: resolution: {integrity: sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==} hasBin: true + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + json-schema-typed@8.0.2: + resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==} + loose-envify@1.4.0: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + media-typer@1.1.1: + resolution: {integrity: sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==} + engines: {node: '>= 0.8'} + + merge-descriptors@2.0.0: + resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} + engines: {node: '>=18'} + + mime-db@1.54.0: + resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} + engines: {node: '>= 0.6'} + + mime-types@3.0.2: + resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} + engines: {node: '>=18'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + negotiator@1.1.0: + resolution: {integrity: sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg==} + engines: {node: '>=18'} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + obug@2.1.4: resolution: {integrity: sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==} engines: {node: '>=12.20.0'} + on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-to-regexp@8.4.2: + resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==} + picomatch@4.0.7: resolution: {integrity: sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==} engines: {node: '>=12'} + pkce-challenge@5.0.1: + resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==} + engines: {node: '>=16.20.0'} + + proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + + qs@6.16.0: + resolution: {integrity: sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==} + engines: {node: '>=0.6'} + quansync@1.0.0: resolution: {integrity: sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA==} + range-parser@1.3.0: + resolution: {integrity: sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==} + engines: {node: '>= 0.6'} + + raw-body@3.0.2: + resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} + engines: {node: '>= 0.10'} + react@18.3.1: resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} engines: {node: '>=0.10.0'} + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} @@ -1094,6 +1403,52 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} hasBin: true + router@2.2.0: + resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} + engines: {node: '>= 18'} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + send@1.2.1: + resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==} + engines: {node: '>= 18'} + + serve-static@2.2.1: + resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==} + engines: {node: '>= 18'} + + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + side-channel-list@1.0.1: + resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.1: + resolution: {integrity: sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==} + engines: {node: '>= 0.4'} + + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + tinyexec@1.3.0: resolution: {integrity: sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==} engines: {node: '>=18'} @@ -1102,6 +1457,10 @@ packages: resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} engines: {node: '>=12.0.0'} + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + tree-kill@1.2.2: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} hasBin: true @@ -1140,6 +1499,10 @@ packages: unrun: optional: true + type-is@2.1.0: + resolution: {integrity: sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==} + engines: {node: '>= 18'} + typescript@5.9.3: resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} engines: {node: '>=14.17'} @@ -1148,15 +1511,31 @@ packages: unconfig-core@7.5.0: resolution: {integrity: sha512-Su3FauozOGP44ZmKdHy2oE6LPjk51M/TRRjHv2HNCWiDvfvCoxC2lno6jevMA91MYAdCdwP05QnWdWpSbncX/w==} + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + use-sync-external-store@1.2.0: resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + verkit@0.3.2: resolution: {integrity: sha512-zj/ob3UsvJGN0whEAKFp53REA5X66hvffVqoCtVQAakJKnKlH+/PcOfMoFwIG/o4rElqLv/ycAFlx8ZlXUorCg==} engines: {node: '>=18.12.0'} + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + ws@8.21.3: resolution: {integrity: sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==} engines: {node: '>=10.0.0'} @@ -1178,6 +1557,11 @@ packages: yuku-parser@0.8.7: resolution: {integrity: sha512-vRD9nwt4L3aYpxNqeSC4WqLv58xrXef0Ong1Mc45CTXTIpvLafx7JO05sczmQZwdLEZvywrLOGdNC5+Rp5N1BQ==} + zod-to-json-schema@3.25.2: + resolution: {integrity: sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==} + peerDependencies: + zod: ^3.25.28 || ^4 + zod@4.4.3: resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} @@ -1604,6 +1988,22 @@ snapshots: '@deepseek-ai/dsh-timeout': 0.1.1-rc.2(@deepseek-ai/cordis@4.0.1)(@deepseek-ai/dsh-invariants@0.1.1-rc.2(@deepseek-ai/cordis@4.0.1)) '@deepseek-ai/schemastery': 3.18.1 + '@deepseek-ai/dsh-mcp-client@0.1.1-rc.2(b23e1476680e2fc848944102d2a500cd)': + dependencies: + '@deepseek-ai/cordis': 4.0.1(@deepseek-ai/cordis-plugin-include@1.0.6)(@deepseek-ai/cordis-plugin-loader@1.0.2) + '@deepseek-ai/dsh-attachment': 0.1.1-rc.2(@deepseek-ai/cordis@4.0.1)(@deepseek-ai/dsh-brand@0.1.1-rc.2(@deepseek-ai/cordis@4.0.1)(@deepseek-ai/dsh-invariants@0.1.1-rc.2(@deepseek-ai/cordis@4.0.1)))(@deepseek-ai/dsh-invariants@0.1.1-rc.2(@deepseek-ai/cordis@4.0.1)) + '@deepseek-ai/dsh-invariants': 0.1.1-rc.2(@deepseek-ai/cordis@4.0.1) + '@deepseek-ai/dsh-llm': 0.1.1-rc.2(@deepseek-ai/cordis@4.0.1)(@deepseek-ai/dsh-attachment@0.1.1-rc.2(@deepseek-ai/cordis@4.0.1)(@deepseek-ai/dsh-brand@0.1.1-rc.2(@deepseek-ai/cordis@4.0.1)(@deepseek-ai/dsh-invariants@0.1.1-rc.2(@deepseek-ai/cordis@4.0.1)))(@deepseek-ai/dsh-invariants@0.1.1-rc.2(@deepseek-ai/cordis@4.0.1)))(@deepseek-ai/dsh-brand@0.1.1-rc.2(@deepseek-ai/cordis@4.0.1)(@deepseek-ai/dsh-invariants@0.1.1-rc.2(@deepseek-ai/cordis@4.0.1)))(@deepseek-ai/dsh-invariants@0.1.1-rc.2(@deepseek-ai/cordis@4.0.1))(@deepseek-ai/dsh-timeout@0.1.1-rc.2(@deepseek-ai/cordis@4.0.1)(@deepseek-ai/dsh-invariants@0.1.1-rc.2(@deepseek-ai/cordis@4.0.1))) + '@deepseek-ai/dsh-subprocess': 0.1.1-rc.2(@deepseek-ai/cordis@4.0.1)(@deepseek-ai/dsh-invariants@0.1.1-rc.2(@deepseek-ai/cordis@4.0.1)) + '@deepseek-ai/dsh-timeout': 0.1.1-rc.2(@deepseek-ai/cordis@4.0.1)(@deepseek-ai/dsh-invariants@0.1.1-rc.2(@deepseek-ai/cordis@4.0.1)) + '@deepseek-ai/dsh-tools': 0.1.1-rc.2(119bc70f73f8eddebfaa6b47561adeb3) + '@deepseek-ai/schemastery': 3.18.1 + '@modelcontextprotocol/sdk': 1.30.0(zod@4.4.3) + zod: 4.4.3 + transitivePeerDependencies: + - '@cfworker/json-schema' + - supports-color + '@deepseek-ai/dsh-message-feedback@0.1.1-rc.2(b957b1ba06764ef01eadad39e9185151)': dependencies: '@deepseek-ai/cordis': 4.0.1(@deepseek-ai/cordis-plugin-include@1.0.6)(@deepseek-ai/cordis-plugin-loader@1.0.2) @@ -1916,6 +2316,32 @@ snapshots: '@deepseek-ai/cosmokit': 1.8.2 '@standard-schema/spec': 1.1.0 + '@hono/node-server@2.1.1(hono@4.13.7)': + dependencies: + hono: 4.13.7 + + '@modelcontextprotocol/sdk@1.30.0(zod@4.4.3)': + dependencies: + '@hono/node-server': 2.1.1(hono@4.13.7) + ajv: 8.20.0 + ajv-formats: 3.0.1(ajv@8.20.0) + content-type: 1.0.5 + cors: 2.8.6 + cross-spawn: 7.0.6 + eventsource: 3.0.7 + eventsource-parser: 3.1.1 + express: 5.2.1 + express-rate-limit: 8.7.0(express@5.2.1) + hono: 4.13.7 + jose: 6.2.12 + json-schema-typed: 8.0.2 + pkce-challenge: 5.0.1 + raw-body: 3.0.2 + zod: 4.4.3 + zod-to-json-schema: 3.25.2(zod@4.4.3) + transitivePeerDependencies: + - supports-color + '@oxc-project/types@0.147.0': {} '@quansync/fs@1.0.0': @@ -2052,58 +2478,332 @@ snapshots: '@yuku-toolchain/types@0.8.7': {} + accepts@2.0.0: + dependencies: + mime-types: 3.0.2 + negotiator: 1.1.0 + + ajv-formats@3.0.1(ajv@8.20.0): + optionalDependencies: + ajv: 8.20.0 + + ajv@8.20.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.7 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + ansis@4.3.1: {} argparse@2.0.1: {} + body-parser@2.3.0: + dependencies: + bytes: 3.1.2 + content-type: 2.1.0 + debug: 4.4.3 + http-errors: 2.0.1 + iconv-lite: 0.7.3 + on-finished: 2.4.1 + qs: 6.16.0 + raw-body: 3.0.2 + type-is: 2.1.0 + transitivePeerDependencies: + - supports-color + + bytes@3.1.2: {} + cac@7.0.0: {} + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + clsx@2.1.1: {} + content-disposition@1.1.0: {} + + content-type@1.0.5: {} + + content-type@2.1.0: {} + + cookie-signature@1.2.2: {} + + cookie@0.7.2: {} + + cors@2.8.6: + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + csstype@3.2.3: {} + debug@4.4.3: + dependencies: + ms: 2.1.3 + defu@6.1.7: {} + depd@2.0.0: {} + dts-resolver@3.0.0: {} + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + ee-first@1.1.1: {} + empathic@2.0.1: {} + encodeurl@2.0.0: {} + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.2: + dependencies: + es-errors: 1.3.0 + + escape-html@1.0.3: {} + + etag@1.8.1: {} + + eventsource-parser@3.1.1: {} + + eventsource@3.0.7: + dependencies: + eventsource-parser: 3.1.1 + + express-rate-limit@8.7.0(express@5.2.1): + dependencies: + debug: 4.4.3 + express: 5.2.1 + ip-address: 10.7.0 + transitivePeerDependencies: + - supports-color + + express@5.2.1: + dependencies: + accepts: 2.0.0 + body-parser: 2.3.0 + content-disposition: 1.1.0 + content-type: 1.0.5 + cookie: 0.7.2 + cookie-signature: 1.2.2 + debug: 4.4.3 + depd: 2.0.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 2.1.1 + fresh: 2.0.0 + http-errors: 2.0.1 + merge-descriptors: 2.0.0 + mime-types: 3.0.2 + on-finished: 2.4.1 + once: 1.4.0 + parseurl: 1.3.3 + proxy-addr: 2.0.7 + qs: 6.16.0 + range-parser: 1.3.0 + router: 2.2.0 + send: 1.2.1 + serve-static: 2.2.1 + statuses: 2.0.2 + type-is: 2.1.0 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + + fast-deep-equal@3.1.3: {} + + fast-uri@3.1.7: {} + fdir@6.5.0(picomatch@4.0.7): optionalDependencies: picomatch: 4.0.7 fflate@0.8.3: {} + finalhandler@2.1.1: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + forwarded@0.2.0: {} + + fresh@2.0.0: {} + + function-bind@1.1.2: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.4 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.2 + get-tsconfig@5.0.0-beta.5: dependencies: resolve-pkg-maps: 1.0.0 + gopd@1.2.0: {} + + has-symbols@1.1.0: {} + + hasown@2.0.4: + dependencies: + function-bind: 1.1.2 + + hono@4.13.7: {} + hookable@6.1.1: {} + http-errors@2.0.1: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.2 + toidentifier: 1.0.1 + + iconv-lite@0.7.3: + dependencies: + safer-buffer: 2.1.2 + immer@10.2.0: {} import-without-cache@0.4.0: {} + inherits@2.0.4: {} + + ip-address@10.7.0: {} + + ipaddr.js@1.9.1: {} + + is-promise@4.0.0: {} + + isexe@2.0.0: {} + + jose@6.2.12: {} + js-tokens@4.0.0: {} js-yaml@4.3.2: dependencies: argparse: 2.0.1 + json-schema-traverse@1.0.0: {} + + json-schema-typed@8.0.2: {} + loose-envify@1.4.0: dependencies: js-tokens: 4.0.0 + math-intrinsics@1.1.0: {} + + media-typer@1.1.1: {} + + merge-descriptors@2.0.0: {} + + mime-db@1.54.0: {} + + mime-types@3.0.2: + dependencies: + mime-db: 1.54.0 + + ms@2.1.3: {} + + negotiator@1.1.0: + dependencies: + content-type: 2.1.0 + + object-assign@4.1.1: {} + + object-inspect@1.13.4: {} + obug@2.1.4: {} + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + parseurl@1.3.3: {} + + path-key@3.1.1: {} + + path-to-regexp@8.4.2: {} + picomatch@4.0.7: {} + pkce-challenge@5.0.1: {} + + proxy-addr@2.0.7: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + + qs@6.16.0: + dependencies: + es-define-property: 1.0.1 + side-channel: 1.1.1 + quansync@1.0.0: {} + range-parser@1.3.0: {} + + raw-body@3.0.2: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.1 + iconv-lite: 0.7.3 + unpipe: 1.0.0 + react@18.3.1: dependencies: loose-envify: 1.4.0 + require-from-string@2.0.2: {} + resolve-pkg-maps@1.0.0: {} rolldown-plugin-dts@0.27.14(rolldown@1.2.6)(typescript@5.9.3): @@ -2141,6 +2841,81 @@ snapshots: '@rolldown/binding-win32-arm64-msvc': 1.2.6 '@rolldown/binding-win32-x64-msvc': 1.2.6 + router@2.2.0: + dependencies: + debug: 4.4.3 + depd: 2.0.0 + is-promise: 4.0.0 + parseurl: 1.3.3 + path-to-regexp: 8.4.2 + transitivePeerDependencies: + - supports-color + + safer-buffer@2.1.2: {} + + send@1.2.1: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 2.0.0 + http-errors: 2.0.1 + mime-types: 3.0.2 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.3.0 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + serve-static@2.2.1: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 1.2.1 + transitivePeerDependencies: + - supports-color + + setprototypeof@1.2.0: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + side-channel-list@1.0.1: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.1: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.1 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + statuses@2.0.2: {} + tinyexec@1.3.0: {} tinyglobby@0.2.17: @@ -2148,6 +2923,8 @@ snapshots: fdir: 6.5.0(picomatch@4.0.7) picomatch: 4.0.7 + toidentifier@1.0.1: {} + tree-kill@1.2.2: {} tsdown@0.22.14(typescript@5.9.3): @@ -2175,6 +2952,12 @@ snapshots: - oxc-resolver - vue-tsc + type-is@2.1.0: + dependencies: + content-type: 2.1.0 + media-typer: 1.1.1 + mime-types: 3.0.2 + typescript@5.9.3: {} unconfig-core@7.5.0: @@ -2182,12 +2965,22 @@ snapshots: '@quansync/fs': 1.0.0 quansync: 1.0.0 + unpipe@1.0.0: {} + use-sync-external-store@1.2.0(react@18.3.1): dependencies: react: 18.3.1 + vary@1.1.2: {} + verkit@0.3.2: {} + which@2.0.2: + dependencies: + isexe: 2.0.0 + + wrappy@1.0.2: {} + ws@8.21.3: {} yuku-ast@0.8.7: @@ -2229,6 +3022,10 @@ snapshots: '@yuku-parser/binding-win32-arm64': 0.8.7 '@yuku-parser/binding-win32-x64': 0.8.7 + zod-to-json-schema@3.25.2(zod@4.4.3): + dependencies: + zod: 4.4.3 + zod@4.4.3: {} zustand@4.4.7(@types/react@18.3.31)(immer@10.2.0)(react@18.3.1): diff --git a/src/api/index.js b/src/api/index.js index 4750712..c6b8772 100644 --- a/src/api/index.js +++ b/src/api/index.js @@ -60,8 +60,8 @@ const HANDLERS = [ * @param {import('@deepseek-ai/cordis').Context} ctx * @param {object} runtime { manager, settings, dataSource, marketCache } */ -export function registerApi(ctx, { manager, settings, dataSource, marketHub, marketFeed, qmtHealthMonitor, storage, tradeSync, positionSync, attribution }) { - const runtime = { ctx, manager, settings, dataSource, marketHub, marketFeed, qmtHealthMonitor, storage, tradeSync, positionSync, attribution }; +export function registerApi(ctx, { manager, settings, dataSource, marketHub, marketFeed, qmtHealthMonitor, storage, tradeSync, positionSync, attribution, mcpManager }) { + const runtime = { ctx, manager, settings, dataSource, marketHub, marketFeed, qmtHealthMonitor, storage, tradeSync, positionSync, attribution, mcpManager }; ctx.effect(() => ctx.webServer.register({ kind: 'prefix', diff --git a/src/api/market.js b/src/api/market.js index 355d47f..3a643b0 100644 --- a/src/api/market.js +++ b/src/api/market.js @@ -42,7 +42,7 @@ function projectQuote(hub, code, snap) { * @param {object} args * @param {object} runtime { settings, dataSource, marketHub, marketFeed(=QuoteSync), qmtHealthMonitor, positionSync } */ -export async function handleMarket(method, args, { settings, dataSource, marketHub, marketFeed: marketSync, qmtHealthMonitor, positionSync }) { +export async function handleMarket(method, args, { settings, dataSource, marketHub, marketFeed: marketSync, qmtHealthMonitor, positionSync, mcpManager }) { switch (method) { case 'market-snapshot': { const codes = Array.isArray(args.codes) ? args.codes : []; @@ -86,6 +86,7 @@ export async function handleMarket(method, args, { settings, dataSource, marketH ? { ...marketSync.getStatus(), state: state(marketSync.getStatus()) } : null, qmt: qmtHealthMonitor ? qmtHealthMonitor.getStatus() : null, + mcp: mcpManager ? mcpManager.getStatus() : null, // R-020/迭代18:QMT MCP 状态(会话头部指示灯) }; } case 'sync-now': { diff --git a/src/api/qmt-connections.js b/src/api/qmt-connections.js index 008bc2a..d4e3e14 100644 --- a/src/api/qmt-connections.js +++ b/src/api/qmt-connections.js @@ -28,6 +28,7 @@ export const QMT_METHODS = new Set([ 'qmt-connections/set-default', 'qmt-connections/test', 'qmt-connections/active-host', + 'qmt-connections/mcp-status', // R-020/迭代18:QMT MCP 状态(probe / 缓存) ]); /** 服务端代理测试连接:GET {baseUrl}/health(浏览器跨域规避,Q3 手动测试) */ @@ -63,7 +64,7 @@ async function testQmtConnection(ctx, args) { * @param {object} args * @param {object} runtime { manager, settings, dataSource, marketCache } */ -export async function handleQmt(method, args, { ctx, settings, dataSource, marketFeed, qmtHealthMonitor }) { +export async function handleQmt(method, args, { ctx, settings, dataSource, marketFeed, qmtHealthMonitor, mcpManager }) { switch (method) { case 'qmt-connections': { const state = getQmtConnections(settings); @@ -80,6 +81,8 @@ export async function handleQmt(method, args, { ctx, settings, dataSource, marke marketFeed?.setBaseUrl(value.baseUrl); qmtHealthMonitor?.probe(); // 切换后立即补探健康 ctx?.logger?.info('[one-divine-lot] 激活配置地址已变更,数据源热切换 → ' + value.baseUrl); + // R-020/迭代18:MCP 目标地址随激活地址变更重挂 + mcpManager?.resync(value.baseUrl).catch(() => {}); } return value; } @@ -94,6 +97,8 @@ export async function handleQmt(method, args, { ctx, settings, dataSource, marke qmtHealthMonitor?.probe(); // 切换后立即补探健康 ctx?.logger?.info('[one-divine-lot] 激活配置已删除,自动切换 → ' + active.name + ' (' + active.baseUrl + ')'); } + // R-020/迭代18:激活已变(或列表清空)→ MCP 跟随新激活 / 卸载 + mcpManager?.resync(active?.baseUrl).catch(() => {}); return { ...afterState, active, removedWasActive: result.removedWasActive, removedWasDefault: result.removedWasDefault }; } case 'qmt-connections/activate': { @@ -105,6 +110,8 @@ export async function handleQmt(method, args, { ctx, settings, dataSource, marke qmtHealthMonitor?.probe(); // 切换后立即补探健康 ctx?.logger?.info('[one-divine-lot] QMT 连接激活: ' + active.name + ' (' + active.baseUrl + ')'); } + // R-020/迭代18:激活切换 → MCP 实例跟随(卸旧挂新,url=新激活 baseUrl+/mcp) + mcpManager?.resync(active?.baseUrl).catch(() => {}); return { ...state, active }; } case 'qmt-connections/set-default': @@ -132,6 +139,12 @@ export async function handleQmt(method, args, { ctx, settings, dataSource, marke } catch { /* baseUrl 非法时返回空 */ } return { name: active.name, baseUrl, host, wsUrl, activeId: state.activeId ?? '' }; } + case 'qmt-connections/mcp-status': { + // R-020/迭代18:MCP 状态(缓存秒回;refresh=true 即时探测) + if (!mcpManager) return { state: 'disabled', error: 'mcpManager 不可用', checkedAt: 0 }; + if (args.refresh) return await mcpManager.probe(); + return mcpManager.getStatus(); + } default: throw Object.assign(new Error('unknown qmt method: ' + method), { code: 'not-found' }); } diff --git a/src/client/views/SettingsSection.jsx b/src/client/views/SettingsSection.jsx index ee36fb6..5ca2cb5 100644 --- a/src/client/views/SettingsSection.jsx +++ b/src/client/views/SettingsSection.jsx @@ -520,6 +520,8 @@ function QmtConnectionsSettings() { const [editingId, setEditingId] = useState(null); const [formName, setFormName] = useState(''); const [formUrl, setFormUrl] = useState(''); + const [mcpStatus, setMcpStatus] = useState(null); // R-020/迭代18:MCP 状态 + const [mcpChecking, setMcpChecking] = useState(false); const call = useRpc(); const toast = useToast(); @@ -533,7 +535,14 @@ function QmtConnectionsSettings() { } }, [call]); - useEffect(() => { load(); }, [load]); + const loadMcp = useCallback(async (refresh) => { + try { + const res = await call('one-divine-lot/qmt-connections/mcp-status', { args: { refresh: !!refresh } }); + if (res && res.ok) setMcpStatus(res.value); + } catch { /* 静默 */ } + }, [call]); + + useEffect(() => { load(); loadMcp(false); }, [load, loadMcp]); const openAdd = () => { setEditingId(null); setFormName(''); setFormUrl(''); setFormOpen(true); setTestResult(null); }; const openEdit = (c) => { setEditingId(c.id); setFormName(c.name); setFormUrl(c.baseUrl); setFormOpen(true); setTestResult(null); }; @@ -576,6 +585,7 @@ function QmtConnectionsSettings() { const res = await call('one-divine-lot/qmt-connections/activate', { args: { id: c.id } }); if (res && res.ok) { setState(res.value); + loadMcp(false); // R-020/迭代18:激活切换后刷新 MCP 状态 toast.success('已激活:' + c.name + '(立即生效)'); } else { toast.error((res && res.error && res.error.message) || '激活失败'); @@ -679,6 +689,41 @@ function QmtConnectionsSettings() { + {/* R-020/迭代18:MCP 连接状态条(随激活连接;绿=已连接+N工具 / 黄=连接中 / 红=断开 / 灰=未启用) */} +
+ MCP 连接 + + + +
+ {error &&
} {!state && !error &&
加载中...
} diff --git a/src/client/views/SyncIndicators.jsx b/src/client/views/SyncIndicators.jsx index e97e95c..c75fbb1 100644 --- a/src/client/views/SyncIndicators.jsx +++ b/src/client/views/SyncIndicators.jsx @@ -2,7 +2,7 @@ * SyncIndicators —— 数据同步指示灯组(R-015 / 迭代 13;2026-09-03 老师调整:三灯带全称标签) * * 位置:会话头部(QmtConnectionChip 内,QMT 下拉框右侧)。 - * 形态:QMT连接(灯)|持仓数据(灯)|行情数据(灯)——统一圆点 + 全称标签。 + * 形态:QMT连接(灯)|持仓数据(灯)|行情数据(灯)|MCP(灯,R-020/迭代18)——统一圆点 + 全称标签。 * * 状态(产品约束-012): * - QMT 连接:绿=健康 / 红=异常 / 灰=未知(qmt-health 缓存) @@ -87,6 +87,19 @@ export function SyncIndicators() { } }, [call, healthRefreshing, load]); + // R-020/迭代18:MCP 状态即时刷新(点灯触发) + const [mcpRefreshing, setMcpRefreshing] = useState(false); + const refreshMcp = useCallback(async () => { + if (mcpRefreshing) return; + setMcpRefreshing(true); + try { + await call('one-divine-lot/qmt-connections/mcp-status', { args: { refresh: true } }); + } catch { /* 静默 */ } finally { + setMcpRefreshing(false); + load(); + } + }, [call, mcpRefreshing, load]); + if (!status) return null; // QMT 连接灯:绿=健康 / 红=异常 / 灰=未知 @@ -115,6 +128,27 @@ export function SyncIndicators() { return lines.filter(Boolean).join(' | '); }; + // MCP 灯(R-020/迭代18):绿=已连接(connected) / 黄=连接中(connecting) / 红=断开(error) / 灰=未启用(disabled/缺失) + const mcp = status.mcp; + const mcpColor = mcp?.state === 'connected' + ? 'var(--dsw-alias-state-success-primary, #2e7d32)' + : mcp?.state === 'connecting' + ? 'var(--dsw-alias-state-warn-primary, #f9a825)' + : mcp?.state === 'error' + ? 'var(--dsw-alias-state-error-primary, #d32f2f)' + : 'var(--dsw-alias-label-tertiary, #bbb)'; + const mcpTitleLines = [ + 'MCP:' + ({ + connected: '已连接' + (mcp?.toolCount != null ? '(' + mcp.toolCount + ' 个工具)' : ''), + connecting: '连接中', + error: '断开' + (mcp?.error ? ':' + String(mcp.error).slice(0, 60) : ''), + disabled: '未启用(无激活连接)', + }[mcp?.state] ?? '未知'), + mcp?.url ? mcp.url : null, + mcp?.checkedAt ? '检测于 ' + new Date(mcp.checkedAt).toLocaleTimeString() : null, + '点击重新检测', + ].filter(Boolean).join(' | '); + const sep = ; return ( @@ -138,6 +172,8 @@ export function SyncIndicators() { onClick={() => syncNow('quote')} dimmed={syncingDomain === 'quote'} /> + {sep} + ); } diff --git a/src/index.js b/src/index.js index 24ccc8e..409625d 100644 --- a/src/index.js +++ b/src/index.js @@ -24,6 +24,7 @@ import { QuoteSync } from './market/QuoteSync.js'; import { QmtHealthMonitor } from './data-source/QmtHealthMonitor.js'; import { TradeSync } from './trades/TradeSync.js'; import { AttributionService } from './trades/AttributionService.js'; +import { QmtMcpManager } from './mcp/QmtMcpManager.js'; // R-020/迭代18:QMT Bridge MCP 能力内建 const name = 'one-divine-lot'; @@ -90,9 +91,12 @@ async function apply(ctx, config) { // R-018 迭代 16:归属服务(关联 = 账本写操作;经 SqliteStore 段表/生命周期同步接口) const attribution = new AttributionService({ store: storage.sqlite }); + // R-020/迭代18:QMT Bridge MCP 客户端管理(动态挂载官方 dsh-mcp-client;随激活连接挂载/切换) + const mcpManager = new QmtMcpManager({ ctx, runtime: { settings, dataSource }, logger }); + mcpManager.start(); // 启动即按激活连接挂载 + 探测(内部 catch 不阻塞) // S6: 服务端 HTTP API(R-004:注入 dataSource 以编排激活热切换;R-009:注入 storage 供 trades/history;R-018:注入 attribution) - registerApi(ctx, { manager, settings, dataSource, marketHub, marketFeed, qmtHealthMonitor, storage, tradeSync, positionSync, attribution }); + registerApi(ctx, { manager, settings, dataSource, marketHub, marketFeed, qmtHealthMonitor, storage, tradeSync, positionSync, attribution, mcpManager }); // 启动时可用性检查(日志,不阻塞) dataSource.isAvailable().then((ok) => { @@ -108,6 +112,7 @@ async function apply(ctx, config) { qmtHealthMonitor.stop(); positionSync.stop(); // 停止持仓快照定时同步(2026-09-02;内存快照保留供收尾读取) tradeSync.stop(); // 停止交易记录定时同步(迭代 07) + mcpManager.dispose(); // R-020/迭代18:卸载 QMT MCP 实例 storage.close(); // 关闭 SQLite 连接(迭代 06) logger.info('[one-divine-lot] 插件已释放'); }; diff --git a/src/mcp/QmtMcpManager.js b/src/mcp/QmtMcpManager.js new file mode 100644 index 0000000..42d0bc4 --- /dev/null +++ b/src/mcp/QmtMcpManager.js @@ -0,0 +1,245 @@ +/** + * QmtMcpManager —— QMT Bridge MCP 客户端管理(R-020 / 迭代 18) + * + * 职责:在神之一手插件 apply 内动态挂载 DSH 官方 @deepseek-ai/dsh-mcp-client, + * 把 QMT Bridge /mcp 的工具以 mcp__QMT_Bridge_MCP__* 注册给模型;管理 MCP 实例的 + * 挂载 / 切换 / 卸载(随 QMT 连接配置激活联动),并提供连接状态(SDK 独立探测)。 + * + * 设计约束(R-020 Q1-Q6): + * - Q1 只挂当前激活连接:唯一实例,serverName 固定 QMT_Bridge_MCP; + * - Q3 MCP url 自动派生:激活连接 baseUrl + /mcp; + * - Q5 失败不崩:failOnStartupError:false + reconnect(对齐宿主受管块同参); + * - Q4 状态出口:mcp-status 端点 + sync-status.mcp 域(设置页 + 会话头部指示灯)。 + * + * 实现参照:mcp_router(ctx.plugin 动态挂载 dsh-mcp-client 实证); + * 挂载错误必须 catch(同 serverName 重复 / 依赖缺失),不影响插件其余功能。 + */ + +import { Client } from '@modelcontextprotocol/sdk/client/index.js'; +import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'; +import { resolveActiveBaseUrl } from '../api/common.js'; + +const SERVER_NAME = 'QMT_Bridge_MCP'; +const PROBE_TIMEOUT_MS = 8000; +const MOUNT_CONFIG_BASE = { + serverName: SERVER_NAME, + transport: 'streamable-http', + headers: {}, + toolCallTimeoutMs: 60000, + failOnStartupError: false, + reconnect: { enabled: true, initialDelayMs: 500, maxDelayMs: 30000, maxAttempts: 10 }, +}; + +export class QmtMcpManager { + /** + * @param {object} opts + * @param {object} opts.ctx Cordis 宿主 ctx + * @param {object} opts.runtime { settings, dataSource } —— 解析激活 baseUrl + * @param {object} [opts.logger] + */ + constructor({ ctx, runtime, logger } = {}) { + this.ctx = ctx; + this.runtime = runtime; + this.logger = logger ?? console; + /** @type {Promise|null} dsh-mcp-client 模块(惰性加载) */ + this._modulePromise = null; + /** @type {object|null} 当前挂载的 fiber(ctx.plugin 返回值,含 dispose) */ + this._fiber = null; + /** @type {string} 当前已挂载的 mcp url('' = 未挂载) */ + this._currentUrl = ''; + /** 状态缓存:{ state, serverName, url, mounted, toolCount, error, latencyMs, checkedAt } */ + this.status = this._disabledStatus(); + } + + _disabledStatus() { + return { + state: 'disabled', serverName: SERVER_NAME, url: '', mounted: false, + toolCount: 0, error: null, latencyMs: 0, checkedAt: 0, + }; + } + + /** 当前激活连接对应的 mcp url(baseUrl + /mcp);无激活连接返回 '' */ + _currentMcpUrl() { + const base = resolveActiveBaseUrl(this.runtime.settings, this.runtime.dataSource); + return base ? String(base).replace(/\/+$/, '') + '/mcp' : ''; + } + + /** 惰性加载 dsh-mcp-client 模块(失败不抛,返回 null) */ + async _loadModule() { + if (!this._modulePromise) { + this._modulePromise = import('@deepseek-ai/dsh-mcp-client') + .catch((err) => { + this.logger.warn?.('[one-divine-lot] 无法加载 @deepseek-ai/dsh-mcp-client: ' + (err?.message ?? err)); + return null; + }); + } + return this._modulePromise; + } + + /** 启动:解析当前激活 baseUrl → 挂载 + 探测(非阻塞,内部 catch) */ + async start() { + try { + const url = this._currentMcpUrl(); + if (url) { + await this._mount(url); + } else { + this.status = this._disabledStatus(); + } + await this.probe(); + } catch (err) { + this.logger.warn?.('[one-divine-lot] QMT MCP 启动异常: ' + (err?.message ?? err)); + } + } + + /** + * 挂载 dsh-mcp-client 到指定 mcp url(先卸旧再挂新;同址幂等)。 + * @param {string} url + */ + async _mount(url) { + const cleanUrl = String(url ?? '').trim().replace(/\/+$/, ''); + if (!cleanUrl) { + await this.unmount(); + return; + } + if (this._currentUrl === cleanUrl && this._fiber) return; // 已挂同址 + + this._unmountFiber(); + const mod = await this._loadModule(); + if (!mod) { + this._currentUrl = cleanUrl; + this.status = { + state: 'error', serverName: SERVER_NAME, url: cleanUrl, mounted: false, + toolCount: 0, error: '@deepseek-ai/dsh-mcp-client 依赖不可用', latencyMs: 0, checkedAt: Date.now(), + }; + return; + } + const config = { ...MOUNT_CONFIG_BASE, url: cleanUrl }; + try { + const fiber = this.ctx.plugin(mod, config); + this._fiber = fiber; + this._currentUrl = cleanUrl; + this.status = { + ...this._disabledStatus(), state: 'connecting', url: cleanUrl, mounted: true, checkedAt: Date.now(), + }; + // 激活结果(连接 + 工具同步)非致命:成功/失败都记录,不抛给插件主体 + fiber.then( + () => this.logger.info?.('[one-divine-lot] QMT MCP 已挂载: ' + SERVER_NAME + ' (' + cleanUrl + ')'), + (err) => { + this.logger.warn?.('[one-divine-lot] QMT MCP 挂载异常: ' + (err?.message ?? err)); + // 挂载失败(如同名 serverName 冲突 / 连接失败)→ 状态标 error,不崩插件 + this.status = { + ...this.status, state: 'error', + error: (err?.message ?? String(err)).slice(0, 300), checkedAt: Date.now(), + }; + }, + ); + } catch (err) { + this.logger.warn?.('[one-divine-lot] QMT MCP 挂载抛出: ' + (err?.message ?? err)); + this._fiber = null; + this._currentUrl = cleanUrl; + this.status = { + ...this._disabledStatus(), state: 'error', url: cleanUrl, mounted: false, + error: (err?.message ?? String(err)).slice(0, 300), checkedAt: Date.now(), + }; + } + } + + /** 卸载当前 fiber(内部 dispose,失败静默) */ + _unmountFiber() { + const fiber = this._fiber; + this._fiber = null; + if (fiber) { + try { fiber.dispose?.(); } catch { /* already disposed */ } + } + } + + /** 显式卸载(置 disabled 状态) */ + async unmount() { + this._unmountFiber(); + this._currentUrl = ''; + this.status = this._disabledStatus(); + } + + /** + * 连接配置热切换联动(activate / update 地址 / remove 激活后调用)。 + * baseUrl 变 → 卸旧挂新;不变 → 仅刷新探测。 + * @param {string} baseUrl 新的激活连接 baseUrl(可能为空 = 无激活) + */ + async resync(baseUrl) { + try { + const next = baseUrl ? String(baseUrl).replace(/\/+$/, '') + '/mcp' : ''; + if (!next) { + await this.unmount(); + return this.status; + } + if (next === this._currentUrl && this._fiber) { + await this.probe(); + return this.status; + } + await this._mount(next); + await this.probe(); + return this.status; + } catch (err) { + this.logger.warn?.('[one-divine-lot] QMT MCP resync 异常: ' + (err?.message ?? err)); + return this.status; + } + } + + /** + * 独立探测:SDK Client 连 {url} → initialize + tools/list → 工具数/延迟。 + * 与 dsh-mcp-client 自管连接互不干扰(只读检查,用完即关)。 + * @param {string} [url] 缺省用当前激活派生 url + */ + async probe(url) { + const target = String(url ?? this._currentMcpUrl() ?? '').trim().replace(/\/+$/, ''); + if (!target) { + this.status = this._disabledStatus(); + return this.status; + } + const startedAt = Date.now(); + let client = null; + try { + client = new Client({ name: 'one-divine-lot-mcp-probe', version: '0.1.0' }, { capabilities: {} }); + const transport = new StreamableHTTPClientTransport(new URL(target), { + requestInit: { headers: {} }, + }); + await Promise.race([ + client.connect(transport), + new Promise((_, rej) => setTimeout(() => rej(new Error('探测超时 (' + PROBE_TIMEOUT_MS + 'ms)')), PROBE_TIMEOUT_MS)), + ]); + const list = await Promise.race([ + client.listTools(), + new Promise((_, rej) => setTimeout(() => rej(new Error('探测超时 (' + PROBE_TIMEOUT_MS + 'ms)')), PROBE_TIMEOUT_MS)), + ]); + const tools = Array.isArray(list?.tools) ? list.tools : []; + this.status = { + state: 'connected', serverName: SERVER_NAME, url: target, + mounted: !!this._fiber && this._currentUrl === target, + toolCount: tools.length, error: null, + latencyMs: Date.now() - startedAt, checkedAt: Date.now(), + }; + try { await client.close(); } catch { /* ignore */ } + } catch (err) { + this.status = { + state: 'error', serverName: SERVER_NAME, url: target, + mounted: !!this._fiber, toolCount: 0, + error: (err?.message ?? String(err)).slice(0, 300), + latencyMs: Date.now() - startedAt, checkedAt: Date.now(), + }; + try { await client?.close(); } catch { /* ignore */ } + } + return this.status; + } + + /** 读取缓存状态(前端秒回) */ + getStatus() { + return { ...this.status }; + } + + /** 释放(插件 dispose 时调用) */ + async dispose() { + await this.unmount(); + } +} + +export default QmtMcpManager; diff --git a/tsdown.config.ts b/tsdown.config.ts index 0a3ab79..71d6202 100644 --- a/tsdown.config.ts +++ b/tsdown.config.ts @@ -2,7 +2,7 @@ import { defineConfig } from 'tsdown'; export default defineConfig([ { - entry: ['src/index.js', 'src/api/index.js', 'src/settings.js', 'src/data-source/*.js', 'src/storage/*.js', 'src/position/*.js', 'src/market/*.js', 'src/trades/*.js'], + entry: ['src/index.js', 'src/api/index.js', 'src/settings.js', 'src/data-source/*.js', 'src/storage/*.js', 'src/position/*.js', 'src/market/*.js', 'src/trades/*.js', 'src/mcp/*.js'], // R-020/迭代18: mcp 域 format: ['esm'], outDir: 'lib', clean: true,