Compare commits
4
Commits
52b4afb8cf
...
v1.3.9
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b41b27bfe4 | ||
|
|
6bcd593b6d | ||
|
|
5c77f5ce14 | ||
|
|
ac2855198b |
No files matched your search
@@ -378,6 +378,34 @@ nohup ./server_linux_amd64 --port 6543 --http-port 9001 > yama.log 2>&1 &
|
|||||||
|
|
||||||
## 更新日志
|
## 更新日志
|
||||||
|
|
||||||
|
### v1.3.9 (2026.9.2)
|
||||||
|
|
||||||
|
**MCP 服务端集成(AI 远程管理)& 远程控制与文件传输闭环 & 主控增强**
|
||||||
|
|
||||||
|
**新功能:**
|
||||||
|
- **MCP 服务端集成**:本地 `127.0.0.1:6544` 暴露 JSON-RPC 2.0 接口,Bearer Token 鉴权,「扩展 > MCP 设置」对话框配置启用 / 端口 / 绑定 / Token;23 个工具覆盖「观测 → 执行 → 控制 → 传输」全链路,默认关闭、分级开关(只读 / 白名单 / 终端 / 远程控制 / 文件传输)全面审计
|
||||||
|
- **MCP 观测类工具(只读)**:search_hosts、get_host_detail、list_processes、list_windows、get_activity_history、list_files、get_screenshot(可选 max_width)、list_services、get_client_log、list_registry、get_audit_log;单飞请求注册表 20s 超时 + 编码修正(clientType / clientID 定位)
|
||||||
|
- **MCP 远程终端工具**:exec_command 一次性执行 + terminal_open / terminal_exec / terminal_close 持久终端(cwd / 环境保留,空闲 300s 清扫),哨兵判定与 timeout_ms 解析修复
|
||||||
|
- **MCP 远程控制工具**:remote_open / remote_close 会话 + remote_keyboard(key_down / up / press / type)、remote_mouse(move / click / drag / scroll)、remote_clipboard 注入;与人工 Web 查看双向互斥,会话状态机 busy / closed 加固
|
||||||
|
- **MCP 文件传输工具**:download_file(V2 协议 + SHA-256 校验)、upload_file(主连接上传),客户端侧 `COMMAND_SEND_FILE_V2` 懒初始化 `InitFileUpload` 修复上传截断
|
||||||
|
- **客户端活动历史**:记录活跃窗口历史(上限 500 条、跳过 <5s),主机「客户端管理」子菜单新增「活动历史」对话框,Unicode 编辑控件正确渲染 UTF-8 标题
|
||||||
|
- **日志右键菜单三连**:消息日志右键新增「在线通知」「拉黑 IP」「解封 IP」,IPv4 提取即时生效
|
||||||
|
- **列表排序记忆**:主机列表、进程 / 窗口列表排序偏好持久化(`list\*Sort` 注册表键),下次启动默认沿用
|
||||||
|
|
||||||
|
**改进:**
|
||||||
|
- 重复连接 standby 处理:重复登录暂存 standby 待旧连接超时 / 断线后晋升,避免循环重连与误报离线
|
||||||
|
- 远程桌面 / 虚拟屏幕对话框标题显示主机备注而非 IP(无备注回退 IP)
|
||||||
|
- 主机列表三态排序(升序 → 降序 → 恢复默认,默认序按在线时间重建)
|
||||||
|
- 在线主机右键三个菜单项新增图标(位图数组 63→66)
|
||||||
|
|
||||||
|
**Bug 修复:**
|
||||||
|
- 客户端屏幕控制批解析错误:记录数是 7 的倍数时误判 MSG32 致注入错乱(`%48==0` 优先)
|
||||||
|
- 心跳延迟更新 use-after-free:跨 PostMessage 持有 IoRefCount + memcpy 显式截断
|
||||||
|
- ONLINELIST_PRIVILEGE 枚举错位损坏历史数据库(权限列移至 16)
|
||||||
|
- 远程桌面光标跨对话框污染(改 `OnSetCursor` 逐窗口路由)
|
||||||
|
- 客户端日志对话框 EDIT 上限停止刷新(`SetLimitText(0)`)
|
||||||
|
- 窗口管理器视图检查读取陈旧状态、进程列表架构列排序无效、FRP / NAT 下窗口标题乱码
|
||||||
|
|
||||||
### v1.3.8 (2026.8.3)
|
### v1.3.8 (2026.8.3)
|
||||||
|
|
||||||
**SYSTEM 权限客户端 & 主控可用性增强 & Bug 修复**
|
**SYSTEM 权限客户端 & 主控可用性增强 & Bug 修复**
|
||||||
|
|||||||
@@ -378,6 +378,34 @@ Valid : 2026-02-01 to 2028-02-01
|
|||||||
|
|
||||||
## Changelog
|
## Changelog
|
||||||
|
|
||||||
|
### v1.3.9 (2026.9.2)
|
||||||
|
|
||||||
|
**MCP Server Integration (AI Remote Management) & Remote Control / File Transfer Loop & Master Enhancements**
|
||||||
|
|
||||||
|
**New features:**
|
||||||
|
- **MCP server integration**: local JSON-RPC 2.0 endpoint on `127.0.0.1:6544` with Bearer token auth, configured via "Extensions > MCP Settings"; 23 tools spanning observe → execute → control → transfer, disabled by default with per-tier gates (read-only / whitelist / terminal / remote control / file transfer) and full audit logging
|
||||||
|
- **MCP read-only observation tools**: search_hosts, get_host_detail, list_processes, list_windows, get_activity_history, list_files, get_screenshot (optional max_width), list_services, get_client_log, list_registry, get_audit_log; single-flight pending registry with 20s timeout, plus encoding fixes (clientType / clientID resolution)
|
||||||
|
- **MCP remote terminal tools**: one-shot exec_command plus persistent terminal_open / terminal_exec / terminal_close (cwd and environment preserved, 300s idle sweep); sentinel detection and timeout_ms parsing fixes
|
||||||
|
- **MCP remote control tools**: remote_open / remote_close sessions plus remote_keyboard (key_down / up / press / type), remote_mouse (move / click / drag / scroll), and remote_clipboard injection; two-way mutual exclusion with human Web viewing and a hardened busy/closed session state machine
|
||||||
|
- **MCP file transfer tools**: download_file (V2 protocol + SHA-256 verification) and upload_file (main-connection upload); client-side lazy `InitFileUpload` in `COMMAND_SEND_FILE_V2` fixes truncated uploads
|
||||||
|
- **Client activity history**: records active-window history (capped at 500 entries, skips <5s); new "Activity History" item under the host's Client Management menu with a Unicode edit control for correct UTF-8 title rendering
|
||||||
|
- **Log context-menu trio**: right-click "Online Notify", "Ban IP", and "Unban IP" on the message log, with IPv4 extraction applied immediately
|
||||||
|
- **Sort preference memory**: host list and process/window list sort preferences persisted (`list\*Sort` registry keys), restored on next launch
|
||||||
|
|
||||||
|
**Improvements:**
|
||||||
|
- Duplicate connections parked as standby and promoted after the old connection times out / disconnects, avoiding reconnect loops and false offline notifications
|
||||||
|
- Remote desktop and virtual screen dialog titles show the host remark instead of the IP (falling back to IP)
|
||||||
|
- Three-state host list sorting (ascending → descending → restore default, default rebuilt by online time)
|
||||||
|
- Icons added to three online-host context menu items (bitmap array 63→66)
|
||||||
|
|
||||||
|
**Bug fixes:**
|
||||||
|
- Client screen-control batch misparse: record count that is a multiple of 7 was misclassified as MSG32, garbling injected input (`%48==0` checked first)
|
||||||
|
- Heartbeat deferred-update use-after-free: IoRefCount held across PostMessage plus explicit memcpy truncation
|
||||||
|
- ONLINELIST_PRIVILEGE enum shift corrupted the history host database (column moved to 16)
|
||||||
|
- Remote desktop cursor cross-contamination across dialogs (routed via `OnSetCursor` per window)
|
||||||
|
- Client log dialog stopped refreshing past the EDIT control limit (`SetLimitText(0)`)
|
||||||
|
- Window manager view check reading stale state, process-list architecture column sort no-op, and FRP/NAT window-title mojibake
|
||||||
|
|
||||||
### v1.3.8 (2026.8.3)
|
### v1.3.8 (2026.8.3)
|
||||||
|
|
||||||
**SYSTEM-level Client & Master Usability Enhancements & Bug Fixes**
|
**SYSTEM-level Client & Master Usability Enhancements & Bug Fixes**
|
||||||
|
|||||||
@@ -378,6 +378,34 @@ nohup ./server_linux_amd64 --port 6543 --http-port 9001 > yama.log 2>&1 &
|
|||||||
|
|
||||||
## 更新日誌
|
## 更新日誌
|
||||||
|
|
||||||
|
### v1.3.9 (2026.9.2)
|
||||||
|
|
||||||
|
**MCP 伺服端整合(AI 遠端管理)& 遠端控制與檔案傳輸閉環 & 主控增強**
|
||||||
|
|
||||||
|
**新功能:**
|
||||||
|
- **MCP 伺服端整合**:本地 `127.0.0.1:6544` 暴露 JSON-RPC 2.0 介面,Bearer Token 鑑權,「擴充 > MCP 設定」對話框設定啟用 / 連接埠 / 綁定 / Token;23 個工具覆蓋「觀測 → 執行 → 控制 → 傳輸」全鏈路,預設關閉、分級開關(唯讀 / 白名單 / 終端 / 遠端控制 / 檔案傳輸)全面稽核
|
||||||
|
- **MCP 觀測類工具(唯讀)**:search_hosts、get_host_detail、list_processes、list_windows、get_activity_history、list_files、get_screenshot(可選 max_width)、list_services、get_client_log、list_registry、get_audit_log;單飛請求登錄表 20s 逾時 + 編碼修正(clientType / clientID 定位)
|
||||||
|
- **MCP 遠端終端工具**:exec_command 一次性執行 + terminal_open / terminal_exec / terminal_close 持久終端(cwd / 環境保留,閒置 300s 清掃),哨兵判定與 timeout_ms 解析修復
|
||||||
|
- **MCP 遠端控制工具**:remote_open / remote_close 工作階段 + remote_keyboard(key_down / up / press / type)、remote_mouse(move / click / drag / scroll)、remote_clipboard 注入;與人工 Web 查看雙向互斥,工作階段狀態機 busy / closed 加固
|
||||||
|
- **MCP 檔案傳輸工具**:download_file(V2 協定 + SHA-256 校驗)、upload_file(主連線上傳),用戶端側 `COMMAND_SEND_FILE_V2` 懶初始化 `InitFileUpload` 修復上傳截斷
|
||||||
|
- **用戶端活動歷史**:記錄活躍視窗歷史(上限 500 條、跳過 <5s),主機「用戶端管理」子選單新增「活動歷史」對話框,Unicode 編輯控件正確渲染 UTF-8 標題
|
||||||
|
- **訊息右鍵選單三連**:訊息記錄右鍵新增「線上通知」「封鎖 IP」「解封 IP」,IPv4 提取即時生效
|
||||||
|
- **列表排序記憶**:主機列表、處理程序 / 視窗列表排序偏好持久化(`list\*Sort` 登錄檔鍵),下次啟動預設沿用
|
||||||
|
|
||||||
|
**改進:**
|
||||||
|
- 重複連線 standby 處理:重複登入暫存 standby 待舊連線逾時 / 斷線後晉升,避免循環重連與誤報離線
|
||||||
|
- 遠端桌面 / 虛擬螢幕對話框標題顯示主機備註而非 IP(無備註回退 IP)
|
||||||
|
- 主機列表三態排序(升冪 → 降冪 → 恢復預設,預設序按上線時間重建)
|
||||||
|
- 線上主機右鍵三個選單項目新增圖示(位圖陣列 63→66)
|
||||||
|
|
||||||
|
**Bug 修復:**
|
||||||
|
- 用戶端螢幕控制批次解析錯誤:記錄數是 7 的倍數時誤判 MSG32 致注入錯亂(`%48==0` 優先)
|
||||||
|
- 心跳延遲更新 use-after-free:跨 PostMessage 持有 IoRefCount + memcpy 顯式截斷
|
||||||
|
- ONLINELIST_PRIVILEGE 列舉錯位損壞歷史資料庫(權限列移至 16)
|
||||||
|
- 遠端桌面游標跨對話框污染(改 `OnSetCursor` 逐視窗路由)
|
||||||
|
- 用戶端記錄對話框 EDIT 上限停止刷新(`SetLimitText(0)`)
|
||||||
|
- 視窗管理員檢視檢查讀取陳舊狀態、處理程序列表架構欄排序無效、FRP / NAT 下視窗標題亂碼
|
||||||
|
|
||||||
### v1.3.8 (2026.8.3)
|
### v1.3.8 (2026.8.3)
|
||||||
|
|
||||||
**SYSTEM 權限用戶端 & 主控可用性增強 & Bug 修復**
|
**SYSTEM 權限用戶端 & 主控可用性增強 & Bug 修復**
|
||||||
|
|||||||
@@ -1379,6 +1379,13 @@ VOID CKernelManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
|
|||||||
}
|
}
|
||||||
|
|
||||||
case COMMAND_SEND_FILE_V2: {
|
case COMMAND_SEND_FILE_V2: {
|
||||||
|
// 主连接接收 V2 上传:确保文件传输模块已初始化(RecvFileChunkV2 依赖 g_status==1)。
|
||||||
|
// static 标志保证进程内只初始化一次,不随主连接重连反复 Init/Uninit。
|
||||||
|
static bool s_v2RecvInited = false;
|
||||||
|
if (!s_v2RecvInited) {
|
||||||
|
InitFileUpload({}, m_LoginMsg, m_LoginSignature, 64, 50, Logf);
|
||||||
|
s_v2RecvInited = true;
|
||||||
|
}
|
||||||
// C2C/V2 文件接收(RecvFileChunkV2 内部会打印进度)
|
// C2C/V2 文件接收(RecvFileChunkV2 内部会打印进度)
|
||||||
int n = RecvFileChunkV2((char*)szBuffer, ulLength, m_conn,
|
int n = RecvFileChunkV2((char*)szBuffer, ulLength, m_conn,
|
||||||
nullptr, m_hash, m_hmac, m_MyClientID);
|
nullptr, m_hash, m_hmac, m_MyClientID);
|
||||||
|
|||||||
+2
-2
@@ -88,7 +88,7 @@ IDR_WAVE WAVE "Res\\msg.wav"
|
|||||||
//
|
//
|
||||||
|
|
||||||
VS_VERSION_INFO VERSIONINFO
|
VS_VERSION_INFO VERSIONINFO
|
||||||
FILEVERSION 1,0,3,8
|
FILEVERSION 1,0,3,9
|
||||||
PRODUCTVERSION 1,0,0,1
|
PRODUCTVERSION 1,0,0,1
|
||||||
FILEFLAGSMASK 0x3fL
|
FILEFLAGSMASK 0x3fL
|
||||||
#ifdef _DEBUG
|
#ifdef _DEBUG
|
||||||
@@ -106,7 +106,7 @@ BEGIN
|
|||||||
BEGIN
|
BEGIN
|
||||||
VALUE "CompanyName", "FUCK THE UNIVERSE"
|
VALUE "CompanyName", "FUCK THE UNIVERSE"
|
||||||
VALUE "FileDescription", "A GHOST"
|
VALUE "FileDescription", "A GHOST"
|
||||||
VALUE "FileVersion", "1.0.3.8"
|
VALUE "FileVersion", "1.0.3.9"
|
||||||
VALUE "InternalName", "ServerDll.dll"
|
VALUE "InternalName", "ServerDll.dll"
|
||||||
VALUE "LegalCopyright", "Copyright (C) 2019-2026"
|
VALUE "LegalCopyright", "Copyright (C) 2019-2026"
|
||||||
VALUE "OriginalFilename", "ServerDll.dll"
|
VALUE "OriginalFilename", "ServerDll.dll"
|
||||||
|
|||||||
Binary file not shown.
@@ -0,0 +1,396 @@
|
|||||||
|
# YAMA MCP 文件传输功能设计(download_file / upload_file)
|
||||||
|
|
||||||
|
> **状态**:设计定稿(经专家审查修订,结论见 §13)。`download_file`(P1)已实施并验收;`upload_file`(P2)已实施(详见 §7,评审修订见 §13.1)。`download_file` 采用 **V2 文件传输协议**(`CMD_DOWN_FILES_V2` + 流式子连接 + SHA-256 校验),`upload_file` 复用服务端既有 `FileBatchTransferWorkerV2`(主连接同步驱动)。两者共享同一套「文件会话」注册表。
|
||||||
|
> **读者**:MCP 后续功能研发/评审人员。
|
||||||
|
> **关联文档**:[Mcp_Phase2_Design.md](./Mcp_Phase2_Design.md)(双模式无头驱动机制)、[FILE_TRANSFER_V2.md](./FILE_TRANSFER_V2.md)(V2 协议清单)、[Mcp_Design.md](./Mcp_Design.md)(Phase 1 协议/架构/配置)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 背景与目标
|
||||||
|
|
||||||
|
MCP 目前已暴露 `list_files`(只读列目录,`McpServer.cpp:1741`),但**没有文件传输工具**:把远程主机的文件/目录拉回主控本机(下载),或把本机文件推到远程(上传),只能通过 GUI 文件管理器手工操作。
|
||||||
|
|
||||||
|
本技术书解决:
|
||||||
|
|
||||||
|
1. **`download_file`**(本期 P1)——远程文件/目录 → 主控本机指定目录,复用 V2 推送协议,带 SHA-256 完整性校验、支持大文件。
|
||||||
|
2. **`upload_file`**(后续 P2)——主控本机文件/目录 → 远程指定目录,复用服务端 V2 发送器。
|
||||||
|
3. **一套共享的「流式文件会话」基础设施**——一次投入,下载/上传两处受益,镜像现有终端(`TermSession`)与远程控制(`ScreenCtrlSession`)会话模式。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 现状回顾
|
||||||
|
|
||||||
|
| 能力 | 现有协议/命令 | 服务端现有实现 | 是否暴露给 MCP |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 远程列目录 | `COMMAND_LIST_DRIVE`→`TOKEN_DRIVE_LIST`;`COMMAND_LIST_FILES`→`TOKEN_FILE_LIST` | `McpServer.cpp:1741`(`list_files`) | ✅ |
|
||||||
|
| 远程→本地下载(V1) | `COMMAND_DOWN_FILES`(`commands.h:166`) + `TOKEN_FILE_SIZE/DATA/FINISH` | `FileManagerDlg.cpp:2164` / `file/CFileManagerDlg.cpp:1136` | ❌ 仅 GUI |
|
||||||
|
| 远程→本地下载(V2) | `CMD_DOWN_FILES_V2`(94, `commands.h:260`) + `COMMAND_SEND_FILE_V2`(85) + `COMMAND_FILE_COMPLETE_V2`(91) | `FileManagerDlg.cpp:3337-3384`(下发)、`2756`(`RecvFileChunkV2` 落盘) | ❌ 仅 GUI |
|
||||||
|
| 本地→远程上传(V2) | `FileBatchTransferWorkerV2`(服务端作 sender) | `2015RemoteDlg.cpp:7572` | ❌ 仅 GUI |
|
||||||
|
|
||||||
|
### 2.1 关键事实
|
||||||
|
|
||||||
|
1. **V2 下载的客户端会另开一条新子连接**:`client/FileManager.cpp:1101-1190` `UploadToRemoteV2()` 解析完文件列表后,`new IOCPClient(...)` → `EnableSubConnAuth()` → `ConnectServer(...)`(`FileManager.cpp:1170-1172`),在**独立鉴权子连接**上推 `COMMAND_SEND_FILE_V2` 分块 + `COMMAND_FILE_COMPLETE_V2` 校验包。即下载 ≠ 复用文件管理器那条子链接,而是一条**持续流子链接**——正属 `Mcp_Phase2_Design.md §4.3` 当初判定「本期不做」的流式能力。
|
||||||
|
|
||||||
|
2. **接收端 `RecvFileChunkV2` 无 UI 可调**:`CDlgFileSend.cpp:128` 已 `RecvFileChunkV2(buf, len, nullptr, nullptr, hash, hmac, 0)` 无头落盘;落盘状态在 `SimplePlugins/file_upload.cpp` 内部按 `transferID` 自维护,天然适合 MCP 无头复用。
|
||||||
|
|
||||||
|
3. **V2 落盘路径由客户端回填**:`CMD_DOWN_FILES_V2` 的 `targetDir` 是**主控本机**保存目录(`FileManagerDlg.cpp:3370` `m_Local_Path`);客户端把它拼进每个 chunk 的 `filename` 发回,服务端 `RecvFileChunkV2` 直接按该完整路径写盘。因此「下载保存到哪」由服务端在命令里指定、客户端原样回填。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 协议选型(已定:V2)
|
||||||
|
|
||||||
|
| 维度 | **V2 推送(选定)** | V1 拉取(回退) |
|
||||||
|
|---|---|---|
|
||||||
|
| 命令 | `CMD_DOWN_FILES_V2` + `COMMAND_SEND_FILE_V2` + `COMMAND_FILE_COMPLETE_V2` | `COMMAND_DOWN_FILES` + `TOKEN_FILE_SIZE/DATA/FINISH` |
|
||||||
|
| 连接 | 客户端新开一条鉴权流式子连接 | 复用文件管理器子链接 |
|
||||||
|
| 完整性 | ✅ SHA-256(`COMMAND_FILE_COMPLETE_V2`) | ❌ 无 |
|
||||||
|
| 大文件 | ✅ 64 位偏移,>4GB | 64 位偏移但无校验 |
|
||||||
|
| 客户端代码 | ✅ 已存在,是当前 GUI 正规路径 | ⚠️ 遗留路径 |
|
||||||
|
| MCP 侧复杂度 | 需路由一条**新流式子连接**(新增会话注册表) | 需在 `MessageHandle` 维护「发 CONTINUE/收 DATA」状态机,逐块 ACK 慢 |
|
||||||
|
|
||||||
|
**结论:采用 V2。** 代价是服务端要新增「流式文件会话」注册与路由;但这是 `upload_file` 同样要复用的基础设施,一次投入两处受益。V1 仅作「不想引入新子连接路由」时的降级备案,不在本期实现。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 设计原则
|
||||||
|
|
||||||
|
1. **复用现有协议,不新造命令**:`CMD_DOWN_FILES_V2`、`COMMAND_SEND_FILE_V2`、`COMMAND_FILE_COMPLETE_V2`、`COMMAND_LIST_DRIVE` 全部为 `common/commands.h` 既有;客户端零改动(upload 方向例外:`COMMAND_SEND_FILE_V2` 需懒初始化文件模块,见 §7.8)。
|
||||||
|
2. **无头接管复用会话模式**:镜像 `McpServer.h` 的 `TermSession` / `ScreenCtrlSession`,新增 `FileTransferSession`;`MessageHandle` 用 `IsFileTransferContext(context*)` 判定是否路由到 MCP 无头落盘,否则回落 GUI 进度框。
|
||||||
|
3. **单设备单传输会话**:与终端/远程控制一致,避免同 host 并发传输的归属歧义;并发返回 `-32003 Device busy`。
|
||||||
|
4. **超时与清理是硬约束**:大文件传输是长任务,超时需独立于 `kMcpToolTimeoutMs`(20s);断线/超时必须清会话 + 关子链接 + 删半成品文件。
|
||||||
|
5. **输出 schema 先行**:两个工具先在 `tools/list` 声明完整 `inputSchema`/`outputSchema`。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 核心机制:流式文件会话
|
||||||
|
|
||||||
|
### 5.1 会话注册表(`McpServer.h/.cpp`)
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
struct FileTransferSession {
|
||||||
|
std::string tool; // "download_file" / "upload_file"
|
||||||
|
std::string localDir; // download: 本机落盘目录(规范化后绝对路径,用于穿越校验)
|
||||||
|
std::string remotePath; // 审计用原始远程路径(UTF-8)
|
||||||
|
uint64_t transferID = 0; // 绑定后从首包填充(download);upload 由本端 GenerateTransferID()
|
||||||
|
uint32_t totalFiles = 0; // 完成判定:首个 COMMAND_SEND_FILE_V2 chunk 的 totalFiles(§13 F4)
|
||||||
|
uint32_t filesDone = 0; // 完成判定:累计收到的 COMMAND_FILE_COMPLETE_V2 数
|
||||||
|
bool done = false;
|
||||||
|
int error = 0; // FEV2_* / 自定义
|
||||||
|
std::vector<FileEntry> files; // {path, size} 已落盘/已发送文件
|
||||||
|
std::vector<FileEntry> skipped; // overwrite=false 时跳过的同名文件
|
||||||
|
time_t startAt = 0;
|
||||||
|
};
|
||||||
|
std::mutex m_FileXferMutex;
|
||||||
|
std::map<uint64_t, FileTransferSession> m_FileXferSessions; // device_id → 会话
|
||||||
|
```
|
||||||
|
|
||||||
|
> **取消 `m_FileXferContextToDevice` 映射与 `IsFileTransferContext(context*)`**:流式子连接经 `TOKEN_CONN_AUTH` 已把 `clientID` 钉在 `ContextObject` 上(`2015RemoteDlg.cpp:6411 SetID`),分派时直接用 `ContextObject->GetClientID()` 定位 `device_id`,无需 context 路由表(§13 F1)。
|
||||||
|
|
||||||
|
配套接口(与 `BeginTermPending` / `OnTerminalData` / `WaitTerminalDone` 同构):
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
bool BeginFileTransferPending(uint64_t device_id, const std::string& tool,
|
||||||
|
const std::string& localDir, const std::string& remotePath);
|
||||||
|
bool IsFileTransferPending(uint64_t device_id); // 分支顶部守卫
|
||||||
|
void OnFileChunkV2(uint64_t device_id, const BYTE* buf, ULONG len); // 路径校验 + 无头 RecvFileChunkV2
|
||||||
|
void OnFileCompleteV2(uint64_t device_id, const BYTE* buf, ULONG len);// SHA-256 + filesDone 计数
|
||||||
|
bool WaitFileTransferDone(uint64_t device_id, int timeoutMs, std::vector<FileEntry>& out);
|
||||||
|
void ClearFileTransfer(uint64_t device_id); // 失败/超时/断线收尾(删半成品)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.2 流式子连接的路由(device_id 键,镜像 `TOKEN_DRIVE_LIST` 守卫)
|
||||||
|
|
||||||
|
V2 下载的数据包 `COMMAND_SEND_FILE_V2`(85) / `COMMAND_FILE_COMPLETE_V2`(91) 本就由 `MessageHandle` 按 `ContextObject->GetClientID()` 分派(`2015RemoteDlg.cpp:5842` / `6101`)。MCP 只需在这两个 case 的 **`dstClientID==0`(M2C)分支顶部**加守卫:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
case COMMAND_SEND_FILE_V2: {
|
||||||
|
FileChunkPacketV2* pkt = (FileChunkPacketV2*)szBuffer;
|
||||||
|
if (pkt->dstClientID == 0) {
|
||||||
|
uint64_t devId = ContextObject->GetClientID();
|
||||||
|
if (McpServer().IsFileTransferPending(devId)) { // ← 新增守卫:无头接管
|
||||||
|
McpServer().OnFileChunkV2(devId, szBuffer, len);
|
||||||
|
break; // 不建 CDlgFileSend、不设 hDlg
|
||||||
|
}
|
||||||
|
// ... 原 GUI 逻辑(建 CDlgFileSend + OnReceiveComplete)不变
|
||||||
|
}
|
||||||
|
// ... C2C 分支(dstClientID != 0)完全不动
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `TOKEN_CONN_AUTH`(`6378`) **完全不动**——它只负责把 `clientID` 钉在 ctx 上,下载复用既有行为。
|
||||||
|
- `TOKEN_DRIVE_LIST`(`6430`) 既有 `IsPending(devId)` 守卫**扩展复用**:`OnDriveList` 识别当前挂起工具为 `download_file` 时,改发 `CMD_DOWN_FILES_V2` 而非 `COMMAND_LIST_FILES`,发完即 `CancelIO` 文件管理器子链接(数据走新流式子连接,§13 F3)。
|
||||||
|
- 守卫为假时全部回落原 GUI/C2C 路径——对既有功能零影响(原则 #1,§13 F7)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. `download_file` 设计(P1)
|
||||||
|
|
||||||
|
### 6.1 工具 Schema
|
||||||
|
|
||||||
|
```
|
||||||
|
input: {
|
||||||
|
id: string (必填, 主机 id)
|
||||||
|
remote_path: string (必填, 远程文件或目录绝对路径, 如 C:\Users\shaun\Pictures)
|
||||||
|
local_dir: string (必填, 主控本机保存目录; 不存在会自动创建)
|
||||||
|
overwrite: boolean (可选, 默认 false; true 覆盖同名文件, false 则跳过)
|
||||||
|
timeout_ms: integer (可选, 默认 600000, 上限 3600000)
|
||||||
|
}
|
||||||
|
output: {
|
||||||
|
files: [{ path: string, size: integer, sha256: string }] // 实际落盘文件
|
||||||
|
total_bytes: integer
|
||||||
|
skipped: integer
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.2 时序
|
||||||
|
|
||||||
|
```
|
||||||
|
MCP工具线程 服务端 MessageHandle 客户端
|
||||||
|
│ 1. BeginFileTransferPending(id,"download_file",localDir,remotePath) │
|
||||||
|
│ 2. 主连接发 COMMAND_LIST_DRIVE ────────────────────────────────────────► 开文件管理器子链接
|
||||||
|
│ 3. ◄── TOKEN_DRIVE_LIST ─────────────────────┤
|
||||||
|
│ OnDriveList → 锁外下发 CMD_DOWN_FILES_V2[targetDir\0][remote_path\0]\0 │
|
||||||
|
│ 4. ────────────────────────────────────────► UploadToRemoteV2()
|
||||||
|
│ (客户端另开一条鉴权流式子连接 ConnectServer)
|
||||||
|
│ 5. 新子连接 TOKEN_CONN_AUTH 钉 clientID → 后续分块按 device_id 守卫接管 │
|
||||||
|
│ 6. ◄── COMMAND_SEND_FILE_V2 分块流 ──────────┤
|
||||||
|
│ → OnFileChunkV2 → RecvFileChunkV2() 无头落盘
|
||||||
|
│ 7. ◄── COMMAND_FILE_COMPLETE_V2(SHA-256) ────┤
|
||||||
|
│ 8. 校验通过 → done → 唤醒工具线程 → CancelIO 两条子连接 → 返回 │
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.3 服务端改动
|
||||||
|
|
||||||
|
1. **`McpServer.h`**:新增 `FileTransferSession` 结构 + 注册表 + 接口声明(§5.1)。
|
||||||
|
2. **`McpServer.cpp`**:
|
||||||
|
- `BuildDownloadFileInputSchema/OutputSchema` + `BuildDownloadFile(...)`(`tools/call` 分派);
|
||||||
|
- `OnFileChunkV2`:**先路径校验**(chunk `filename` 规范化后仍在 `local_dir` 内,防 `..` 穿越,§13 F5)→ 调 `RecvFileChunkV2(buf,len,nullptr,nullptr,hash,hmac,0)` 无头落盘,记 `totalFiles`/进度;
|
||||||
|
- `OnFileCompleteV2`:`HandleFileCompleteV2` 校验 SHA-256 → `filesDone++`;`filesDone==totalFiles` 时置 `done` + `notify`(§13 F4);
|
||||||
|
- `hash/hmac` 镜像 `FileManagerDlg.cpp:2755` 的 `GetPwdHash()/GetHMAC(100)`,兼容客户端 M2C `hmac` 为空(`client/FileManager.cpp:1167`,§13 F6);
|
||||||
|
- `ClearFileTransfer`(发送失败/超时/断线):`CancelIO` 子链接 + 删除半成品。
|
||||||
|
3. **`2015RemoteDlg.cpp`(`MessageHandle`)**:`COMMAND_SEND_FILE_V2`(85)、`COMMAND_FILE_COMPLETE_V2`(91) 的 `dstClientID==0` 分支顶部加 `IsFileTransferPending(devId)` 守卫;`TOKEN_DRIVE_LIST` 处扩展 `OnDriveList` 识别 `download_file` 改发 `CMD_DOWN_FILES_V2`。`TOKEN_CONN_AUTH` 与 C2C 分支**完全不动**。
|
||||||
|
4. **`McpSettingsDlg.cpp/.h`**:新增 `McpFileTransfer` 配置项(默认 0)。
|
||||||
|
|
||||||
|
### 6.4 落盘与路径安全
|
||||||
|
|
||||||
|
- 发 `CMD_DOWN_FILES_V2` 前把 `local_dir` 规范化为绝对路径并 `CreateDirectory`;`targetDir` 用 ANSI(MBCS 构建本机路径)。
|
||||||
|
- 每收到一个 chunk,校验其 `filename` 规范化后**前缀必须在 `local_dir` 内**(防 `..` 穿越,呼应 `FILE_TRANSFER_V2.md §8.3` 已识别的风险);逃逸则置 `error` 并 `CancelIO`。
|
||||||
|
- 编码:`remote_path` 走 UTF-8→ANSI(936)(Windows 客户端,复用 `McpServer.cpp` 既有 `ToAnsi`,与 `list_files` 一致)。
|
||||||
|
|
||||||
|
### 6.5 超时与并发
|
||||||
|
|
||||||
|
- **P1 范围:同步 + 单文件/已打包目录**。一次 `tools/call` 阻塞到底,不引入 job/task 异步模型(决策见 §12)。核心场景「先 `terminal_exec` 压缩成单 zip,再 `download_file` 拉回」天然是单文件,同步足够。
|
||||||
|
- 默认 `timeout_ms=600000`、上限 3600000,因大文件远超 `kMcpToolTimeoutMs`(20s)。
|
||||||
|
- **单飞互斥(§13 F2)**:`BeginFileTransferPending` 与既有一次性 `m_Pending` **双向互斥**——下载登记时占用 `m_Pending`,反之 `BeginPending` 也检查 `m_FileXferSessions`,保证同 host 任意时刻只一个 MCP 工具在飞;并发返回 `-32003 Device busy`。
|
||||||
|
- 断线(`OfflineProc` 擦会话)+ 超时(删半成品)双收尾。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. `upload_file` 设计(P2)
|
||||||
|
|
||||||
|
### 7.1 工具 Schema
|
||||||
|
|
||||||
|
```
|
||||||
|
input: {
|
||||||
|
id: string (必填, 目标主机 id)
|
||||||
|
local_path: string (必填, 主控本机文件或目录绝对路径; 目录递归上传)
|
||||||
|
remote_dir: string (必填, 远程主机保存目录; 不存在会自动创建)
|
||||||
|
overwrite: boolean (可选, 默认 false; true 覆盖同名文件, false 跳过)
|
||||||
|
timeout_ms: integer (可选, 默认 600000, 上限 3600000)
|
||||||
|
}
|
||||||
|
output: {
|
||||||
|
files: [{ path: string, size: integer, sha256: string }] // 已发送文件(远程完整路径);P2 sha256 恒为空串(见 §7.5)
|
||||||
|
total_bytes: integer
|
||||||
|
skipped: integer
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.2 方向与连接(与 download 的关键差异)
|
||||||
|
|
||||||
|
| 维度 | download_file(P1) | upload_file(P2) |
|
||||||
|
|---|---|---|
|
||||||
|
| 数据方向 | 远程客户端 → 主控 | 主控 → 远程客户端 |
|
||||||
|
| 发送方 | 客户端(`UploadToRemoteV2` → `FileBatchTransferWorkerV2`) | 主控(`FileBatchTransferWorkerV2`) |
|
||||||
|
| 连接 | 客户端**新开一条鉴权流式子连接** | 复用**主连接**(`ctx->Send2Client`),无子连接 |
|
||||||
|
| 完成信号 | 客户端发 `COMMAND_FILE_COMPLETE_V2`,服务端 `OnFileCompleteV2` 校验计数 | 服务端**作为发送方**自己发 `COMMAND_FILE_COMPLETE_V2`(SHA-256),客户端 `HandleFileCompleteV2` 校验 |
|
||||||
|
| MessageHandle 改动 | 2 处守卫分支 + `OnDriveList` 扩展 | **零改动**(客户端 `KernelManager.cpp:1381` 接收,但需懒初始化文件模块,见 §7.8) |
|
||||||
|
|
||||||
|
> **修正旧稿(§13.1 评审)**:旧稿写「只需等客户端回 `COMMAND_FILE_COMPLETE_V2`」是错的。V2 协议里 COMPLETE 包恒由**发送方**发、接收方校验,**无接收方→发送方 ACK**。upload 的服务端是发送方,故它**发** COMPLETE 而非「等」。这也意味着 upload 比 download 更简单——无需路由外来流式子连接、无需 `OnFileCompleteV2` 计数。
|
||||||
|
|
||||||
|
### 7.3 时序
|
||||||
|
|
||||||
|
```
|
||||||
|
MCP工具线程 主连接 (ctx→Send2Client) 客户端 KernelManager
|
||||||
|
1. 校验 McpFileTransfer=1 && McpReadonly=0(§8)
|
||||||
|
2. CollectLocalFiles({local_path}) 收集本机文件+目录项(目录项在前、子项随后)
|
||||||
|
3. (overwrite=false) list_files 预检 remote_dir 一层 → 得已存在顶层名 → skip 列表
|
||||||
|
4. BeginFileUpload(id) 登记会话(单设备单传输互斥标记,§6.5)
|
||||||
|
5. 同步驱动(工具线程内):FileBatchTransferWorkerV2(files, remote_dir, cb, f, hash, hmac, opts)
|
||||||
|
6. ── COMMAND_SEND_FILE_V2 分块流 ───► RecvFileChunkV2 落盘
|
||||||
|
7. ── COMMAND_FILE_COMPLETE_V2(SHA256)► HandleFileCompleteV2 校验
|
||||||
|
8. worker 同步返回 → ClearFileTransfer → 返回 files/total_bytes/skipped
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.4 服务端改动
|
||||||
|
|
||||||
|
1. **`McpServer.h`**:新增 `BeginFileUpload(id)`,登记 `FileTransferSession{tool="upload_file", startAt}` 作为**单设备单传输互斥标记**(与 `m_Pending`/`m_FileXferSessions` 互斥,见 §6.5)。upload 走主连接、工具线程**同步**驱动 `FileBatchTransferWorkerV2`,会话**无** `fmSubCtx`/`streamSubCtx`/`fileEntries`/`done` 等字段(`ClearFileTransfer` 对空指针安全)。
|
||||||
|
2. **`McpServer.cpp`**:
|
||||||
|
- `BuildUploadFileInputSchema/OutputSchema` + `BuildUploadFile(...)`(`tools/call` 分派);
|
||||||
|
- 无头回调 `UploadSendChunkHeadless(user, chunk, data, size)`:经 `UploadCallbackData{parent,clientID,deadline}` 里 `parent->FindHost(clientID)` 定位 ctx → `ctx->Send2Client(data,size)`;客户端离线或整体超时返回 false 中止(镜像 GUI `SendFileChunkToClientV2`,去掉 `dlg` 进度,见 `2015RemoteDlg.cpp:7424-7459`);
|
||||||
|
- 收集:本地 `CollectLocalFiles` 递归(`common/file_upload.cpp:38` 的 `ExpandDirectories` 未在 `file_upload.h` 导出,故在 McpServer.cpp 本地同构实现,目录项在前、子项随后);
|
||||||
|
- overwrite 预检:复用 `list_files` 的 `COMMAND_LIST_DRIVE→TOKEN_DRIVE_LIST→COMMAND_LIST_FILES→TOKEN_FILE_LIST` 机制列 `remote_dir` **一层**(顶层名,不递归),过滤同名 → `skipped`;
|
||||||
|
- 驱动:`FileBatchTransferWorkerV2(files, remote_dir, &cb, headlessCallback, nullptr, GetPwdHash(), GetHMAC(100), opts)`;`opts.transferID=GenerateTransferID()`、`srcClientID=0`、`dstClientID=clientID`、`enableResume=false`;
|
||||||
|
- 完成判定:`result==0 && FindHost(clientID)!=nullptr`(镜像 GUI `SendFilesToClientV2Internal` 末尾)。
|
||||||
|
3. **`2015RemoteDlg.cpp`(MessageHandle)**:**零改动**。
|
||||||
|
4. **`McpSettingsDlg.cpp/.h`**:零改动(`McpFileTransfer` 已存在,§8 复用)。
|
||||||
|
|
||||||
|
### 7.5 完整性边界(诚实声明)
|
||||||
|
|
||||||
|
- V2 无接收方→发送方 ACK:客户端 `HandleFileCompleteV2` 校验失败只打日志(`RecvFileChunkV2` 返回 8,`KernelManager.cpp:1381` 不回传主控)。
|
||||||
|
- **P2 的 `output.files[].sha256` 恒为空串**:`FileBatchTransferWorkerV2` 内部会为每个文件自算 SHA-256 并写入 `COMMAND_FILE_COMPLETE_V2` 包发给客户端,但该值**不回传调用方**;服务端侧又无导出的 SHA-256 工具函数可自行复算。故 P2 输出不填 sha256(延后 P3:worker 回传哈希,或服务端引入 SHA-256 工具)。传输完整性仍依赖客户端本地 `HandleFileCompleteV2` 校验(与 GUI upload / C2C 同权,属 V2 协议既有边界,非 MCP 引入)。
|
||||||
|
- 若未来需要「接收方验真回执」,需新增反向 ACK 包(协议扩展,进 P3)。
|
||||||
|
|
||||||
|
### 7.6 overwrite 语义
|
||||||
|
|
||||||
|
- `overwrite=false`(默认):发送前用 `list_files` 预检 `remote_dir` **一层**(顶层名,不递归),本地顶层项名已存在者整体跳过(单文件精确、目录整体跳过),文件条目计入 `skipped`(目录项不计)。编码 UTF-8→ANSI(936),与 `list_files`/`download_file` 一致。
|
||||||
|
- `overwrite=true`:全量发送(客户端 `RecvFileChunkV2` 覆盖写)。
|
||||||
|
- 代价:一次预检往返;对称于 download 的「本地同名跳过」语义。
|
||||||
|
|
||||||
|
### 7.7 安全
|
||||||
|
|
||||||
|
- 门槛 `McpFileTransfer=1 && McpReadonly=0`(§8):upload 写远程盘,复用「允许写」主开关。
|
||||||
|
- `remote_dir` 路径规范化(防 `..` 穿越到预期目录之外)。`local_path` 是主控本机路径(信任本地文件系统),主要风险是 AI 误推敏感文件/覆盖远程关键文件——由 `McpReadonly=0` + 审计兜底(与 `terminal_*`/`remote_*` 同款「开关+审计」,不加目录黑名单,见 §12.1)。
|
||||||
|
|
||||||
|
### 7.8 客户端改动(实施期修正,评审见 §13.1 U7)
|
||||||
|
|
||||||
|
`RecvFileChunkV2` 依赖全局 `g_status==1`(`SimplePlugins/file_upload.cpp:2812` `if (!g_status) return -1;`),但客户端**主连接此前从不初始化文件传输模块**——`InitFileUpload` 只在 `FileManager`/`ScreenManager` 构造里成对调用(下载/屏幕方向),上传走主连接时 `g_status==0`,每个 chunk 都被 `RecvFileChunkV2` 直接丢弃(表现为落盘 0 字节 / 截断)。
|
||||||
|
|
||||||
|
修法:在 `client/KernelManager.cpp` 的 `COMMAND_SEND_FILE_V2` 分支加**懒初始化**(进程内仅一次):
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
static bool s_v2RecvInited = false;
|
||||||
|
if (!s_v2RecvInited) {
|
||||||
|
InitFileUpload({}, m_LoginMsg, m_LoginSignature, 64, 50, Logf);
|
||||||
|
s_v2RecvInited = true;
|
||||||
|
}
|
||||||
|
int n = RecvFileChunkV2((char*)szBuffer, ulLength, m_conn, nullptr, m_hash, m_hmac, m_MyClientID);
|
||||||
|
```
|
||||||
|
|
||||||
|
要点(独立评审结论,均为安全):
|
||||||
|
|
||||||
|
- 与 `FileManager.cpp:40` 的 Init 参数**逐字节一致**(`{}`, `m_LoginMsg`, `m_LoginSignature`, 64, 50, `Logf`),不新增路径。
|
||||||
|
- `static` 保证进程内只初始化一次,主连接重连(`ClientDll.cpp:660/665` 反复 `SAFE_DELETE`+`new CKernelManager`)不反复 Init/Uninit;`~CKernelManager` 保持原样**不** `UninitFileUpload`,故 `g_threadCount` 从 1 起永不归 0,`g_status` 恒为 1。
|
||||||
|
- `InitFileUpload` 幂等(`g_fileStatesMtx` + `g_threadCount` 引用计数,二次调用 `g_threadCount>1` 早退)、非阻塞(仅置标志 + 派生 detach 线程)、license 校验在启动期 `licenseInit()` 后必过、`verifyMessage` 对空签名 `return false`(`license.cpp:244`)不会越界。
|
||||||
|
- 影响面:主连接 `OnReceive` 无 V1 `COMMAND_SEND_FILE` 分支,`g_status=1` 只作用于 `RecvFileChunkV2`;`FileManager`/`ScreenManager` 的成对 Init/Uninit 只是在 1↔2 间震荡,语义不变。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 安全门
|
||||||
|
|
||||||
|
| 工具 | 定性 | 门槛 |
|
||||||
|
|---|---|---|
|
||||||
|
| `download_file` | 不改远程状态,但**数据外带** | `McpFileTransfer=1`(仅此一开关;**不依赖** `McpReadonly`,与 `list_files`/`get_screenshot` 同权) |
|
||||||
|
| `upload_file` | **写远程盘** | `McpFileTransfer=1` **且** `McpReadonly=0`(复用既有「允许写」主开关) |
|
||||||
|
|
||||||
|
- **只新增一个开关 `McpFileTransfer`(默认 0)**:download 只看它;upload 额外要求 `McpReadonly=0`。决策依据见 §12。
|
||||||
|
- `tools/list` 据此隐藏两个工具(与 `exec_command`/`terminal_*`/`remote_*` 同款开关判断)。
|
||||||
|
- 审计:`host_id + tool + remote_path + local_dir + 结果`,写 `Mprintf` + 审计日志。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. 改动文件清单
|
||||||
|
|
||||||
|
| 文件 | 改动 |
|
||||||
|
|---|---|
|
||||||
|
| `server/2015Remote/McpServer.h` | `FileTransferSession` + 注册表 + 8 个接口声明 |
|
||||||
|
| `server/2015Remote/McpServer.cpp` | `download_file` schema/实现 + 会话状态机 + 路由分支 |
|
||||||
|
| `server/2015Remote/2015RemoteDlg.cpp` | `MessageHandle`:`COMMAND_SEND_FILE_V2`(85) / `COMMAND_FILE_COMPLETE_V2`(91) 两处守卫分支 + `TOKEN_DRIVE_LIST` 处 `OnDriveList` 扩展;`TOKEN_CONN_AUTH` 不动 |
|
||||||
|
| `server/2015Remote/McpSettingsDlg.cpp/.h` | `McpFileTransfer` 配置项 |
|
||||||
|
| (P2)`server/2015Remote/McpServer.h` | `FileTransferSession.tool` 增 `"upload_file"`;新增 `BeginFileUpload(id)`(单设备单传输互斥标记) |
|
||||||
|
| (P2)`server/2015Remote/McpServer.cpp` | `upload_file` schema/实现:无头回调 `UploadSendChunkHeadless` + `CollectLocalFiles` 收集 + overwrite 预检(复用 `list_files` 链路)+ 驱动 `FileBatchTransferWorkerV2`(复用主连接,无子连接路由) |
|
||||||
|
| (P2)`client/KernelManager.cpp` | `COMMAND_SEND_FILE_V2` 分支加懒初始化 `InitFileUpload`(`g_status` 由 0 置 1,见 §7.8) |
|
||||||
|
|
||||||
|
**客户端**:`UploadToRemoteV2` / `RecvFileChunkV2` / `FileBatchTransferWorkerV2` 均已存在、未改动;仅 `COMMAND_SEND_FILE_V2` 分支新增懒初始化(见 §7.8)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. 分阶段实施与回滚
|
||||||
|
|
||||||
|
- **P1**:`download_file` + `McpFileTransfer` 开关 + 路由分支。可独立合入、独立验收(真实主机拖回一个目录,SHA-256 与 `certutil -hashfile` 比对一致)。
|
||||||
|
- **P2**:`upload_file`(§7,主连接复用 + 发送方驱动,`MessageHandle` 零改动)。可独立合入、独立验收(推一个目录到真实主机,SHA-256 与源文件 `certutil -hashfile` 比对一致)。
|
||||||
|
- **P3**(可选):断点续传(需先验证服务端续传状态落盘;文件管理器侧现 `enableResume=false`,`client/FileManager.cpp:1164`)、大文件进度流式上报。
|
||||||
|
- **回滚**:改动集中在 `McpServer.*` + `MessageHandle` 三个 `if` 分支,revert 当期 commit 即可,不影响既有 GUI 文件管理器。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. 测试点
|
||||||
|
|
||||||
|
- 单文件 / 目录(含中文名、深层嵌套)下载;SHA-256 与 `certutil -hashfile` 比对一致。
|
||||||
|
- `overwrite=false` 同名跳过;`local_dir` 不存在自动创建。
|
||||||
|
- 路径穿越:`remote_path` 含 `..` 或 chunk 文件名逃逸 `local_dir` 时被拒。
|
||||||
|
- 大文件(>2GB)超时与断线;并发下载同主机返回 `-32003`。
|
||||||
|
- 编码:GBK 中文文件名往返无乱码(与 `list_files` 同规则)。
|
||||||
|
- 断线收尾:传输中客户端掉线 → 会话擦除 + 半成品删除,无句柄/内存泄漏。
|
||||||
|
- **上传(P2)**:单文件 / 目录(含中文名、深层嵌套)上传;`remote_dir` 不存在自动创建;SHA-256 与源文件 `certutil -hashfile` 比对一致。
|
||||||
|
- 上传 `overwrite=false` 同名跳过(`skipped` 计数);`overwrite=true` 覆盖。
|
||||||
|
- 上传 `remote_dir` 含 `..` 逃逸被拒;大文件(>2GB)超时与断线;并发上传/上传-下载同主机返回 `-32003`。
|
||||||
|
- 上传编码:GBK 中文文件名往返无乱码。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. 决策记录(以「简单易用」为原则)
|
||||||
|
|
||||||
|
原则:最少开关、最少认知负担、复用既有机制、不为低频场景预留复杂度;只有威胁模型确实需要时才加复杂度。
|
||||||
|
|
||||||
|
### 12.1 download 数据外带 → **不加目录白名单,信任 `McpFileTransfer` + 审计**
|
||||||
|
|
||||||
|
- **威胁模型**:MCP 默认 `127.0.0.1` + token;「数据外带」的实际顾虑是 AI 误操作或提示注入。但一旦开启 `terminal_exec`(全 shell、无白名单),外带通道远大于 download,目录白名单对已开终端者是冗余防御。
|
||||||
|
- **一致性**:`terminal_*` / `remote_*` 均只有开关 + 审计、无路径白名单;给 download 单独加白名单是特例,增加解释负担。
|
||||||
|
- **简单**:工具契约保持 `id + remote_path + local_dir`,不引入「允许根目录」概念。
|
||||||
|
- **逃生舱**:若未来确有需要,复用 `McpCmdWhitelist` 那种**单字符串**配置作可选收紧项,进 P3,不进 P1。
|
||||||
|
|
||||||
|
### 12.2 大文件模型 → **P1 同步 + 大超时,收窄到单文件/已打包目录**
|
||||||
|
|
||||||
|
- MCP 是 request/response;为一次性传输引入「提交任务 + 轮询进度 + 任务清理」违背「简单 + 每阶段独立可交付」。
|
||||||
|
- 核心场景(先压缩成单 zip 再拉回)天然是单文件,同步足够;`get_screenshot` 已先例式地内联返回字节。
|
||||||
|
- 异步任务模型进 P3,且**只在确有超大流式需求时**才做,不预建。
|
||||||
|
|
||||||
|
### 12.3 upload 开关 → **单一 `McpFileTransfer`,upload 复用 `McpReadonly=0`**
|
||||||
|
|
||||||
|
- `download_file` = `McpFileTransfer=1`(不依赖 `McpReadonly`):它只读远程,与 `list_files`/`get_screenshot` 同权,readonly 语义本就只拦「改远程」。
|
||||||
|
- `upload_file` = `McpFileTransfer=1 && McpReadonly=0`:upload 改远程,复用既有「允许写」主开关,与 `terminal_*`/`remote_*` 的 `X && !Readonly` 模式一致。
|
||||||
|
- 只新增 **1 个**开关;代价是「不能只开 upload 不开 download」——这是可接受的简化(无人有此诉求)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 13. 专家审查记录(实施前)
|
||||||
|
|
||||||
|
原则优先级:① 对既有功能影响最小 → ② 简单易用 → ③ 优先 V2。逐条核对了 `2015RemoteDlg.cpp` 的 `MessageHandle` 分派、`client/FileManager.cpp` 与 `SimplePlugins/file_upload.cpp` 的实现,结论如下:
|
||||||
|
|
||||||
|
| # | 审查发现 | 结论 |
|
||||||
|
|---|---|---|
|
||||||
|
| F1 | 路由机制比草案更简单:`COMMAND_SEND_FILE_V2`(`5842`)/`COMMAND_FILE_COMPLETE_V2`(`6101`) 本就按 `ContextObject->GetClientID()` 分派,流式子连接经 `TOKEN_CONN_AUTH`(`6411 SetID`) 钉住 clientID | **无需 context 路由表**。改为 `device_id` 键 + 两个 case 顶部的 `IsFileTransferPending(devId)` 守卫,`TOKEN_CONN_AUTH` 完全不动 |
|
||||||
|
| F2 | 一次性 `m_Pending` 与新增文件会话可能并发冲突(同 host 双工具) | `BeginFileTransferPending` 与 `m_Pending` **双向互斥**,同 host 全 MCP 工具单飞 |
|
||||||
|
| F3 | 文件管理器子链接发完 `CMD_DOWN_FILES_V2` 后即无用(数据走新流式子连接) | `OnDriveList` 发完命令即 `CancelIO` 该子链接;客户端 `UploadToRemoteV2` 用独立 `IOCPClient`,不依赖它 |
|
||||||
|
| F4 | 完成信号是**每文件一个** `COMMAND_FILE_COMPLETE_V2`(`FileCompletePacketV2.fileIndex`) | 会话记 `totalFiles`(首 chunk)/`filesDone`(complete 计数),`filesDone==totalFiles` 才算 done |
|
||||||
|
| F5 | `RecvFileChunkV2` 内部直接按 chunk `filename` 落盘 | MCP 在调用**前**校验 `filename` 规范化仍在 `local_dir` 内,逃逸即取消传输 |
|
||||||
|
| F6 | `RecvFileChunkV2` 需正确 `hash/hmac` | 镜像 `FileManagerDlg.cpp:2755` 的 `GetPwdHash()/GetHMAC(100)`;客户端 M2C `hmac` 为空(`FileManager.cpp:1167`) |
|
||||||
|
| F7 | 三处改动均为「加 if 守卫 + 现有逻辑作 else」,C2C 分支不动 | 结构性满足原则 #1,对既有 GUI/C2C 零影响 |
|
||||||
|
|
||||||
|
**判定**:7 项问题均已在正文对应章节修正,无阻塞项,**定稿**。
|
||||||
|
|
||||||
|
### 13.1 upload_file 评审记录(P2,实施前)
|
||||||
|
|
||||||
|
核对了 `2015RemoteDlg.cpp` 的 `SendFilesToClientV2Internal`/`SendFileChunkToClientV2`(GUI upload 发送方)、`SimplePlugins/file_upload.cpp` 的 `FileBatchTransferWorkerV2`/`RecvFileChunkV2`、`client/KernelManager.cpp:1381` 的接收分支,结论:
|
||||||
|
|
||||||
|
| # | 审查发现 | 结论 |
|
||||||
|
|---|---|---|
|
||||||
|
| U1 | 旧稿「服务端等客户端回 `COMMAND_FILE_COMPLETE_V2`」方向写反 | COMPLETE 恒由**发送方**发、接收方 `HandleFileCompleteV2` 校验,无 ACK 回传。upload 服务端是发送方 → **发** COMPLETE,不是「等」 |
|
||||||
|
| U2 | upload 走**主连接**(`ctx->Send2Client`),不像 download 要客户端新开鉴权流式子连接 | `MessageHandle` 零改动;无需 `IsFileTransferPending` 路由守卫、无需 `OnFileCompleteV2` 收包计数 |
|
||||||
|
| U3 | 复用 `FileBatchTransferWorkerV2` 时回调需无头版 | 镜像 GUI `SendFileChunkToClientV2` 去掉 `dlg` 进度,`m_parent->FindHost(clientID)` 定位 ctx;离线返回 false 中止 |
|
||||||
|
| U4 | 完成判定无接收方回执 | `result==0 && FindHost(clientID)!=nullptr`(与 GUI `SendFilesToClientV2Internal` 末尾一致);`output.sha256` 恒为空串,非接收方验真(见 §7.5) |
|
||||||
|
| U5 | `overwrite=false` 需预知远程同名文件 | 复用 `list_files` 的 `COMMAND_LIST_DRIVE→TOKEN_DRIVE_LIST` 预检 `remote_dir`,过滤同名进 `skipped` |
|
||||||
|
| U6 | 本地文件收集 | `common/file_upload.cpp:38` `ExpandDirectories` 未在 `file_upload.h` 导出,改为 McpServer.cpp 本地 `CollectLocalFiles` 同构实现(目录项在前、子项随后) |
|
||||||
|
| U7 | 实施验证发现:客户端主连接 `g_status==0`,`RecvFileChunkV2` 逐 chunk `return -1`(上传落盘 0 字节/截断) | 客户端 `COMMAND_SEND_FILE_V2` 分支加懒初始化(§7.8):`static` 一次 + 永久引用计数、析构不 `Uninit`;与 `FileManager.cpp:40` 参数一致,独立评审确认无稳定性风险 |
|
||||||
|
|
||||||
|
**判定**:7 项均已写入 §7 对应小节,无阻塞项,**定稿**。
|
||||||
Binary file not shown.
@@ -2201,6 +2201,8 @@ BOOL CMy2015RemoteDlg::OnInitDialog()
|
|||||||
McpServer().SetTerminalEnabled(THIS_CFG.GetInt("settings", "McpTerminal", 0) != 0);
|
McpServer().SetTerminalEnabled(THIS_CFG.GetInt("settings", "McpTerminal", 0) != 0);
|
||||||
// 远程控制开关:默认关;要求只读关(McpReadonly=0)才生效(工具列表/分派双重门控)。
|
// 远程控制开关:默认关;要求只读关(McpReadonly=0)才生效(工具列表/分派双重门控)。
|
||||||
McpServer().SetRemoteControlEnabled(THIS_CFG.GetInt("settings", "McpRemoteControl", 0) != 0);
|
McpServer().SetRemoteControlEnabled(THIS_CFG.GetInt("settings", "McpRemoteControl", 0) != 0);
|
||||||
|
// 文件传输开关:默认关;仅 download_file 门控(不要求只读关)。
|
||||||
|
McpServer().SetFileTransferEnabled(THIS_CFG.GetInt("settings", "McpFileTransfer", 0) != 0);
|
||||||
if (!McpServer().Start(mcpBind, mcpPort)) {
|
if (!McpServer().Start(mcpBind, mcpPort)) {
|
||||||
Mprintf("McpServer start failed on %s:%d\n", mcpBind.c_str(), mcpPort);
|
Mprintf("McpServer start failed on %s:%d\n", mcpBind.c_str(), mcpPort);
|
||||||
} else {
|
} else {
|
||||||
@@ -5843,6 +5845,14 @@ VOID CMy2015RemoteDlg::MessageHandle(CONTEXT_OBJECT* ContextObject)
|
|||||||
// V2 文件传输(支持 C2C)
|
// V2 文件传输(支持 C2C)
|
||||||
FileChunkPacketV2* pkt = (FileChunkPacketV2*)szBuffer;
|
FileChunkPacketV2* pkt = (FileChunkPacketV2*)szBuffer;
|
||||||
|
|
||||||
|
// P6:MCP download_file 挂起时接管流式子连接(dstClientID==0 → 主控端)。
|
||||||
|
// OnFileChunkV2 自身会做 len 校验,这里先判长度再读 dstClientID,避免越界。
|
||||||
|
if (len >= sizeof(FileChunkPacketV2) && pkt->dstClientID == 0 &&
|
||||||
|
McpServer().IsFileTransferPending(ContextObject->GetClientID())) {
|
||||||
|
McpServer().OnFileChunkV2(ContextObject->GetClientID(), ContextObject, szBuffer, len);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if (pkt->dstClientID == 0) {
|
if (pkt->dstClientID == 0) {
|
||||||
// 目标是主控端:本地接收
|
// 目标是主控端:本地接收
|
||||||
if (ContextObject->hDlg == NULL) {
|
if (ContextObject->hDlg == NULL) {
|
||||||
@@ -6103,6 +6113,13 @@ VOID CMy2015RemoteDlg::MessageHandle(CONTEXT_OBJECT* ContextObject)
|
|||||||
if (len < sizeof(FileCompletePacketV2)) break;
|
if (len < sizeof(FileCompletePacketV2)) break;
|
||||||
FileCompletePacketV2* pkt = (FileCompletePacketV2*)szBuffer;
|
FileCompletePacketV2* pkt = (FileCompletePacketV2*)szBuffer;
|
||||||
|
|
||||||
|
// P6:MCP download_file 挂起时接管完成校验包(dstClientID==0 → 主控端)。
|
||||||
|
if (pkt->dstClientID == 0 &&
|
||||||
|
McpServer().IsFileTransferPending(ContextObject->GetClientID())) {
|
||||||
|
McpServer().OnFileCompleteV2(ContextObject->GetClientID(), szBuffer, len);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if (pkt->dstClientID == 0) {
|
if (pkt->dstClientID == 0) {
|
||||||
// 目标是主控端:本地校验
|
// 目标是主控端:本地校验
|
||||||
bool verifyOk = HandleFileCompleteV2((char*)szBuffer, len, 0);
|
bool verifyOk = HandleFileCompleteV2((char*)szBuffer, len, 0);
|
||||||
@@ -6436,6 +6453,13 @@ VOID CMy2015RemoteDlg::MessageHandle(CONTEXT_OBJECT* ContextObject)
|
|||||||
ContextObject->CancelIO(); // 只列盘 → 用完即关一次性子链接
|
ContextObject->CancelIO(); // 只列盘 → 用完即关一次性子链接
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
// P6:MCP download_file 挂起时接管文件管理器子链接,下发 CMD_DOWN_FILES_V2。
|
||||||
|
// true=已接管(保持子链接,ClearFileTransfer 收尾);false=会话已清理(迟到包)→ 关孤儿子链接。
|
||||||
|
if (McpServer().IsFileTransferPending(devId)) {
|
||||||
|
if (!McpServer().OnDownloadDriveList(devId, ContextObject))
|
||||||
|
ContextObject->CancelIO();
|
||||||
|
break;
|
||||||
|
}
|
||||||
ContextObject->EnableZstdContext(6);
|
ContextObject->EnableZstdContext(6);
|
||||||
g_2015RemoteDlg->SendMessage(WM_OPENFILEMANAGERDIALOG, 0, (LPARAM)ContextObject);
|
g_2015RemoteDlg->SendMessage(WM_OPENFILEMANAGERDIALOG, 0, (LPARAM)ContextObject);
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -518,6 +518,7 @@
|
|||||||
<Image Include="res\Bitmap\AuthGen.bmp" />
|
<Image Include="res\Bitmap\AuthGen.bmp" />
|
||||||
<Image Include="res\Bitmap\authorize.bmp" />
|
<Image Include="res\Bitmap\authorize.bmp" />
|
||||||
<Image Include="res\Bitmap\Backup.bmp" />
|
<Image Include="res\Bitmap\Backup.bmp" />
|
||||||
|
<Image Include="res\Bitmap\BrowseWnd.bmp" />
|
||||||
<Image Include="res\Bitmap\CancelShare.bmp" />
|
<Image Include="res\Bitmap\CancelShare.bmp" />
|
||||||
<Image Include="res\Bitmap\ClientLog.bmp" />
|
<Image Include="res\Bitmap\ClientLog.bmp" />
|
||||||
<Image Include="res\Bitmap\Forbidden.bmp" />
|
<Image Include="res\Bitmap\Forbidden.bmp" />
|
||||||
@@ -534,6 +535,7 @@
|
|||||||
<Image Include="res\Bitmap\GrayDesktop.bmp" />
|
<Image Include="res\Bitmap\GrayDesktop.bmp" />
|
||||||
<Image Include="res\Bitmap\Help.bmp" />
|
<Image Include="res\Bitmap\Help.bmp" />
|
||||||
<Image Include="res\Bitmap\History.bmp" />
|
<Image Include="res\Bitmap\History.bmp" />
|
||||||
|
<Image Include="res\Bitmap\HistoryActivity.bmp" />
|
||||||
<Image Include="res\Bitmap\HostProxy.bmp" />
|
<Image Include="res\Bitmap\HostProxy.bmp" />
|
||||||
<Image Include="res\Bitmap\Import.bmp" />
|
<Image Include="res\Bitmap\Import.bmp" />
|
||||||
<Image Include="res\Bitmap\ImportLicense.bmp" />
|
<Image Include="res\Bitmap\ImportLicense.bmp" />
|
||||||
@@ -552,6 +554,7 @@
|
|||||||
<Image Include="res\Bitmap\PEEdit.bmp" />
|
<Image Include="res\Bitmap\PEEdit.bmp" />
|
||||||
<Image Include="res\Bitmap\Plugin.bmp" />
|
<Image Include="res\Bitmap\Plugin.bmp" />
|
||||||
<Image Include="res\Bitmap\PluginConfig.bmp" />
|
<Image Include="res\Bitmap\PluginConfig.bmp" />
|
||||||
|
<Image Include="res\Bitmap\PortProxyAuto.bmp" />
|
||||||
<Image Include="res\Bitmap\PortProxyStd.bmp" />
|
<Image Include="res\Bitmap\PortProxyStd.bmp" />
|
||||||
<Image Include="res\Bitmap\PrivateScreen.bmp" />
|
<Image Include="res\Bitmap\PrivateScreen.bmp" />
|
||||||
<Image Include="res\Bitmap\proxy.bmp" />
|
<Image Include="res\Bitmap\proxy.bmp" />
|
||||||
|
|||||||
@@ -88,6 +88,8 @@
|
|||||||
<ClCompile Include="McpServer.cpp" />
|
<ClCompile Include="McpServer.cpp" />
|
||||||
<ClCompile Include="McpSettingsDlg.cpp" />
|
<ClCompile Include="McpSettingsDlg.cpp" />
|
||||||
<ClCompile Include="CClientLog.cpp" />
|
<ClCompile Include="CClientLog.cpp" />
|
||||||
|
<ClCompile Include="CActivityDialog.cpp" />
|
||||||
|
<ClCompile Include="LogSearchBar.cpp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="..\..\client\Audio.h" />
|
<ClInclude Include="..\..\client\Audio.h" />
|
||||||
@@ -196,6 +198,8 @@
|
|||||||
<ClInclude Include="McpServer.h" />
|
<ClInclude Include="McpServer.h" />
|
||||||
<ClInclude Include="McpSettingsDlg.h" />
|
<ClInclude Include="McpSettingsDlg.h" />
|
||||||
<ClInclude Include="CClientLog.h" />
|
<ClInclude Include="CClientLog.h" />
|
||||||
|
<ClInclude Include="CActivityDialog.h" />
|
||||||
|
<ClInclude Include="LogSearchBar.h" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ResourceCompile Include="2015Remote.rc" />
|
<ResourceCompile Include="2015Remote.rc" />
|
||||||
@@ -338,5 +342,9 @@
|
|||||||
<Image Include="res\Bitmap\Debug.bmp" />
|
<Image Include="res\Bitmap\Debug.bmp" />
|
||||||
<Image Include="res\ClientLog.ico" />
|
<Image Include="res\ClientLog.ico" />
|
||||||
<Image Include="res\Bitmap\ClientLog.bmp" />
|
<Image Include="res\Bitmap\ClientLog.bmp" />
|
||||||
|
<Image Include="res\Bitmap\Forbidden.bmp" />
|
||||||
|
<Image Include="res\Bitmap\HistoryActivity.bmp" />
|
||||||
|
<Image Include="res\Bitmap\BrowseWnd.bmp" />
|
||||||
|
<Image Include="res\Bitmap\PortProxyAuto.bmp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
+894
-21
File diff suppressed because it is too large.
Load diff
@@ -8,6 +8,7 @@
|
|||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <condition_variable>
|
#include <condition_variable>
|
||||||
#include <map>
|
#include <map>
|
||||||
|
#include <set>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <ctime>
|
#include <ctime>
|
||||||
@@ -28,6 +29,14 @@
|
|||||||
class CMy2015RemoteDlg;
|
class CMy2015RemoteDlg;
|
||||||
class context;
|
class context;
|
||||||
|
|
||||||
|
// 文件传输结果条目(download_file 输出 files[] 的一项)。
|
||||||
|
// path 为本机落盘绝对路径(ANSI,输出 JSON 时再转 UTF-8);sha256 为小写 hex。
|
||||||
|
struct FileTransferEntry {
|
||||||
|
std::string path;
|
||||||
|
uint64_t size = 0;
|
||||||
|
std::string sha256;
|
||||||
|
};
|
||||||
|
|
||||||
// MCP (Model Context Protocol) 服务端:httplib 封装 + JSON-RPC 2.0 分发 + 静态 token 校验。
|
// MCP (Model Context Protocol) 服务端:httplib 封装 + JSON-RPC 2.0 分发 + 静态 token 校验。
|
||||||
// 与 CWebService 平级、互不依赖;默认禁用,经「扩展 → MCP设置」开启后监听
|
// 与 CWebService 平级、互不依赖;默认禁用,经「扩展 → MCP设置」开启后监听
|
||||||
// (默认 127.0.0.1:6544,仅本机回环)。见 docs/Mcp_Design.md。
|
// (默认 127.0.0.1:6544,仅本机回环)。见 docs/Mcp_Design.md。
|
||||||
@@ -194,6 +203,45 @@ public:
|
|||||||
// 屏幕子连接断开(OfflineProc 调用):擦除该子连接对应的会话 + 路由(幂等)。
|
// 屏幕子连接断开(OfflineProc 调用):擦除该子连接对应的会话 + 路由(幂等)。
|
||||||
void OnScreenControlClosed(context* subCtx);
|
void OnScreenControlClosed(context* subCtx);
|
||||||
|
|
||||||
|
// ===== P6:MCP 文件传输(download_file,V2 协议)=====
|
||||||
|
// 下载走 CMD_DOWN_FILES_V2 → 客户端新开流式子连接回传 COMMAND_SEND_FILE_V2 /
|
||||||
|
// COMMAND_FILE_COMPLETE_V2(dstClientID==0)。会话按 device_id 键控,单设备单传输;
|
||||||
|
// 复用 list_files 的 COMMAND_LIST_DRIVE → TOKEN_DRIVE_LIST 开文件管理器子链接下发命令。
|
||||||
|
void SetFileTransferEnabled(bool enabled) { m_fileTransferEnabled = enabled; }
|
||||||
|
bool IsFileTransferEnabled() const { return m_fileTransferEnabled; }
|
||||||
|
|
||||||
|
// 该 host 是否有进行中的文件传输会话(MessageHandle 分派守卫)。
|
||||||
|
bool IsFileTransferPending(uint64_t device_id);
|
||||||
|
|
||||||
|
// 登记文件传输会话(false = 该 host 已有文件会话或一次性挂起请求)。
|
||||||
|
bool BeginFileTransferPending(uint64_t device_id, const std::string& tool,
|
||||||
|
const std::string& localDir, const std::string& remotePath,
|
||||||
|
bool overwrite);
|
||||||
|
|
||||||
|
// 登记上传会话(false = 该 host 已有文件会话或一次性挂起请求)。upload 走主连接、
|
||||||
|
// 工具线程同步驱动 FileBatchTransferWorkerV2,会话仅为单设备单传输互斥标记
|
||||||
|
// (无 fmSubCtx/streamSubCtx,ClearFileTransfer 对空指针安全)。
|
||||||
|
bool BeginFileUpload(uint64_t device_id);
|
||||||
|
|
||||||
|
// TOKEN_DRIVE_LIST:识别 download_file,存文件管理器子链接并下发 CMD_DOWN_FILES_V2。
|
||||||
|
// 返回 true=已接管(保持子链接,收尾由 ClearFileTransfer);false=会话已清理(调用方 CancelIO)。
|
||||||
|
bool OnDownloadDriveList(uint64_t device_id, context* fmSubCtx);
|
||||||
|
|
||||||
|
// COMMAND_SEND_FILE_V2:解析 chunk,路径校验 + overwrite 判定 + 落盘(RecvFileChunkV2)。
|
||||||
|
void OnFileChunkV2(uint64_t device_id, context* streamSubCtx, const BYTE* buf, ULONG len);
|
||||||
|
|
||||||
|
// COMMAND_FILE_COMPLETE_V2:SHA-256 校验(HandleFileCompleteV2)+ 完成判定。
|
||||||
|
void OnFileCompleteV2(uint64_t device_id, const BYTE* buf, ULONG len);
|
||||||
|
|
||||||
|
// 工具线程:等待传输完成;true=成功(files 已填,error=0),false=失败/超时(error 填原因)。
|
||||||
|
// 无论成败,files 都会返回已见文件的落盘路径(成功=完整文件,失败=供调用方删半成品)。
|
||||||
|
// 调用方随后必须调 ClearFileTransfer 收尾(擦会话 + CancelIO 子链接)。
|
||||||
|
bool WaitFileTransferDone(uint64_t device_id, int timeoutMs,
|
||||||
|
std::vector<FileTransferEntry>& files, int& skipped, int& error);
|
||||||
|
|
||||||
|
// 收尾:擦会话 + 锁外 CancelIO 文件管理器/流式两条子链接(幂等)。
|
||||||
|
void ClearFileTransfer(uint64_t device_id);
|
||||||
|
|
||||||
// 安全配置(启动时由 CMy2015RemoteDlg 读 THIS_CFG 后设置)。
|
// 安全配置(启动时由 CMy2015RemoteDlg 读 THIS_CFG 后设置)。
|
||||||
void SetReadonly(bool readonly) { m_readonly = readonly; }
|
void SetReadonly(bool readonly) { m_readonly = readonly; }
|
||||||
void SetCmdWhitelist(const std::string& whitelist) { m_cmdWhitelist = whitelist; }
|
void SetCmdWhitelist(const std::string& whitelist) { m_cmdWhitelist = whitelist; }
|
||||||
@@ -272,10 +320,34 @@ private:
|
|||||||
std::map<uint64_t, ScreenCtrlSession> m_ScreenCtrlSessions; // device_id → 会话
|
std::map<uint64_t, ScreenCtrlSession> m_ScreenCtrlSessions; // device_id → 会话
|
||||||
std::map<context*, uint64_t> m_ScreenCtrlContextToDevice; // subCtx → device_id(OfflineProc 反查)
|
std::map<context*, uint64_t> m_ScreenCtrlContextToDevice; // subCtx → device_id(OfflineProc 反查)
|
||||||
|
|
||||||
|
// ===== P6:文件传输会话(受 m_FileXferMutex 保护;单设备单传输)=====
|
||||||
|
struct FileTransferSession {
|
||||||
|
std::string tool; // "download_file" / "upload_file"
|
||||||
|
std::string localDir; // download:本机保存目录(ANSI,结尾 '\')
|
||||||
|
std::string remotePath; // 客户端远程路径(ANSI,发 CMD_DOWN_FILES_V2 用)
|
||||||
|
bool overwrite = false; // 是否覆盖已存在文件
|
||||||
|
context* fmSubCtx = nullptr; // 文件管理器子链接(下发 CMD_DOWN_FILES_V2)
|
||||||
|
context* streamSubCtx = nullptr; // 流式子链接(首个 chunk 填,收尾 CancelIO)
|
||||||
|
uint64_t transferID = 0; // 客户端回填的传输会话 ID
|
||||||
|
uint32_t totalFiles = 0; // 客户端声明的文件总数(含目录项,见 F4)
|
||||||
|
uint32_t directoryCount = 0; // 目录项计数(目录不发 COMPLETE 包)
|
||||||
|
uint32_t filesDone = 0; // 已完成的文件数(含跳过的)
|
||||||
|
bool done = false; // 全部完成或出错
|
||||||
|
int error = 0; // 0=ok;8=哈希不匹配;1001=路径逃逸;1002=超时
|
||||||
|
std::map<uint32_t, FileTransferEntry> fileEntries; // fileIndex → {path,size,sha256}
|
||||||
|
std::set<uint32_t> skipIndexes; // overwrite=false 跳过的 fileIndex
|
||||||
|
std::vector<FileTransferEntry> skipped; // 跳过的文件(供输出)
|
||||||
|
time_t startAt = 0;
|
||||||
|
};
|
||||||
|
std::mutex m_FileXferMutex;
|
||||||
|
std::condition_variable m_FileXferCv;
|
||||||
|
std::map<uint64_t, FileTransferSession> m_FileXferSessions; // device_id → 会话
|
||||||
|
|
||||||
bool m_readonly = true;
|
bool m_readonly = true;
|
||||||
std::string m_cmdWhitelist;
|
std::string m_cmdWhitelist;
|
||||||
bool m_terminalEnabled = false; // 持久终端开关(默认关;要求 m_readonly=false)
|
bool m_terminalEnabled = false; // 持久终端开关(默认关;要求 m_readonly=false)
|
||||||
bool m_remoteControlEnabled = false; // 远程控制开关(默认关;要求 m_readonly=false)
|
bool m_remoteControlEnabled = false; // 远程控制开关(默认关;要求 m_readonly=false)
|
||||||
|
bool m_fileTransferEnabled = false; // 文件传输开关(默认关;download_file)
|
||||||
};
|
};
|
||||||
|
|
||||||
// 全局访问器(仿 WebService(),见 WebService.h 末尾)
|
// 全局访问器(仿 WebService(),见 WebService.h 末尾)
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ INT_PTR CMcpSettingsDlg::DoModal()
|
|||||||
{
|
{
|
||||||
USES_CONVERSION;
|
USES_CONVERSION;
|
||||||
CString title = _TR("MCP设置");
|
CString title = _TR("MCP设置");
|
||||||
BuildDialogTemplate(m_Template, T2CW(title), 320, 390);
|
BuildDialogTemplate(m_Template, T2CW(title), 320, 420);
|
||||||
InitModalIndirect((LPCDLGTEMPLATE)m_Template.data());
|
InitModalIndirect((LPCDLGTEMPLATE)m_Template.data());
|
||||||
return CDialog::DoModal();
|
return CDialog::DoModal();
|
||||||
}
|
}
|
||||||
@@ -142,6 +142,9 @@ BOOL CMcpSettingsDlg::OnInitDialog()
|
|||||||
m_btnRemoteControl.Create(_TR("启用远程控制(AI 操控桌面)"),
|
m_btnRemoteControl.Create(_TR("启用远程控制(AI 操控桌面)"),
|
||||||
WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_AUTOCHECKBOX,
|
WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_AUTOCHECKBOX,
|
||||||
r0, this, IDC_MCP_REMOTECONTROL);
|
r0, this, IDC_MCP_REMOTECONTROL);
|
||||||
|
m_btnFileTransfer.Create(_TR("启用文件传输(download_file)"),
|
||||||
|
WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_AUTOCHECKBOX,
|
||||||
|
r0, this, IDC_MCP_FILETRANSFER);
|
||||||
m_lblWhitelist.Create(_TR("命令白名单"), WS_CHILD | WS_VISIBLE, r0, this, (UINT)-1);
|
m_lblWhitelist.Create(_TR("命令白名单"), WS_CHILD | WS_VISIBLE, r0, this, (UINT)-1);
|
||||||
m_editWhitelist.Create(WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP |
|
m_editWhitelist.Create(WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP |
|
||||||
ES_MULTILINE | ES_AUTOVSCROLL | ES_WANTRETURN | WS_VSCROLL,
|
ES_MULTILINE | ES_AUTOVSCROLL | ES_WANTRETURN | WS_VSCROLL,
|
||||||
@@ -164,6 +167,7 @@ BOOL CMcpSettingsDlg::OnInitDialog()
|
|||||||
m_btnReadonly.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0));
|
m_btnReadonly.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0));
|
||||||
m_btnTerminal.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0));
|
m_btnTerminal.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0));
|
||||||
m_btnRemoteControl.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0));
|
m_btnRemoteControl.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0));
|
||||||
|
m_btnFileTransfer.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0));
|
||||||
m_lblWhitelist.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_editWhitelist.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0));
|
||||||
m_btnOK.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0));
|
m_btnOK.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0));
|
||||||
@@ -179,6 +183,7 @@ BOOL CMcpSettingsDlg::OnInitDialog()
|
|||||||
int readonly = THIS_CFG.GetInt("settings", "McpReadonly", 1);
|
int readonly = THIS_CFG.GetInt("settings", "McpReadonly", 1);
|
||||||
int terminal = THIS_CFG.GetInt("settings", "McpTerminal", 0);
|
int terminal = THIS_CFG.GetInt("settings", "McpTerminal", 0);
|
||||||
int remoteControl = THIS_CFG.GetInt("settings", "McpRemoteControl", 0);
|
int remoteControl = THIS_CFG.GetInt("settings", "McpRemoteControl", 0);
|
||||||
|
int fileTransfer = THIS_CFG.GetInt("settings", "McpFileTransfer", 0);
|
||||||
std::string whitelist = THIS_CFG.GetStr("settings", "McpCmdWhitelist", "");
|
std::string whitelist = THIS_CFG.GetStr("settings", "McpCmdWhitelist", "");
|
||||||
|
|
||||||
m_btnEnable.SetCheck(enabled ? BST_CHECKED : BST_UNCHECKED);
|
m_btnEnable.SetCheck(enabled ? BST_CHECKED : BST_UNCHECKED);
|
||||||
@@ -188,6 +193,7 @@ BOOL CMcpSettingsDlg::OnInitDialog()
|
|||||||
m_btnReadonly.SetCheck(readonly ? BST_CHECKED : BST_UNCHECKED);
|
m_btnReadonly.SetCheck(readonly ? BST_CHECKED : BST_UNCHECKED);
|
||||||
m_btnTerminal.SetCheck(terminal ? BST_CHECKED : BST_UNCHECKED);
|
m_btnTerminal.SetCheck(terminal ? BST_CHECKED : BST_UNCHECKED);
|
||||||
m_btnRemoteControl.SetCheck(remoteControl ? BST_CHECKED : BST_UNCHECKED);
|
m_btnRemoteControl.SetCheck(remoteControl ? BST_CHECKED : BST_UNCHECKED);
|
||||||
|
m_btnFileTransfer.SetCheck(fileTransfer ? BST_CHECKED : BST_UNCHECKED);
|
||||||
// 白名单存储为逗号分隔,展示为每行一条。
|
// 白名单存储为逗号分隔,展示为每行一条。
|
||||||
m_editWhitelist.SetWindowText(CString(WhitelistForDisplay(whitelist).c_str()));
|
m_editWhitelist.SetWindowText(CString(WhitelistForDisplay(whitelist).c_str()));
|
||||||
|
|
||||||
@@ -206,6 +212,7 @@ void CMcpSettingsDlg::OnOK()
|
|||||||
bool readonly = (m_btnReadonly.GetCheck() == BST_CHECKED);
|
bool readonly = (m_btnReadonly.GetCheck() == BST_CHECKED);
|
||||||
bool terminal = (m_btnTerminal.GetCheck() == BST_CHECKED);
|
bool terminal = (m_btnTerminal.GetCheck() == BST_CHECKED);
|
||||||
bool remoteControl = (m_btnRemoteControl.GetCheck() == BST_CHECKED);
|
bool remoteControl = (m_btnRemoteControl.GetCheck() == BST_CHECKED);
|
||||||
|
bool fileTransfer = (m_btnFileTransfer.GetCheck() == BST_CHECKED);
|
||||||
|
|
||||||
// 端口校验:1-65535
|
// 端口校验:1-65535
|
||||||
int port = atoi(CT2A(sPort));
|
int port = atoi(CT2A(sPort));
|
||||||
@@ -231,13 +238,14 @@ void CMcpSettingsDlg::OnOK()
|
|||||||
THIS_CFG.SetInt("settings", "McpReadonly", readonly ? 1 : 0);
|
THIS_CFG.SetInt("settings", "McpReadonly", readonly ? 1 : 0);
|
||||||
THIS_CFG.SetInt("settings", "McpTerminal", terminal ? 1 : 0);
|
THIS_CFG.SetInt("settings", "McpTerminal", terminal ? 1 : 0);
|
||||||
THIS_CFG.SetInt("settings", "McpRemoteControl", remoteControl ? 1 : 0);
|
THIS_CFG.SetInt("settings", "McpRemoteControl", remoteControl ? 1 : 0);
|
||||||
|
THIS_CFG.SetInt("settings", "McpFileTransfer", fileTransfer ? 1 : 0);
|
||||||
std::string whitelist = CT2A(sWhitelist);
|
std::string whitelist = CT2A(sWhitelist);
|
||||||
whitelist = NormalizeWhitelist(whitelist);
|
whitelist = NormalizeWhitelist(whitelist);
|
||||||
THIS_CFG.SetStr("settings", "McpCmdWhitelist", whitelist);
|
THIS_CFG.SetStr("settings", "McpCmdWhitelist", whitelist);
|
||||||
|
|
||||||
// 拆成两段可翻译的单行键,中间用 \r\n 连接(多行键无法在 INI 中表示)
|
// 拆成两段可翻译的单行键,中间用 \r\n 连接(多行键无法在 INI 中表示)
|
||||||
MessageBox(_TR("MCP 设置已保存。") + _T("\r\n") +
|
MessageBox(_TR("MCP 设置已保存。") + _T("\r\n") +
|
||||||
_TR("启用/端口/绑定地址/Token/只读/白名单/持久终端/远程控制的改动需重启程序生效。") + _T("\r\n") +
|
_TR("启用/端口/绑定地址/Token/只读/白名单/持久终端/远程控制/文件传输的改动需重启程序生效。") + _T("\r\n") +
|
||||||
_TR("持久终端与远程控制仅在只读模式关闭时生效。"),
|
_TR("持久终端与远程控制仅在只读模式关闭时生效。"),
|
||||||
_TR("提示"), MB_ICONINFORMATION);
|
_TR("提示"), MB_ICONINFORMATION);
|
||||||
|
|
||||||
@@ -278,12 +286,25 @@ void CMcpSettingsDlg::LayoutControls(int cx, int cy)
|
|||||||
m_btnRemoteControl.MoveWindow(margin, y, cx - margin * 2, 22);
|
m_btnRemoteControl.MoveWindow(margin, y, cx - margin * 2, 22);
|
||||||
y += 30;
|
y += 30;
|
||||||
|
|
||||||
|
m_btnFileTransfer.MoveWindow(margin, y, cx - margin * 2, 22);
|
||||||
|
y += 30;
|
||||||
|
|
||||||
const int whitelistH = 90;
|
const int whitelistH = 90;
|
||||||
m_lblWhitelist.MoveWindow(margin, y, labelW, rowH);
|
m_lblWhitelist.MoveWindow(margin, y, labelW, rowH);
|
||||||
m_editWhitelist.MoveWindow(margin + labelW, y - 2, cx - margin * 2 - labelW, whitelistH);
|
m_editWhitelist.MoveWindow(margin + labelW, y - 2, cx - margin * 2 - labelW, whitelistH);
|
||||||
y += whitelistH + gap;
|
y += whitelistH + gap;
|
||||||
|
|
||||||
int bottomY = cy - margin - btnH;
|
// 确定 / 取消按钮紧跟白名单下方,不再锚定到底部,避免白名单与按钮之间出现大段空白。
|
||||||
m_btnCancel.MoveWindow(cx - margin - btnW, bottomY, btnW, btnH);
|
m_btnCancel.MoveWindow(cx - margin - btnW, y, btnW, btnH);
|
||||||
m_btnOK.MoveWindow(cx - margin - btnW * 2 - gap, bottomY, btnW, btnH);
|
m_btnOK.MoveWindow(cx - margin - btnW * 2 - gap, y, btnW, btnH);
|
||||||
|
y += btnH + margin;
|
||||||
|
|
||||||
|
// 收缩对话框到内容实际高度并重新居中,消除底部空白。
|
||||||
|
if (y < cy) {
|
||||||
|
CRect wnd;
|
||||||
|
GetWindowRect(&wnd);
|
||||||
|
SetWindowPos(NULL, 0, 0, wnd.Width(), y + (wnd.Height() - cy),
|
||||||
|
SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);
|
||||||
|
CenterWindow();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -30,6 +30,7 @@ private:
|
|||||||
IDC_MCP_WHITELIST = 1006, // 命令白名单编辑框(多行,逗号/换行分隔,空 = 内置只读前缀)
|
IDC_MCP_WHITELIST = 1006, // 命令白名单编辑框(多行,逗号/换行分隔,空 = 内置只读前缀)
|
||||||
IDC_MCP_TERMINAL = 1007, // 「启用持久终端」复选框(全命令,无白名单,要求只读关)
|
IDC_MCP_TERMINAL = 1007, // 「启用持久终端」复选框(全命令,无白名单,要求只读关)
|
||||||
IDC_MCP_REMOTECONTROL = 1008, // 「启用远程控制」复选框(AI 操控桌面,要求只读关)
|
IDC_MCP_REMOTECONTROL = 1008, // 「启用远程控制」复选框(AI 操控桌面,要求只读关)
|
||||||
|
IDC_MCP_FILETRANSFER = 1009, // 「启用文件传输」复选框(download_file,不要求只读关)
|
||||||
};
|
};
|
||||||
|
|
||||||
CButton m_btnEnable;
|
CButton m_btnEnable;
|
||||||
@@ -38,6 +39,7 @@ private:
|
|||||||
CButton m_btnReadonly;
|
CButton m_btnReadonly;
|
||||||
CButton m_btnTerminal;
|
CButton m_btnTerminal;
|
||||||
CButton m_btnRemoteControl;
|
CButton m_btnRemoteControl;
|
||||||
|
CButton m_btnFileTransfer;
|
||||||
CStatic m_lblWhitelist;
|
CStatic m_lblWhitelist;
|
||||||
CEdit m_editWhitelist;
|
CEdit m_editWhitelist;
|
||||||
CButton m_btnOK, m_btnCancel;
|
CButton m_btnOK, m_btnCancel;
|
||||||
|
|||||||
@@ -46,7 +46,7 @@
|
|||||||
|
|
||||||
// 程序版本号 [建议格式: X.Y.Z]
|
// 程序版本号 [建议格式: X.Y.Z]
|
||||||
// 影响:关于对话框、标题栏
|
// 影响:关于对话框、标题栏
|
||||||
#define BRAND_VERSION "1.3.8"
|
#define BRAND_VERSION "1.3.9"
|
||||||
|
|
||||||
// 启动画面名称 [建议大写,更有 Logo 感]
|
// 启动画面名称 [建议大写,更有 Logo 感]
|
||||||
// 影响:启动画面 Logo 文字(大号艺术字体渲染)
|
// 影响:启动画面 Logo 文字(大号艺术字体渲染)
|
||||||
|
|||||||
@@ -1987,10 +1987,15 @@ MCP
|
|||||||
端口需为 1-65535 的数字=Port must be a number between 1 and 65535
|
端口需为 1-65535 的数字=Port must be a number between 1 and 65535
|
||||||
Token 不能为空=Token cannot be empty
|
Token 不能为空=Token cannot be empty
|
||||||
MCP 设置已保存。=MCP settings saved.
|
MCP 设置已保存。=MCP settings saved.
|
||||||
启用/端口/绑定地址/Token/只读/白名单/持久终端的改动需重启程序生效。=Changes to enable/port/bind/token/read-only/whitelist/persistent terminal take effect after restart.
|
启用/端口/绑定地址/Token/只读/白名单/持久终端/远程控制/文件传输的改动需重启程序生效。=Changes to enable/port/bind address/token/read-only/whitelist/persistent terminal/remote control/file transfer take effect after restart.
|
||||||
只读模式(禁命令执行)=Read-only mode (disable command execution)
|
只读模式(禁命令执行)=Read-only mode (disable command execution)
|
||||||
命令白名单=Command whitelist
|
命令白名单=Command whitelist
|
||||||
MCP命令执行=MCP command execution
|
MCP命令执行=MCP command execution
|
||||||
启用持久终端(全命令,无白名单)=Enable persistent terminal (full commands, no whitelist)
|
启用持久终端(全命令,无白名单)=Enable persistent terminal (full commands, no whitelist)
|
||||||
持久终端仅在只读模式关闭时生效。=Persistent terminal takes effect only when read-only mode is off.
|
持久终端与远程控制仅在只读模式关闭时生效。=Persistent terminal and remote control take effect only when read-only mode is off.
|
||||||
MCP持久终端=MCP persistent terminal
|
MCP持久终端=MCP persistent terminal
|
||||||
|
MCP远程控制=MCP remote control
|
||||||
|
MCP文件传输=MCP file transfer
|
||||||
|
Token=Token
|
||||||
|
启用远程控制(AI 操控桌面)=Enable remote control (AI controls the desktop)
|
||||||
|
启用文件传输(download_file)=Enable file transfer (download_file)
|
||||||
@@ -1978,10 +1978,15 @@ MCP
|
|||||||
端口需为 1-65535 的数字=連接埠需為 1-65535 的數字
|
端口需为 1-65535 的数字=連接埠需為 1-65535 的數字
|
||||||
Token 不能为空=Token 不能為空
|
Token 不能为空=Token 不能為空
|
||||||
MCP 设置已保存。=MCP 設定已儲存。
|
MCP 设置已保存。=MCP 設定已儲存。
|
||||||
启用/端口/绑定地址/Token/只读/白名单/持久终端的改动需重启程序生效。=啟用/連接埠/綁定位址/Token/唯讀/白名單/持久終端的變更需重新啟動程式後生效。
|
启用/端口/绑定地址/Token/只读/白名单/持久终端/远程控制/文件传输的改动需重启程序生效。=啟用/連接埠/綁定位址/Token/唯讀/白名單/持久終端/遠端控制/檔案傳輸的變更需重新啟動程式後生效。
|
||||||
只读模式(禁命令执行)=唯讀模式(禁命令執行)
|
只读模式(禁命令执行)=唯讀模式(禁命令執行)
|
||||||
命令白名单=命令白名單
|
命令白名单=命令白名單
|
||||||
MCP命令执行=MCP命令執行
|
MCP命令执行=MCP命令執行
|
||||||
启用持久终端(全命令,无白名单)=啟用持久終端(完整命令,無白名單)
|
启用持久终端(全命令,无白名单)=啟用持久終端(完整命令,無白名單)
|
||||||
持久终端仅在只读模式关闭时生效。=持久終端僅在唯讀模式關閉時生效。
|
持久终端与远程控制仅在只读模式关闭时生效。=持久終端與遠端控制僅在唯讀模式關閉時生效。
|
||||||
MCP持久终端=MCP持久終端
|
MCP持久终端=MCP持久終端
|
||||||
|
MCP远程控制=MCP遠端控制
|
||||||
|
MCP文件传输=MCP檔案傳輸
|
||||||
|
Token=Token
|
||||||
|
启用远程控制(AI 操控桌面)=啟用遠端控制(AI 操控桌面)
|
||||||
|
启用文件传输(download_file)=啟用檔案傳輸(download_file)
|
||||||
Reference in new issue
Block a user