codexteer 0.16.0 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +37 -2
- package/README.md +154 -16
- package/assets/remote-device-key.swift +94 -0
- package/bin/codexteer-update.mjs +3 -0
- package/bin/codexteer.mjs +6 -2
- package/docs/cloud.md +113 -0
- package/docs/desktop-cloud.md +36 -0
- package/docs/distribution.md +21 -9
- package/docs/orchestration.md +120 -0
- package/docs/remote-cli-validation.json +139 -0
- package/docs/remote-control.md +65 -0
- package/docs/remote-device-enrollment.md +91 -0
- package/docs/remote-observation.md +57 -0
- package/docs/remote-operations.md +89 -0
- package/docs/thread-create.md +90 -0
- package/package.json +13 -2
- package/scripts/update-desktop.applescript +51 -0
- package/src/agent-launch.mjs +27 -0
- package/src/app-server.mjs +34 -10
- package/src/checkpoint.mjs +52 -16
- package/src/cli.mjs +233 -19
- package/src/cloud-api.mjs +118 -0
- package/src/cloud-cli.mjs +319 -0
- package/src/cloud-connection.mjs +64 -0
- package/src/cloud-id.mjs +16 -0
- package/src/cloud-observe.mjs +178 -0
- package/src/cloud-send.mjs +33 -0
- package/src/cloud.mjs +172 -0
- package/src/connection.mjs +23 -5
- package/src/creation-verification.mjs +36 -0
- package/src/desktop-cloud-api.mjs +119 -0
- package/src/desktop-cloud-id.mjs +18 -0
- package/src/desktop-cloud-project.mjs +148 -0
- package/src/desktop-cloud.mjs +98 -0
- package/src/desktop.mjs +3 -1
- package/src/directive.mjs +5 -2
- package/src/distribution.mjs +6 -6
- package/src/evidence.mjs +3 -0
- package/src/findings.mjs +7 -7
- package/src/help.mjs +155 -26
- package/src/journal.mjs +37 -9
- package/src/launcher.mjs +16 -9
- package/src/monitor.mjs +18 -6
- package/src/native-command.mjs +100 -0
- package/src/native-rpc.mjs +116 -0
- package/src/native-worker.mjs +25 -0
- package/src/observe.mjs +21 -7
- package/src/orchestrate-prompt.mjs +96 -0
- package/src/orchestrate.mjs +29 -0
- package/src/paged-read.mjs +7 -1
- package/src/projects.mjs +104 -0
- package/src/prompt-command.mjs +25 -0
- package/src/prompt.mjs +35 -11
- package/src/remote-client-info.mjs +16 -0
- package/src/remote-control-auth.mjs +109 -0
- package/src/remote-control-socket.mjs +137 -0
- package/src/remote-control.mjs +134 -0
- package/src/remote-device-key.mjs +41 -0
- package/src/remote-device-login.mjs +76 -0
- package/src/remote-device-protocol.mjs +72 -0
- package/src/remote-device-store.mjs +71 -0
- package/src/remote-device-worker.mjs +48 -0
- package/src/remote-device.mjs +202 -0
- package/src/remote-io.mjs +163 -0
- package/src/remote-key-access.mjs +76 -0
- package/src/remote-key-worker.mjs +38 -0
- package/src/remote-keychain.mjs +72 -0
- package/src/remote-management.mjs +234 -0
- package/src/remote.mjs +132 -0
- package/src/resource.mjs +8 -6
- package/src/runner.mjs +6 -1
- package/src/runtime.mjs +4 -2
- package/src/socket-endpoint.mjs +40 -0
- package/src/standalone-create.mjs +135 -0
- package/src/store.mjs +6 -3
- package/src/supervise.mjs +32 -55
- package/src/supervision.mjs +41 -19
- package/src/target.mjs +25 -0
- package/src/task-id.mjs +23 -0
- package/src/thread-create.mjs +128 -0
- package/src/thread-id.mjs +21 -6
- package/src/thread-verify.mjs +108 -0
- package/src/turn-repair.mjs +228 -0
- package/src/update.mjs +158 -0
- package/src/wrapper.mjs +68 -5
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# codexteer専用端末鍵の登録とペアリング
|
|
2
|
+
|
|
3
|
+
`--remote-transport paired`はcodexteer専用の端末鍵と登録を使います。既存Desktop鍵を使う`control`と、Desktop接続で観測する`native`から明示的に区別します。登録・ペアリングは利用者が実行するセットアップ操作であり、read / doctor / 監督から自動では行いません。
|
|
4
|
+
|
|
5
|
+
## 手順
|
|
6
|
+
|
|
7
|
+
Mac上で専用鍵を準備します。次のkeychain方式はXcode command-line toolsが必要です。
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
codexteer remote prepare --key-provider keychain --json
|
|
11
|
+
codexteer remote enroll --json
|
|
12
|
+
codexteer remote verify --json
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
`remote enroll`は公式ブラウザにfreshな本人認証を要求します。ブラウザは`https://auth.openai.com/oauth/authorize`だけを開きます。パスワードやトークンをチャットやCLI引数へ渡さず、公式画面で認証します。登録用スコープは`codex.remote_control.enroll`です。PKCE S256、state、localhostの公式コールバックポート1455 / 1457を使い、得たトークンのアカウント・スコープ・発行時刻・password auth timeを照合します。既存の通常ログイントークンを登録用step-up tokenとして代用しません。
|
|
16
|
+
|
|
17
|
+
対象WindowsホストのDesktopで、Settings → Connections → Control this PC → Set up / Addからペアリング画面を開きます。Computer向けのPIN表示へ切り替えるなどして手動ペアリングコードを取得します。CLIの追加インストールは不要です。
|
|
18
|
+
|
|
19
|
+
公式CLIを利用できる環境では、Desktopと同じプロファイルで次の方法も使えます。別プロファイルを起動したり、ホストのdaemonを再起動する必要はありません。
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
codex remote-control pair
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Mac上の端末で次を実行し、発行したコードを入力します。TTY入力は非表示で、Enterで確定します。コードはargv・履歴JSON・専用登録ファイルには保存しません。パイプ入力も利用できますが、コードをshell履歴や共有ログに残さないようにしてください。
|
|
26
|
+
|
|
27
|
+
```sh
|
|
28
|
+
codexteer --host 'remote-control:env_example' remote pair --code-stdin --json
|
|
29
|
+
codexteer remote verify --json
|
|
30
|
+
codexteer --remote-transport paired doctor --thread '<hostId付きリンク>' --json
|
|
31
|
+
codexteer --remote-transport paired read '<hostId付きリンク>' --json
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
ペアリングAPIはコードが指すホストを認可します。応答のenvironment IDが指定hostと異なれば`REMOTE_PAIR_HOST_MISMATCH`で報告し、指定チャットを開きません。これはペアリング前の原子的なhost照合ではありません。入力するコードが指定ホストで発行されたものかを確認してください。`remote verify`は専用clientに許可されたホスト一覧を公式APIで再照合します。
|
|
35
|
+
|
|
36
|
+
Windows環境が`remote-control:env_example`なら、Mac側の`--host`へその値をそのまま使います。
|
|
37
|
+
|
|
38
|
+
## 鍵方式
|
|
39
|
+
|
|
40
|
+
| 明示する方式 | 実装と保護 | 制約 |
|
|
41
|
+
| --- | --- | --- |
|
|
42
|
+
| `remote prepare --key-provider openai-native` | Desktop同梱の未変更native addonを署名済みNodeで実行し、新しい専用鍵を作る。公式のhardware / OS非抽出policyを使用 | ハードウェアとOS権限に依存する。利用できない場合は停止する |
|
|
43
|
+
| `remote prepare --key-provider keychain` | codexteer自身のSwift helperをローカルビルド・アドホック署名し、ログインキーチェーンに非抽出P-256鍵を作る。OSのSecKey属性で非抽出・256bitを確認し、公開鍵だけをSPKIへ変換 | Secure Enclaveとは表示しない。OSのキーチェーン確認要求には利用者が対応する |
|
|
44
|
+
|
|
45
|
+
方式を失敗時に自動切替しません。準備済みの鍵を別方式で置換する要求は`REMOTE_KEY_PROVIDER_MISMATCH`で停止します。Desktop本体・同梱Nodeの署名やentitlementは変更しません。keychain方式のhelperにも、Desktopのアクセスグループや特権entitlementは追加しません。
|
|
46
|
+
|
|
47
|
+
helperはsecret keyのexport APIを呼びません。OSに非抽出属性を設定し、返されたSecKey属性がfalseであることを確認できた鍵だけを登録へ進めます。属性を確認できない候補では登録を拒否します。単に生成引数を渡したことを非抽出性の証明にはしません。
|
|
48
|
+
|
|
49
|
+
## 保存・中断と再検証
|
|
50
|
+
|
|
51
|
+
専用登録は、選んだCODEX_HOMEの`codex-steer/remote-device/<accountのSHA256>.json`へowner-onlyで保存します。含まれるのはアカウント識別子、client ID、公開鍵メタデータ、OS key ID、登録phase、ペアリング先です。Desktopの`.codex-global-state.json`は更新しません。bearer / OAuth step-up / remote-control token / pairing code / 署名proofは保存しません。
|
|
52
|
+
|
|
53
|
+
Swift helperは`remote-device/key-helpers/<source hash>/`へ保存し、実行ごとに所有者・mode・binary hash・コード署名を検査します。登録にはそのruntimeを固定し、更新時に鍵を使うhelperを勝手に作り直しません。使用中のruntimeを削除しないでください。
|
|
54
|
+
|
|
55
|
+
```sh
|
|
56
|
+
codexteer remote status --json
|
|
57
|
+
codexteer remote recover-key --json
|
|
58
|
+
codexteer remote verify --json
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
- `prepared`は鍵だけの準備完了。まだサーバー登録はありません。ブラウザ認証の中断はこの鍵を保持します。
|
|
62
|
+
- keychainの`creating-key`は鍵作成の結果が未確定。保存済みcandidate key IDの`recover-key`で同じ鍵を照合します。新規作成はしません。
|
|
63
|
+
- `starting` / `creating-key` / `key-created`でサーバーclient IDを取得済みのものは、未完了の登録です。別clientや鍵を自動作成しません。
|
|
64
|
+
- `finish-unknown`は登録完了応答が未確定。`remote verify`は同じ鍵とclient IDで認証だけを確認し、成功すればenrolledへ進めます。finish要求は再送しません。
|
|
65
|
+
- ペアリング応答不明は`pairing_unknown`。同じコードを自動再送せず、`remote verify`で公式の許可ホスト一覧を読み直します。
|
|
66
|
+
|
|
67
|
+
`remote verify`のWebSocket操作は認証proofだけで、App Server initializeやチャットRPCは送りません。別途pairedのdoctor/readを実行して初めて、その対象の読み取りを検証します。
|
|
68
|
+
|
|
69
|
+
## 監督との接続
|
|
70
|
+
|
|
71
|
+
paired接続は既存のRemote Control v3 adapterを使い、送信には同じ`turn/steer.expectedTurnId`照合、freshness、pause/stop、finding重複防止、配送照合を適用します。native / control / pairedのカーソル・監督IDは交換できません。専用登録がない場合や指定ホストがペアリングされていない場合は、外部socketを開かずに停止します。失敗時にDesktop鍵へ切り替えません。
|
|
72
|
+
|
|
73
|
+
登録・ペアリングと、チャットの作成・送信は別々に利用者が許可する操作です。対象のホスト・プロジェクト・チャットを確認し、許可された範囲だけで使ってください。callerと購読条件は[リモートCLI操作](remote-operations.md)を参照してください。監督役から資格情報セットアップは実行できません。
|
|
74
|
+
|
|
75
|
+
## 端末名とサーバー表示の照合
|
|
76
|
+
|
|
77
|
+
従来のWebSocketは`User-Agent: codexteer`しか送らず、同じ専用clientのサーバー表示は`Unknown Computer`・platform nullでした。HTTPとWebSocketへcodexteerの製品名・実OSバージョン・CPUを送るよう変更しました。macOSでは固定の`/usr/bin/sw_vers -productVersion`から取得し、失敗・不正値はバージョンなしへ限定します。OSの識別のためにDesktop製品を名乗りません。
|
|
78
|
+
|
|
79
|
+
```sh
|
|
80
|
+
codexteer remote status --json
|
|
81
|
+
codexteer remote verify --json
|
|
82
|
+
codexteer remote identity --json
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
`controller_name`は`codexteer_mac_os_arm64`などのCLI側の識別名です。`remote identity`は公式アプリが使う`GET /wham/remote/control/clients`を読み、同じ専用clientだけを選別して`server_display_name`・OS・端末種別を返します。他端末・アカウントユーザー・位置情報は出力しません。identity自体は接続や名前変更を行いません。
|
|
86
|
+
|
|
87
|
+
任意のクライアント表示名の更新は未対応です。`name_matches`と`custom_name_update`でローカル識別名とサーバー表示の違いを明示します。表示名を合わせるための自動再登録・鍵置換・ホスト名変更は行いません。
|
|
88
|
+
|
|
89
|
+
## 互換性
|
|
90
|
+
|
|
91
|
+
登録とペアリングの経路はDesktopのバージョンに依存します。外部CLI向けの安定した登録SDKであるとは扱いません。schemaの変更や認証拒否では停止し、利用環境で確認してください。秘密値や実際の識別子を含む診断記録は公開しないでください。
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# リモートプロジェクトとチャットの観測
|
|
2
|
+
|
|
3
|
+
`projects list --remote`でDesktopに登録済みのリモートプロジェクトを取得できます。`hostId`付きリンクは、そのホストのチャットとして読み取り・監視・観測専用の監督に使えます。この文書の既定native経路では、指示送信・再開・作成・履歴修復は未対応です。指定ターンへの送信は、別途明示する[control経路](remote-control.md)に実装しています。controlの認証チャレンジ受信まで確認済みですが、既存端末鍵へのアクセスで停止しています。
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
codexteer projects list --remote --json
|
|
7
|
+
codexteer projects list --all --json
|
|
8
|
+
codexteer projects list --host 'remote-control:env_example' --json
|
|
9
|
+
|
|
10
|
+
codexteer read 'codex://threads/<UUID>?hostId=remote-control%3Aenv_example' --json
|
|
11
|
+
codexteer doctor --thread 'codex://threads/<UUID>?hostId=remote-control%3Aenv_example' --json
|
|
12
|
+
codexteer watch 'codex://threads/<UUID>?hostId=remote-control%3Aenv_example' --stream --json
|
|
13
|
+
codexteer supervise 'codex://threads/<UUID>?hostId=remote-control%3Aenv_example' --agent codex
|
|
14
|
+
codexteer supervise prompt 'codex://threads/<UUID>?hostId=remote-control%3Aenv_example' --use-path-cli
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
UUIDと`--host HOST-ID`を別々に指定しても同じ対象になります。リンクの`hostId`と`--host`が矛盾した場合は`HOST_MISMATCH`で停止します。裸のUUIDは従来どおりローカルです。ホストの省略を補うために別ホストを検索しません。
|
|
18
|
+
|
|
19
|
+
## プロジェクト一覧
|
|
20
|
+
|
|
21
|
+
引数なしの`projects list`は従来どおりローカルApp Serverのcanonical ID・全rootsを返します。`--all`はその一覧とリモート登録一覧を併合します。`--remote`またはリモートの`--host`はローカルApp Serverへ接続せず、選択した`CODEX_HOME/.codex-global-state.json`の`remote-projects`だけを読みます。状態ファイルを更新しません。
|
|
22
|
+
|
|
23
|
+
リモート項目はDesktopの登録ID、登録名、remotePath、host_idを返します。Windows/UNCパスをMacのパスへ変換・realpathしません。`source: desktop-registration`と`availability: unverified`は登録情報であり、接続・リモート上の存在・Git判定を確認していないことを示します。今回確認したDesktopのnative `list_projects`はリモートGitメタデータの取得に失敗した項目を省略するため、それを完全な登録一覧として使いません。空配列もリモート上にプロジェクトがないという証明ではありません。
|
|
24
|
+
|
|
25
|
+
## 読み取りの前提
|
|
26
|
+
|
|
27
|
+
Desktopが接続済みの対象ホストを、既存shared wrapperの署名付きNode helperからnative `read_thread`で読みます。既存のnative認証・ソケット所有者検査・matching bundled command schema・live native catalogの検証を使います。別のリモート接続、認証情報のコピー、SSH、モデルの中継ターン、Desktopの再起動は行いません。`--connection desktop`との併用は`HOST_CONNECTION_MISMATCH`です。
|
|
28
|
+
|
|
29
|
+
native toolには実在するローカル呼出し元と保存済みターンが必要です。Codexチャット内では`CODEX_THREAD_ID`を使います。普通の端末から実行する場合は、同じプロファイルの既存ローカルチャットを明示します。
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
codexteer --context-thread '<LOCAL-CALLER-UUID>' read '<HOST-LINK>' --json
|
|
33
|
+
codexteer --context-thread '<LOCAL-CALLER-UUID>' supervise '<HOST-LINK>' --agent codex
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
このcontextはnative呼出しの関連付け用です。呼出し元へのメッセージ送信・新しいターン作成は行いません。未指定・リモートのcontext・保存済みターンのないcontextは拒否します。対象チャットの応答hostIdが選択先と異なった場合も、結果を採用せず停止します。
|
|
37
|
+
|
|
38
|
+
native履歴は10ターン単位で全ページを読み、既存のイベント差分・カーソルを生成します。最大100ページ(1000ターン)を超える場合や、ページの重複・不完全なturnでは部分履歴を完全な結果として返しません。コマンド出力とdiffはDesktopが項目ごとに最大20000文字へ制限するため、その場合は`[truncated by Desktop]`を付けます。思考内容は公開イベントに含めません。履歴が大きいチャットは読み取りに時間がかかります。`watch --stream`のキャンセルは次の境界で終了し、未完了のnative読取helperは最大30秒でタイムアウトします。
|
|
39
|
+
|
|
40
|
+
## 監督の範囲と記録
|
|
41
|
+
|
|
42
|
+
既定native経路で生成するプロンプトは`observation_only: true`です。既定の方針も観測・問題報告用になります。`--host`、`--context-thread`、`--supervisor`、`--connection`を全コマンドに保持します。明示した方針が送信を求めても、リモートへの送信は`REMOTE_READ_ONLY`で拒否します。Desktopのnative送信APIでは`expectedTurnId`照合と暗黙のターン開始禁止を指定できないため、既存の送信制約を省略して対応扱いにはしません。
|
|
43
|
+
|
|
44
|
+
監督記録・指摘・履歴・ロックは`CODEX_HOME/codex-steer/hosts/<host-idのSHA256>/`でホスト別に保存し、記録には`host_id`を付けます。既存ローカル記録の場所は変えません。カーソルもhost_idに結び付け、別ホストやローカルのものは`STALE_CURSOR`です。
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
codexteer --host 'remote-control:env_example' supervise list --json
|
|
48
|
+
codexteer supervise status '<HOST-LINK>' --json
|
|
49
|
+
codexteer supervise pause '<HOST-LINK>' --json
|
|
50
|
+
codexteer supervise stop '<HOST-LINK>' --json
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
`pause`後も観測できます。`stop`後はその監督の観測を拒否し、対象Codexの作業は継続します。`doctor`はリモート読み取りの検証結果と送信未対応を分けて返します。診断成功は指示配送・画面表示・承認往復を保証しません。
|
|
54
|
+
|
|
55
|
+
## 検証
|
|
56
|
+
|
|
57
|
+
自動テストは架空のホストと一時プロファイルを使い、複数ページの読取り、別ホストの応答拒否、カーソル・監督記録・停止制御のホスト分離を確認します。実際のホスト名・業務タイトル・チャットID・応答本文は公開記録に含めません。環境ごとの接続条件は利用者自身の`doctor`結果で確認してください。
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# リモートのCLI操作
|
|
2
|
+
|
|
3
|
+
`--host <HOST-ID> --remote-transport paired`で、接続先のタスクをcodexteer CLIから操作できます。接続先にはCodex Desktopと公式ペアリングが必要で、codexteerやCodex CLIの追加インストールは不要です。ローカルのshared wrapperと専用鍵は従来どおり使用します。既定のnative経路は観測専用、既存Desktop鍵のcontrol経路は別機能のままです。失敗時に別ホスト・別資格情報・別backendへ自動切替しません。
|
|
4
|
+
|
|
5
|
+
## 対応範囲
|
|
6
|
+
|
|
7
|
+
| ローカルの操作 | pairedでの動作 |
|
|
8
|
+
| --- | --- |
|
|
9
|
+
| `projects list` | 接続先の`project/list`で正式ID・全rootsを取得。Desktop登録IDも別フィールドで対応付ける |
|
|
10
|
+
| `threads list` | 接続先のタスクID・タイトル・所属・作業場所・hostId付きリンクを取得。ページ・検索・project指定・Desktop絞込みに対応 |
|
|
11
|
+
| `thread create` | Desktopの公式createで対象ホストへ作成し、pairedでcanonical所属・初期指示・全rootsを照合 |
|
|
12
|
+
| `thread verify` | 保存済みremote receiptを読み直して照合。作成・初期送信・所属変更は繰り返さない |
|
|
13
|
+
| `thread resolve` | hostIdを保持してIDを解決 |
|
|
14
|
+
| `read/status/watch/doctor` | 選択先の履歴・稼働状態・差分・接続を読む |
|
|
15
|
+
| `send` | 実行中の同じターンへ`expectedTurnId`付きで一度だけ送る |
|
|
16
|
+
| `send --new-turn` | idleだけを対象とし、制御側Desktopで対象チャットを開いてから同じ接続先へ`turn/start`する |
|
|
17
|
+
| `supervise`・findings・history・instructions | 従来のpause/stop・freshness・重複防止・受信照合を維持。記録・ロックはホストごとに分離 |
|
|
18
|
+
| `orchestrate` / `orchestrate prompt` | 接続先host・paired・callerを子作成/監督コマンドへ保持。エージェント自体は制御側で動く |
|
|
19
|
+
| `checkpoint capture/verify/attach`・ファイル根拠 | 接続先の公式`fs/*`で読む。相対入力パスは対象チャットのcwdで解決。ファイル根拠・artifactはリモート絶対パス |
|
|
20
|
+
| `checkpoint run`・`resource run` | defaultは接続先の`command/exec`。明示的な`thread-shell`にも対応(下記)。Macでは実行しない |
|
|
21
|
+
| resourceの取得・更新・解放・一覧 | ホスト別の協調記録。OSの排他制御を作るものではない |
|
|
22
|
+
| `open` | 制御側Desktopで指定したリモートチャットを表示 |
|
|
23
|
+
| `desktop start/update/update-status`・`debug-ui` | Macの起動/更新/Accessibility用で接続先へは未対応。ローカルで誤実行せず`REMOTE_HOST_OPERATION_UNSUPPORTED` |
|
|
24
|
+
| `thread repair` | 修復機能はローカル専用。リモート指定は`REMOTE_REPAIR_UNSUPPORTED`で明示拒否し、保存履歴やDBを変更しない |
|
|
25
|
+
| `--backend ui` | 接続先のUI送信は未対応。明示的に拒否する |
|
|
26
|
+
|
|
27
|
+
Macの起動・更新・Accessibilityとオフライン履歴修復は、リモート接続先へ提供していません。上表は実装範囲であり、接続先APIが全操作を許可する保証でもありません。
|
|
28
|
+
|
|
29
|
+
## 一覧と作成
|
|
30
|
+
|
|
31
|
+
```sh
|
|
32
|
+
codexteer --host remote-control:env_example --remote-transport paired projects list --json
|
|
33
|
+
codexteer --host remote-control:env_example --remote-transport paired threads list --limit 20 --json
|
|
34
|
+
codexteer --host remote-control:env_example --remote-transport paired threads list --project CANONICAL-ID --search 検証 --json
|
|
35
|
+
codexteer --host remote-control:env_example --remote-transport paired threads list --cursor CURSOR --json
|
|
36
|
+
codexteer --host remote-control:env_example --remote-transport paired --context-thread LOCAL-CALLER-ID thread create --project PROJECT-ID --title 検証専用 '初回の指示' --json
|
|
37
|
+
codexteer --host remote-control:env_example --remote-transport paired thread verify ATTEMPT-ID --json
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
`project_id`は接続先App Serverの正式ID、`desktop_project_ids`は制御側Desktopの登録IDです。両者を同じIDとは扱いません。登録ID・正式ID・一意の正確な名前で作成対象を選べます。Desktopの登録とlive projectの全rootsが一意に一致しなければ作成しません。Windows/UNCをMacのパスへ変換したり、Macのrealpath/statで検査したりしません。
|
|
41
|
+
|
|
42
|
+
paired一覧の`availability: verified`は`availability_scope: project-catalog`です。ホストへ接続して正式一覧を読めた意味で、各rootの現在の存在・読み書き可否は`roots_availability: unverified`です。
|
|
43
|
+
|
|
44
|
+
作成はDesktopの公式native APIを使うため、**実在するローカルcallerと保存済みturnが必要**です。Codex内では`CODEX_THREAD_ID`を使用し、通常の端末からは`--context-thread LOCAL-CALLER-ID`を明示します。callerの追加ターンやAIによる中継は作りません。pairedの一覧・read・通常sendにはcallerは不要です。ローカルstandalone作成は従来どおりcaller不要で、環境変数も継承しません。
|
|
45
|
+
|
|
46
|
+
`--dry-run`はnative schema・live project・登録の対応を検査するだけで、作成・送信・receipt保存をしません。作成後は実ホスト、Desktop所属とcanonical所属、タイトル、初回本文、project rootsを照合します。Desktopが付与するそのタスク固有のvisualizations rootだけは、同じprofile・履歴日・thread IDから照合して許容し、それ以外の追加rootは拒否します。
|
|
47
|
+
|
|
48
|
+
POSIXパスの大文字小文字は区別します。Windowsのドライブ付きパスとバックスラッシュのUNCパスだけは、Windowsの表記を正規化して照合します。再照合が失敗した場合はreceiptのverificationをfailedへ更新して古い認定フラグを除去し、created/acceptedは保持します。
|
|
49
|
+
|
|
50
|
+
`creation_status: created`は既存タスクがある状態です。後段の照合失敗でもそのIDとacceptedを保持します。応答不明はunknownとしてreceiptを残し、自動再作成・再送しません。異なるホストが返った場合も判明したIDを保持して停止し、そのタスクへ追加操作しません。
|
|
51
|
+
|
|
52
|
+
## 新しいターンと監督
|
|
53
|
+
|
|
54
|
+
```sh
|
|
55
|
+
codexteer --remote-transport paired --context-thread LOCAL-CALLER-ID send 'codex://threads/UUID?hostId=remote-control%3Aenv_example' '続きの指示' --new-turn --json
|
|
56
|
+
codexteer --remote-transport paired --context-thread LOCAL-CALLER-ID supervise prompt 'codex://threads/UUID?hostId=remote-control%3Aenv_example' --use-path-cli --json
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
new-turnは、実在callerによるDesktop native readでthread/hostを照合し、公式navigation APIで制御側Desktopに対象チャットを表示します。通常sendと異なり画面遷移があり、そのDesktopの持続する接続で対象を開いた後、pairedでidle・freshnessを再確認して一度だけ`turn/start`します。短命なsenderだけでresumeする経路は使いません。ユーザーからの再開委任がないidleタスクを監督目的だけで再開しません。
|
|
60
|
+
|
|
61
|
+
生成する監督・オーケストレーションコマンドはhost・transport・指定callerを保持します。停止済みの監督を別ID・unbound送信で迂回しません。承認・質問を自動回答するAPIは追加していません。画面への吹き出し表示や承認UI往復は、受付・保存・明示応答とは別の検証項目です。
|
|
62
|
+
|
|
63
|
+
## リモートの検証コマンド
|
|
64
|
+
|
|
65
|
+
既定の`command/exec`は接続先の設定された権限を使い、sandbox/permissionの上書きを送りません。接続先によってはこのAPIが`RPC_REJECTED`(-32600)になる場合があります。この拒否から自動で別経路を試しません。
|
|
66
|
+
|
|
67
|
+
公式`thread/shellCommand`は**ホストのfull accessでシェルを実行**するAPIです。その意味を選べるよう、次の明示指定に限定します。
|
|
68
|
+
|
|
69
|
+
```sh
|
|
70
|
+
codexteer --host remote-control:env_example --remote-transport paired resource run test-resource --owner codex --thread TEST-THREAD-ID --exec-backend thread-shell --shell powershell --cwd 'C:\Windows' --timeout-ms 10000 --include-output -- cmd.exe /d /c 'echo test'
|
|
71
|
+
codexteer --host remote-control:env_example --remote-transport paired checkpoint capture TEST-THREAD-ID tests --path 'C:\test\input.txt' --json
|
|
72
|
+
codexteer --host remote-control:env_example --remote-transport paired checkpoint run TEST-THREAD-ID CHECKPOINT-ID --exec-backend thread-shell --shell powershell --timeout-ms 10000 --include-output -- cmd.exe /d /c 'echo test'
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
`--shell powershell|posix`が必須で、相手のshellを推測しません。resourceでは対象`--thread`も必須、checkpointは自身のthreadを使います。idle以外では実行しません。argvは選んだshellの規則で引用し、任意のshell式として連結しません。`cmd.exe /c`などに式を渡す場合は、その式自体が明示したユーザーのコマンドです。
|
|
76
|
+
|
|
77
|
+
受け付けたという空応答だけでは成功としません。ランダムIDを含む実行項目、集約出力中の独立した1行に一度だけ現れる完了marker(stdoutのmarker後にstderrが続く場合も許容)、同じ項目のexitCodeを接続先の保存履歴で照合します。人間/モデルの「成功した」という発言では判定しません。full access・backend・実行先・終了コードを結果に表示します。制御側Macのプロセスを起動しません。
|
|
78
|
+
|
|
79
|
+
リモートのtimeout 0はthread-shellで公式APIの既定に合わせ1時間、汎用command/execで24時間として送ります(ローカルの無制限とは異なります)。明示timeoutは最大24時間です。thread-shellには対応する限定的な取消操作がないため、観測中断時に別のCodex作業を止める操作は行いません。完了を照合できない場合はunknown・再送禁止とし、resourceは期限までleaseを保持、checkpointは無効にします。承認/質問への代理回答やpermission overrideは行いません。
|
|
80
|
+
|
|
81
|
+
checkpointは接続先の`fs/watch`で途中変更を観測し、入力hashも前後照合します。symlinkのcanonical targetが分からない場合、巨大な入力、読取/監視失敗は有効な検証にしません。全体の厳密固定が必要なら隔離した入力を使います。監督ID付きthread-shellは、準備後の送信直前に同じsession・connection・transportとactive状態を再確認し、受付応答までpause/stopと同じロックを保持します。先にpause/stopが完了すれば送信しません。送信中のpause/stopは`RECORD_BUSY`で失敗し、停止済みという誤った応答を返しません。受付応答後はロックを解放するためpause/stopできますが、すでに受け付けたコマンドの取消・終了は保証しません。応答不明でも自動再送せず、観測とleaseの扱いは上記に従います。
|
|
82
|
+
|
|
83
|
+
## 公開検証情報
|
|
84
|
+
|
|
85
|
+
回帰テストは架空の識別子・ホスト・一時プロファイルを使います。実アカウントの登録情報、接続先一覧、チャットやプロジェクトの実ID、業務タイトル、実行ログは公開しません。テスト件数、pause競合のローカル再現、依存DB照合の要約は`remote-cli-validation.json`を参照してください。
|
|
86
|
+
|
|
87
|
+
`native`は観測専用、`control`は既存のactiveターンへの送信、`paired`は明示した管理操作を扱います。正式project IDとDesktop登録IDは別の識別子です。Desktopの承認・質問UIや表示は、RPC受付とは別に利用環境で確認してください。
|
|
88
|
+
|
|
89
|
+
監督のpause/resume/stopでも、生成された`--host`と`--remote-transport`を保持してください。登録と異なるtransportの制御は`SUPERVISOR_MISMATCH`で拒否し、保存状態を変更しません。
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Desktopプロジェクトにチャットを直接作成する
|
|
2
|
+
|
|
3
|
+
本書のcaller不要standalone方式はローカル向けです。`--host ... --remote-transport paired thread create`のリモート作成・正式project ID・caller要件・host照合は[リモートCLI操作](remote-operations.md)を参照してください。
|
|
4
|
+
|
|
5
|
+
`projects list`でローカルプロジェクトを確認し、`thread create`で初回指示付きのチャットを作成します。Desktopのnative RPCを使い、中継AIは実装から削除しました。「サンプル」のような複数フォルダ・共有フォルダを持つプロジェクトにも対応します。
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
codexteer projects list --json
|
|
9
|
+
codexteer thread create --project 'サンプル' \
|
|
10
|
+
--title '関連リポジトリの調査' '関連リポジトリを読み、結果を報告してください' --json
|
|
11
|
+
codexteer thread create --project 'サンプル' \
|
|
12
|
+
--dry-run '調査してください' --json
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
**プロジェクト名と初回指示だけで作成できます。`--context-thread`も`CODEX_THREAD_ID`も不要です。** 既存チャットがないプロジェクトでも使えます。既存チャットを探したり、別のAIを起動したりせず、作成したチャット自身の実在thread/turn IDでnative所属登録を呼びます。
|
|
16
|
+
|
|
17
|
+
`--context-thread`を明示した場合だけ、その既存チャットの保存済みターンを関連付けてnative `create_thread`を呼びます。idleでも使用できます。呼出し元への送信や追加のAIターンはありません。省略時は環境変数`CODEX_THREAD_ID`も参照せず、独立した作成になります。
|
|
18
|
+
|
|
19
|
+
プロジェクト名は完全一致で選び、同名なら`projects list`のIDを指定します。App Serverのcanonical IDとDesktop IDは読取り専用の対応表・native一覧で照合します。同名のChatGPTプロジェクトは選択しません。cwdだけで所属を推測しません。
|
|
20
|
+
|
|
21
|
+
## 作成と所属登録
|
|
22
|
+
|
|
23
|
+
標準経路は次のとおりです。
|
|
24
|
+
|
|
25
|
+
```text
|
|
26
|
+
terminal CLI → App Server thread/start(選択プロジェクトの全フォルダ)
|
|
27
|
+
→ Desktopの長期接続で購読 → turn/start(初回指示を1回だけ送信)
|
|
28
|
+
→ 共有App Serverのcommand/exec(追加のthread/turnを作らないAPI)
|
|
29
|
+
→ 固定の署名付きNode helper(read-only sandbox)
|
|
30
|
+
→ native tools/call move_thread_to_project(新規チャット自身)
|
|
31
|
+
→ Desktopの所属サービスで登録・表示更新
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
App Serverはnative操作用にcodexteer同梱の固定helperだけを起動します。native引数は所有者専用の一時JSONファイルで渡し、shellやargvへ埋め込みません。helperの許可操作はcatalog、list_projects、固定上限のlist_threads、引数制限付きread_thread、ローカルcreate_thread、新規チャット自身のmove_thread_to_project、正確なthread IDだけを受け付けるnavigate_to_codex_pageです。navigate_to_codex_pageはpairedの購読確立のため制御側Desktopで対象リモートチャットを開きます。呼出し前に一覧とnative readでthread/hostを照合します。read_threadは[リモート観測](remote-observation.md)用で、作成先の範囲を広げません。新規チャットの初回処理以外のモデル推論は発生しません。
|
|
35
|
+
|
|
36
|
+
作成前に、実行中サーバーと同じ同梱CLIからcommand/execとread-only sandboxのschemaを確認します。helperはDesktopのpeer認証を通過する署名付きApp Serverの正規の子として実行されます。Unix socketへ接続するためsandboxのnetworkAccessはtrueですが、helperにHTTP/DNSや外部サービスへの接続処理はありません。認証チェックや署名は変更しません。
|
|
37
|
+
|
|
38
|
+
codexteerは選択プロジェクトからプライマリcwdと全workspace rootsを解決します。名前とcanonical所属をAPIで保存し、Desktopの購読を確立します。初回指示の前に範囲と空の履歴を確認します。初回受付後、Desktopのnative所属サービスを呼び、スケジュールと同じ所属ストアと表示へ反映します。作成応答だけを成功の証拠にせず、Desktop native一覧のthread ID・local host・Desktop project ID、canonical projectId、全フォルダ、userMessageとして保存された初回指示の完全一致とタイトルを照合します。旧global stateファイルの`thread-project-assignments`だけを現在の所属とはみなしません。関連付けを明示したnative作成の場合は、`codex_delegation`の呼出し元IDと本文を含むliteral全文を比較します。XMLとして解釈しません。
|
|
39
|
+
|
|
40
|
+
受付が履歴の永続化より早い場合は、最大5秒間、読取りだけで反映を待ちます。作成や初回ターンを再実行しません。standaloneではcanonical所属をthread/startで指定し、購読前にmetadata APIでも保存します。関連付けを明示したnative作成では必要に応じてmetadata APIでcanonical所属を登録します。Desktopの状態ファイル・DBを直接書き換えません。
|
|
41
|
+
|
|
42
|
+
CLI終了後の承認はDesktopが担当します。codexteerは承認を自動回答しません。
|
|
43
|
+
|
|
44
|
+
## 設定と接続の制約
|
|
45
|
+
|
|
46
|
+
- `--title`、`--model`、`--thinking`を明示指定できます。standaloneで指定しないモデル・権限・承認はプロジェクトのApp Server設定に従います。他チャットの設定を継承しません。関連付けを明示した場合はDesktopのnative作成規則に従います。
|
|
47
|
+
- standaloneで`--title`を省略すると、初回指示の空白をまとめた先頭80文字を名前にします。タイトル生成用のAIは起動しません。対象サーバーでは未使用の空チャットをDesktopが購読するために、先に名前を保存して履歴を永続化する必要があります。
|
|
48
|
+
- cwd・permissions・sandbox・approval policy・approvals reviewerの上書きはnative schemaにないため、指定すると作成前に拒否します。
|
|
49
|
+
- `--via-thread`のAI中継経路は削除しました。自動fallbackもありません。
|
|
50
|
+
- 本書のstandalone方式は登録済みローカルプロジェクト向けです。worktreeやクラウド実行はこの方式では扱いません。Codex Cloudには`--connection cloud thread create --project <ENV_ID>`で同じ作成操作を使えます。環境一覧・監督を含む[Cloud操作](cloud.md)を参照してください。
|
|
51
|
+
- `--timeout-ms`は各RPCの待機期限で、既定120000、1000〜600000です。
|
|
52
|
+
- `--dry-run`は接続・プロジェクト・native schemaを読み、チャット・指示・receiptを作りません。
|
|
53
|
+
|
|
54
|
+
native RPCはDesktopのバージョンに依存します。未対応schema、unsafe socket、変更されたendpoint、認証拒否では止めます。
|
|
55
|
+
|
|
56
|
+
wrapperはnative endpointを所有者専用runtimeに公開します。公開されていない旧wrapperでは、選択済み・所有者検証済みPIDと同梱Node実行ファイルを確認し、継承環境のnative endpointだけを取得します。環境全体を出力・保存しません。
|
|
57
|
+
|
|
58
|
+
作成処理は既存の共有App Serverのcommand/execを使うため、専用control serviceの追加・native_tools_rpcフラグ・追加のDesktop再起動を必要としません。wrapper自身からnative接続する旧経路と、外部Nodeがnative workerを直接起動する経路は削除しました。通常の`codexteer`コマンドがそのまま使えます。
|
|
59
|
+
|
|
60
|
+
`--dry-run`でもschemaと要求JSONの一時ファイルを作成・削除します。チャット、追加AIターン、永続receiptは作りません。
|
|
61
|
+
|
|
62
|
+
## 配送不明と部分成功
|
|
63
|
+
|
|
64
|
+
作成前に`CODEX_HOME/codex-steer/creations/<attempt_id>.json`を所有者専用で保存します。standaloneのschemaは3、`creation_mode`は`standalone`、外部`context_thread_id`と`context_turn_id`はnullです。`native_context_*`は新規チャット自身の実在IDです。`native_call_id`は所属登録のcall IDでattempt IDと一致します。初回指示のSHA-256、所属ID、rootと名前を記録します。standaloneでは省略時の名前が初回指示の先頭80文字になるため、JSON出力・receiptの`title`にもこの文字列が含まれます。指示全文の専用フィールドやAI中継用のmessages journalは作りません。
|
|
65
|
+
|
|
66
|
+
- `creation_status: created`は既に子チャットが存在することを示します。照合失敗でも同じIDを調べ、作成を再実行しません。
|
|
67
|
+
- `sent: true` / `delivery_status: accepted`は初回ターンの受付です。後続の所属登録・照合やreceipt保存が失敗しても、この受付を取り消しません。standaloneでは所属登録前に指示が開始するため、所属登録失敗もcreated/acceptedとして保持します。
|
|
68
|
+
- `creation_status: unknown` / `dispatching`は未作成の証拠ではありません。接続断・時間切れ・不明応答で自動再送しません。
|
|
69
|
+
- `verification_status: verified`と所属・初回指示の個別verifiedが、照合完了の証拠です。
|
|
70
|
+
- 最終receipt保存だけ失敗した場合は`receipt_update_required`を返します。受付済み操作を再送可能な失敗に変換しません。
|
|
71
|
+
|
|
72
|
+
## 作成済みreceiptの再照合
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
codexteer thread verify <ATTEMPT-ID> --json
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
`creation_status: created`、`sent: true`、`delivery_status: accepted`を持つschema 2/3のreceiptを、同じCODEX_HOMEで再照合します。作成・購読・移動・metadata更新・指示送信は行いません。新しいthread/turnやAI処理も発生しません。更新するのはcodexteer自身のreceiptだけで、以前の照合状態・エラーと今回の全チェックを`verification_history`へ残します。unknown/dispatching/未受付をこのコマンドで成功扱いにはしません。
|
|
79
|
+
|
|
80
|
+
Desktop native所属、現在のプロジェクトID対応と全フォルダ、threadのcanonical所属・cwd・全フォルダ・指定タイトルに加え、保存された初回本文のSHA-256とUnicode文字数をreceiptと比較します。standaloneでは初回turn IDも一致が必要です。初回本文を再度渡す必要はありません。関連付けを明示した場合は既知の呼出し元を含むliteral envelopeから本文を取り出し、XMLとして解釈しません。
|
|
81
|
+
|
|
82
|
+
native一覧はDesktopの実schemaに従い最新50件と全pinnedです。対象が見つからない、別host/projectに所属する、重複して見える、schemaやruntimeが変わる場合は成功と推測しません。失敗の`verification_checks`を確認してください。`--timeout-ms`は各RPCの期限です。`--supervisor`と観測専用の`--connection desktop`からは実行できません。保存済みCLIは変更しないため、この機能には修正版CLIが必要です。
|
|
83
|
+
|
|
84
|
+
## 検証と互換性
|
|
85
|
+
|
|
86
|
+
作成とreceipt照合の回帰テストは、架空のプロジェクト・識別子・一時プロファイルを使います。未知の応答、別プロジェクト、追加root、初回指示の不一致では停止し、作成や配送を繰り返しません。実環境で使う前に、その環境のDesktopとCLIでschema・所属・購読・承認動作を確認してください。
|
|
87
|
+
|
|
88
|
+
この文書のプロジェクト名とIDは説明用の例です。実際のチャットID、プロジェクト一覧、ローカルパス、実行ログは公開資料に含めません。
|
|
89
|
+
|
|
90
|
+
DesktopのCloud環境構成で作成する場合は[Desktop Cloud](desktop-cloud.md)を参照してください。環境選択とローカルDesktopプロジェクト所属は別で、通常のリモートCloud環境への初回所属登録は未対応です。Desktop自身の実行環境に限定した署名付きAPIの対応範囲は、Desktop Cloudの説明を参照してください。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codexteer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"description": "Send steering messages to running Codex Desktop threads from a terminal.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
"check": "node scripts/check-syntax.mjs",
|
|
11
11
|
"test": "node --test",
|
|
12
12
|
"test:protocol": "node scripts/probe-app-server.mjs",
|
|
13
|
+
"test:remote-control": "node scripts/probe-remote-control.mjs",
|
|
13
14
|
"test:connection": "node scripts/probe-desktop-connection.mjs",
|
|
14
15
|
"benchmark:read": "node scripts/benchmark-read.mjs",
|
|
15
16
|
"test:package": "node scripts/test-package.mjs"
|
|
@@ -26,12 +27,22 @@
|
|
|
26
27
|
"url": "git+https://github.com/vinhphatfsg/codexteer.git"
|
|
27
28
|
},
|
|
28
29
|
"files": [
|
|
30
|
+
"docs/cloud.md",
|
|
31
|
+
"docs/desktop-cloud.md",
|
|
29
32
|
"bin/*.mjs",
|
|
30
33
|
"src/*.mjs",
|
|
31
34
|
"scripts/*.applescript",
|
|
32
35
|
"assets/send-pururu.wav",
|
|
36
|
+
"assets/remote-device-key.swift",
|
|
33
37
|
"CHANGELOG.md",
|
|
34
|
-
"docs/distribution.md"
|
|
38
|
+
"docs/distribution.md",
|
|
39
|
+
"docs/orchestration.md",
|
|
40
|
+
"docs/thread-create.md",
|
|
41
|
+
"docs/remote-observation.md",
|
|
42
|
+
"docs/remote-control.md",
|
|
43
|
+
"docs/remote-device-enrollment.md",
|
|
44
|
+
"docs/remote-operations.md",
|
|
45
|
+
"docs/remote-cli-validation.json"
|
|
35
46
|
],
|
|
36
47
|
"bundleDependencies": [
|
|
37
48
|
"ws"
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
-- Match known update labels inside the fixed Codex application process.
|
|
2
|
+
-- This does not authenticate a Sparkle window. Nonmatching controls and OS
|
|
3
|
+
-- permission prompts are left to the user.
|
|
4
|
+
on run argv
|
|
5
|
+
set operation to item 1 of argv
|
|
6
|
+
tell application "System Events"
|
|
7
|
+
if not (exists (first application process whose bundle identifier is "com.openai.codex")) then return "absent"
|
|
8
|
+
set appProcess to first application process whose bundle identifier is "com.openai.codex"
|
|
9
|
+
tell appProcess
|
|
10
|
+
if operation is "preflight" or operation is "check" then
|
|
11
|
+
repeat with entry in menu items of menu 1 of menu bar item 2 of menu bar 1
|
|
12
|
+
if name of entry is in {"Check for Updates…", "Check for Updates...", "更新を確認…", "更新を確認..."} then
|
|
13
|
+
if not enabled of entry then error "The update menu is disabled."
|
|
14
|
+
if operation is "check" then click entry
|
|
15
|
+
return "checking"
|
|
16
|
+
end if
|
|
17
|
+
end repeat
|
|
18
|
+
error "Supported Codex update menu not found (English/Japanese supported)."
|
|
19
|
+
end if
|
|
20
|
+
if operation is not "advance" and operation is not "inspect" then error "Unknown updater operation."
|
|
21
|
+
-- Do not retain System Events' name-based window references: multiple
|
|
22
|
+
-- Codex windows may have the same title. Resolve positional references
|
|
23
|
+
-- afresh, and tolerate only stale AX elements during this polling pass.
|
|
24
|
+
repeat with windowIndex from 1 to count of windows
|
|
25
|
+
try
|
|
26
|
+
repeat with buttonIndex from 1 to count of buttons of window windowIndex
|
|
27
|
+
try
|
|
28
|
+
set buttonName to name of button buttonIndex of window windowIndex
|
|
29
|
+
if buttonName is in {"Install and Relaunch", "インストールして再起動", "Install Update", "アップデートをインストール"} then
|
|
30
|
+
if enabled of button buttonIndex of window windowIndex then
|
|
31
|
+
if operation is "advance" then click button buttonIndex of window windowIndex
|
|
32
|
+
if buttonName is in {"Install and Relaunch", "インストールして再起動"} then return "installing"
|
|
33
|
+
return "downloading"
|
|
34
|
+
end if
|
|
35
|
+
end if
|
|
36
|
+
on error errorMessage number errorNumber
|
|
37
|
+
if errorNumber is not -1719 and errorNumber is not -1728 then error errorMessage number errorNumber
|
|
38
|
+
end try
|
|
39
|
+
end repeat
|
|
40
|
+
repeat with textIndex from 1 to count of static texts of window windowIndex
|
|
41
|
+
set messageText to value of static text textIndex of window windowIndex as text
|
|
42
|
+
if messageText contains "You’re up to date!" or messageText contains "currently the newest version available" or messageText contains "最新版を使用しています!" or messageText contains "は現在入手できる最新バージョンです" then return "up_to_date"
|
|
43
|
+
end repeat
|
|
44
|
+
on error errorMessage number errorNumber
|
|
45
|
+
if errorNumber is not -1719 and errorNumber is not -1728 then error errorMessage number errorNumber
|
|
46
|
+
end try
|
|
47
|
+
end repeat
|
|
48
|
+
end tell
|
|
49
|
+
end tell
|
|
50
|
+
return "waiting"
|
|
51
|
+
end run
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { once } from "node:events";
|
|
3
|
+
import { constants } from "node:os";
|
|
4
|
+
|
|
5
|
+
function launchFailure(agent, error) {
|
|
6
|
+
return Object.assign(new Error(`Could not start ${agent} (${error.code ?? "START_FAILED"}). Check that it is installed and executable on PATH.`), { code: error.code ?? "START_FAILED" });
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export async function launchAgent(agent, agentArgs, prompt) {
|
|
10
|
+
let child;
|
|
11
|
+
try {
|
|
12
|
+
// Keep forwarded argv intact. The agent's own terminator keeps its variadic
|
|
13
|
+
// options from consuming the generated prompt as another option value.
|
|
14
|
+
child = spawn(agent, [...agentArgs, "--", prompt], { shell: false, stdio: "inherit" });
|
|
15
|
+
} catch (error) { throw launchFailure(agent, error); }
|
|
16
|
+
|
|
17
|
+
const signals = ["SIGINT", "SIGTERM", "SIGHUP"];
|
|
18
|
+
const handlers = signals.map(signal => () => {
|
|
19
|
+
if (child.exitCode === null && child.signalCode === null) child.kill(signal);
|
|
20
|
+
});
|
|
21
|
+
signals.forEach((signal, index) => process.on(signal, handlers[index]));
|
|
22
|
+
try {
|
|
23
|
+
const [code, signal] = await once(child, "exit");
|
|
24
|
+
return code ?? (signal ? 128 + (constants.signals[signal] ?? 0) : 1);
|
|
25
|
+
} catch (error) { throw launchFailure(agent, error); }
|
|
26
|
+
finally { signals.forEach((signal, index) => process.off(signal, handlers[index])); }
|
|
27
|
+
}
|
package/src/app-server.mjs
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { isDesktopCloudTask } from "./desktop-cloud-id.mjs";
|
|
2
|
+
import { sendDesktopCloudInput } from "./desktop-cloud.mjs";
|
|
1
3
|
import { mkdir, rm, readFile, writeFile } from "node:fs/promises";
|
|
2
4
|
import { randomUUID } from "node:crypto";
|
|
3
5
|
import path from "node:path";
|
|
@@ -7,9 +9,14 @@ import { normalizeThreadId } from "./thread-id.mjs";
|
|
|
7
9
|
import { ensureDesktopSubscription } from "./subscription.mjs";
|
|
8
10
|
import { assertRuntimeOperation, compatibleClient } from "./compatibility.mjs";
|
|
9
11
|
import { turnMetadata } from "./paged-read.mjs";
|
|
12
|
+
import { targetHost, remoteReadOnly, remoteTransport, hostNamespace, hostFields, isControlTransport } from "./target.mjs";
|
|
13
|
+
import { remoteControlConnection, remoteNewTurnUnsupported } from "./remote-control.mjs";
|
|
14
|
+
import { ensureRemoteDesktopSubscription } from "./remote-management.mjs";
|
|
15
|
+
import { isCloudTask } from "./task-id.mjs";
|
|
16
|
+
import { sendCloudInput } from "./cloud-send.mjs";
|
|
10
17
|
|
|
11
18
|
export class SendFailure extends Error {
|
|
12
|
-
constructor(message, { code = "NOT_SENT", uncertain = false, threadId, rpcCode, capability, operation } = {}) {
|
|
19
|
+
constructor(message, { code = "NOT_SENT", uncertain = false, threadId, rpcCode, capability, operation, remoteControlAuth } = {}) {
|
|
13
20
|
super(message);
|
|
14
21
|
this.code = code;
|
|
15
22
|
this.delivery_status = uncertain ? "unknown" : "not_sent";
|
|
@@ -18,12 +25,22 @@ export class SendFailure extends Error {
|
|
|
18
25
|
this.rpc_code = rpcCode;
|
|
19
26
|
this.capability = capability;
|
|
20
27
|
this.operation = operation;
|
|
28
|
+
this.remote_control_auth = remoteControlAuth;
|
|
21
29
|
}
|
|
22
30
|
}
|
|
23
31
|
|
|
24
32
|
function activeTurn(thread) {
|
|
33
|
+
if (thread.status?.type !== "active") return null;
|
|
34
|
+
if (thread.historyMode === "paginated") {
|
|
35
|
+
// readSendState obtains every page in ascending order. Codex may also
|
|
36
|
+
// report orphaned historical turns as inProgress while a new turn runs.
|
|
37
|
+
// The latest turn is only a candidate: turn/steer.expectedTurnId must
|
|
38
|
+
// still match the actual runtime turn. Never fall back to an older ID.
|
|
39
|
+
const latest = thread.turns?.at(-1);
|
|
40
|
+
return latest?.status === "inProgress" ? latest : null;
|
|
41
|
+
}
|
|
25
42
|
const turns = thread.turns?.filter(turn => turn.status === "inProgress") ?? [];
|
|
26
|
-
return
|
|
43
|
+
return turns.length === 1 ? turns[0] : null;
|
|
27
44
|
}
|
|
28
45
|
|
|
29
46
|
function verifyThread(thread, threadId) {
|
|
@@ -57,7 +74,7 @@ export async function sendOnClient(client, threadId, message, { newTurn = false,
|
|
|
57
74
|
const input = [{ type: "text", text: message }];
|
|
58
75
|
if (!newTurn) {
|
|
59
76
|
const turn = activeTurn(thread);
|
|
60
|
-
if (!turn) throw new Error("The task has no
|
|
77
|
+
if (!turn) throw new Error("The task has no eligible active turn. Read it again before sending; use --new-turn only for an idle task.");
|
|
61
78
|
await beforeSend?.(thread, client);
|
|
62
79
|
// Desktop uses clientId on the resulting userMessage to render external
|
|
63
80
|
// steering as a user bubble instead of just an anonymous "steered" marker.
|
|
@@ -84,10 +101,17 @@ export async function sendOnClient(client, threadId, message, { newTurn = false,
|
|
|
84
101
|
return { turn_id: accepted.turn.id, client_message_id: clientUserMessageId };
|
|
85
102
|
}
|
|
86
103
|
|
|
87
|
-
export async function sendAppServerMessage(threadInput, message, { dryRun = false, newTurn = false, clientMessageId = randomUUID(), beforeSend, connect
|
|
104
|
+
export async function sendAppServerMessage(threadInput, message, { dryRun = false, newTurn = false, clientMessageId = randomUUID(), beforeSend, connect, discover, subscribe = ensureDesktopSubscription, inspectControl = verifyControlEndpoint } = {}) {
|
|
105
|
+
if (isDesktopCloudTask(threadInput)) return sendDesktopCloudInput(threadInput, message, { dryRun, newTurn, clientMessageId, beforeSend });
|
|
106
|
+
if (isCloudTask(threadInput)) return sendCloudInput(threadInput, message, { dryRun, newTurn, clientMessageId, beforeSend });
|
|
107
|
+
const remote = targetHost() !== "local";
|
|
108
|
+
if (remote && !isControlTransport()) throw remoteReadOnly();
|
|
109
|
+
if (remote && newTurn && remoteTransport() !== "paired") throw remoteNewTurnUnsupported();
|
|
110
|
+
const route = remote ? remoteControlConnection() : {};
|
|
111
|
+
connect ??= route.connect ?? RpcClient.connect; discover ??= route.discover ?? discoverRuntime;
|
|
88
112
|
const threadId = normalizeThreadId(threadInput);
|
|
89
113
|
if (typeof message !== "string" || !message.trim()) throw new Error("Message must not be empty.");
|
|
90
|
-
const plan = { thread_id: threadId, backend: "app-server", delivery_action: newTurn ? "new-turn" : "steer", message_characters: [...message].length, dry_run: dryRun };
|
|
114
|
+
const plan = { thread_id: threadId, ...hostFields(), backend: "app-server", delivery_action: newTurn ? "new-turn" : "steer", message_characters: [...message].length, dry_run: dryRun };
|
|
91
115
|
if (dryRun) return { ...plan, sent: false, delivery_status: "not_sent" };
|
|
92
116
|
let client;
|
|
93
117
|
let lock;
|
|
@@ -97,8 +121,8 @@ export async function sendAppServerMessage(threadInput, message, { dryRun = fals
|
|
|
97
121
|
try {
|
|
98
122
|
const { paths, state } = await discover();
|
|
99
123
|
assertRuntimeOperation(state, operation);
|
|
100
|
-
if (newTurn) await inspectControl(paths);
|
|
101
|
-
const candidate = path.join(paths.lease, `send-${threadId}`);
|
|
124
|
+
if (newTurn && !remote) await inspectControl(paths);
|
|
125
|
+
const candidate = path.join(paths.lease, `send-${remote ? `${hostNamespace()}-` : ""}${threadId}`);
|
|
102
126
|
try { await mkdir(candidate, { mode: 0o700 }); } catch (error) {
|
|
103
127
|
if (error.code === "EEXIST") throw new Error("Another send is in progress for this task, or a previous sender exited unexpectedly. Restart the shared Desktop after checking delivery before retrying.");
|
|
104
128
|
throw error;
|
|
@@ -111,18 +135,18 @@ export async function sendAppServerMessage(threadInput, message, { dryRun = fals
|
|
|
111
135
|
if (current.state.instance !== state.instance || current.paths.socket !== paths.socket || (newTurn && current.paths.control !== paths.control)) {
|
|
112
136
|
throw Object.assign(new Error("Runtime changed before delivery. Read the target again before sending."), { code: "RUNTIME_CHANGED" });
|
|
113
137
|
}
|
|
114
|
-
if (newTurn) await inspectControl(current.paths);
|
|
138
|
+
if (newTurn && !remote) await inspectControl(current.paths);
|
|
115
139
|
};
|
|
116
140
|
const receipt = await sendOnClient(client, threadId, message, { newTurn, clientMessageId,
|
|
117
141
|
beforeSend: async (...args) => { await beforeSend?.(...args); await checkRuntime(); },
|
|
118
|
-
subscribe: async id => { await checkRuntime(); return subscribe(paths.control, id); },
|
|
142
|
+
subscribe: async id => { await checkRuntime(); return remote ? ensureRemoteDesktopSubscription(id) : subscribe(paths.control, id); },
|
|
119
143
|
});
|
|
120
144
|
result = { ...plan, ...receipt, sent: true, delivery_status: "accepted" };
|
|
121
145
|
return result;
|
|
122
146
|
} catch (error) {
|
|
123
147
|
throw new SendFailure(error.uncertain
|
|
124
148
|
? "Delivery is unknown because App Server did not confirm receipt. Check the target task before retrying."
|
|
125
|
-
: error.message, { code: error.code ?? "NOT_SENT", uncertain: error.uncertain, threadId, rpcCode: error.rpcCode, capability: error.capability, operation: error.operation });
|
|
149
|
+
: error.message, { code: error.code ?? "NOT_SENT", uncertain: error.uncertain, threadId, rpcCode: error.rpcCode, capability: error.capability, operation: error.operation, remoteControlAuth: error.remote_control_auth });
|
|
126
150
|
} finally {
|
|
127
151
|
// Cleanup failure must never turn an acknowledged send into a retryable
|
|
128
152
|
// error. Keep its acceptance receipt and leave any stale lock fail-closed.
|