Files
VisionL/docs/development/architecture.md
T
AskaEth 0c437a96f1 docs: 项目架构设计文档(中文)及使用指南
- architecture.md: 完整架构设计(技术栈、API、CLI、生命周期、测试策略)
- api.md: REST + WebSocket 接口文档
- cli.md: CLI 命令参考
- contributing.md: 开发规范与 JSON 转义要求
- quickstart.md: 快速开始指南
- llm-integration.md: 智能体集成指南
- examples.md: 8 个典型使用场景
2026-08-12 20:14:54 +08:00

13 KiB
Raw Blame History

VisionL 架构设计

面向 AI 智能体的开源可持久化浏览器 — 架构设计文档

1. 概述

VisionL 是一款专为具备本地读写执行能力的 AI 智能体设计的开源浏览器。 LLM 通过 VisionL-cli 工具调用来操控网页。页面独立持久化——关闭 GUI 窗口不会杀死页面后台, 每个页面只能通过 CLI 参数或未来 GUI 的右键菜单按钮来关闭。

核心原则

  • 页面持久化:页面在客户端断连后依然存活,只有显式 kill 才能终止
  • 智能体优先:CLI 子命令为 LLM 工具调用而设计,输出结构化 JSON
  • 本地优先Daemon 仅监听 localhost,不对外开放
  • GUI 就绪HTTP/WS API 同时服务于 CLI 和未来的 GUI,避免重复实现

2. 技术栈

层级 选择 理由
浏览器引擎 Playwright + Chromium AI 浏览器自动化的事实标准
语言 TypeScript + Node.js Playwright 原生语言,生态最优
通信协议 HTTP REST + WebSocket (localhost) 职责分离,GUI 可复用同一套 API
包管理 npm workspaces (monorepo) 共享类型,单仓库管理
CLI 框架 commander + chalk 轻量、社区熟知
HTTP 服务 原生 http + ws 本地 daemon 无需重型框架

JSON 转义处理

所有返回给智能体的 JSON 必须正确处理特殊字符转义。页面内容(文本、HTML)中可能包含 引号、反斜杠、控制字符、Unicode 代理对等,统一通过 JSON.stringify 序列化整个响应对象, 禁止手动拼接 JSON 字符串。


3. 项目结构

VisionL/
├── docs/
│   ├── development/
│   │   ├── architecture.md      # 本文档
│   │   ├── api.md               # REST + WS 接口文档
│   │   ├── cli.md               # CLI 命令参考
│   │   └── contributing.md      # 开发规范
│   └── usage/
│       ├── quickstart.md        # 快速开始
│       ├── llm-integration.md   # 智能体集成指南
│       └── examples.md          # 典型场景示例
├── packages/
│   ├── core/                    # 共享库:类型定义、HTTP 客户端、工具函数
│   │   ├── src/
│   │   │   ├── types/
│   │   │   │   ├── page.ts      # Page 结构
│   │   │   │   ├── api.ts       # 请求/响应类型
│   │   │   │   └── ws.ts        # WebSocket 事件类型
│   │   │   ├── client.ts        # Daemon HTTP + WS 客户端(CLI 和 GUI 共用)
│   │   │   ├── escape.ts        # JSON 安全转义
│   │   │   └── index.ts
│   │   └── package.json
│   ├── daemon/                  # 后台进程:Playwright 管理 + HTTP/WS 服务
│   │   ├── src/
│   │   │   ├── server.ts        # HTTP + WS 入口
│   │   │   ├── routes/
│   │   │   │   ├── pages.ts     # 页面 CRUD
│   │   │   │   ├── actions.ts   # click/type/scroll/eval/navigate
│   │   │   │   ├── content.ts   # screenshot/text/html
│   │   │   │   └── health.ts    # 健康检查
│   │   │   ├── browser-manager.ts  # Playwright 浏览器/页面生命周期
│   │   │   ├── page-registry.ts    # 内存页面表(id → Playwright Page
│   │   │   ├── ws-relay.ts      # WebSocket 事件广播
│   │   │   └── pidfile.ts       # Daemon 进程管理
│   │   └── package.json
│   └── cli/                     # CLI 工具:用户 + LLM 交互入口
│       ├── src/
│       │   ├── index.ts         # CLI 入口(commander
│       │   ├── commands/
│       │   │   ├── page.ts      # visionl page open/close/list/info
│       │   │   ├── action.ts    # visionl click/type/scroll/eval/wait/navigate
│       │   │   ├── view.ts      # visionl screenshot/text/html
│       │   │   └── daemon.ts    # visionl daemon start/stop/status
│       │   ├── format.ts        # 输出格式化(JSON/pretty
│       │   └── auto-daemon.ts   # 自动拉起 daemon 逻辑
│       └── package.json
├── package.json                 # monorepo root
├── tsconfig.json
├── .gitignore
├── README.md
└── LICENSE

4. Daemon 设计

4.1 REST API

方法 路径 说明 请求体 响应
POST /pages 打开新页面 { url, alias? } { id, url, alias?, title, status }
GET /pages 列出所有页面 [{ id, url, alias?, title, status }]
GET /pages/:id 页面详情 { id, url, alias?, title, status }
DELETE /pages/:id 杀死页面 { ok: true }
POST /pages/:id/navigate 跳转 URL { url } { url, title }
POST /pages/:id/click 点击元素 { selector } { success }
POST /pages/:id/type 输入文本 { selector, text } { success }
POST /pages/:id/scroll 滚动页面 { deltaY?, toBottom? } { success }
POST /pages/:id/eval 执行 JS { code } { result }
POST /pages/:id/wait 等待条件 { selector?, timeout?, ms? } { success }
GET /pages/:id/screenshot 页面截图 { base64, mime }
GET /pages/:id/text 页面纯文本 { text }
GET /pages/:id/html 页面 HTML { html }
GET /health 健康检查 { status: "ok" }

4.2 WebSocket 事件

客户端通过 ws://127.0.0.1:{port}/ws 建立连接,daemon 主动推送:

事件 数据 触发时机
page:created { id, url, alias?, title } 新页面打开
page:closed { id } 页面被杀死
page:navigated { id, url, title } 页面 URL 变化
page:crashed { id, error } 页面崩溃
page:console { id, level, text } 控制台输出(调试用)

4.3 页面生命周期

visionl page open https://example.com --alias demo
  → POST /pages  { url, alias: "demo" }
  → daemon 创建 Playwright Context + Page
  → 注册到 PageRegistry { id: "p_abc123", page, alias: "demo" }
  → 返回 { id: "p_abc123", url, alias: "demo", title, status: "active" }

visionl page screenshot p_abc123
  → GET /pages/p_abc123/screenshot
  → daemon 调用 page.screenshot(),返回 base64

visionl page kill p_abc123
  → DELETE /pages/p_abc123
  → daemon 调用 page.close()、context.close()
  → 从 PageRegistry 移除

规则:

  • 页面在被显式 kill 之前永远存活,无超时、无闲置清理
  • Daemon 重启不会恢复之前的页面(v1 不包含此功能)
  • visionl daemon stop 优雅退出 daemon,但不杀死页面(Playwright 浏览器进程独立管理)

5. CLI 设计

5.1 页面管理

visionl page open <url>            [--alias <name>]
visionl page list
visionl page info  <id|alias>
visionl page kill  <id|alias>
visionl page kill-all

5.2 页面操作

visionl click    <id|alias> <selector>
visionl type     <id|alias> <selector> <text>
visionl scroll   <id|alias> [--down <px>] [--bottom]
visionl navigate <id|alias> <url>
visionl eval     <id|alias> <js-code>
visionl wait     <id|alias> [--selector <sel>] [--ms <n>]

5.3 内容获取

visionl screenshot <id|alias> [-o <file.png>]
visionl text       <id|alias>
visionl html       <id|alias>

5.4 Daemon 管理

visionl daemon start  [--port <n>]
visionl daemon stop
visionl daemon status

5.5 REST 直通

visionl raw POST /pages '{"url":"https://example.com"}'
visionl raw GET  /pages
visionl raw GET  /pages/p_abc123/text

5.6 输出格式

默认输出 JSON(供 LLM 解析)。使用 --pretty 切换为人类可读格式。

// 成功
{ "ok": true, "data": { ... } }

// 失败
{ "ok": false, "error": { "code": "PAGE_NOT_FOUND", "message": "页面 p_xyz 不存在" } }

JSON 转义规则:所有响应通过 JSON.stringify 序列化完整对象。页面内容字段(texthtml 作为不透明字符串处理,由 JSON.stringify 负责转义,不手动拼接。

5.7 自动拉起

CLI 在任何页面/操作命令前执行 daemon 存活检测:

1. 向配置端口(默认 9527)发送 GET /health
2. 有响应 → 继续执行
3. 超时/拒绝 → 检查 pidfile~/.visionl/daemon.pid
   - pid 存活且端口可达 → 更新端口,继续
   - pid 已死或端口不可达 → 清理残留 pidfile,启动 daemon
4. 启动:child_process.spawn('visionl-daemon', ['--port', port])
5. 轮询 GET /health(最多 3 秒,间隔 100ms)
6. 就绪 → 继续;超时 → 报错退出

状态文件:

  • ~/.visionl/daemon.pid — daemon 进程 PID
  • ~/.visionl/daemon.port — daemon 监听端口

6. 类型定义(core 包)

// packages/core/src/types/page.ts
interface PageInfo {
  id: string;          // 自动生成,如 "p_a1b2c3d4"
  url: string;
  alias?: string;      // 用户指定,可选
  title: string;
  status: 'active' | 'crashed';
}

// packages/core/src/types/api.ts
interface ApiResponse<T = unknown> {
  ok: boolean;
  data?: T;
  error?: {
    code: string;
    message: string;
  };
}

// 错误码
enum ErrorCode {
  PAGE_NOT_FOUND = 'PAGE_NOT_FOUND',
  DAEMON_UNREACHABLE = 'DAEMON_UNREACHABLE',
  INVALID_URL = 'INVALID_URL',
  ALIAS_EXISTS = 'ALIAS_EXISTS',
  TIMEOUT = 'TIMEOUT',
  INTERNAL = 'INTERNAL',
}

// packages/core/src/types/ws.ts
type WsEvent =
  | { type: 'page:created'; data: PageInfo }
  | { type: 'page:closed'; data: { id: string } }
  | { type: 'page:navigated'; data: { id: string; url: string; title: string } }
  | { type: 'page:crashed'; data: { id: string; error: string } }
  | { type: 'page:console'; data: { id: string; level: string; text: string } };

7. 关键设计决策

v1 不包含会话恢复

页面在客户端断连后存活,但 daemon 重启后不会恢复。理由:

  • Playwright 浏览器进程状态复杂,难以序列化/恢复
  • v1 聚焦 CLI 智能体工作流,daemon 在任务期间保持运行即可
  • 会话恢复(跨 daemon 重启保存/恢复页面状态)可在 v2 添加

无身份认证

Daemon 仅绑定 127.0.0.1,任何本地进程均可连接。这是刻意的设计:智能体与 daemon 运行在同一台机器。v2 可添加基于 token 的认证以支持多用户场景。

Page ID 与别名解析

  • Page IDp_xxxxxxxx)始终唯一,自动生成
  • 别名由用户指定,必须唯一。重复使用已有别名会报错
  • 命令同时接受两种形式:visionl text myaliasvisionl text p_abc123
  • 解析顺序:先尝试别名匹配,再尝试 ID 匹配

并发模型

  • 每个 daemon 只有一个 Playwright Browser 实例
  • 每次 POST /pages 创建一个新的 BrowserContext(隔离 cookie/存储)+ Page
  • v1 不设并发上限;由操作系统自然限制
  • 未来:可配置最大页面数、排队、优先级

8. 错误处理策略

场景 Daemon 行为 CLI 行为
页面不存在 404 + 错误码 退出码 1,输出 JSON 错误
Daemon 不可达 自动拉起或退出码 1
无效 URL 400 + 错误码 退出码 1,输出 JSON 错误
选择器未找到 操作响应内 404 退出码 1{ ok: false, error: ... }
页面崩溃 page:crashed WS 事件,status → crashed 后续操作返回 500
Daemon 端口被占用 启动失败,日志记录 退出码 1,建议 --port 覆盖

9. 测试策略

层级 工具 范围
core 类型 ts 类型检查 编译期正确性
core/client.ts vitest + msw HTTP 客户端行为、错误处理
daemon/routes vitest + playwright-test 路由逻辑 + 真实 Chromium
daemon/browser-manager vitest + playwright-test 页面生命周期、崩溃恢复
cli/commands vitest + mock 客户端 命令解析、输出格式化
cli/auto-daemon vitest + fs mock Pidfile 逻辑、spawn 行为
集成测试 vitest + 真实 daemon CLI → daemon → Playwright 端到端
JSON 转义 vitest 验证特殊字符、Unicode、XSS 向量安全

10. 未来规划(v1 范围外)

  • visionl-gui:基于 Electron/Tauri 的桌面 GUI,用于查看和管理页面
  • 会话持久化:跨 daemon 重启保存/恢复页面状态
  • 多用户支持:基于 token 的认证
  • 插件系统:自定义页面处理器
  • 远程 daemon 支持(突破 localhost 限制)
  • 网络拦截和 Mock API
  • Cookie/存储管理 CLI
  • 页面录制与回放