docs: add AI agent command bar + privacy gateway three-mode design
This commit is contained in:
@@ -0,0 +1,218 @@
|
||||
# AI Agent Command Bar + Privacy Gateway Three-Mode Design
|
||||
|
||||
**Date:** 2026-03-03
|
||||
**Branch:** feature/enhance-testing-security-states
|
||||
**Status:** Approved
|
||||
|
||||
## Overview
|
||||
|
||||
Add two missing frontend entry points:
|
||||
1. **AI Agent Command Bar** — bottom of ConsolePage, submit tasks to KVM Agent
|
||||
2. **Privacy Gateway Mode Selector** — extend /privacy page CertificateTab with OFF/AUDIT/REDACT modes
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
Browser (port 8080)
|
||||
├── /api/v1/agent/* → Go reverse proxy → KVM Agent (port 8890)
|
||||
└── /api/v1/privacy/* → Go reverse proxy → Privacy Gateway (port 8889)
|
||||
```
|
||||
|
||||
Go adds `proxy_handler.go` using `httputil.ReverseProxy`, one instance per target.
|
||||
|
||||
## Backend Changes
|
||||
|
||||
### Go: proxy_handler.go (new)
|
||||
|
||||
- `AgentProxy`: `/api/v1/agent/*` → `http://localhost:8890`
|
||||
- `PrivacyProxy`: `/api/v1/privacy/*` → `http://localhost:8889`
|
||||
- Register in `router.go`
|
||||
- Strip prefix before forwarding
|
||||
- Log proxy errors
|
||||
|
||||
### privacy_api.py (redesign)
|
||||
|
||||
**Remove:** `privacy_enabled: bool`, old boolean format
|
||||
|
||||
**New state.json format:**
|
||||
```json
|
||||
{"mode": "off"}
|
||||
```
|
||||
|
||||
**Endpoints:**
|
||||
```
|
||||
GET /api/v1/privacy/mode → {mode: "off"|"audit"|"redact"}
|
||||
POST /api/v1/privacy/mode body: {mode: "off"|"audit"|"redact"} → {mode: "..."}
|
||||
GET /api/v1/privacy/stats → {requests, files, pii_by_type, actions:{allow,audit,redact}, top_domains}
|
||||
GET /api/v1/privacy/audit ?limit=50 → [{...log entries}]
|
||||
GET /api/v1/privacy/cert → (unchanged)
|
||||
POST /api/v1/privacy/masking → (unchanged)
|
||||
```
|
||||
|
||||
### addon.py (update)
|
||||
|
||||
Read `mode` from state.json, three-branch logic:
|
||||
```python
|
||||
if mode == "off": pass_through() # no logging
|
||||
elif mode == "audit": log_request(); allow() # log only
|
||||
elif mode == "redact": log_request(); redact() # log + redact
|
||||
```
|
||||
|
||||
### audit_logger.py (extend)
|
||||
|
||||
Log schema per intercepted request:
|
||||
```json
|
||||
{
|
||||
"timestamp": "ISO8601",
|
||||
"mode": "audit|redact",
|
||||
"domain": "api.openai.com",
|
||||
"method": "POST",
|
||||
"path": "/v1/chat/completions",
|
||||
"action": "allow|redact",
|
||||
"files_detected": 1,
|
||||
"pii_types": ["id_card", "phone"],
|
||||
"source_ip": "192.168.1.x",
|
||||
"request_size_bytes": 12345
|
||||
}
|
||||
```
|
||||
|
||||
Storage: SQLite table `audit_log`, supports `GET /api/v1/privacy/audit?limit=50`.
|
||||
|
||||
### runner.py (task cancellation)
|
||||
|
||||
- Add `asyncio.Event cancel_event` per running task
|
||||
- `DELETE /api/v1/agent/tasks/{id}` sets the event
|
||||
- runner checks event between action steps → sets status `CANCELLED`
|
||||
- KVM HID cleanup via `safe_cleanup()` on cancel
|
||||
|
||||
### kvm_agent/api_server.py
|
||||
|
||||
No changes needed — `DELETE /api/v1/agent/tasks/{id}` already exists.
|
||||
|
||||
## Frontend Changes
|
||||
|
||||
### New: `AgentCommandBar.tsx`
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ [任务描述输入框.............................] [执行] [终止] │
|
||||
│ 状态: 空闲 | 运行中 (task_id: abc123) │
|
||||
│ 最近: ✅ 打开记事本 ❌ 找不到文件 │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
- Placed below `.video-container` in ConsolePage
|
||||
- [终止] button visible only when task is running
|
||||
- Task history: last 5 entries, success/fail icons
|
||||
|
||||
### New: `agentStore.ts`
|
||||
|
||||
```typescript
|
||||
interface AgentStore {
|
||||
status: "idle" | "running" | "error"
|
||||
currentTask: Task | null
|
||||
taskHistory: Task[]
|
||||
submitTask(description: string): Promise<void>
|
||||
cancelTask(id: string): Promise<void>
|
||||
pollStatus(id: string): void // 2s interval, stops on terminal state
|
||||
}
|
||||
```
|
||||
|
||||
### Modified: `PrivacyPage.tsx` — CertificateTab
|
||||
|
||||
Replace binary ToggleCard with three-segment mode selector:
|
||||
|
||||
```
|
||||
隐私网关模式:
|
||||
┌──────────┬──────────┬──────────┐
|
||||
│ 关闭 │ 审计 │ 脱敏 │
|
||||
└──────────┴──────────┴──────────┘
|
||||
描述: 不拦截任何请求 / 记录但不拦截 / 记录并脱敏文件
|
||||
```
|
||||
|
||||
- Calls `POST /api/v1/privacy/mode {mode: "..."}`
|
||||
- Loads current mode on mount via `GET /api/v1/privacy/mode`
|
||||
|
||||
### i18n keys to add
|
||||
|
||||
```json
|
||||
"agent.command_placeholder": "描述任务,例如:打开记事本",
|
||||
"agent.submit": "执行",
|
||||
"agent.cancel": "终止",
|
||||
"agent.status_idle": "空闲",
|
||||
"agent.status_running": "运行中",
|
||||
"agent.task_success": "成功",
|
||||
"agent.task_failed": "失败",
|
||||
"agent.task_cancelled": "已终止",
|
||||
"privacy.mode_off": "关闭",
|
||||
"privacy.mode_audit": "审计",
|
||||
"privacy.mode_redact": "脱敏",
|
||||
"privacy.mode_off_desc": "不拦截任何请求",
|
||||
"privacy.mode_audit_desc": "记录 PII 但不拦截",
|
||||
"privacy.mode_redact_desc": "记录并自动脱敏文件"
|
||||
```
|
||||
|
||||
## Data Flow
|
||||
|
||||
### Agent Task Submission
|
||||
|
||||
```
|
||||
User input → agentStore.submitTask()
|
||||
→ POST /api/v1/agent/tasks {task_type: "general", description: "..."}
|
||||
→ Go proxy → api_server.py
|
||||
→ TaskQueue (SQLite) → runner.py executes
|
||||
→ Poll GET /api/v1/agent/tasks/{id} every 2s
|
||||
→ On COMPLETED/FAILED/CANCELLED → stop polling, update history
|
||||
```
|
||||
|
||||
### Agent Task Cancellation
|
||||
|
||||
```
|
||||
User clicks [终止] → agentStore.cancelTask(id)
|
||||
→ DELETE /api/v1/agent/tasks/{id}
|
||||
→ Go proxy → api_server.py
|
||||
→ Sets cancel_event on runner
|
||||
→ runner finishes current action step → stops
|
||||
→ safe_cleanup() → status = CANCELLED
|
||||
→ Frontend poll detects CANCELLED → shows "已终止"
|
||||
```
|
||||
|
||||
### Privacy Mode Switch
|
||||
|
||||
```
|
||||
User clicks mode → POST /api/v1/privacy/mode {mode: "audit"}
|
||||
→ Go proxy → privacy_api.py
|
||||
→ Writes state.json {mode: "audit"}
|
||||
→ addon.py reads mode on next intercept
|
||||
```
|
||||
|
||||
### Audit Log Query (PrivacyPage AuditTab)
|
||||
|
||||
```
|
||||
AuditTab mount → GET /api/v1/privacy/audit?limit=50
|
||||
→ Go proxy → privacy_api.py
|
||||
→ SQLite query audit_log → return entries
|
||||
→ Render table with timestamp/domain/action/pii columns
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
| Layer | What to test |
|
||||
|-------|-------------|
|
||||
| Go proxy | `httptest` mock downstream, verify routing + error forwarding |
|
||||
| privacy_api | pytest: mode switch, stats format, audit log insert/query |
|
||||
| addon.py | mock three-mode branches (off/audit/redact) |
|
||||
| runner.py | cancel_event propagation, CANCELLED status |
|
||||
| AgentCommandBar | vitest: submit → poll → complete flow, cancel button visibility |
|
||||
| PrivacyPage | vitest: mode selector loads current mode, calls correct endpoint |
|
||||
|
||||
## Implementation Order
|
||||
|
||||
1. **Go proxy** (unblocks all frontend API calls)
|
||||
2. **privacy_api.py redesign** (mode enum + audit log)
|
||||
3. **addon.py update** (three-mode branch)
|
||||
4. **runner.py cancellation**
|
||||
5. **agentStore.ts + AgentCommandBar.tsx**
|
||||
6. **PrivacyPage CertificateTab mode selector**
|
||||
7. **i18n + wiring**
|
||||
8. **Tests**
|
||||
Reference in New Issue
Block a user