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

4 阅读33分钟

权限与能力管理

简介

本文件系统化梳理 iOS/macOS 平台的“权限与能力管理”体系,覆盖相机、麦克风、位置、运动追踪、日历、联系人、屏幕录制、通知、AppleScript、无障碍等权限的申请、运行时检查、状态监控与降级策略。文档同时给出权限配置清单、隐私政策链接与数据保护建议,并总结最佳实践与用户体验优化策略。

项目结构

围绕权限与能力管理的关键模块分布如下:

  • iOS 端
    • 网关连接控制器负责聚合当前设备权限状态并下发到网关
    • 相机访问封装在共享模块中,供 iOS/macOS 复用
    • 位置服务封装了授权请求与定位获取流程
  • macOS 端
    • 权限管理器统一处理各类权限的检查与交互式授权
    • 权限监控器周期性轮询权限状态变化
    • 设置界面支持逐项触发授权并刷新状态
  • 共享模块
    • 相机权限封装提供跨平台一致的授权判断与请求
  • 配置与清单
    • Info.plist 中声明各权限的用途说明文案
    • entitlements 与签名脚本定义应用能力与运行时权限
graph TB
subgraph "iOS"
GConn["GatewayConnectionController<br/>聚合权限状态并下发"]
CamCtl["CameraController<br/>相机访问封装"]
LocSvc["LocationService<br/>位置授权与定位"]
Ent["OpenClaw.entitlements<br/>能力清单"]
PlistI["Info.plist(iOS)<br/>权限用途说明"]
end
subgraph "macOS"
PermMgr["PermissionManager<br/>权限检查/授权/状态"]
PermMon["PermissionMonitor<br/>状态轮询监控"]
PermSet["PermissionsSettings<br/>设置页交互"]
PlistM["Info.plist(macOS)<br/>权限用途说明"]
end
subgraph "共享"
CamAuth["CameraAuthorization<br/>跨平台相机授权"]
end
GConn --> CamAuth
CamCtl --> CamAuth
LocSvc --> GConn
PermMgr --> PermMon
PermSet --> PermMgr
Ent --> GConn
PlistI --> GConn
PlistM --> PermMgr

核心组件

  • iOS 网关连接控制器:动态收集相机、麦克风、语音识别、位置、屏幕录制、相册、通讯录、日历、提醒、运动追踪等权限状态,作为“permissions”参数随连接上报。
  • macOS 权限管理器:集中实现各类权限的检查、交互式授权与状态查询;提供批量授权与单个能力授权接口。
  • 相机权限封装:跨平台通用的相机/麦克风授权判断与请求逻辑。
  • 位置服务:封装 iOS 位置授权流程,支持按需或始终授权模式。
  • 权限监控与设置:macOS 提供定时轮询监控权限状态,设置页支持逐项触发授权并延时刷新以等待系统设置稳定。

架构总览

下图展示 iOS/macOS 权限与能力管理的整体交互:

sequenceDiagram
participant UI as "设置界面(macOS)"
participant PM as "PermissionManager"
participant OS as "系统权限中心"
participant GW as "网关连接(iOS)"
UI->>PM : 触发某能力授权
PM->>OS : 请求授权(交互式)
OS-->>PM : 返回授权结果
PM-->>UI : 更新状态并延时刷新
UI->>GW : 拉取当前权限状态
GW->>GW : 聚合权限字典并下发

详细组件分析

iOS 权限状态采集与下发

  • 采集范围:相机、麦克风、语音识别、位置、屏幕录制、相册、通讯录、日历、提醒、运动追踪、手表支持状态等。
  • 采集方式:直接调用系统 API 查询授权状态,部分能力通过组合状态判断(如日历/提醒的全权/写入权限)。
  • 下发策略:作为“permissions”键值对随连接配置发送至网关,便于后端按权限启用相应能力。
flowchart TD
Start(["开始"]) --> Cam["相机授权状态"]
Cam --> Mic["麦克风授权状态"]
Mic --> SR["语音识别授权状态"]
SR --> Loc["位置授权+服务可用"]
Loc --> Scr["屏幕录制可用"]
Scr --> Photos["相册授权(授权/有限)"]
Photos --> Contacts["通讯录授权(授权/有限)"]
Contacts --> Cal["日历授权(全权/写入)"]
Cal --> Rem["提醒授权(全权/写入)"]
Rem --> Motion["运动追踪授权(活动/计步)"]
Motion --> Watch["手表支持/配对/安装/可达"]
Watch --> Build["构建权限字典"]
Build --> End(["结束"])

macOS 权限管理器

  • 统一入口:批量授权与单个能力授权,内部根据能力类型分派到对应子流程。
  • 授权流程:
    • 通知:请求授权选项并检查最终状态。
    • AppleScript:执行轻量脚本验证自动化权限,必要时打开系统设置对应面板。
    • 无障碍:通过系统信任接口触发提示。
    • 屏幕录制:预检与请求授权。
    • 相机/麦克风:调用系统 API 请求授权。
    • 语音识别:调用系统 API 请求授权。
    • 位置:若未开启系统定位服务则引导打开系统设置;否则发起当用/始终授权请求,并在超时后引导打开系统设置。
  • 状态查询:提供批量状态查询接口,用于 UI 或监控器刷新。
classDiagram
class PermissionManager {
+ensure(caps, interactive) [Capability : Bool]
+status(caps) [Capability : Bool]
+ensureVoiceWakePermissions(interactive) Bool
+voiceWakePermissionsGranted() Bool
}
class LocationPermissionRequester {
+request(always) CLAuthorizationStatus
+locationManagerDidChangeAuthorization(...)
}
PermissionManager --> LocationPermissionRequester : "位置授权时使用"

相机权限封装(跨平台)

  • 功能:判断相机/麦克风是否已授权;若未授权且允许交互,则触发系统授权请求。
  • 适用:iOS 的相机控制器与 macOS 的相机捕获服务均复用该封装。
sequenceDiagram
participant Caller as "调用方"
participant CA as "CameraAuthorization"
participant OS as "系统权限中心"
Caller->>CA : isAuthorized(mediaType)
alt 已授权
CA-->>Caller : true
else 未授权
Caller->>CA : 请求授权
CA->>OS : 发起授权
OS-->>CA : 返回结果
CA-->>Caller : true/false
end

位置权限(iOS)

  • 流程:若当前为“未决定”,先请求当用授权;若需要“始终”,而当前为“当用”,则进一步请求始终授权。
  • 定位获取:通过统一请求器封装一次性定位与超时控制,避免 UI 卡死。
sequenceDiagram
participant UI as "调用方"
participant LS as "LocationService"
participant LPR as "LocationPermissionRequester"
participant OS as "系统定位服务"
UI->>LS : 请求位置(含精度/过期/超时)
LS->>OS : 若未授权则请求授权
OS-->>LS : 授权状态
alt 需要“始终”
LS->>OS : 请求“始终”授权
OS-->>LS : 授权状态
end
LS->>LS : 解析定位(超时/精度/缓存)
LS-->>UI : 返回定位

权限状态监控与设置页

  • 监控器:注册/注销计数,首次注册启动定时器,周期性轮询最新权限状态;最小轮询间隔保障性能。
  • 设置页:逐项触发授权,授权完成后延时多次刷新以等待系统设置稳定生效。
flowchart TD
Reg["注册监控"] --> Start["启动定时器(1秒)"]
Start --> Poll["轮询权限状态"]
Poll --> Update{"状态变化?"}
Update --> |是| Apply["更新内存状态"]
Update --> |否| Wait["等待下次轮询"]
Apply --> Wait
Unreg["注销监控"] --> Stop["停止定时器并清空时间戳"]
subgraph "设置页"
Click["点击某能力授权"] --> Ensure["PermissionManager.ensure(...)"]
Ensure --> Refresh["延时刷新(多阶段)"]
end

依赖关系分析

  • iOS 端
    • 网关连接控制器依赖系统框架(AVFoundation、Contacts、CoreLocation、EventKit、Photos、ReplayKit、Speech)进行权限状态判断。
    • 相机控制器与相机捕获服务依赖共享的相机授权封装。
    • 位置服务依赖 CoreLocation。
  • macOS 端
    • 权限管理器依赖 AVFoundation、CoreLocation、Speech、UserNotifications、ApplicationServices 等。
    • 设置页依赖权限管理器与监控器。
  • 配置与清单
    • Info.plist 中的 Usage Description 文案用于系统弹窗说明用途。
    • entitlements 与签名脚本定义应用运行所需的系统能力键。
graph LR
GConn["GatewayConnectionController"] --> Sys["系统框架(AV/CL/EK/PH/RP/Speech)"]
CamCtl["CameraController"] --> CamAuth["CameraAuthorization"]
CamCap["CameraCaptureService"] --> CamAuth
LocSvc["LocationService"] --> CL["CoreLocation"]
PermMgr["PermissionManager"] --> SysMods["AV/CL/Speech/UN/AX"]
PermSet["PermissionsSettings"] --> PermMgr
PermMon["PermissionMonitor"] --> PermMgr
PlistI["Info.plist(iOS)"] --> GConn
PlistM["Info.plist(macOS)"] --> PermMgr
Ent["OpenClaw.entitlements"] --> GConn

性能考量

  • 轮询节流:macOS 权限监控器设置最小轮询间隔,避免频繁查询导致性能损耗。
  • 异步与并发:授权与状态查询采用异步方式,避免阻塞主线程。
  • 延迟刷新:设置页在触发授权后分阶段延迟刷新,减少 UI 抖动与无效重绘。
  • 位置请求超时:位置服务对一次性定位请求设置超时,防止长时间等待。

故障排查指南

  • 位置授权失败
    • 现象:位置授权无响应或立即拒绝。
    • 排查:确认系统定位服务已开启;若授权超时,系统会自动打开系统设置;检查位置权限文案与 Info.plist 配置。
  • 语音唤醒权限
    • 现象:无法使用语音唤醒。
    • 排查:确保麦克风与语音识别权限均已授权;可通过权限管理器批量检查。
  • 相机/麦克风被拒
    • 现象:相机/麦克风调用抛出权限错误。
    • 排查:调用相机授权封装进行授权;若被拒,引导用户前往系统设置开启。
  • 日历/提醒/通讯录权限
    • 现象:相关能力不可用。
    • 排查:iOS 端通过 EventKit/Contacts 判断授权状态;macOS 端通过权限管理器检查并触发授权。
  • 权限状态不一致
    • 现象:UI 显示与实际行为不符。
    • 排查:使用权限监控器强制刷新;设置页触发授权后等待系统设置稳定再刷新。

结论

本系统在 iOS 与 macOS 上实现了统一的权限与能力管理:iOS 侧通过网关连接控制器聚合权限状态并下发,macOS 侧通过权限管理器与监控器实现可观察的状态更新与交互式授权。共享模块保证跨平台一致性,配置清单确保系统弹窗具备清晰的用途说明。整体设计兼顾用户体验与合规要求,提供完善的降级与回退路径。

附录

权限配置清单与用途说明

  • iOS
    • Info.plist 中包含相机、麦克风、语音识别、位置、运动追踪、本地网络、照片库等用途说明。
    • entitlements 用于声明推送环境等能力。
  • macOS
    • Info.plist 中包含通知、屏幕录制、相机、位置、麦克风、语音识别、Apple Events 等用途说明。
    • 签名脚本定义运行时所需系统能力键。

隐私政策与数据保护

  • 隐私政策链接:iOS 应用元数据中提供隐私政策地址。
  • 数据保护建议:
    • 仅在获得明确授权后采集与传输敏感数据。
    • 对本地缓存的位置、媒体等数据进行最小化存储与及时清理。
    • 在 UI 中清晰说明权限用途,尊重用户选择并提供便捷的系统设置入口。

最佳实践与用户体验优化

  • 权限申请时机
    • 在功能首次使用前或用户主动触发时申请,避免冷启动即弹窗。
    • 对于位置、相机等敏感权限,提供简短说明与“稍后”选项。
  • 状态反馈
    • 使用设置页直观展示权限状态与操作按钮,授权后延时刷新。
    • 对于系统设置变更,提供手动刷新入口。
  • 降级与容错
    • 当权限被拒时,提供替代方案或禁用相关功能,避免崩溃。
    • 对位置请求设置合理超时与错误提示。

设备配对与连接

iOS 配对与连接相关代码主要位于以下模块:

  • Onboarding(引导与配对 UI):OnboardingWizardView、QRScannerView
  • Gateway(连接控制与发现):GatewayConnectionController、GatewayDiscoveryModel、GatewaySettingsStore、GatewayTLSPinning
  • Model(应用状态与会话):NodeAppModel
  • 平台对比参考:Android TLS 实现、通用重连策略、配对请求存储
graph TB
subgraph "iOS 引导层"
OW["OnboardingWizardView<br/>引导与配对 UI"]
QR["QRScannerView<br/>二维码扫描"]
end
subgraph "iOS 连接层"
GDC["GatewayConnectionController<br/>连接控制器"]
GDM["GatewayDiscoveryModel<br/>发现模型"]
GST["GatewaySettingsStore<br/>配置与凭据存储"]
TLS["GatewayTLSPinning<br/>TLS 严格校验"]
end
subgraph "iOS 应用状态"
NAM["NodeAppModel<br/>全局状态与会话"]
end
OW --> QR
OW --> GDC
GDC --> GDM
GDC --> GST
GDC --> TLS
GDC --> NAM

核心组件

  • 引导与配对 UI(OnboardingWizardView)
    • 负责展示配对步骤、触发 QR 扫描、处理扫描结果、显示连接状态与错误提示
    • 自动检测上次保存的网关与凭据,必要时自动弹出 QR 扫描
  • 连接控制器(GatewayConnectionController)
    • 统一协调发现、解析、TLS 探测、信任提示与自动连接
    • 处理手动连接、自动连接与重连逻辑
  • 发现模型(GatewayDiscoveryModel)
    • 基于 Bonjour 的网关发现,聚合多域结果并维护状态文本与调试日志
  • 配置与凭据存储(GatewaySettingsStore)
    • 使用 Keychain 存储实例 ID、令牌、密码、最近一次连接信息等
    • 支持迁移与清理
  • TLS 严格校验(GatewayTLSPinning)
    • 对服务器证书进行指纹比对,支持首次信任(TOFU)与持久化
  • 应用状态(NodeAppModel)
    • 维护连接状态、自动重连开关、配对暂停标志与会话任务

架构总览

下图展示了 iOS 端从扫描到连接的关键交互路径,包括配对、发现、信任与连接。

sequenceDiagram
participant User as "用户"
participant OW as "OnboardingWizardView"
participant QR as "QRScannerView"
participant GDC as "GatewayConnectionController"
participant GDM as "GatewayDiscoveryModel"
participant GST as "GatewaySettingsStore"
participant TLS as "GatewayTLSPinning"
participant NAM as "NodeAppModel"
User->>OW : 打开引导界面
OW->>QR : 打开二维码扫描器
QR-->>OW : 返回扫描结果深度链接/设置码
OW->>GDC : 解析并发起连接
GDC->>GST : 加载实例ID/令牌/密码
GDC->>GDM : 启动/更新发现
GDC->>GDC : 解析服务端点SRV/A/AAAA
alt 首次TLS连接
GDC->>GDC : 探测TLS指纹
GDC-->>OW : 触发信任提示
OW->>GDC : 用户确认信任
GDC->>GST : 持久化指纹
end
GDC->>NAM : 应用连接配置URL/凭据/TLS参数
NAM-->>User : 显示连接状态/成功

详细组件分析

二维码扫描与配对流程

  • 扫描入口与错误处理
    • 引导界面提供“扫描二维码”入口;当扫描失败或无有效内容时,显示错误提示并保持 UI 可用
  • 深度链接解析
    • 支持 setup code(base64url JSON)与通用网关链接两种格式
    • 解析后填充主机、端口、TLS 与可选令牌/密码,并自动发起连接
  • 图片导入回退
    • 允许从相册选择图片,通过 CoreImage 提取 QR 文本并重复上述流程
flowchart TD
Start(["开始"]) --> OpenScanner["打开二维码扫描器"]
OpenScanner --> Scan["扫描/选择图片"]
Scan --> Parse{"解析成功?"}
Parse --> |否| Error["显示错误并关闭扫描器"]
Parse --> |是| Fill["填充主机/端口/TLS/凭据"]
Fill --> Connect["发起连接"]
Connect --> End(["结束"])
Error --> End

网关发现机制

  • 多域发现与聚合
    • 在多个 Bonjour 域启动浏览器,收集服务结果,按名称排序并去重
    • 维护状态文本与调试日志,支持开启/关闭调试输出
  • 端点解析
    • 优先解析 SRV/A/AAAA 记录,避免使用未鉴权的 TXT 记录作为路由
  • 自动连接策略
    • 根据首选/上次发现的稳定 ID 或单个网关,在满足“已信任指纹”的前提下自动连接
classDiagram
class GatewayDiscoveryModel {
+gateways : [DiscoveredGateway]
+statusText : String
+debugLog : [DebugLogEntry]
+start()
+stop()
-recomputeGateways()
-updateStatusText()
}
class GatewayConnectionController {
+restartDiscovery()
+connectWithDiagnostics()
-resolveServiceEndpoint()
-maybeAutoConnect()
}
GatewayConnectionController --> GatewayDiscoveryModel : "使用"

信任建立与 TLS 校验

  • 首次连接的指纹探测
    • 若目标为明文且无已存指纹,则探测远端 TLS 指纹并通过信任提示展示给用户
  • 严格校验与持久化
    • 严格比对证书指纹;若允许 TOFU 且用户确认,将指纹写入持久化存储
  • 平台一致性
    • Android 端采用类似的指纹比对与 TOFU 流程,确保跨平台一致的安全体验
sequenceDiagram
participant GDC as "GatewayConnectionController"
participant TLS as "GatewayTLSPinning"
participant GST as "GatewaySettingsStore"
participant User as "用户"
GDC->>GDC : 探测TLS指纹
GDC-->>User : 展示信任提示SHA-256
User-->>GDC : 确认信任
GDC->>GST : 持久化指纹
GDC->>TLS : 应用严格校验参数
TLS-->>GDC : 校验通过

连接状态管理与自动重连

  • 状态驱动的 UI 更新
    • 通过 NodeAppModel 的状态文本与标识位(如配对暂停、自动重连开关)驱动 UI 表现
  • 自动重连循环
    • 在后台/前台切换、连接断开或需要配对时,按指数退避策略重试
    • 支持抖动、最大延迟与中止信号,避免风暴式重试
  • 会话与能力注册
    • 连接配置变更后,重新应用节点能力、命令与权限,确保即时生效
flowchart TD
S(["连接断开/需要重连"]) --> Check["检查自动重连开关/场景条件"]
Check --> |允许| Loop["进入重连循环指数退避+抖动"]
Loop --> Attempt["尝试连接应用配置"]
Attempt --> Success{"连接成功?"}
Success --> |是| Reset["重置退避"]
Success --> |否| Backoff["增加延迟并等待"]
Backoff --> Loop
Reset --> Done(["完成"])

配对代码生成与验证

  • 生成与去重
    • 生成唯一的人类友好配对码,保证不与现有集合冲突
  • 请求生命周期
    • 维护请求列表,按最后可见时间裁剪过期项,限制最大挂起数量
  • 验证与批准
    • 通过通道批准配对码,返回对应条目;支持账户维度匹配
flowchart TD
Req["收到配对请求(id, meta)"] --> Load["读取现有请求"]
Load --> Match{"是否已有相同ID且账户匹配?"}
Match --> |是| Update["更新最后可见时间/保留原码"]
Match --> |否| Gen["生成新唯一码"]
Update --> Persist["写回存储裁剪过期/上限"]
Gen --> Persist
Persist --> Return["返回(code, created)"]

连接控制器工作原理

  • 关键职责
    • 解析服务端点、构建 URL、组装连接选项(角色、作用域、能力、命令、权限、客户端标识)
    • 管理信任提示、接受/拒绝、持久化指纹与自动连接
  • 场景适配
    • 发现连接:解析 SRV/A/AAAA,必要时探测指纹
    • 手动连接:根据主机/端口/TLS 决策,必要时探测指纹
    • 最近连接:优先从持久化恢复
classDiagram
class GatewayConnectionController {
+connect(gateway)
+connectManual(host,port,useTLS)
+connectLastKnown()
+acceptPendingTrustPrompt()
+declinePendingTrustPrompt()
-makeConnectOptions()
-resolveServiceEndpoint()
-probeTLSFingerprint()
}
class GatewaySettingsStore {
+loadGatewayToken()
+loadGatewayPassword()
+saveLastGatewayConnectionManual()
+saveLastGatewayConnectionDiscovered()
}
class NodeAppModel {
+applyGatewayConnectConfig()
+gatewayAutoReconnectEnabled
+gatewayPairingPaused
}
GatewayConnectionController --> GatewaySettingsStore : "读取凭据/连接记录"
GatewayConnectionController --> NodeAppModel : "应用连接配置"

网关信任提示与安全认证

  • 信任提示
    • 首次 TLS 连接时,展示远端证书 SHA-256 指纹,要求用户核验
  • 认证源
    • 支持设备令牌、共享令牌、密码等多种认证方式
  • 平台实现
    • iOS 与 Android 均实现指纹比对与 TOFU 逻辑,确保一致的安全边界
sequenceDiagram
participant GDC as "GatewayConnectionController"
participant GST as "GatewaySettingsStore"
participant TLS as "GatewayTLSPinning"
participant User as "用户"
GDC->>TLS : 探测远端证书指纹
TLS-->>GDC : 返回指纹
GDC-->>User : 展示信任提示
User-->>GDC : 同意/拒绝
alt 同意
GDC->>GST : 保存指纹
GDC->>TLS : 应用严格校验
else 拒绝
GDC-->>User : 显示离线
end

连接失败处理与重连机制

  • 失败分类
    • 未配对:暂停重连,等待用户批准;UI 保持稳定,避免闪烁
    • 凭据缺失/错误:暂停自动重连,直到用户提供正确凭据
  • 重连策略
    • 指数退避 + 抖动,支持最大延迟与中止信号
    • 在前台活跃、非后台静默期间维持更积极的重连节奏

连接配置存储

  • Keychain 存储
    • 实例 ID、令牌、密码、最近一次连接信息、客户端 ID 覆盖与代理选择
  • UserDefaults 协同
    • 用于开关、默认值与迁移;Keychain 作为最终可信存储
  • 迁移与清理
    • 首次访问时将旧的 UserDefaults 数据迁移到 Keychain,并清理冗余键

依赖关系分析

  • 组件耦合
    • OnboardingWizardView 依赖 GatewayConnectionController 与 GatewaySettingsStore
    • GatewayConnectionController 依赖 GatewayDiscoveryModel、GatewaySettingsStore、GatewayTLSPinning 与 NodeAppModel
  • 外部依赖
    • Bonjour/NWBrowser 用于服务发现
    • CoreImage 用于图片导入的 QR 提取
    • Keychain 用于凭据与连接记录的持久化
graph LR
OW["OnboardingWizardView"] --> GDC["GatewayConnectionController"]
OW --> GST["GatewaySettingsStore"]
GDC --> GDM["GatewayDiscoveryModel"]
GDC --> GST
GDC --> TLS["GatewayTLSPinning"]
GDC --> NAM["NodeAppModel"]

性能考虑

  • 发现与解析
    • 限制解析超时,避免阻塞主线程;在主线程运行底层网络回调以确保及时响应
  • 重连退避
    • 合理设置初始延迟与最大延迟,结合抖动降低同步风暴风险
  • UI 刷新
    • 使用观察者模式与最小化状态变更,减少不必要的视图重建

系统集成功能

iOS 应用位于 apps/ios/Sources 下,采用按功能域分层的组织方式:

  • 设备与系统状态:Device 子目录
  • 系统服务访问:Calendar、Contacts、Reminders、EventKit
  • 网关连接与安全:Gateway 子目录
  • 应用模型与业务逻辑:Model 子目录
  • 通知与系统事件:Services 子目录
  • 应用入口与生命周期:OpenClawApp.swift
graph TB
subgraph "应用入口"
A["OpenClawApp.swift<br/>应用入口与生命周期"]
end
subgraph "设备与系统状态"
B["DeviceInfoHelper.swift<br/>设备与平台信息"]
C["DeviceStatusService.swift<br/>设备状态聚合"]
D["NetworkStatusService.swift<br/>网络状态检测"]
end
subgraph "系统服务"
E["CalendarService.swift<br/>日历事件"]
F["ContactsService.swift<br/>联系人"]
G["RemindersService.swift<br/>提醒事项"]
H["EventKitAuthorization.swift<br/>权限判定"]
end
subgraph "网关与连接"
I["GatewayConnectionController.swift<br/>发现/连接/信任验证"]
J["NodeAppModel.swift<br/>应用模型与会话管理"]
end
subgraph "通知与事件"
K["NotificationService.swift<br/>通知中心封装"]
L["OpenClawApp.swift<br/>推送/后台任务/场景事件"]
end
A --> J
J --> I
J --> C
J --> D
J --> E
J --> F
J --> G
E --> H
F --> H
G --> H
A --> K
A --> L

核心组件

  • 应用入口与生命周期:负责应用启动、场景状态变更、远程推送注册与处理、后台刷新任务调度。
  • 设备信息与状态:提供平台版本、设备型号、应用版本、电池/热/存储/网络状态等聚合数据。
  • 系统服务访问:通过 EventKit/Contacts 访问日历、联系人、提醒事项;统一权限判定与错误处理。
  • 网关连接控制器:负责网关发现、服务解析、TLS 指纹校验、自动重连与能力/命令/权限上报。
  • 应用模型:统一管理节点与操作者会话、语音唤醒、Talk 模式、深链处理、通知桥接与后台恢复策略。
  • 通知中心:封装 UNUserNotificationCenter 的授权状态查询、授权请求与通知添加。

架构总览

系统以 NodeAppModel 为核心协调器,向上承载 UI 与用户交互,向下对接设备状态、系统服务、网关连接与通知系统。应用入口 OpenClawAppDelegate 负责系统级事件(推送、后台任务、场景切换)与应用模型的绑定。

sequenceDiagram
participant App as "OpenClawApp"
participant Delegate as "OpenClawAppDelegate"
participant Model as "NodeAppModel"
participant Gate as "GatewayConnectionController"
participant Dev as "DeviceStatusService"
participant Net as "NetworkStatusService"
participant Cal as "CalendarService"
participant Con as "ContactsService"
participant Rem as "RemindersService"
participant Noti as "NotificationService"
App->>Delegate : 初始化并设置委托
App->>Model : 创建并注入到环境
App->>Gate : 构造连接控制器
Model->>Dev : 获取设备状态
Model->>Net : 获取网络状态
Model->>Cal : 日历读写
Model->>Con : 联系人读写
Model->>Rem : 提醒事项读写
Delegate->>Noti : 注册推送与授权
Delegate->>Model : 推送到达时唤醒
Gate->>Model : 自动重连/能力上报

详细组件分析

设备信息与状态监控

  • 设备信息:平台字符串、设备家族、机器标识、应用版本/构建号等,用于网关节点负载与设置展示。
  • 设备状态:电池电量/状态、低电量模式、热状态、存储总量/可用/已用、网络状态、系统运行时长。
  • 网络状态:基于 Network.framework 的 NWPathMonitor,支持超时回退与接口类型识别。
classDiagram
class DeviceInfoHelper {
+platformString() String
+platformStringForDisplay() String
+deviceFamily() String
+modelIdentifier() String
+appVersion() String
+appBuild() String
+openClawVersionString() String
}
class DeviceStatusService {
-networkStatus NetworkStatusService
+status() OpenClawDeviceStatusPayload
+info() OpenClawDeviceInfoPayload
-batteryStatus() OpenClawBatteryStatusPayload
-thermalStatus() OpenClawThermalStatusPayload
-storageStatus() OpenClawStorageStatusPayload
}
class NetworkStatusService {
+currentStatus(timeoutMs : Int) OpenClawNetworkStatusPayload
-payload(from : NWPath) OpenClawNetworkStatusPayload
-fallbackPayload() OpenClawNetworkStatusPayload
}
DeviceStatusService --> NetworkStatusService : "依赖"

系统服务访问与数据同步(日历、联系人、提醒事项)

  • 权限控制:统一通过 EventKitAuthorization 判定读/写权限,避免在调用链中弹窗导致阻塞。
  • 日历:支持事件范围查询与新增,限制返回条数,支持默认日历解析与错误语义化。
  • 联系人:支持名称搜索与全量枚举,去重与匹配策略,支持电话/邮箱归一化与存在性检查。
  • 提醒事项:支持列表筛选(全部/完成/未完成)与新增,支持截止时间解析与默认列表解析。
classDiagram
class CalendarService {
+events(params) OpenClawCalendarEventsPayload
+add(params) OpenClawCalendarAddPayload
-resolveCalendar(...) EKCalendar
-resolveRange(...) (Date,Date)
}
class ContactsService {
+search(params) OpenClawContactsSearchPayload
+add(params) OpenClawContactsAddPayload
-authorizedStore() CNContactStore
-findExistingContact(...) CNContact?
-matchContacts(...) CNContact?
-normalizePhone(String) String
}
class RemindersService {
+list(params) OpenClawRemindersListPayload
+add(params) OpenClawRemindersAddPayload
-resolveList(...) EKCalendar
}
class EventKitAuthorization {
<<static>>
+allowsRead(status) Bool
+allowsWrite(status) Bool
}
CalendarService --> EventKitAuthorization : "权限判定"
RemindersService --> EventKitAuthorization : "权限判定"

网关连接与系统事件处理

  • 发现与连接:基于 Bonjour/MDNS 解析服务端点,支持手动与自动连接,TLS 指纹校验与信任提示。
  • 自动重连:根据场景状态(前台/后台)与健康监测策略进行重连与抑制。
  • 通知桥接:将 Apple Watch 快速回复映射为应用内事件,支持静默推送唤醒与后台刷新任务。
  • 会话与能力:动态生成能力、命令与权限集合,确保与系统授权状态一致。
sequenceDiagram
participant UI as "界面"
participant Gate as "GatewayConnectionController"
participant DNS as "GatewayServiceResolver"
participant TLS as "GatewayTLSFingerprintProbe"
participant Model as "NodeAppModel"
UI->>Gate : 触发连接
Gate->>DNS : 解析服务端点
DNS-->>Gate : 返回主机/端口
Gate->>TLS : 探测TLS指纹
TLS-->>Gate : 返回指纹或失败
alt 已信任或允许TOFU
Gate->>Model : 启动自动重连/能力上报
else 需要信任提示
Gate-->>UI : 展示信任提示
end

通知与系统事件处理

  • 授权与分类:动态请求通知授权,按动作数量与样式创建分类,支持被动/限时/活动打断级别。
  • 推送处理:远程推送到达时唤醒应用模型,执行静默唤醒逻辑并安排后台刷新任务。
  • 场景事件:场景进入后台时安排后台刷新任务,前台恢复时清理抑制与恢复会话。
flowchart TD
Start(["推送到达"]) --> Parse["解析通知内容"]
Parse --> IsWatch{"是否Apple Watch提示?"}
IsWatch --> |是| Present["显示横幅/列表/声音"]
IsWatch --> |否| NoPresent["不显示"]
Present --> Route["路由到应用模型处理"]
NoPresent --> Route
Route --> Wake["执行静默唤醒/后台刷新"]
Wake --> End(["完成"])

依赖关系分析

  • 组件耦合:NodeAppModel 作为协调器,依赖设备状态、网络状态、系统服务与网关控制器;应用入口仅负责生命周期与系统事件。
  • 外部依赖:UIKit/Foundation/Network/EventKit/Contacts/Photos/ReplayKit 等系统框架;OpenClawKit 协议与数据模型。
  • 可能的循环依赖:当前结构以 NodeAppModel 为中心向外依赖,未见明显循环;注意服务层不应反向依赖应用模型。
graph LR
OpenClawApp["OpenClawApp"] --> NodeAppModel["NodeAppModel"]
NodeAppModel --> Gateway["GatewayConnectionController"]
NodeAppModel --> Device["DeviceStatusService"]
NodeAppModel --> Net["NetworkStatusService"]
NodeAppModel --> Cal["CalendarService"]
NodeAppModel --> Con["ContactsService"]
NodeAppModel --> Rem["RemindersService"]
OpenClawApp --> Notify["NotificationService"]

性能考虑

  • 异步与超时:网络状态检测使用带超时的 Continuation,避免长时间阻塞;推送与后台任务使用任务调度与过期处理。
  • 资源释放:后台任务到期时取消挂起任务,结束后台任务句柄;断开网关连接前清理状态。
  • 权限与UI:避免在 invoke 流程中触发系统授权弹窗,降低超时风险;对联系人/日历等批量操作设置上限。
  • 缓存策略:应用模型维护会话键与品牌配置,减少重复请求;后台恢复时优先健康检查再断线重连,避免“假连接”。

语音触发与唤醒

语音相关能力在多端并行实现,并通过网关统一管理全局触发词列表:

  • iOS:VoiceWakeManager 负责唤醒检测;TalkModeManager 支持连续对话与 TTS
  • macOS:VoiceWakeRuntime/VoiceWakeTester/VoiceWakeSettings 提供运行时、测试与设置界面
  • Android:VoiceWakeMode 控制唤醒模式;SecurePrefs 存储本地偏好;GatewayEventHandler 同步网关触发词
  • 网关:voicewake.ts 提供方法与事件广播;infra 层持久化触发词
  • 通用 TTS:支持多种提供商与输出格式转换(含电话场景)
graph TB
subgraph "iOS"
VWM["VoiceWakeManager.swift"]
VWP["VoiceWakePreferences.swift"]
TMM["TalkModeManager.swift"]
end
subgraph "macOS"
VWR["VoiceWakeRuntime.swift"]
VWT["VoiceWakeTester.swift"]
VWS["VoiceWakeSettings.swift"]
end
subgraph "Android"
VWMd["VoiceWakeMode.kt"]
GHE["GatewayEventHandler.kt"]
SEC["SecurePrefs.kt"]
end
subgraph "网关"
GWV["voicewake.ts服务端"]
INF["voicewake.ts基础设施"]
end
subgraph "通用"
TTS["tts.ts"]
TTSM["telephony-tts.ts"]
TTSO["tts-openai.ts"]
DVM["voice-message.ts"]
end
VWM --- VWP
TMM --- TTS
VWR --- VWT
VWS --- VWR
VWMd --- GHE
GHE --- GWV
GWV --- INF
TTSM --- TTS
TTSO --- TTS
DVM --- TTS

核心组件

  • 语音唤醒管理器(iOS)
    • 负责麦克风与语音识别权限申请、音频引擎配置、识别任务启动与结果回调、暂停/恢复外部音频捕获、触发词匹配与命令分发
  • 语音唤醒运行时(macOS)
    • 基于 AVAudioEngine 与 SFSpeechRecognizer 的识别管线;静音窗口、触发后等待、冷却与去抖;触发音效播放
  • 触发词同步与存储(网关)
    • 提供获取/设置接口与事件广播;本地 JSON 文件持久化;默认触发词与清理规则
  • 语音反馈与通话模式(多端)
    • Talk 模式:静音窗口发送、打断策略、ElevenLabs 流式播放;电话场景:TTS 输出格式转换(PCM→μ-law 8kHz)
  • 偏好设置与测试(macOS)
    • 触发词编辑、附加语言选择、麦克风选择、电平表与测试状态展示

架构总览

语音唤醒与通话的整体流程如下:

sequenceDiagram
participant User as "用户"
participant iOS as "VoiceWakeManager(iOS)"
participant macOS as "VoiceWakeRuntime(macOS)"
participant GW as "网关(voicewake)"
participant Infra as "持久化(触发词)"
participant TTS as "TTS/播放"
User->>iOS : "说“触发词 …”"
iOS->>iOS : "权限检查/音频引擎配置"
iOS->>GW : "WebSocket : voicewake.get / voicewake.set"
GW->>Infra : "读取/写入触发词"
iOS-->>User : "触发音效 + 开始录音"
iOS->>TTS : "合成并播放回复"
User->>macOS : "启用 Voice Wake"
macOS->>GW : "同步触发词"
macOS-->>User : "测试/设置界面"

详细组件分析

iOS 语音唤醒管理器(VoiceWakeManager)

职责与流程要点:

  • 权限管理:麦克风与语音识别授权,超时处理与错误提示
  • 音频引擎:测量模式、混音与其他应用、蓝牙支持;输入节点 tap 缓冲队列
  • 识别管线:识别请求、部分结果、回调中提取命令、去重与重启
  • 外部音频捕获:暂停/恢复,避免冲突
  • 触发词:从 UserDefaults 加载、变更监听、清洗与限制长度/数量
classDiagram
class VoiceWakeManager {
+isEnabled : Bool
+isListening : Bool
+statusText : String
+triggerWords : [String]
+lastTriggeredCommand : String?
-audioEngine : AVAudioEngine
-speechRecognizer : SFSpeechRecognizer?
-recognitionRequest : SFSpeechAudioBufferRecognitionRequest?
-recognitionTask : SFSpeechRecognitionTask?
-tapQueue : AudioBufferQueue?
-tapDrainTask : Task
-lastDispatched : String?
-onCommand(cmd)
-userDefaultsObserver
-suppressedByTalk : Bool
+configure(onCommand)
+setEnabled(Bool)
+setSuppressedByTalk(Bool)
+start() async
+stop()
+suspendForExternalAudioCapture() Bool
+resumeAfterExternalAudioCapture(Bool)
-startRecognition() throws
-tearDownRecognitionPipeline()
-makeRecognitionResultHandler() (result,error)->Void
-handleRecognitionCallback(...)
-extractCommand(...)
-requestMicrophonePermission() async Bool
-requestSpeechPermission() async Bool
}
class AudioBufferQueue {
-buffers : [AVAudioPCMBuffer]
+enqueueCopy(...)
+drain() [AVAudioPCMBuffer]
+clear()
}
VoiceWakeManager --> AudioBufferQueue : "使用"

macOS 语音唤醒运行时与测试

  • 运行时(VoiceWakeRuntime)
    • 静音窗口、触发后等待、强制停止、去抖、RMS 能量估计、触发音效
    • 文本回退静音检测、冷却与防重复触发
  • 测试器(VoiceWakeTester)
    • 识别请求/任务生命周期、输入节点 tap、缓冲追加、状态机更新
    • 触发词归一化、片段令牌化、静音等待与最终化
flowchart TD
Start(["开始识别"]) --> CheckFmt["检查音频格式<br/>通道数/采样率有效"]
CheckFmt --> InstallTap["安装输入节点 Tap<br/>缓冲队列"]
InstallTap --> EngineStart["启动 AVAudioEngine"]
EngineStart --> Listen["监听识别回调"]
Listen --> Match{"匹配触发词?"}
Match --> |是| BeginCap["开始捕获<br/>播放触发音效"]
Match --> |否| Listen
BeginCap --> SilenceWin["静音窗口计时"]
SilenceWin --> Timeout{"超时/静音?"}
Timeout --> |是| Finalize["提交并重置"]
Timeout --> |否| Continue["继续收集"]
Continue --> Listen

触发词同步与偏好设置

  • 网关侧
    • 方法:voicewake.get / voicewake.set;事件:voicewake.changed
    • 存储:~/.openclaw/settings/voicewake.json;默认触发词;清理与上限
  • iOS 侧
    • UserDefaults 读写触发词;变更监听;清洗与长度/数量限制
  • macOS 侧
    • 设置界面:触发词表格、附加语言、麦克风选择、电平表、测试状态
  • Android 侧
    • VoiceWakeMode:off/foreground/always;SecurePrefs 存储触发词与模式;GatewayEventHandler 从网关拉取并应用
sequenceDiagram
participant UI as "设置界面(macOS)"
participant iOS as "VoiceWakePreferences"
participant GW as "网关"
participant INF as "持久化"
UI->>GW : "voicewake.set({triggers})"
GW->>INF : "写入触发词"
GW-->>UI : "voicewake.changed 广播"
GW-->>iOS : "WebSocket : voicewake.changed"
iOS->>iOS : "加载并清洗触发词"

语音通话模式与语音 Orb 覆盖层

  • 行为概览:持续语音对话,静音窗口发送,打断策略,回复写入 WebChat,支持语音指令前缀控制 TTS
  • macOS UI:菜单栏切换、Overlay 显示阶段状态(听/想/说)、点击交互
  • iOS:动态噪声阈值估计、阈值上下限、音频活动记录
  • Android:输出格式校验(pcm_16/22/24/44kHz、mp3_),延迟层级校验
sequenceDiagram
participant User as "用户"
participant TMM as "TalkModeManager(iOS)"
participant TMR as "TalkModeRuntime(macOS)"
participant Chat as "聊天/WebChat"
participant TTS as "ElevenLabs TTS"
User->>TMM : "开始 Talk"
TMM->>TMM : "噪声阈值估计/静音检测"
TMM->>Chat : "发送转录(静音窗口)"
Chat-->>TMM : "模型回复(JSON语音指令可选)"
TMM->>TTS : "流式合成"
TTS-->>User : "播放语音"
User->>TMM : "打断(可选)"
TMM->>TTS : "停止播放"

语音反馈与电话场景 TTS

  • 通用 TTS:按优先级尝试提供商,ElevenLabs 输出格式适配,返回采样率与格式
  • 电话 TTS:将 PCM 转换为 μ-law 8kHz(Twilio/Media Streams)
  • Discord 语音消息:生成波形占位图,失败时回退
flowchart TD
In["文本"] --> Resolve["解析配置/提供商顺序"]
Resolve --> Try["逐个尝试提供商"]
Try --> |成功| Out["PCM/格式信息"]
Try --> |失败| Next["下一个提供商"]
Next --> Try
Out --> Convert["电话场景: PCM→μ-law 8kHz"]
Convert --> Done["返回音频缓冲"]

依赖关系分析

  • 平台耦合度
    • iOS 与 macOS 分别封装独立的识别与 UI;Android 以模式开关与本地偏好为主
  • 网关集中化
    • 触发词由网关统一管理并通过事件广播,确保跨端一致性
  • 音频栈
    • AVAudioEngine + SFSpeechRecognizer(iOS/macOS);Tap 队列与异步 drain 保证实时性
  • TTS 与输出
    • 通用 TTS 解析配置与提供商;电话场景专用格式转换
graph LR
iOS_VWM["VoiceWakeManager(iOS)"] --> |权限/识别| iOS_AVA["AVAudioEngine/SFSpeech"]
macOS_VWR["VoiceWakeRuntime(macOS)"] --> |识别/静音| macOS_AVA["AVAudioEngine/SFSpeech"]
Android_GW["GatewayEventHandler(Android)"] --> |广播| Android_UI["VoiceWakeMode/SecurePrefs"]
iOS_TMM["TalkModeManager(iOS)"] --> |TTS| TTS_Core["tts.ts"]
macOS_TMR["TalkModeRuntime(macOS)"] --> |TTS| TTS_Core
TTS_Core --> Phone["telephony-tts.ts"]
TTS_Core --> OpenAI["tts-openai.ts"]

性能考量

  • 音频缓冲与实时性
    • iOS/macOS 使用输入节点 tap 将缓冲复制到队列,drain 任务周期性追加至识别请求,降低主线程压力
  • 静音检测与阈值
    • iOS 动态噪声阈值估计,基于样本排序取均值;macOS RMS 能量估计与噪声地板融合,提升端点检测鲁棒性
  • 输出格式与延迟
    • Android 支持多种 PCM 采样率;macOS/iOS 默认低延迟 PCM;可选 mp3 以换取更低带宽
  • 识别与播放
    • 识别部分结果与去重,避免重复触发;触发音效与播放打断策略减少误操作

Canvas 表面渲染

Canvas 在多端通过 WebView/原生窗口承载 A2UI 内容,并由统一的消息协议驱动渲染与交互。

graph TB
subgraph "iOS"
IOS_ScreenController["ScreenController<br/>导航/快照/调试状态"]
IOS_ScreenWebView["ScreenWebView<br/>WKWebView 协作器"]
IOS_ScreenRecordService["ScreenRecordService<br/>ReplayKit 录制"]
end
subgraph "Android"
AND_CanvasScreen["CanvasScreen<br/>Compose 承载 WebView"]
AND_CanvasController["CanvasController<br/>导航/快照/调试状态"]
end
subgraph "macOS"
MAC_CanvasManager["CanvasManager<br/>面板展示/自动导航"]
end
subgraph "共享层"
Shared_A2UICommands["CanvasA2UICommands<br/>推送/重置命令"]
Shared_A2UIAction["CanvasA2UIAction<br/>动作上下文/格式化"]
Shared_BootstrapJS["bootstrap.js<br/>A2UI Host/Lit 组件"]
Shared_ScreenCommands["ScreenCommands<br/>屏幕命令定义"]
end
IOS_ScreenController --> IOS_ScreenWebView
IOS_ScreenController --> IOS_ScreenRecordService
AND_CanvasScreen --> AND_CanvasController
MAC_CanvasManager --> Shared_BootstrapJS
Shared_A2UICommands --> Shared_BootstrapJS
Shared_A2UIAction --> Shared_BootstrapJS
Shared_ScreenCommands --> IOS_ScreenRecordService
Shared_ScreenCommands --> AND_CanvasController

核心组件

  • iOS ScreenController:负责 WebView 导航、快照生成、调试状态注入与 A2UI 就绪检测
  • Android CanvasController:负责 WebView 导航、快照生成、调试状态注入与参数解析
  • macOS CanvasManager:负责 Canvas 面板展示、自动导航到网关推送的 A2UI 地址、调试状态刷新
  • 共享 A2UI 命令与动作:定义 canvas.a2ui.push/reset 与动作上下文格式化
  • bootstrap.js:A2UI Host 组件,处理消息、派发用户动作、桥接到原生
  • 屏幕录制服务:iOS 使用 ReplayKit,Android/macOS 通过网关或应用内流程实现

架构总览

Canvas 渲染采用“消息驱动 + 平台桥接”的模式:

  • 应用侧通过命令推送 A2UI 消息,A2UI Host 解析并渲染为可交互界面
  • 用户在 Canvas 中触发的动作被序列化并通过原生桥发送至应用层
  • 应用层根据动作类型执行业务逻辑(如屏幕录制、导航等),并将结果回推渲染
sequenceDiagram
participant App as "应用层"
participant Host as "A2UI Host<br/>bootstrap.js"
participant Native as "原生桥接"
participant GW as "网关/远程通道"
App->>Host : "canvas.a2ui.push" 推送消息数组
Host->>Host : "processMessages()" 渲染表面
Host-->>App : "getSurfaces()" 返回表面列表
App->>Native : "注册消息处理器/JS 接口"
GW-->>App : "推送 A2UI 地址/快照"
App->>Host : "自动导航到 A2UI 地址"
App->>Native : "屏幕录制/快照请求"
Native-->>App : "返回录制数据/快照"

详细组件分析

iOS 屏幕控制器与 WebView 集成

  • 导航与安全:支持本地文件/HTTP/HTTPS;对本地回环地址进行隔离,避免从远程网关加载
  • 调试状态:通过注入脚本显示网关状态标题/副标题
  • 快照:基于 WKWebView 截图,支持 PNG/JPEG 及质量控制
  • A2UI 就绪检测:轮询判断 openclawA2UI 是否可用
  • 屏幕录制:通过 ScreenRecordService 使用 ReplayKit 写入 MP4,支持音频与帧率限制
classDiagram
class ScreenController {
+string urlString
+string? errorText
+navigate(to)
+reload()
+eval(javaScript)
+snapshotPNGBase64(maxWidth)
+snapshotBase64(maxWidth, format, quality)
+waitForA2UIReady(timeoutMs)
+attachWebView(webView)
+detachWebView(webView)
+isLocalNetworkCanvasURL(url)
+parseA2UIActionBody(body)
}
class ScreenWebView {
+controller : ScreenController
+makeUIView(context)
+updateUIView(view, context)
+dismantleUIView(view, coordinator)
}
class ScreenRecordService {
+record(screenIndex, durationMs, fps, includeAudio, outPath)
-prepareWriter(...)
-handleVideoSample(...)
-handleAudioSample(...)
-finishWriting(...)
}
ScreenController --> ScreenWebView : "持有/更新"
ScreenController --> ScreenRecordService : "调用录制"

Android WebView 集成与 Canvas 控制器

  • Compose 承载 WebView 生命周期管理,销毁时移除 JS 接口与停止加载
  • CanvasController 提供导航、调试状态注入、JS 评估与快照(PNG/JPEG)
  • 参数解析:支持 url、maxWidth、format、quality 等
flowchart TD
Start(["进入 CanvasScreen"]) --> Attach["绑定 WebView<br/>注册 JS 接口"]
Attach --> Navigate["CanvasController.navigate(url)"]
Navigate --> Eval["CanvasController.eval(js)"]
Navigate --> Snapshot["CanvasController.snapshotBase64/format/quality/maxWidth"]
Snapshot --> Dispose["页面销毁/退出时清理"]
Dispose --> End(["结束"])

macOS Canvas 面板与自动导航

  • CanvasManager 管理面板生命周期与锚点定位,支持默认锚点或鼠标位置
  • 自动导航:监听网关推送,解析 A2UI 地址并在面板可见时跳转
  • 调试状态:根据连接模式显示标题/副标题
sequenceDiagram
participant GW as "网关"
participant CM as "CanvasManager"
participant WC as "CanvasWindowController"
GW-->>CM : "推送 canvasHostUrl"
CM->>CM : "resolveA2UIHostUrl()"
CM->>WC : "maybeAutoNavigateToA2UI()"
WC-->>WC : "load(target)"

A2UI 消息处理与动作桥接

  • 命令:canvas.a2ui.push/reset,支持 JSONL 别名
  • 动作上下文:包含会话、表面、组件与上下文 JSON,支持标签值清洗与紧凑 JSON
  • Host:接收消息数组,渲染为多个表面;捕获用户动作并桥接原生
  • 平台差异:iOS 使用 WKScriptMessageHandler,Android 使用 WebView.addJavascriptInterface
classDiagram
class CanvasA2UICommands {
<<enum>>
+push
+pushJSONL
+reset
}
class CanvasA2UIAction {
+extractActionName(dict)
+sanitizeTagValue(str)
+compactJSON(obj)
+formatAgentMessage(ctx)
+jsDispatchA2UIActionStatus(id, ok, error)
}
class BootstrapJS {
+applyMessages(messages)
+reset()
+connectedCallback()
+disconnectedCallback()
}
CanvasA2UICommands --> BootstrapJS : "命令驱动"
CanvasA2UIAction --> BootstrapJS : "动作上下文/格式化"

屏幕录制与远程控制

  • iOS:使用 ReplayKit 捕获视频/AAC,AVAssetWriter 写入 MP4;支持帧率限制与音频开关
  • Android/macOS:通过网关推送或应用内命令触发录制/快照,返回二进制或 Base64 数据
  • 远程控制:macOS CanvasManager 监听网关状态,自动导航到 A2UI 地址,实现跨设备同步
flowchart TD
Req["应用发起录制请求"] --> Parse["解析参数<br/>screenIndex/duration/fps/audio"]
Parse --> Clamp["速率限制/参数校验"]
Clamp --> Start["启动捕获/写入器"]
Start --> Loop["采样循环<br/>视频/音频"]
Loop --> Stop["停止捕获/完成写入"]
Stop --> Done["返回输出路径/数据"]

依赖关系分析

  • 平台耦合度:iOS/macOS 主要依赖 WKWebView/原生窗口;Android 依赖 WebView
  • 共享协议:A2UI 命令与动作在多端一致,确保跨平台一致性
  • 外部依赖:ReplayKit(iOS)、AVFoundation(iOS)、Lit/A2UI 组件(Web)
graph LR
Shared["共享层<br/>A2UI 命令/动作/Host"] --> iOS["iOS<br/>ScreenController/WKWebView"]
Shared --> Android["Android<br/>CanvasController/WebView"]
Shared --> macOS["macOS<br/>CanvasManager/窗口"]
iOS --> ReplayKit["ReplayKit/AVFoundation"]
Android --> WebView["Android WebView"]
macOS --> Window["原生窗口/面板"]

性能考量

  • 快照压缩与尺寸裁剪:iOS/macOS 优先使用原生编码;Android 通过 Bitmap 缩放与压缩
  • 帧率与时长限制:iOS 录制对 FPS 与时长进行钳制,避免资源浪费
  • UI 更新节流:A2UI Host 在收到消息后批量渲染并请求更新,减少重绘次数
  • 调试状态开销:仅在启用调试时注入状态脚本,避免影响生产性能

iOS 节点

iOS 节点应用位于 apps/ios,采用 Swift 6、Xcode 16+、基于 XcodeGen 的工程配置。核心模块包括:

  • 应用入口与场景生命周期:OpenClawApp、NodeAppModel
  • Canvas 渲染与交互:ScreenController、WebView 集成
  • 语音触发:VoiceWakeManager(基于 SFSpeech)
  • 实时活动:LiveActivityManager、OpenClawActivityAttributes、ActivityWidget
  • 网关连接与能力路由:GatewayConnectionController、NodeAppModel 中的能力分发
  • 权限与后台任务:Info.plist 声明、BGTaskScheduler、后台保活与重连策略
graph TB
A["OpenClawApp<br/>应用入口"] --> B["NodeAppModel<br/>状态/会话/能力路由"]
B --> C["ScreenController<br/>Canvas 渲染/快照/脚本执行"]
B --> D["VoiceWakeManager<br/>语音触发"]
B --> E["LiveActivityManager<br/>实时活动"]
B --> F["GatewayConnectionController<br/>网关连接/权限上报"]
E --> G["OpenClawActivityAttributes<br/>活动属性/状态"]
E --> H["OpenClawLiveActivity<br/>小组件视图"]

核心组件

  • 应用模型与会话管理:NodeAppModel 统一维护“节点”和“操作者”两类网关会话,负责健康监测、后台保活、权限协调、命令路由与错误处理。
  • Canvas 渲染与交互:ScreenController 提供导航、默认画布加载、调试状态注入、快照与 JS 评估,支持本地资源与远程 URL。
  • 语音触发:VoiceWakeManager 管理麦克风与语音识别授权、音频引擎、识别回调与命令提取,支持与其他音频子系统(如 Talk)互斥。
  • 实时活动:LiveActivityManager 管理 Activity 启动、状态更新与重复实例清理;小组件通过 OpenClawLiveActivity 展示。
  • 网关连接与权限:GatewayConnectionController 汇总当前权限状态并上报;OpenClawApp 注册后台刷新任务以唤醒恢复。

架构总览

下图展示从用户交互到网关调用、再到 Canvas 与实时活动的端到端流程。

sequenceDiagram
participant U as "用户"
participant A as "NodeAppModel"
participant S as "ScreenController"
participant V as "VoiceWakeManager"
participant L as "LiveActivityManager"
participant G as "GatewayConnectionController"
U->>V : "开启语音触发"
V-->>A : "识别到唤醒词"
A->>G : "发送转写文本到网关"
G-->>A : "确认/错误"
A->>S : "根据指令呈现/隐藏/导航 Canvas"
S-->>A : "JS 评估/快照结果"
A->>L : "更新实时活动状态"
L-->>U : "锁屏/小组件显示状态"

组件详解

设备配对流程

  • 客户端侧:NodeAppModel 在启动或场景激活时,通过网关会话查询待审批请求列表,暂停自动重连以稳定 UI,等待人工批准后恢复。
  • 网关侧:设备配对插件周期轮询待审批请求,向客户端推送提示;批准/拒绝通过网关 RPC 完成。
  • 流程要点:前台优先、避免后台重连风暴、明确的人机审批窗口。
sequenceDiagram
participant N as "NodeAppModel"
participant GW as "Gateway"
participant P as "配对插件服务"
N->>GW : "查询待审批列表"
GW-->>N : "返回 pending 列表"
N->>N : "暂停自动重连,展示审批 UI"
P->>GW : "轮询待审批请求"
GW-->>P : "返回最新状态"
U->>N : "批准/拒绝"
N->>GW : "提交批准/拒绝"
GW-->>N : "更新状态并断开/恢复"

Canvas 表面渲染与交互

  • 导航与默认画布:支持本地 scaffold HTML 与远程 URL;对 loopback 场景进行安全过滤,避免误加载远程内容。
  • JS 评估与快照:提供 PNG/JPEG 快照与 JS 字符串评估接口,用于 A2UI 动作反馈与调试。
  • A2UI 动作:解析用户在 Canvas 内部点击的动作参数,构造消息上下文并通过网关下发。
flowchart TD
Start(["进入 Canvas 命令"]) --> CheckBG{"是否后台?"}
CheckBG --> |是| Deny["返回后台不可用错误"]
CheckBG --> |否| Parse["解析参数/URL"]
Parse --> Load["加载默认画布或指定 URL"]
Load --> Ready{"页面 ready?"}
Ready --> |是| Eval["JS 评估/快照"]
Ready --> |否| Wait["等待 ready 或超时"]
Eval --> Done(["返回结果"])
Wait --> Done

语音触发(Voice Wake)

  • 权限与授权:分别请求麦克风与语音识别授权;在模拟器上禁用以规避音频栈问题。
  • 音频管线:安装输入 tap 将 PCM 缓冲复制到队列,异步注入识别请求;识别回调在主线程处理。
  • 互斥与暂停:当 Talk 模式启用或外部音频捕获需求出现时,暂停语音触发并释放音频会话,结束后恢复。
  • 命令提取:基于唤醒词门控规则匹配识别片段,去抖后派发命令。
flowchart TD
S(["开始"]) --> Perm["检查/请求麦克风/语音识别权限"]
Perm --> |失败| Fail["设置状态为拒绝并停止"]
Perm --> |成功| Init["配置 AVAudioSession/AudioEngine"]
Init --> Tap["安装输入 tap 并启动引擎"]
Tap --> Rec["创建识别请求/任务"]
Rec --> Drain["后台队列持续出队缓冲并注入识别"]
Drain --> Result{"识别回调"}
Result --> |错误| Restart["稍后重启识别"]
Result --> |有结果| Match["匹配唤醒词/去抖"]
Match --> Dispatch["派发命令并恢复识别"]

LiveActivity 实时活动

  • 生命周期:启动时校验权限,若已存在则复用并清理重复实例;根据连接状态更新内容(连接中/空闲/断开)。
  • 内容状态:包含状态文本、是否空闲/断开/连接中及起始时间。
  • 小组件:使用 ActivityConfiguration 动态岛适配不同区域布局。
classDiagram
class LiveActivityManager {
+isActive : Bool
+startActivity(agentName, sessionKey)
+handleConnecting()
+handleReconnect()
+handleDisconnect()
-hydrateCurrentAndPruneDuplicates()
-updateCurrent(state)
-connectingState()
-idleState()
-disconnectedState()
}
class OpenClawActivityAttributes {
+agentName : String
+sessionKey : String
<<ContentState>>
}
LiveActivityManager --> OpenClawActivityAttributes : "创建/更新"

推送通知与后台执行

  • 注册与令牌:应用启动即注册远程通知;APNs 令牌在网关连接建立后上报。
  • 后台刷新:注册 BGAppRefreshTask,按需调度唤醒刷新,记录日志便于排障。
  • 保活与重连:场景切换、后台保活租约、健康监测与断线重连策略协同工作,避免后台死连接。
sequenceDiagram
participant App as "OpenClawApp"
participant BG as "BGTaskScheduler"
participant GW as "Gateway"
App->>BG : "注册后台刷新任务"
BG-->>App : "提交/调度成功/失败"
BG->>App : "回调执行"
App->>GW : "尝试恢复/健康检查"
GW-->>App : "连接状态/错误"

权限模型与后台限制

  • 权限声明:Info.plist 明确相机、麦克风、定位、运动、照片库、Bonjour、本地网络等用途说明。
  • 运行时授权:VoiceWakeManager 与 GatewayConnectionController 分别检查并请求授权。
  • 后台限制:后台执行受限,Canvas、Camera、Screen、Talk 等命令在后台被限制;后台定位需“始终”授权。

离线与设备间同步

  • 离线策略:NodeAppModel 在后台场景采用“保活租约 + 健康监测 + 主动断开/重连”的组合,避免后台死连接。
  • 同步与会话:主会话键与选中代理 ID 保存在网关设置存储中,跨会话保持一致。
  • 会话键:主会话键与代理选择影响分享网关中会话上下文。

数据加密与隐私

  • 加密开关:Info.plist 中关闭“使用非豁免加密”,表明应用不涉及加密出口控制。
  • 隐私声明:各权限用途在 Info.plist 中明确说明,符合 App Store 审核要求。
  • 建议:生产环境建议启用传输层加密(TLS)与最小化数据采集,遵循平台隐私指引。

依赖关系分析

  • 模块耦合:NodeAppModel 作为中枢,依赖 ScreenController、VoiceWakeManager、LiveActivityManager 与 GatewayConnectionController。
  • 外部框架:ActivityKit、WidgetKit、Speech、AVFoundation、WebKit、UserNotifications 等。
  • 目标产物:主应用、分享扩展、活动小组件、WatchApp 扩展等。
graph LR
NodeAppModel --> ScreenController
NodeAppModel --> VoiceWakeManager
NodeAppModel --> LiveActivityManager
NodeAppModel --> GatewayConnectionController
LiveActivityManager --> OpenClawActivityAttributes
LiveActivityManager --> OpenClawLiveActivity

性能与后台行为

  • 后台限制:Canvas、Camera、Screen、Talk 等命令在后台受限;后台定位需“始终”授权。
  • 保活策略:场景切换、保活租约、健康监测与主动断开/重连协同,减少后台死连接。
  • 资源影响:测试路径强调避免持续高热与过度耗电,建议在后台触发场景中谨慎使用高负载能力。

LiveActivity 实时活动

LiveActivity 相关代码主要位于 iOS 应用与 Activity 小组件扩展中,核心由以下模块组成:

  • 活动属性与状态模型:定义活动的属性键与内容状态,确保应用与小组件共享一致的数据契约。
  • 活动管理器:负责启动、更新与清理活动,维护当前活动实例与去重逻辑。
  • 小组件视图:在锁屏与动态岛屿展示活动状态,提供简洁的状态指示与时间显示。
  • 应用层编排:在连接状态变化时驱动活动状态更新,保证 UI 与活动状态一致。
  • 配置清单:声明支持 LiveActivities 能力与后台模式,为活动运行提供系统级许可。
graph TB
subgraph "iOS 应用"
NAM["NodeAppModel<br/>连接状态编排"]
LAM["LiveActivityManager<br/>活动生命周期管理"]
end
subgraph "LiveActivity 扩展"
ATTR["OpenClawActivityAttributes<br/>属性+状态模型"]
WIDGET["OpenClawLiveActivity<br/>锁屏/动态岛屿视图"]
WBUNDLE["OpenClawActivityWidgetBundle<br/>小组件入口"]
end
subgraph "系统与配置"
SYS["iOS 系统 ActivityKit"]
INFO_APP["Info.plist(iOS 应用)<br/>NSSupportsLiveActivities"]
INFO_WDG["Info.plist(Activity 小组件)<br/>NSSupportsLiveActivities"]
end
NAM --> LAM
LAM --> ATTR
WIDGET --> ATTR
WBUNDLE --> WIDGET
LAM --> SYS
WIDGET --> SYS
INFO_APP --> SYS
INFO_WDG --> SYS

核心组件

  • 活动属性与状态模型
    • 定义活动属性键(如代理名、会话键),以及内容状态字段(文本、是否空闲/断开/连接中、开始时间等),用于在应用与小组件之间传递一致的状态。
  • 活动管理器
    • 单例管理当前活动实例,负责启动、更新与清理;具备去重能力,保留最“新”的活动并结束其他重复实例;提供连接中/空闲/断开三种状态的快速切换。
  • 小组件视图
    • 锁屏视图与动态岛屿视图根据状态渲染不同元素(指示圆点颜色、文本、尾部图标/计时器),并在断开/连接中/空闲状态下采用不同的视觉反馈。
  • 应用层编排
    • 在连接建立、断开、重连等关键节点调用活动管理器更新状态,确保活动与应用实际状态保持一致。
  • 配置与权限
    • 应用与小组件 Info.plist 均声明支持 LiveActivities;应用 Info.plist 还声明了后台模式,为活动在后台维持与更新提供基础条件。

架构总览

下图展示了从应用层到系统与小组件的整体流程:应用在连接状态变化时通过活动管理器更新活动内容;小组件基于同一属性模型渲染锁屏与动态岛屿界面;系统负责活动的生命周期与显示。

sequenceDiagram
participant APP as "NodeAppModel"
participant LAM as "LiveActivityManager"
participant AK as "ActivityKit"
participant W as "OpenClawLiveActivity"
APP->>LAM : "请求启动/更新活动"
alt 当前无活动
LAM->>AK : "Activity.request(...)"
AK-->>LAM : "返回活动实例"
else 已有活动
LAM->>AK : "Activity.update(content)"
AK-->>LAM : "更新完成"
end
AK-->>W : "推送最新状态"
W-->>W : "根据状态渲染锁屏/动态岛屿"

组件详解

活动属性与状态模型

  • 属性键
    • 包含代理名与会话键,用于区分不同会话与代理实例,便于在多实例场景下进行状态隔离与追踪。
  • 内容状态
    • 提供状态文本、空闲/断开/连接中的布尔标记与开始时间,小组件据此决定渲染样式与尾部信息(如计时器或断网图标)。
  • 预览与调试
    • 提供预览状态常量,便于在开发阶段快速验证小组件外观与交互。
classDiagram
class OpenClawActivityAttributes {
+String agentName
+String sessionKey
}
class ContentState {
+String statusText
+Bool isIdle
+Bool isDisconnected
+Bool isConnecting
+Date startedAt
}
OpenClawActivityAttributes --> ContentState : "包含"

活动管理器

  • 单例与日志
    • 使用单例模式集中管理活动生命周期,内部记录关键事件日志,便于问题定位。
  • 启动与去重
    • 启动时检查系统授权与现有活动集合,保留最新的活动实例并结束其他重复实例,避免 UI 与系统状态不一致。
  • 状态更新
    • 提供连接中、空闲、断开三态的快速构建与更新方法,统一由活动实例异步更新。
  • 活动可用性检测
    • 通过活动状态判断当前活动是否仍处于活跃状态,若非活跃则清空缓存实例,保证后续操作正确性。
flowchart TD
Start(["进入 startActivity"]) --> Hydrate["去重与提取当前活动"]
Hydrate --> HasAct{"已有活动?"}
HasAct --> |是| UpdateConn["更新为连接中状态"]
HasAct --> |否| CheckAuth["检查系统授权"]
CheckAuth --> AuthOK{"授权已开启?"}
AuthOK --> |否| LogSkip["记录日志并跳过启动"]
AuthOK --> |是| Create["创建活动实例"]
Create --> UpdateConn
UpdateConn --> End(["完成"])

小组件视图

  • 锁屏视图
    • 左侧状态圆点按三态着色,中间为品牌与状态文本,右侧根据状态显示进度、断网图标或计时器。
  • 动态岛屿
    • 在展开区域以“左侧指示点 + 中间文本 + 右侧图标/计时”的布局呈现;紧凑与最小化视图分别适配不同空间。
  • 状态映射
    • 断开/连接中/空闲/其他四种状态对应不同颜色与尾部元素,确保用户在锁屏即可直观感知连接健康度。
flowchart TD
S["接收最新状态"] --> IsConn{"isConnecting?"}
IsConn --> |是| ShowProg["显示进度条"]
IsConn --> |否|
IsDisc{"isDisconnected?"}
IsDisc --> |是| ShowSlash["显示断网图标"]
IsDisc --> |否|
IsIdle{"isIdle?"}
IsIdle --> |是| ShowAntenna["显示天线图标"]
IsIdle --> |否| ShowTimer["显示已连接时长"]

应用层编排与状态同步

  • 连接建立/重连
    • 在连接成功后将活动切换为空闲态,向用户传达“可工作”的状态。
  • 断开/离线
    • 在断开或离线时将活动切换为断开态,提示用户网络异常。
  • 连接中
    • 在尝试连接或重连过程中将活动切换为连接中态,避免用户误以为已连接。
  • 启动时机
    • 若当前无活动,则启动新活动;否则仅更新状态,减少不必要的创建与销毁。
sequenceDiagram
participant NAM as "NodeAppModel"
participant LAM as "LiveActivityManager"
NAM->>LAM : "handleReconnect()"
LAM->>LAM : "updateCurrent(idleState)"
NAM->>LAM : "handleDisconnect()"
LAM->>LAM : "updateCurrent(disconnectedState)"
NAM->>LAM : "handleConnecting()"
LAM->>LAM : "updateCurrent(connectingState)"
NAM->>LAM : "startActivity(agentName, sessionKey)"
LAM->>LAM : "去重并创建活动"

macOS 工作活动存储(对比参考)

  • 作用
    • 记录作业与工具类活动的会话键、角色、类型、标签与时间戳,推导当前工作状态与图标状态,辅助 macOS 上的活动可视化与状态管理。
  • 关键点
    • 通过会话键聚合作业与工具活动,延迟移除工具结果以避免闪烁;根据主会话优先级与最近更新时间选择当前活动。
  • 与 LiveActivity 的关系
    • 虽为 macOS 实现,但其“会话键/角色/类型/时间戳”的建模思路可借鉴至 iOS 活动状态设计,提升跨端一致性。

依赖关系分析

  • 组件耦合
    • NodeAppModel 与 LiveActivityManager 强耦合:前者在连接状态变化时直接驱动后者更新活动状态,确保 UI 与活动状态一致。
    • LiveActivityManager 与 ActivityKit 弱耦合:通过系统 API 创建与更新活动,内部封装错误日志与去重逻辑,降低上层复杂度。
    • 小组件与属性模型弱耦合:小组件仅依赖属性模型提供的状态字段进行渲染,不直接参与业务逻辑。
  • 外部依赖
    • iOS 系统 ActivityKit 提供活动生命周期与显示能力;Info.plist 声明支持 LiveActivities 与后台模式,为活动在后台维持与更新提供系统级许可。
graph LR
NAM["NodeAppModel"] --> LAM["LiveActivityManager"]
LAM --> AK["ActivityKit"]
W["OpenClawLiveActivity"] --> ATTR["OpenClawActivityAttributes"]
AK --> W
INFO_APP["Info.plist(iOS)"] --> AK
INFO_WDG["Info.plist(Widget)"] --> AK

性能与后台策略

  • 后台更新策略
    • 应用 Info.plist 声明了后台模式,结合 LiveActivities 能力,可在后台维持活动显示与状态更新。建议在后台场景下避免频繁刷新,采用节流或按需更新策略。
  • 去重与清理
    • 活动管理器在启动时对重复活动进行清理,保留最新实例,减少系统资源占用与渲染冲突。
  • 渲染优化
    • 小组件视图根据状态选择轻量渲染(如断开态仅显示图标),降低锁屏与动态岛屿的绘制开销。
  • 状态切换成本
    • 连接中/空闲/断开三态切换通过统一工厂方法生成状态对象,避免重复计算与内存抖动。