普通视图

发现新文章,点击刷新页面。
昨天以前首页

万字解析 OpenClaw 源码架构-跨平台应用之MacOS 应用

作者 毛骗导演
2026年3月14日 17:30

菜单栏控制界面简介

本文面向 macOS 菜单栏控制界面,系统性阐述菜单栏图标功能、状态指示器与快捷操作面板的设计与实现。内容覆盖菜单项组织结构、上下文菜单与系统托盘集成、应用生命周期管理、内存优化与系统事件响应、用户交互设计、键盘快捷键支持与无障碍功能,以及菜单栏自定义选项、主题切换与通知配置方法。目标是帮助开发者与使用者全面理解该界面的架构与使用方式。

项目结构

菜单栏控制界面主要由以下模块构成:

  • 应用入口与场景管理:负责菜单栏图标、状态栏按钮外观、菜单打开/关闭事件处理、悬浮 HUD 与聊天面板的协调。
  • 菜单内容与上下文菜单:提供主菜单、会话注入、设备节点展示、用量与计费信息等动态内容。
  • 图标渲染与状态指示:基于状态生成菜单栏图标,包含动画与徽章提示。
  • 面板与悬浮窗:提供无边框面板承载聊天,以及悬停 HUD 快速预览工作状态。
  • 设置与自定义:提供多标签设置窗口,支持权限、通道、语音唤醒、实例、会话、Cron、技能、调试与关于等。
graph TB
subgraph "应用层"
App["OpenClawApp<br/>MenuBar.swift"]
Delegate["AppDelegate<br/>MenuBar.swift"]
Settings["SettingsRootView<br/>SettingsRootView.swift"]
end
subgraph "菜单与上下文"
MenuContent["MenuContent<br/>MenuContentView.swift"]
Sessions["MenuSessionsInjector<br/>MenuSessionsInjector.swift"]
ContextCard["MenuContextCardInjector<br/>MenuContextCardInjector.swift"]
end
subgraph "图标与状态"
StatusLabel["CritterStatusLabel<br/>CritterStatusLabel.swift"]
IconState["IconState<br/>IconState.swift"]
IconRenderer["CritterIconRenderer<br/>CritterIconRenderer.swift"]
end
subgraph "面板与HUD"
HoverHUD["HoverHUDController<br/>HoverHUD.swift"]
PanelFactory["OverlayPanelFactory<br/>OverlayPanelFactory.swift"]
WebChat["WebChatManager<br/>WebChatManager.swift"]
end
App --> MenuContent
App --> StatusLabel
StatusLabel --> IconRenderer
MenuContent --> Sessions
MenuContent --> Settings
App --> HoverHUD
HoverHUD --> PanelFactory
HoverHUD --> WebChat
App --> WebChat
App --> Delegate

核心组件

  • OpenClawApp:应用主体,定义菜单栏场景(MenuBarExtra),绑定状态与更新控制器,处理菜单呈现状态变化与悬浮 HUD 抑制策略。
  • MenuContent:主菜单视图,包含连接状态切换、心跳发送、浏览器控制、相机授权、执行审批模式、画布开关、语音唤醒、仪表盘与聊天入口、调试菜单、设置与关于、退出等。
  • CritterStatusLabel:状态栏图标组件,根据状态渲染动画与徽章,支持闪烁、摆动、耳部动画与庆祝效果。
  • CritterIconRenderer:图标绘制引擎,生成模板化图标,支持身体、耳朵、腿部、眼睛与徽章绘制,并进行抗锯齿与透明度处理。
  • IconState:图标状态模型,区分空闲、主要工作、其他工作与覆盖状态,提供徽章符号与显著性。
  • MenuSessionsInjector:菜单注入器,动态向菜单插入会话列表、用量统计、计费图表与设备节点,支持宽度缓存与后台刷新。
  • HoverHUDController:悬停 HUD 控制器,提供悬停延时显示、面板悬停检测、点击展开聊天、全局点击外区域自动隐藏等功能。
  • OverlayPanelFactory:无边框面板工厂,统一创建、动画呈现、帧调整与隐藏逻辑。
  • WebChatManager:聊天面板管理器,支持窗口与面板两种呈现模式,提供锚点定位与可见性回调。
  • SettingsRootView:设置根视图,多标签页组织,支持权限监控、调试标签按需显示、Nix 模式提示等。

架构总览

菜单栏控制界面采用“场景驱动 + 动态注入 + 状态驱动”的架构:

  • 场景驱动:通过 MenuBarExtra 定义菜单栏入口,状态绑定驱动图标与菜单行为。
  • 动态注入:MenuSessionsInjector 在菜单打开时注入会话、用量、计费与设备节点,保持菜单宽度稳定与后台刷新。
  • 状态驱动:IconState 与 AppState 决定图标状态、动画与菜单项可用性;HoverHUD 与 WebChatManager 协调面板与 HUD 的显示与隐藏。
sequenceDiagram
participant 用户 as "用户"
participant 状态栏 as "状态栏按钮"
participant 应用 as "OpenClawApp"
participant 菜单 as "MenuContent"
participant 注入器 as "MenuSessionsInjector"
participant HUD as "HoverHUDController"
participant 面板 as "WebChatManager"
用户->>状态栏 : 左键点击
状态栏->>应用 : 触发左键回调
应用->>面板 : 切换聊天面板
面板-->>应用 : 可见性变更回调
应用->>HUD : 抑制悬浮显示
用户->>状态栏 : 右键点击
状态栏->>应用 : 触发右键回调
应用->>应用 : 绑定 isMenuPresented = true
应用->>菜单 : 打开菜单
菜单->>注入器 : 菜单即将打开
注入器->>注入器 : 缓存/刷新数据
注入器-->>菜单 : 注入会话/用量/设备
用户->>状态栏 : 悬停
状态栏->>HUD : 悬停进入
HUD->>HUD : 延时显示
HUD-->>用户 : 展示悬浮 HUD
用户->>HUD : 点击
HUD->>面板 : 展开聊天面板

详细组件分析

菜单栏图标与状态指示器

  • 图标生成:CritterIconRenderer 使用位图与路径绘制,确保 Retina 下清晰锐利;支持身体、耳朵、腿部、眼睛与徽章绘制,并启用模板渲染以适配浅色/深色模式。
  • 状态映射:IconState 决定徽章符号与显著性,Idle、WorkingMain、WorkingOther、Overridden 四种状态;BadgeProminence 控制徽章尺寸与对比度。
  • 动画与闪烁:CritterStatusLabel 管理眨眼、摆动、耳部与腿部动画参数,结合 AppState 控制是否启用动画与睡眠状态。
classDiagram
class IconState {
+idle
+workingMain(ActivityKind)
+workingOther(ActivityKind)
+overridden(ActivityKind)
+badgeSymbolName : String
+badgeProminence : BadgeProminence
+isWorking : Bool
}
class CritterIconRenderer {
+makeIcon(blink, legWiggle, earWiggle, earScale, earHoles, eyesClosedLines, badge) NSImage
-drawBody()
-drawFace()
-drawBadge()
}
class CritterStatusLabel {
+isPaused : Bool
+isSleeping : Bool
+isWorking : Bool
+earBoostActive : Bool
+blinkTick : Int
+sendCelebrationTick : Int
+gatewayStatus
+animationsEnabled : Bool
+iconState : IconState
}
IconState --> CritterIconRenderer : "决定徽章与状态"
CritterStatusLabel --> IconState : "消费状态"
CritterStatusLabel --> CritterIconRenderer : "生成图标"

主菜单与上下文菜单

  • 主菜单结构:包含连接状态切换、心跳发送、浏览器控制、相机授权、执行审批模式、画布开关、语音唤醒、仪表盘、聊天、Talk Mode、设置、调试菜单、关于与退出。
  • 上下文菜单注入:MenuSessionsInjector 在菜单打开时注入会话头、会话列表、用量与计费图表、设备节点与更多设备菜单,支持宽度缓存与后台刷新,避免频繁布局抖动。
  • 菜单项高亮:MenuItemHighlightColors 提供高亮与非高亮颜色方案,保证在选中状态下仍可读。
flowchart TD
Start(["菜单即将打开"]) --> InjectHeader["注入会话头部"]
InjectHeader --> CheckSnapshot{"有会话快照?"}
CheckSnapshot --> |是| InjectRows["注入会话行(排序/过滤)"]
CheckSnapshot --> |否| LoadingMsg["显示加载/断连消息"]
InjectRows --> InjectUsage["注入用量头部与行"]
InjectUsage --> InjectCost["注入计费图表子菜单"]
InjectCost --> InjectNodes["注入设备节点与更多设备"]
InjectNodes --> End(["完成"])
LoadingMsg --> End

悬浮 HUD 与聊天面板

  • 悬浮 HUD:HoverHUDController 提供悬停延时显示、面板悬停检测、点击展开聊天、全局点击外区域自动隐藏与动画过渡。
  • 聊天面板:WebChatManager 支持窗口与面板两种呈现模式,面板具备锚点定位与可见性回调,适配菜单栏按钮位置。
  • 面板工厂:OverlayPanelFactory 统一创建无边框面板、动画呈现与帧调整,保证跨屏幕与多分辨率兼容。
sequenceDiagram
participant 状态栏 as "状态栏按钮"
participant HUD as "HoverHUDController"
participant 工厂 as "OverlayPanelFactory"
participant 面板 as "WebChatManager"
状态栏->>HUD : 悬停进入
HUD->>HUD : 启动延时任务
HUD->>HUD : 延时后检查悬停状态
HUD->>工厂 : 创建面板并动画呈现
工厂-->>HUD : 面板可见
HUD->>面板 : 展示聊天面板(锚点定位)
用户->>HUD : 点击HUD
HUD->>面板 : 切换到聊天面板

设置与自定义

  • 多标签设置:SettingsRootView 提供通用、通道、语音唤醒、配置、实例、会话、Cron、技能、权限、调试与关于等标签页。
  • 权限监控:在权限标签页启用时,周期性刷新权限状态,便于用户确认授权。
  • 调试标签:仅在调试模式开启时显示,包含健康检查、心跳发送、远程隧道重置、日志与重启等调试能力。
  • Nix 模式提示:在 Nix 环境下显示配置与状态目录路径,便于用户识别。

通知与覆盖层

  • 通知覆盖层:NotifyOverlay 提供覆盖层弹窗,支持首次出现动画、窗口定位与自动隐藏,适合在菜单栏附近展示简短通知。
  • 通知生命周期:通过 dismiss 任务与窗口动画,确保覆盖层在合适时机消失且不影响菜单栏交互。

依赖关系分析

  • 组件耦合与内聚:
    • OpenClawApp 与 MenuContent 通过状态绑定强关联,确保 UI 与业务状态一致。
    • MenuSessionsInjector 与 ControlChannel、SessionLoader、NodesStore 解耦,通过观察与缓存机制降低菜单打开时的阻塞。
    • HoverHUDController 与 WebChatManager 通过回调与可见性状态解耦,避免直接耦合。
  • 外部依赖与集成点:
    • MenuBarExtraAccess 提供菜单栏额外访问能力。
    • Sparkle 更新器在签名条件下启用,否则使用禁用控制器。
    • 系统事件:全局鼠标按下监听用于 HUD 自动隐藏,窗口层级与集合行为确保面板始终可见且不抢夺焦点。
graph LR
OpenClawApp["OpenClawApp"] --> MenuContent["MenuContent"]
OpenClawApp --> HoverHUD["HoverHUDController"]
OpenClawApp --> WebChat["WebChatManager"]
MenuContent --> Sessions["MenuSessionsInjector"]
HoverHUD --> PanelFactory["OverlayPanelFactory"]
WebChat --> PanelFactory
OpenClawApp --> Sparkle["SparkleUpdaterController"]
OpenClawApp --> MBEA["MenuBarExtraAccess"]

性能考虑

  • 图标渲染优化:使用 36×36 像素位图作为 Retina 后备缓冲,避免缩放失真;禁用抗锯齿与模板渲染提升清晰度。
  • 菜单注入缓存:MenuSessionsInjector 缓存会话、用量与计费数据,限定刷新间隔,菜单打开时仅做增量更新与宽度缓存,减少布局抖动。
  • 异步与取消:所有网络与 IO 操作均使用 Task 并在菜单关闭或状态变化时及时取消,避免资源泄漏。
  • HUD 延时与动画:悬停延时与短时动画减少不必要的 UI 更新,全局事件监听仅在需要时安装。
  • 面板复用:WebChatManager 对面板控制器进行缓存,避免重复初始化带来的启动延迟。

macOS 应用

OpenClaw 的 macOS 应用位于 apps/macos 目录,采用 Swift Package Manager 组织多目标产物:菜单栏可执行程序、IPC 库、发现库、以及一个 CLI 工具。Swabble 作为语音唤醒与转写能力的核心模块被集成进来;同时通过 Sparkle 实现更新分发,Peekaboo 提供系统级自动化桥接能力。

graph TB
subgraph "macOS 应用包"
OC["OpenClaw 可执行程序"]
IPC["OpenClawIPC 库"]
DISC["OpenClawDiscovery 库"]
CLI["OpenClawMacCLI 可执行程序"]
end
subgraph "外部依赖"
SWABBLE["Swabble 核心与工具集"]
SPARKLE["Sparkle 更新框架"]
MBX["MenuBarExtraAccess 菜单栏扩展"]
SUBPROC["swift-subprocess 子进程"]
LOGGING["swift-log 日志"]
PEEK["Peekaboo 桥接"]
end
OC --> IPC
OC --> DISC
OC --> SWABBLE
OC --> SPARKLE
OC --> MBX
OC --> SUBPROC
OC --> LOGGING
OC --> PEEK
CLI --> DISC
CLI --> SWABBLE

核心组件

  • 菜单栏控制界面:基于 MenuBarExtraAccess 构建,提供快速入口与状态指示,支持与主应用交互。
  • 语音唤醒与转写:Swabble 提供唤醒词检测、音频缓冲转换、实时转写与会话存储。
  • WebChat 聊天界面:通过 OpenClawChatUI 集成,提供网页聊天体验并与后端协议对接。
  • 后台服务与 IPC:OpenClawIPC 提供跨进程通信能力,OpenClawDiscovery 负责设备/服务发现。
  • 更新与分发:Sparkle 驱动自动更新,配合签名与公证流程实现安全分发。
  • 系统集成:Peekaboo 桥接系统自动化能力,日志与子进程管理提升稳定性。

架构总览

下图展示 macOS 应用从启动到功能运行的关键路径:菜单栏入口触发主逻辑,Swabble 处理语音输入,IPC 与协议层连接后端,Sparkle 负责更新,Peekaboo 提供系统级能力。

graph TB
MB["菜单栏入口<br/>MenuBarExtraAccess"] --> APP["OpenClaw 主程序"]
APP --> WAKE["Swabble 语音唤醒<br/>WakeWordGate"]
WAKE --> PIPE["音频管线<br/>SpeechPipeline"]
PIPE --> BUF["缓冲转换<br/>BufferConverter"]
BUF --> TR["转写与会话<br/>TranscriptsStore"]
APP --> IPC["OpenClawIPC"]
IPC --> PROTO["OpenClaw 协议层"]
APP --> UI["WebChat 界面<br/>OpenClawChatUI"]
APP --> SPK["Sparkle 更新"]
APP --> PEE["Peekaboo 桥接"]
APP --> LOG["日志与监控"]

详细组件分析

菜单栏控制界面

  • 设计目标:在菜单栏提供最小化占用的控制入口,承载状态显示与常用操作。
  • 关键点:使用 MenuBarExtraAccess 构建,结合主程序状态动态更新菜单项,避免阻塞主线程。
  • 交互流程:点击菜单项触发主程序逻辑,如打开 WebChat、切换录音状态或查看健康状态。
sequenceDiagram
participant U as "用户"
participant MB as "菜单栏"
participant APP as "OpenClaw 主程序"
U->>MB : 点击菜单图标
MB->>APP : 触发菜单事件
APP->>APP : 更新状态/打开界面
APP-->>U : 展示结果/反馈

语音唤醒功能

  • 唤醒词检测:SwabbleKit 的 WakeWordGate 提供轻量级唤醒词门控,降低误触发。
  • 音频管线:SpeechPipeline 负责持续采集与预处理,BufferConverter 将音频缓冲标准化以便后续处理。
  • 会话存储:TranscriptsStore 记录转写片段,支持回放与上下文构建。
  • 命令行工具:CLI 提供 mic/list、mic/set、service/install 等命令,便于开发调试与自动化。
flowchart TD
Start(["开始监听"]) --> Detect["唤醒词检测"]
Detect --> |未触发| Wait["继续等待"]
Detect --> |触发| Pipeline["音频管线处理"]
Pipeline --> Convert["缓冲转换"]
Convert --> Transcribe["实时转写"]
Transcribe --> Store["会话存储"]
Store --> Notify["通知主程序"]
Wait --> Detect
Notify --> End(["结束一轮"])

WebChat 聊天界面

  • 集成方式:通过 OpenClawChatUI 提供网页聊天界面,与后端协议层对接实现消息收发。
  • 控制流:主程序负责初始化 UI、建立连接、转发用户输入与系统事件,保持界面响应性。
  • 适配策略:针对不同分辨率与主题模式进行布局与样式适配,确保一致的用户体验。
sequenceDiagram
participant U as "用户"
participant UI as "WebChat 界面"
participant IPC as "OpenClawIPC"
participant PROTO as "协议层"
U->>UI : 输入消息/发送
UI->>IPC : 发送消息请求
IPC->>PROTO : 转发至后端
PROTO-->>IPC : 返回响应
IPC-->>UI : 渲染消息/状态
UI-->>U : 展示结果

系统集成特性

  • 自动化桥接:Peekaboo 桥接系统自动化能力,支持与系统服务交互。
  • 日志与监控:swift-log 提供统一日志输出,便于问题定位与性能观测。
  • 子进程管理:swift-subprocess 管理外部进程生命周期,保证稳定性与可控性。
graph TB
APP["OpenClaw 主程序"] --> PEE["Peekaboo 桥接"]
APP --> LOG["swift-log 日志"]
APP --> SUB["swift-subprocess 子进程"]
PEE --> SYS["系统服务/自动化"]
LOG --> MON["监控与诊断"]
SUB --> EXT["外部工具/服务"]

依赖关系分析

  • 内部模块:OpenClaw 依赖 OpenClawIPC、OpenClawDiscovery、OpenClawChatUI、OpenClawProtocol 等内部产品。
  • 外部模块:Swabble 提供语音相关能力;Sparkle 负责更新;MenuBarExtraAccess 提供菜单栏扩展;Peekaboo 提供系统桥接;swift-log 与 swift-subprocess 提供日志与子进程能力。
  • 版本与平台:最低 macOS 版本要求在 Package 中声明,Swabble 对新版本 macOS 有明确可用性标注。
graph LR
OC["OpenClaw"] --> IPC["OpenClawIPC"]
OC --> DISC["OpenClawDiscovery"]
OC --> UI["OpenClawChatUI"]
OC --> PROTO["OpenClawProtocol"]
OC --> SWAB["Swabble"]
OC --> SPK["Sparkle"]
OC --> MBX["MenuBarExtraAccess"]
OC --> PEE["Peekaboo"]
OC --> LOG["swift-log"]
OC --> SUB["swift-subprocess"]

性能考虑

  • 低延迟唤醒:WakeWordGate 与 SpeechPipeline 应尽量减少预处理开销,避免阻塞主线程。
  • 缓冲与内存:BufferConverter 与 TranscriptsStore 需要合理设置缓冲大小与清理策略,防止内存膨胀。
  • 线程模型:遵循 Swift 并发模型,避免在主线程执行耗时任务,使用后台队列处理音频与网络。
  • I/O 优化:IPC 与协议层应批量处理消息,减少频繁的小数据包传输。
  • 日志级别:生产环境降低日志级别,仅保留关键信息,避免磁盘与 CPU 开销。

系统集成特性

macOS 相关实现主要集中在 apps/macos 工程中,采用多目标组织方式:

  • 可执行目标 OpenClaw:菜单栏应用主体
  • 库目标 OpenClawIPC、OpenClawDiscovery:跨进程通信与发现能力
  • CLI 目标 OpenClawMacCLI:命令行工具
  • 测试目标 OpenClawIPCTests:测试套件
graph TB
subgraph "macOS 工程"
A["OpenClaw<br/>菜单栏应用"]
B["OpenClawIPC<br/>IPC 库"]
C["OpenClawDiscovery<br/>发现库"]
D["OpenClawMacCLI<br/>CLI 工具"]
E["OpenClawIPCTests<br/>测试套件"]
end
subgraph "外部依赖"
S["Sparkle<br/>自动更新"]
M["MenuBarExtraAccess<br/>菜单栏扩展"]
L["Logging<br/>日志"]
P["Peekaboo<br/>桥接/自动化"]
end
A --> B
A --> C
A --> D
A --> S
A --> M
A --> L
A --> P
E --> B
E --> A
E --> C

核心组件

  • 权限管理器:统一处理各类系统权限的检查、请求与状态监控
  • 设置界面:集中展示与管理权限、位置访问模式、自动更新等
  • 后台服务与事件:LaunchAgent 生命周期、心跳与系统事件过滤
  • 自动更新:Sparkle 控制器、签名检测、发布脚本
  • 系统设置跳转:便捷打开系统隐私与安全设置

架构总览

下图展示 macOS 端系统集成的关键交互:菜单栏应用、权限管理、后台服务、自动更新与系统设置。

graph TB
subgraph "用户空间"
UI["菜单栏应用<br/>MenuBar.swift"]
SET["设置界面<br/>SettingsRootView.swift"]
PERM["权限管理器<br/>PermissionManager.swift"]
HELP["系统设置跳转<br/>SystemSettingsURLSupport.swift"]
end
subgraph "系统服务"
LA["LaunchAgent<br/>launchd.ts"]
SYS["系统权限/设置"]
UPD["Sparkle 更新<br/>make_appcast.sh"]
end
subgraph "外部库"
SPK["Sparkle"]
MBE["MenuBarExtraAccess"]
LOG["Logging"]
PBO["Peekaboo"]
end
UI --> PERM
UI --> SET
PERM --> SYS
SET --> HELP
UI --> LA
UI --> UPD
UI --> SPK
UI --> MBE
UI --> LOG
UI --> PBO

详细组件分析

权限管理与用户授权

  • 统一入口:PermissionManager 提供权限检查、请求与状态查询
  • 支持能力:通知、AppleScript、无障碍、屏幕录制、麦克风、语音识别、摄像头、位置
  • 交互策略:非交互模式仅返回当前状态;交互模式触发系统授权对话或引导至系统设置
  • 状态监控:PermissionMonitor 定时轮询并缓存状态,避免频繁调用系统 API
  • 系统设置跳转:针对不同权限类别提供便捷链接,快速打开系统隐私与安全设置
classDiagram
class PermissionManager {
+ensure(caps, interactive) [Capability : Bool]
+ensureNotifications(interactive) Bool
+ensureAppleScript(interactive) Bool
+ensureAccessibility(interactive) Bool
+ensureScreenRecording(interactive) Bool
+ensureMicrophone(interactive) Bool
+ensureSpeechRecognition(interactive) Bool
+ensureCamera(interactive) Bool
+ensureLocation(interactive) Bool
+status(caps) [Capability : Bool]
}
class PermissionMonitor {
+register()
+unregister()
+refreshNow()
-startMonitoring()
-stopMonitoring()
-checkStatus(force)
}
class SystemSettingsURLSupport {
+openFirst(urls)
}
PermissionManager --> SystemSettingsURLSupport : "打开系统设置"
PermissionMonitor --> PermissionManager : "轮询状态"

权限设置界面与位置访问

  • 集中式权限面板:显示各能力授权状态、一键请求、刷新按钮
  • 位置访问控制:支持关闭、使用期间、始终三种模式,并可选择精确位置
  • 用户体验:在切换模式后自动尝试授权,失败时引导至系统设置
flowchart TD
Start(["进入权限设置"]) --> ShowCaps["展示各能力状态"]
ShowCaps --> ChooseMode{"选择位置模式"}
ChooseMode --> |Off| Done["保持关闭"]
ChooseMode --> |WhileUsing/Always| Request["请求授权"]
Request --> Granted{"已授权?"}
Granted --> |是| Done
Granted --> |否| OpenPrefs["打开系统设置"]
OpenPrefs --> Revert["回滚到上一模式"]
Revert --> Done

后台服务机制与系统事件监听

  • LaunchAgent 管理:安装、停止、重启、修复引导,支持保留 umask 与节流
  • 心跳与系统事件:基于文件系统的事件队列,区分执行完成、定时任务等事件类型
  • 运行时事件桥接:通过运行时接口向系统发送通知
sequenceDiagram
participant User as "用户"
participant App as "菜单栏应用"
participant Daemon as "LaunchAgent"
participant FS as "系统事件文件"
participant Runner as "心跳运行器"
User->>App : 打开设置/触发动作
App->>Daemon : 安装/重启/停止
Daemon-->>FS : 写入系统事件
Runner->>FS : 轮询/读取事件
Runner->>Runner : 过滤执行完成/定时任务事件
Runner-->>App : 处理结果/触发后续动作

自动更新机制与发布流程

  • Sparkle 集成:根据签名状态启用/禁用自动更新控制器
  • 发布脚本:生成 appcast,嵌入发布说明,签名更新包
  • 版本与下载前缀:从 zip 文件名推断版本,支持预发布格式
sequenceDiagram
participant Dev as "开发者"
participant Script as "make_appcast.sh"
participant Sparkle as "Sparkle 工具"
participant Repo as "发布仓库"
Dev->>Script : 传入 zip 与密钥
Script->>Sparkle : generate_appcast
Sparkle-->>Script : 生成 appcast.xml
Script->>Repo : 写回 appcast.xml
Repo-->>Dev : 可用的更新源

系统启动项配置

  • LaunchAgent 安装:写入 plist,设置 KeepAlive、umask、节流间隔
  • 重启顺序:bootout -> unload -> 删除旧 plist -> 写新 plist -> bootstrap -> kickstart
  • attach-only 模式:禁用 LaunchAgent 写入,避免自动启动
flowchart TD
Start(["安装/重启 LaunchAgent"]) --> StopOld["bootout + unload 旧 Agent"]
StopOld --> Cleanup["删除旧 plist"]
Cleanup --> WriteNew["写入新 plist"]
WriteNew --> Bootstrap["bootstrap 新 Agent"]
Bootstrap --> Kickstart["kickstart -k"]
Kickstart --> Done(["完成"])

系统版本兼容性

  • 最低系统版本:macOS 15.0
  • 平台约束:Swift 包定义中指定最低版本
  • 权限 API 兼容:对较老版本进行降级处理(如屏幕录制)

系统通知集成、Spotlight 支持与快速查看

  • 系统通知:通过运行时接口发送系统通知,支持优先级与投递方式
  • Spotlight/快速查看:本仓库未提供直接实现,建议结合 Info.plist 中的使用说明描述与系统框架进行扩展(概念性说明)

依赖关系分析

  • 包依赖:Sparkle、MenuBarExtraAccess、Logging、Peekaboo 等
  • 目标耦合:OpenClaw 主目标依赖 IPC、Discovery、Kit、Swabble 等产品库
  • 测试依赖:测试目标依赖 IPC 与协议库
graph LR
OpenClaw["OpenClaw 目标"] --> IPC["OpenClawIPC"]
OpenClaw --> Discovery["OpenClawDiscovery"]
OpenClaw --> Kit["OpenClawKit"]
OpenClaw --> Protocol["OpenClawProtocol"]
OpenClaw --> Swabble["SwabbleKit"]
OpenClaw --> MBE["MenuBarExtraAccess"]
OpenClaw --> Subproc["Subprocess"]
OpenClaw --> Logging["Logging"]
OpenClaw --> Sparkle["Sparkle"]
OpenClaw --> Peekaboo["Peekaboo"]
OpenClaw --> PKit["PeekabooAutomationKit"]

性能考量

  • 权限轮询节流:PermissionMonitor 使用最小检查间隔,避免频繁调用系统 API
  • 后台服务稳定性:LaunchAgent 采用 KeepAlive 与节流参数,减少资源占用
  • 心跳事件过滤:仅处理必要事件,跳过空心跳与执行完成噪声
  • 日志与可观测性:引入 Logging,便于定位问题

应用打包与分发

围绕 macOS 打包的核心脚本与配置位于 scripts/ 与 apps/macos/ 目录中,CI 流程由 .github/workflows/ci.yml 驱动。下图展示与打包分发直接相关的文件与职责:

graph TB
subgraph "脚本层"
P["package-mac-app.sh<br/>构建与打包.app"]
S["codesign-mac-app.sh<br/>代码签名"]
N["notarize-mac-artifact.sh<br/>公证与贴签"]
D["create-dmg.sh<br/>制作 DMG"]
PD["package-mac-dist.sh<br/>打包 zip+DMG+公证"]
MA["make_appcast.sh<br/>生成 appcast.xml"]
BI["build_icon.sh<br/>生成.icns"]
SB["sparkle-build.ts<br/>版本映射工具"]
end
subgraph "应用定义"
PSW["apps/macos/Package.swift<br/>产品与资源声明"]
PMD["apps/macos/README.md<br/>打包与签名说明"]
end
subgraph "CI"
CI["ci.yml<br/>macOS 检查流水线"]
end
P --> S --> N --> D
P --> BI
P --> PSW
PD --> N
PD --> D
MA --> CI
SB --> P
CI --> PSW

核心组件

  • 应用包构建与装配:负责 Swift 产物构建、Info.plist 注入、资源复制、签名与 Sparkle 嵌入。
  • 代码签名:自动选择证书、注入权限、校验 Team ID、支持临时签名与时间戳策略。
  • 公证与贴签:提交 zip/dmg/pkg 至 Apple 公证服务,必要时对 app 与 DMG 进行贴签验证。
  • DMG 制作:生成带背景、图标布局与 Applications 快捷方式的最终分发镜像。
  • 更新通道:通过 Sparkle 生成 appcast.xml 并嵌入发布说明。
  • CI 集成:在 macOS runner 上执行 Swift 构建、测试与覆盖率检查。

架构总览

下图展示从源码到分发产物的端到端流程,包括本地开发与 CI 两条路径:

sequenceDiagram
participant Dev as "开发者/CI"
participant Build as "package-mac-app.sh"
participant Sign as "codesign-mac-app.sh"
participant Notarize as "notarize-mac-artifact.sh"
participant DMG as "create-dmg.sh"
participant Appcast as "make_appcast.sh"
Dev->>Build : 触发打包
Build->>Build : 构建 Swift 产物/复制资源/写入 Info.plist
Build->>Sign : 传入 .app 进行签名
Sign-->>Build : 返回签名结果
Build-->>Dev : 产出 dist/OpenClaw.app
Dev->>Notarize : 提交 zip/dmg/pkg 公证
Notarize-->>Dev : 返回公证状态/贴签
Dev->>DMG : 生成 DMG含背景与布局
DMG-->>Dev : 输出 .dmg
Dev->>Appcast : 生成 appcast.xml 并上传
Appcast-->>Dev : appcast.xml 就绪

组件详解

应用包结构与资源装配

  • 包结构:dist/OpenClaw.app/Contents 下包含 MacOS、Resources、Frameworks、Info.plist。
  • 资源复制:图标、设备模型、Textual 资源包、OpenClawKit 资源包等。
  • Info.plist 注入:设置 Bundle ID、版本号、构建号、Sparkle 更新地址与公钥、自动检查开关等。
  • 多架构合并:若构建多架构,使用 lipo 合并 Sparkle.framework 与主二进制。
flowchart TD
Start(["开始"]) --> Clean["清理旧 .app 目录"]
Clean --> Mkdir["创建 Contents/MacOS/Resources/Frameworks"]
Mkdir --> CopyPlist["复制 Info.plist 模板并写入键值"]
CopyPlist --> CopyBin["复制主二进制并处理多架构"]
CopyBin --> EmbedSparkle["复制并合并 Sparkle.framework"]
EmbedSparkle --> CopyRes["复制图标/模型/Textual/OpenClawKit 资源"]
CopyRes --> End(["完成"])

Info.plist 配置要点

  • 关键键值:
    • CFBundleIdentifier:用于签名与权限持久化
    • CFBundleShortVersionString:显示版本
    • CFBundleVersion:Sparkle 比较用的构建号(需为纯数字且单调递增)
    • OpenClawBuildTimestamp / OpenClawGitCommit:构建元数据
    • SUFeedURL / SUPublicEDKey:Sparkle 更新通道
    • SUEnableAutomaticChecks:自动检查开关
  • 版本映射:当使用日期型语义版本时,脚本通过工具计算 Sparkle 可归一化的构建号。

图标资源管理

  • 生成流程:从 .icon 资源导出多尺寸 PNG,再合成 .icns,放置于 Resources/OpenClaw.icns。
  • 脚本支持自定义目标路径与 Xcode 路径,便于在 CI 中复用。

代码签名流程与权限策略

  • 自动选择签名身份:优先 Developer ID Application,其次 Apple Distribution,再 Apple Development,最后首个可用。
  • 权限注入:为应用注入自动化、音频、相机、位置等权限键。
  • Team ID 校验:签名后遍历所有 Mach-O,确保与主包 Team ID 一致,避免加载失败。
  • 临时签名:允许使用 ad-hoc(-)签名,但会禁用 runtime 选项并导致 TCC 权限不持久。
  • 时间戳策略:根据证书类型自动启用或关闭时间戳。
flowchart TD
A["选择签名身份"] --> B{"身份为空?"}
B -- 是 --> C["尝试 Developer ID Application"]
C --> D{"找到?"}
D -- 否 --> E["尝试 Apple Distribution"]
E --> F{"找到?"}
F -- 否 --> G["尝试 Apple Development"]
G --> H{"找到?"}
H -- 否 --> I["使用首个可用身份或报错"]
B -- 否 --> J["使用指定身份"]
J --> K["注入权限与签名参数"]
K --> L["签名主二进制"]
L --> M["深度签名 Sparkle 框架"]
M --> N["签名其他 Frameworks/Dylibs"]
N --> O["签名 .app 包"]
O --> P{"Team ID 一致?"}
P -- 否 --> Q["报错并退出"]
P -- 是 --> R["完成"]

Gatekeeper 验证与公证

  • Gatekeeper:要求应用具备有效签名与可识别的 Team ID,且无未签名嵌入组件。
  • 公证:通过 notarytool 提交 zip/dmg/pkg,等待 Apple 审核通过后返回票据。
  • 贴签:对 DMG 与 app 进行 stapler 贴签,确保离线验证成功。
sequenceDiagram
participant Dev as "开发者"
participant Zip as "zip/dmg/pkg"
participant Notary as "Apple Notary Service"
participant Stapler as "stapler"
Dev->>Zip : 准备待公证产物
Dev->>Notary : 提交公证凭配置的凭据
Notary-->>Dev : 返回公证状态
alt 需要贴签
Dev->>Stapler : 对产物与 app 进行贴签
Stapler-->>Dev : 验证通过
end

DMG 制作与分发镜像

  • 功能:创建带背景、图标布局、Applications 快捷方式的 DMG,自动调整窗口大小与图标位置。
  • 可定制:窗口边界、图标尺寸、背景图、额外扇区等。
  • 验证:对最终 DMG 进行完整性校验。

更新通道与 appcast.xml

  • 生成:解析 zip 名称推断版本,生成 HTML 发布说明,调用 Sparkle 工具生成 appcast.xml。
  • 上传:将 appcast.xml 与 zip 一同发布至指定链接。
  • 依赖:需要 Sparkle 工具链在 PATH 中可用。

CI/CD 集成与自动化

  • macOS 检查:在单个 runner 上顺序执行 TS 测试、Swift lint/format、Swift 构建与测试。
  • 缓存:缓存 SwiftPM 依赖,提升重复构建速度。
  • 并发:macOS 并发作业数有限,合并为单一作业以提高队列利用率。

依赖关系分析

  • 脚本间耦合:
    • package-mac-app.sh 依赖 codesign-mac-app.sh 完成签名。
    • package-mac-dist.sh 串联 zip、公证与 DMG 制作。
    • make_appcast.sh 依赖 sparkle-build.ts 计算构建号。
  • 应用定义:
    • apps/macos/Package.swift 声明产品、依赖与资源复制规则,影响打包阶段的资源装配。
graph LR
P["package-mac-app.sh"] --> S["codesign-mac-app.sh"]
P --> PSW["apps/macos/Package.swift"]
PD["package-mac-dist.sh"] --> N["notarize-mac-artifact.sh"]
PD --> D["create-dmg.sh"]
MA["make_appcast.sh"] --> SB["sparkle-build.ts"]

性能与可靠性考量

  • 多架构构建:默认按当前架构构建,发布时建议统一为 arm64 x86_64,减少用户下载体积与兼容性问题。
  • 缓存策略:SwiftPM 缓存与 UI 构建缓存可显著缩短 CI 时间。
  • 公证等待:公证可能成为瓶颈,建议在 CI 中并行化其他任务,公证完成后集中处理贴签与 DMG 制作。
  • 资源复制:避免重复拷贝与权限变更,减少打包时间。

语音唤醒功能

语音唤醒功能在项目中的组织结构如下:

graph TB
subgraph "macOS 应用层"
A[VoiceWakeRuntime] -- "实时唤醒监听" --> B[VoiceWakeTester]
A -- "音频处理" --> C[AVAudioEngine]
A -- "识别结果" --> D[Speech.framework]
E[VoiceWakeOverlayController] -- "UI 展示" --> F[VoiceSessionCoordinator]
G[VoiceWakeForwarder] -- "消息转发" --> H[GatewayConnection]
end
subgraph "Swabble 核心层"
I[WakeWordGate] -- "唤醒词匹配" --> J[WakeWordSegment]
K[SwabbleKit] -- "跨平台支持" --> L[多平台复用]
end
subgraph "网关服务层"
M[voicewake.ts] -- "配置管理" --> N[voicewake.json]
O[GatewayRPC] -- "状态同步" --> P[WebSocket 广播]
end
subgraph "配置层"
Q[VoiceWakeSettings] -- "用户配置" --> R[全局唤醒词列表]
S[VoiceWakePreferences] -- "偏好设置" --> T[音质参数]
end
A --> I
G --> O
R --> M

核心组件

语音唤醒运行时 (VoiceWakeRuntime)

VoiceWakeRuntime 是整个语音唤醒系统的核心执行组件,负责:

  • 实时音频流处理:通过 AVAudioEngine 实时捕获和处理音频数据
  • 唤醒词检测:使用 WakeWordGate 进行精确的唤醒词匹配
  • 状态管理:维护识别状态、会话管理和错误处理
  • 资源控制:智能启动和停止音频引擎以节省系统资源

唤醒词门控 (WakeWordGate)

WakeWordGate 提供了高级的唤醒词匹配算法:

  • 时间感知匹配:基于语音段的时间戳进行精确匹配
  • 后触发间隔要求:确保唤醒词后有足够的时间间隔才触发
  • 多词支持:支持多个唤醒词及其别名
  • 文本规范化:自动处理大小写、重音符号等字符差异

音频处理管道

系统采用分层的音频处理架构:

flowchart TD
A[麦克风输入] --> B[AVAudioEngine 输入节点]
B --> C[音频缓冲区处理]
C --> D[RMS 声音级别计算]
D --> E[噪声过滤器]
E --> F[Speech.framework 识别]
F --> G[唤醒词匹配]
G --> H[触发事件]
I[音频质量监控] --> D
J[自适应阈值] --> E
K[静音检测] --> H

架构概览

语音唤醒系统的整体架构采用模块化设计,确保各组件间的松耦合和高内聚:

graph TB
subgraph "输入层"
A[麦克风设备] --> B[音频采集]
B --> C[音频格式转换]
end
subgraph "处理层"
C --> D[音频预处理]
D --> E[语音活动检测]
E --> F[实时识别]
F --> G[唤醒词匹配]
end
subgraph "控制层"
G --> H[状态管理]
H --> I[会话协调]
I --> J[UI 更新]
end
subgraph "输出层"
J --> K[语音反馈]
J --> L[消息转发]
J --> M[日志记录]
end
subgraph "配置层"
N[全局配置] --> O[本地设置]
O --> P[用户偏好]
end
P --> H

详细组件分析

语音唤醒运行时实现

VoiceWakeRuntime 采用了 Actor 模式确保线程安全:

classDiagram
class VoiceWakeRuntime {
-recognizer : SFSpeechRecognizer
-audioEngine : AVAudioEngine
-recognitionRequest : SFSpeechAudioBufferRecognitionRequest
-recognitionTask : SFSpeechRecognitionTask
-isCapturing : Bool
-noiseFloorRMS : Double
-lastHeard : Date
+refresh(state : AppState)
+start(with : RuntimeConfig)
+stop()
+handleRecognition(update : RecognitionUpdate)
-beginCapture(command : String)
-monitorCapture(config : RuntimeConfig)
-finalizeCapture(config : RuntimeConfig)
}
class RuntimeConfig {
+triggers : [String]
+micID : String?
+localeID : String?
+triggerChime : VoiceWakeChime
+sendChime : VoiceWakeChime
}
class RecognitionUpdate {
+transcript : String?
+segments : [WakeWordSegment]
+isFinal : Bool
+error : Error?
+generation : Int
}
VoiceWakeRuntime --> RuntimeConfig : "使用"
VoiceWakeRuntime --> RecognitionUpdate : "处理"

音频处理流程

音频处理采用流水线模式:

sequenceDiagram
participant Mic as 麦克风
participant Engine as AVAudioEngine
participant Tap as 音频采样器
participant Recognizer as 语音识别器
participant Gate as 唤醒词门控
participant UI as 用户界面
Mic->>Engine : 音频数据
Engine->>Tap : 缓冲区采样
Tap->>Recognizer : 语音特征
Recognizer->>Gate : 识别结果
Gate->>UI : 触发事件
UI->>UI : 更新状态显示

唤醒词匹配算法

WakeWordGate 实现了复杂的匹配逻辑:

flowchart TD
A[输入语音片段] --> B[文本规范化]
B --> C[唤醒词令牌化]
C --> D[语音段分析]
D --> E{匹配检查}
E --> |找到匹配| F[验证后触发间隔]
E --> |无匹配| G[继续监听]
F --> |间隔不足| H[等待更多语音]
F --> |间隔充足| I[触发唤醒]
H --> D
G --> D
I --> J[开始录音会话]

匹配算法细节

算法的关键参数包括:

  • 最小后触发间隔:默认 0.45 秒,防止误触发
  • 最小命令长度:默认 1 个词,避免短促声音触发
  • 文本规范化:忽略大小写、重音符号和标点符号
  • 时间窗口:基于语音段的时间戳进行精确匹配

音频质量优化

系统实现了多层次的音频质量优化:

graph LR
subgraph "噪声过滤"
A[自适应噪声门限] --> B[RMS 声音级别检测]
B --> C[动态阈值调整]
end
subgraph "音频增强"
D[音频缓冲] --> E[采样率转换]
E --> F[通道格式适配]
end
subgraph "质量监控"
G[实时电平监测] --> H[性能指标记录]
H --> I[自动调优]
end
A --> D
D --> G

音频参数配置

关键的音频参数包括:

  • 最小语音 RMS:1e-3,用于检测语音活动
  • 噪声提升因子:6.0,提高语音检测的灵敏度
  • 缓冲区大小:2048 字节,平衡延迟和性能
  • 采样率:由系统自动选择,确保最佳质量

用户界面集成

语音唤醒功能与用户界面的集成提供了直观的操作体验:

stateDiagram-v2
[*] --> 空闲
空闲 --> 监听中 : 启动语音唤醒
监听中 --> 检测到 : 唤醒词识别
检测到 --> 录音中 : 开始录音
录音中 --> 发送中 : 静音检测
发送中 --> 空闲 : 发送完成
发送中 --> 录音中 : 继续录音
录音中 --> 空闲 : 取消录音
监听中 --> 推话语模式 : 按住右 Option
推话语模式 --> 录音中 : 开始录音
录音中 --> 空闲 : 释放按键

依赖关系分析

语音唤醒功能的依赖关系展现了清晰的分层架构:

graph TB
subgraph "外部依赖"
A[Apple Speech.framework] --> B[语音识别]
C[AVFoundation] --> D[音频处理]
E[Foundation] --> F[系统服务]
end
subgraph "内部模块"
G[VoiceWakeRuntime] --> H[SwabbleKit]
G --> I[VoiceWakeForwarder]
G --> J[VoiceWakeOverlayController]
H --> K[WakeWordGate]
I --> L[GatewayConnection]
J --> M[VoiceSessionCoordinator]
end
subgraph "配置管理"
N[VoiceWakeSettings] --> O[全局配置]
O --> P[本地存储]
P --> Q[voicewake.json]
end
G --> N
H --> N
I --> N

数据流分析

语音唤醒的数据流遵循严格的处理顺序:

sequenceDiagram
participant User as 用户
participant Runtime as 语音唤醒运行时
participant Gate as 唤醒词门控
participant Forwarder as 消息转发器
participant Gateway as 网关服务
User->>Runtime : 语音输入
Runtime->>Gate : 识别结果
Gate->>Gate : 唤醒词匹配
Gate->>Runtime : 匹配成功
Runtime->>Forwarder : 转发请求
Forwarder->>Gateway : RPC 调用
Gateway-->>Forwarder : 执行结果
Forwarder-->>Runtime : 处理完成
Runtime-->>User : 反馈响应

性能考虑

语音唤醒功能在性能方面采用了多项优化策略:

内存管理优化

  • 延迟初始化:AVAudioEngine 仅在需要时创建,避免应用启动时占用音频资源
  • 自动资源回收:空闲时自动释放音频引擎和相关资源
  • 内存池管理:使用固定大小的缓冲区减少内存分配开销

处理效率优化

  • 异步处理:所有音频处理采用异步模式,避免阻塞主线程
  • 批处理优化:音频缓冲区批量处理,减少回调频率
  • 智能重启:失败时自动重启识别器,确保稳定性

系统资源优化

  • 蓝牙耳机保护:避免在 Voice Wake 关闭时切换到低质量模式
  • CPU 使用率控制:根据音频活动动态调整处理强度
  • 电池优化:在移动设备上自动降低处理频率

WebChat 聊天界面

WebChat 聊天界面主要由两个部分组成:

graph TB
subgraph "macOS 应用层"
A[WebChatSwiftUI.swift] --> B[WebChatManager.swift]
B --> C[WebChatSwiftUIWindowController]
C --> D[OpenClawChatView]
end
subgraph "共享 UI 组件层"
E[ChatView.swift] --> F[ChatViewModel.swift]
F --> G[ChatTransport.swift]
E --> H[ChatMessageViews.swift]
E --> I[ChatTheme.swift]
end
subgraph "网关通信层"
J[GatewayConnection] --> K[WebSocket 连接]
K --> L[chat.history]
K --> M[chat.send]
K --> N[chat.abort]
end
D --> E
C --> D
F --> G
G --> J

核心组件

macOS 窗口控制器

WebChatSwiftUIWindowController 是 macOS 平台的核心控制器,负责管理聊天界面的显示和生命周期:

classDiagram
class WebChatSwiftUIWindowController {
-presentation : WebChatPresentation
-sessionKey : String
-hosting : NSHostingController
-contentController : NSViewController
-window : NSWindow?
-dismissMonitor : Any?
+onClosed : () -> Void
+onVisibilityChanged : (Bool) -> Void
+show()
+presentAnchored(anchorProvider)
+close()
+isVisible : Bool
}
class WebChatPresentation {
<<enumeration>>
window
panel(anchorProvider)
+isPanel : Bool
}
class MacGatewayChatTransport {
+requestHistory(sessionKey)
+sendMessage(sessionKey, message, thinking, idempotencyKey, attachments)
+abortRun(sessionKey, runId)
+listSessions(limit)
+requestHealth(timeoutMs)
+events()
+mapPushToTransportEvent(push)
}
WebChatSwiftUIWindowController --> WebChatPresentation
WebChatSwiftUIWindowController --> MacGatewayChatTransport

聊天视图模型

ChatViewModel 是整个聊天界面的状态管理中心:

classDiagram
class OpenClawChatViewModel {
+messages : [OpenClawChatMessage]
+input : String
+thinkingLevel : String
+isLoading : Bool
+isSending : Bool
+isAborting : Bool
+errorText : String?
+attachments : [OpenClawPendingAttachment]
+healthOK : Bool
+pendingRunCount : Int
+sessionKey : String
+sessionId : String?
+streamingAssistantText : String?
+pendingToolCalls : [OpenClawChatPendingToolCall]
+sessions : [OpenClawChatSessionEntry]
-transport : OpenClawChatTransport
-eventTask : Task
-pendingRuns : Set~String~
-pendingToolCallsById : [String : OpenClawChatPendingToolCall]
+load()
+send()
+abort()
+refresh()
+switchSession(to : )
+addAttachments(urls : )
+removeAttachment(id : )
}
class OpenClawChatTransport {
<<protocol>>
+requestHistory(sessionKey)
+sendMessage(sessionKey, message, thinking, idempotencyKey, attachments)
+abortRun(sessionKey, runId)
+listSessions(limit)
+requestHealth(timeoutMs)
+events()
+setActiveSessionKey(sessionKey)
}
OpenClawChatViewModel --> OpenClawChatTransport

架构概览

WebChat 采用分层架构设计,确保了良好的模块分离和可维护性:

graph TB
subgraph "用户界面层"
A[OpenClawChatView] --> B[ChatMessageViews]
A --> C[ChatTheme]
A --> D[ChatComposer]
end
subgraph "业务逻辑层"
E[OpenClawChatViewModel] --> F[ChatViewModel Operations]
F --> G[Message Processing]
F --> H[Session Management]
F --> I[Attachment Handling]
end
subgraph "传输层"
J[MacGatewayChatTransport] --> K[GatewayConnection]
K --> L[WebSocket Protocol]
L --> M[chat.history]
L --> N[chat.send]
L --> O[chat.abort]
L --> P[sessions.list]
end
subgraph "数据层"
Q[Local State] --> R[Message Cache]
Q --> S[Session Cache]
Q --> T[Attachment Cache]
end
A --> E
E --> J
J --> K
K --> L

详细组件分析

消息渲染引擎

消息渲染引擎是 WebChat 的核心组件之一,负责将原始消息数据转换为美观的用户界面:

sequenceDiagram
participant VM as ChatViewModel
participant View as OpenClawChatView
participant Message as ChatMessageBubble
participant Parser as AssistantTextParser
participant Renderer as ChatMarkdownRenderer
VM->>VM : 处理传入消息
VM->>View : 更新消息列表
View->>Message : 创建消息气泡
Message->>Parser : 解析助手文本
Parser->>Renderer : 渲染 Markdown
Renderer->>Message : 返回渲染内容
Message->>View : 显示最终 UI

消息类型处理

系统支持多种消息类型,每种类型都有特定的渲染逻辑:

消息类型 描述 渲染方式
text 文本消息 标准文本渲染
file/attachment 文件附件 附件卡片显示
toolcall/tool_use 工具调用 工具调用卡片
toolresult/tool_result 工具结果 工具结果卡片
thinking 思考内容 斜体文本显示

实时通信机制

WebChat 使用 WebSocket 实现与网关的实时通信:

sequenceDiagram
participant UI as WebChat UI
participant Transport as MacGatewayChatTransport
participant Gateway as GatewayConnection
participant Stream as AsyncStream
UI->>Transport : 初始化传输层
Transport->>Gateway : 建立 WebSocket 连接
Gateway->>Stream : 创建事件流
Stream->>Transport : 推送聊天事件
Transport->>UI : 分发事件到 ViewModel
UI->>UI : 更新界面状态
Note over UI,Gateway : 实时消息推送流程

事件处理流程

系统支持多种事件类型,每种事件都有相应的处理逻辑:

flowchart TD
Start([接收事件]) --> Type{事件类型}
Type --> |health| Health[健康检查事件]
Type --> |chat| Chat[聊天事件]
Type --> |agent| Agent[代理事件]
Type --> |tick| Tick[Tick 事件]
Type --> |seqGap| Gap[序列间隙事件]
Health --> HealthHandler[更新健康状态]
Chat --> ChatHandler[处理聊天消息]
Agent --> AgentHandler[处理工具调用]
Tick --> TickHandler[轮询健康状态]
Gap --> GapHandler[刷新历史记录]
HealthHandler --> End([完成])
ChatHandler --> End
AgentHandler --> End
TickHandler --> End
GapHandler --> End

会话管理

WebChat 支持多会话管理,用户可以在不同会话之间切换:

classDiagram
class WebChatManager {
+windowController : WebChatSwiftUIWindowController?
+panelController : WebChatSwiftUIWindowController?
+cachedPreferredSessionKey : String?
+show(sessionKey)
+togglePanel(sessionKey, anchorProvider)
+closePanel()
+preferredSessionKey()
+resetTunnels()
}
class SessionCache {
+sessions : [OpenClawChatSessionEntry]
+lastUpdated : Date
+cacheDuration : TimeInterval
+getCachedSession(key)
+updateCache(sessions)
}
class SessionValidator {
+validateSessionKey(key)
+normalizeSessionKey(key)
+checkSessionExists(key)
}
WebChatManager --> SessionCache
WebChatManager --> SessionValidator

主题定制系统

WebChat 提供了灵活的主题定制系统,支持深色和浅色模式:

classDiagram
class OpenClawChatTheme {
+surface : Color
+background : View
+card : Color
+subtleCard : AnyShapeStyle
+userBubble : Color
+assistantBubble : Color
+onboardingAssistantBubble : Color
+userText : Color
+assistantText : Color
+composerBackground : AnyShapeStyle
+composerField : AnyShapeStyle
+composerBorder : Color
+divider : Color
}
class ChatBubbleShape {
+cornerRadius : CGFloat
+tail : Tail
+insetAmount : CGFloat
+path(in : CGRect)
}
class ThemeManager {
+currentTheme : OpenClawChatTheme
+applyTheme(theme)
+updateThemeForAppearance(appearance)
+getUserPreference()
}
OpenClawChatTheme --> ChatBubbleShape
ThemeManager --> OpenClawChatTheme

主题变量说明

主题变量 用途 默认值
surface 背景表面颜色 系统窗口背景色
userBubble 用户消息气泡颜色 自定义蓝色调
assistantBubble 助手消息气泡颜色 系统背景色
userText 用户文本颜色 白色
assistantText 助手文本颜色 系统标签色
composerBackground 输入框背景 材质效果
composerField 输入区域样式 材质效果

附件处理系统

WebChat 支持多种类型的附件处理:

flowchart TD
Upload[用户上传附件] --> Validate[验证附件]
Validate --> SizeCheck{大小检查}
SizeCheck --> |超过限制| Error[显示错误]
SizeCheck --> |符合要求| TypeCheck{类型检查}
TypeCheck --> |图片| ImageProcess[图片处理]
TypeCheck --> |其他| OtherProcess[其他类型处理]
ImageProcess --> Preview[生成预览]
OtherProcess --> Store[存储附件]
Preview --> AddToList[添加到附件列表]
Store --> AddToList
AddToList --> Send[发送消息]
Error --> End[结束]
Send --> End

依赖关系分析

WebChat 的依赖关系清晰明确,遵循单一职责原则:

graph TB
subgraph "外部依赖"
A[SwiftUI] --> B[AppKit/UIKit]
C[Foundation] --> D[Observation]
E[OSLog] --> F[UniformTypeIdentifiers]
end
subgraph "内部模块"
G[OpenClawChatUI] --> H[ChatView]
G --> I[ChatViewModel]
G --> J[ChatTransport]
G --> K[ChatTheme]
G --> L[ChatMessageViews]
M[OpenClawKit] --> N[GatewayConnection]
M --> O[AnyCodable]
M --> P[ToolDisplay]
Q[OpenClawProtocol] --> R[GatewayModels]
Q --> S[AnyCodable]
end
subgraph "平台特定"
T[macOS] --> U[NSWindow]
T --> V[NSHostingController]
W[iOS] --> X[UIViewController]
W --> Y[UIHostingController]
end
H --> G
I --> G
J --> M
K --> G
L --> G
G --> M
M --> Q

性能考虑

内存管理

WebChat 采用了多项内存优化策略:

  1. 懒加载消息列表:使用 LazyVStack 减少内存占用
  2. 消息去重算法:避免重复消息占用内存
  3. 附件缓存管理:限制附件大小和数量
  4. 任务取消机制:及时取消不再需要的任务

渲染优化

flowchart TD
Start([消息渲染开始]) --> CheckCache{检查缓存}
CheckCache --> |命中| UseCache[使用缓存内容]
CheckCache --> |未命中| ParseText[解析文本内容]
ParseText --> CheckType{检查消息类型}
CheckType --> |普通文本| RenderText[渲染文本]
CheckType --> |Markdown| ParseMarkdown[解析 Markdown]
CheckType --> |附件| RenderAttachment[渲染附件]
CheckType --> |工具调用| RenderToolCall[渲染工具调用]
ParseMarkdown --> RenderText
RenderAttachment --> OptimizeImage[优化图片]
OptimizeImage --> RenderText
UseCache --> End([渲染完成])
RenderText --> End

网络优化

  1. 事件流管理:使用 AsyncStream 高效处理实时事件
  2. 健康检查轮询:智能轮询策略减少网络开销
  3. 序列间隙检测:自动检测并处理网络中断
  4. 超时处理:合理的超时设置避免资源泄露

万字解析 OpenClaw 源码架构-跨平台应用之Android 应用

作者 毛骗导演
2026年3月14日 08:46

Android 应用层的关键文件组织如下:

  • 应用入口与运行时:NodeApp、NodeRuntime
  • 前台服务:NodeForegroundService
  • 网关会话:GatewaySession(WebSocket 客户端)
  • 平台服务:DeviceNotificationListenerService(通知监听)
  • 设备信息与健康:DeviceHandler(电池、内存等)
  • 安全与偏好:SecurePrefs
  • UI 绑定:MainViewModel(对 NodeRuntime 的调用封装)
graph TB
subgraph "应用层"
A["NodeApp<br/>应用入口"]
B["NodeRuntime<br/>节点运行时"]
C["NodeForegroundService<br/>前台服务"]
D["DeviceNotificationListenerService<br/>通知监听服务"]
end
subgraph "网关层"
E["GatewaySession<br/>WebSocket 会话"]
end
subgraph "设备与系统"
F["DeviceHandler<br/>设备信息/健康"]
G["SecurePrefs<br/>加密偏好"]
end
A --> B
B --> C
B --> E
B --> F
B --> G
D --> B

核心组件

  • NodeApp:应用生命周期持有者,延迟初始化 NodeRuntime
  • NodeRuntime:核心运行时,负责:
    • 网关连接(操作员会话与节点会话)
    • 自动重连与断线恢复
    • 状态流(连接状态、服务器名、麦克风状态等)
    • 事件分发与命令派发(InvokeDispatcher)
    • 通知监听事件转发到节点会话
  • NodeForegroundService:前台服务,负责:
    • 创建并更新通知
    • 订阅 NodeRuntime 状态流,动态刷新通知内容
    • 提供“断开”动作以触发 NodeRuntime 断开连接
  • GatewaySession:WebSocket 客户端,封装连接、请求、事件与自动重连循环
  • DeviceNotificationListenerService:系统通知监听服务,将通知事件转发给 NodeRuntime
  • DeviceHandler:设备健康信息采集(电池、内存、温度等)
  • SecurePrefs:加密存储(SharedPreferences 加密包装),用于实例 ID、显示名、网关凭据等

架构总览

下图展示从前台服务到运行时、再到网关会话的整体交互流程。

sequenceDiagram
participant Sys as "系统"
participant Svc as "NodeForegroundService"
participant RT as "NodeRuntime"
participant GS as "GatewaySession"
participant GW as "网关"
Sys->>Svc : "启动前台服务"
Svc->>RT : "订阅状态流状态/服务器/连接/Mic"
RT-->>Svc : "状态变化连接/断开/麦克风状态"
Svc->>Svc : "构建/更新通知"
Note over Svc : "点击“断开”触发停止动作"
Svc->>RT : "disconnect()"
RT->>GS : "关闭会话/取消重连"
GS-->>RT : "onDisconnected 回调"
RT-->>Svc : "状态流更新"
Svc->>Svc : "更新通知为断开状态"

详细组件分析

NodeForegroundService 前台服务

  • 职责
    • 在应用启动后立即以前台服务方式运行,避免被系统回收
    • 订阅 NodeRuntime 的多源状态流,动态更新通知标题与文本
    • 提供“断开”动作,通过广播触发 NodeRuntime 断开连接并停止自身
  • 关键点
    • 使用通知通道(IMPORTANCE_LOW)与 FOREGROUND_SERVICE_TYPE_DATA_SYNC
    • 通知设置为 ongoing 且仅提示一次,确保用户可随时查看
    • 首次启动时调用 startForegroundWithTypes,后续使用 notify 更新
  • 生命周期
    • onCreate:创建通知通道、初始通知、订阅状态流
    • onStartCommand:处理 ACTION_STOP 动作,触发断开并 stopSelf
    • onDestroy:取消协程作业,释放资源
flowchart TD
Start(["服务启动"]) --> Ensure["创建通知通道"]
Ensure --> InitNotify["构建初始通知并启动前台"]
InitNotify --> Subscribe["订阅 NodeRuntime 状态流"]
Subscribe --> OnChange{"状态变化?"}
OnChange --> |是| Build["构建新通知"]
Build --> Update["notify 更新"]
OnChange --> |否| Wait["等待下次变化"]
Update --> Wait
Wait --> OnChange
StopAction["收到 ACTION_STOP"] --> Disconnect["调用 NodeRuntime.disconnect()"]
Disconnect --> StopSelf["stopSelf()"]

NodeRuntime 节点运行时与生命周期控制

  • 职责
    • 管理两个 GatewaySession:操作员会话与节点会话
    • 维护连接状态、服务器名、远端地址、主会话键等状态流
    • 自动发现与自动连接(受信任网关 TLS 指纹约束)
    • 将通知监听事件转发至节点会话
    • 管理麦克风、TTS、Canvas 等子系统
  • 连接与重连
    • runLoop 循环尝试连接,失败指数退避,保持持续重连
    • onConnected/onDisconnected 回调驱动状态流更新
  • 状态流
    • isConnected、statusText、serverName、remoteAddress、micEnabled、micIsListening 等
    • 通过 combine 组合多个流,驱动前台通知实时更新
  • 生命周期控制
    • setForeground:切换前后台,必要时停止语音会话
    • disconnect:清空目标端点并断开会话
classDiagram
class NodeRuntime {
+connect(endpoint)
+disconnect()
+refreshGatewayConnection()
+setForeground(Boolean)
+statusText : StateFlow
+isConnected : StateFlow
+serverName : StateFlow
+remoteAddress : StateFlow
+micEnabled : StateFlow
+micIsListening : StateFlow
}
class GatewaySession {
+connect(endpoint, token, password, options, tls)
+disconnect()
+reconnect()
+request(method, paramsJson)
+sendNodeEvent(event, payloadJson)
}
NodeRuntime --> GatewaySession : "管理两个会话"

网关会话(GatewaySession)与自动重连

  • 运行循环
    • runLoop:根据 desired 目标连接;失败则延迟重试,指数退避上限 8 秒
    • connectOnce:建立单次连接,等待关闭或异常
  • 请求与事件
    • request:发送 RPC 请求并等待响应,超时抛出异常
    • handleEvent:处理事件帧,识别 node.invoke.request 并派发到 onInvoke
  • TLS 与指纹
    • 支持 TLS 参数与指纹回调,首次连接时探测并保存指纹,后续自动信任
flowchart TD
Loop["runLoop 开始"] --> Target{"存在目标连接?"}
Target --> |否| Close["关闭当前连接/等待"] --> Delay["delay(250ms)"] --> Loop
Target --> |是| Connect["connectOnce 连接"]
Connect --> Ok{"连接成功?"}
Ok --> |是| Run["保持连接/等待关闭"]
Ok --> |否| Retry["增加尝试次数"] --> Backoff["指数退避(<=8s)"] --> Loop

通知监听服务与状态同步

  • DeviceNotificationListenerService
    • 实现 onNotificationPosted,解析通知为内部条目并写入存储
    • 过滤自身包名的通知,向 NodeEventSink 发送变更事件
  • 事件同步
    • NodeRuntime 初始化时设置事件 Sink,将事件转发到节点会话(node.event)
    • 前台服务订阅 NodeRuntime 状态流,实现 UI/通知与运行时状态的双向同步
sequenceDiagram
participant OS as "系统通知栏"
participant NLS as "DeviceNotificationListenerService"
participant Sink as "NodeEventSink"
participant NR as "NodeRuntime"
participant NS as "Node Session"
OS->>NLS : "通知发布"
NLS->>NLS : "解析为条目/写入存储"
NLS->>Sink : "emitNotificationsChanged(...)"
Sink->>NR : "转发事件"
NR->>NS : "sendNodeEvent(event, payload)"

设备健康与内存管理

  • DeviceHandler
    • 读取电池快照(状态、充电、电量分数、温度)
    • 读取内存快照(总内存、可用内存、是否低内存、压力等级)
    • 输出 JSON 负载供上层使用
  • 内存与电池指标可用于:
    • UI 健康指示
    • 自适应降级(如降低麦克风采样率、暂停非关键任务)

安全与配置

  • 权限与前台服务类型
    • AndroidManifest 声明 FOREGROUND_SERVICE、FOREGROUND_SERVICE_DATA_SYNC、INTERNET 等
    • NodeForegroundService 使用 FOREGROUND_SERVICE_TYPE_DATA_SYNC
  • 加密存储
    • SecurePrefs 使用 EncryptedSharedPreferences 存储网关令牌、密码、TLS 指纹等
    • 实例 ID 与显示名自动生成/迁移,保证唯一性与隐私
  • TLS 指纹校验
    • 首次连接探测指纹,用户确认后持久化,后续自动信任

依赖关系分析

  • 组件耦合
    • NodeForegroundService 强依赖 NodeRuntime 的状态流,弱依赖 NodeApp(获取 runtime)
    • NodeRuntime 依赖 GatewaySession(两个会话)、DeviceNotificationListenerService(事件源)、DeviceHandler(健康数据)、SecurePrefs(配置与凭据)
    • GatewaySession 依赖 OkHttp WebSocket、DeviceIdentityStore、DeviceAuthStore
  • 外部依赖
    • Android 系统服务:通知、前台服务、通知监听服务
    • OkHttp:WebSocket 客户端
  • 可能的循环依赖
    • 当前结构无直接循环依赖;事件通过回调与状态流解耦
graph LR
Svc["NodeForegroundService"] --> RT["NodeRuntime"]
RT --> GS["GatewaySession"]
RT --> DH["DeviceHandler"]
RT --> NLS["DeviceNotificationListenerService"]
RT --> SP["SecurePrefs"]
GS --> OkHttp["OkHttp WebSocket"]

性能与电池优化

  • 通知即时行为
    • 使用 FOREGROUND_SERVICE_IMMEDIATE,确保通知立即可见,减少用户感知延迟
  • 协程与背压
    • 使用 SupervisorJob + IO 主线程,避免主线程阻塞
    • 状态流合并使用 distinctUntilChanged,减少无效 UI 刷新
  • 自动重连与退避
    • 指数退避上限 8 秒,降低网络波动对系统的影响
  • 电池与内存
    • DeviceHandler 提供电池/内存快照,便于在低电量/低内存时降级处理
  • 建议
    • 在低电量/低内存场景下暂停非关键任务(如 Canvas 重载)
    • 控制通知频率,避免频繁更新导致 CPU/电量消耗
    • 对长耗时任务拆分为小步,配合 WorkManager 或前台服务

界面组件

Android UI 采用 Jetpack Compose 架构,以“主题层 → 布局层 → 屏幕层 → 功能组件层”的分层组织方式:

  • 主题层:统一颜色、字体与动态色方案
  • 布局层:顶部状态栏、底部导航、Scaffold 容器
  • 屏幕层:引导流程、标签页容器、各功能页(聊天、语音、屏幕、设置)
  • 功能组件层:聊天输入区、消息气泡、工具调用提示、错误提示等
graph TB
A["MainActivity<br/>设置主题与根内容"] --> B["OpenClawTheme<br/>Material3 动态色"]
B --> C["RootScreen<br/>引导/标签页入口"]
C --> D["PostOnboardingTabs<br/>Scaffold + TabBar"]
D --> E["ConnectTabScreen"]
D --> F["ChatSheetContent<br/>消息列表 + 输入区"]
D --> G["VoiceTabScreen"]
D --> H["ScreenTabScreen"]
D --> I["SettingsSheet<br/>权限与设备配置"]

核心组件

  • 主题系统:基于 Material3 动态色,提供覆盖容器色与图标色的辅助函数,确保深浅模式一致体验
  • 移动 UI 令牌:集中定义颜色、字体族与排版样式,统一视觉语言
  • 根屏幕:根据引导完成状态切换到标签页容器
  • 标签页容器:顶部状态栏 + 底部导航 + 内容区域,支持 IME 折叠与 Tab 切换
  • 设置页面:集中管理权限、位置模式、睡眠策略、通知与数据访问等
  • 聊天界面:会话选择、消息列表、输入区(文本+图片附件)、工具调用提示、实时流式助手

架构总览

Compose 驱动的单 Activity 多屏幕架构,通过 ViewModel 暴露状态流,屏幕层订阅并渲染 UI;功能组件通过参数回调与 ViewModel 交互,形成清晰的数据流向。

sequenceDiagram
participant A as "MainActivity"
participant B as "OpenClawTheme"
participant C as "RootScreen"
participant D as "PostOnboardingTabs"
participant E as "MainViewModel"
participant F as "ChatSheetContent"
participant G as "ChatComposer"
A->>B : "设置主题"
B->>C : "包裹根内容"
C->>D : "根据引导状态切换"
D->>E : "订阅状态流"
D->>F : "加载聊天/语音/屏幕/设置"
F->>E : "读取消息/会话/健康状态"
F->>G : "传递发送/刷新/中止回调"
G->>E : "触发发送/中止/切换思考级别"

详细组件分析

主题系统与颜色/字体令牌

  • 动态色方案:根据系统深浅模式选择动态亮/暗配色,作为 MaterialTheme 的 colorScheme
  • 覆盖容器色与图标色:提供 overlayContainerColor 与 overlayIconColor,用于浮层与覆盖 UI 的一致性
  • 颜色令牌:集中定义背景、表面、边框、文本、强调色、成功/警告/危险等语义色
  • 字体令牌:定义字体族与多级排版样式(标题、正文、说明、脚注等),统一在组件中引用
classDiagram
class OpenClawTheme {
+OpenClawTheme(content)
+overlayContainerColor()
+overlayIconColor()
}
class MobileUiTokens {
+mobileBackgroundGradient
+mobileSurface
+mobileText*
+mobileAccent*
+mobileCode*
+mobileFontFamily
+mobileTitle1/mobileTitle2
+mobileHeadline/mobileBody
+mobileCallout/mobileCaption1/mobileCaption2
}
OpenClawTheme --> MobileUiTokens : "使用颜色/字体令牌"

标签页容器与状态指示器

  • 顶部状态栏:根据连接状态映射为不同视觉状态(已连接、连接中、警告、错误、离线),并以色块与点标示
  • 底部导航:Tab 切换时高亮当前项,结合 IME 显示隐藏规则优化输入体验
  • 内容区域:按需渲染各功能页,如屏幕页支持“恢复仪表盘”提示
flowchart TD
A["状态文本"] --> B{"解析状态"}
B --> |已连接| C["Connected<br/>绿色系"]
B --> |连接中/重连| D["Connecting<br/>蓝色系"]
B --> |配对/授权| E["Warning<br/>橙色系"]
B --> |错误/失败| F["Error<br/>红色系"]
B --> |默认| G["Offline<br/>灰阶"]

聊天界面

  • 会话选择:横向滚动展示历史会话,当前会话高亮
  • 错误提示:错误文本出现时以警示色块显示
  • 消息列表:支持文本、Markdown、图片、工具调用、流式助手等多类型渲染
  • 输入区:支持文本输入、图片附件、思考级别选择、刷新/中止、发送按钮与禁用态
sequenceDiagram
participant U as "用户"
participant CS as "ChatSheetContent"
participant VM as "MainViewModel"
participant CC as "ChatComposer"
participant MV as "ChatMessageViews"
U->>CS : "打开聊天页"
CS->>VM : "订阅消息/会话/健康状态"
CS->>CC : "传入发送/刷新/中止回调"
U->>CC : "输入文本/选择图片/选择思考级别"
CC->>VM : "sendChat(message, thinking, attachments)"
VM-->>CS : "更新消息/健康状态"
CS->>MV : "渲染消息列表"

设置页面

  • 设备信息:实例 ID、设备型号、版本号
  • 权限管理:麦克风、相机、短信、通知、照片、联系人、日历、运动、位置(含精确位置)
  • 位置模式:Off/WhileUsing/Precise Location 三态控制
  • 屏幕常亮:防止休眠开关
  • 交互流程:权限请求通过 ActivityResultLauncher 触发,状态变更通过 ViewModel 同步
flowchart TD
A["进入设置页"] --> B["读取权限状态"]
B --> C{"是否已授予?"}
C --> |是| D["显示“管理”按钮"]
C --> |否| E["显示“授权”按钮"]
D --> F["打开系统设置或执行操作"]
E --> G["启动权限请求"]
G --> H["回调后更新状态"]

数据流与状态管理

  • ViewModel 暴露 StateFlow,屏幕与组件通过 collectAsState 订阅
  • 聊天:消息列表、错误、健康状态、思考级别、流式助手文本、待执行工具调用、会话列表
  • 连接:网关发现状态、连接状态、远端地址、服务器名
  • 语音/屏幕/节点:相机、Canvas 控制、前台服务等
classDiagram
class MainViewModel {
+canvasCurrentUrl/statusText/isConnected
+chatMessages/chatError/chatHealthOk
+chatThinkingLevel/chatStreamingAssistantText
+chatSessions/pendingRunCount
+locationMode/locationPreciseEnabled
+preventSleep/cameraEnabled
+displayName/instanceId
+set*()/connect()/disconnect()
}
class ChatSheetContent
class ChatComposer
class SettingsSheet
ChatSheetContent --> MainViewModel : "收集状态/触发操作"
ChatComposer --> MainViewModel : "发送/中止/切换思考级别"
SettingsSheet --> MainViewModel : "权限/位置/睡眠策略"

依赖关系分析

  • 组件耦合:屏幕层仅依赖 ViewModel 的 StateFlow,功能组件通过回调与 ViewModel 解耦
  • 主题与样式:所有 UI 组件统一引用 MobileUiTokens 中的颜色与排版,避免硬编码
  • 权限与系统服务:设置页通过 ActivityResultLauncher 与系统权限对话交互,避免直接持有上下文引用
graph LR
MV["MainViewModel"] --> RC["RootScreen"]
RC --> POT["PostOnboardingTabs"]
POT --> CNT["ChatSheetContent"]
POT --> SET["SettingsSheet"]
CNT --> CMP["ChatComposer"]
CNT --> MSG["ChatMessageViews"]
CMP --> THEME["OpenClawTheme"]
MSG --> THEME
SET --> THEME

性能考量

  • 布局折叠:底部导航在 IME 弹出时自动隐藏,减少重绘范围
  • 滚动优化:消息列表与会话选择使用水平滚动与懒加载容器,限制一次性渲染量
  • 图片处理:图片附件在 IO 线程解码与 Base64 编码,完成后在主线程更新 UI
  • 状态订阅:仅在必要作用域内订阅 StateFlow,避免过度重组
  • 主题与样式:集中定义样式令牌,减少重复计算与资源分配

权限与安全

Android 应用位于 apps/android/app,核心安全与权限相关文件分布如下:

  • 权限请求:PermissionRequester.kt
  • 安全偏好:SecurePrefs.kt
  • 清单与服务:AndroidManifest.xml
  • 构建与依赖:build.gradle.kts
  • 网络与备份配置:network_security_config.xml、backup_rules.xml
  • TLS 固定:GatewayTls.kt
  • 设备权限状态上报:DeviceHandler.kt
  • 设置界面权限状态展示:SettingsSheet.kt
  • 安全偏好测试:SecurePrefsTest.kt
graph TB
subgraph "Android 应用"
A["PermissionRequester<br/>权限请求器"]
B["SecurePrefs<br/>安全偏好设置"]
C["AndroidManifest<br/>权限与服务声明"]
D["build.gradle.kts<br/>构建与依赖"]
E["network_security_config.xml<br/>网络安全策略"]
F["backup_rules.xml<br/>备份规则"]
G["GatewayTls<br/>TLS 固定"]
H["DeviceHandler<br/>设备权限状态上报"]
I["SettingsSheet<br/>设置界面权限状态"]
J["SecurePrefsTest<br/>安全偏好测试"]
end
A --> C
B --> C
G --> C
H --> C
I --> C
D --> C
E --> C
F --> C
J --> B

核心组件

  • 权限请求器(PermissionRequester):封装多权限一次性请求、理由说明对话框、超时控制与设置引导。
  • 安全偏好(SecurePrefs):基于 EncryptedSharedPreferences 的敏感数据加密存储,同时维护明文偏好用于非敏感配置;支持实例 ID、网关凭据、唤醒词等。
  • 清单与服务(AndroidManifest):声明网络、定位、相机、麦克风、通知、短信、媒体访问、日历联系人等权限;注册前台服务、通知监听服务、FileProvider。
  • 网络安全策略(network_security_config.xml):允许本地与特定域名的清晰文本流量,适配受信尾随网络场景。
  • 备份规则(backup_rules.xml):启用全量文件备份。
  • TLS 固定(GatewayTls):自定义 TrustManager 实现证书指纹校验,支持首次信任(TOFU)与持久化存储。
  • 设备权限状态上报(DeviceHandler):汇总设备权限状态并以 JSON 形式上报。
  • 设置界面权限状态展示(SettingsSheet):在设置页动态检查并显示各类权限状态。

架构总览

下图展示了从“权限请求”到“安全存储”再到“网络通信”的整体链路,以及与清单和服务的关系。

sequenceDiagram
participant UI as "设置界面/调用方"
participant PR as "PermissionRequester"
participant AM as "Android 系统权限框架"
participant SP as "SecurePrefs"
participant TLS as "GatewayTls"
participant NET as "远端网关"
UI->>PR : 请求若干运行时权限
PR->>AM : 发起多权限请求
AM-->>PR : 返回授权结果
PR-->>UI : 合并当前状态并提示设置入口
UI->>SP : 写入/读取敏感配置如网关令牌
SP-->>UI : 返回解密后的值
UI->>TLS : 基于参数构建 TLS 配置
TLS-->>UI : 返回校验证书的 SSL Socket 工厂
UI->>NET : 使用已校验的 TLS 连接进行通信

详细组件分析

权限请求器(PermissionRequester)

  • 功能要点
    • 批量检测缺失权限,必要时弹出理由对话框,尊重用户选择。
    • 使用 ActivityResultLauncher 触发系统授权对话框,支持超时控制。
    • 合并当前授权状态与回调结果,对被拒绝且无理由的权限引导至系统设置。
  • 关键行为
    • 检测逻辑:仅对未授予的权限发起请求。
    • 超时与并发:通过互斥锁与超时机制避免阻塞与竞态。
    • 结果合并:若某权限在回调前已被授予,视为已授权。
  • 用户体验
    • 对相机、麦克风、短信等权限提供明确标签提示。
    • 对不可恢复权限(无理由)引导至应用详情页设置。
flowchart TD
Start(["开始"]) --> Detect["检测缺失权限"]
Detect --> AnyMissing{"存在缺失权限?"}
AnyMissing --> |否| ReturnAllTrue["返回全部已授权"]
AnyMissing --> |是| NeedRationale{"是否需要理由说明?"}
NeedRationale --> |是| ShowRationale["显示理由对话框"]
ShowRationale --> UserChoice{"用户同意?"}
UserChoice --> |否| ReturnCurrent["返回当前授权状态"]
UserChoice --> |是| Launch["启动系统权限请求"]
NeedRationale --> |否| Launch
Launch --> Await["等待授权结果或超时"]
Await --> Merge["合并当前状态与回调结果"]
Merge --> Denied{"是否存在被拒绝且无理由的权限?"}
Denied --> |是| OpenSettings["引导打开应用设置"]
Denied --> |否| Done["完成"]
OpenSettings --> Done
ReturnAllTrue --> End(["结束"])
ReturnCurrent --> End
Done --> End

安全偏好设置(SecurePrefs)

  • 数据隔离
    • 明文偏好:存放非敏感配置(如实例 ID、显示名、位置模式、唤醒词等)。
    • 加密偏好:存放敏感信息(如网关令牌、密码),使用 EncryptedSharedPreferences 与 AES256-GCM。
  • 主密钥与加密方案
    • 使用 MasterKey.AES256_GCM 作为主密钥,Key/Value 分别采用 AES256_SIV 与 AES256_GCM。
  • 生命周期与状态流
    • 大部分字段以 StateFlow 暴露,便于 UI 订阅。
  • 迁移与兼容
    • 对历史键值进行迁移(例如位置模式的“always”迁移到新枚举值)。
  • 测试覆盖
    • 单元测试验证迁移逻辑正确性。
classDiagram
class SecurePrefs {
-appContext : Context
-json : Json
-plainPrefs : SharedPreferences
-masterKey : MasterKey
-securePrefs : SharedPreferences
-_instanceId : StateFlow~String~
-_displayName : StateFlow~String~
-_locationMode : StateFlow~LocationMode~
-_gatewayToken : StateFlow~String~
+setDisplayName(value)
+setLocationMode(mode)
+setGatewayToken(token)
+saveGatewayToken(token)
+loadGatewayToken() String?
+saveGatewayPassword(password)
+loadGatewayPassword() String?
+getString(key) String?
+putString(key,value)
+remove(key)
}

Android 清单与权限配置(AndroidManifest)

  • 权限声明
    • 网络与前台服务:INTERNET、ACCESS_NETWORK_STATE、FOREGROUND_SERVICE、FOREGROUND_SERVICE_DATA_SYNC。
    • 通知与 Wi‑Fi:POST_NOTIFICATIONS、NEARBLY_WIFI_DEVICES(含 flags)、ACCESS_FINE_LOCATION、ACCESS_COARSE_LOCATION。
    • 媒体与存储:CAMERA、RECORD_AUDIO、SEND_SMS、READ_MEDIA_IMAGES、READ_MEDIA_VISUAL_USER_SELECTED、READ_EXTERNAL_STORAGE(maxSdk=32)。
    • 联系人与日历:READ_CONTACTS、WRITE_CONTACTS、READ_CALENDAR、WRITE_CALENDAR。
    • 行为识别:ACTIVITY_RECOGNITION。
  • 特性声明
    • 摄像头与电话硬件为可选(required=false)。
  • 服务与 Provider
    • 前台服务、通知监听服务、FileProvider(用于分享文件)。
graph LR
M["AndroidManifest"] --> P1["网络/服务权限"]
M --> P2["媒体/存储权限"]
M --> P3["位置/通知权限"]
M --> P4["联系人/日历权限"]
M --> S1["前台服务"]
M --> S2["通知监听服务"]
M --> PDR["FileProvider"]

网络安全策略与备份规则

  • 网络安全配置
    • 允许基础清晰文本流量,针对 openclaw.local 与 ts.net 子域开放 HTTP。
    • 适用于受信尾随网络环境,降低本地开发与内网场景的 TLS 成本。
  • 备份规则
    • 启用全量文件备份,注意敏感数据应仅存于加密偏好中。

TLS 固定与安全通信

  • 目标
    • 在客户端侧对远端网关证书进行指纹校验,防止中间人攻击。
  • 实现
    • 自定义 TrustManager:当提供期望指纹时严格比对;否则回退到默认信任策略。
    • 支持首次信任(TOFU):若允许且首次成功握手,则将指纹持久化。
    • 提供 HostnameVerifier 与 SSLSocketFactory,便于 OkHttp 或原生 HTTPS 使用。
  • 使用建议
    • 对外网与非受信网络强制启用 TLS 与指纹校验;对本地环回地址可放宽策略。
sequenceDiagram
participant APP as "应用"
participant TLS as "GatewayTls"
participant CHAIN as "证书链"
participant STORE as "指纹存储"
APP->>TLS : 传入 GatewayTlsParams
TLS->>CHAIN : 获取首张证书并计算 SHA-256
alt 提供期望指纹
TLS->>TLS : 比对指纹一致?
TLS-->>APP : 一致则允许,否则取消
else 允许 TOFU
TLS->>STORE : 持久化指纹
TLS-->>APP : 允许连接
else 默认策略
TLS->>TLS : 使用系统信任策略校验
TLS-->>APP : 结果由系统决定
end

设备权限状态上报与设置页展示

  • 设备权限状态上报
    • DeviceHandler 汇总相机、麦克风、位置、照片、联系人、日历、运动、通知等权限状态,生成 JSON。
  • 设置页展示
    • SettingsSheet 在 onResume 时检查各权限状态,更新 UI 展示。

依赖关系分析

  • 构建与依赖
    • Compose、OkHttp、安全加密库(androidx.security:security-crypto)、相机库等。
    • 测试框架(Robolectric、Kotest、MockWebServer)。
  • 运行时依赖
    • 权限请求依赖 ActivityResultContracts 与 ActivityCompat。
    • 安全存储依赖 EncryptedSharedPreferences 与 MasterKey。
    • TLS 固定依赖 javax.net.ssl 与系统信任管理器。
graph TB
BR["build.gradle.kts"] --> ACT["AndroidX Activity"]
BR --> SEC["AndroidX Security Crypto"]
BR --> OK["OkHttp"]
BR --> CAM["CameraX"]
PR["PermissionRequester"] --> ACT
SP["SecurePrefs"] --> SEC
TLS["GatewayTls"] --> OK

性能考量

  • 权限请求
    • 使用互斥锁避免并发重复请求;超时控制防止 UI 卡死。
    • 合并当前状态与回调结果,减少后续判断成本。
  • 安全存储
    • EncryptedSharedPreferences 会带来一定开销,建议批量写入与合理缓存。
    • 对频繁读取的键值可引入内存缓存(当前实现以 StateFlow 为主)。
  • TLS 固定
    • 证书指纹计算与持久化需在后台线程执行,避免阻塞主线程。
    • 首次握手失败重试与超时控制,提升稳定性。

设备控制

Android 节点应用位于 apps/android/app,核心控制逻辑集中在 node 包中;网关侧策略与测试位于 src/gateway。

graph TB
subgraph "Android 节点"
ID["InvokeDispatcher<br/>命令分发器"]
DH["DeviceHandler<br/>设备信息/权限/健康"]
CH["CameraHandler<br/>相机列表/拍照/录像"]
CNH["ContactsHandler<br/>联系人搜索/新增"]
NH["NotificationsHandler<br/>通知快照/动作"]
SH["SmsHandler<br/>短信发送"]
LH["LocationHandler<br/>位置获取"]
AH["A2UIHandler<br/>A2UI 就绪/消息应用"]
end
subgraph "网关"
POL["node-command-policy.ts<br/>命令白名单/平台默认值"]
TEST["android-node.capabilities.live.test.ts<br/>能力验证测试"]
DISC["GatewayDiscovery.kt<br/>mDNS/NSD 发现"]
PAIR["message-handler.ts<br/>配对握手/拒绝"]
PNOTI["device-pair/notify.ts<br/>配对提醒轮询"]
end
ID --> DH
ID --> CH
ID --> CNH
ID --> NH
ID --> SH
ID --> LH
ID --> AH
POL --> ID
TEST --> ID
DISC --> ID
PAIR --> ID
PNOTI --> ID

核心组件

  • 命令分发器(InvokeDispatcher)
    • 统一入口,根据命令名路由到对应处理器,并进行前台限制、能力可用性检查与错误包装
    • 支持 Canvas/A2UI、相机、位置、设备、通知、系统、相册、联系人、日历、运动、短信等命令族
  • 各子处理器
    • DeviceHandler:设备状态、信息、权限、健康度
    • CameraHandler:相机设备枚举、拍照、录视频(含大小限制与错误处理)
    • ContactsHandler:联系人搜索、新增(含权限校验与批量插入)
    • NotificationsHandler:通知快照、动作执行(打开/忽略/回复)
    • SmsHandler:短信发送(错误码映射)
    • LocationHandler:位置获取(权限、精度、超时)
    • A2UIHandler:A2UI 主机解析、就绪检测、消息应用
  • 网关侧策略
    • node-command-policy.ts:按平台定义默认命令集、危险命令白名单、节点声明校验
  • 文档与测试
    • 平台文档:Android 节点连接、命令面与注意事项
    • 能力测试:覆盖 Canvas、相机、位置、设备、通知、调试等命令

架构总览

Android 节点通过 mDNS/NSD 发现网关,建立 WebSocket 连接并完成配对。命令由网关侧策略决定是否允许,节点侧通过 InvokeDispatcher 分发至各 Handler 执行,结果回传给网关。

sequenceDiagram
participant Node as "Android 节点"
participant Disc as "GatewayDiscovery<br/>mDNS/NSD"
participant GW as "Gateway"
participant Policy as "node-command-policy.ts"
participant Disp as "InvokeDispatcher"
participant H as "各 Handler"
Node->>Disc : 发现 _openclaw-gw._tcp
Disc-->>Node : 返回网关地址/端口
Node->>GW : 建立 WebSocket 连接
GW-->>Node : 握手/配对请求
alt 未配对且非静默
GW-->>Node : 拒绝连接并提示配对
else 已配对或静默
GW-->>Node : 允许连接
end
Node->>GW : node.invoke(command, params)
GW->>Policy : 校验命令是否允许
Policy-->>GW : 允许/拒绝
GW->>Disp : 调用分发器
Disp->>H : 路由到具体处理器
H-->>Disp : 执行结果/错误
Disp-->>GW : 包装响应
GW-->>Node : 返回 payload 或错误

详细组件分析

命令分发与注册机制(NodeHandler 系统)

  • 命令注册
    • 通过 InvokeCommandRegistry 查找命令规格,包含命令名、是否要求前台、可用性条件
    • 可用性条件支持:Always、CameraEnabled、LocationEnabled、SmsAvailable、MotionActivityAvailable、MotionPedometerAvailable、DebugBuild
  • 分发流程
    • 若命令未知,返回 INVALID_REQUEST
    • 若命令要求前台但节点不在前台,返回 NODE_BACKGROUND_UNAVAILABLE
    • 根据可用性条件检查(如相机/位置/短信/运动),不满足则返回相应错误码
    • 路由到具体处理器执行,A2UI 需要先确保 Canvas 可用与 A2UI 主机就绪
  • 错误处理
    • 统一包装为 GatewaySession.InvokeResult,包含 ok、payload 或 error(code/message)
flowchart TD
Start(["收到 node.invoke"]) --> Lookup["查找命令规格"]
Lookup --> Unknown{"未知命令?"}
Unknown --> |是| ErrUnknown["返回 INVALID_REQUEST"]
Unknown --> |否| Foreground{"需要前台?"}
Foreground --> |是且不在前台| ErrBg["返回 NODE_BACKGROUND_UNAVAILABLE"]
Foreground --> |否| Avail["检查可用性条件"]
Avail --> Denied{"条件不满足?"}
Denied --> |是| ErrCond["返回对应错误码"]
Denied --> |否| Route["路由到具体处理器"]
Route --> Exec["执行并返回结果"]
Exec --> End(["结束"])
ErrUnknown --> End
ErrBg --> End
ErrCond --> End

设备权限与状态(DeviceHandler)

  • 设备状态:电池、热状态、存储、网络连通性、耗电模式、uptime
  • 设备信息:设备名、型号标识、系统版本、应用版本/构建号、语言区域
  • 权限状态:相机、麦克风、位置、短信、通知监听、通知、相册、通讯录、日历、运动
  • 健康度:内存压力、电池状态/充电类型、温度、电流、Doze/LowPower 模式、安全补丁级别
classDiagram
class DeviceHandler {
+handleDeviceStatus(params) InvokeResult
+handleDeviceInfo(params) InvokeResult
+handleDevicePermissions(params) InvokeResult
+handleDeviceHealth(params) InvokeResult
-statusPayloadJson() String
-infoPayloadJson() String
-permissionsPayloadJson() String
-healthPayloadJson() String
}

相机控制(CameraHandler)

  • 列表:列举可用相机设备
  • 拍照:触发闪光、HUD 提示、捕获并返回 base64 图像
  • 录像:可选包含外部音频,限制最大负载,过大则删除临时文件并返回 PAYLOAD_TOO_LARGE
  • 错误处理:将异常映射为错误码与用户可读消息
sequenceDiagram
participant GW as "网关"
participant Disp as "InvokeDispatcher"
participant Cam as "CameraHandler"
GW->>Disp : node.invoke(camera.snap/clip)
Disp->>Cam : handleSnap/handleClip
Cam->>Cam : 触发 HUD/闪光/开始录制
Cam-->>Disp : 成功返回 base64/元数据
Disp-->>GW : InvokeResult.ok

联系人管理(ContactsHandler)

  • 搜索:支持查询关键字与数量上限,返回联系人列表
  • 新增:支持姓名、组织、电话、邮箱,通过批量插入写入系统通讯录
  • 权限:读取需 READ_CONTACTS,写入需 WRITE_CONTACTS;无权限直接返回 CONTACTS_PERMISSION_REQUIRED
flowchart TD
S(["contacts.search"]) --> CheckPerm["检查 READ_CONTACTS 权限"]
CheckPerm --> |无| ErrPerm["返回 CONTACTS_PERMISSION_REQUIRED"]
CheckPerm --> |有| Query["查询联系人"]
Query --> Ok["返回 contacts[]"]
A(["contacts.add"]) --> CheckWrite["检查 WRITE_CONTACTS 权限"]
CheckWrite --> |无| ErrPermAdd["返回 CONTACTS_PERMISSION_REQUIRED"]
CheckWrite --> |有| Validate["校验参数姓名/组织/电话/邮箱至少一项"]
Validate --> |无效| ErrInvalid["返回 CONTACTS_INVALID"]
Validate --> |有效| Insert["批量插入系统通讯录"]
Insert --> OkAdd["返回新增联系人"]

通知处理(NotificationsHandler)

  • 快照:读取通知快照(若启用监听但未连接,尝试重新绑定)
  • 动作:支持 open、dismiss、reply,reply 需要 replyText
  • 失败:返回 UNAVAILABLE 或具体错误码
sequenceDiagram
participant GW as "网关"
participant Disp as "InvokeDispatcher"
participant Noti as "NotificationsHandler"
GW->>Disp : node.invoke(notifications.list/actions)
Disp->>Noti : handleNotificationsList/handleNotificationsActions
Noti->>Noti : 读取快照/必要时重绑
Noti-->>Disp : 返回快照或执行结果
Disp-->>GW : InvokeResult

短信发送(SmsHandler)

  • 参数解析后委托底层 SmsManager 执行
  • 错误映射:将内部错误字符串按冒号前缀提取为错误码,返回 SMS_SEND_FAILED 默认码

位置服务(LocationHandler)

  • 权限:需要粗/精定位之一;前台运行时才允许
  • 精度:precise/coarse/balanced,受精确权限与系统设置影响
  • 超时:默认 10 秒,范围 1–60 秒
  • 异常:超时返回 LOCATION_TIMEOUT,其他异常返回 LOCATION_UNAVAILABLE

A2UI 交互(A2UIHandler)

  • 主机解析:优先节点 Canvas 主机,否则回退到运营者主机
  • 就绪检测:导航到 A2UI 页面并轮询检查 ready 标志
  • 消息应用:支持 messages 数组或 jsonl 字段,严格校验 v0.8 消息格式

网关命令策略与平台默认

  • 平台默认命令集:Android 默认开放 Canvas、Camera、Location、通知、系统通知、设备信息/状态/权限/健康、联系人、日历、提醒、相册、运动等命令
  • 危险命令:相机拍照/录像、屏幕录制、联系人新增、日历新增、提醒新增、短信发送等需显式允许
  • 声明校验:命令必须同时在节点声明的 commands 列表中

设备发现、蓝牙配对与网络通信

  • 设备发现:Android 使用 mDNS/NSD 发现 _openclaw-gw._tcp,支持本地与单播 DNS-SD(跨网络场景)
  • 蓝牙配对:通过网关握手阶段触发配对请求,未配对且非静默时拒绝连接并提示
  • 网络通信:WebSocket 连接,支持 Token/密码认证与 TLS

依赖关系分析

  • 节点侧
    • InvokeDispatcher 依赖各 Handler 与 A2UIHandler,受 InvokeCommandRegistry 的规格约束
    • 各 Handler 依赖系统服务(相机、联系人、通知、位置、短信等)
  • 网关侧
    • node-command-policy.ts 决定命令允许与否,结合节点声明与配置
    • 测试用例覆盖 Android 节点能力矩阵,验证命令返回结构与错误码
graph LR
Reg["InvokeCommandRegistry"] --> Disp["InvokeDispatcher"]
Disp --> DH["DeviceHandler"]
Disp --> CH["CameraHandler"]
Disp --> CNH["ContactsHandler"]
Disp --> NH["NotificationsHandler"]
Disp --> SH["SmsHandler"]
Disp --> LH["LocationHandler"]
Disp --> AH["A2UIHandler"]
Policy["node-command-policy.ts"] --> Disp
Test["android-node.capabilities.live.test.ts"] --> Disp

性能考量

  • 相机录视频负载限制:超过阈值会删除临时文件并返回错误,避免大包导致传输失败
  • 前台限制:Canvas/A2UI/相机/录屏等命令仅在前台可用,减少后台资源占用
  • 通知监听:若监听未连接,自动尝试重绑,降低用户干预成本
  • 网络发现:本地与单播 DNS-SD 双通道,提升跨网络发现成功率

网关通信

Android 网关通信相关代码主要位于 Android 应用模块中,核心类为 GatewaySession 与 DeviceAuthPayload;同时在后端/通用层提供协议定义与校验工具。

graph TB
subgraph "Android 应用"
GS["GatewaySession.kt<br/>会话管理/连接/消息处理"]
DAP["DeviceAuthPayload.kt<br/>认证载荷构建/归一化"]
end
subgraph "通用协议"
PI["protocol/index.ts<br/>帧校验/常量导出"]
SCHEMA["protocol/schema.ts<br/>协议模式入口"]
end
GS --> DAP
GS --> PI
PI --> SCHEMA

核心组件

  • GatewaySession(Android)
    • 负责 WebSocket 连接、消息收发、RPC 请求/响应、事件分发、节点调用请求处理、TLS 配置与指纹回调、Canvas URL 规范化等
    • 内部 Connection 类封装单次连接细节,包括握手、认证、心跳与断线处理
  • DeviceAuthPayload(Android)
    • 构建 v3 设备认证载荷字符串,统一元数据字段大小写规则,便于跨运行时一致性校验
  • 协议与校验(通用)
    • 提供帧类型、参数 Schema、AJV 校验器、协议版本常量与错误码导出

架构总览

Android 端通过 OkHttp WebSocket 客户端发起连接,按“握手挑战—连接参数—认证—会话建立”的顺序完成握手;随后进入消息循环,区分“响应帧”和“事件帧”,并支持节点侧向客户端发起的“invoke 请求”。

sequenceDiagram
participant App as "Android 应用"
participant WS as "OkHttp WebSocket"
participant Srv as "网关服务器"
App->>WS : "建立 WebSocket 连接"
WS-->>App : "onOpen()"
App->>Srv : "等待 connect.challenge 事件"
Srv-->>App : "事件 : connect.challenge { nonce }"
App->>App : "构造 connect 参数 + 设备签名"
App->>Srv : "RPC : connect"
Srv-->>App : "响应 : connect 成功 + 会话信息"
App->>App : "保存设备令牌/Canvas URL/会话键"
App->>Srv : "心跳/事件/请求/响应 循环"

组件详解

GatewaySession 会话管理

  • 连接与生命周期
    • 支持 connect/disconnect/reconnect,内部使用协程与互斥锁保证并发安全
    • 运行循环按指数回退策略重连,最大延迟上限控制
  • 消息处理
    • onMessage 解析 JSON 帧,区分 "res"(响应)与 "event"(事件)
    • pending 映射用于匹配请求 ID 与响应
  • 认证与握手
    • 等待 connect.challenge 事件提取 nonce,随后发送 connect RPC
    • 可选携带 token/password 与设备签名(公钥、签名、时间戳、nonce)
  • 节点调用
    • 接收 "node.invoke.request" 事件,调用应用提供的处理器,再以 "node.invoke.result" 回_ack_
  • Canvas URL 规范化
    • 根据连接是否 TLS 以及返回的 canvasHostUrl,修正 scheme/port/path/query/fragment
classDiagram
class GatewaySession {
+connect(endpoint, token, password, options, tls)
+disconnect()
+reconnect()
+request(method, paramsJson, timeoutMs)
+sendNodeEvent(event, payloadJson)
+refreshNodeCanvasCapability(timeoutMs)
}
class Connection {
+connect()
+request(method, params, timeoutMs)
+awaitClose()
-sendConnect(nonce)
-handleMessage(text)
-handleEvent(frame)
-handleResponse(frame)
-handleInvokeEvent(payloadJson)
-sendInvokeResult(id, nodeId, result, timeoutMs)
}
GatewaySession --> Connection : "持有并管理"

DeviceAuthPayload 认证机制

  • 载荷格式
    • v3 版本字符串由固定字段拼接,字段包括版本、设备 ID、客户端 ID、模式、角色、作用域列表、签名时间、令牌、nonce、平台、设备系列
    • 平台与设备系列字段进行大小写归一化(仅 ASCII A-Z 转小写),确保跨运行时一致性
  • 使用场景
    • 在 connect 参数中生成设备签名,随同公钥一起提交给网关,由网关验证签名与时间戳
flowchart TD
Start(["开始"]) --> BuildFields["拼接字段: v3|deviceId|clientId|mode|role|scopes|signedAtMs|token|nonce|platform|deviceFamily"]
BuildFields --> Normalize["平台/设备系列字段大小写归一化"]
Normalize --> Join["以'|'连接为最终载荷字符串"]
Join --> Sign["使用设备私钥对载荷签名"]
Sign --> Done(["结束"])

协议常量与校验(通用)

  • 协议版本
    • 通过 schema 导入统一导出协议版本常量,客户端在 connect 参数中声明 min/maxProtocol
  • 帧与参数校验
    • 使用 AJV 编译各 Schema,提供 validateXxx 函数用于请求/响应/事件/参数的运行时校验
    • formatValidationErrors 将校验错误格式化为可读字符串
  • 错误码与帧类型
    • 导出 ErrorCodes、GatewayFrame、RequestFrame、ResponseFrame、EventFrame 等类型与校验器
graph LR
IDX["protocol/index.ts"] --> SCH["protocol/schema.ts"]
IDX --> VALID["AJV 校验器"]
IDX --> CONST["协议常量/版本"]

心跳检测与保活

  • 服务端心跳
    • 网关定期下发 "tick" 事件,客户端收到后更新最近心跳时间
  • 客户端保活
    • OkHttp 客户端设置 pingInterval,默认 30 秒;若长时间无消息,可结合业务层 watchdog 强制重连(参考其他平台实现)

消息序列化与反序列化

  • 发送
    • request 方法构造 "req" 帧,序列化为 JSON 后通过 WebSocket 发送
  • 接收
    • onMessage 解析文本为 JSON 对象,根据 "type" 分派到 handleResponse 或 handleEvent
    • 响应帧通过 pending 映射匹配请求 ID,超时抛出异常
  • 节点调用结果
    • invoke 结果以 "node.invoke.result" RPC 返回,支持 payload 与 error 字段

连接配置与 TLS

  • TLS 配置
    • 可选 SSL Socket Factory 与 Hostname Verifier,支持自定义证书指纹校验回调
  • Ping 与超时
    • 写超时 60 秒,读超时无限制,ping 间隔 30 秒
  • Canvas URL 规范化
    • 根据连接是否 TLS 修正 https/scheme 与端口,保留路径/查询/片段

节点调用(GatewaySessionInvoke)

  • 事件到 RPC 的桥接
    • 当收到 "node.invoke.request" 事件时,解析参数(id/nodeId/command/paramsJSON/timeoutMs),调用应用提供的处理器
    • 处理完成后以 "node.invoke.result" 发送结果,超时范围在 15–120 秒之间
  • 测试场景
    • 单测覆盖握手、事件分发、结果回传与关闭流程
sequenceDiagram
participant GS as "GatewaySession.Connection"
participant App as "应用处理器"
GS->>GS : "接收事件 : node.invoke.request"
GS->>App : "调用处理器(InvokeRequest)"
App-->>GS : "返回 InvokeResult"
GS->>GS : "构造 node.invoke.result 参数"
GS-->>GS : "发送 RPC : node.invoke.result"

依赖关系分析

  • Android 端
    • GatewaySession 依赖 OkHttp WebSocket、Kotlinx Serialization、协程与互斥锁
    • DeviceAuthPayload 作为纯函数对象,被 Connection 构造 connect 参数时调用
  • 通用层
    • protocol/index.ts 导出 AJV 校验器与协议常量,供服务端/客户端共享
    • schema.ts 汇总各类 Schema,形成强类型协议模型
graph TB
A["GatewaySession.kt"] --> B["DeviceAuthPayload.kt"]
A --> C["protocol/index.ts"]
C --> D["protocol/schema.ts"]

性能与可靠性

  • 指数回退重连
    • 连接失败时按 1.7 指数增长延迟,上限 8 秒,避免风暴式重试
  • 超时与确认
    • connect RPC 超时 12 秒,invoke 结果确认超时在 15–120 秒区间
  • 心跳与保活
    • OkHttp ping 间隔 30 秒;业务层可结合 watchdog 在长时间无消息时触发重连
  • TLS 与指纹
    • 可配置自定义证书链与主机名校验,并在连接建立时回调指纹,便于运维审计

相机与屏幕录制

Android 相关实现集中在 apps/android/app 模块,核心文件包括:

  • 节点命令处理器:CameraHandler、PhotosHandler
  • 相机采集与录制:CameraCaptureManager
  • 状态与权限:CameraHudState、PermissionRequester
  • 辅助工具:JpegSizeLimiter
  • 文档参考:docs/platforms/android.md
graph TB
subgraph "Android 应用"
CH["CameraHandler<br/>节点命令入口"]
CCM["CameraCaptureManager<br/>相机采集/录制"]
PH["PhotosHandler<br/>相册读取"]
PR["PermissionRequester<br/>权限申请"]
HLS["CameraHudState<br/>HUD 状态模型"]
JSL["JpegSizeLimiter<br/>JPEG 尺寸限制器"]
end
CH --> CCM
CH --> HLS
CH --> PR
PH --> PR
CCM --> PR
CCM --> JSL

核心组件

  • CameraHudState:定义相机 HUD 的状态类型(拍照、录制、成功、错误),用于 UI 提示
  • CameraHandler:节点命令入口,负责 camera.list、camera.snap、camera.clip 的调用与结果封装
  • CameraCaptureManager:实际执行相机操作,含权限检查、设备选择、拍照与录制、文件输出与事件监听
  • PhotosHandler:读取系统相册最新图片,按预算压缩为 JPEG 并返回 base64
  • PermissionRequester:统一的权限申请与引导,支持理由说明与设置页跳转
  • JpegSizeLimiter:在尺寸与质量之间迭代压缩,确保输出不超过上限

架构总览

下图展示了从节点命令到相机采集与录制的整体流程,以及与权限系统的交互。

sequenceDiagram
participant Node as "节点命令"
participant Handler as "CameraHandler"
participant Manager as "CameraCaptureManager"
participant Perm as "PermissionRequester"
participant Cam as "相机系统(CamerX)"
participant UI as "HUD 状态"
Node->>Handler : 调用 camera.snap 或 camera.clip
Handler->>UI : 显示提示(拍照/录制)
alt 需要相机权限
Handler->>Perm : 请求相机权限
Perm-->>Handler : 返回授权结果
end
Handler->>Manager : 执行 snap/clip
Manager->>Cam : 绑定生命周期/选择相机/开始录制
Cam-->>Manager : 回调事件(Finalize/状态)
Manager-->>Handler : 返回结果(照片/文件)
Handler->>UI : 显示成功/错误提示
Handler-->>Node : 返回 JSON 结果

组件详解

相机状态管理:CameraHudState

  • 定义状态类型:Photo(拍照)、Recording(录制中)、Success(成功)、Error(错误)
  • 数据结构包含 token、kind、message,用于 UI 层渲染与自动隐藏

相机处理器:CameraHandler

职责与流程:

  • 设备列表:调用 CameraCaptureManager.listDevices,返回设备数组
  • 拍照:显示 HUD、触发闪光、调用 snap,返回 base64 JPEG;失败时显示错误 HUD
  • 录制:显示 HUD、可选启用外部音频、调用 clip,将 mp4 文件读入内存并 base64 编码返回;超过阈值则删除临时文件并报错

关键行为:

  • 参数解析:includeAudio、durationMs、deviceId、facing、quality、maxWidth
  • 负载限制:对 clip 的二进制大小进行上限检查,避免 WebSocket 超限
  • HUD 生命周期:成功/错误提示与自动隐藏时间

相机采集与录制:CameraCaptureManager

职责与流程:

  • 设备枚举:通过 ProcessCameraProvider 获取可用摄像头并映射为设备信息
  • 权限保障:ensureCameraPermission / ensureMicPermission
  • 拍照:
    • 解析参数:facing、quality、maxWidth、deviceId
    • 绑定生命周期并拍摄 JPEG,读取 EXIF 方向并旋转,按 maxWidth 缩放
    • 使用 JpegSizeLimiter 控制 JPEG 大小,返回 JSON 字符串
  • 录制:
    • 解析参数:facing、durationMs、includeAudio、deviceId
    • 设置最低质量以减小文件体积
    • 绑定 Preview 与 VideoCapture,预热后开始录制,延时后停止
    • 监听 Finalize 事件,超时或失败时清理临时文件并抛出异常
    • 返回 File 与元数据(时长、是否含音频)

辅助工具:

  • takeJpegWithExif:异步拍摄 JPEG 并返回字节与 EXIF 方向
  • cameraDeviceInfoOrNull / cameraIdOrNull:从 CameraInfo 解析设备信息
  • JpegSizeLimiter:在尺寸与质量间迭代压缩,确保不超过上限

相册处理:PhotosHandler

职责与流程:

  • 权限检查:根据系统版本选择 READ_MEDIA_IMAGES 或 READ_EXTERNAL_STORAGE
  • 查询策略:按拍摄时间与添加时间降序查询最近图片
  • 解码与缩放:按最大宽度计算 inSampleSize 并解码,必要时缩放
  • 压缩与预算:使用预算约束编码 JPEG,逐张评估 base64 长度,累计不超过总预算
  • 返回结构:每张图片包含 format、base64、width、height、createdAt

权限管理:PermissionRequester

  • 支持多权限一次性申请
  • 对需要理由的权限弹窗说明用途
  • 对被拒绝且不再提示的权限,引导用户前往应用设置开启
  • 内部互斥与超时控制,保证并发安全

JPEG 尺寸限制器:JpegSizeLimiter

  • 输入:初始宽高、起始质量、最大字节数
  • 策略:优先降低质量,再逐步缩小尺寸,直到满足上限
  • 输出:最终字节、宽高、质量

屏幕录制(跨平台对比)

  • iOS 实现要点:
    • 计算配置:时长、帧率、音频开关、输出路径
    • 启动/停止/写入:分阶段回调,准备写入器、处理视频样本、完成写入
    • FPS 采样间隔:按目标帧率去抖,避免过量写入
  • macOS 实现要点:
    • 可选音频输入配置(AAC)
    • 流停止错误记录与日志

依赖关系分析

  • CameraHandler 依赖 CameraCaptureManager、PermissionRequester、CameraHudState
  • CameraCaptureManager 依赖 CameraX(ProcessCameraProvider、ImageCapture、VideoCapture)、ExifInterface、JpegSizeLimiter
  • PhotosHandler 依赖系统媒体存储 ContentResolver、Bitmap 解码与压缩
  • PermissionRequester 依赖 Android ActivityResultLauncher 与系统设置页面
classDiagram
class CameraHandler {
+handleList(params)
+handleSnap(params)
+handleClip(params)
}
class CameraCaptureManager {
+listDevices()
+snap(params)
+clip(params)
}
class PermissionRequester {
+requestIfMissing(perms)
}
class PhotosHandler {
+handlePhotosLatest(params)
}
class JpegSizeLimiter {
+compressToLimit(...)
}
class CameraHudState {
+token
+kind
+message
}
CameraHandler --> CameraCaptureManager : "调用"
CameraHandler --> PermissionRequester : "请求权限"
CameraHandler --> CameraHudState : "更新HUD"
CameraCaptureManager --> JpegSizeLimiter : "压缩JPEG"
PhotosHandler --> PermissionRequester : "读取相册需权限"

性能考量

  • 拍照路径
    • 预热与方向:先旋转再缩放,减少重复变换开销
    • 压缩预算:JPEG 压缩与尺寸缩放双管齐下,确保 payload 不超限
  • 录制路径
    • 最低质量:优先降低质量而非分辨率,兼顾体积与清晰度
    • 预览绑定:强制绑定 Preview 以激活编码管线,避免无有效数据
    • 超时与清理:录制完成后等待 Finalize,超时或失败及时删除临时文件
  • 相册读取
    • inSampleSize 估算与按预算编码,避免一次性加载过大位图
    • 累计预算控制,保证多图返回不越界

应用架构

Android 应用位于 apps/android/app 模块,采用 Kotlin + Jetpack Compose UI + OkHttp WebSocket 通信的现代 Android 技术栈。核心目录与职责概览:

  • app/src/main/java/ai/openclaw/app
    • 入口与生命周期:NodeApp、MainActivity、NodeForegroundService
    • 状态与视图模型:MainViewModel
    • 核心运行时:NodeRuntime 及其子系统(网关会话、Canvas、相机、语音等)
    • UI 层:RootScreen 与各功能页(Compose)
    • 配置与清单:AndroidManifest.xml、build.gradle.kts
  • app/src/main/res:资源与主题
  • 测试:app/src/test/java 下按功能域分层测试
graph TB
subgraph "应用进程"
NA["NodeApp<br/>Application"]
MA["MainActivity<br/>ComponentActivity"]
VM["MainViewModel<br/>AndroidViewModel"]
NS["NodeForegroundService<br/>Service"]
end
subgraph "运行时核心"
NR["NodeRuntime<br/>核心编排"]
GS["GatewaySession<br/>操作端/节点端"]
CC["CanvasController<br/>WebView 承载"]
CM["CameraCaptureManager<br/>CameraX 封装"]
end
NA --> NR
MA --> VM
VM --> NR
MA --> NS
NR --> GS
NR --> CC
NR --> CM

核心组件

  • NodeApp:应用入口,负责严格模式调试与延迟初始化 NodeRuntime 单例
  • MainActivity:承载 Compose UI,绑定 MainViewModel,处理权限与系统窗口标志,延后启动前台服务
  • MainViewModel:将 NodeRuntime 的大量 StateFlow 暴露给 UI,并转发用户设置与连接控制命令
  • NodeRuntime:应用核心运行时,聚合网关会话、Canvas、相机、位置、短信、语音等子系统,统一状态与事件分发
  • NodeForegroundService:前台服务,根据 NodeRuntime 状态流动态更新通知
  • GatewaySession:封装 WebSocket 连接、鉴权、RPC 请求/响应、事件分发与自动重连
  • CanvasController:WebView 容器与调试状态注入,提供快照与脚本执行能力
  • CameraCaptureManager:基于 CameraX 的拍照/视频录制封装,含权限与 EXIF 方向处理

架构总览

应用采用“单例运行时 + 响应式状态流”的架构模式:

  • NodeApp.lazy 持有 NodeRuntime,避免冷启动路径冗余
  • NodeRuntime 使用协程与 StateFlow 组织多源状态(连接、Canvas、相机、语音、聊天等),并通过 GatewaySession 与远端网关保持双向通信
  • MainActivity 仅承担 UI 与生命周期职责,通过 MainViewModel 访问运行时能力
  • NodeForegroundService 以前台服务形式常驻,实时反映连接状态与麦克风监听状态
sequenceDiagram
participant App as "NodeApp"
participant Runtime as "NodeRuntime"
participant Operator as "GatewaySession(操作端)"
participant Node as "GatewaySession(节点端)"
participant Service as "NodeForegroundService"
App->>Runtime : lazy 初始化
Note over Runtime : 启动协程作用域并注册各子系统
Runtime->>Operator : 构建连接参数并发起连接
Runtime->>Node : 构建连接参数并发起连接
Operator-->>Runtime : 连接成功/失败回调
Node-->>Runtime : 连接成功/失败回调
Runtime-->>Service : 状态流变化连接/服务器名/麦克风
Service-->>Service : 更新通知

详细组件分析

启动流程与生命周期

  • 应用启动
    • AndroidManifest 指定 Application 为 NodeApp,Activity 为 MainActivity
    • NodeApp 在 onCreate 中启用严格模式(调试构建)并延迟初始化 NodeRuntime
  • MainActivity 生命周期
    • onCreate:禁用系统窗口装饰适配、初始化 PermissionRequester、绑定相机/短信权限请求器、设置 UI 主题与根屏幕、在首帧后延时启动 NodeForegroundService
    • onStart/onStop:切换前台状态,影响 NodeRuntime 内部的语音会话与外部音频捕获标记
  • 前台服务
    • NodeForegroundService 在 onCreate 中创建通知通道并首次显示“正在启动”通知
    • 通过组合 NodeRuntime 的多个状态流,动态更新标题与内容;支持从通知触发断开连接
flowchart TD
Start(["应用启动"]) --> AppInit["NodeApp.onCreate()<br/>启用严格模式"]
AppInit --> RuntimeInit["NodeApp.runtime.lazy 初始化"]
RuntimeInit --> ActivityInit["MainActivity.onCreate()"]
ActivityInit --> BindPerms["绑定权限请求器"]
ActivityInit --> SetUI["设置主题与根屏幕"]
ActivityInit --> DelayStart["首帧后延时启动前台服务"]
ActivityInit --> Foreground["NodeForegroundService.start()"]
Foreground --> CombineState["合并状态流<br/>连接/服务器/麦克风"]
CombineState --> UpdateNotify["更新通知"]
ActivityInit --> OnStart["onStart() 设置前台=true"]
ActivityInit --> OnStop["onStop() 设置前台=false"]

状态管理与 MainViewModel

  • MainViewModel 作为 AndroidViewModel,持有 NodeApp.runtime 并直接暴露 NodeRuntime 的大量 StateFlow(连接状态、Canvas 状态、相机/位置/麦克风/扬声器、聊天状态等)
  • 提供 setter 方法将用户设置与连接控制命令转发至 NodeRuntime,实现 UI 对运行时的可控访问
  • 通过 viewModels() 在 MainActivity 中获取实例,确保进程内共享与生命周期感知
classDiagram
class MainViewModel {
+canvas : CanvasController
+camera : CameraCaptureManager
+sms : SmsManager
+isConnected : StateFlow<Boolean>
+statusText : StateFlow<String>
+chat* 系列状态
+set*() 用户设置方法
+connect()/disconnect()
+sendChat()/abortChat()
}
class NodeRuntime {
+canvas : CanvasController
+camera : CameraCaptureManager
+sms : SmsManager
+gateways/statusText/pendingGatewayTrust
+chat* 系列状态
+set*() 用户设置
+connect()/disconnect()
+sendChat()/abortChat()
}
MainViewModel --> NodeRuntime : "委托调用"

NodeRuntime:运行时核心

  • 协程与作用域:使用 SupervisorJob + Dispatchers.IO 管理子系统任务,保证异常不扩散
  • 子系统聚合:Canvas、相机、位置、短信、通知、系统、照片、联系人、日历、运动、A2UI、Invoke 分发器等
  • 网关会话:维护两个 GatewaySession(操作端与节点端),分别处理连接、断开、事件与 RPC 请求
  • 状态流:对外暴露大量只读 StateFlow,内部通过 MutableStateFlow 维护可变状态并进行去抖与合并
  • 自动连接:依据偏好设置与发现列表,自动连接可信网关(基于存储的 TLS 指纹)
  • Canvas A2UI:支持从 WebView 触发 agent.request 并回传状态反馈
classDiagram
class NodeRuntime {
-scope : CoroutineScope
-canvas : CanvasController
-camera : CameraCaptureManager
-location : LocationCaptureManager
-sms : SmsManager
-discovery : GatewayDiscovery
-operatorSession : GatewaySession
-nodeSession : GatewaySession
-invokeDispatcher : InvokeDispatcher
+connect()/disconnect()
+requestCanvasRehydrate()
+handleCanvasA2UIActionFromWebView()
}
class GatewaySession {
+connect()
+disconnect()
+reconnect()
+request()
+sendNodeEvent()
}
class CanvasController {
+attach()/detach()
+navigate()
+eval()/snapshot*
}
NodeRuntime --> CanvasController : "持有"
NodeRuntime --> GatewaySession : "两个会话"
NodeRuntime --> CanvasController : "A2UI 交互"

UI 与导航

  • RootScreen:根据 onboardingCompleted 决定展示引导流程或主标签页
  • MainActivity:设置系统窗口装饰、权限请求器、绑定相机/短信权限、根据生命周期控制“防休眠”标志位、渲染主题与根屏幕
  • NodeForegroundService:根据运行时状态流动态更新通知,支持从通知断开连接
sequenceDiagram
participant UI as "RootScreen"
participant VM as "MainViewModel"
participant NR as "NodeRuntime"
UI->>VM : collect onboardingCompleted
alt 未完成
UI->>UI : 显示引导流程
else 已完成
UI->>UI : 显示主标签页
end
VM->>NR : set*()/connect()/sendChat()

网络与安全

  • GatewaySession 使用 OkHttp WebSocket 实现连接、鉴权、RPC 请求与事件分发
  • 支持 TLS 参数解析与指纹校验,首次连接时捕获指纹并提示用户验证,随后持久化到偏好中
  • 自动重连策略随失败次数指数退避,上限保护
flowchart TD
Connect["发起连接"] --> Challenge["接收挑战 nonce"]
Challenge --> Auth["构造 connect 参数<br/>签名/公钥/角色/权限"]
Auth --> Send["发送 connect 请求"]
Send --> Resp{"响应 ok?"}
Resp -- 是 --> Ready["建立会话<br/>保存 canvasHostUrl/mainSessionKey"]
Resp -- 否 --> Fail["抛出错误并断开"]
Ready --> Event["事件/请求分发"]
Event --> Retry["异常/断开后按指数退避重连"]

设备能力与媒体

  • CanvasController:WebView 容器,支持导航、调试状态注入、JS 评估与图片快照(PNG/JPEG)
  • CameraCaptureManager:基于 CameraX 的拍照与视频录制,含 EXIF 方向旋转、质量压缩、最大尺寸限制与权限检查
classDiagram
class CanvasController {
+attach()/detach()
+navigate()
+eval()
+snapshotPngBase64()
+snapshotBase64()
}
class CameraCaptureManager {
+snap()
+clip()
+listDevices()
-ensureCameraPermission()
-ensureMicPermission()
}
NodeRuntime --> CanvasController : "持有"
NodeRuntime --> CameraCaptureManager : "持有"

依赖关系分析

  • 模块耦合
    • NodeApp 与 NodeRuntime:单例持有,低耦合高内聚
    • MainActivity 与 MainViewModel:通过 ViewModelProvider 解耦,UI 不直接依赖运行时
    • NodeRuntime 与子系统:通过组合模式聚合,职责清晰
  • 外部依赖
    • Jetpack Compose、Material3、Navigation
    • OkHttp WebSocket、CameraX、Kotlinx Serialization、Kotlinx Coroutines
    • BouncyCastle、CommonMark 等
graph LR
NA["NodeApp"] --> NR["NodeRuntime"]
MA["MainActivity"] --> VM["MainViewModel"]
VM --> NR
NR --> GS["GatewaySession"]
NR --> CC["CanvasController"]
NR --> CM["CameraCaptureManager"]
MA --> NS["NodeForegroundService"]

性能考量

  • 启动路径优化:MainActivity 在首帧后才启动前台服务,减少冷启动阻塞
  • 状态流去抖与合并:NodeRuntime 使用 combine + distinctUntilChanged 控制 UI 更新频率
  • 图片快照与压缩:CanvasController 与 CameraCaptureManager 对图片进行缩放与质量压缩,避免超大负载
  • 协程调度:IO 调度器用于网络与磁盘 IO,Main 调度器用于 UI 相关操作
  • 通知更新:NodeForegroundService 仅在状态变化时更新通知,降低系统开销

语音功能

Android 语音相关代码主要位于应用模块的 voice 包与根级配置类中,配合 UI 层的引导与权限请求,形成完整的语音工作流。

graph TB
subgraph "Android 应用"
A["VoiceWakeMode<br/>唤醒模式枚举"]
B["VoiceWakeManager<br/>唤醒监听器"]
C["WakeWords<br/>唤醒词工具"]
D["SecurePrefs<br/>安全偏好存储"]
E["VoiceWakeCommandExtractor<br/>命令提取器"]
F["TalkDirectiveParser<br/>指令解析器"]
G["TalkModeManager<br/>Talk 模式管理器"]
H["ElevenLabsStreamingTts<br/>流式 TTS"]
I["OnboardingFlow<br/>引导与权限"]
J["VoiceTabScreen<br/>语音标签页 UI"]
end
A --> D
C --> D
D --> B
D --> G
B --> E
G --> F
G --> H
I --> J
J --> G

核心组件

  • 语音唤醒模式:VoiceWakeMode 定义 off/foreground/always 三种模式,并提供从原始字符串解析的方法。
  • 唤醒词管理:WakeWords 提供解析、变更检测与清洗逻辑;SecurePrefs 负责持久化存储与默认值。
  • 唤醒监听:VoiceWakeManager 使用 Android SpeechRecognizer 进行持续监听,结合 VoiceWakeCommandExtractor 提取触发后的命令。
  • Talk 指令解析:TalkDirectiveParser 解析首行 JSON 指令,支持多键名别名映射,剥离后返回纯文本与未知键列表。
  • Talk 模式:TalkModeManager 实现录音、转写、对话、TTS 播放与中断控制,支持 ElevenLabs 流式 TTS 与系统 TTS 双通道。
  • 流式 TTS:ElevenLabsStreamingTts 通过 WebSocket 接收实时音频,AudioTrack/PCM 或 MediaPlayer 播放。

架构总览

Android 语音功能采用“手动触发 + 云端转写 + 本地/云端 TTS”的混合架构。用户在语音标签页点击开始录音,TalkModeManager 启动 SpeechRecognizer,转写为文本后发送到网关,等待最终回复并进行 TTS 播放。若具备 ElevenLabs 凭证则优先使用其流式 TTS,否则回退系统 TTS。

sequenceDiagram
participant UI as "语音标签页 UI"
participant TM as "TalkModeManager"
participant SR as "SpeechRecognizer"
participant GW as "网关会话"
participant EL as "ElevenLabs"
participant SYS as "系统TTS"
UI->>TM : 开始录音
TM->>SR : 启动识别(云转写)
SR-->>TM : 部分/最终结果
TM->>GW : chat.send(带会话键)
GW-->>TM : agent 流事件/最终事件
alt 有 ElevenLabs 凭证
TM->>EL : 流式合成(WebSocket)
EL-->>TM : 音频流
TM-->>UI : 播放音频
else 回退
TM->>SYS : 文本转语音
SYS-->>TM : 语音输出
TM-->>UI : 播放语音
end

详细组件分析

语音唤醒模式与唤醒词管理

  • VoiceWakeMode:定义三种模式,提供字符串到枚举的解析,默认值为前台模式。
  • WakeWords:限制最大数量与长度,支持逗号分隔解析、变更检测与清洗。
  • SecurePrefs:持久化存储唤醒词列表与模式,提供默认唤醒词集合;加载时进行 JSON 解码与清洗。
classDiagram
class VoiceWakeMode {
+Off
+Foreground
+Always
+fromRawValue(raw)
}
class WakeWords {
+maxWords : Int
+maxWordLength : Int
+parseCommaSeparated(input)
+parseIfChanged(input, current)
+sanitize(words, defaults)
}
class SecurePrefs {
+setWakeWords(words)
+setVoiceWakeMode(mode)
+loadWakeWords()
+loadVoiceWakeMode()
}
VoiceWakeMode <.. SecurePrefs : "使用"
WakeWords <.. SecurePrefs : "清洗/校验"

语音唤醒监听与命令提取

  • VoiceWakeManager:封装 SpeechRecognizer 生命周期,处理错误与重启;监听部分/最终结果,调用 VoiceWakeCommandExtractor 提取命令。
  • VoiceWakeCommandExtractor:基于触发词正则匹配,提取触发词之后的自然语言命令,过滤空值与标点。
flowchart TD
Start(["开始监听"]) --> Listen["SpeechRecognizer 启动"]
Listen --> OnResult{"收到结果?"}
OnResult --> |否| Restart["延迟重启"]
OnResult --> |是| Extract["提取命令"]
Extract --> Valid{"命令有效?"}
Valid --> |否| Restart
Valid --> |是| Dispatch["派发命令回调"]
Dispatch --> Restart

Talk 指令解析机制

  • TalkDirectiveParser:解析首行 JSON 指令,支持多键名别名(如 voice_id、speakerBoost 等),记录未知键;剥离指令后返回纯文本与未知键列表。
flowchart TD
In(["输入文本"]) --> Split["按行分割"]
Split --> FindHead["定位首个非空行"]
FindHead --> IsObj{"是否为 JSON 对象?"}
IsObj --> |否| ReturnPlain["返回纯文本与空未知键"]
IsObj --> |是| Parse["解析对象字段"]
Parse --> MapKeys["键名归一化映射"]
MapKeys --> BuildDirective["构建指令对象"]
BuildDirective --> HasDirective{"存在有效字段?"}
HasDirective --> |否| ReturnPlain
HasDirective --> |是| Strip["移除首行与空行"]
Strip --> CollectUnknown["收集未知键"]
CollectUnknown --> Out(["返回指令+文本+未知键"])

Talk 模式:录音、转写、TTS 播放

  • 录音与转写:TalkModeManager 使用 SpeechRecognizer,启用云转写与静默窗口策略,避免过早结束。
  • 对话与订阅:支持 chat.subscribe 订阅事件流,缓存最终文本,减少轮询。
  • TTS 播放:优先 ElevenLabs 流式 TTS(WebSocket),失败或不支持时回退到文件下载播放或系统 TTS;支持音频焦点与中断控制。
sequenceDiagram
participant TM as "TalkModeManager"
participant SR as "SpeechRecognizer"
participant GW as "网关"
participant ST as "ElevenLabsStreamingTts"
participant AT as "AudioTrack/MediaPlayer"
participant SYS as "系统TTS"
TM->>SR : startListening(云转写)
SR-->>TM : 部分/最终结果
TM->>GW : chat.send + subscribe
GW-->>TM : 流式/最终事件
alt ElevenLabs 可用
TM->>ST : start + sendText
ST-->>TM : 音频流
TM->>AT : 播放
else 回退
TM->>SYS : speak
SYS-->>TM : 语音
TM->>AT : 播放
end

依赖关系分析

  • 组件耦合
    • VoiceWakeManager 依赖 WakeWords 与 VoiceWakeCommandExtractor,受 SecurePrefs 中的模式与触发词影响。
    • TalkModeManager 依赖 GatewaySession、ElevenLabsStreamingTts 与系统 TTS,内部维护状态流与播放令牌。
  • 外部依赖
    • Android SpeechRecognizer(云转写)
    • ElevenLabs API(流式 TTS)
    • Android AudioManager/AudioTrack/MediaPlayer(音频播放)
graph LR
SW["SecurePrefs"] --> VWM["VoiceWakeManager"]
WW["WakeWords"] --> VWM
VWE["VoiceWakeCommandExtractor"] --> VWM
VWM --> CMD["命令回调"]
TM["TalkModeManager"] --> SR["SpeechRecognizer"]
TM --> GW["GatewaySession"]
TM --> ELS["ElevenLabsStreamingTts"]
TM --> SYS["系统TTS"]

性能考虑

  • 转写与静默策略
    • 使用云转写模型,配合静默窗口参数,提升自然停顿后的识别稳定性。
    • 在播放期间可选择“说话时打断”以避免录音拾取播放音频导致的设备特定问题。
  • TTS 播放路径
    • 优先 WebSocket 流式 PCM,降低延迟;失败时降级为 MP3 文件下载播放,提高兼容性。
    • AudioTrack 缓冲区大小与最小缓冲区计算,避免 OEM 设备(如 OxygenOS/OnePlus)的 AudioTrack 写入问题。
  • 状态与资源管理
    • 使用播放令牌与生成器确保并发播放不会互相干扰;及时释放 MediaPlayer/AudioTrack 与 SpeechRecognizer。
    • 缓存最终聊天文本,减少历史查询开销。

Android 应用

Android 应用位于 apps/android 目录,采用 Gradle 多模块结构,核心模块为 app;测试与基准位于 benchmark、test 等目录。应用通过 Jetpack Compose 构建 UI,使用 OkHttp WebSocket 连接网关,结合 CameraX 实现相机与视频录制能力,并通过自研 NodeRuntime 统一调度各类节点命令与状态。

graph TB
subgraph "应用层"
MA["MainActivity<br/>生命周期与权限绑定"]
VM["MainViewModel<br/>状态与行为入口"]
RT["NodeRuntime<br/>运行时与会话调度"]
end
subgraph "节点处理层"
CAM["CameraCaptureManager<br/>拍照/录屏"]
DEVH["DeviceHandler<br/>设备信息/健康/权限"]
INV["InvokeDispatcher<br/>命令分发"]
A2UI["A2UIHandler<br/>Canvas/A2UI"]
end
subgraph "网关通信层"
GS["GatewaySession<br/>WebSocket/RPC"]
DISC["GatewayDiscovery<br/>发现/信任提示"]
end
subgraph "系统与权限"
PERM["PermissionRequester<br/>动态权限请求"]
SVC["NodeForegroundService<br/>前台服务"]
NLS["DeviceNotificationListenerService<br/>通知监听"]
end
MA --> VM
VM --> RT
RT --> CAM
RT --> DEVH
RT --> INV
RT --> A2UI
RT --> GS
RT --> DISC
MA --> PERM
MA --> SVC
MA --> NLS

核心组件

  • MainActivity:负责应用启动、权限请求器绑定、前台服务启动时机、保持屏幕常亮等。
  • MainViewModel:桥接 UI 与 NodeRuntime,暴露状态流与操作方法。
  • NodeRuntime:统一运行时,管理网关会话、节点命令分发、Canvas/A2UI、语音、聊天、设备能力等。
  • PermissionRequester:封装动态权限请求流程,支持理由对话与设置页引导。
  • CameraCaptureManager:基于 CameraX 的拍照与录屏能力,支持参数化控制与权限校验。
  • DeviceHandler:提供设备状态、信息、权限与健康度查询。
  • GatewaySession:基于 OkHttp 的 WebSocket 会话,负责连接、RPC 请求、事件分发与重连。

架构总览

应用采用“运行时统一调度 + 节点处理器 + 网关会话”的分层架构。NodeRuntime 将 UI 层与底层系统能力解耦,通过 InvokeDispatcher 将命令路由到具体处理器(如 CameraHandler、LocationHandler、DeviceHandler 等),并通过 GatewaySession 与网关建立长连接,实现命令调用、事件推送与 Canvas/A2UI 交互。

sequenceDiagram
participant UI as "UI/MainActivity"
participant VM as "MainViewModel"
participant RT as "NodeRuntime"
participant GS as "GatewaySession"
participant DISP as "InvokeDispatcher"
participant H as "各处理器(如Camera/Device)"
UI->>VM : 用户操作/状态订阅
VM->>RT : 调用连接/断开/命令
RT->>GS : 建立/维护会话
RT->>DISP : 分发命令(command,params)
DISP->>H : 路由到对应处理器
H-->>DISP : 返回结果或错误
DISP-->>RT : 汇总结果
RT-->>GS : 发送 node.invoke.result 或 node.event
GS-->>UI : 推送事件/状态更新

详细组件分析

设备控制与命令执行

  • 运行时调度:NodeRuntime 初始化多个处理器(相机、位置、设备、通知、系统、照片、联系人、日历、运动、短信、A2UI、调试等),并通过 InvokeDispatcher 将命令路由至对应处理器。
  • 命令分发:InvokeDispatcher 在处理前检查前置条件(如前台状态、相机启用、位置模式、短信可用性、录音权限等),并根据处理器返回结果构造响应。
  • 网关会话:GatewaySession 负责 WebSocket 连接、RPC 请求、事件分发与重连策略,支持 TLS 参数与指纹校验,确保首次连接的信任提示与后续自动连接的安全性。
classDiagram
class NodeRuntime {
+gateways
+discoveryStatusText
+isConnected
+nodeConnected
+statusText
+camera
+location
+sms
+canvas
+connect(endpoint)
+disconnect()
+refreshGatewayConnection()
}
class InvokeDispatcher {
+handleInvoke(command,params)
}
class GatewaySession {
+connect(endpoint,token,password,options,tls)
+request(method,params,timeout)
+sendNodeEvent(event,payload)
+reconnect()
+disconnect()
}
NodeRuntime --> InvokeDispatcher : "分发命令"
NodeRuntime --> GatewaySession : "会话管理"

相机与屏幕录制

  • 权限与参数:支持 CAMERA、RECORD_AUDIO(可选)权限;参数包括 facing、quality、maxWidth、durationMs、deviceId、includeAudio 等。
  • 拍照:使用 CameraX ImageCapture,读取 EXIF 方向并旋转/缩放,压缩至 5MB 以内,返回 base64 JPEG。
  • 录屏:使用 VideoCapture + Recorder,最低质量以减小文件体积;需预热摄像头并提供空预览以激活编码器;支持带/不带音频录制。
  • 错误处理:超时与失败场景删除临时文件、释放资源并抛出明确异常。
flowchart TD
Start(["开始 clip/snap"]) --> CheckPerm["检查相机/麦克风权限"]
CheckPerm --> |缺失| RequestPerm["弹窗请求权限"]
RequestPerm --> |拒绝| ThrowErr["抛出权限不足错误"]
RequestPerm --> |允许| BindCam["绑定生命周期与相机选择器"]
BindCam --> Mode{"操作类型"}
Mode --> |snap| TakePhoto["拍照并读取EXIF方向"]
TakePhoto --> Rotate["按EXIF旋转位图"]
Rotate --> Scale["按maxWidth缩放"]
Scale --> Limit["压缩至5MB内(base64上限)"]
Limit --> ReturnSnap["返回JPEG payload"]
Mode --> |clip| Record["准备录制(可含音频)"]
Record --> Warm["预热摄像头1.5秒"]
Warm --> StartRec["开始录制并等待定时结束"]
StartRec --> StopRec["停止录制并等待完成事件"]
StopRec --> Finalize{"是否成功完成"}
Finalize --> |否| Clean["删除临时文件并抛错"]
Finalize --> |是| ReturnClip["返回文件路径/时长/音频标记"]

设备命令与权限管理

  • 设备状态/信息/健康:DeviceHandler 提供电池、存储、网络、内存、温度、压力等级等信息;权限状态汇总(相机、麦克风、位置、短信、通知监听、通知、相册、联系人、日历、运动等)。
  • 权限请求:PermissionRequester 支持多权限批量请求、理由对话、被拒后引导至系统设置页。
  • 后台限制:应用通过前台服务维持关键能力(数据同步、通知等),并在生命周期变化时调整行为(如前台切换时停止语音会话)。
classDiagram
class DeviceHandler {
+handleDeviceStatus(params)
+handleDeviceInfo(params)
+handleDevicePermissions(params)
+handleDeviceHealth(params)
}
class PermissionRequester {
+requestIfMissing(permissions,timeout)
-showRationaleDialog(permissions)
-showSettingsDialog(permissions)
}
class NodeRuntime {
+setForeground(value)
+setMicEnabled(value)
+setSpeakerEnabled(value)
}
DeviceHandler ..> GatewaySession : "返回JSON负载"
PermissionRequester --> MainActivity : "触发系统权限对话"
NodeRuntime --> PermissionRequester : "绑定相机/短信等权限"

网络通信与设备发现

  • WebSocket 会话:GatewaySession 建立 wss/ws 连接,发送 connect 挑战、签名设备信息、接收 canvasHostUrl 与会话默认值;支持 TLS 参数与指纹校验。
  • 自动连接与信任:NodeRuntime 在发现可信网关后自动连接,首次连接要求用户确认 TLS 指纹并持久化;手动连接模式要求已保存指纹。
  • 事件与 RPC:支持 node.event 推送与 node.invoke.request 调用,InvokeDispatcher 将请求路由到对应处理器并回传结果。
sequenceDiagram
participant RT as "NodeRuntime"
participant GS as "GatewaySession"
participant GW as "网关"
RT->>GS : connect(endpoint, token/password, options, tls)
GS->>GW : 建立WebSocket
GW-->>GS : challenge(nonce)
GS->>GS : 构造connect参数(签名/权限/能力)
GS->>GW : 发送connect
GW-->>GS : 返回server/canvasHostUrl/sessionDefaults
GS-->>RT : onConnected回调
RT->>GS : sendNodeEvent / request
GS-->>RT : 事件/响应

Android 权限体系与后台服务限制

  • 权限清单:应用声明 INTERNET、NETWORK_STATE、FOREGROUND_SERVICE、POST_NOTIFICATIONS、NEARBY_WIFI_DEVICES、LOCATION、CAMERA、RECORD_AUDIO、SMS、READ_MEDIA_*、READ_CONTACTS、READ_CALENDAR、ACTIVITY_RECOGNITION 等。
  • 动态权限:相机、录音、短信、通知监听等在运行时请求;PermissionRequester 提供理由对话与设置页引导。
  • 后台限制:应用通过前台服务维持数据同步;前台切换时停止语音会话以节省资源;最小化对电池与性能的影响。

依赖关系分析

  • 构建与工具链:根级 build.gradle.kts 引入 Android 插件、ktlint、Compose、Serialization 插件;app/build.gradle.kts 配置编译目标、签名、依赖与打包规则。
  • 第三方库:OkHttp、Material3、CameraX、dnsjava、BCProv、Commonmark、Kotlinx Serialization、Kotlinx Coroutines、Kotest/Robolectric 测试框架等。
  • 资源与图标:mipmap、values、xml 等资源目录用于主题、备份/数据提取规则、网络配置与文件提供者。
graph LR
A["根构建脚本"] --> B["应用模块构建脚本"]
B --> C["OkHttp/网络"]
B --> D["CameraX/相机"]
B --> E["Material3/Compose"]
B --> F["Kotlinx/序列化"]
B --> G["测试框架"]

性能考虑

  • 启动路径精简:MainActivity 在首帧后才启动前台服务,降低冷启动时间。
  • 拍摄与录制优化:拍照前旋转/缩放在主线程完成但耗时短;录屏使用最低质量与空预览激活编码器,缩短初始化时间。
  • 资源压缩:JPEG 压缩限制在 5MB 内,避免传输与解析开销过大。
  • 任务调度:使用 SupervisorJob 与 IO 线程池隔离网络与 I/O;状态流组合与去重减少 UI 重组。
  • 打包与混淆:开启资源压缩与 ProGuard 规则,排除无关文件与元数据。
❌
❌