Skip to content

Latest commit

 

History

History
335 lines (273 loc) · 23.5 KB

code-walkthrough.zh-CN.md

File metadata and controls

335 lines (273 loc) · 23.5 KB

桌面、Home、ROM 与系统应用源码导读

English | 简体中文

沿着可执行程序入口,追踪应用托管、数据访问和 Agent 交互。以下路径相对于仓库根目录。 Agent 架构导读继续介绍内核、peer、工具路由和 Tokio 任务。

启动配方:未验证。 先按根目录 README 准备依赖;已有框架克隆时,通过其 sources hub 配置复用。

1. 找到运行中的组件

组件 入口和职责
桌面 desktop/src/main.rs 启动 octosense,为 octosense-shell 提供薄入口。
Home phone/src/main.rs 启动 octosense-home,在共用 Shell 上增加设置和平台集成。
原生应用 实现 Makepad AppModule 的 Rust 模块,或通过窗口管理协议托管的可执行程序。
隔离脚本应用 App Hub 的 Card runner 校验 manifest.json,在受限 Makepad Script/Splash VM 中运行 main.splash。
宿主服务 Rust 按明确的应用身份执行操作并返回数据。
应用 Agent 按应用/账户限定的 octos peer,拥有会话、工作目录及获授予的工具。
系统 Agent Shell 的助手会话,拥有发现、委派和选定系统操作的工具。
ROM 特权 agent Android 的 Java/Binder 平台服务 AgentPlatformService。

隔离应用的 main.splash 使用 Makepad Script/Splash。L0 .card 文件经过 Octoscript 独立的解析/检查器,再通过 lowering 转为 Makepad 可渲染内容。产品中同时使用这两条路径; 阅读或修改某个文件时,沿着它实际的加载路径追踪。

2. 运行 Reference,追踪原生托管

准备依赖后,在仓库根目录运行:

# 独立的 Reference 窗口。
cargo run --locked -p octosense-reference

# 把 Reference 链接进桌面,并在启动后打开。
MAKEPAD_WM_TEST_APP=reference cargo run --locked -p octosense --features app-reference -- --module reference

# 标准桌面:App Hub、Rinx、Terminal、原生 Makepad 应用和内核集成。
cargo run --locked --release -p octosense

隐藏窗口检查可添加 MAKEPAD_HIDE_WINDOWS=1 MAKEPAD_REMOTE=8000, 使用 /help 描述的 Makepad 控制接口,并通过 /quit 退出。 --module 选择托管方式;MAKEPAD_WM_TEST_APP 请求启动应用。

按以下顺序阅读:

  1. apps/reference/src/lib.rs: ReferenceView 保存 count;handle_event 接收按钮和文本动作、修改标签; draw_walk 将绘图交给内部 View。
  2. 同文件的 ReferenceModule::register 注册控件类型;create 返回 InstanceParts,包含根控件、服务执行器和退出回调。ReferenceExecutor 不暴露工具。
  3. native-apps.json 声明 Reference 的源码、feature、 托管、存储和空 Agent 授权。生成器据此生成原生应用注册表和 Cargo feature 区块。
  4. desktop/src/main.rs 导入 Shell 的 App 并调用 octosense_main!。在 crates/shell/src/lib.rs 中,该宏调用 Makepad 的 app_main! 并设置包目录。
  5. App::launch_app_with_args 查找注册的应用,按需检查脚本应用身份和准入, 准备存储,必要时聚焦已有窗口,然后选择模块或进程托管。
  6. ModuleHost::create 创建实例范围、 存储命名空间、回复句柄、视口和 VM,再创建模块。宿主策略决定它可以获得哪些已声明的 Agent 服务。

进程托管继续看 clients.rs 和 crates/process-apps。Shell 启动子进程、连接窗口管理协议, 转发输入并显示子进程画面。Terminal 在 macOS/Windows,以及启用 Vulkan 的 Linux Wayland 会话中通常走这条路径;模块回退和覆盖配置见桌面 README。

原生应用还可以通过 peer_link 访问自己的 Agent。进程应用在已有的、经过认证的 hub socket 上发送 octos_peer 消息。 Shell 将帧归属到它启动的应用,检查 agent.octos 授权和用户同意,再把请求接到模块也使用的 app-peer broker。模块的 OctosPeer 通道则通过 module_connected 和 on_module_frame 接入同一路径。工具结果和取消沿该链路返回;进程死亡时,关闭它的上下文, 让等待中的调用失败,同时保留持久 peer。Terminal 是唯一带 Agent 的进程应用:它的 native-apps.json 条目为 Agent 授予了全部四个 octos.* 服务;它自己的 Agent 只能调用两个只读工具 terminal.read_screen 和 terminal.read_scrollback(own_tools)。native-apps.json 中的另一个进程应用 Task 没有 Agent。

3. 运行并追踪脚本 bundle

桌面会打包选定的系统 bundle;可以从启动器打开 News 或 Calendar。 单独预览时,先按 App Hub 仓库的说明准备依赖,然后在该检出目录运行 card-host:

# 将路径替换为你的 OctoSense 检出目录。
cargo run --locked --release -p octosense-card-host --bin card-host -- --bundle /path/to/OctoSense/apps/news/bundle --system

--system 准入内置 os.* bundle。Shell 的宿主服务和 Agent UI 需要完整 Shell。 例如,在 OctoSense 根目录运行 Mail 的演示服务:

MAKEPAD_APP_CONFIG='{"mail_demo":true}' cargo run --locked --release -p octosense

演示账户密码为 demo,发送留在演示环境。新商店应用在 OctoSense App Flow(原 Design Flow)中开发,使用 App Hub 的准入/发布流程。本地签名目录配方用于在发布前测试应用能否安装到 Shell。

desktop/system-apps.json 和 phone/system-apps.json 选择 apps/ 中的 bundle。 阅读 apps.rs 中的启动器条目、agent_apps 和 register_host_services。App Hub 的原生 CARD_MODULE 托管这些解释执行程序; 可选的 AppCard 助手使用自己的模块。

在桌面上,脚本应用的窗口按清单 window 请求的尺寸打开。window 是 App Hub 的可选提示, 需要 "schema_minor": 1。lib.rs 中的 App::launch_module_as 用 apps::window_hint 从 Card 运行器将要打开的应用包中读取它, desktop_layout.rs 中的 preferred_size 再把它限制在桌面减去每个新窗口都保留的边距之内(左右各 42 点,上下各 32 点),且不小于 80 x 60。程序坞浮在桌面之上、不预留区域时(macOS 和 OctoSense 风格),底部边距改为从程序坞 上沿算起(desktop::dock_overlay),所以窗口的下边缘不会位于程序坞后面。没有 window 时,窗口宽度取桌面的 72%、高度取 76%,最大 1000 x 720 点。 两种情况下用户都可以调整窗口大小;手机忽略这个提示。PDF Tools 请求它设计图的 1536 x 1024。

原生模块打开额外窗口(makepad 的 ModuleWindows::open)时,也可以为它指定尺寸。 App::drain_module_windows 通过 desktop_layout::requested_size 以同样的方式限制这个尺寸; 尺寸为零、为负数或不是有限值时视为没有指定,窗口按默认尺寸打开。OctoSense 自带的原生应用 目前都没有指定尺寸:Rinx 的 Moments 和 Article editor 窗口不传尺寸,其他原生应用不打开额外窗口。

通过标题栏按钮、双击标题或最大化快捷键最大化的窗口会填满工作区;吸附的窗口填满工作区中 属于它的那一部分。两者都止于浮在桌面之上的程序坞的上沿:DesktopWindows::rects 填满程序坞 上方的 desktop_layout::room,程序坞的高度由 shell 在每次绘制之前从 App::dock_overlay 取得并记录 (DesktopWindows::dock),新窗口的尺寸也用同一个查询。Shell 全屏、程序坞因此隐藏时 (desktop::dock_auto_hides),这个高度为 0,最大化的窗口会填满整个工作区。 还原时窗口回到它自己的位置和尺寸。程序坞预留区域(Windows)或没有程序坞时,最大化的窗口 像以前一样填满整个工作区。

按 ⌘F 或在 macOS 系列样式中点绿色按钮,窗口进入全屏,铺满整个桌面。DesktopWindow::fullscreen 与 maximized 相互独立,WmLayout::rects_of 给窗口越过外边距的矩形,这块矩形由 WmDesk::draw_walk 每帧按桌面自身的矩形设置。WmDesk::draw_tile 只绘制窗口内容。顶栏和浮动程序坞 隐藏(desktop::front_fullscreen),直到指针停在屏幕顶边或底边(edge_reveal.rs、 App::top_edge、App::place_bar)。退出全屏时,窗口的矩形、缩放状态和它在窗口堆叠中的位置都 原样恢复,因为它们从未改变。

脚本应用在用户点按或点击的处理函数中调用 host.request("window.fullscreen", {on: true}, fn(r){…}),让自己的窗口进入演示全屏 (app_fullscreen.rs)。Shell 的 window 宿主服务把请求排入队列。事件处理完后,App::serve_window_requests 查找由该应用的 Card 运行器运行发出请求的那个 isolate 的窗口(App::window_of),所以应用只能改动自己的窗口。 用户在最近五秒内没有在这个窗口里按过应用时,请求被拒绝(Activation):makepad 无法分辨 host.request 来自哪个处理函数,所以 Shell 像浏览器一样保留一个短暂的激活状态。只有 MpRunViewAction::Pressed 才算数:MpModuleView 在按下落在模块的活动根部件上、且上方没有 宿主服务面板时发出它;分组标签、已停止应用的关闭画面和进程窗口只发出用于切换焦点的 Clicked。 演示全屏以应用自己的 {on: false} 以外的方式结束时,窗口的按下记录随之作废 (Presenter::finish),所以应用不能在钩子里立刻弹回全屏。来自 Agent 工具调用的 {on: true} 也会被拒绝:App Hub 的 Card 运行器在应用的工具调用进行期间收回请求的界面权限 (ServiceCall::may_prompt),而且从不因为某个能力而收回。随后 WmLayout::set_fullscreen 让这个窗口进入全屏,WmState::presentation 让任何屏幕边缘都 不再唤出顶栏、标题栏或程序坞(desktop::dock_shows、App::desktop_pointer),就像幻灯片的 放映模式。Shell 自己的窗口如果还不在宿主全屏中,就进入全屏(HostFullscreen,使用栏上窗口 控制按钮的同一个调用:macOS 上是原生全屏,其他平台是 makepad 的最大化;makepad 的 Window::fullscreen 在 macOS 上目前不起作用),OSD 显示 "Press Esc to exit full screen" 三秒。以下任一情况结束演示全屏:Esc、{on: false}、⌘F、窗口关闭或失去焦点、应用模块出错、 切换风格、用户离开宿主全屏。App::sync_presentation 在每个事件之后检查;只有这次演示全屏让 宿主窗口进入了全屏时,宿主窗口才退出全屏,即使它在演示结束之后 20 秒内才进入全屏 (PENDING_LEAVE_SECONDS)。回答是 {applied, fullscreen, fills_screen},应用可选的 on_app_fullscreen(on) 钩子在开始和结束时都会收到通知。在手机上,这个请求不改变任何东西 (fills_screen: true)。隐藏窗口运行的 Shell(MAKEPAD_HIDE_WINDOWS)从不让宿主窗口进入 全屏,所以在真实显示器上进入 macOS 全屏尚未验证;Windows 和 Linux 上的最大化同样未验证。

准入验证应用包,解析元数据与资源上限。能力名称披露用途,不是执行许可。脚本的 host.request(...) 按应用身份执行。 App Hub 的 crates/appstore/src/services.rs 定义 HostService、ServiceCall 和回复机制; 调用携带应用身份和宿主目录,供服务检查。只有明确声明的 Agent 工具,才会把相应操作提供给模型。

4. 将工具追到应用数据和 Glance

从 Calendar 的 tools.json 和 CalendarService 开始。 JSON 声明 schema 和策略;Rust 的 handle 分派方法,load/save 管理 <host_dir>/calendar/events.json。

sequenceDiagram
    participant H as 人或系统 Agent
    participant A as Calendar 应用 Agent
    participant R as Shell 工具中继
    participant C as Calendar Rust 服务
    participant D as events.json
    H->>A: 请求 Calendar 添加日程
    A->>R: calendar.add_event 和结构化参数
    R->>R: 检查调用者、授权、schema 和策略
    R->>C: 以 os.calendar 身份执行
    C->>D: 读取、校验并保存
    C-->>R: 对象结果(id/start)
    R-->>A: 工具结果
    A-->>H: 回答原会话
Loading

script_apps.rs 加载工具并实现 HostServiceExecutor;relay.rs 负责授权。 calendar.remove_event 需要破坏性操作审批。Shell 的 审批路由 应用用户设定的常设规则,或显示确认面板; 已注册的 confirm: app 工具使用所属应用的面板。开发者模式提供另外一种由用户开启的覆盖。 路由在仅所有者可访问的审计日志中记录决定。这些路径决定工具是否运行;模型只提供调用和参数。

内置声明提供以下操作:

拥有 Agent 的应用 工具与实现
News news.list、news.read、news.notify;News 宿主服务处理调用,并将通知交给 Shell。
Mail accounts/folders/sync/list/peek 绑定 broker 账户;peek 不标记已读。notify/publish_card 经 Shell 发布,skip_event 记录无需操作的决策。发信和凭据仍不对 Agent 开放。
Calendar calendar.events、add_event、update_event、remove_event、notify、agenda;Rust 服务管理日程及事件/议程卡片模板。
Photos、Maps、YouTube、Camera 只有 <namespace>.notify;Shell 的 NoticeService 处理各应用的命名空间。Camera 由 Home 打包。

AI providers 配置宿主,目前不声明应用 Agent。Calendar 的月历、按日列表和编辑器 披露 calendar 用途,服务检查已准入应用身份与数据范围,并与上述 Agent 工具共享本地日程存储。Glance 中已保存日程 的卡片可打开 Calendar 中同一条记录;编辑日程也会刷新它的卡片。

共用通知的实现见 glance_notice.rs 和 resources/glance/notice.card。 Mail 和 News 保留自己的服务并安装通知回调;serve_system_apps 只为尚无服务的命名空间 注册 NoticeService。publish_args 填入应用名称/图标、时间、标题和正文, 再由 glance::publish_for 检查发布者当前准入状态与账户身份。通知卡片可以打开所属应用, 并设置 notify: true。Calendar 继续使用自己的事件和议程模板。

更通用的 glance.publish API 接受 L0 source 和可选 data,或自带值的 Splash script,两种卡片都在发布应用的策略下渲染。 notify: true 排入一条 toast 通知;用户关闭卡片时,宿主的 glance::dismiss 删除它。 上述固定通知工具接受文本参数,script-card API 则用于更丰富的应用界面。 用户在这些界面上的操作使用应用自己的 API 权限。

在 main 上,Agent 的工具调用若最终映射到 glance.publish,只能指定应用已准入应用包中的模板 并提供 initial 对象,或提交合法的 L0 源码,不能提交 script(见 script_apps.rs 中的 check_agent_publication)。 desktop-v0.1.0-beta.2 仍接受 Agent 发布的 script 卡片。

实现旁的测试直接展示这些约定:script_apps.rs 中的 a_host_service_tool_runs_as_the_apps_own_request、glance_notice.rs 中的 system_apps_without_a_service_of_their_own_get_the_notice_service,以及 glance.rs 中的 a_script_card_is_admitted_as_it_is。它们分别检查调用归属、命名空间回退和脚本卡片准入路径。

增加工具时,保持数据边界清晰:

边界 访问路径
脚本存储 在有配额的应用私有隔离目录中使用运行时 storage API,即使遗漏 storage 声明也一样。
Agent 工作目录 通过获授权的文件工具和 Shell 策略访问 peer 的应用/账户目录。
宿主服务数据库 经明确的 Rust 方法/工具访问 Calendar 日程、Mail 缓存和 News 数据。凭据保留在宿主确认面板和保险库中。

跨应用调用由请求方在 agent.tools 中列出带点号的工具名,所有者必须声明可共享, relay 也必须授权。准入还检查 HostLimits.offered_tools:默认提供的工具中没有 mail.send 等名称。先补齐声明、准入 offer 和执行器路径,再将其写成可用的集成方案。 Agent 导读继续解释委派和向其他 Agent 请求帮助。

5. 追踪人的对话

桌面上聚焦拥有 Agent 的应用,从栏按钮、Shift+F8 或菜单打开 Ask <app>。 F8 打开系统 Agent。agents.list 报告应用 Agent;agents.ask 等待首次同意和 peer 准备完成,再返回 peer slug。系统 Agent 用 peer_send_input 发送任务,用 peer_gather 获取答案。声明了 sys.chat 的卡片也可以访问其应用 Agent。工作区中的发布者如果声明了 agent 却没有内嵌会话,宿主会提供 Card / Chat 页签及绑定账户的会话;发布数据和当前 L0 状态只是上下文,不授予工具权限。Enable assistant 打开首次使用许可。这也适用于手机上的内置通知和 Calendar 卡片。显式 Mail 演示会话仍返回预设文本。手机触控导航尚无打开独立 Ask-app 面板的控件。

继续阅读 app_chat/、 system_chat/、 agents.rs、 contained.rs 和 app-peers。人的工作和系统 Agent 的工作使用 peer 上 不同的 session/lane;面板的 Stop 中断人的回合。Makepad 处理 UI 事件,broker/内核通道 传递请求和通知;配套导读将这些通道映射到 Rust 任务。

桌面需配置兼容的 OCTOS_APP_CORE_BIN,并在 AI providers 中设置提供方; octos-core 在构建中启用集成。详见内核指南。 Android 打包 liboctos.so 并作为子进程运行,OpenHarmony 在进程内运行内核。 应用 UI 托管与内核托管分别选择。

6. 追踪 Home 的设置和平台桥接

在 phone/ 中运行 Cargo,让其 .cargo/config.toml 选择手机版 bundle:

cargo run --locked --release -p octosense-home --features mobile-only
# 同时链接 Reference 和 Sheets。
cargo run --locked --release -p octosense-home --features mobile-only,mobile-apps

phone/src/main.rs 定义含有 #[deref] shell: ShellApp 和 SettingsRuntime 的 App。install_ext 注册受信任的设置模块。handle_event 先处理设置启动/计时及入口 intent,把事件传给 Shell,最后消费排队的平台包和设置请求。 如果设置更新丢失,先检查这个顺序。

settings_app.rs、 settings_script.rs 和 settings_script_host_facade.rs 将脚本控制器/UI 接到 Rust 宿主校验。权限来自编译进来的受信任单例。 android_settings.rs 根据 channel 分派观测与结果。 命令被接受的结果和随后观测到的平台状态使用不同处理器;后者确认设备当前实际状态。

Android 路径继续进入 MakepadAppExtension.java、 其客户端,以及 phone/android/contracts/。 桥接的 SystemBridgeService.java 使用 Binder 回调和调用者检查。独立 Home 在 Android 授予的权限/角色内工作, ROM 提供额外的特权组件。Home 构建说明 介绍 APK 打包及单独的设备验证步骤。

7. 追踪 ROM 打包和特权服务

阅读 octosense.mk 和 Android.bp 中的产品配置,再看 build-home.py、 stage-home.py 和 stage-forks.sh。构建产生 Home/Bridge APK 对和回执, staging 校验并复制产物,LineageOS 构建产生镜像。安装、刷机和 OTA 分别有自己的脚本和验证步骤。

AgentPlatformService.java 在 caller 中检查 Binder UID、允许的包身份和平台签名。 IAgentPlatform.aidl 定义的方法调用平台后端并返回能力/结果。Android 管理其服务生命周期和 Binder 执行; LLM 系统对话则归前文介绍的 octos 内核所有。

Home 的 AgentPlatformClient 连接这一可选 ROM 服务。System Bridge、Quickstep、 SystemUI 和 Settings broker 各自有 Android 角色及权限边界。 按照 ROM 的验证说明, 在分配给本任务的设备上检查启动、平台操作和更新行为。

8. 追踪可选的 AppCard 产品

apps/appcard/module/src/lib.rs 将 AppCard 适配为 AppModule;apps/appcard/app/app 实现 router/composer 和生成卡片,旁边还有 transport/store/render crates。 使用 --features app-appcard 启用;默认构建和 mobile-apps 不包含它。

共用 Shell 独立实现系统聊天、应用聊天和隔离应用 peer。AppCard 的旧 personal-data 集成读取较早的原生 Mail 格式,而当前 Mail 在宿主服务中管理缓存。 学习原生托管先看 Reference,学习应用工具先看 Calendar/News; 修改 AppCard 的路由和卡片生成产品时,再读它自己的文档。

应用更新与重启

crates/updater-ui 是不提供代理工具的原生模块。在 macOS 上,其工作线程校验并 暂存发布包,再把等待中的辅助程序交给 crates/shell/src/update_restart.rs。 shell 请求进程内模块和独立应用退出;取消或未回答的关闭请求不会为更新而强制 结束应用。只有完成关闭确认才提交辅助程序。旧进程退出后,辅助程序先按已安装 应用自身的签名身份检查新应用的代码签名(crates/updater/src/macos/codesign.rs, 使用 Apple 的 Security 框架),再原子交换应用包,并以相同配置启动。 crates/updater/src/macos 确认新版本启动,或恢复保留的旧包;提交之后任何一步 失败都会重新打开旧应用。更新器指南 说明旧发布的 兼容性检查和手动恢复途径。