Feature: Add persistent remote terminal MCP tools

Add terminal_open / terminal_exec / terminal_close so an AI can hold one
shell session per Windows host and run a sequence of commands with cwd and
environment preserved, instead of the one-shot exec_command.

The persistent terminal is a separate full-command write capability gated by
McpTerminal (default off) plus McpReadonly=0, with no whitelist and full
audit. One device maps to one terminal session via the shared m_TermSessions
map; idle sessions are swept after 300s. terminal_exec rejects commands that
contain & or | (they corrupt the sentinel control-operator chain) as well as
control characters.

Also harden exec_command and terminal_exec against newline/CR injection, fix a
dangling subCtx after an abrupt shell disconnect, and refresh lastActiveAt on
command completion. Add en/zh-TW translations for the new UI strings and a
design document covering both exec_command and the persistent terminal.

Co-Authored-By: deepseek-v4-pro
This commit is contained in:
yuanyuanxiang
2026-08-24 19:11:16 +02:00
parent 0ddbc1aced
commit 70dbb6b255
8 changed files with 1099 additions and 9 deletions

View File

@@ -10,6 +10,7 @@
#include <map>
#include <vector>
#include <cstdint>
#include <ctime>
// httplib 与 Windows 头部的 min/max 宏冲突,按 file_server.h 的既有约定处理。
#undef min
@@ -133,6 +134,29 @@ public:
// 工具线程:清理终端挂起(发送失败等提前退出路径)。
void ClearTermPending(uint64_t device_id);
// ===== P4持久远程终端terminal_open / terminal_exec / terminal_close=====
void SetTerminalEnabled(bool enabled) { m_terminalEnabled = enabled; }
bool IsTerminalEnabled() const { return m_terminalEnabled; }
// 登记持久终端挂起复用单设备单终端约束sessionId 由调用方生成)。
bool BeginTermOpen(uint64_t device_id, const std::string& sessionId);
// 复位单条命令字段并置 busy输出 subCtx/isPty。false = 不存在/不匹配/未就绪/忙碌/已关。
bool BeginTermCommand(uint64_t device_id, const std::string& sessionId,
const std::string& command, const std::string& nonce,
context*& subCtx, bool& isPty);
// 等待本条命令完成done || closed || 超时。done=保持会话closed/超时=清理会话+路由。
bool WaitTermCommand(uint64_t device_id, std::vector<BYTE>& out, int& exitCode,
bool& closed, int timeoutMs);
// 关闭持久终端(校验 sessionIdCancelIO + 清理路由/会话)。
// 返回0=已关闭1=会话不存在幂等2=sessionId 不匹配(调用方报错)。
int CloseTermSession(uint64_t device_id, const std::string& sessionId);
// idle 回收:关闭 lastActiveAt 超时且非 busy 的持久会话。返回回收数量。
int SweepIdleTerminals(time_t idleTimeoutSec);
// 安全配置(启动时由 CMy2015RemoteDlg 读 THIS_CFG 后设置)。
void SetReadonly(bool readonly) { m_readonly = readonly; }
void SetCmdWhitelist(const std::string& whitelist) { m_cmdWhitelist = whitelist; }
@@ -185,6 +209,11 @@ private:
int exitCode = -1;
bool done = false; // 哨兵命中
bool closed = false; // TOKEN_TERMINAL_CLOSE进程退出
// ===== 新增(持久终端)=====
std::string sessionId; // 持久会话 token一次性 exec 为空)
bool persistent = false;
bool busy = false; // 一条命令在飞terminal_exec 复位→WaitTermCommand 返回)
time_t lastActiveAt = 0; // idle 回收用(秒)
};
std::mutex m_TermMutex;
std::condition_variable m_TermCv;
@@ -193,6 +222,7 @@ private:
bool m_readonly = true;
std::string m_cmdWhitelist;
bool m_terminalEnabled = false; // 持久终端开关(默认关;要求 m_readonly=false
};
// 全局访问器(仿 WebService(),见 WebService.h 末尾)