Feature: Add MCP tools for remote chat with the client user

Add chat_open, chat_send, chat_wait and chat_close to the MCP tool set,
behind a new McpChat switch that defaults to off and is independent of
the read-only switch. The tools reuse the existing client chat link: the
main connection sends COMMAND_CHAT, the client opens a sub-connection
with TOKEN_CONN_AUTH and TOKEN_CHAT_START, the server answers with
COMMAND_NEXT_CHAT, and both directions then carry raw CP936 text.

Sessions are per host and held in shared pointers under their own mutex,
so a waiting chat_wait survives a close or a dropped sub-connection, and
chat_send is serialised per session. Text is converted strictly between
UTF-8 and CP936: invalid UTF-8, characters GBK cannot represent (emoji,
accented letters) and messages over the client's 4000-byte buffer are
rejected with -32602 instead of being mangled. A one-second settle delay
after the sub-connection binds keeps the first message from reaching the
client before its chat window exists.

The routing hooks run before the host-management hooks in MessageHandle
and clear the route in OfflineProc before the context leaves the list. A
five-second tombstone absorbs a late TOKEN_CHAT_START after a timeout, so
a failed chat_open does not pop the MFC chat dialog. Idle sessions are
swept on every tool call, and audit entries record the session id and
byte counts only, never message bodies.

The settings dialog gains the chat checkbox and the translated labels
for all three languages, and the strict UTF-8 to CP936 helpers move from
McpMachine.cpp to McpInternal so both tool families can share them.

Co-Authored-By: deepseek-flash
This commit is contained in:
yuanyuanxianganddeepseek-flash committed 2026-10-23 11:19:21 +02:00
1 parent fff7c67af6
commit c99a1c375e
17 files changed
+1519 -31

No files matched your search

+16
View File
@@ -2423,6 +2423,8 @@ BOOL CMy2015RemoteDlg::OnInitDialog()
McpServer().SetMachineEnabled(THIS_CFG.GetInt("settings", "McpMachine", 0) != 0);
McpServer().SetMachineDangerousEnabled(
THIS_CFG.GetInt("settings", "McpMachineDangerous", 0) != 0);
// P9 远程交谈(chat_*):独立开关默认关,与只读开关无关。
McpServer().SetChatEnabled(THIS_CFG.GetInt("settings", "McpChat", 0) != 0);
if (!McpServer().Start(mcpBind, mcpPort)) {
Mprintf("McpServer start failed on %s:%d\n", mcpBind.c_str(), mcpPort);
} else {
@@ -5527,6 +5529,12 @@ BOOL CALLBACK CMy2015RemoteDlg::OfflineProc(CONTEXT_OBJECT* ContextObject)
McpServer().OnManagedContextClosed(ContextObject);
}
// MCP P9 远程交谈:子连接(或主连接)断开时清路由/标记会话 closed 并唤醒 chat_wait。
// 同样必须在 RemoveFromHostList 之前,避免连接池复用同一 context 指针后误路由。
if (McpServer().IsRunning()) {
McpServer().OnChatContextClosed(ContextObject);
}
SOCKET nSocket = ContextObject->sClientSocket;
CDialogBase* p = (CDialogBase*)ContextObject->hDlg;
@@ -5889,6 +5897,14 @@ VOID CMy2015RemoteDlg::MessageHandle(CONTEXT_OBJECT* ContextObject)
return;
}
// ===== MCP 远程交谈:子连接路由(P9)=====
// 必须在 P8 之前:聊天文本是裸 GBK,首字节可能是 61(SYSINFOLIST)/255(CONNECTION_LIST),
// 若先交给 P8 会被误当作就绪包绑定。已绑定的子连接由 MCP 消费;未绑定的只有
// TOKEN_CHAT_START(62) 会被尝试绑定,其余(含 TOKEN_CONN_AUTH=246)原样交回下方分发。
if (McpServer().IsRunning() && McpServer().OnChatSubPacket(ContextObject, szBuffer, len)) {
return;
}
// ===== MCP 主机管理 / 连接管理:子连接路由(P8)=====
// 已绑定的子连接上的所有包由 MCP 消费;未绑定的子连接只有就绪包
// TOKEN_SYSINFOLIST(61) / TOKEN_CONNECTION_LIST(255) 会被尝试绑定,其余原样交回
@@ -328,6 +328,7 @@
<ClInclude Include="KeyBoardDlg.h" />
<ClInclude Include="LangManager.h" />
<ClInclude Include="McpServer.h" />
<ClInclude Include="McpChat.h" />
<ClInclude Include="McpInternal.h" />
<ClInclude Include="McpMachine.h" />
<ClInclude Include="McpSettingsDlg.h" />
@@ -399,6 +400,7 @@
<ClCompile Include="WebService.cpp" />
<ClCompile Include="HostJson.cpp" />
<ClCompile Include="McpServer.cpp" />
<ClCompile Include="McpChat.cpp" />
<ClCompile Include="McpInternal.cpp" />
<ClCompile Include="McpMachine.cpp" />
<ClCompile Include="McpSettingsDlg.cpp" />
@@ -89,6 +89,7 @@
<ClCompile Include="TriggerSettingsDlg.cpp" />
<ClCompile Include="HostJson.cpp" />
<ClCompile Include="McpServer.cpp" />
<ClCompile Include="McpChat.cpp" />
<ClCompile Include="McpInternal.cpp" />
<ClCompile Include="McpMachine.cpp" />
<ClCompile Include="McpSettingsDlg.cpp" />
@@ -205,6 +206,7 @@
<ClInclude Include="PreviewTipWnd.h" />
<ClInclude Include="TriggerSettingsDlg.h" />
<ClInclude Include="McpServer.h" />
<ClInclude Include="McpChat.h" />
<ClInclude Include="McpInternal.h" />
<ClInclude Include="McpMachine.h" />
<ClInclude Include="McpSettingsDlg.h" />
File diff suppressed because it is too large. Load diff
+30
View File
@@ -0,0 +1,30 @@
#pragma once
// MCP「远程交谈」工具族:chat_open / chat_send / chat_wait / chat_close。
//
// 复用 Windows 客户端既有的「远程交谈」链路(协议不变):
// 主连接 [COMMAND_CHAT=161]
// → 客户端子连接先 [TOKEN_CONN_AUTH=246],再 [TOKEN_CHAT_START=62]
// → 服务端在子连接回 [COMMAND_NEXT_CHAT=0](客户端据此弹 IDD_CHAT)
// → 双向文本 [CP936 字节][NUL];服务端 [COMMAND_CHAT_CLOSE=1] 结束。
//
// 会话状态由 CMcpServer 持有(McpServer.h),本文件只负责工具 schema、参数校验、
// 编码转换与响应构造。协议、客户端怪癖与验证矩阵见 docs/Mcp_Chat_Design.md。
#include "jsoncpp/json.h"
#include <string>
class CMy2015RemoteDlg;
namespace McpChat {
bool Handles(const std::string& toolName);
// 开关 close 时不追加任何工具(tools/list 与调用侧一致)。
void AppendTools(Json::Value& tools);
std::string Call(const Json::Value& id, const std::string& toolName,
const Json::Value& args, CMy2015RemoteDlg* parent);
} // namespace McpChat
+38
View File
@@ -160,6 +160,35 @@ std::string ToAnsi(const std::string& utf8, UINT cp) {
return out;
}
bool Utf8ToCpExact(const std::string& utf8, UINT cp, std::string& out) {
out.clear();
if (utf8.empty()) return true;
int wlen = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, utf8.c_str(), (int)utf8.size(), NULL, 0);
if (wlen <= 0) return false;
std::wstring w(wlen, L'\0');
if (MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, utf8.c_str(), (int)utf8.size(),
&w[0], wlen) != wlen)
return false;
BOOL usedDefault = FALSE;
int alen = WideCharToMultiByte(cp, WC_NO_BEST_FIT_CHARS, w.c_str(), wlen, NULL, 0, NULL, &usedDefault);
if (alen <= 0 || usedDefault) return false;
out.assign(alen, '\0');
usedDefault = FALSE;
if (WideCharToMultiByte(cp, WC_NO_BEST_FIT_CHARS, w.c_str(), wlen, &out[0], alen,
NULL, &usedDefault) != alen || usedDefault) {
out.clear();
return false;
}
return true;
}
bool IsValidUtf8(const char* s, size_t n) {
if (!s || n == 0) return true;
int wlen = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, s, (int)n, NULL, 0);
return wlen > 0;
}
void AuditManageAction(CMy2015RemoteDlg* parent, uint64_t devId,
const std::string& tool, const std::string& detail) {
if (!parent) return;
@@ -169,4 +198,13 @@ void AuditManageAction(CMy2015RemoteDlg* parent, uint64_t devId,
(LPARAM)new CString(_TR("MCP管理操作")));
}
void AuditChatAction(CMy2015RemoteDlg* parent, uint64_t devId,
const std::string& tool, const std::string& detail) {
if (!parent) return;
std::string text = "host " + std::to_string(devId) + " " + tool + ": " + detail;
parent->PostMessageA(WM_SHOWERRORMSG,
(WPARAM)new CString(ToAnsi(text, 936).c_str()),
(LPARAM)new CString(_TR("MCP远程交谈")));
}
} // namespace McpInternal
+12
View File
@@ -52,8 +52,20 @@ size_t BoundedStrlen(const char* p, size_t avail);
std::string ToUtf8(const char* s, UINT cp);
std::string ToAnsi(const std::string& utf8, UINT cp);
// 严格 UTF-8 → 目标代码页:任一字符无法表示即失败(不用 '?' 顶替)。
// UTF-8 本身非法也失败。聊天消息用它拒绝 GBK 不可表示的字符(emoji 等)。
bool Utf8ToCpExact(const std::string& utf8, UINT cp, std::string& out);
// 校验字节串是否为合法 UTF-8(n=0 视为合法)。
bool IsValidUtf8(const char* s, size_t n);
// 写操作审计:PostMessageA(WM_SHOWERRORMSG) 落到服务端消息/审计日志(不可关闭)。
void AuditManageAction(CMy2015RemoteDlg* parent, uint64_t devId,
const std::string& tool, const std::string& detail);
// 远程交谈审计:与 AuditManageAction 同落地方式,标题区分(MCP远程交谈)。
// 只记录会话 id / 字节数,绝不记录消息正文。
void AuditChatAction(CMy2015RemoteDlg* parent, uint64_t devId,
const std::string& tool, const std::string& detail);
} // namespace McpInternal
+1 -29
View File
@@ -110,35 +110,7 @@ std::string OptStr(const Json::Value& args, const char* key) {
return Trim(GetStringArg(args, key));
}
// 严格 UTF-8 → 目标代码页:任一字符无法表示即失败(不用 '?' 顶替)。
bool Utf8ToCpExact(const std::string& utf8, UINT cp, std::string& out) {
out.clear();
if (utf8.empty()) return true;
int wlen = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, utf8.c_str(), (int)utf8.size(), NULL, 0);
if (wlen <= 0) return false;
std::wstring w(wlen, L'\0');
if (MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, utf8.c_str(), (int)utf8.size(),
&w[0], wlen) != wlen)
return false;
BOOL usedDefault = FALSE;
int alen = WideCharToMultiByte(cp, WC_NO_BEST_FIT_CHARS, w.c_str(), wlen, NULL, 0, NULL, &usedDefault);
if (alen <= 0 || usedDefault) return false;
out.assign(alen, '\0');
usedDefault = FALSE;
if (WideCharToMultiByte(cp, WC_NO_BEST_FIT_CHARS, w.c_str(), wlen, &out[0], alen,
NULL, &usedDefault) != alen || usedDefault) {
out.clear();
return false;
}
return true;
}
bool IsValidUtf8(const char* s, size_t n) {
if (!s || n == 0) return true;
int wlen = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, s, (int)n, NULL, 0);
return wlen > 0;
}
// Utf8ToCpExact / IsValidUtf8 由 McpInternal 提供(McpChat.cpp 共用)。
bool IsWindowsClient(context* ctx) {
if (!ctx) return false;
+8
View File
@@ -10,6 +10,7 @@
#include "common/file_upload.h" // V2 文件传输(RecvFileChunkV2 / HandleFileCompleteV2 / 包结构)
#include "McpInternal.h" // 共享助手(McpMachine.cpp 也使用)
#include "McpMachine.h" // 主机管理 / 连接管理工具族
#include "McpChat.h" // 远程交谈工具族
#include <sstream>
@@ -1490,6 +1491,10 @@ std::string BuildToolsListResult(const Json::Value& id) {
// McpMachineDangerous / McpReadonly 逐项判断,关闭时不追加任何工具。
McpMachine::AppendTools(tools);
// 20) 远程交谈(P9:chat_open / chat_send / chat_wait / chat_close)。
// 独立开关 McpChat(默认关),与 McpReadonly 无关;关闭时不追加任何工具。
McpChat::AppendTools(tools);
result["tools"] = tools;
return BuildResult(id, result);
}
@@ -4533,6 +4538,9 @@ std::string BuildToolsCall(const Json::Value& root, CMy2015RemoteDlg* parent) {
// P8:连接管理 / 主机管理工具族(独立分发;门控在 McpMachine::Call 内复核)。
if (McpMachine::Handles(toolName)) return McpMachine::Call(id, toolName, args, parent);
// P9:远程交谈工具族(独立开关 McpChat;门控在 McpChat::Call 内复核)。
if (McpChat::Handles(toolName)) return McpChat::Call(id, toolName, args, parent);
if (toolName == "list_online_hosts") return BuildListOnlineHosts(id, parent);
if (toolName == "search_hosts") return BuildSearchHosts(id, args, parent);
if (toolName == "get_host_detail") return BuildGetHostDetail(id, args, parent);
+99
View File
@@ -10,6 +10,8 @@
#include <map>
#include <set>
#include <vector>
#include <deque>
#include <memory>
#include <cstdint>
#include <ctime>
@@ -37,6 +39,14 @@ struct FileTransferEntry {
std::string sha256;
};
// 远程交谈消息(chat_wait 输出 messages[] 的一项)。
// text 为 UTF-8;at 为收到消息的 Unix 秒;id 为会话内单调递增序号。
struct ChatMessage {
uint64_t id = 0;
int64_t at = 0;
std::string text;
};
// MCP (Model Context Protocol) 服务端:httplib 封装 + JSON-RPC 2.0 分发 + 静态 token 校验。
// 与 CWebService 平级、互不依赖;默认禁用,经「扩展 → MCP设置」开启后监听
// (默认 127.0.0.1:6544,仅本机回环)。见 docs/Mcp_Design.md。
@@ -305,6 +315,57 @@ public:
// (吸收迟到的 61/255,避免超时后弹出 MFC 对话框)。
void EndManagedSession(uint64_t device_id, bool cancelIo, bool markOrphan);
// ===== P9:远程交谈(chat_open / chat_send / chat_wait / chat_close)=====
// 复用 Windows 客户端既有「远程交谈」:主连接下发 COMMAND_CHAT(161) → 客户端新建子连接
// (先 TOKEN_CONN_AUTH(246) 再 TOKEN_CHAT_START(62))→ 服务端在该子连接回
// COMMAND_NEXT_CHAT(0) → 客户端弹 IDD_CHAT。之后双向文本为 raw CP936 + NUL。
// 协议不变(客户端聊天窗口已改为 Unicode 对话框,仅影响显示);独立 m_ChatMutex,
// 不与 P8 的 m_Pending 共享(不受单设备单飞行约束)。
// 见 docs/Mcp_Chat_Design.md。
void SetChatEnabled(bool enabled) { m_chatEnabled = enabled; }
bool IsChatEnabled() const { return m_chatEnabled; }
// MessageHandle 顶部钩子(MCP 终端之后、P8 之前:聊天文本首字节可能是 61/255/246)。
// true = 本包已由 MCP 消费。未绑定子连接上的 246(TOKEN_CONN_AUTH) 走快速路径返回
// false,交给认证分支;已绑定子连接上的 0xF6 前导字节文本则由本函数消费。
bool OnChatSubPacket(context* subCtx, const BYTE* data, ULONG len);
// OfflineProc 钩子:接管的子连接断开 → 会话 closed + 清路由 + 唤醒等待者(幂等)。
void OnChatContextClosed(context* subCtx);
// 工具线程:登记会话并发 COMMAND_CHAT 之后调用。false = 该设备已有未关闭会话(-32003)。
bool BeginChatOpen(uint64_t device_id, context* mainCtx, const std::string& sessionId);
// 工具线程:等待客户端子连接就绪且建窗等待期满(readyAt)。
enum class ChatReadyResult { Ok, Timeout, Closed, NotFound };
ChatReadyResult WaitChatReady(uint64_t device_id, const std::string& sessionId, int timeoutMs);
// 工具线程:发送前登记(校验 sessionId/started/readyAt/sendBusy)。输出 subCtx。
// 返回:0=ok;1=不存在/不匹配/未就绪/已关;2=忙。
int BeginChatSend(uint64_t device_id, const std::string& sessionId, context*& subCtx);
// 工具线程:发送结束(清 sendBusy)。closed 期间发送失败时调用方仍须调用。
void FinishChatSend(uint64_t device_id, const std::string& sessionId);
// 工具线程:取走最多 maxMessages 条;inbox 为空则等到 deadline / closed。
enum class ChatWaitResult { Ok, Timeout, Closed, NotFound, Mismatch, Busy };
ChatWaitResult WaitChatMessages(uint64_t device_id, const std::string& sessionId,
int timeoutMs, size_t maxMessages,
std::vector<ChatMessage>& out, bool& timedOut,
bool& closed, int& dropped);
// 关闭会话(校验 sessionId;标记 closed + 清路由 + 唤醒)。返回 0=已关;1=不存在(幂等);
// 2=sessionId 不匹配。子连接 [1] 与 CancelIO 由调用方在锁外执行。
int CloseChatSession(uint64_t device_id, const std::string& sessionId,
context*& subCtxOut, bool& hadSession);
// chat_open 超时/失败收尾:标记 closed + 清路由;已绑定子连接写入 subCtxOut(调用方 CancelIO),
// 未绑定则打 device/peer 5s 墓碑,吸收迟到的 62(避免超时后弹出 MFC 对话框)。
void AbortChatOpen(uint64_t device_id, context*& subCtxOut, bool& hadSession);
// idle 回收:关闭 lastActiveAt 超时且无等待/无发送的会话。返回回收数量。
int SweepIdleChats(time_t idleTimeoutSec);
// 安全配置(启动时由 CMy2015RemoteDlg 读 THIS_CFG 后设置)。
void SetReadonly(bool readonly) { m_readonly = readonly; }
void SetCmdWhitelist(const std::string& whitelist) { m_cmdWhitelist = whitelist; }
@@ -451,6 +512,44 @@ private:
std::condition_variable m_FileXferCv;
std::map<uint64_t, FileTransferSession> m_FileXferSessions; // device_id → 会话
// ===== P9:远程交谈(受 m_ChatMutex 保护;单设备单会话)=====
// 用 shared_ptr 持有会话:chat_wait / chat_send 在工作线程里复制一份后,即使 map 条目
// 被 chat_close 或断线删除也安全,不需要额外的「迭代器有效」规则。
struct ChatSession {
std::string sessionId;
context* mainCtx = nullptr; // 主连接(仅身份校验)
uint64_t mainClientId = 0;
context* subCtx = nullptr; // 已绑定的子连接;关闭后置空
std::string peer; // GetPeerName()(老客户端 clientID=0 兜底)
bool started = false; // 已收到 TOKEN_CHAT_START 并回 [0]
bool closed = false;
bool sendBusy = false; // 同一会话串行发送
bool waitBusy = false; // 同一会话只允许一个 chat_wait
std::chrono::steady_clock::time_point readyAt; // 建窗等待期满(绑定 + 1000ms)
time_t lastActiveAt = 0;
std::deque<ChatMessage> inbox; // 未投递消息(UTF-8)
size_t inboxBytes = 0;
uint64_t nextMsgId = 1;
int dropped = 0; // inbox 溢出丢弃的条数(chat_wait 报告)
};
using ChatSessionPtr = std::shared_ptr<ChatSession>;
std::mutex m_ChatMutex;
std::condition_variable m_ChatCv;
std::map<uint64_t, ChatSessionPtr> m_ChatSessions; // device_id → 会话
std::map<context*, uint64_t> m_ChatContextToDevice; // subCtx → device_id(顶部路由)
std::set<context*> m_ChatAbsorbedSubCtx; // CancelIO 后尾巴包静默丢弃
struct ChatOrphanMark { std::chrono::steady_clock::time_point expiry; };
std::map<uint64_t, ChatOrphanMark> m_ChatOrphanByDevice; // 迟到 62 墓碑(5s)
std::map<std::string, ChatOrphanMark> m_ChatOrphanByPeer;
std::atomic<int> m_ChatRoutes{0}; // 路由集合大小(快速路径)
bool m_chatEnabled = false; // P9 开关(默认关)
// 持 m_ChatMutex 调用:只更新 m_ChatRoutes(store,relaxed 读取在锁外)。
void RefreshChatRoutesLocked();
// 持 m_ChatMutex 调用:清理过期墓碑。
void PruneChatOrphansLocked(std::chrono::steady_clock::time_point now);
bool m_readonly = true;
std::string m_cmdWhitelist;
bool m_terminalEnabled = false; // 持久终端开关(默认关;要求 m_readonly=false)
+14 -2
View File
@@ -106,7 +106,7 @@ INT_PTR CMcpSettingsDlg::DoModal()
{
USES_CONVERSION;
CString title = _TR("MCP设置");
BuildDialogTemplate(m_Template, T2CW(title), 320, 480);
BuildDialogTemplate(m_Template, T2CW(title), 320, 510);
InitModalIndirect((LPCDLGTEMPLATE)m_Template.data());
return CDialog::DoModal();
}
@@ -154,6 +154,9 @@ BOOL CMcpSettingsDlg::OnInitDialog()
m_btnMachineDangerous.Create(_TR("启用危险主机操作(冻结/解冻进程、停止/重启服务)"),
WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_AUTOCHECKBOX,
r0, this, IDC_MCP_MACHINE_DANGEROUS);
m_btnChat.Create(_TR("启用远程交谈(AI 与用户对话)"),
WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_AUTOCHECKBOX,
r0, this, IDC_MCP_CHAT);
m_lblWhitelist.Create(_TR("命令白名单"), WS_CHILD | WS_VISIBLE, r0, this, (UINT)-1);
m_editWhitelist.Create(WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP |
ES_MULTILINE | ES_AUTOVSCROLL | ES_WANTRETURN | WS_VSCROLL,
@@ -180,6 +183,7 @@ BOOL CMcpSettingsDlg::OnInitDialog()
m_btnManage.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0));
m_btnMachine.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0));
m_btnMachineDangerous.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0));
m_btnChat.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0));
m_lblWhitelist.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0));
m_editWhitelist.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0));
m_btnOK.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0));
@@ -199,6 +203,7 @@ BOOL CMcpSettingsDlg::OnInitDialog()
int manage = THIS_CFG.GetInt("settings", "McpManage", 0);
int machine = THIS_CFG.GetInt("settings", "McpMachine", 0);
int machineDangerous = THIS_CFG.GetInt("settings", "McpMachineDangerous", 0);
int chat = THIS_CFG.GetInt("settings", "McpChat", 0);
std::string whitelist = THIS_CFG.GetStr("settings", "McpCmdWhitelist", "");
m_btnEnable.SetCheck(enabled ? BST_CHECKED : BST_UNCHECKED);
@@ -212,6 +217,7 @@ BOOL CMcpSettingsDlg::OnInitDialog()
m_btnManage.SetCheck(manage ? BST_CHECKED : BST_UNCHECKED);
m_btnMachine.SetCheck(machine ? BST_CHECKED : BST_UNCHECKED);
m_btnMachineDangerous.SetCheck(machineDangerous ? BST_CHECKED : BST_UNCHECKED);
m_btnChat.SetCheck(chat ? BST_CHECKED : BST_UNCHECKED);
// 白名单存储为逗号分隔,展示为每行一条。
m_editWhitelist.SetWindowText(CString(WhitelistForDisplay(whitelist).c_str()));
@@ -234,6 +240,7 @@ void CMcpSettingsDlg::OnOK()
bool manage = (m_btnManage.GetCheck() == BST_CHECKED);
bool machine = (m_btnMachine.GetCheck() == BST_CHECKED);
bool machineDangerous = (m_btnMachineDangerous.GetCheck() == BST_CHECKED);
bool chat = (m_btnChat.GetCheck() == BST_CHECKED);
// 危险子集依附于主机管理总开关:没开主机管理就没有工具可放开,直接清除并提示。
if (machineDangerous && !machine) {
@@ -271,6 +278,7 @@ void CMcpSettingsDlg::OnOK()
THIS_CFG.SetInt("settings", "McpManage", manage ? 1 : 0);
THIS_CFG.SetInt("settings", "McpMachine", machine ? 1 : 0);
THIS_CFG.SetInt("settings", "McpMachineDangerous", machineDangerous ? 1 : 0);
THIS_CFG.SetInt("settings", "McpChat", chat ? 1 : 0);
std::string whitelist = CT2A(sWhitelist);
whitelist = NormalizeWhitelist(whitelist);
THIS_CFG.SetStr("settings", "McpCmdWhitelist", whitelist);
@@ -279,7 +287,8 @@ void CMcpSettingsDlg::OnOK()
MessageBox(_TR("MCP 设置已保存。") + _T("\r\n") +
_TR("启用/端口/绑定地址/Token/只读/白名单/持久终端/远程控制/文件传输/管理操作/主机管理/危险主机操作的改动需重启程序生效。") + _T("\r\n") +
_TR("持久终端、远程控制与管理操作仅在只读模式关闭时生效。") + _T("\r\n") +
_TR("主机管理的写操作需只读模式关闭;冻结/解冻进程、停止/重启服务还需启用危险主机操作。"),
_TR("主机管理的写操作需只读模式关闭;冻结/解冻进程、停止/重启服务还需启用危险主机操作。") + _T("\r\n") +
_TR("远程交谈开关的改动需重启程序生效。"),
_TR("提示"), MB_ICONINFORMATION);
CDialog::OnOK();
@@ -331,6 +340,9 @@ void CMcpSettingsDlg::LayoutControls(int cx, int cy)
m_btnMachineDangerous.MoveWindow(margin, y, cx - margin * 2, 22);
y += 30;
m_btnChat.MoveWindow(margin, y, cx - margin * 2, 22);
y += 30;
const int whitelistH = 90;
m_lblWhitelist.MoveWindow(margin, y, labelW, rowH);
m_editWhitelist.MoveWindow(margin + labelW, y - 2, cx - margin * 2 - labelW, whitelistH);
+2
View File
@@ -34,6 +34,7 @@ private:
IDC_MCP_MANAGE = 1010, // 「启用管理操作」复选框(kill/窗口/服务/电源,要求只读关)
IDC_MCP_MACHINE = 1011, // 「启用主机管理」复选框(host_* 工具族)
IDC_MCP_MACHINE_DANGEROUS = 1012, // 「启用危险主机操作」复选框(冻结/停止/重启)
IDC_MCP_CHAT = 1013, // 「启用远程交谈」复选框(chat_* 工具族,不要求只读关)
};
CButton m_btnEnable;
@@ -46,6 +47,7 @@ private:
CButton m_btnManage;
CButton m_btnMachine;
CButton m_btnMachineDangerous;
CButton m_btnChat;
CStatic m_lblWhitelist;
CEdit m_editWhitelist;
CButton m_btnOK, m_btnCancel;
+3
View File
@@ -2009,6 +2009,9 @@ Token=Token
启用主机管理(进程/窗口/服务/任务/网络状态)=Maschinenverwaltung aktivieren (Prozess/Fenster/Dienst/Aufgabe/Netzwerkstatus)
启用危险主机操作(冻结/解冻进程、停止/重启服务)=Gefaehrliche Maschinenoperationen aktivieren (Prozess einfrieren/auftauen, Dienst stoppen/neu starten)
危险主机操作依附于主机管理:请先启用主机管理。=Gefaehrliche Maschinenoperationen setzen die Maschinenverwaltung voraus. Aktivieren Sie zuerst die Maschinenverwaltung.
启用远程交谈(AI 与用户对话)=Fernchat aktivieren (KI spricht mit dem Benutzer)
MCP远程交谈=MCP-Fernchat
远程交谈开关的改动需重启程序生效。=Aenderungen am Fernchat-Schalter werden nach einem Neustart wirksam.
主机管理的写操作需只读模式关闭;冻结/解冻进程、停止/重启服务还需启用危险主机操作。=Schreiboperationen der Maschinenverwaltung erfordern deaktivierten Nur-Lesen-Modus; Einfrieren/Auftauen von Prozessen und Stoppen/Neustarten von Diensten erfordern zusaetzlich gefaehrliche Maschinenoperationen.
第 %d 个钱包地址不合法!=Wallet-Adresse #%d ist ungueltig!
%d 新连接\r\n=%d Neue Verbindung\r\n
+3
View File
@@ -2014,6 +2014,9 @@ Token=Token
启用主机管理(进程/窗口/服务/任务/网络状态)=Enable host management (processes/windows/services/tasks/network state)
启用危险主机操作(冻结/解冻进程、停止/重启服务)=Enable dangerous host operations (freeze/thaw process, stop/restart service)
危险主机操作依附于主机管理:请先启用主机管理。=Dangerous host operations depend on host management. Enable host management first.
启用远程交谈(AI 与用户对话)=Enable remote chat (AI talks to the user)
MCP远程交谈=MCP remote chat
远程交谈开关的改动需重启程序生效。=Changes to the remote chat switch take effect after restart.
主机管理的写操作需只读模式关闭;冻结/解冻进程、停止/重启服务还需启用危险主机操作。=Host management write operations require read-only mode to be off; freezing/thawing processes and stopping/restarting services also require dangerous host operations.
第 %d 个钱包地址不合法!=Wallet address #%d is invalid!
%d 新连接\r\n=%d New connection\r\n
+3
View File
@@ -2005,6 +2005,9 @@ Token=Token
启用主机管理(进程/窗口/服务/任务/网络状态)=啟用主機管理(處理程序/視窗/服務/排程工作/網路狀態)
启用危险主机操作(冻结/解冻进程、停止/重启服务)=啟用危險主機操作(凍結/解凍處理程序、停止/重新啟動服務)
危险主机操作依附于主机管理:请先启用主机管理。=危險主機操作依附於主機管理:請先啟用主機管理。
启用远程交谈(AI 与用户对话)=啟用遠端交談(AI 與使用者對話)
MCP远程交谈=MCP遠端交談
远程交谈开关的改动需重启程序生效。=遠端交談開關的變更需重新啟動程式後生效。
主机管理的写操作需只读模式关闭;冻结/解冻进程、停止/重启服务还需启用危险主机操作。=主機管理的寫入操作需關閉唯讀模式;凍結/解凍處理程序、停止/重新啟動服務還需啟用危險主機操作。
第 %d 个钱包地址不合法!=第 %d 個錢包位址不合法!
%d 新连接\r\n=%d 新連線\r\n