aiterm-mcp 0.38.1 → 0.39.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 CHANGED
@@ -7,6 +7,33 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.39.0] - 2026-09-26
11
+
12
+ ### 追加
13
+
14
+ - `agent_launch`と既存のPTY/agent操作toolに`remote`を追加する。SSHで入った別端末のAitermへ同じtoolを中継し、端末へ入る操作と現地のagent起動を1回で行う。接続先・鍵・パスフレーズは呼び出しごとに受け取り、保存・管理しない。Codex/Claude Code/Cursor親への回答自動配送は別端末の子にも働き、記録は旧版が読まない`remote-`付きの保存場所へ分ける。
15
+ - `pty_read(agent_transcript:true)`が`raw:true`を受け付け、削減前の回答本文を返す。
16
+ - `agent_steer`がClaude CodeとCursorの実行中turnにも差し込めるようにする。これまではCodex/Grok専用で、BellTeamのように処理中のBotへ待ち行列のメッセージを差し込む呼び出し側では、Claude Code/CursorのBot宛てがエラーになっていた。
17
+ - `aiterm-update`を追加する。この端末と`--host`で指定したSSH接続先のAitermを同じ版へ入れ替え、各端末で新しい版の`aiterm-setup --json`を実行し直す。版の解決は呼んだ端末で1回だけ行い、`aiterm-update`を持たない旧版の端末にはnpmで入れてから渡す。更新前から動いている`aiterm-mcp`の数を`running_servers`で返す。
18
+
19
+ ### 修正
20
+
21
+ - Claude Code 2.1.282の番号付きログイン方式選択を、初回案内の未完了として見分ける。ログイン済みでも初回案内が済んでいないと起動のたびにこの画面で止まるため、ready timeoutを待たずに`vendor_onboarding_required`と直し方を返す。初回テーマ選択もtimeoutを待たずに確定する。
22
+ - Cursor Agentが利用上限に達した画面を見分ける。Cursorは上限時にtranscriptへ完了を書かず入力欄も戻さないため、Aitermは完了を待ち続けていた。現在の画面に上限の説明が出ていれば`rate_limited`(aiterm-waitはexit 6)として上限の説明を返し、`pty_observe`は`blocked`/`rate_limited`を返す。後ろに入力欄や実行中表示がある古い上限表示は数えない。
23
+ - Codex CLI 0.157の利用上限接近の画面(「Approaching rate limits」)を見分ける。0.157では切替先modelが`gpt-6-luna`になり、画面下の案内も`enter select · esc back`へ変わったため、Aitermはこの画面を見分けられず、次の送信が入力待ちにならないまま止まっていた。切替先modelを固定せず、新しい案内行もmodalのfooterとして扱い、従来どおり現在のmodelのまま続ける「2」だけを選ぶ。
24
+ - Claude Codeの起動時の確認画面(フォルダの信頼、Bypass Permissionsの確認)で、選択が「Yes」の行へ移ったのを画面で確かめてからEnterを送る。どちらも既定の選択が「No, exit」なので、起動直後のCLIが「↓」を取り落とすとEnterでCLIが終了していた(2.1.283で再現)。選択が動かなければ「↓」を一度だけ送り直し、それでも動かなければEnterを送らず`startup_dialog`で止める。
25
+ - `agent_steer`でGrokへ差し込んだ文が現在のturnに入らず、turnの完了後に別turnとして動いていた。Aitermは最初のturnの終わりを完了として届けるため、差し込んだ指示への回答は行き場を失っていた。Grokが待ち行列へ入れたのを確かめてから標準の「send now」で現在turnへ移し、この時に書かれる`turn_ended`(`cancelled`、`trigger=send_now`)は完了と数えない。Cursorも同じく「follow-ups」枠へ入った文を「enter steer」で現在turnへ移す。
26
+ - `agent_steer`の結果の型がCodex/Grokのvendorとharnessだけを許していたため、Claude Code/Cursorへの差し込みは本文が届いても呼び出し側にはエラーとして返っていた。
27
+ - WindowsのCursor Agentを、Git BashではなくPowerShell 7のpaneから起動する。Git Bash配下で起動したCursorはhookへ渡すJSONの先頭にUTF-8 BOMを付け、BOMを読めない利用者のhook(Throughline、caveat等)が送信を止めていた。この時Cursorは`prompt_history.json`だけを残して会話を作らず、画面にも理由を出さないため、Aitermは完了を待ち続けていた。
28
+ - Codex DesktopのSteerが、Desktopの更新後に動かなくなっていた。setup時に保存した同梱Codex CLIの場所(Windowsは版ごとのcache directory、macOSはbundle内)がDesktopの更新で無くなり、回答の配送も親hookも`CODEX_RECEIVER_TRANSPORT_FAILED`(ENOENT)で失敗していた。保存した場所が無ければ使う時点で公式Desktopから探し直して設定を更新し、探せなければ`CODEX_DESKTOP_BINARY_MOVED`と直し方を返す。macOSでは`ChatGPT.app/Contents/Resources/codex-cli/CodexCLI.app/Contents/MacOS/codex`の新しい同梱場所も見つける。
29
+
30
+ ## [0.38.2] - 2026-09-25
31
+
32
+ ### 修正
33
+
34
+ - Claude Code初回起動時のテーマ選択を、画面で選択済みの項目を確定して通過する。続くログイン方式の選択は入力欄と誤認せず、公式CLIでの初回設定が必要と明示する。
35
+ - WindowsのCursor親受け口試験で、Windows専用の起動引数を誤って`null`と期待していた判定を修正する。
36
+
10
37
  ## [0.38.1] - 2026-09-22
11
38
 
12
39
  ### 修正
@@ -1744,7 +1771,9 @@ prototype (preserved under `prototype/python/` as the porting source and referen
1744
1771
  `ubuntu-latest` for Node 18/20/22, publishing to npm on `v*` tags with
1745
1772
  provenance.
1746
1773
 
1747
- [Unreleased]: https://github.com/kitepon/aiterm-mcp/compare/v0.38.1...HEAD
1774
+ [Unreleased]: https://github.com/kitepon/aiterm-mcp/compare/v0.39.0...HEAD
1775
+ [0.39.0]: https://github.com/kitepon/aiterm-mcp/compare/v0.38.2...v0.39.0
1776
+ [0.38.2]: https://github.com/kitepon/aiterm-mcp/compare/v0.38.1...v0.38.2
1748
1777
  [0.38.1]: https://github.com/kitepon/aiterm-mcp/compare/v0.38.0...v0.38.1
1749
1778
  [0.38.0]: https://github.com/kitepon/aiterm-mcp/compare/v0.37.10...v0.38.0
1750
1779
  [0.37.10]: https://github.com/kitepon/aiterm-mcp/compare/v0.37.9...v0.37.10
package/README.ja.md CHANGED
@@ -74,7 +74,7 @@ hookはAiterm自身の配送記録と本文が一致する回答だけを取り
74
74
 
75
75
  解除・hook未対応の旧版への巻き戻し前は`aiterm-setup --codex-steer disable`を実行してCodexを再起動してください。
76
76
  macOS・Windowsの公式Codex Desktopと、公式キュー・hookに対応する同梱CLIを対象にします。
77
- WindowsのDesktop更新後はsetupを再実行してください。LinuxのSteer付き導入は理由付き`unsupported`を返します。
77
+ Desktopの更新で同梱Codex CLIの場所が変わった時は、Aitermが使う時点で探し直して設定を更新します。探せない時は`CODEX_DESKTOP_BINARY_MOVED`を返すので、Desktopを起動してからsetupを再実行してください。LinuxのSteer付き導入は理由付き`unsupported`を返します。
78
78
  Aiterm単品の公式キュー配送は従来どおり利用できます。
79
79
 
80
80
  cloneもビルドも不要。どのクライアントでも公開パッケージを次のコマンドで起動する:
@@ -143,7 +143,7 @@ diagnostics、recovery、update、releaseを所有します。このREADMEと[
143
143
 
144
144
  **言葉でなく実測で:** 記録済み203テストのベンチマークでは、`pty_read` はコンテキストに載るトークンを生ログの **約 7.1 分の 1** に減らす。しかも pass/fail の判定は畳んでも残る。→ [組み込みシェルツールとの使い分け](#組み込みシェルツールとの使い分け)
145
145
 
146
- 18ツール: 7つのPTYツール、正規のagent起動入口`agent_launch`、実行中のCodex/Grokを誘導する`agent_steer`、移行用の旧4alias、`agent_configure`、`agent_approval`、`claude_turn`、`claude_approval`、`diagnostics`。backendはPOSIXのtmux/Windows nativeのpsmuxなので、MCPサーバやAIクライアントが再起動してもsessionは生き残る。
146
+ 18ツール: 7つのPTYツール、正規のagent起動入口`agent_launch`、実行中のClaude/Codex/Grok/Cursorを誘導する`agent_steer`、移行用の旧4alias、`agent_configure`、`agent_approval`、`claude_turn`、`claude_approval`、`diagnostics`。backendはPOSIXのtmux/Windows nativeのpsmuxなので、MCPサーバやAIクライアントが再起動してもsessionは生き残る。
147
147
 
148
148
  **v0.28.0では実行基盤harnessとmodelを分離した。** harnessはagent loop・認証・hook・session・transcriptを所有し、modelはその上で選ぶ。Cursor Agent CLIでGPT/Claude/Grokを選んでも完了契約はCursor方式のまま。Composerは別harnessではなく、`harness:"grok-cli", model:"grok-composer-2.5-fast"`で表す。旧4起動ツールは同じ実装へ流れる互換alias。
149
149
 
@@ -202,12 +202,24 @@ runtime-error store は canonical dotagents config の `collection.enabled: true
202
202
  場合だけ収集し、既定OFF、network送信は行いません。tag起点CIのnpm provenance(OIDC Trusted
203
203
  Publishing)で公開し、GitHub Release が Official MCP Registry を再登録します。
204
204
 
205
- **状態:** 開発継続中 · 現行公開版 **v0.38.1** · 動作対象は Linux · WSL2 · macOS · Windows ネイティブ · MIT · [変更履歴](CHANGELOG.md)。
205
+ **状態:** 開発継続中 · 現行公開版 **v0.39.0** · 動作対象は Linux · WSL2 · macOS · Windows ネイティブ · MIT · [変更履歴](CHANGELOG.md)。
206
206
 
207
207
  ### 更新と巻き戻し
208
208
 
209
- npm packageが単独配布の正本であり、dotagentsは介在しません。global installは
210
- `npm install -g aiterm-mcp@latest`で更新し、`aiterm-setup --json`を再実行します。巻き戻す時は
209
+ npm packageが単独配布の正本であり、dotagentsは介在しません。global installは`aiterm-update`で更新します。
210
+ npmで同じ導入先を指定の版へ入れ替え、新しい版の`aiterm-setup --json`で登録と実動作を確かめ直します。
211
+
212
+ ```bash
213
+ aiterm-update # この端末をlatestへ
214
+ aiterm-update --host rabbit --host win-test # この端末とSSH接続先を同じ版へ
215
+ aiterm-update --version 0.39.0 --check # 入れ替えずに現在の版と更新先を確かめる
216
+ ```
217
+
218
+ `--host`は`~/.ssh/config`の接続名かホスト名で、接続先は保存しません。版の解決は呼んだ端末で1回だけ行い、
219
+ 全端末を同じ版へ揃えます。更新機能より古い版の端末では、npmで入れてから新しい`aiterm-update`へ渡します。
220
+ 導入先へ書けない時は`permission_required`と管理者権限での手順を返します。更新前から動いている`aiterm-mcp`は
221
+ MCP clientが起動し直すまで旧版のcodeで動き(`running_servers`)、tmux/psmuxのsessionは残ります。
222
+ `aiterm-update`を持たない版では`npm install -g aiterm-mcp@latest`の後に`aiterm-setup --json`を再実行します。巻き戻す時は
211
223
  `npm install -g "aiterm-mcp@<known-good-version>"`のように既知の正常versionを明示します。setupを持つ版では同じ入口を再実行し、MCP clientを再起動します。
212
224
  `npx`設定では`aiterm-mcp@latest`へ変えると更新でき、`aiterm-mcp@<version>`へ変えると固定・巻き戻し
213
225
  できます。downgrade前に[変更履歴](CHANGELOG.md)でstate/schema互換を確認してください。maintainer向けの
@@ -486,6 +498,8 @@ aiterm は同じ核心の洞察——端末を出会いの場にする——を
486
498
 
487
499
  `agent_launch({ harness, cwd, trust_project: true })`はpromptなしでも既知のworkspace・project hooks・MCP初期同意を
488
500
  進め、入力受付とharness生存を確認して`startup.status="ready"`を返す。指定なしのpromptなし起動は`not_checked`。
501
+ Claude Code初回起動の文字表示テーマ選択では、画面で選択済みの項目を確定して起動を続ける。
502
+ 続いてログイン方式の選択が出た場合は`vendor_onboarding_required`を返す。公式の対話型Claude Code CLIで初回設定を完了する。
489
503
  WindowsのCodexもhook確認を認識し、npm shim経由の起動を一つのharnessとして識別する。
490
504
  初手の`initial_prompt.status`は`not_requested`/`not_sent`/`submitted_unconfirmed`/`started`を区別する。
491
505
  未送信・未確認の失敗もsession付きstructuredContentを保持する。未確認のpromptを再送せず、返ったcursorで観測する。
@@ -506,7 +520,7 @@ Claudeの相関済み承認は既存の`claude_approval`を使う。
506
520
  | `pty_observe` | pane/harnessの生存、native process identity、状態と活動 | `session_id`, `cursor?` |
507
521
  | `agent_launch` | harnessとmodelを別軸で選ぶ正規agent起動入口 | `harness`, `prompt?`, `model?`, `reasoning_effort?`, `cwd?`, `write_scope?`, `trust_project?`, `env_vars?`, `throughline_source_session?`, `throughline_supplement_file?` |
508
522
  | `agent_approval` | Codexの現在の承認を検査し、単発許可・拒否を送る | `action`, `session_id`, `approval_choice?`, `observed_prompt_digest?` |
509
- | `agent_steer` | 実行中のCodex/Grok turnへtextを差し込む。idleなら送信せず`idle`を返す | `session_id`, `text` |
523
+ | `agent_steer` | 実行中のClaude/Codex/Grok/Cursor turnへ、各harness標準の操作でtextを差し込む。差し込み後の作業の完了は1回だけ届く。idleなら送信せず`idle`を返す。Grokが待ち行列へ入れない時とCursorの入力欄に残った時は`steered`を返さず失敗する | `session_id`, `text` |
510
524
  | `claude_agent` / `codex_agent` / `grok_agent` / `composer_agent` | deprecated互換alias | 旧launcher引数 |
511
525
  | `agent_configure` | 起動中のClaude/Codex/Grok/Composer/Cursorを再起動せずmodel/effort変更 | `session_id`, `model?`, `reasoning_effort?` |
512
526
  | `claude_turn` | 相関済みClaude operationをdispatch(issue)または回収(recover) | `action`, `session_id`, `operation_id`, `text?` |
@@ -569,6 +583,21 @@ Cursor親(`clientInfo.name`が`cursor-vscode`)も同じ完了観測と本文
569
583
  Claudeをリンク経由の`cwd`から起動した場合も、実体パスに対応する会話記録を参照する。
570
584
 
571
585
 
586
+ ### 別端末のagentを1回で起動する(`remote`)
587
+
588
+ `agent_launch`に`remote`を付けると、SSHで入った別端末のAitermで同じ起動を行う。端末へ入る操作と現地のagentを起動する操作が1回で済み、親から見た完了の届き方はこの端末の子と同じになる。Linux・macOS・Windowsの端末へ同じ依頼を並行して投げる用途を想定している。
589
+
590
+ ```jsonc
591
+ agent_launch({ "harness": "codex-cli", "remote": { "host": "rabbit" }, "cwd": "/home/kite/project", "prompt": "..." })
592
+ ```
593
+
594
+ - `remote`は`host`、`user`、`port`、`identity_file`、`passphrase`または`passphrase_env`、`ssh_options`(`Key=Value`)を受け取る。`host`だけなら`~/.ssh/config`の接続名として使う。Aitermは接続先を保存・管理しない。どこへどの鍵で入るかは呼び出し側が持つ。
595
+ - 平文の`passphrase`は呼び出したAIの会話記録に残る。ssh-agentか`passphrase_env`(環境変数名)を推奨する。受け取ったパスフレーズはMCP processのメモリにだけ置き、`SSH_ASKPASS`でsshへ渡す。状態ファイルやログには書かない。
596
+ - 現地には`aiterm-mcp`、tmux(Windowsはpsmux)、使うharnessのCLIが要る。接続先のshell(POSIX系、PowerShell、cmd)は最初の接続で見分ける。POSIX系ではログインshellからPATHだけを受け取るので、`~/.local/bin`、Homebrew、nvmなどに置いたCLIも使える。WindowsはユーザーのPATHのまま`aiterm-mcp`を起動する。
597
+ - 以後の`pty_send`、`pty_read`、`pty_close`、`pty_observe`、`agent_steer`などにも同じ`remote`を付ける。session名は現地のもので、この端末の同名sessionとは別に扱う。
598
+ - Codex/Claude Code/Cursor親には、この端末の子と同じく回答本文が自動で届く。完了は`ssh <host> aiterm-wait`で観測し、SSHが切れても同じcursorでつなぎ直す。それ以外の親には、sshを使う`wait_process`を返す。
599
+ - 同じ接続先への呼び出しはControlMasterで1本のSSHに相乗りする。`remote`付きの`image`添付と`claude_turn issue`は未対応。
600
+
572
601
  ### トークン削減
573
602
 
574
603
  - `pty_read` は既定で制御文字除去・連続重複圧縮・head+tail 折りたたみ(+復元ヒント・メタ併記)をかける。
package/README.md CHANGED
@@ -74,7 +74,7 @@ hookはAiterm自身の配送記録と本文が一致する回答だけを取り
74
74
 
75
75
  解除・hook未対応の旧版への巻き戻し前は`aiterm-setup --codex-steer disable`を実行してCodexを再起動してください。
76
76
  macOS・Windowsの公式Codex Desktopと、公式キュー・hookに対応する同梱CLIを対象にします。
77
- WindowsのDesktop更新後はsetupを再実行してください。LinuxのSteer付き導入は理由付き`unsupported`を返します。
77
+ When a Desktop update moves the bundled Codex CLI, Aiterm finds it again at use time and updates its configuration. If it cannot, it returns `CODEX_DESKTOP_BINARY_MOVED`; start Desktop and rerun setup.LinuxのSteer付き導入は理由付き`unsupported`を返します。
78
78
  Aiterm単品の公式キュー配送は従来どおり利用できます。
79
79
 
80
80
  No clone or build is required. Each client launches the published package with:
@@ -145,7 +145,7 @@ Aiterm and is not a runtime dependency.
145
145
 
146
146
  **Measured, not claimed:** in the recorded 203-test benchmark, a `pty_read` puts **~7.1× fewer tokens** in your context than the raw log — and the pass/fail verdict survives the fold. → [When to reach for it vs. the built-in shell](#when-to-reach-for-it-vs-the-built-in-shell)
147
147
 
148
- Eighteen tools: seven **PTY tools** — `pty_open` / `pty_send` / `pty_read` / `pty_key` / `pty_close` / `pty_list` / `pty_observe` — to open, drive, read, and observe one persistent terminal; one canonical **agent launcher**, `agent_launch`, which selects `claude-code`, `codex-cli`, `grok-cli`, or `cursor-cli` as the execution harness; `agent_steer` for an active Codex or Grok turn; four deprecated launcher aliases kept for migration; `agent_configure`; `agent_approval`; `claude_turn`; `claude_approval`; and `diagnostics`. The backend is **tmux on POSIX and psmux on native Windows**, so sessions survive even if the MCP server or the AI client restarts.
148
+ Eighteen tools: seven **PTY tools** — `pty_open` / `pty_send` / `pty_read` / `pty_key` / `pty_close` / `pty_list` / `pty_observe` — to open, drive, read, and observe one persistent terminal; one canonical **agent launcher**, `agent_launch`, which selects `claude-code`, `codex-cli`, `grok-cli`, or `cursor-cli` as the execution harness; `agent_steer` for an active Claude, Codex, Grok, or Cursor turn; four deprecated launcher aliases kept for migration; `agent_configure`; `agent_approval`; `claude_turn`; `claude_approval`; and `diagnostics`. The backend is **tmux on POSIX and psmux on native Windows**, so sessions survive even if the MCP server or the AI client restarts.
149
149
 
150
150
  **v0.28.0 separates the execution harness from the model.** The harness owns the agent loop, authentication, hooks, session, and transcript; `model` is what that harness runs. Cursor Agent CLI can therefore select GPT, Claude, or Grok without changing the completion contract from Cursor hooks to another harness's. Grok Composer is a Grok CLI model preset, not another harness: use `harness: "grok-cli", model: "grok-composer-2.5-fast"`. The old four launcher tools are thin compatibility aliases over the same implementation.
151
151
 
@@ -216,12 +216,25 @@ collection is off by default and performs no network I/O. It ships via
216
216
  tag-triggered CI with npm provenance (OIDC Trusted Publishing); the GitHub
217
217
  Release re-registers the Official MCP Registry entry.
218
218
 
219
- **Status:** actively maintained · current public release **v0.38.1** · runs on Linux · WSL2 · macOS · native Windows (tmux on POSIX, the tmux-CLI-compatible [psmux](https://github.com/psmux/psmux) on native Windows — no WSL required) · MIT · see the [CHANGELOG](CHANGELOG.md).
219
+ **Status:** actively maintained · current public release **v0.39.0** · runs on Linux · WSL2 · macOS · native Windows (tmux on POSIX, the tmux-CLI-compatible [psmux](https://github.com/psmux/psmux) on native Windows — no WSL required) · MIT · see the [CHANGELOG](CHANGELOG.md).
220
220
 
221
221
  ### Update and rollback
222
222
 
223
223
  The npm package is the standalone distribution; dotagents is not involved. For a global install,
224
- update with `npm install -g aiterm-mcp@latest` and `aiterm-setup --json`. To roll back, install a known-good immutable version,
224
+ update with `aiterm-update`. It reinstalls the requested version into the same npm prefix, then reruns the new version's
225
+ `aiterm-setup --json` to re-register and re-verify.
226
+
227
+ ```bash
228
+ aiterm-update # this machine to latest
229
+ aiterm-update --host rabbit --host win-test # this machine and SSH hosts to the same version
230
+ aiterm-update --version 0.39.0 --check # report current and target versions without changing anything
231
+ ```
232
+
233
+ `--host` takes an `~/.ssh/config` alias or host name; hosts are not stored. The version is resolved once on the calling
234
+ machine so every host lands on the same version. Hosts older than `aiterm-update` get it through npm first. When the
235
+ npm prefix is not writable, the result is `permission_required` with the command to run as an administrator. `aiterm-mcp`
236
+ servers that were already running keep the old code until their MCP client restarts (`running_servers`); tmux/psmux
237
+ sessions survive. Versions without `aiterm-update` use `npm install -g aiterm-mcp@latest` and `aiterm-setup --json`. To roll back, install a known-good immutable version,
225
238
  for example `npm install -g "aiterm-mcp@<known-good-version>"`, then restart the MCP client. setupを持つ版では再起動前に`aiterm-setup --json`を再実行する。For an `npx` configuration,
226
239
  use `aiterm-mcp@latest` to update or replace it with `aiterm-mcp@<version>` to pin or roll back.
227
240
  Check the [CHANGELOG](CHANGELOG.md) for state/schema compatibility before downgrading. Maintainer
@@ -517,6 +530,8 @@ displayed token count or null. Callers do not need raw argv or pane-text parsing
517
530
 
518
531
  `agent_launch({ harness, cwd, trust_project: true })` completes known workspace, project-hook, and project-MCP startup
519
532
  consent even without a prompt, then verifies input readiness and harness liveness before returning `startup.status="ready"`.
533
+ For Claude Code's first-run text-style menu, it confirms the item already selected on screen before continuing startup.
534
+ If the CLI then requests an account login method, the launch reports `vendor_onboarding_required`; complete that choice in the official interactive Claude Code CLI.
520
535
  A prompt-free launch without this option retains `startup.status="not_checked"`. `initial_prompt.status` distinguishes
521
536
  `not_requested`, `not_sent`, `submitted_unconfirmed`, and `started`. Failure responses retain structured session information.
522
537
  WindowsのCodexもhook確認を認識し、npm shim経由の起動を一つのharnessとして識別する。
@@ -538,7 +553,7 @@ continue to use `claude_approval`.
538
553
  | `pty_observe` | Pane/harness liveness, native process identity, state, and activity | `session_id`, `cursor?` |
539
554
  | `agent_launch` | Canonical agent launch; harness and model are independent | `harness`, `prompt?`, `model?`, `reasoning_effort?`, `cwd?`, `write_scope?`, `trust_project?`, `env_vars?`, `throughline_source_session?`, `throughline_supplement_file?` |
540
555
  | `agent_approval` | Inspect a Codex approval and submit a one-time approval or denial | `action`, `session_id`, `approval_choice?`, `observed_prompt_digest?` |
541
- | `agent_steer` | Inject text into the active Codex or Grok turn; return `idle` without sending when no turn is active | `session_id`, `text` |
556
+ | `agent_steer` | Inject text into the active Claude, Codex, Grok, or Cursor turn with each harness's own steering control, so the steered work still ends in one completion; return `idle` without sending when no turn is active, and fail instead of returning `steered` when Grok does not queue the text or Cursor leaves it in the composer | `session_id`, `text` |
542
557
  | `claude_agent` / `codex_agent` / `grok_agent` / `composer_agent` | Deprecated compatibility aliases | legacy launcher arguments |
543
558
  | `agent_configure` | Change model/effort in a running Claude, Codex, Grok, Composer, or Cursor session without restarting it | `session_id`, `model?`, `reasoning_effort?` |
544
559
  | `claude_turn` | Issue (dispatch-only) or recover one correlated Claude operation | `action`, `session_id`, `operation_id`, `text?` |
@@ -614,6 +629,21 @@ Claudeをリンク経由の`cwd`から起動した場合も、実体パスに対
614
629
 
615
630
  `aiterm-wait` takes no locks, never writes session state, and never dispatches — any number can run beside the MCP server and each other, and `pty_close`/concurrent sends are unaffected.
616
631
 
632
+ ### Launch an agent on another machine in one call (`remote`)
633
+
634
+ Add `remote` to `agent_launch` and the same launch runs in the Aiterm of another machine reached over SSH. Entering the machine and starting its agent become one call, and completion reaches the parent exactly as it does for a local child. The intended use is sending the same task to Linux, macOS, and Windows machines in parallel.
635
+
636
+ ```jsonc
637
+ agent_launch({ "harness": "codex-cli", "remote": { "host": "rabbit" }, "cwd": "/home/kite/project", "prompt": "..." })
638
+ ```
639
+
640
+ - `remote` takes `host`, `user`, `port`, `identity_file`, `passphrase` or `passphrase_env`, and `ssh_options` (`Key=Value`). A bare `host` is used as an `~/.ssh/config` alias. Aiterm does not store or manage destinations; the caller owns where to connect and with which key.
641
+ - A plain `passphrase` stays in the calling AI's conversation log. Prefer ssh-agent or `passphrase_env` (an environment variable name). A received passphrase lives only in the MCP process memory and reaches ssh through `SSH_ASKPASS`; it is never written to state files or logs.
642
+ - The remote machine needs `aiterm-mcp`, tmux (psmux on Windows), and the harness CLI. The remote shell family (POSIX, PowerShell, or cmd) is detected on first contact. On POSIX machines Aiterm takes only PATH from the login shell, so CLIs under `~/.local/bin`, Homebrew, or nvm are found; on Windows it starts `aiterm-mcp` with the user's PATH as is.
643
+ - Pass the same `remote` to later `pty_send`, `pty_read`, `pty_close`, `pty_observe`, `agent_steer`, and so on. Session names belong to the remote machine and never collide with local sessions of the same name.
644
+ - Codex, Claude Code, and Cursor parents receive the answer automatically, as with a local child. Completion is observed with `ssh <host> aiterm-wait`, reconnecting at the same cursor if SSH drops. Other parents get an ssh-based `wait_process`.
645
+ - Calls to the same destination share one SSH connection through ControlMaster. `image` attachments and `claude_turn issue` are not yet supported with `remote`.
646
+
617
647
  ### Token reduction
618
648
 
619
649
  - `pty_read` by default strips control characters, collapses repeated lines, and folds long output into head+tail (with a restore hint and a meta line).
@@ -6,7 +6,7 @@ import * as path from "node:path";
6
6
  import * as os from "node:os";
7
7
  import { AitermError } from "./errors.js";
8
8
  import { resolveWindowsPowerShell7 } from "./windows-powershell.js";
9
- import { isWin } from "./tmux-runtime.js";
9
+ import { isWin, spawnInMacGuiWhenOutsideAqua } from "./tmux-runtime.js";
10
10
  export function isUsableExecutableFile(candidate) {
11
11
  try {
12
12
  if (!fs.statSync(candidate).isFile())
@@ -82,7 +82,7 @@ export function spawnAgentControlCommand(bin, args, _cwd, options) {
82
82
  // これを直接 spawn すると常に失敗し、「受入が通した bin で起動が必ず失敗する」矛盾になる。
83
83
  return spawnSync(resolveWinPaneShell("bash"), [bin, ...args], options);
84
84
  }
85
- return spawnSync(bin, args, options);
85
+ return spawnInMacGuiWhenOutsideAqua(bin, args, options) ?? spawnSync(bin, args, options);
86
86
  }
87
87
  export function resolveWinPaneShell(shell) {
88
88
  if (!isWin)
@@ -0,0 +1,27 @@
1
+ // Codex DesktopのSteerで使う同梱Codex CLIの場所。Desktopは更新のたびに同梱物の場所を変えることがある
2
+ // (Windowsは版ごとのcache directory、macOSはbundle内の配置)。setup時に保存した場所が消えていたら、
3
+ // 使う時点で公式Desktopから探し直して設定を更新する。探せなければ理由付きで失敗し、別のCodexへは切り替えない。
4
+ import * as fs from "node:fs";
5
+ import * as path from "node:path";
6
+ import { CodexDeliveryError } from "./codex-delivery-error.js";
7
+ import { codexHookDirectory, writeHookJson } from "./codex-hook-state.js";
8
+ async function platformFinder() {
9
+ if (process.platform === "win32")
10
+ return (await import("./windows-codex-setup.js")).findWindowsCodexBinary;
11
+ return (await import("./setup-codex-relay.js")).findDesktopBinary;
12
+ }
13
+ export async function currentCodexDesktopBinary(config, options = {}) {
14
+ const exists = options.exists ?? fs.existsSync;
15
+ if (exists(config.binary))
16
+ return config.binary;
17
+ let binary;
18
+ try {
19
+ binary = (options.find ?? await platformFinder())();
20
+ }
21
+ catch (error) {
22
+ throw new CodexDeliveryError("CODEX_DESKTOP_BINARY_MOVED", `Codex Desktopの更新で ${config.binary} が無くなり、新しい場所も特定できません(${error instanceof Error ? error.message : String(error)})。` +
23
+ "Codex Desktopを起動してから aiterm-setup --codex-steer enable を実行してください");
24
+ }
25
+ writeHookJson(path.join(options.directory ?? codexHookDirectory(), "config.json"), { ...config, binary });
26
+ return binary;
27
+ }
@@ -6,6 +6,7 @@ import { realCodexHome } from "./harnesses/codex.js";
6
6
  import { withCodexReceiver } from "./codex-parent-receiver.js";
7
7
  import { CodexDeliveryError } from "./codex-delivery-error.js";
8
8
  import { readRuntimeProcesses } from "./process-runtime.js";
9
+ import { currentCodexDesktopBinary } from "./codex-desktop-binary.js";
9
10
  import { answerDigest, codexHookDirectory, codexInputDirectory, hookInputSchema, readCodexHookConfig, writeHookJson } from "./codex-hook-state.js";
10
11
  export async function runCodexResultHook(input, emit, options = {}) {
11
12
  const event = z.object({ session_id: z.uuid(), turn_id: z.string().min(1),
@@ -96,7 +97,7 @@ export async function runCodexResultHook(input, emit, options = {}) {
96
97
  taken.pop();
97
98
  }
98
99
  }
99
- }, options.runtime ?? { executable: config.binary, timeout_ms: 5_000 });
100
+ }, options.runtime ?? { executable: await currentCodexDesktopBinary(config), timeout_ms: 5_000 });
100
101
  const text = taken.map(item => item.text).join("\n\n");
101
102
  await emit(!taken.length ? {} : event.hook_event_name === "Stop"
102
103
  ? { decision: "block", reason: text }
@@ -7,6 +7,7 @@ import { realCodexHome } from "./harnesses/codex.js";
7
7
  import { CodexDeliveryError } from "./codex-delivery-error.js";
8
8
  import { readRelayConfig, parentRelaySocket } from "./codex-relay-config.js";
9
9
  import { withCodexRelay, verifyLoadedParent } from "./codex-relay-client.js";
10
+ import { currentCodexDesktopBinary } from "./codex-desktop-binary.js";
10
11
  import { finishCodexHookSubmission, assertCodexHookParentCurrent, assertCodexHooksReady, codexHookDirectory, readCodexHookConfig, registerCodexHookInput } from "./codex-hook-state.js";
11
12
  export { CodexDeliveryError } from "./codex-delivery-error.js";
12
13
  /** modelの引数ではなく、CodexがMCP要求へ付けるmetadataだけを宛先にする。 */
@@ -29,7 +30,7 @@ function relaySocket(runtime) {
29
30
  }
30
31
  export async function withCodexReceiver(parent, action, runtime = {}) {
31
32
  const config = runtime.executable ? null : readCodexHookConfig();
32
- const executable = runtime.executable ?? (config?.enabled ? config.binary : null) ?? resolveAgentBin("codex");
33
+ const executable = runtime.executable ?? (config?.enabled ? await currentCodexDesktopBinary(config) : null) ?? resolveAgentBin("codex");
33
34
  if (!executable)
34
35
  throw new CodexDeliveryError("CODEX_RECEIVER_UNAVAILABLE", "Codexの実行ファイルを確認できません");
35
36
  const child = spawn(executable, runtime.args ?? ["app-server", "--listen", "stdio://"], {