---
date: 2025-11-20T22:30:00+08:00
researcher: Claude (Sonnet 4.5)
git_commit: ebad08ed897d9be0701f628734b9102c05f46993
branch: ai
repository: FWAuto
topic: "統一 Chat 介面架構：將所有互動整合到單一對話介面"
tags: [research, architecture, chat, cli, unified-interface, slash-commands]
status: complete
last_updated: 2025-11-20
last_updated_by: Claude (Sonnet 4.5)
last_updated_note: "Finalized all Open Questions decisions: command parameters parsing, failure handling, context injection, and feat integration"
---

# Research: 統一 Chat 介面架構

**Date**: 2025-11-20T22:30:00+08:00
**Researcher**: Claude (Sonnet 4.5)
**Git Commit**: ebad08ed897d9be0701f628734b9102c05f46993
**Branch**: ai
**Repository**: FWAuto

## Research Question

如何將 FWAuto 改造為統一的 Chat 介面，讓使用者透過對話方式執行所有功能（build、deploy、log 等），同時保持架構簡潔（KISS 原則）？

### 設計需求

1. **CLI 命令策略**：保留獨立命令作為快捷方式（`fwauto build`），`fwauto` 無參數時進入 chat 模式
2. **命令辨識**：只用 Slash 命令（`/build`, `/deploy`, `/log`）來明確區分命令和對話
3. **執行模式**：同步阻塞（執行命令時暫停對話，完成後繼續）
4. **技術實作**：在 chat_node 中直接調用其他 node 函數（`build_node(state)`）

## Summary

研究發現 FWAuto **已經具備統一介面的基礎架構**（`_interactive_mode()` 在 cli.py:459-527），只需進行以下調整：

1. **將 `fwauto chat` 命令改造為統一介面**
   - 新增 slash 命令解析器（`/build`, `/deploy`, `/log` 等）
   - 在 chat loop 中直接調用 node 函數
   - 其他輸入轉給 AI 對話

2. **保留現有的獨立 CLI 命令**
   - `fwauto build`, `fwauto deploy` 等快速執行模式維持不變
   - 適合 CI/CD 和腳本使用

3. **技術可行性分析**
   - ✅ 所有 nodes 都是同步函數，可直接在 async chat loop 中調用
   - ✅ State 傳遞機制簡單：`result = node_func(current_state)` → 更新 state
   - ✅ 不需要修改 workflow routing 或 graph.py

### 架構對比

**現況**（多入口）:
```
fwauto build   → workflow → build_node → END
fwauto deploy  → workflow → deploy_node → END
fwauto log     → workflow → log_node → END
fwauto chat    → workflow → chat_node → END
```

**提議**（統一 Chat 介面）:
```
fwauto (無參數) → chat_node(while loop):
  - /build  → build_node(state) → 顯示結果 → 繼續對話
  - /deploy → deploy_node(state) → 顯示結果 → 繼續對話
  - /log    → log_node(state) → 顯示結果 → 繼續對話
  - 其他    → AI 對話 → 繼續對話
  - exit    → 結束

fwauto build   → 維持現狀（快速執行，非互動）
```

## Detailed Findings

### 1. 現有架構分析

#### CLI 入口點（cli.py）

**位置**: src/fwauto/cli.py:529-535

```python
def main():
    """Entry point for the CLI."""
    # Check if running in interactive mode (no arguments)
    if len(sys.argv) == 1:
        _interactive_mode()  # 已有互動模式！
    else:
        app()
```

**重要發現**：FWAuto 已經有一個互動模式（`_interactive_mode()`），特性：
- ASCII 藝術標題 (cli.py:461-475)
- 使用 `prompt_toolkit` 支援 CJK 輸入 (cli.py:493)
- 使用 `shlex.split()` 解析命令 (cli.py:505)
- 透過 Click 的 context 調用 Typer 命令 (cli.py:508-510)

**現有流程**：
```
fwauto (無參數)
  ↓
_interactive_mode()
  ↓
while True:
  user_input = get_input("fwauto>")
  args = shlex.split(user_input)  # 解析命令
  click_app.invoke(ctx)           # 調用 Typer 命令
```

這個機制可以重用！

#### Chat Node 實作（chat.py）

**位置**: src/fwauto/nodes/chat.py:26-115

**關鍵特性**：
1. 使用 Thread 隔離執行 (chat.py:78-114)
2. 長連線 `ClaudeSDKClient` (chat.py:132)
3. `while True` 對話循環 (chat.py:137-177)
4. 使用 `prompt_toolkit` 支援 CJK (chat.py:126-130)

**當前簽名**：
```python
def chat_node(state: FirmwareState) -> dict[str, Any]:
    """Interactive chat node using ClaudeSDKClient."""
```

### 2. 所有 Nodes 的統一簽名

**位置**: src/fwauto/nodes/*.py

所有 nodes 都遵循相同的函數簽名：

```python
def build_node(state: FirmwareState) -> dict[str, Any]:
def deploy_node(state: FirmwareState) -> dict[str, Any]:
def log_node(state: FirmwareState) -> dict[str, Any]:
def ai_brain_node(state: FirmwareState) -> dict[str, Any]:
def chat_node(state: FirmwareState) -> dict[str, Any]:
def init_project_node(state: FirmwareState) -> dict[str, Any]:
```

**重要發現**：
- ✅ 所有都是**同步函數**
- ✅ 輸入/輸出格式一致
- ✅ 可以直接在 chat_node 中調用：`result = build_node(state)`

### 3. State 更新機制

**位置**: src/fwauto/state.py:6-77

`FirmwareState` 是 TypedDict，nodes 返回 partial updates：

```python
# Node 返回部分更新
def build_node(state: FirmwareState) -> dict[str, Any]:
    return {
        **state,  # 保留所有現有欄位
        "build_status": "success",  # 更新特定欄位
        "build_log": "...",
    }

# 在 chat loop 中更新 state
current_state = {**current_state, **result}
```

**優點**：
- 簡單的字典合併
- 不需要特殊的 state 管理機制
- 與 LangGraph workflow 的 state 更新模式一致

### 4. Slash 命令解析機制設計

目前 FWAuto **沒有**現成的 slash 命令解析器，需要新建。

**設計方案**：

```python
# 新增 src/fwauto/slash_commands.py

import re
from typing import Literal

SlashCommand = Literal["build", "deploy", "log", "run", "feat"]

def parse_slash_command(user_input: str) -> tuple[SlashCommand | None, list[str]]:
    """
    解析 slash 命令。

    Args:
        user_input: 使用者輸入

    Returns:
        (command, args) tuple
        - command: 命令名稱（None 表示不是 slash 命令）
        - args: 命令參數列表

    Examples:
        "/build" → ("build", [])
        "/log 有任何 error 嗎?" → ("log", ["有任何 error 嗎?"])
        "/deploy --binary-args on" → ("deploy", ["--binary-args", "on"])
        "如何使用 build?" → (None, [])
    """
    # 檢查是否以 "/" 開頭
    if not user_input.startswith("/"):
        return (None, [])

    # 移除開頭的 "/"
    content = user_input[1:].strip()

    # 使用 shlex 解析（支援引號）
    import shlex
    parts = shlex.split(content)

    if not parts:
        return (None, [])

    command = parts[0].lower()
    args = parts[1:]

    # 驗證命令是否合法
    valid_commands = ["build", "deploy", "log", "run", "feat"]
    if command not in valid_commands:
        return (None, [])

    return (command, args)
```

### 5. Chat Node 中調用其他 Nodes

**技術可行性**：✅ 完全可行

**原因**：
1. **所有 nodes 都是同步函數**：可以直接在 async 環境中調用同步函數（Python 允許）
2. **State 獨立於 workflow**：不需要透過 LangGraph routing
3. **錯誤處理獨立**：每個 node 負責自己的錯誤處理

**示例實作**（在 `_run_chat_loop` 中）：

```python
async def _run_chat_loop(options: ClaudeAgentOptions, chat_logger, initial_state):
    """Execute async interactive chat loop with slash commands."""
    session = PromptSession(...)
    current_state = initial_state  # 維護當前 state

    async with ClaudeSDKClient(options=options) as client:
        await client.connect()

        while True:
            user_input = await session.prompt_async()

            # 解析 slash 命令
            command, args = parse_slash_command(user_input)

            if command:
                # 執行 FWAuto 命令
                console.print(f"[cyan]⚡ 執行命令: {command}[/cyan]")

                if command == "build":
                    from ..nodes.build import build_node
                    result = build_node(current_state)
                    current_state = {**current_state, **result}

                    # 顯示結果
                    if result["build_status"] == "success":
                        console.print("[green]✅ Build 成功[/green]")
                    else:
                        console.print("[red]❌ Build 失敗[/red]")
                        console.print(result.get("build_log", ""))

                elif command == "deploy":
                    # 類似處理...
                    pass

                elif command == "log":
                    # 處理 log 命令的參數
                    if not args:
                        console.print("[yellow]⚠️ 請提供分析問題[/yellow]")
                        continue

                    prompt = " ".join(args)
                    current_state["user_prompt"] = prompt

                    from ..nodes.log import log_node
                    result = log_node(current_state)
                    current_state = {**current_state, **result}

                console.print()  # 空行分隔
                continue

            # 一般對話（非 slash 命令）
            await client.query(user_input)
            async for msg in client.receive_response():
                print_ai_message(msg, prefix="💬")
```

### 6. 與現有 Interactive Mode 的關係

**位置**: src/fwauto/cli.py:459-527

**現有 `_interactive_mode()` 特性**：
- 顯示 ASCII 藝術標題
- 使用 `get_input()` 獲取輸入（支援 CJK）
- 使用 `shlex.split()` 解析命令
- 透過 Click context 調用 Typer 命令

**兩種互動模式對比**：

| 特性 | `_interactive_mode()` | 新 `chat_node` |
|------|----------------------|----------------|
| 觸發方式 | `fwauto` 無參數 | `fwauto` 無參數 |
| 輸入解析 | shlex.split | slash command parser |
| 命令執行 | Click context invoke | 直接調用 node 函數 |
| 對話功能 | ❌ 無 AI 對話 | ✅ 支援 AI 對話 |
| Workflow | ✅ 完整 workflow | ⚠️ 繞過 workflow |

**設計選擇**：
- **選項 A**：移除 `_interactive_mode()`，讓 `fwauto` 無參數直接進入 `chat_node`
- **選項 B**：保留 `_interactive_mode()` 作為「command palette」，`fwauto chat` 為 AI 對話
- **選項 C**：融合兩者，讓 `_interactive_mode()` 支援 AI 對話

**推薦**：選項 A（移除 `_interactive_mode()`）
- 理由：統一介面，避免混淆
- 使用者只需記住一個入口：`fwauto`
- 保留獨立命令作為快捷方式

### 7. CLI 改造方案

**位置**: src/fwauto/cli.py:529-535

**當前**：
```python
def main():
    if len(sys.argv) == 1:
        _interactive_mode()  # 舊的互動模式
    else:
        app()
```

**改造後**：
```python
def main():
    if len(sys.argv) == 1:
        # 進入統一 chat 介面
        _run_unified_chat()
    else:
        app()  # 保留獨立命令

def _run_unified_chat():
    """統一 chat 介面（取代舊的 _interactive_mode）"""
    console.print("[cyan]💬 FWAuto Chat Interface[/cyan]")
    console.print("[dim]輸入 /build, /deploy, /log 執行命令，其他為 AI 對話[/dim]\n")

    # 初始化 state
    state: FirmwareState = {
        "user_prompt": "",
        "project_root": "",
        "project_initialized": False,
        # ... 其他欄位
        "execution_mode": "chat",
        "command": "chat",
    }

    # 直接調用 chat_node（它會處理 slash 命令和 AI 對話）
    from .nodes.chat import chat_node
    chat_node(state)
```

## Code References

| 檔案路徑 | 行號 | 說明 |
|---------|------|------|
| `src/fwauto/cli.py` | 529-535 | CLI main 入口點 |
| `src/fwauto/cli.py` | 459-527 | 現有的 `_interactive_mode()` |
| `src/fwauto/cli.py` | 395-436 | `chat` 命令定義 |
| `src/fwauto/nodes/chat.py` | 26-115 | chat_node 主邏輯 |
| `src/fwauto/nodes/chat.py` | 117-190 | `_run_chat_loop` async 函數 |
| `src/fwauto/nodes/build.py` | 21-133 | build_node 函數 |
| `src/fwauto/nodes/deploy.py` | 54+ | deploy_node 函數 |
| `src/fwauto/nodes/log.py` | 22+ | log_node 函數 |
| `src/fwauto/state.py` | 6-77 | FirmwareState 定義 |
| `src/fwauto/graph.py` | 16-190 | Workflow routing（不需修改） |

## Architecture Insights

### 設計模式

#### 1. Unified Interface Pattern（統一介面模式）

**概念**：
- 所有功能透過單一入口點存取
- 使用自然語言（slash 命令）作為介面
- 系統作為「通用 AI 助手」而非工具集合

**優點**：
- 降低學習曲線（只需記住一個入口）
- 流暢的使用者體驗（連續對話中執行多個操作）
- 上下文感知（AI 可以理解先前的對話）

**類似案例**：
- Slack 的 slash commands
- Discord bot commands
- VSCode 的 Command Palette (Ctrl+Shift+P)

#### 2. Command vs Conversation Separation（命令與對話分離）

**設計決策**：使用 `/` 前綴明確區分

**優點**：
- 使用者可以討論「如何使用 build」而不會觸發 build
- AI 不需要進行意圖識別（降低複雜度）
- 錯誤率低（明確的命令觸發）

#### 3. Synchronous Command Execution in Async Loop（async 環境中同步執行命令）

**技術發現**：
- Python 允許在 async 函數中直接調用同步函數
- 命令執行時會阻塞事件循環，但這是**期望的行為**（使用者等待結果）
- 不需要 `asyncio.to_thread()` 或其他複雜機制

**優點**：
- 簡單直接（KISS 原則）
- 使用者體驗符合預期（執行命令時等待）
- 不需要引入後台任務管理

### 架構影響分析

#### 需要修改的檔案

1. **src/fwauto/cli.py** - 修改 `main()` 入口點
   - 移除或重構 `_interactive_mode()`
   - `fwauto` 無參數時直接調用 chat_node

2. **src/fwauto/nodes/chat.py** - 新增 slash 命令處理
   - 在 `_run_chat_loop()` 中新增命令解析邏輯
   - 根據命令調用對應的 node 函數
   - 更新 state 並顯示結果

3. **新增 src/fwauto/slash_commands.py** - Slash 命令解析器
   - `parse_slash_command()` 函數
   - 命令驗證和參數解析

4. **src/fwauto/prompts/chat.md** - 更新 system prompt
   - 告知 AI slash 命令的存在
   - 引導使用者使用 slash 命令執行操作

#### 不需要修改的部分

1. **src/fwauto/graph.py** - Workflow routing
   - 保持不變，仍然支援獨立命令的 workflow 執行

2. **src/fwauto/nodes/*.py** - 其他 nodes
   - 函數簽名不變
   - 可以直接被 chat_node 調用

3. **src/fwauto/state.py** - State 定義
   - 現有欄位足夠使用
   - 不需要新增 chat 特定欄位

### 技術可行性總結

| 層面 | 可行性 | 複雜度 | 說明 |
|------|--------|--------|------|
| Slash 命令解析 | ✅ 完全可行 | 低 | 使用 regex + shlex |
| 在 chat 中調用 nodes | ✅ 完全可行 | 低 | 直接函數調用 |
| State 管理 | ✅ 完全可行 | 低 | 字典合併 |
| CLI 入口改造 | ✅ 完全可行 | 低 | 修改 main() |
| 與現有功能相容 | ✅ 完全可行 | 低 | 保留獨立命令 |

**總體評估**：⭐⭐⭐⭐⭐ 高度可行，符合 KISS 原則

## Open Questions → 決策結果

### 1. 如何處理需要額外參數的命令？

**例如**：`/deploy --binary-args "on"` 或 `/log --log-path logs/uart.log`

**決策**：✅ 解析完整的命令列參數（2025-11-20）

**實施方式**：
- 使用 `shlex.split()` 解析命令字串
- 重用 Typer 的參數定義和驗證邏輯
- 支援完整的命令列語法（短選項、長選項、引號等）

**範例**：
```python
# 使用者輸入
"/deploy --binary-args \"on\""
"/log 有任何 error 嗎? --log-path logs/uart.log"

# 解析結果
command = "deploy"
args = ["--binary-args", "on"]

command = "log"
args = ["有任何 error 嗎?", "--log-path", "logs/uart.log"]
```

**技術挑戰**：
- 需要將解析後的參數轉換為 node 函數所需的 state 欄位
- 可能需要建立 CLI 參數 → State 欄位的映射表

### 2. 命令執行失敗後的行為

**場景**：使用者執行 `/build` 但編譯失敗

**決策**：✅ 詢問使用者「是否需要 AI 協助修復？」（2025-11-20）

**實施方式**：
```python
# Build 失敗後
if result["build_status"] == "error":
    console.print("[red]❌ Build 失敗[/red]")
    console.print(result["build_log"])

    # 詢問是否需要 AI 修復
    user_choice = prompt("是否需要 AI 協助修復？(y/n): ")

    if user_choice.lower() in ["y", "yes"]:
        # 調用 ai_brain_node 進行 auto-fix
        from ..nodes.ai_brain import ai_brain_node
        fix_result = ai_brain_node(current_state)
        current_state = {**current_state, **fix_result}

        # 重新編譯驗證
        build_result = build_node(current_state)
        current_state = {**current_state, **build_result}
```

**優點**：
- 給予使用者選擇權
- 避免非預期的程式碼修改
- 支援 AI auto-fix 的迭代循環（最多 3 次）

### 3. 對話歷史與命令執行的關聯

**問題**：當使用者執行 `/build` 後，AI 是否能「看到」build 的結果？

**決策**：✅ 注入完整 log 到對話歷史（2025-11-20）

**實施方式**：
```python
# 執行命令後，將結果注入為「系統訊息」
result = build_node(current_state)

# 構造系統訊息
system_message = f"""
[系統] 使用者執行了 `/build` 命令

結果：{"成功" if result["build_status"] == "success" else "失敗"}

完整 Log:
{result["build_log"]}
"""

# 注入到 ClaudeSDKClient 的對話歷史
await client.query(system_message)  # 作為 system/user message
```

**優點**：
- AI 具備完整上下文
- 使用者可以詢問「剛剛的錯誤是什麼意思？」、「如何修復？」
- 支援連續操作的上下文理解

**技術考量**：
- Log 可能很長，需要注意 token 消耗
- 若 log 超過一定長度（例如 2000 字），可以截斷或摘要

### 4. 與 `fwauto feat` 的關係

**問題**：`fwauto feat "實作摩斯密碼"` 會自動實作功能並編譯。在 chat 模式中，如何實現類似功能？

**決策**：✅ 透過 AI 對話自然語言（2025-11-20）

**實施方式**：
- **不新增** `/feat` slash 命令
- 使用者直接對 AI 說「幫我實作摩斯密碼功能」
- AI 理解意圖後，使用 Write/Edit 工具修改程式碼
- 使用者執行 `/build` 編譯驗證
- 若失敗，依據 Question 2 的決策詢問是否需要修復

**保留 `fwauto feat` 命令**：
- 作為快速執行的快捷方式（非互動）
- 適合已知明確需求的場景
- 與 chat 模式互補

**範例對話流程**：
```
使用者: 幫我實作摩斯密碼編碼功能，支援 A-Z
AI: 好的，我會為你實作摩斯密碼功能...
    [使用 Write/Edit 工具修改程式碼]
    程式碼已完成，請執行 /build 編譯測試

使用者: /build
系統: [執行 build_node]
      ✅ Build 成功

使用者: /deploy
系統: [執行 deploy_node]
      ✅ Deploy 成功
```

**優點**：
- 最自然的互動方式
- 不需要記憶額外的 slash 命令
- 充分利用 AI 的自然語言理解能力

**技術挑戰**：
- 需要在 `prompts/chat.md` 中明確指示 AI 如何處理功能實作請求
- AI 需要知道完成程式碼後應該提示使用者執行 `/build`

## Related Research

- `.ai/prpi/research/2025-11-19-claude-code-sdk-usage-patterns.md` - Claude Code SDK 使用模式
  - 相關章節：§4 System Prompt 覆寫策略
  - 相關章節：§9 AI Nodes 整合可行性分析（決定保持三個獨立 nodes）

## Technical Debt

### 1. 移除舊的 Interactive Mode

**位置**: src/fwauto/cli.py:459-527

`_interactive_mode()` 在改造後將被 unified chat 取代，但有些使用者可能已習慣。

**風險**：Breaking change

**緩解策略**：
- 在 CHANGELOG 中明確說明
- 提供遷移指南

### 2. Slash 命令與 Typer 命令的參數一致性

**問題**：`/deploy --binary-args "on"` 需要與 `fwauto deploy --binary-args "on"` 行為一致

**挑戰**：
- 需要解析並轉換參數格式
- 可能需要重用 Typer 的參數定義

**建議方向**：
- 建立共用的參數解析邏輯
- 避免重複定義參數規則

### 3. Chat Logger 與命令執行的記錄

**位置**: src/fwauto/nodes/chat.py:69-76

`ChatLogger` 目前只記錄 AI 對話，不記錄 slash 命令執行。

**建議方向**：
- 擴展 ChatLogger 記錄命令執行
- 格式：`{"type": "command", "command": "/build", "result": {...}}`
