Electric Agents 移动端演进解析:React Native 客户端从 Cloud 接入到商店上架的完整技术路径
Electric Agents 移动端演进解析React Native 客户端从 Cloud 接入到商店上架的完整技术路径【免费下载链接】electricThe agent platform built on sync.项目地址: https://gitcode.com/GitHub_Trending/el/electric本文以 packages/agents-mobile/CHANGELOG.md 为主线结合 agents-mobile 包的源码、Expo 配置与 CI 配置梳理electric-ax/agents-mobileElectric Agents 的移动客户端从 0.0.1 到 0.6.5 的功能演进与工程实践。读者可以借此掌握移动端如何通过 Electric 的同步协议连接 agents 服务器、如何完成 Cloud OAuth 登录、原生聊天交互如何逐步替代 WebView、以及为 App Store / Google Play 上架做了哪些隐私与兼容性打磨。electric-ax/agents-mobile是 Electric Agents 生态中的 React NativeExpo客户端用于在手机上登录 Electric Cloud、选择 agent 服务器、创建并运行 agent 会话、与正在运行的 agent 聊天。它的演进史几乎浓缩了移动端接入基于同步的 agent 平台的全部关键问题OAuth 深链、认证头注入、形状同步、原生 vs WebView 渲染、应用商店审核合规。下面按功能主线而非单纯版本顺序展开。一、包定位与技术栈1.1 在 monorepo 中的位置与依赖关系agents-mobile 依赖同仓库的两个核心包见 packages/agents-mobile/package.jsonelectric-ax/agents-runtimeworkspace:*提供 slash 命令语法、composer 输入序列化、URL 拼接等共享能力通过/client子路径导出electric-ax/agents-server-uiworkspace:*提供认证 fetchserverFetch、实体 API URL 助手、上传附件等桌面/移动共享逻辑。移动端本身没有本地 runtime不像桌面端可以跑本地 Horton agent因此它本质上是远程 agent 服务器的移动控制台——这正是 onboarding 向导中没有桌面向导的model providers步骤的原因OnboardingScreen.tsx 中注释明确说明。1.2 客户端技术栈从 package.json 可以看到完整的技术选型领域选型框架Expo 54.0.35 expo-router ~6.0.24 React Native 0.81.5 React 19.1.0同步tanstack/db、tanstack/electric-db-collection、tanstack/react-dbElectric shape 同步到本地集合原生能力expo-image-picker图片附件、expo-clipboard复制会话 ID、expo-linking/expo-web-browserOAuth、expo-splash-screen、expo-system-ui监控sentry/react-native存储react-native-async-storage/async-storage登录态、置顶会话等本地状态校验zod形状 schema 解析脚本方面提供了start/android/ios/web四个启动入口以及typecheck、testvitest、doctorexpo-doctor、export:android/export:ios纯 JS bundle 导出和ci:checktypecheck doctor export:android用于 CI。二、从零起步0.0.1 打通 Cloud 接入2.1 三层 Cloud 能力落地0.0.1 一次性引入了三个基础能力见 CHANGELOG 中ca01b9d、8fd9bfa、64d9354、508742f① React Native 应用骨架创建 agents-mobile 包并接入 Expo Router 路由体系。② Electric Cloud 登录通过dashboard.electric-sql.cloud的 loopback OAuth 流程桌面端与 CLI 同款用全屏WebView承载 OAuth 页面并通过onShouldStartLoadWithRequest拦截回调 URL无需任何后端改动。登录后通过auth.whoami拉取用户名与工作区列表并支持一键跳转 Cloud 控制台。③ 端到端连接 Cloud agent 服务器这一步是移动端接入的硬核部分在 agentsClient.ts 中仍有完整实现令牌交换把 dashboard JWT 兑换成服务级 agents tokengetTokenForAgents认证注入在每次出站请求上注入Authorization/x-electric-service/electric-principal三个头经serverFetch shape collection 的fetchClient包括 React Native 长轮询DurableStream并把这些头作为 prop 转发到 Expo DOM-embed 边界让 embed 内部自己的auth-fetch实例也能复用URL 组装改用appendPathToUrl处理带?service…的 Cloud URL正确 spawn通过规范的/_electric/entities/type/name端点把initialMessage放进请求体——这修复了一个 STREAM_NOT_FOUND 竞态此前 PUT 后再 POST /send 时spawn 确认可能先于流就绪返回导致首条消息 404runner 选择器让用户指定某个 pull-wake runner 来承接会话。④ 服务器发现登录 Cloud 后onboarding 的服务器选择步骤以及独立的 server-setup 屏幕会列出用户可见的全部 agent 服务器按 Workspace › Project › Environment › Server 面包屑展示一键填充 URL同时保留手工输入 URL 以支持本地/非 Cloud 服务器。桌面端与移动端订阅同一组四个 admin-API shapesagent-servers、environments、projects、workspaces并在客户端 join。2.2 同步模型五种 shapes 客户端集合AgentsProvider.tsx 定义了移动端同步的全部数据面entitiesCollection会话实体entityTypesCollectionagent 类型及其creation_schema、slash_commandsrunnersCollectionrunner 及其sandbox_profilesusersCollection租户内用户entityEffectivePermissionsCollection当前 principal 的有效权限这些集合统一通过/_electric/electric/v1/shape端点以 Electric shape 方式同步见 agentsClient.ts 中createEntitiesCollection等工厂函数并定义了 zod schema 做运行时校验。0.0.2 还引入了一个细粒度响应式实体时间线查询时间线行由 TanStack DB 用多源查询和 live child collections 维护agent 流式响应可以增量更新而无需整体重建整个聊天时间线。2.3 信号控制0.0.30.0.3 引入移动端 agent 信号控制运行中会话的输入框出现停止控件会话菜单以子菜单形式暴露全部实体信号类型内嵌聊天时间线为原生 composer/drawer 留出 inset并对齐消息宽度、做底部渐隐遮罩。SessionMenu.tsx 中的SIGNAL_OPTION_GROUPS是信号集的完整定义中断/停止组SIGINT中断当前运行并继续、复合信号SIGSTOPSIGINTStop immediately暂停/继续组SIGSTOP当前运行结束后暂停、SIGHUP当前运行结束后重载、SIGCONT恢复暂停、SIGUSR投递给信号处理器终止组destructiveSIGTERM优雅永久停止、SIGKILL立即永久终止。三、OAuth 深链攻坚Android 登录流的多层修复0.0.60.0.6 是移动端最典型的一次真机问题修复Android尤其 13 的 release 构建上 Chrome Custom Tab 的electric-agents://oauth/callback重定向会丢失导致登录永远无法完成、用户被弹回欢迎页。CHANGELOG 记录了五层加固源码中均有对应实现Config plugin 注入onNewIntentwith-android-on-new-intent.js 通过withMainActivity修改生成的MainActivity.kt添加override fun onNewIntent(intent: Intent) { super.onNewIntent(intent) setIntent(intent) }这是因为 Android 13 在用户停留于 Custom Tab 时会杀掉应用进程重定向回来时通过新 intent 重启 Activity默认模板的MainActivity不重写onNewIntent导致getIntent()一直指向最初的 LAUNCHER intent。该插件还有两个工程细节必须从expo/config-plugins导入pnpm 不会把expo/config-plugins提升进本包 node_modules会做重复注入检测避免手工补丁过的工程被二次注入。全局深链监听除/oauth/callback路由外CloudAuthProvider额外挂载全局Linking监听让冷启动重定向在 Expo Router 导航到路由之前就能被消费。parseCallbackUrl改用Linking.parseHermes release 构建中new URL()对自定义 scheme 不可靠searchParams.get()可能对存在的参数静默返回null而expo-linking的解析器专为应用深链设计。signIn的深链等待期当浏览器会话返回dismissAndroid 上成功但被 OS 杀进程的典型表现时不立即回滚到signed-out而是等待DISMISS_GRACE_MS 6000ms见 cloudAuth.tsconst DISMISS_GRACE_MS 6000completeCallbackUrl幂等三路并发处理器浏览器会话结果、全局 Linking 监听、/oauth/callback路由可能同时消费同一 URLcompletingUrl去重保证不会互相踩踏。此外/oauth/callback路由改为在渲染阶段用Redirect导航而非 effect 里的router.replace后者在 dev 构建中会与 Expo Router 自身的 intent 处理竞态并把用户引导到/onboarding而不是/SessionListScreen在AgentsProvider未挂载时访问useAgents会崩溃。配套新增useAgentsRouteGuard对/、/session、/new-session、/diagnostics做路由守卫未完成设置时重定向到/onboarding//server-setup。cloudAuth.ts 中CloudAuth类是一个非 React 的单例状态机维护signed-out / signing-in / signed-in / error状态、通过subscribe/setState通知CloudAuthContextsignIn生成 CSRF statecrypto.randomUUID()回退Math.random校验回调中的 state 防 CSRFgetAgentsToken(serviceId)做 per-service agents token 的内存缓存whoami刷新把 401/403 视为硬登出。出于移动端限制登录流程不使用桌面/CLI 的 loopback HTTPGoogle 的 Use Secure Browsers 政策禁止内嵌 WebView 承载 OAuth而是走系统浏览器 自定义 scheme 深链。四、聊天体验从 WebView 渐进走向原生0.0.14 / 0.0.15 / 0.6.24.1 原生 slash-command composer0.0.140.0.14 让移动端输入框获得与桌面端对齐的 slash 命令能力且跑在原生TextInput上而非 WebView自动补全、结构化composer_input载荷、命令/参数的内联高亮。关键在于共享语法——slash 命令语法和序列化器被移入electric-ax/agents-runtime并经由/client导出成为两个平台唯一的 truth source桌面 composer 改为复用它们且行为不变。slashAutocomplete.ts 展示了纯函数化的实现思路全部逻辑不依赖 WebView 坐标resolveSlashTrigger(value, selection)用TextInput.onSelectionChange上报的光标位置解析当前触发词支持在文本中间触发范围选择则抑制菜单在 RN 尚未回报首个 selection 事件前假定光标在末尾保证敲下/的瞬间菜单就弹出filterSlashCommands前缀过滤上限MAX_SLASH_SUGGESTIONS 8与桌面端一致computeHighlightRanges把已识别命令连同其声明数量的参数词渲染成徽章未知/token保持原样buildSlashCommandInsertion在触发区间内拼入/command并把光标落在参数位。NativeComposer.tsx 中的useSlashAutocompletehook 把这些纯函数接到 RN 状态上让弹层SlashCommandMenu保持原生。4.2 Schema 驱动的创建表单与图片附件0.0.150.0.15 把桌面SchemaForm的能力带到移动端schema 驱动 spawn argsnew-session 屏幕把 agent 类型的creation_schema渲染为原生控件——enum 属性变 picker sheetmodel 枚举按 provider 分组并记住上次选择、boolean 变 switch、string/number 变文本字段、string-array 变逗号分隔字段、其他对象变 JSON 字段必填项门控Start session按钮按钮固定在屏幕底部滚动内容留出 padding。图片附件会话内与新建会话两个 composer 都能经expo-image-picker从照片库/相机附图是否允许取决于会话模型是否接受图片输入spawn 后首条消息立即发送以锁定上传目标。共享发送路径uploadMessageAttachments同时接受浏览器File与 React Native 文件描述符schema 分类助手inlineSchemaProperties、model/reasoning/speed 探测、model-settings 分组抽成桌面/移动共用的lib/schemaProperties模块。会话标题生成加固当首条消息引用模型看不到的图片时Horton 的标题模型可能道歉式跑题把道歉句当成标题。修复是系统提示要求按意图推断标题且绝不道歉外加一个 guard 拒绝句子式输出、回退到本地派生标题。4.3 时间线防闪烁0.6.20.6.2 修复了发送消息、composer 伸缩多行/附件和消息排队时聊天时间线闪烁的问题WebView embed 现在改为命令式接收动态更新底部 inset、内联排队消息、滚动而不是通过 props 触发的 re-render同时修复快速打字时发送后 composer 偶尔不清空的问题。五、协作与权限置顶、分享、fork0.0.13 / 0.0.16 / 0.6.05.1 会话置顶0.0.13长按根会话行或任意搜索结果弹出 context sheet展示实体信息标题、会话 ID、类型/状态、子 agent、runner、sandbox、spawned、last active与 Pin/Unpin会话内 kebab 菜单同样有 Pin/Unpin 项。置顶会话显示在分组上方的 Pinned 区按设备持久化到 AsyncStoragepinnedEntities.ts是 Web 侧边栏置顶的移动镜像。5.2 会话分享0.0.160.0.16 把桌面ShareEntityDialog的能力以移动优先的 UX 落地ShareSessionScreen.tsx模态路由从会话菜单的Share进入链接 pill展示缩写后的会话 Web URL一次点击唤起系统分享面板含 CopyPeople with access列表Owner 行置顶 Google Drive 风格的General access区对工作区级All users授权 搜索优先的Add people区角色View / Chat / Manage权限集与字形同桌面端逐行通过 bottom-sheet picker 提交含破坏性的Remove access没有延迟的 Grant/Update 按钮授权列表来自受 manage 保护的 RESTGET /grants端点——同步的 effective-permissions shape 只覆盖当前 principal无法列出他人的访问权限非 manager 仍可使用链接操作并看到需要 manage 权限的提示。会话 Web 链接由 sessionLinks.ts 的sessionWebUrl()生成直接拼{serverUrl}/__agent_ui/#/entity/{id}而不是服务器根路径——因为根路径的绝对路径 302 重定向会丢掉 Cloud 的/t/service-id/v1租户前缀。会话 ID 可用sessionIdFromEntityUrl()提取并在菜单状态头、长按行中一键复制SessionMenu.tsx。共享的用户展示助手userDisplay()/initials()移入agents-server-ui的lib/userDisplay.ts授权 diff/移除/访问模型分组逻辑则沉淀为纯函数、可单测的entityGrants模块。5.3 能力前奏租户用户与权限0.0.110.0.11 已把 tenant-scoped users 暴露为 Electric shape并加入聊天分享对话框可授予用户 principal 或工作区全部用户 view/chat/manage 权限view/chat 分享包含 fork 访问权fork 出的聊天归创建它的 principal 所有侧边栏可按创建者标识与过滤共享聊天Cloud 请求会把已登录用户注入为 Electric principal。移动端随之同步 users 与 effective-permissions shapes、标记与过滤共享聊天、在权限不足时禁用聊天与信号控件并在 Account 屏显示当前 principal 便于调试。5.4 双通道 Fork0.6.00.6.0 把桌面端 fork 能力补齐到移动端且无服务器 API 变更整棵子树 fork会话 kebabSessionMenu中新增受控的Fork subtree项——仅根实体!entity.parent、会话已停止/杀死或调用者缺fork权限时禁用带 single-flight 防重入、pending spinner 与内联错误成功后导航到新根。底层是 agentsClient.ts 中的原生forkEntityPOST…/fork空 body 整棵子树 HEAD 克隆构建在共享的entityApiUrl助手之上并经由AgentsProvider暴露SESSION_PERMISSIONS增加fork原生图标集新增git-fork字形。逐条消息 Fork from here共享ChatLogViewembed 里的 pointer fork 之前走 WebView 的fetch现在其 mutation 改走原生 RN 网络——createForkEntity增加可选 transport 参数embed 注入编组后的onRequestForkEntity回调使 embed 里唯一的 mutation 与移动端其他 mutation 走同一条原生路径镜像桌面端的 Electron IPC 路由同时保留共享的失败 toast 行为。嵌入按钮加固抽出经过测试的singleFlight原语嵌入 fork 按钮增加 spinner disabled 态并在 embed 内挂载ToastProvider让 fork 失败在 WebView 内可见而不是消失于无监听器的总线。六、上架打磨隐私、权限、图标与崩溃监控0.0.8 / 0.0.11 / 0.6.46.1 Sentry 崩溃上报0.0.110.0.11 接入 Sentry仅上报错误、开发环境禁用source-map 上传通过withSentryExpo 配置插件与getSentryExpoConfigmetro 包装器接通。sentry.ts 展示了初始化参数——enabled: !__DEV__、tracesSampleRate: 0不上报 trace、sendDefaultPii: falseDSN 是随二进制分发的公开标识符可通过EXPO_PUBLIC_SENTRY_DSN在打包时覆盖。app.config.ts 中 Sentry 配置指向 EU 区域https://de.sentry.io/与 DSN host 保持一致。app/_layout.tsx里Sentry.ErrorBoundary把未捕获的渲染错误变成可恢复页面带 Try again而不是白屏崩溃。6.2 iOS 16.4 部署目标0.0.11一个非常典型的隐性兼容问题聊天渲染在 Expo DOM WebView 中其 markdown 栈用到 regex lookbehind而 JavaScriptCore 只在 iOS 16.4 才支持低于该版本整个 DOM bundle 解析失败、聊天空白。修复是把 iOS 最低部署目标提升到 16.4经expo-build-properties配置见 app.config.ts[expo-build-properties, { ios: { deploymentTarget: 16.4 } }],6.3 商店审核合规0.6.4 0.0.80.6.4 为 App Store / Play 审核做了系统性的清扫对应源码都在 app.config.tsiOS 隐私清单为 required-reason APIAsyncStorage Sentry 使用的 UserDefaults、文件时间戳、系统启动时间、磁盘空间声明privacyManifestsNSPrivacyAccessedAPITypes中CA92.1/C617.1/35F9.1/E174.1收集的数据类型仅声明崩溃数据与性能数据App FunctionalityNSPrivacyTracking: false。注释解释Apple 会拒绝ITMS-91053调用 required-reason API 而不声明的上传且不可靠地读取静态链接 pod 自己的清单所以必须在 app 级声明。权限最小化expo-image-picker配置microphonePermission: false丢掉未使用的麦克风权限Android 侧blockedPermissions: [android.permission.RECORD_AUDIO, android.permission.READ_EXTERNAL_STORAGE]。注释特别说明WRITE_EXTERNAL_STORAGE故意保留——image-picker 在 Android 10API 29之前的相机路径硬依赖它屏蔽会破坏 Android 7–9 的拍照。品牌化启动屏深色背景 splash 图标避免冷启动白闪、不透明 iOS 图标、Android 13 monochrome adaptive-icon 层adaptive-icon-monochrome.png系统按壁纸着色、新增expo-system-ui让userInterfaceStyle在 Android 生效。错误边界整个应用包进可恢复错误边界上文 Sentry.ErrorBoundary。OAuth 回调超时逃生舱等待深链的窗口期逻辑对应DISMISS_GRACE_MS防止卡死在 spinner。日志纪律认证诊断只进开发日志devWarn用__DEV__门控生产设备日志logcat / Console.app不出现 token 交换与 HTTP 状态细节。0.0.8 则补上了 Google Play 的硬性要求——Account 屏在登录 Electric Cloud 时显示Delete account按钮点击在系统浏览器打开账户删除说明页。0.0.8 还完成了 onboarding 的移动优先重设计见下节。七、Onboarding 与设置流程的收敛0.0.8 / 0.0.117.1 强制两步向导0.0.8 把 onboarding 重设计为镜像桌面向导的两步流程Cloud 登录 → Server 选择并在保存服务器连接前强制展示移除 Dont show this again 与 Skip for now 逃生阀形成不变量onboardingDismissedtrue ⟹ serverUrl is setapp/_layout.tsx 中的重定向逻辑强制执行该不变量Cloud 服务器行点击即通过新的onConnect(url)回调提交连接修复了此前点击只填充 URL 输入框的 bug自托管 URL 手工输入收进可折叠的 Custom server 区并内联报错ServerSetupScreen基于第 2 步的版式重写让 Settings → Server 与 onboarding 服务器步骤保持对齐Cloud → server 的自动推进在每次登录转换中只发生一次用startStep播种避免热重启恢复会话后按 Back 又被静默推回DiagnosticsScreen增加__DEV__门控的Clear all local data清空 AsyncStorage、登出 Cloud、重载 JS bundle 进入全新 onboarding文案对齐桌面 Settings → General → Reset。0.0.11 还加固了 kebab 菜单的服务器选择器连接失败在 sheet 内展示而不是静默吞掉Cloud 服务器只在切换成功后持久化子菜单关闭动画结束后才复位到根页面。7.2 列表与视觉收敛0.0.8 让移动端会话概览对齐桌面侧边栏隐藏 principal 实体、状态分组采用相同的生命周期排序并为内嵌 stream WebView 启动时的白屏闪烁问题把主题背景应用到所有原生 DOM/WebView 层0.0.81331cf6。八、工程基建EAS、CI 与依赖治理8.1 动态 Expo 配置与 EAS profiles0.0.6 / 0.0.80.0.6c1834f3为 EAS builds 与 CI 做了系统准备动态 Expo config、EAS build profiles、移动端 CI/export 脚本并对齐共享 React/TypeScript 依赖解析让 Expo DOM embed 通过类型检查与expo-doctor。0.0.81c6ebf9进一步把expo升到 54.0.35、expo-router升到 ~6.0.24使Agents Mobile PRCI 不再因 expo-doctor 的 SDK 版本匹配检查失败。eas.json 定义了六档构建 profiledevelopmentdevelopmentClient内部、preview/preview-ios-simulatorAPK / 模拟器、canaryAPK、canary-store与productionstore 分发并带 Android 提交通道配置track: internal/track: production。app.config.ts中的resolveVersionCode()展示了一个跨 CI 流程保证单调递增版本号的技巧优先读ELECTRIC_AGENTS_MOBILE_VERSION_CODE环境变量其次读 CI 预写的.build-info.json最后回退Date.now()/1000整数。8.2 tanstack/db 单实例治理0.0.11tanstack/db实际是单例collection/transaction/live query 依赖instanceof检查与模块级状态但 lockfile 漂移出了多个0.6.x副本破坏 StreamDB 集合。修复分两步先在根pnpm.overrides把0.6.0 0.7.0全部折叠到 0.6.7范围限定不触碰钉在0.0.x/0.5.8的旧示例 starter随后durable-streams/state0.3.x把tanstack/db改为可选 peer 依赖并把 tsdb 耦合工具拆到durable-streams/state/db子路径于是移除 override通过把tanstack/react-db提到^0.1.85、tanstack/electric-db-collection提到^0.3.5、durable-streams/server提到^0.3.7来收敛到单一 0.6.7。8.3 服务器 URL 形态统一0.0.60.0.6d344c32把 Electric Agents 服务器 URL 统一为 opaque 的租户级 base URL根于/t/tenant-id/v1桌面与移动 Cloud 客户端同步迁移观察流 ensure 端点归入/_electric/observations/*/ensure-stream预 alpha 的 entity/cron/schema/tag/docs API 更名新增非交互式electric agents viewtranscript 命令Horton 标题提取适配轻量桌面 inbox collection facades。九、如何体验与验证在 monorepo 根目录安装依赖后进入 packages/agents-mobile 即可启动pnpm install cd packages/agents-mobile pnpm start # 启动 Expo dev server pnpm android # 在 Android 模拟器/设备运行 pnpm ios # 在 iOS 模拟器/设备运行 pnpm web # 浏览器预览web 入口质量与 CI 相关命令见 package.jsonpnpm typecheck # tsc --noEmit pnpm test # vitestagentsClient / slashAutocomplete / entityGrants / pinnedEntities 等均有单测 pnpm doctor # expo-doctor 检查 pnpm export:android # 导出 Android bundleCI 用 pnpm ci:check # typecheck doctor export:android移动端没有本地 runtime运行前需要准备一个可达的 agent 服务器本地自托管或 Electric Cloud首次启动必须完成 Cloud 登录与服务器选择两步向导。结语从 CHANGELOG 的演进脉络可以看出agents-mobile的成熟并非单一功能堆叠而是同步协议接入 → 原生交互替代 → 协作能力补全 → 上架合规收敛四个阶段的螺旋上升底层始终依赖 Electric shape 同步与共享的agents-runtime/agents-server-ui能力移动端特有的深链、WebView embed、权限与隐私问题则在每个版本中被逐个击破。对于想要在 React Native 中构建同步驱动的 agent 客户端的开发者这个包连同其测试如 agentsClient.test.ts、slashAutocomplete.test.ts、entityGrants.test.ts是一份可对照的完整参考实现。【免费下载链接】electricThe agent platform built on sync.项目地址: https://gitcode.com/GitHub_Trending/el/electric创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考