From f4207f858662b064567a66004a6234c54927652c Mon Sep 17 00:00:00 2001 From: ETWen Date: Thu, 4 Jun 2026 15:05:36 +0800 Subject: [PATCH] docs: redesign Phase 9 Serial MCP as GUI-owned port + named pipe bridge GUI holds the COM port exclusively; ETTerms.SerialMcp becomes a thin client that bridges over a local named pipe (SerialBridgeServer). AI TX/RX flows through the GUI channel so it is visible live in the terminal view. Tools change from open/close to attach/detach. --- ARCHITECTURE.md | 89 +++++++++++++++++++++++++++++++------------------ CLAUDE.md | 8 ++--- 2 files changed, 60 insertions(+), 37 deletions(-) diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 88dcf1a..c31771a 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -32,7 +32,7 @@ ETTerms 是一個給工程師 / 韌體 / 硬體驗證人員用的**單一視窗 | 祕密儲存 | **Windows Credential Manager**(DPAPI / CredMan) | 連線密碼、SSH key passphrase,不落地明碼 | | PDU 控制(選用) | **SnmpSharpNet** | 沿用 MyTeraTerm PDU 控制(`pductrl` / `pduconnect`) | | 日誌 | 自製 **AppLogger**(從 MyTeraTerm 移植) | 檔案 + Debug 雙輸出 | -| AI / MCP 整合(選用) | **stdio MCP server**(官方 C# SDK `ModelContextProtocol`) | 獨立行程把 serial port 暴露成 AI 可呼叫工具(Kiro CLI / Claude CLI),見 [AI / MCP Integration](#ai--mcp-integrationserial-mcp-server) | +| AI / MCP 整合(選用) | **stdio MCP server**(官方 C# SDK `ModelContextProtocol`) | 獨立行程,但不自己開 port——經本機 named pipe 橋接到 GUI 持有的 serial session,把收發暴露成 AI 可呼叫工具(Kiro CLI / Claude CLI),見 [AI / MCP Integration](#ai--mcp-integrationserial-mcp-server) | | 打包 | `dotnet publish` + (選用)Inno Setup / MSIX | 單機安裝,current-user | > **與舊版 MyTeraTerm 的關鍵差異:** 舊版是把真正的 `ttermpro.exe`(TeraTerm)嵌進 Panel,靠 **com0com 虛擬 COM 對**攔截 serial 來跑腳本。ETTerms 改走**全原生**:SSH.NET 做 SSH、`System.IO.Ports` 做 serial、自繪 VT100 控制項做終端機畫面,**不再依賴外部 TeraTerm exe,也不再需要 com0com**。腳本引擎從「驅動 com0com bridge」改成「驅動原生 `ISessionChannel`」。 @@ -141,7 +141,8 @@ ETTerms/ │ ├── SerialChannel.cs # System.IO.Ports 實作 │ ├── ShellChannel.cs # Windows ConPTY 本機 Shell │ ├── SessionPage.cs # 一個分頁 = TerminalView + Channel + 狀態 - │ └── SessionManager.cs # 開 / 關 / 列舉所有 active session + │ ├── SessionManager.cs # 開 / 關 / 列舉所有 active session + │ └── SerialBridgeServer.cs# 🔜 本機 named pipe server:把 serial session 的讀寫橋接給 MCP(Phase 9) │ ├── Connections/ # ── 連線資料 ── │ ├── Connection.cs # 連線 metadata 模型 @@ -160,9 +161,10 @@ ETTerms/ ├── AppSettings.cs # 使用者偏好 (JSON, %LocalAppData%\ETTerms\settings.json) └── NativeTheme.cs # 深色標題列 (DWM) │ - └── ETTerms.SerialMcp/ # 🔜 Serial MCP server(stdio,給 AI agent 直接操作 serial) + └── ETTerms.SerialMcp/ # 🔜 Serial MCP server(stdio)——不自己開 port,經 named pipe 橋接 GUI ├── Program.cs # stdio MCP host 進入點 - ├── SerialTools.cs # serial_list / open / write / read / close 工具 + ├── SerialBridgeClient.cs # 連 GUI 的 named pipe,轉發 write / 接收 RX + ├── SerialTools.cs # serial_list / attach / write / read / detach 工具(轉發到 pipe) └── ETTerms.SerialMcp.csproj# net8.0 console + ModelContextProtocol SDK ``` @@ -354,40 +356,59 @@ ScriptRunner.RunAsync(scriptText, activeChannel) ## AI / MCP Integration(Serial MCP Server) -> 讓 **Kiro CLI / Claude CLI** 等 AI agent 直接對 serial port 下指令、讀輸出。ETTerms 額外提供一支獨立的 **stdio MCP server**(`src/ETTerms.SerialMcp/`),把 serial port 包成 AI 可呼叫的工具。它與 WinForms 主程式**各自獨立行程**:由 MCP client(Kiro CLI / Claude CLI)啟動並維持整個 session 存活,因此能**持續持有 COM port**——連線狀態可跨多次工具呼叫保留,也能接收裝置主動推送的非同步輸出。 +> 讓 **Kiro CLI / Claude CLI** 等 AI agent 收發 serial,**且使用者能在 ETTerms GUI 即時看到 AI 的每筆收發**。 +> +> **關鍵設計:COM port 由 GUI 唯一持有,MCP server 不自己開 port。** 一個 COM port 同一時間只能被一個行程開啟;若讓 MCP server 自己開,GUI 就無法同時開、使用者也看不到。因此改成 **GUI 當 port 的唯一擁有者**,在 GUI 內跑一支本機 **named pipe server**(`SerialBridgeServer`);獨立的 `ETTerms.SerialMcp`(由 Kiro/Claude CLI 啟動)退化成**瘦客戶端**,所有 `write` / `read` 都經 pipe 轉發給 GUI,由 GUI 代為讀寫實體 port。 -**為何要獨立常駐行程?** CLI agent 的每條 shell 指令都是一個新行程,`open→write→read` 無法跨呼叫保留狀態(port 一關就斷)。常駐的 MCP server 才能維持一條連線、累積 RX。 +### 全閉迴路(推薦用法) + +整個迴路可全部跑在 ETTerms GUI 內,使用者一邊看、AI 一邊操作: ``` -┌── Kiro CLI / Claude CLI (MCP client) ──┐ -│ AI 呼叫工具:serial_open / write... │ -└───────────────┬─────────────────────────┘ - │ stdio (JSON-RPC 2.0) -┌───────────────┴─────────────────────────┐ -│ ETTerms.SerialMcp (常駐行程) │ -│ 背景 reader 累積 RX → serial_read 取出 │ -│ │ System.IO.Ports.SerialPort │ -└────────┼─────────────────────────────────┘ - │ ← COM 互斥:與 GUI 不可同開同一 port → - [ 實體 COM port / UART / 開發板 ] +ETTerms GUI(單一行程,唯一開 COM3 的人) +│ +├─ Tab1: Serial 連線 ── SerialChannel 實體持有 COM3 +│ TerminalView 即時顯示(AI 的 TX 以 [AI] 標色,RX 照常顯示) +│ +├─ SerialBridgeServer(本機 named pipe: \\.\pipe\etterms-serial) +│ ▲ write 轉發 / RX 廣播 +│ │ +└─ Tab2: PowerShell (ConPTY) 跑 kiro-cli + └─ kiro 啟動子行程 ETTerms.SerialMcp(stdio / JSON-RPC) + └─ 不開 COM,連上面的 pipe → 收發都流經 GUI 的 channel ``` +**資料流:** +- **AI 送資料:** `serial_write` → pipe → GUI `SerialChannel.Write()` 送出 COM3,**同時把這段 echo 進 Tab1 TerminalView(`[AI]` 標色)** → 使用者看得到 AI 打了什麼。 +- **裝置回資料:** COM3 RX → GUI 一邊顯示在 Tab1、一邊經 pipe 廣播 → `serial_read` 取出 → AI 讀到。 + +因為 RX/TX 物理上都流經 GUI 的 channel,**使用者在 GUI 看到的就是 AI 看到的**,完全同步。 + ### 暴露的工具 | 工具 | 參數 | 說明 | |------|------|------| -| `serial_list` | — | 列出可用 COM port(`SerialPort.GetPortNames()`) | -| `serial_open` | portName, baudRate, dataBits, parity, stopBits, handshake, newLine | 開啟並持有 port(啟動背景 reader 累積 RX) | -| `serial_write` | text, appendNewLine? | 送出文字(可選附加換行) | -| `serial_read` | waitFor?, timeoutMs? | 取出 RX 緩衝;可等待特定字串或逾時 | -| `serial_close` | — | 關閉 port | +| `serial_list` | — | 列出 **GUI 目前開著的 serial session**(名稱 + COM port + baud),供 AI 選定要操作哪一個 | +| `serial_attach` | portName \| sessionName | 經 pipe 綁定到 GUI 某個已開啟的 serial session(之後 read/write 都對它) | +| `serial_write` | text, appendNewLine? | 經 pipe 請 GUI 對綁定的 session 送出文字(GUI 同步 echo 到 TerminalView) | +| `serial_read` | waitFor?, timeoutMs? | 取出該 session 累積的 RX;可等待特定字串或逾時 | +| `serial_detach` | — | 解除綁定(**不關閉 GUI 的 port**,GUI 仍持有) | + +> 與舊版規劃的差異:不再有「MCP 自己 `serial_open` / `serial_close` 實體 port」;改為 `serial_attach` / `serial_detach` 綁定 / 解除 GUI 既有 session。port 的開關一律在 GUI 操作。 + +### named pipe 協議(精簡) + +- **傳輸:** Windows named pipe(`\\.\pipe\etterms-serial`),純本機、不開網路埠。 +- **訊息:** 換行分隔的 JSON,例:`{"op":"write","session":"COM3","data":"...","newline":true}` / `{"op":"read","session":"COM3","waitFor":"OK","timeoutMs":3000}` / `{"op":"list"}`。 +- **RX 推送:** GUI 主動把 RX 以 `{"op":"rx","session":"COM3","data":"..."}` 推給已連線的 client,client 端累積成 buffer 供 `serial_read` 消費。 +- **找不到 session:** AI `attach` / `write` 一個 GUI 沒開的 port 時,回明確錯誤(要求使用者先在 GUI 開啟)。 ### 設計重點 -- **技術:** .NET 8 console(`net8.0`,無 WinForms)+ 官方 C# MCP SDK(`ModelContextProtocol`),stdio / JSON-RPC 2.0。 -- **參數語意:** 直接用 `System.IO.Ports.SerialPort`,與主程式的 `SerialSettings` 同一組參數(PortName / BaudRate / DataBits / Parity / StopBits / Handshake / NewLine)。 -- **COM 互斥:** MCP server 開了某 port 時,ETTerms GUI 不可同時開同一 port(反之亦然)——一個 COM 同時只能被一個行程開啟。 -- **安全:** 本機、無雲、不碰 credential;僅操作硬體 serial。 +- **技術:** `ETTerms.SerialMcp` 為 .NET 8 console(`net8.0`,無 WinForms)+ 官方 C# MCP SDK(`ModelContextProtocol`),stdio / JSON-RPC;GUI 端的 `SerialBridgeServer` 用 `System.IO.Pipes`。 +- **port 擁有權單一化:** 實體 `SerialPort` 只有 GUI 開,根除「兩個行程搶同一 COM」的問題。 +- **可視性:** AI 的 TX 在 GUI 以 `[AI]` 標色,與使用者手打的輸入區分;RX 兩邊同源。 +- **安全:** 本機、無雲、不碰 credential;pipe 僅限本機行程,只搬 serial bytes。 ### 註冊(Kiro CLI) @@ -396,7 +417,7 @@ kiro-cli mcp add --name serial --command dotnet ` --args "run --project src\ETTerms.SerialMcp\ETTerms.SerialMcp.csproj" ``` -或寫進 agent.json 的 `mcpServers`;Claude CLI 則用其對應的 `mcpServers` 設定。註冊後直接對 AI 說「列出 COM port、開 COM3 115200、送 AT 看回應」即可。 +或寫進 agent.json 的 `mcpServers`;Claude CLI 則用其對應的 `mcpServers` 設定。**使用前提:先在 ETTerms GUI 開好要操作的 serial 連線**,AI 才能 `serial_attach` 上去。註冊後即可對 AI 說「列出目前 serial session → 接上 COM3 → 送指令看回應」。 --- @@ -563,14 +584,16 @@ dotnet publish src\ETTerms\ETTerms.csproj -c Release -r win-x64 --self-contained - [ ] `dotnet publish` + Inno Setup / MSIX 安裝程式(待使用者指示) **驗收條件:** ✅ PDU 指令可用;Settings 重啟保留;Local Shell 行為正常(ConPTY);SFTP 可瀏覽遠端目錄。打包待後續。 -### Phase 9 — AI 整合:Serial MCP Server(工作量:S)🔜 規劃中 -**目標:** 提供獨立 stdio MCP server,讓 Kiro CLI / Claude CLI 等 AI agent 直接對 serial port 下指令、讀輸出。 +### Phase 9 — AI 整合:Serial MCP Server(GUI 橋接模式)(工作量:M)🔜 規劃中 +**目標:** 讓 Kiro CLI / Claude CLI 等 AI agent 收發 serial,且**使用者能在 GUI 即時看到 AI 的收發**。COM port 由 GUI 唯一持有,MCP 經本機 named pipe 橋接(見 [AI / MCP Integration](#ai--mcp-integrationserial-mcp-server))。 **包含:** +- [ ] GUI:`Sessions/SerialBridgeServer.cs`——本機 named pipe server,暴露 list / attach / write / read,並把 serial session 的 RX 廣播給 client +- [ ] GUI:TerminalView 支援把 MCP 來源的 TX 以 `[AI]` 標色 echo,使用者可區分 AI 與手打輸入 - [ ] `src/ETTerms.SerialMcp/`:.NET 8 console + 官方 C# MCP SDK(`ModelContextProtocol`),stdio / JSON-RPC -- [ ] 工具:`serial_list` / `serial_open` / `serial_write` / `serial_read`(含 `waitFor` + `timeout`)/ `serial_close` -- [ ] 常駐持有 COM port + 背景 reader 累積 RX(跨呼叫保留狀態、可收 async 輸出) -- [ ] 註冊說明(`kiro-cli mcp add` / agent.json `mcpServers`)寫入 README -**驗收條件:** 在 Kiro CLI 註冊後,能透過 AI 對話「列 COM port → 開 COM3 115200 → 送指令 → 讀回應」完成一輪 serial 互動;同一 COM port 不與 GUI 同時開啟。 +- [ ] `SerialBridgeClient.cs`:連 GUI pipe、轉發 write、累積 RX +- [ ] 工具:`serial_list` / `serial_attach` / `serial_write` / `serial_read`(含 `waitFor` + `timeoutMs`)/ `serial_detach` +- [ ] 註冊說明(`kiro-cli mcp add` / agent.json `mcpServers`)寫入 README,含「需先在 GUI 開好 port」前提 +**驗收條件:** GUI 開一條 Serial(COM3)→ 另一分頁 PowerShell 跑 kiro → AI 經 MCP `serial_attach` COM3 → `serial_write` 送指令、`serial_read` 讀回應,**整個過程在 GUI Tab1 即時可見(AI 的 TX 有 `[AI]` 標色)**;全程只有 GUI 開該 port。 --- diff --git a/CLAUDE.md b/CLAUDE.md index d8f69f9..433ee52 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -8,7 +8,7 @@ ETTerms 是一個 **C# .NET 8 WinForms** 的原生 Windows 終端機工作台, **開發策略:GUI 先行** — 先把視窗外殼 + 分頁 + 連線清單做出來,再逐步補 Serial → SSH → VT100 → 腳本引擎 → Settings/About → PDU/Shell/SFTP。 -**進度:** Phase 1–5 ✅、Phase 6 ✅(TTL 引擎 + Group 同步,SSH 待驗收)、Phase 7 ✅(Settings/About)、Phase 8 ✅(PDU + Shell/ConPTY + SFTP + Settings 擴充)、Phase 9 🔜(Serial MCP server,讓 AI 直接操作 serial)。打包待指示。 +**進度:** Phase 1–5 ✅、Phase 6 ✅(TTL 引擎 + Group 同步,SSH 待驗收)、Phase 7 ✅(Settings/About)、Phase 8 ✅(PDU + Shell/ConPTY + SFTP + Settings 擴充)、Phase 9 🔜(Serial MCP server:**GUI 持有 COM port,MCP 經本機 named pipe 橋接**,AI 收發的資料即時顯示在 GUI)。打包待指示。 **v0.1.2:** 新增 `sprintf2`(TeraTerm 相容 C printf 格式化);`wait` 改為命中關鍵字後須等裝置安靜(`SettleMs` 預設 300ms)才接受並取「最後一次」出現,排除輸出中途的指令回顯(避免腳本搶跑)。 @@ -23,7 +23,7 @@ ETTerms 是一個 **C# .NET 8 WinForms** 的原生 Windows 終端機工作台, - **連線儲存:** SQLite(`Microsoft.Data.Sqlite`) - **密碼儲存:** Windows Credential Manager(不落地明碼) - **PDU:** SnmpSharpNet(iPoMan II/III via SNMP) -- **AI / MCP(選用):** 獨立 stdio MCP server(`ETTerms.SerialMcp`,官方 C# SDK `ModelContextProtocol`)把 serial port 暴露給 Kiro CLI / Claude CLI +- **AI / MCP(選用):** stdio MCP server(`ETTerms.SerialMcp`,官方 C# SDK `ModelContextProtocol`)。**不自己開 COM port**,而是經本機 named pipe 接上 GUI 持有的 serial session,把 serial 收發暴露給 Kiro CLI / Claude CLI;AI 的 TX/RX 同步顯示在 GUI - **設定持久化:** JSON → `%LocalAppData%\ETTerms\settings.json` ## 常用指令 @@ -57,7 +57,7 @@ kiro-cli mcp add --name serial --command dotnet --args "run --project src\ETTerm - 🚫 **不嵌 TeraTerm、不依賴 com0com** —— ETTerms 走全原生(這是與舊版 MyTeraTerm 的關鍵差異)。 - 🚫 不要把 `For_AI/` 內容 commit 進 git。 - ⚠️ Serial COM port 同時只能被一個 session 開啟,開啟前檢查可用性。 -- ⚠️ Serial MCP server(`ETTerms.SerialMcp`)與 GUI **不可同時開同一個 COM port**;AI 操作該 port 前,先關掉 GUI 對它的連線(反之亦然)。 +- ⚠️ COM port 由 **GUI 唯一持有**;Serial MCP server(`ETTerms.SerialMcp`)**不自己開 port**,而是經本機 named pipe 接上 GUI 已開啟的 serial session 來收發。AI 操作前該 port 必須已在 GUI 開啟(pipe 找不到對應 session 就回錯誤)。 - ⚠️ VT 相容性以常見情境(VT100 / 常見 ANSI)為主,冷門 escape 後補,不阻塞 GUI 進度。 - ⚠️ 本專案**無伺服端祕密 / 無 DB 密碼 / 無 EC2 / 無 VM**,因此不套用 AWS / VirtualBox 部署流程。 - ⚠️ Group 同步指令(`waitall` / `sendlnall` / `sendlngroup`)**只能在 Run Group 模式**使用;`▶ Script` 和 `▶ Run All` 須拒絕含這些指令的腳本。 @@ -65,6 +65,6 @@ kiro-cli mcp add --name serial --command dotnet --args "run --project src\ETTerm ## 資料夾用途 - **`src/ETTerms/`** — 主應用程式(WinForms 視窗外殼 + 連線 / 終端機 / 腳本引擎)。 -- **`src/ETTerms.SerialMcp/`** — 🔜 獨立 stdio MCP server(給 AI agent 直接操作 serial),與 WinForms 主程式各自獨立行程;net8.0 console + `ModelContextProtocol` SDK。 +- **`src/ETTerms.SerialMcp/`** — 🔜 stdio MCP server(給 AI agent 收發 serial)。獨立行程,但**不直接開 COM port**:經本機 named pipe 連到 GUI 的 `SerialBridgeServer`,由 GUI 代為讀寫實體 port;net8.0 console + `ModelContextProtocol` SDK。 - **`For_AI/`** — AI 協作素材與**參考專案**(`KKTerm-main` UI 參考、`MyTeraTerm` Script 參考)。整個資料夾 gitignored,僅供開發對照。 - 本專案**無 `secret/` 資料夾**:沒有伺服端祕密 / DB 密碼 / compile-time secret,連線密碼一律走 Windows Credential Manager。