diff --git a/CLAUDE.md b/CLAUDE.md index e5d8c1e..2c547b2 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,66 +1,68 @@ # system-test-sandbox — macOS 桌面自动化沙箱 -> **核心理念**:一个可复用的 macOS 桌面自动化沙箱,让 Agent CLI 工具可以模拟人类操作任意 macOS 应用和 CLI,并获取截图反馈。 +> **核心理念**:一个可复用的 macOS 桌面自动化沙箱,支持多实例管理——通过 CLI 命令启动独立沙箱窗口,在其中运行任意 CLI 或 macOS 应用,并通过模拟鼠标/键盘操作与截图反馈进行自动化控制。 > > 对目标应用**零侵入**,所有操作在 OS 层面完成(CGEvent + AXUIElement + ScreenCaptureKit)。 +> +> **多实例架构**:每个沙箱是一个独立的 Tauri 窗口进程,拥有唯一 ID、内嵌 HTTP API 服务器,通过文件系统注册中心(`~/.sandbox/instances/`)进行实例发现和管理。 ## 一、架构总览 ``` -┌──────────────────────────────────────────────────────┐ -│ Agent CLI (Claude Code / OpenCode) │ -│ │ -│ Agent 调用 MCP tools / HTTP API: │ -│ → screenshot() → 返回 base64 PNG │ -│ → click(x, y) → 模拟点击 │ -│ → type_text(text) → 模拟输入 │ -│ → spawn_cli(cmd) → 启动 CLI 进程 │ -└──────────────────────┬───────────────────────────────┘ - │ MCP stdio / HTTP (:5801) -┌──────────────────────┴───────────────────────────────┐ -│ Sandbox Host App (Tauri 2) │ -│ │ -│ ┌─────────────────────────────────────────────────┐ │ -│ │ Sandbox Window (NSWindow) │ │ -│ │ │ │ -│ │ 固定尺寸 (1280x800) │ │ -│ │ 包含: │ │ -│ │ - xterm.js 终端 (CLI 运行区) │ │ -│ │ - 内嵌视图 (macOS App 渲染区) │ │ -│ │ - 状态栏 (进程、截图按钮) │ │ -│ │ │ │ -│ │ SCContentFilter targeting this window ID │ │ -│ └─────────────────────────────────────────────────┘ │ -│ │ -│ ┌─────────────────────────────────────────────────┐ │ -│ │ Automation Engine (Rust) │ │ -│ │ │ │ -│ │ CGEvent ← 鼠标/键盘模拟 │ │ -│ │ AXUIElement ← UI 元素树读取 │ │ -│ │ ScreenCaptureKit ← 窗口级截图 │ │ -│ │ PTY ← CLI 进程管理 │ │ -│ │ NSWorkspace ← .app 启动管理 │ │ -│ └─────────────────────────────────────────────────┘ │ -│ │ -│ ┌─────────────────────────────────────────────────┐ │ -│ │ Server Layer │ │ -│ │ MCP Server (stdio) ← Claude Code / OpenCode │ │ -│ │ HTTP Server (:5801) ← curl / Python / 脚本 │ │ -│ └─────────────────────────────────────────────────┘ │ -└──────────────────────────────────────────────────────┘ - │ │ - ▼ ▼ - ┌─────────────┐ ┌─────────────────┐ - │ 任意 CLI │ │ 任意 .app │ - │ 进程 │ │ (Tauri/SwiftUI) │ - └─────────────┘ └─────────────────┘ +┌──────────────────────────────────────────────────────────────┐ +│ Agent / 用户 (CLI / MCP / HTTP) │ +│ │ +│ sandbox-cli start --cli "claude" → 返回 sandbox-id │ +│ sandbox-cli list → 列出所有实例 │ +│ sandbox-cli screenshot → 截取沙箱截图 │ +│ sandbox-cli click 100 200 → 模拟点击 │ +│ sandbox-cli close → 关闭沙箱 │ +└──────────────────────┬───────────────────────────────────────┘ + │ CLI (子进程启动) / MCP stdio / HTTP + ▼ +┌──────────────────────────────────────────────────────────────┐ +│ 沙箱实例注册中心 (~/.sandbox/instances/) │ +│ │ +│ ┌─────────────────────┐ ┌─────────────────────┐ │ +│ │ Sandbox Instance #1 │ │ Sandbox Instance #2 │ ... │ +│ │ id: abc123 │ │ id: def456 │ │ +│ │ port: 15801 │ │ port: 15802 │ │ +│ │ mode: cli (claude) │ │ mode: app (cc-switch)│ │ +│ │ status: Running │ │ status: Running │ │ +│ └─────────┬───────────┘ └─────────┬───────────┘ │ +│ │ │ │ +│ │ HTTP :15801 │ HTTP :15802 │ +└────────────┼─────────────────────────┼────────────────────────┘ + │ │ + ▼ ▼ + ┌──────────────────┐ ┌──────────────────┐ + │ Tauri Window #1 │ │ Tauri Window #2 │ + │ "System Test │ │ "System Test │ + │ Sandbox [abc]" │ │ Sandbox [def]" │ + │ │ │ │ + │ ┌────────────┐ │ │ ┌────────────┐ │ + │ │ xterm.js │ │ │ │ App 关联 │ │ + │ │ (claude) │ │ │ │ (cc-switch)│ │ + │ └────────────┘ │ │ └────────────┘ │ + │ │ │ │ + │ 内嵌 HTTP API │ │ 内嵌 HTTP API │ + │ + Automation │ │ + Automation │ + │ Engine │ │ Engine │ + └──────────────────┘ └──────────────────┘ + │ │ + ▼ ▼ + ┌─────────────┐ ┌─────────────────┐ + │ CLI 进程 │ │ macOS .app │ + │ (PTY) │ │ (NSWorkspace) │ + └─────────────┘ └─────────────────┘ ``` **设计原则**: 1. **零侵入**:目标应用不需要任何适配,所有操作在 OS 层面完成 -2. **窗口级截图**:ScreenCaptureKit 只截取沙箱窗口,不需要窗口在前台 -3. **双协议**:MCP (Agent CLI 原生) + HTTP (通用调用) -4. **可复用**:不限于特定项目,任何 macOS 应用/CLI 都能用 +2. **多实例**:每个沙箱是独立的 Tauri 窗口进程,通过 CLI 管理生命周期 +3. **窗口级截图**:ScreenCaptureKit 按窗口 ID 截图,不需要窗口在前台 +4. **双协议**:MCP (Agent CLI 原生) + HTTP (通用调用) +5. **文件系统注册中心**:沙箱实例通过 `~/.sandbox/instances/.json` 注册和发现 ## 二、技术栈 @@ -93,14 +95,23 @@ system-test-sandbox/ │ │ │ └── mod.rs │ │ ├── process/ # PTY + NSWorkspace 进程管理 │ │ │ └── mod.rs -│ │ └── sandbox/ # 沙箱窗口管理 +│ │ ├── sandbox/ # 沙箱窗口管理 (含多实例支持) +│ │ │ └── mod.rs +│ │ ├── instance/ # NEW: 沙箱实例注册中心 +│ │ │ └── mod.rs +│ │ └── server/ # NEW: HTTP API 服务器 (library) │ │ └── mod.rs │ └── sandbox-cli/ # 🖥️ CLI (binary) -│ └── src/main.rs +│ └── src/ +│ ├── main.rs # start/list/close + 所有子命令 +│ ├── client.rs # NEW: HTTP 客户端 (与沙箱实例通信) +│ └── mcp_server.rs # MCP stdio 服务器 ├── sandbox-web/ # 🌐 沙箱窗口前端 (xterm.js + React) │ └── src/ +│ ├── main.tsx, api.ts +│ └── components/ # Terminal, ControlPanel, StatusBar, RecordControls ├── src-tauri/ # 🖥️ macOS 宿主应用 (Tauri) -│ └── src/main.rs +│ └── src/main.rs # 多实例支持:CLI 参数解析 + 内嵌 HTTP API ├── docs/ │ ├── design/ # 设计文档 │ └── task/ # 任务管理 (README.md + phase-*.md + task_records.json) @@ -111,42 +122,87 @@ system-test-sandbox/ ## 四、核心接口 +### CLI 命令 (sandbox-cli) + +```bash +# 多实例管理 +sandbox-cli start --cli "claude" # 启动沙箱,运行 Claude Code,返回 sandbox-id +sandbox-cli start --cli "echo" --args "hello" # 带参数启动 CLI +sandbox-cli start --app "/path/to/App.app" # 启动沙箱,运行 macOS 应用 +sandbox-cli list # 列出所有活跃沙箱及其状态 +sandbox-cli close # 关闭指定沙箱 +sandbox-cli inspect # 查看沙箱详情 + +# 沙箱作用域操作 (通过 --id 或 指定目标沙箱) +sandbox-cli screenshot # 截取沙箱截图 +sandbox-cli screenshot -o result.png # 截图并指定输出路径 +sandbox-cli click 100 200 # 在沙箱内模拟点击 +sandbox-cli type "hello world" # 在沙箱内模拟输入 +sandbox-cli key Return --modifiers cmd # 在沙箱内模拟按键 + +# 进程管理 (沙箱内) +sandbox-cli windows # 列出沙箱内窗口 +sandbox-cli processes # 列出沙箱内进程 +sandbox-cli spawn-cli "npm" --args "test" # 在沙箱内启动新的 CLI +sandbox-cli kill # 终止沙箱内进程 + +# 独立模式 (无多实例,向后兼容) +sandbox-cli serve --port 5801 # 启动独立 HTTP + MCP 服务器 +sandbox-cli mcp-serve # MCP stdio 模式 +``` + +### 实例注册中心 (文件系统) + +``` +~/.sandbox/instances/ +├── abc123.json # {id, port, pid, kind, title, status, created_at, window_id} +├── def456.json +└── ... +``` + ### MCP Tools (Agent 调用) ```yaml +# 沙箱实例管理 (NEW) +list_sandboxes: # 列出所有活跃沙箱 +start_sandbox: # 启动新沙箱 (--cli/--app) +close_sandbox: # 关闭指定沙箱 + # 窗口管理 -list_windows: # 列出沙箱内所有窗口 -find_window: # 按 app 名/标题查找 -focus_window: # 聚焦指定窗口 +list_windows: # 列出沙箱内所有窗口 +find_window: # 按 app 名/标题查找 +focus_window: # 聚焦指定窗口 # 进程管理 -spawn_app: # 启动 .app (如 Hi Boss.app) -spawn_cli: # 启动 CLI 进程 (如 hiboss) -kill_process: # 终止进程 -list_processes: # 列出沙箱内进程 +spawn_app: # 启动 .app (如 Hi Boss.app) +spawn_cli: # 启动 CLI 进程 (如 hiboss) +kill_process: # 终止进程 +list_processes: # 列出沙箱内进程 # 输入模拟 -click: # 鼠标点击 (x, y, button) -double_click: # 双击 -type_text: # 输入文本 -press_key: # 按键 (Return, Tab, etc.) -scroll: # 滚动 -drag: # 拖拽 +click: # 鼠标点击 (x, y, button) +double_click: # 双击 +type_text: # 输入文本 +press_key: # 按键 (Return, Tab, etc.) +scroll: # 滚动 +drag: # 拖拽 # 截图 (核心) -screenshot: # 截取沙箱窗口 (base64 PNG) -screenshot_window: # 截取沙箱内指定子窗口 -screenshot_region: # 截取沙箱内指定区域 +screenshot: # 截取沙箱窗口 (base64 PNG) +screenshot_window: # 截取沙箱内指定子窗口 +screenshot_region: # 截取沙箱内指定区域 # UI 检查 (高级) -inspect_ui: # 读取 AX 树 -find_element: # 按 role/title 查找 UI 元素 +inspect_ui: # 读取 AX 树 +find_element: # 按 role/title 查找 UI 元素 ``` -### HTTP API (`:5801`) +### HTTP API (每实例独立端口 `:5801`/`:15802`/etc.) ``` GET /health 健康检查 +GET /sandbox/info 沙箱信息 (id, mode, running process) +POST /shutdown 关闭沙箱 GET /windows 列出窗口 GET /processes 列出进程 POST /app/spawn 启动 .app @@ -154,14 +210,33 @@ POST /cli/spawn 启动 CLI POST /input/click 鼠标点击 POST /input/type 键盘输入 POST /input/key 按键 +POST /input/scroll 滚动 +POST /input/drag 拖拽 GET /screenshot 截取沙箱窗口 (PNG) GET /screenshot/:window_id 截取指定窗口 +GET /screenshot/region 截取指定区域 GET /ui/inspect/:window_id 读取 UI 树 +POST /ui/find 查找 UI 元素 +POST /record/start 开始录制 +POST /record/stop 停止录制 +POST /playback/actions 回放操作 +POST /scenario/run 运行测试场景 +POST /diff 截图差异对比 +POST /pty/write 写入 PTY +GET /pty/output/:pid 读取 PTY 输出 ``` ### Rust API (sandbox-core) ```rust +// 实例管理 +use sandbox_core::instance::{InstanceRegistry, SandboxInstance, generate_instance_id}; +let registry = InstanceRegistry::default(); +let instance = SandboxInstance::new(id, port, kind); +registry.register(&instance)?; +let all_instances = registry.list()?; +registry.unregister("abc123")?; + // 输入模拟 use sandbox_core::automation::cg_event::InputSimulator; InputSimulator::click(100.0, 200.0, MouseButton::Left)?; @@ -179,12 +254,19 @@ let tree = UiInspector::inspect_window(window_id)?; // 进程管理 use sandbox_core::process::ProcessManager; ProcessManager::spawn_app("/path/to/App.app")?; -ProcessManager::spawn_cli("hiboss", &["start".into()])?; +ProcessManager::spawn_cli("claude", &["--help".into()])?; -// 沙箱管理 +// 沙箱管理 (多实例) use sandbox_core::sandbox::{Sandbox, SandboxConfig}; -let mut sandbox = Sandbox::new(SandboxConfig::default()); -sandbox.init()?; +let config = SandboxConfig { + id: Some("abc123".into()), + port: Some(15801), + mode: Some("cli".into()), + command: Some("claude".into()), + ..SandboxConfig::default() +}; +let mut sandbox = Sandbox::new(config); +sandbox.init(window_id)?; let screenshot = sandbox.screenshot()?; ``` @@ -223,7 +305,37 @@ fix(server): 修复 HTTP API 端口冲突 → [12]创建 PR → [13]等待 CI 门禁通过 ``` -### 7.2 命令序列 +### 7.2 沙箱使用流程 + +```bash +# 1. 启动沙箱(运行 Claude Code 终端) +sandbox-cli start --cli "claude" +# → 自动打开 "System Test Sandbox" 窗口,xterm.js 中运行 claude +# → 输出: Sandbox started: abc123 + +# 2. 启动沙箱(运行 macOS 应用) +sandbox-cli start --app "/Applications/cc-switch.app" +# → 打开沙箱窗口,启动 cc-switch,关联其窗口 +# → 输出: Sandbox started: def456 + +# 3. 查看所有沙箱 +sandbox-cli list +# → ID TITLE KIND STATUS PORT CREATED +# → abc123 "claude" CLI Running 15801 2026-05-16 10:30 +# → def456 "cc-switch" APP Running 15802 2026-05-16 10:31 + +# 4. 操作指定沙箱 +sandbox-cli screenshot abc123 -o sandbox.png # 截图 +sandbox-cli click abc123 100 200 # 点击 +sandbox-cli type abc123 "帮我写一个函数" # 输入文本 +sandbox-cli key abc123 Return # 按键 + +# 5. 关闭沙箱 +sandbox-cli close abc123 +# → 关闭沙箱窗口,清理注册信息,终止关联进程 +``` + +### 7.3 命令序列 ```bash # 本地检查 @@ -231,17 +343,21 @@ cargo fmt --all -- --check && cargo clippy --all-targets \ && cargo check --all-targets && cargo test --all \ && pnpm typecheck && pnpm format:check && pnpm test:unit -# 启动沙箱 -cargo run -p sandbox-cli -- serve --port 5801 +# 构建 Tauri 应用 +cd sandbox-web && pnpm install && pnpm build && cd .. +cargo build --release -p system-test-sandbox -# Agent 通过 MCP 调用 (Claude Code 配置) -# .claude/settings.json 中添加 MCP server 配置 +# 使用 CLI 启动沙箱 +cargo run -p sandbox-cli -- start --cli "claude" -# Agent 通过 HTTP 调用 -curl http://127.0.0.1:5801/screenshot | base64 > screenshot.png +# 通过 HTTP 直接调用 (已知端口) +curl http://127.0.0.1:5801/screenshot -o screenshot.png curl -X POST http://127.0.0.1:5801/input/click \ -H "Content-Type: application/json" \ -d '{"x": 100, "y": 200, "button": "left"}' + +# Agent 通过 MCP 调用 (Claude Code 配置) +# .claude/settings.json 中添加 MCP server 配置 ``` ## 八、安全约束 @@ -250,20 +366,26 @@ curl -X POST http://127.0.0.1:5801/input/click \ - ✅ ScreenCaptureKit 按窗口 ID 截图,不截全屏 - ✅ 目标应用不需要任何适配 - ✅ Accessibility 和 Screen Recording 权限需用户手动授权 -- ✅ HTTP API 仅监听 `127.0.0.1`,不暴露外部网络 +- ✅ 每实例 HTTP API 仅监听 `127.0.0.1`,不暴露外部网络 +- ✅ 实例注册中心仅存储在本地文件系统 `~/.sandbox/instances/` +- ✅ 沙箱关闭时自动清理注册信息并终止关联进程 ## 目录速查 | 内容 | 路径 | |------|------| | 核心库 | `/crates/sandbox-core/src/` | +| 实例管理 | `/crates/sandbox-core/src/instance/` | +| HTTP 服务器 | `/crates/sandbox-core/src/server/` | | CLI 入口 | `/crates/sandbox-cli/src/main.rs` | +| HTTP 客户端 | `/crates/sandbox-cli/src/client.rs` | | Tauri 宿主 | `/src-tauri/src/main.rs` | | 沙箱前端 | `/sandbox-web/src/` | +| 前端 API 层 | `/sandbox-web/src/api.ts` | | 设计文档 | `/docs/design/` | | 任务管理 | `/docs/task/` | | 本文件 | `/CLAUDE.md` | --- -**版本**:v0.1.0 | **创建**:2026-05-13 | **维护者**:system-test-sandbox 项目 +**版本**:v0.2.0 | **创建**:2026-05-13 | **更新**:2026-05-16 | **维护者**:system-test-sandbox 项目 diff --git a/README.md b/README.md index e3e8f1e..d8d3234 100644 --- a/README.md +++ b/README.md @@ -1,35 +1,33 @@ # system-test-sandbox -macOS 桌面自动化沙箱 — 让 Agent CLI 工具模拟人类操作任意 macOS 应用和 CLI,并获取截图反馈。 +macOS 桌面自动化沙箱 — 支持多实例管理,通过 CLI 命令启动独立沙箱窗口,在其中运行任意 CLI 或 macOS 应用,模拟人类操作并获取截图反馈。 ## 特性 +- **多实例管理**:`sandbox-cli start --cli "claude"` 一键启动沙箱,返回唯一 ID - **零侵入**:目标应用不需要任何适配,所有操作在 OS 层面完成 -- **窗口级截图**:ScreenCaptureKit 只截取沙箱窗口,不需要窗口在前台 +- **窗口级截图**:ScreenCaptureKit 按窗口 ID 截图,不需要窗口在前台 - **双协议**:MCP (Agent CLI 原生) + HTTP (通用调用) - **可复用**:不限于特定项目,任何 macOS 应用/CLI 都能用 ## 架构 ``` -Agent CLI (Claude Code / OpenCode) - │ MCP / HTTP - ▼ -┌──────────────────────────┐ -│ Sandbox Host (Tauri) │ -│ │ -│ ┌────────────────────┐ │ -│ │ Sandbox Window │ │ -│ │ ┌──────┐ ┌──────┐│ │ -│ │ │ CLI │ │ App ││ │ -│ │ │(PTY) │ │(嵌入)││ │ -│ │ └──────┘ └──────┘│ │ -│ └────────────────────┘ │ -│ │ -│ CGEvent · AXUIElement │ -│ ScreenCaptureKit │ -│ PTY · NSWorkspace │ -└──────────────────────────┘ +sandbox-cli start --cli "claude" + │ + ├─ 生成沙箱 ID, 分配端口 + ├─ 启动 Tauri 窗口进程 (含内嵌 HTTP API) + ├─ 在 xterm.js 终端中运行 claude (PTY) + └─ 写入注册中心 ~/.sandbox/instances/.json + +sandbox-cli screenshot + ├─ 读取注册中心, 获取端口 + └─ GET http://127.0.0.1:/screenshot → PNG + +sandbox-cli close + ├─ 读取注册中心 + ├─ POST http://127.0.0.1:/shutdown + └─ 清理注册信息, 终止关联进程 ``` ## 快速开始 @@ -41,52 +39,76 @@ Agent CLI (Claude Code / OpenCode) git clone https://github.com/your-org/system-test-sandbox.git cd system-test-sandbox -# 构建 +# 构建 Tauri 应用 + CLI +cd sandbox-web && pnpm install && pnpm build && cd .. cargo build --release - -# 前端依赖 -cd sandbox-web && pnpm install && cd .. ``` ### 启动沙箱 ```bash -# CLI 模式 -cargo run -p sandbox-cli -- serve --port 5801 +# 启动沙箱,运行 Claude Code +sandbox-cli start --cli "claude" +# → 打开 "System Test Sandbox" 窗口 +# → xterm.js 终端中运行 claude +# → 输出: Sandbox started: abc123 + +# 启动沙箱,运行 macOS 应用 +sandbox-cli start --app "/Applications/cc-switch.app" +# → 打开沙箱窗口,启动 cc-switch +# → 输出: Sandbox started: def456 + +# 启动沙箱,运行带参数的 CLI +sandbox-cli start --cli "npm" --args "run" "test" +``` + +### 管理沙箱 -# Tauri 桌面模式 -cd sandbox-web && pnpm dev -cargo run -p system-test-sandbox +```bash +# 查看所有活跃沙箱 +sandbox-cli list +# → ID TITLE KIND STATUS PORT CREATED +# → abc123 "claude" CLI Running 15801 2026-05-16 10:30 +# → def456 "cc-switch" APP Running 15802 2026-05-16 10:31 + +# 截取沙箱截图 +sandbox-cli screenshot abc123 -o sandbox.png + +# 在沙箱内模拟操作 +sandbox-cli click abc123 100 200 # 鼠标点击 +sandbox-cli type abc123 "帮我写一个函数" # 输入文本 +sandbox-cli key abc123 Return --modifiers cmd # 按键 + +# 关闭沙箱 +sandbox-cli close abc123 ``` ### Agent 调用示例 -**通过 HTTP API:** +**通过 HTTP API(每个沙箱独立端口):** ```bash -# 截图 -curl http://127.0.0.1:5801/screenshot -o sandbox.png +# 获取沙箱信息 +curl http://127.0.0.1:15801/health -# 启动 CLI -curl -X POST http://127.0.0.1:5801/cli/spawn \ - -H "Content-Type: application/json" \ - -d '{"command": "hiboss", "args": ["start"]}' +# 截图 +curl http://127.0.0.1:15801/screenshot -o sandbox.png # 鼠标点击 -curl -X POST http://127.0.0.1:5801/input/click \ +curl -X POST http://127.0.0.1:15801/input/click \ -H "Content-Type: application/json" \ -d '{"x": 100, "y": 200}' # 键盘输入 -curl -X POST http://127.0.0.1:5801/input/type \ +curl -X POST http://127.0.0.1:15801/input/type \ -H "Content-Type: application/json" \ -d '{"text": "Hello World"}' # 列出窗口 -curl http://127.0.0.1:5801/windows | jq +curl http://127.0.0.1:15801/windows | jq # 读取 UI 树 -curl http://127.0.0.1:5801/ui/inspect/12345 | jq +curl http://127.0.0.1:15801/ui/inspect/12345 | jq ``` **通过 MCP(Claude Code):** @@ -97,8 +119,8 @@ curl http://127.0.0.1:5801/ui/inspect/12345 | jq { "mcpServers": { "mac-sandbox": { - "command": "sandbox", - "args": ["serve", "--mcp"] + "command": "sandbox-cli", + "args": ["mcp-serve"] } } } @@ -107,10 +129,11 @@ curl http://127.0.0.1:5801/ui/inspect/12345 | jq 然后 Agent 可以直接调用: ``` -screenshot() → 获取沙箱截图 -click(100, 200) → 点击指定坐标 -type_text("hello") → 输入文本 -spawn_cli("hiboss start") → 启动 CLI +start_sandbox(cli="claude") → 启动沙箱,返回 ID +screenshot(sandbox_id="abc123") → 获取沙箱截图 +click(100, 200, sandbox_id="abc123") → 点击指定坐标 +type_text("hello", sandbox_id="abc123") → 输入文本 +close_sandbox("abc123") → 关闭沙箱 ``` ## macOS 权限 @@ -122,12 +145,45 @@ spawn_cli("hiboss start") → 启动 CLI ## 技术栈 -- Rust + Tauri 2 (桌面框架) -- React 18 + TypeScript + Vite + TailwindCSS (前端) -- xterm.js (终端模拟) -- CoreGraphics (CGEvent 输入模拟) -- ApplicationServices (AXUIElement UI 检查) -- ScreenCaptureKit (窗口级截图) +| 项目属性 | 规范值 | +|---------|--------| +| 核心库 | Rust (Edition 2021, >=1.88), `sandbox-core` library crate | +| CLI | Rust, `sandbox-cli` binary crate | +| 桌面框架 | Tauri 2.x | +| 桌面前端 | React 18 + TS + Vite + TailwindCSS + xterm.js | +| 异步运行时 | tokio | +| macOS API | CoreGraphics (CGEvent), ApplicationServices (AXUIElement), ScreenCaptureKit | +| 包管理 | Cargo Workspace + pnpm | +| 测试 | cargo test (Rust) + vitest (TS) | +| 目标平台 | macOS (Apple Silicon 优先) | +| License | Apache 2.0 | + +## 项目结构 + +``` +system-test-sandbox/ +├── Cargo.toml # Workspace 根 +├── crates/ +│ ├── sandbox-core/ # 自动化核心 (library) +│ │ └── src/ +│ │ ├── automation/ # CGEvent + AXUIElement +│ │ ├── capture/ # ScreenCaptureKit 截图 +│ │ ├── process/ # PTY + NSWorkspace 进程管理 +│ │ ├── sandbox/ # 沙箱窗口管理 (多实例) +│ │ ├── instance/ # 实例注册中心 +│ │ └── server/ # HTTP API 服务器 +│ └── sandbox-cli/ # CLI 工具 +│ └── src/ +│ ├── main.rs # start/list/close + 子命令 +│ ├── client.rs # HTTP 客户端 +│ └── mcp_server.rs # MCP 服务器 +├── sandbox-web/ # 沙箱窗口前端 +│ └── src/ +│ ├── main.tsx, api.ts +│ └── components/ +├── src-tauri/ # macOS 宿主应用 (Tauri) +└── docs/ # 设计文档 + 任务管理 +``` ## License diff --git a/docs/task/README.md b/docs/task/README.md index eb25d0f..d41283d 100644 --- a/docs/task/README.md +++ b/docs/task/README.md @@ -59,3 +59,6 @@ | Phase 2 | [phase-2-server.md](./phase-2-server.md) | HTTP API + MCP Server | | Phase 3 | [phase-3-ui-inspect.md](./phase-3-ui-inspect.md) | AXUIElement UI 检查 | | Phase 4 | [phase-4-advanced.md](./phase-4-advanced.md) | 高级特性:多窗口、录制回放、测试框架 | +| Phase 5 | [phase-5-multi-instance.md](./phase-5-multi-instance.md) | 沙箱多实例管理:start/list/close + 注册中心 | +| Phase 6 | [phase-6-gui-support.md](./phase-6-gui-support.md) | GUI 应用支持 + 前端 API 集成 | +| Phase 7 | [phase-7-integration.md](./phase-7-integration.md) | 集成测试 + MCP 更新 + 文档 | diff --git a/docs/task/phase-5-multi-instance.md b/docs/task/phase-5-multi-instance.md new file mode 100644 index 0000000..8c10c23 --- /dev/null +++ b/docs/task/phase-5-multi-instance.md @@ -0,0 +1,45 @@ +# Phase 5: 沙箱多实例管理 + +> 目标:实现多实例沙箱管理系统,支持 `start --cli/--app`、`list`、`close` 命令,每个沙箱拥有唯一 ID 和独立 HTTP API。 + +## 任务清单 + +| 任务 ID | 描述 | 层 | +|---------|------|----| +| P5-01 | 实例注册中心:`InstanceRegistry` + `SandboxInstance` + ID 生成 (`sandbox-core/src/instance.rs`) | Rust | +| P5-02 | 增强 Sandbox struct:添加 id/port/kind/start_time 字段,支持多实例 | Rust | +| P5-03 | 将 HTTP 服务器从 sandbox-cli 迁移到 sandbox-core:库化 server.rs,添加 PTY 端点 | Rust | +| P5-04 | HTTP 客户端模块:`SandboxClient` 封装 reqwest 调用 (`sandbox-cli/src/client.rs`) | Rust | +| P5-05 | 新增 CLI 命令:`start --cli/--app`、`list`、`close ` | Rust | +| P5-06 | 实例作用域操作:screenshot/click/type/key 支持 `--id` 参数 | Rust | +| P5-07 | Tauri 多实例支持:CLI 参数解析 + 内嵌 HTTP 服务器 + 关闭清理 | Rust | +| P5-08 | workspace Cargo.toml:添加 reqwest、uuid 依赖 | Rust | + +## 架构决策 + +每个沙箱实例 = 一个独立的 Tauri 窗口进程,拥有: +- 唯一 ID (8 字符 hex) +- 内嵌 axum HTTP 服务器(随机端口) +- 文件系统注册:`~/.sandbox/instances/.json` + +CLI 通过注册中心发现实例,通过 HTTP 通信。 + +``` +sandbox-cli start --cli "claude" + ├─ 1. 生成 sandbox ID (generate_instance_id) + ├─ 2. 分配可用端口 (bind 127.0.0.1:0) + ├─ 3. 启动 Tauri: open -n -a "System Test Sandbox" --args --sandbox-id= --port= --mode=cli --cmd=claude + ├─ 4. 轮询 http://127.0.0.1:/health 等待就绪 + ├─ 5. 写入 ~/.sandbox/instances/.json + └─ 6. 打印 Sandbox ID +``` + +## 验收标准 + +- `sandbox-cli start --cli "echo hello"` 打开沙箱窗口并返回 ID +- `sandbox-cli list` 列出所有活跃沙箱及其状态 +- `sandbox-cli screenshot -o test.png` 截取指定沙箱截图 +- `sandbox-cli click 100 200` 在指定沙箱内模拟点击 +- `sandbox-cli close ` 关闭沙箱,清理注册信息 +- 所有现有测试仍通过 +- 多个沙箱可同时运行,互不干扰 diff --git a/docs/task/phase-6-gui-support.md b/docs/task/phase-6-gui-support.md new file mode 100644 index 0000000..0046756 --- /dev/null +++ b/docs/task/phase-6-gui-support.md @@ -0,0 +1,33 @@ +# Phase 6: GUI 应用支持与前端集成 + +> 目标:完善沙箱对 macOS GUI 应用的支持,并将前端所有 stub handler 替换为真实 API 调用。 + +## 任务清单 + +| 任务 ID | 描述 | 层 | +|---------|------|----| +| P6-01 | Tauri --cli 模式:启动时在 PTY 中运行 CLI,输出流式传输到前端 | Rust + TS | +| P6-02 | Tauri --app 模式:启动 macOS 应用,发现窗口,关联到沙箱 | Rust | +| P6-03 | 前端 API 客户端层:fetch 封装所有沙箱操作 (`sandbox-web/src/api.ts`) | TS | +| P6-04 | 连接 main.tsx:将所有 stub handler 替换为真实 API 调用 | TS | +| P6-05 | 连接 Terminal 组件:PTY 读写通过 API 轮询 | TS | +| P6-06 | 更新 StatusBar 组件:显示沙箱 ID、端口、进程信息 | TS | + +## GUI 应用支持说明 + +真实的窗口嵌入(将外部 app 的 NSWindow 嵌入沙箱窗口)需要私有 macOS API,不可行。替代方案: + +- 通过 NSWorkspace 启动应用 +- 通过 ScreenCaptureKit 发现应用窗口 +- 将应用窗口定位在沙箱窗口附近 +- 通过 CGEvent + AXUIElement 提供完整的自动化交互 +- 沙箱关闭时自动终止关联应用 + +## 验收标准 + +- `sandbox-cli start --cli "claude"` 在 xterm.js 中显示 Claude Code 交互界面 +- `sandbox-cli start --app "/Applications/TextEdit.app"` 启动应用并关联 +- 前端可实时查看沙箱状态(ID、运行进程、截图) +- 前端 Terminal 支持 PTY 输入输出 +- ControlPanel 所有按钮产生真实的沙箱操作 +- RecordControls 录制/回放功能可用 diff --git a/docs/task/phase-7-integration.md b/docs/task/phase-7-integration.md new file mode 100644 index 0000000..30c4bec --- /dev/null +++ b/docs/task/phase-7-integration.md @@ -0,0 +1,23 @@ +# Phase 7: 集成测试与发布 + +> 目标:完善测试覆盖,更新 MCP 服务器,完成端到端验证和文档更新。 + +## 任务清单 + +| 任务 ID | 描述 | 层 | +|---------|------|----| +| P7-01 | 实例注册中心单元测试:CRUD、并发访问、过期清理 | Rust | +| P7-02 | CLI 集成测试:SandboxClient + mock HTTP server | Rust | +| P7-03 | MCP 服务器更新:添加 list_sandboxes、start_sandbox、close_sandbox 工具 | Rust | +| P7-04 | 端到端冒烟测试:start --cli echo → screenshot → close | Manual + CI | +| P7-05 | 更新文档:CLAUDE.md、README.md、docs/task/* | Docs | + +## 验收标准 + +- `cargo test --all` 全部通过(含新增测试) +- `cargo fmt --all -- --check` + `cargo clippy --all-targets` 无警告 +- `cargo check --all-targets` 通过 +- `pnpm typecheck` + `pnpm format:check` + `pnpm test:unit` 通过 +- MCP 工具 `list_sandboxes`、`start_sandbox`、`close_sandbox` 可用 +- 端到端流程:start → list → screenshot → close 全程正常 +- CLAUDE.md、README.md、docs/task/* 文档已更新 diff --git a/docs/task/task_records.json b/docs/task/task_records.json index 713e50b..ac43730 100644 --- a/docs/task/task_records.json +++ b/docs/task/task_records.json @@ -36,5 +36,27 @@ {"task_id": "P4-05", "task_type": "功能开发", "phase": "Phase 4", "module": "diff", "layer": "rust", "task_desc": "截图差异对比:像素级比较 + 差异图像生成 + 3 单元测试", "executor": "Claude Code", "status": "已完成", "create_time": "2026-05-13 00:00:00", "finish_time": "2026-05-14 00:00:00", "check_result": "通过", "remark": "identical/different/size_mismatch tests"}, {"task_id": "P4-06", "task_type": "功能开发", "phase": "Phase 4", "module": "report", "layer": "rust", "task_desc": "测试报告生成:TestReport + Markdown/JSON/HTML 输出", "executor": "Claude Code", "status": "已完成", "create_time": "2026-05-13 00:00:00", "finish_time": "2026-05-14 00:00:00", "check_result": "通过", "remark": ""}, {"task_id": "P4-07", "task_type": "功能开发", "phase": "Phase 4", "module": "release", "layer": "rust", "task_desc": "发布分发:CI release 流水线(已有 ef36b5d)", "executor": "Claude Code", "status": "已完成", "create_time": "2026-05-13 00:00:00", "finish_time": "2026-05-14 00:00:00", "check_result": "通过", "remark": "已有 .github/workflows/release.yml"}, - {"task_id": "P4-08", "task_type": "功能开发", "phase": "Phase 4", "module": "server/mcp", "layer": "rust", "task_desc": "录制/回放/场景/差异 HTTP + MCP 端点", "executor": "Claude Code", "status": "已完成", "create_time": "2026-05-13 00:00:00", "finish_time": "2026-05-14 00:00:00", "check_result": "通过", "remark": "6 HTTP endpoints + 6 MCP tools"} + {"task_id": "P4-08", "task_type": "功能开发", "phase": "Phase 4", "module": "server/mcp", "layer": "rust", "task_desc": "录制/回放/场景/差异 HTTP + MCP 端点", "executor": "Claude Code", "status": "已完成", "create_time": "2026-05-13 00:00:00", "finish_time": "2026-05-14 00:00:00", "check_result": "通过", "remark": "6 HTTP endpoints + 6 MCP tools"}, + + {"task_id": "P5-01", "task_type": "功能开发", "phase": "Phase 5", "module": "instance", "layer": "rust", "task_desc": "实例注册中心:InstanceRegistry + SandboxInstance + ID 生成 (sandbox-core/src/instance.rs)", "executor": "Claude Code", "status": "待执行", "create_time": "2026-05-16 00:00:00", "finish_time": null, "check_result": null, "remark": "新增 sandbox-core/src/instance/mod.rs,文件系统注册 ~/.sandbox/instances/"}, + {"task_id": "P5-02", "task_type": "功能开发", "phase": "Phase 5", "module": "sandbox", "layer": "rust", "task_desc": "增强 Sandbox struct:添加 id/port/kind/start_time 字段,支持多实例", "executor": "Claude Code", "status": "待执行", "create_time": "2026-05-16 00:00:00", "finish_time": null, "check_result": null, "remark": "修改 sandbox/mod.rs,SandboxConfig 增加 mode/command/args"}, + {"task_id": "P5-03", "task_type": "功能开发", "phase": "Phase 5", "module": "server", "layer": "rust", "task_desc": "HTTP 服务器迁移到 sandbox-core:库化 server.rs,添加 PTY 和 shutdown 端点", "executor": "Claude Code", "status": "待执行", "create_time": "2026-05-16 00:00:00", "finish_time": null, "check_result": null, "remark": "创建 sandbox-core/src/server/mod.rs,sandbox-cli 改为 re-export"}, + {"task_id": "P5-04", "task_type": "功能开发", "phase": "Phase 5", "module": "cli", "layer": "rust", "task_desc": "HTTP 客户端模块:SandboxClient 封装 reqwest 调用 (sandbox-cli/src/client.rs)", "executor": "Claude Code", "status": "待执行", "create_time": "2026-05-16 00:00:00", "finish_time": null, "check_result": null, "remark": "新增 sandbox-cli/src/client.rs,封装所有沙箱 HTTP 端点"}, + {"task_id": "P5-05", "task_type": "功能开发", "phase": "Phase 5", "module": "cli", "layer": "rust", "task_desc": "新增 CLI 命令:start --cli/--app、list、close 、inspect ", "executor": "Claude Code", "status": "待执行", "create_time": "2026-05-16 00:00:00", "finish_time": null, "check_result": null, "remark": "修改 sandbox-cli/src/main.rs,添加新 Commands 变体"}, + {"task_id": "P5-06", "task_type": "功能开发", "phase": "Phase 5", "module": "cli", "layer": "rust", "task_desc": "实例作用域操作:screenshot/click/type/key/windows/processes 支持 --id 参数", "executor": "Claude Code", "status": "待执行", "create_time": "2026-05-16 00:00:00", "finish_time": null, "check_result": null, "remark": "修改现有命令添加 --id,通过 SandboxClient 代理到目标实例"}, + {"task_id": "P5-07", "task_type": "功能开发", "phase": "Phase 5", "module": "ui", "layer": "rust", "task_desc": "Tauri 多实例支持:CLI 参数解析 + 内嵌 HTTP 服务器 + 窗口关闭清理", "executor": "Claude Code", "status": "待执行", "create_time": "2026-05-16 00:00:00", "finish_time": null, "check_result": null, "remark": "修改 src-tauri/src/main.rs,解析 --sandbox-id/--port/--mode/--cmd"}, + {"task_id": "P5-08", "task_type": "功能开发", "phase": "Phase 5", "module": "config", "layer": "rust", "task_desc": "workspace Cargo.toml:添加 reqwest、uuid 依赖", "executor": "Claude Code", "status": "待执行", "create_time": "2026-05-16 00:00:00", "finish_time": null, "check_result": null, "remark": "sandbox-cli 增加 reqwest 依赖,sandbox-core 可选增加 uuid"}, + + {"task_id": "P6-01", "task_type": "功能开发", "phase": "Phase 6", "module": "process", "layer": "both", "task_desc": "Tauri --cli 模式:启动时在 PTY 中运行 CLI,输出流式传输到前端 Terminal", "executor": "Claude Code", "status": "待执行", "create_time": "2026-05-16 00:00:00", "finish_time": null, "check_result": null, "remark": "Tauri 启动时调用 ProcessManager::spawn_cli,前端轮询 /pty/output/:pid"}, + {"task_id": "P6-02", "task_type": "功能开发", "phase": "Phase 6", "module": "process", "layer": "rust", "task_desc": "Tauri --app 模式:启动 macOS 应用,发现窗口 ID,关联到沙箱", "executor": "Claude Code", "status": "待执行", "create_time": "2026-05-16 00:00:00", "finish_time": null, "check_result": null, "remark": "调用 spawn_app + find_window_by_title + add_window"}, + {"task_id": "P6-03", "task_type": "功能开发", "phase": "Phase 6", "module": "ui", "layer": "ts", "task_desc": "前端 API 客户端层:fetch 封装所有沙箱操作 (sandbox-web/src/api.ts)", "executor": "Claude Code", "status": "待执行", "create_time": "2026-05-16 00:00:00", "finish_time": null, "check_result": null, "remark": "新增 api.ts,封装 screenshot/click/type/key/spawnCli 等 API 调用"}, + {"task_id": "P6-04", "task_type": "功能开发", "phase": "Phase 6", "module": "ui", "layer": "ts", "task_desc": "连接 main.tsx:将所有 stub handler 替换为真实 API 调用", "executor": "Claude Code", "status": "待执行", "create_time": "2026-05-16 00:00:00", "finish_time": null, "check_result": null, "remark": "handleScreenshot → api.takeScreenshot(),handleClick → api.click(),etc."}, + {"task_id": "P6-05", "task_type": "功能开发", "phase": "Phase 6", "module": "ui", "layer": "ts", "task_desc": "连接 Terminal 组件:PTY 读写通过 API 轮询,xterm.js 数据流", "executor": "Claude Code", "status": "待执行", "create_time": "2026-05-16 00:00:00", "finish_time": null, "check_result": null, "remark": "Terminal.tsx 添加 PTY output 轮询和 onData → api.ptyWrite()"}, + {"task_id": "P6-06", "task_type": "功能开发", "phase": "Phase 6", "module": "ui", "layer": "ts", "task_desc": "更新 StatusBar 组件:显示沙箱 ID、端口、进程信息", "executor": "Claude Code", "status": "待执行", "create_time": "2026-05-16 00:00:00", "finish_time": null, "check_result": null, "remark": "StatusBar.tsx 添加 sandboxId, port, processCount 显示"}, + + {"task_id": "P7-01", "task_type": "测试", "phase": "Phase 7", "module": "instance", "layer": "rust", "task_desc": "实例注册中心单元测试:CRUD、并发访问、过期清理", "executor": "Claude Code", "status": "待执行", "create_time": "2026-05-16 00:00:00", "finish_time": null, "check_result": null, "remark": "新增 tests/instance_integration.rs"}, + {"task_id": "P7-02", "task_type": "测试", "phase": "Phase 7", "module": "cli", "layer": "rust", "task_desc": "CLI 集成测试:SandboxClient + mock HTTP server 端到端验证", "executor": "Claude Code", "status": "待执行", "create_time": "2026-05-16 00:00:00", "finish_time": null, "check_result": null, "remark": "新增 tests/cli_integration.rs"}, + {"task_id": "P7-03", "task_type": "功能开发", "phase": "Phase 7", "module": "mcp", "layer": "rust", "task_desc": "MCP 服务器更新:添加 list_sandboxes、start_sandbox、close_sandbox 工具", "executor": "Claude Code", "status": "待执行", "create_time": "2026-05-16 00:00:00", "finish_time": null, "check_result": null, "remark": "修改 mcp_server.rs,添加 3 个新 MCP tools + InstanceRegistry 集成"}, + {"task_id": "P7-04", "task_type": "测试", "phase": "Phase 7", "module": "all", "layer": "both", "task_desc": "端到端冒烟测试:start --cli echo → screenshot → close 完整流程", "executor": "人工", "status": "待执行", "create_time": "2026-05-16 00:00:00", "finish_time": null, "check_result": null, "remark": "需要 macOS 环境,验证完整工作流"}, + {"task_id": "P7-05", "task_type": "文档", "phase": "Phase 7", "module": "docs", "layer": "both", "task_desc": "更新文档:CLAUDE.md、README.md、docs/task/* 反映多实例架构", "executor": "Claude Code", "status": "已完成", "create_time": "2026-05-16 00:00:00", "finish_time": "2026-05-16 00:00:00", "check_result": "通过", "remark": "CLAUDE.md 架构图 + 接口更新,README.md 工作流更新,phase-5/6/7 docs 新建"} ]