跳到内容
CADCAMFUN
文档

文档

移动伴侣

用于配对桌面的 iOS 客户端,具有证书固定、项目审查和共享助理会话。

从英文自动翻译。 查看英文原文

平台和能力

apps/mobile 是一个使用 React Native 57.0.86、React 3.19、Expo Router、NativeWind、Reanimated 和 Gesture Handler 的 Expo SDK 2 应用程序。其支持的固定网络传输由本地在 Swift 中实现 companion-net 模块。 iOS 包标识符是 com.cadcamfun.mobile.

手机读取项目、查看历史记录和拉取请求、调度工作流程、接收活动,并与配对桌面上运行的助手会话进行交互。桌面继续拥有数据库、几何计算、AI 提供商凭证和工作流程执行。这些操作需要一个可访问、正在运行的桌面。

使用原生 iOS 构建

Expo Go 不包括 companion-net 并且无法连接到配对的桌面。当固定模块不可用时,本机请求会失败。 Web 开发后备使用浏览器获取和本地存储;它不提供本机证书固定。

应用结构

Expo Router 将连接屏幕与经过身份验证的应用程序选项卡分开。 app/connect.tsx 处理配对输入, app/pair.tsx 接受深层链接,并且 app/(app) 包含项目、助手、活动和设置。项目屏幕包括概述/历史记录/拉取/工作流程面板、提交详细信息和拉取请求审查。

  • components/:可重用控件、项目面板、助理记录和连接 UI。
  • lib/client.ts:通过注入的传输类型键入客户端; API 类型无需服务器运行时即可导入。
  • lib/pairing.ts, native.ts, 和 storage.ts:配对验证、传输选择和持久凭据。
  • lib/sse.ts, live-events.ts, 和 chat-log.ts:流解析、实时订阅和转录状态。
  • modules/companion-net/:Expo 模块和 Swift 固定 HTTPS 实现。

传输和网关范围

伴随网关与 sidecar 的环回监听器是分开的。默认情况下它是关闭的。 Enabling it starts an HTTPS server on 0.0.0.0,使用端口 8443 除非选择了另一个有效端口。该服务保持其启用状态,并在 sidecar 启动后恢复。桌面防火墙和网络路由必须允许电话到达此侦听器。

电话发送可撤销的设备令牌。网关存储其 SHA-256 哈希,根据允许列表检查请求方法/路径,并使用桌面令牌将接受的请求转发到环回 API。手机未收到该启动令牌。无效的身份验证返回 401,拒绝的路由返回 403,重复失败的身份验证可能返回 429,不可用的上游返回 502。

Allowed families include project reads; version-control, pull-request, and workflow endpoints; project events; notifications; assistant status/models/agents; streaming chat; queue/abort; permissions; and session state/control. Files, credentials, plugins, billing, gateway administration, and GitHub remote/push/pull endpoints are excluded.

设备访问包括写操作

该网关当前允许版本控制提交、分支、签出和恢复请求以及拉取请求审查和合并。设备令牌的范围仅限于网关路由策略;它们不是只读角色或每个项目的限制。在桌面配套设置中撤销丢失或不受信任的手机。

网关限制失败的身份验证尝试:在 60 秒内从一个 IP 地址进行 10 次失败的承载尝试后,来自该地址的进一步请求将接收 HTTP 429,直到窗口到期(apps/sidecar/src/services/companion-gateway.ts).

助理会话和离线消息

同伴将聊天请求发送至 POST /api/ai/chat/stream 并解析服务器发送的事件。会话标识符允许桌面对话的继续。客户端可以引导或排队消息、中止转弯、回答问题以及回复权限请求。在桌面上配置模型提供者;使用前手机会查询AI状态。

如果流在没有终端的情况下结束 done 或 error 帧并且未被取消,客户端报告连接不完整。本机传输在取消和完成时关闭其流句柄。本机解码保留字节块之间不完整的 UTF-8 标量。

无法发送的助手消息可以进入持久队列。它最多保留 50 个条目,满时丢弃最旧的条目,并重试前台和连接恢复。失败的条目仍排队。该队列存储消息以供稍后执行;它不是项目几何图形的离线副本或离线 AI 运行时。

构建并运行

从存储库根目录
bun install
bun run --cwd apps/mobile test
bun run --cwd apps/mobile typecheck
bun run --cwd apps/mobile build
bun run --cwd apps/mobile ios
bun run --cwd apps/mobile ios:release --device

构建命令将 iOS JavaScript 包导出到 apps/mobile/dist;它不生产 IPA。 iOS 运行命令需要 macOS、Xcode 和 CocoaPods。设备安装还需要受信任的设备、开发人员模式以及为生成的 Xcode 项目配置的 Apple 签名团队。 native:generate 运行适用于 iOS 的 Expo prebuild,无需安装 pod。对于日常工作来说, start (别名 dev) 运行 Expo 开发客户端捆绑器, ios:dev 在 localhost 上启动它并打开 iOS 模拟器, ios:build 编译调试应用程序而不启动捆绑程序,并且 web 启动 Expo Web 目标。这 test 脚本运行 bun test test 里面 apps/mobile.

.github/workflows/ci.yml 定义伴随类型检查、逻辑测试和 JavaScript 导出。 .github/workflows/ios.yml 生成 Xcode 项目,构建未签名的发布模拟器应用程序,将其与实时 sidecar 配对,检查经过身份验证的项目列表,并捕获屏幕截图。这些配置的检查不会建立物理设备、签名、TestFlight 或真实提供商的接受。

存储和设备安全

在本机 iOS 上,配对凭据存储在 Expo SecureStore 中。配对条目使用 WHEN_UNLOCKED_THIS_DEVICE_ONLY。脱机队列条目和会话可见标记也使用 SecureStore。浏览器后备使用本地存储进行开发。

该应用程序查询生物识别功能并使用 expo-local-authentication 用于本地身份验证,并启用系统密码回退。生物识别 UI 不会更改网关的服务器端路由权限。设备撤销由配对桌面控制。

桌面将其网关证书和私钥保存在其应用程序数据伴随目录中;密钥是使用仅限所有者的权限写入的。替换该证书会更改固定指纹并需要新的可信配对。

项目审核工作流程

  1. 打开项目并选择一个桌面项目。
  2. 检查其工作状态、分支、提交、拉取请求或工作流程运行。
  3. 打开提交以查看其差异,或打开拉取请求以查看更改并提交结论。
  4. 当适合项目时,使用可用的合并/关闭或工作流调度操作。
  5. 使用选定的项目上下文打开 Assistant 以继续或启动 AI 会话。

所有更改都在桌面上执行。网络中断、授权撤销或桌面停止阻止新的服务器工作;稍后重试本地排队的助理消息。

客户端界面

CompanionClient 接受一个 Transport with JSON request and text-stream methods. It exposes project reads; VCS status, history, diff, commit, branch, checkout, and restore; pull requests and reviews; workflow runs and dispatch; AI status/models/agents; session control; notifications; and project event streams.

JSON 失败变为 CompanionError 带有状态和消息。流式聊天和实时事件使用共享 SSE 解析器,因此任意网络块边界不会成为应用程序事件边界。测试注入传输,无需导入本机模块或运行模拟器。

配对手机

  1. 在桌面上,打开“设置”及其移动配套面板,或者 /settings/companion,然后打开网关。
  2. 创建命名设备配对并显示二维码。该链接包含 HTTPS 候选主机、设备令牌和证书指纹。
  3. 在 iOS 上,扫描二维码或粘贴 cadcamfun://pair 关联。出现提示时允许本地网络访问。
  4. 该应用程序验证链接,与广告的候选主机进行竞争,并保留第一个成功的健康连接。
  5. 确认设备出现在桌面设置中。使用桌面的撤销操作来删除其访问权限。

候选主机包括合适的私有 IPv4 和覆盖网络地址;网关最多通告六个已发现的地址。当前侦听器是 IPv4,因此不会公布发现的 IPv6 地址。 VPN 或覆盖网络必须已提供供远离本地网络使用的路由;配对不会创建中继或 VPN。

证书固定

Swift 实现使用短暂的 URLSession 具有信任委托的实例。仅当其 DER 字节的 SHA-256 哈希等于配对的指纹时,它才接受叶证书。请求需要 HTTPS,重定向被拒绝,并且本机调用者永远不会默默地降级到浏览器传输。

因此,证书信任来自配对链接。将 QR 码和粘贴的链接视为凭据。应通过桌面配对流程替换具有意外来源或指纹的链接,而不是绕过 TLS 验证。

诊断连接

  • 无本地运输: 安装包含本地模块的 iOS 开发或发布版本。
  • 无法访问主机: 检查本地网络权限、桌面防火墙、通告地址、网关端口和网络路由。
  • 401: 设备令牌无效或已撤销;创建一个新鲜的配对。
  • 403: 端点位于同伴允许列表之外。
  • 证书不匹配: 与当前桌面证书再次配对。
  • 助理不可用: 检查桌面提供程序配置以及 sidecar 是否正在运行。

电话设置和本地状态

环境默认行为
外貌系统选择系统、浅色或深色。系统遵循 iOS,直到被覆盖。
触觉启用选择刻度和成功/错误反馈具有单独的切换。
人脸识别锁残疾人启用需要可用的、注册的生物识别信息和成功的系统身份验证。
锁定背景启用启用生物识别锁后,切换到非活动/后台会锁定配对的应用程序。
桌面连接当前配对显示地址和指纹前缀;点击桌面行可重新检查连接性。
排队的消息零当无法访问桌面时显示待处理的助手消息。

UI 首选项位于 AsyncStorage 下 cadcamfun.ui.v1;配对凭证、排队消息和已见会话标记使用 SecureStore。 UI 存储会记住一个配对桌面的所选项目和每个项目的最后一个会话。更改桌面标识会清除记住的项目/会话映射,因此不会在不相关的服务器上重复使用它。

锁定协调器会跳过启动和连接屏幕,并显示配对应用程序的全屏解锁覆盖图。它不会暂停服务器端会话或撤销令牌。取消配对会删除手机保存的配对并返回“连接”;桌面撤销是防止令牌再次进行身份验证的单独操作。

网关管理示例

这些请求针对桌面环回 API 及其启动令牌运行,而不是针对电话网关。使用隔离的开发实例。将示例端口替换为其就绪事件中的端口,并提供该进程的令牌。

检查并启用开发网关
CADCAM_URL=http://127.0.0.1:43111
CADCAM_TOKEN=local-development-token

curl --fail-with-body "$CADCAM_URL/api/companion"   -H "Authorization: Bearer $CADCAM_TOKEN"

curl --fail-with-body -X POST "$CADCAM_URL/api/companion/enable"   -H "Authorization: Bearer $CADCAM_TOKEN"   -H 'Content-Type: application/json'   -d '{"port":18443}'

状态响应包含 enabled, 一个可为空的 port, 一个可为空的 fingerprint,建议的 host, 和 devices。该服务接受从 1024 到 65535 的显式端口。已使用的端口无法启动监听;选择另一个端口并重新生成配对地址。

创建命名设备配对
curl --fail-with-body -X POST "$CADCAM_URL/api/companion/pair"   -H "Authorization: Bearer $CADCAM_TOKEN"   -H 'Content-Type: application/json'   -d '{"name":"Development iPhone","host":"192.168.1.20"}'

将示例主机替换为电话可以路由到的地址。成功的配对创建将返回与设备的 HTTP 201 id 和一个带有凭证的 link。修剪后的名称必须包含 1–60 个字符。私下扫描/粘贴该链接;不要将其复制到错误报告中。创建令牌不会自动打开网关。

要删除访问权限,请发送 DELETE /api/companion/devices/<id> 使用桌面授权。要停止所有新网关流量,请发送 POST /api/companion/disable。每个请求都会检查设备哈希,因此在后续请求中会拒绝已撤销的令牌。

在不丢失上下文的情况下进行恢复

  1. 点击“设置”中的桌面连接行,以针对所公布的主机重试运行状况。
  2. 如果只有一条网络路径出现故障,请验证 Wi-Fi/热点/VPN 路由是否正确,并在桌面地址更改后重新生成二维码。
  3. 如果授权失败,请撤销桌面上的过时设备并创建新的配对。
  4. 如果只有辅助流失败,请重新打开项目会话并检查其状态,然后重新发送相同的指令。
  5. 在提交重复提示之前检查排队消息计数;排队的操作稍后重试。

离线队列具有有限的保留,并通过携带存储的会话标识符(如果可用)的新聊天请求来重放每个挂起的操作。它不是一次性交付保证。服务器执行后网络中断可能会让客户端不确定是否完成;在重复更改之前检查生成的项目/会话状态。

实施锚点: app/(app)/settings.tsx, components/layout/AppLockCoordinator.tsx, lib/store.ts, lib/server.tsx, 和 lib/offline-queue.ts.

网关允许列表

电话仅通过配套网关与桌面通信,该网关承认这组固定的路由并将它们转发到环回 API:

方法路径图案目的
GET/api/health可达性检查。
GET/api/projects, /api/projects/:id项目列表和标题。
获取、发布/api/projects/:id/vcs/* (除了 remote, push, pull), /pulls/*, /actions/*历史记录、拉取请求、评论和工作流程运行。 GitHub 同步保留在桌面上,因为它需要所有者的 GitHub 令牌。
GET/api/projects/:id/events实时 SSE 历史记录、工作流程和助理活动流;手机唯一的推送通道。
获取、发布、放置、删除/api/notifications, /mark-all-read, /:id阅读、标记已读和删除通知。
GET/api/ai/status, /models, /agents助理发现。
POST/api/ai/chat/stream, chat/abort, chat/queue, permission/reply, sessions/state, sessions/control助理会议。

由于列表是固定在 sidecar 中的,因此需要其他路由(例如文件上传或 GitHub 推送)的功能在列表更改之前是仅限桌面的功能。

手机存储什么

物品贮存笔记
网关源SecureStore首选主机加上来自配对链路的备用主机。
设备令牌安全存储, WHEN_UNLOCKED_THIS_DEVICE_ONLY除非作为网关的承载标头,否则绝不会离开设备。
证书指纹SecureStore在每个连接上与网关的证书进行比较。
离线队列、会话标记SecureStore离线时撰写的消息以及上次查看的会话状态。

在浏览器开发版本中,相同的数据回退到未加密的本地存储。仅将其与一次性配对一起使用。

屏幕

路线文件屏幕
app/index.tsx进入:无配对→配对界面;配对 → 项目
app/connect.tsx, app/pair.tsx配对:用相机扫描二维码或粘贴链接
app/(app)/projects/index.tsx项目清单
app/(app)/projects/[id].tsx一个项目
app/(app)/projects/[id]/commit/[oid].tsx提交及其更改
app/(app)/projects/[id]/pulls/[number].tsx拉取请求:审查和合并
app/(app)/assistant.tsx助理聊天
app/(app)/activity.tsx现场活动
app/(app)/settings.tsx连接和设备设置

路由使用 Expo Router 文件约定: (app) 是一个共享布局而不添加 URL 段的组,并且 [id] 是一个参数。

离线助手队列

当无法到达桌面时,在助手中输入的消息会在电话上排队而不是丢失:

规则价值
在哪里SecureStore,因此被杀死的应用程序会保留队列
存储了什么projectId, text, 选修的 sessionId, agent, mode (常规、cad、cam、gcode、刀具路径;默认常规)和 queuedAt
容量50 个条目;最旧的先被丢弃
当它排水时当应用程序以存储的配对返回前台时
如何每个条目都会打开一个新的流转并读取直到 done;一个 error 事件或后台转换使其排队
反馈祝酒词:“已发送排队消息”或“已发送 N 个排队消息”

只有连接失败才会对消息进行排队。当客户端报告状态 0 或错误文本提及网络故障、超时、拒绝连接或类似情况时,发送将被视为离线; HTTP 和业务错误(“请求……因……而失败”)会显示出来,而不是排队。

在 3D

移动设备和边车 拖动进行轨道旋转·滚动进行缩放·右键拖动进行平移
Processes at run time. The Tauri shell spawns the sidecar and waits for its {”event”:”ready”,”port”:N} line on stdout (sidecar.rs); the webview then talks HTTP to 127.0.0.1 on that port with the bearer token. The sidecar owns the SQLite database, the embedded nikcli AI host (CADCAMFUN_AI_HOST=nikcli, the default) and plugins; the mobile app reaches it through companion pairing.