ドキュメント
モバイルコンパニオン
ペアリングされたデスクトップ用の 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 実装。
トランスポートとゲートウェイの範囲
コンパニオン ゲートウェイは、サイドカーのループバック リスナーとは別のものです。デフォルトではオフになっています。有効にすると、HTTPS サーバーが起動します。 0.0.0.0、別の有効なポートが選択されていない限り、ポート 8443 を使用します。サービスは有効な状態を保持し、サイドカーの起動後に再開します。デスクトップ ファイアウォールとネットワーク ルートは、電話機がこのリスナーに到達することを許可する必要があります。
電話機は取り消し可能なデバイス トークンを送信します。ゲートウェイは 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 秒以内に 1 つの 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 プリビルドを実行します。日々の業務においては、 start (エイリアス dev) expo 開発クライアント バンドラーを実行します。 ios:dev ローカルホスト上で起動し、iOS シミュレーターを開きます。 ios:build バンドラーを起動せずにデバッグ アプリをコンパイルし、 web expo Web ターゲットを開始します。の test スクリプトの実行 bun test test 内部 apps/mobile.
.github/workflows/ci.yml コンパニオンの型チェック、ロジック テスト、および JavaScript エクスポートを定義します。 .github/workflows/ios.yml xcode プロジェクトを生成し、未署名のリリース シミュレーター アプリを構築し、それをライブ サイドカーと組み合わせ、認証されたプロジェクト リストを確認し、スクリーンショットをキャプチャします。これらの構成されたチェックでは、物理デバイス、署名、TestFlight、または実際のプロバイダーの受け入れは確立されません。
ストレージとデバイスのセキュリティ
ネイティブ iOS では、ペアリング認証情報は Expo SecureStore に保存されます。ペアリングエントリでは、 WHEN_UNLOCKED_THIS_DEVICE_ONLY。オフライン キュー エントリとセッション認識マーカーも SecureStore を使用します。ブラウザーのフォールバックは、開発にローカル ストレージを使用します。
アプリは生体認証機能を照会し、使用します expo-local-authentication ローカル認証の場合、システム パスコード フォールバックが有効になっています。生体認証 UI は、ゲートウェイのサーバー側のルート許可を変更しません。デバイスの取り消しは、ペアリングされたデスクトップによって制御されます。
デスクトップは、ゲートウェイ証明書と秘密キーをアプリケーション データ コンパニオン ディレクトリに保存します。キーは所有者のみのアクセス許可で書き込まれます。その証明書を置き換えると、固定されたフィンガープリントが変更され、新しい信頼できるペアリングが必要になります。
プロジェクトレビューのワークフロー
- 「プロジェクト」を開き、デスクトップ プロジェクトを選択します。
- 動作ステータス、ブランチ、コミット、プル リクエスト、またはワークフローの実行を検査します。
- コミットを開いて差分を確認するか、プル リクエストを開いて変更を確認し、判定を送信します。
- プロジェクトに応じて、利用可能なマージ/クローズまたはワークフローディスパッチアクションを使用します。
- 選択したプロジェクト コンテキストでアシスタントを開いて、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 パーサーを使用するため、任意のネットワーク チャンク境界がアプリケーション イベント境界にはなりません。テストでは、ネイティブ モジュールをインポートしたりシミュレーターを実行したりせずに、トランスポートを挿入します。
電話をペアリングする
- デスクトップで、[設定] とその [モバイル] コンパニオン パネルを開きます。または
/settings/companionをクリックし、ゲートウェイをオンにします。 - 名前付きデバイスペアリングを作成し、QR コードを表示します。リンクには、HTTPS ホスト候補、デバイス トークン、および証明書のフィンガープリントが含まれています。
- iOS では、QR コードをスキャンするか、
cadcamfun://pairリンク。プロンプトが表示されたら、ローカル ネットワーク アクセスを許可します。 - アプリはリンクを検証し、アドバタイズされたホスト候補と競合し、最初に成功した正常性接続を維持します。
- デバイスがデスクトップ設定に表示されることを確認します。デスクトップの取り消しアクションを使用して、そのアクセスを削除します。
ホスト候補には、適切なプライベート IPv4 およびオーバーレイ ネットワーク アドレスが含まれます。ゲートウェイは、検出されたアドレスを最大 6 つアドバタイズします。現在のリスナーは IPv4 であるため、検出された IPv6 アドレスはアドバタイズされません。 VPN またはオーバーレイ ネットワークは、ローカル ネットワークから離れた場所で使用するためのルーティングをすでに提供している必要があります。ペアリングではリレーや VPN は作成されません。
証明書の固定
Swift 実装ではエフェメラルを使用します URLSession 信頼できるデリゲートを持つインスタンス。 DER バイトの SHA-256 ハッシュがペアリングのフィンガープリントと等しい場合にのみ、リーフ証明書を受け入れます。リクエストには HTTPS が必要で、リダイレクトは拒否され、ネイティブ呼び出し元がサイレントにブラウザー トランスポートにダウングレードすることはありません。
したがって、証明書の信頼はペアリング リンクから得られます。 QR コードと貼り付けたリンクを認証情報として扱います。予期しない発信元またはフィンガープリントを持つリンクは、TLS 検証をバイパスするのではなく、デスクトップ ペアリング フローを通じて置き換える必要があります。
接続を診断する
- ネイティブトランスポートなし: ローカル モジュールを含む iOS 開発ビルドまたはリリース ビルドをインストールします。
- 到達可能なホストがありません: ローカルネットワークの許可、デスクトップファイアウォール、アドバタイズされたアドレス、ゲートウェイポート、およびネットワークルーティングを確認してください。
- 401: デバイストークンが無効であるか取り消されています。新しいペアリングを作成します。
- 403: エンドポイントがコンパニオン許可リストの外にあります。
- 証明書の不一致: 現在のデスクトップ証明書と再度ペアリングします。
- アシスタントが利用できません: デスクトッププロバイダーの構成とサイドカーが実行されているかどうかを確認してください。
電話の設定とローカル状態
| 設定 | デフォルト | 行動 |
|---|---|---|
| 外観 | システム | システム、ライト、またはダークを選択します。システムはオーバーライドされるまで iOS に従います。 |
| ハプティクス | 有効 | 選択チェックと成功/エラーのフィードバックには個別の切り替えがあります。 |
| Face ID ロック | 無効 | 有効にするには、利用可能な登録済みの生体認証とシステム認証の成功が必要です。 |
| 背景をロックする | 有効 | 生体認証ロックが有効になっている場合、非アクティブ/バックグラウンドに移動すると、ペアリングされたアプリがロックされます。 |
| デスクトップ接続 | 現在のペアリング | アドレスとフィンガープリントのプレフィックスを表示します。デスクトップの行をタップすると、接続が再チェックされます。 |
| キューに入れられたメッセージ | ゼロ | デスクトップにアクセスできないときに保留中のアシスタント メッセージを表示します。 |
UI 設定は AsyncStorage に存在します。 cadcamfun.ui.v1;認証情報、キューに入れられたメッセージ、および表示されたセッション マーカーのペアリングには SecureStore が使用されます。 UI ストアは、選択されたプロジェクトと、ペアになった 1 つのデスクトップのプロジェクトごとの最後のセッションを記憶します。デスクトップ ID を変更すると、記憶されているプロジェクト/セッション マッピングがクリアされるため、関係のないサーバーに対して再利用されなくなります。
ロック コーディネーターは、起動画面と接続画面をスキップし、ペアリングされたアプリの全画面ロック解除オーバーレイを表示します。サーバー側のセッションを一時停止したり、トークンを取り消したりすることはありません。ペアリングを解除すると、電話機に保存されているペアリングが削除され、接続に戻ります。デスクトップの取り消しは、トークンの再認証を防ぐ別のアクションです。
ゲートウェイ管理の例
これらの要求は、電話ゲートウェイに対してではなく、起動トークンを使用してデスクトップ ループバック API に対して実行されます。分離された開発インスタンスを使用します。サンプル ポートを Ready イベントのポートに置き換え、そのプロセスのトークンを指定します。
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、null 許容値 port、null 許容値 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"}' ホスト例を、電話機がルーティングできるアドレスに置き換えます。 Successful pairing creation returns HTTP 201 with a device id そして資格を持った link。トリミングされた名前には、1 ~ 60 文字が含まれている必要があります。そのリンクを非公開でスキャンして貼り付けます。これをバグレポートにコピーしないでください。トークンを作成しても、ゲートウェイは自動的にオンになりません。
アクセスを削除するには、送信してください DELETE /api/companion/devices/<id> デスクトップ認証を使用します。すべての新しいゲートウェイ トラフィックを停止するには、次のように送信します。 POST /api/companion/disable。デバイス ハッシュはリクエストごとにチェックされるため、取り消されたトークンは後続のリクエストで拒否されます。
コンテキストを失わずに復元する
- [設定] でデスクトップ接続の行をタップして、アドバタイズされたホストに対して正常性を再試行します。
- 1 つのネットワーク パスのみに障害が発生した場合は、正しい Wi-Fi/ホットスポット/VPN ルートを確認し、デスクトップ アドレスの変更後に QR コードを再生成します。
- 認証が失敗した場合は、デスクトップ上の古いデバイスを取り消し、新しいペアリングを作成します。
- アシスタント ストリームのみが失敗した場合は、同じ命令を再送信する前に、プロジェクト セッションを再度開き、その状態を検査します。
- 重複したプロンプトを送信する前に、キューに入れられたメッセージの数を検査します。キューに入れられた操作は後で再試行されます。
オフライン キューの保持期間には制限があり、保存されたセッション ID が利用可能な場合は、そのセッション ID を含む新しいチャット リクエストを通じて保留中の各操作を再生します。 1 回限りの配信を保証するものではありません。サーバーの実行後にネットワークが切断されると、クライアントは完了について不確かなままになる可能性があります。変更を繰り返す前に、結果のプロジェクト/セッションの状態を検査してください。
実装アンカー: 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 | アシスタントセッション。 |
リストはサイドカーで固定されているため、別のルートが必要な機能 (ファイルのアップロードや GitHub プッシュなど) は、リストが変更されるまでデスクトップのみの機能となります。
携帯電話に保存されているもの
| アイテム | ストレージ | 注意事項 |
|---|---|---|
| ゲートウェイ起点 | SecureStore | 優先ホストとペアリング リンクからの代替。 |
| デバイストークン | セキュアストア、 WHEN_UNLOCKED_THIS_DEVICE_ONLY | ゲートウェイへのベアラー ヘッダーとして送信する場合を除き、デバイスを離れることはありません。 |
| 証明書のフィンガープリント | SecureStore | すべての接続でゲートウェイの証明書と比較します。 |
| オフラインキュー、セッションマーカー | SecureStore | オフライン中に作成されたメッセージと最後に確認されたセッションの状態。 |
ブラウザ開発ビルドでは、同じデータが暗号化されないローカル ストレージにフォールバックされます。使い捨てペアリングでのみ使用してください。
スクリーン
| ルートファイル | 画面 |
|---|---|
app/index.tsx | エントリ: ペアリングなし → ペア画面;ペアリング → プロジェクト |
app/connect.tsx, app/pair.tsx | ペアリング: カメラで QR コードをスキャンするか、リンクを貼り付けます。 |
app/(app)/projects/index.tsx | プロジェクト一覧 |
app/(app)/projects/[id].tsx | 1つのプロジェクト |
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 エラーとビジネス エラー (「リクエストへの…が…で失敗しました」) は、キューに入れられる代わりに表示されます。