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

+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);