aiterm-mcp 0.61.16 → 0.61.18

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,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.61.18] - 2026-10-25
11
+
12
+ - Fix CURRENT-MENU-1: parse the current Claude auth phase, choices, cursor and occupied code field together; navigation consumes that same parse and rejects stale menus before keys.
13
+
14
+ - Add typed native auth stages and generation-bound `agent_auth respond`; keep existing receipt fields. Authorization codes bypass lastcmd, normal turns and pane logs. Legacy launch UUIDs remain missing rather than synthesized.
15
+ - Distinguish explicit Claude code/choice prompts, official device/browser stages, and unavailable input. Windows secret input remains unavailable until foreground ownership is accepted.
16
+
17
+ ## [0.61.17] - 2026-10-24
18
+
19
+ ### Changed
20
+
21
+ - Ordinary MCP delivery and parent-return hooks no longer enter the shared execution tracking dispatcher, participant enrollment or writer mutex. The legacy native MCP entry provides the standard delivery handlers plus direct leaf-first stop; hosts must remove mandatory ExecutionBridge registration.
22
+ - Stop forwarding reads existing agent parent relationships and sends normal interruption keys without shared graph access or writes. A lineage read failure still forwards the explicit root command. Native hook setup migrates back to the standard parent hook scripts.
23
+
10
24
  ## [0.61.16] - 2026-10-24
11
25
 
12
26
  ### Fixed
@@ -2854,7 +2868,9 @@ prototype (preserved under `prototype/python/` as the porting source and referen
2854
2868
  `ubuntu-latest` for Node 18/20/22, publishing to npm on `v*` tags with
2855
2869
  provenance.
2856
2870
 
2857
- [Unreleased]: https://github.com/kitepon/aiterm-mcp/compare/v0.61.16...HEAD
2871
+ [Unreleased]: https://github.com/kitepon/aiterm-mcp/compare/v0.61.18...HEAD
2872
+ [0.61.18]: https://github.com/kitepon/aiterm-mcp/compare/v0.61.17...v0.61.18
2873
+ [0.61.17]: https://github.com/kitepon/aiterm-mcp/compare/v0.61.16...v0.61.17
2858
2874
  [0.61.16]: https://github.com/kitepon/aiterm-mcp/compare/v0.61.15...v0.61.16
2859
2875
  [0.61.15]: https://github.com/kitepon/aiterm-mcp/compare/v0.61.14...v0.61.15
2860
2876
  [0.61.14]: https://github.com/kitepon/aiterm-mcp/compare/v0.61.13...v0.61.14
@@ -3057,3 +3073,6 @@ prototype (preserved under `prototype/python/` as the porting source and referen
3057
3073
  [0.3.0]: https://github.com/kitepon/aiterm-mcp/compare/v0.2.0...v0.3.0
3058
3074
  [0.2.0]: https://github.com/kitepon/aiterm-mcp/compare/v0.1.0...v0.2.0
3059
3075
  [0.1.0]: https://github.com/kitepon/aiterm-mcp/releases/tag/v0.1.0
3076
+
3077
+
3078
+ Claude authentication URL receipts recognize the official claude.com/cai/oauth/authorize PKCE authorization request and its nonsecret code=true display flag. Callback authorization codes and tokens remain excluded; the common URL credential filter is unchanged. This auth-only patch contains no native goal feature.
package/README.ja.md CHANGED
@@ -209,7 +209,7 @@ runtime-error store は canonical dotagents config の `collection.enabled: true
209
209
  場合だけ収集し、既定OFF、network送信は行いません。tag起点CIのnpm provenance(OIDC Trusted
210
210
  Publishing)で公開し、GitHub Release が Official MCP Registry を再登録します。
211
211
 
212
- **状態:** 開発継続中 · 現行公開版 **v0.61.16** · 動作対象は Linux · WSL2 · macOS · Windows ネイティブ · MIT · [変更履歴](CHANGELOG.md)。
212
+ **状態:** 開発継続中 · 現行公開版 **v0.61.18** · 動作対象は Linux · WSL2 · macOS · Windows ネイティブ · MIT · [変更履歴](CHANGELOG.md)。
213
213
 
214
214
  ### 更新と巻き戻し
215
215
 
@@ -592,7 +592,7 @@ Claudeの相関済み承認は既存の`claude_approval`を使う。
592
592
  | `pty_list` | textと構造化したsession一覧、明示した非秘密環境変数の照会 | `env_keys?` |
593
593
  | `pty_observe` | pane/harnessの生存、native process identity、状態と活動 | `session_id`, `cursor?` |
594
594
  | `agent_launch` | harnessとmodelを別軸で選ぶ正規agent起動入口 | `harness`, `prompt?`, `model?`, `reasoning_effort?`, `fast?`, `cwd?`, `write_scope?`, `trust_project?`, `env_vars?`, `throughline_source_session?`, `throughline_supplement_file?` |
595
- | `agent_auth` | 公式CLIの認証を開始・確認・取消し、公式URL・device code・入力待ちを返す | `harness`, `action`, `session_id?`, `cwd?`, `env_vars?`, `relogin?` |
595
+ | `agent_auth` | 公式CLIの認証を開始・確認・取消し、公式URL・device code・入力待ちを返す | `harness`, `action`, `session_id?`, `cwd?`, `env_vars?`, `relogin?`, `expected_binding?`, `response?` |
596
596
  | `agent_models` | harnessが今選べるmodelとreasoning effortを、そのharness自身の一覧からpromptを送らずに取得 | `harness`, `cwd?`, `include_hidden?` |
597
597
  | `agent_approval` | Codexの現在の承認を検査し、単発許可・拒否を送る | `action`, `session_id`, `approval_choice?`, `observed_prompt_digest?` |
598
598
  | `claude_agent` / `codex_agent` / `grok_agent` | deprecated互換alias(`composer_agent`は0.41.0で削除。Composerは`agent_launch`の`cursor-cli`で使う) | 旧launcher引数 |
@@ -636,7 +636,10 @@ Aitermは、既定では実行時エラーをどこへも送らない。送る
636
636
  - 返りの`fast`は、切り替えた後の実際の状態。`agent_models`のmodelごとの`fast`が、そのmodelで切り替えられるかを示す(GrokとCursorのfast版は別のmodelとして並ばず、元のmodelの`fast: true`になる)。最上位の`fast_unavailable`は、アカウントの都合でどのmodelでも使えない時の訳。
637
637
  - 断りは文の先頭の符号で分かる: `FAST_MODE_NOT_SUPPORTED`(そのmodelは対応していない)、`FAST_MODE_UNAVAILABLE`(アカウントや契約で使えない)、`FAST_MODE_MODEL_CONFLICT`(modelへfast版のIDを書いて`fast: false`を渡した)、`FAST_MODE_UNCONFIRMED`(切り替えた後の状態を確かめられない)。起動で`FAST_MODE_UNAVAILABLE`・`FAST_MODE_UNCONFIRMED`になった時は、起動を断らずに通常の速度で起こし、返りを`fast: false`、`fast_error: { code, message }`にする。
638
638
 
639
- `agent_auth({ harness, action:"start"|"status"|"cancel", session_id?, cwd?, env_vars?, relogin? })`は、各harnessの公式CLIで認証を進める。Claudeは`claude auth login`、CodexとGrokは`login --device-auth`、Cursorは`NO_OPEN_BROWSER=1 cursor-agent login`を使い、資格情報は各CLIだけが保存する。Aitermは資格情報を読取り・copy・編集せず、独自OAuthも実装しない。`remote`は他toolと同じ標準対応。
639
+ `agent_auth({ harness, action:"start"|"status"|"cancel"|"respond", session_id?, cwd?, env_vars?, relogin?, expected_binding?, response? })`は、各harnessの公式CLIで認証を進める。Claudeは`claude auth login`、CodexとGrokは`login --device-auth`、Cursorは`NO_OPEN_BROWSER=1 cursor-agent login`を使い、資格情報は各CLIだけが保存する。Aitermは資格情報を読取り・copy・編集せず、独自OAuthも実装しない。`remote`は他toolと同じ標準対応。
640
+
641
+ 認証UIは追加の`auth_ui`を使う。`stage`は`open_url` / `device_code` / `paste_code` / `select` / `waiting` / `complete` / `failed` / `unavailable`。公式URL・表示code・具体的な選択ラベル・入力種別をNativeが返し、`input_required`だけから入力内容や端末キーを推測しない。`respond`は直近の`auth_ui.binding`を`expected_binding`へ渡し、`expected_prompt_digest`には同じ`auth_ui.prompt_digest`を渡す。`response:{kind:"authorization_code",value}`または`{kind:"choice",id}`を同じ認証flowへ送る。認証コードを`pty_send`へ渡さない。専用入力はlastcmd・通常turn・paneログ・argv・一時fileへコードを保存せず、CLIの画面echoもpaneログへ保存しない。未発行と入力後不明は構造化errorの`effect:not_issued|unknown`で区別し、自動再送しない。legacy flowの`launch_id:null`は実native識別情報で照合し、UUIDを補わない。Windowsの専用入力は前面所有確認が未受入のため`not_issued`で拒否する。
642
+
640
643
 
641
644
  `start`は、公式の状態が認証済みなら何も起こさず`authenticated`(`session_id:null`)を返す。`relogin:true`を付けると、認証済みに見えても公式ログインを開始し、`session_id`付きの結果を返す。別のアカウントへ入り直す時と、ログインの期限切れを状態から見抜けない時に使う。Aitermは資格情報を消さず、置き換えは公式CLIが行う。ただし、公式CLIがログインを始めた時点で元のログインを消す事がある(Codex 0.160.0の`codex login --device-auth`は、始めた時点で`auth.json`を消す。途中で`cancel`しても戻らない)。Claude CodeとCursorは、途中で`cancel`すれば元の資格情報が残る(無効な資格情報で確認)。
642
645
 
@@ -649,9 +652,9 @@ Aitermは、既定では実行時エラーをどこへも送らない。送る
649
652
  | Grok | `grok models`(状態の命令は無い) | `blocked`(`You are not authenticated.`)。Grokは使えない資格情報を自分で消す |
650
653
  | Cursor | `cursor-agent status` | `failed`(`Logged in (unable to fetch user details)`。期限切れか通信できないかは区別できない)。`relogin:true`で入り直す |
651
654
 
652
- 結果は`aiterm.agent-auth-result.v1`。`status`は`waiting`/`authenticated`/`blocked`/`failed`、`session_id`・`url`・`user_code`・`input_required`・`message`を返す。`start`のsession IDを保存して`status`へ渡す。公式HTTPS URLと明示device codeだけを返し、CLIの生出力・token・OAuth callback codeを結果へ載せない。`input_required:true`なら同じsessionの`pty_read(screen:true)`で公式画面を表示し、`pty_send`/`pty_key`で人の入力を中継する。
655
+ 結果は`aiterm.agent-auth-result.v1`。`status`は`waiting`/`authenticated`/`blocked`/`failed`、`session_id`・`url`・`user_code`・`input_required`・`message`を返す。`start`のsession IDを保存して`status`へ渡す。公式HTTPS URLと明示device codeだけを返し、CLIの生出力・token・OAuth callback codeを結果へ載せない。`auth_ui`の安全な案内だけを表示し、入力は同じbindingとprompt_digestを持つ専用respondへ渡す。生画面や通常PTY入力へ認証コードを中継しない。
653
656
 
654
- Grokには公式認証status commandが無いため、session付きの確認は公式loginのexit 0を正本にし、session無しの確認は`blocked`を返す。他harnessは公式statusも照合する。Claudeは認証後の公式初回案内を同じsessionで進め、選択待ちは`blocked`/`input_required:true`で返す。`authenticated`は認証結果であり、`agent_launch`の起動準備完了は別途確認する。`cancel`は指定した認証sessionだけを閉じ、資格情報を削除しない。
657
+ Grokには公式認証status commandが無いため、session付きの確認は公式loginのexit 0を正本にし、session無しの確認は公式`models`の認証要求を照合し、不明なら成功扱いしない。他harnessは公式statusも照合する。Claudeは認証後の公式初回案内を同じsessionで進め、選択待ちは`blocked`/`input_required:true`で返す。`authenticated`は認証結果であり、`agent_launch`の起動準備完了は別途確認する。`cancel`は指定した認証sessionだけを閉じ、資格情報を削除しない。
655
658
 
656
659
  PTYが消失した場合は、相関記録の有無にかかわらず`status`が`failed`、`cancel`が既に終了・取消済みを示す`blocked`を返し、どちらも`session_id:null`となる。保存したsession IDを解除して`start`で再開始できる。生存中の通常PTYやharness不一致、記録の破損・読取り失敗はエラーを返す。
657
660
 
@@ -742,20 +745,10 @@ agent_launch({ "harness": "codex-cli", "remote": { "host": "rabbit" }, "cwd": "/
742
745
 
743
746
  BellTeamのmodel・effort・fast変更も、既存session世代に結合したnative管理要求で受け付ける。途中で世代・受付枠・停止状態が変われば、それ以降の打鍵を止める。途中まで変更した結果は作用不明のまま保持し、自動でやり直さない。
744
747
 
745
- POSIXの明示native入口でも、`agent_auth`の名前なし開始、認証済みで何も起こさない開始、既存v1の確認・初回案内・取消を受け付ける。認証PTYは実際の作成元nativeと世代へ結合する。BellTeamの参照なし入力・キーは、この証拠がある認証PTYにだけ許可し、通常agentの本文には配送参照を引き続き必要とする。作成証拠のない旧認証metadataを自動登録しない。
746
-
747
- この明示入口では `agent_execution` の `reconcile_launch` に元の `request` と正確な `target: {session_id, launch_id}` を渡し、結果不明の空起動を既存世代へ結合できる。保存したnative/serverと現在のmanaged世代、raw状態で初期promptなし、生存して入力待ちのharnessをmetadata受付内で照合し、該当するunknown launch枠だけを解放する。受付済みでも本文未発行なら、照合を繰り返すたびに現在世代・入力待ち・競合操作なしを再確認する。保存した受付結果だけで後の世代を確認済みとしない。起動・打鍵・close・本文再送は行わない。issuedのRPC、入力状態不明、世代交代や世代未確認は拒否する。後続本文は元参照で通常のsend受付を通して一度だけ発行する。照合成功は失われた元RPC receiptの回収を意味しない。
748
-
749
- CLIが終了した結果不明の空起動には、BellTeam native入口の `agent_execution({action: "close_launch", request, target: {session_id, launch_id}})` を明示的に使う。元requestと期待世代を受付内とnative close直前で照合し、原native/server・managed世代一致、raw初期prompt `none`・初期配送なし・send未発行・現在CLI終了の正規probeがすべて必要になる。Claude CodeとCodexで同じ証明条件を使う。生存・issued・送信済み・欠測・世代交代は拒否し、sessionだけのcloseへの代用や自動再起動・再送はしない。旧版は新actionを拒否する。
750
-
751
- 成功時の `structuredContent` は応答するSDKの別管理requestのExecutionRecordで、元unknown recordは変えない。`_meta["aiterm/closed_launch"]` に厳密なreceiptを返す。固定項目は `schema: "aiterm.closed-launch.v1"`、`version: 1`、`action: "close_launch"`、`outcome: "closed"`、元 `request`、正確な `target`、`management_ref`(応答SDKのMCP request、`tool: "pty_close"`)。`evidence` は原 `native` binding、`initial_prompt: "none"`、`initial_prompt_delivery: null`、`harness_alive: false`、`absence_confirmed: true`、`metadata_committed: true` を持つ。実close・不在確認・slot解放commitが揃った場合だけ返し、失敗や結果不明には返さない。本体が不明を解除する際は元pending操作と自身のstop/versionも照合する。過去receiptで後発の不明を解除しない。
752
-
753
- 同じ明示入口で、返却済みunknown launchのCLIが終了している場合は、元native/server・managed世代とraw初期promptなし・本文未発行・harness終了を同期検証できた時だけ通常の`pty_close`で対象席を閉じられる。成功と不在を確認して該当枠だけを外し、元unknown requestを保持する。発行中RPC、生存harness、入力や世代の欠測は拒否する。旧requestを再実行せず、必要な後続は新しい通常要求として行う。
754
-
755
748
 
756
749
  ## 人が覗く
757
750
 
758
- POSIXの明示native実行構成では、検証済みcallerへ通常メッセージが届く間も、そのcallerから別席への送信を継続できる。callerの世代や各requestのslotは書き換えない。新しく接続した参加者も、初回の非同期要求で宣言済みの役割を保存する際は、既存の上限付きmetadata受付で競合を待つ。この待機は入力を再送しない。agentの貼付け・Enterはmetadata mutexの競合を入力前に待ち、不明な操作・世代交代・停止では入力を止める。BellTeamは閉じた席をpromptなしの明示管理`agent_launch`で再作成できる。promptを送る呼出しには元の実行参照が必要である。
751
+ 通常の配送と親への返答は、実行追跡用の共有lockを取得も待機もしない。旧native入口も通常配送と末端優先の`agent_stop`を提供し、`agent_execution`登録を要求しない。この入口を使う本体もExecutionBridgeの先行登録を外す。記録の失敗で配送を止めない。
759
752
 
760
753
  セッションは共有tmux socket(Windows nativeはpsmux namespace)上にある。`pty_open`/`agent_launch`の戻り値に表示されるattachコマンドで、人間がClaude/Codex/Grok/Cursor harnessの同じ端末へ入り、途中でキーボードを引き取れる。
761
754
  POSIXでは、tmuxのソケットがまだ無くても最初の`agent_launch`でserverを起動できる。Aitermは作成したsessionとserverを実際のreceiptから照合して受付を確定し、不明な結果を入力の再送で補わない。
@@ -814,3 +807,6 @@ aiterm が、あなたの AI に別のエージェントへ仕事を渡させた
814
807
  ## ライセンス
815
808
 
816
809
  MIT
810
+
811
+
812
+ Claude認証は公式claude.com/cai/oauth/authorizeのPKCE要求と非秘密のcode=true表示flagを認証URLとして返す。callback認可codeやtokenは除外し、共通URL credential filterは変更しない。この認証専用差分にnative goal機能は含まない。
package/README.md CHANGED
@@ -227,7 +227,7 @@ collection is off by default and performs no network I/O. It ships via
227
227
  tag-triggered CI with npm provenance (OIDC Trusted Publishing); the GitHub
228
228
  Release re-registers the Official MCP Registry entry.
229
229
 
230
- **Status:** actively maintained · current public release **v0.61.16** · 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).
230
+ **Status:** actively maintained · current public release **v0.61.18** · 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).
231
231
 
232
232
  ### Update and rollback
233
233
 
@@ -631,7 +631,7 @@ continue to use `claude_approval`.
631
631
  | `pty_list` | Text and structured session list (each agent session carries its harness conversation ID as `vendor_session_id`), with explicitly requested non-secret environment values | `env_keys?` |
632
632
  | `pty_observe` | Pane/harness liveness, native process identity, state, and activity | `session_id`, `cursor?` |
633
633
  | `agent_launch` | Canonical agent launch; harness and model are independent | `harness`, `prompt?`, `model?`, `reasoning_effort?`, `fast?`, `cwd?`, `write_scope?`, `trust_project?`, `env_vars?`, `throughline_source_session?`, `throughline_supplement_file?` |
634
- | `agent_auth` | 公式CLIの認証を開始・確認・取消し、公式URL・device code・入力待ちを返す | `harness`, `action`, `session_id?`, `cwd?`, `env_vars?`, `relogin?` |
634
+ | `agent_auth` | 公式CLIの認証を開始・確認・取消し、公式URL・device code・入力待ちを返す | `harness`, `action`, `session_id?`, `cwd?`, `env_vars?`, `relogin?`, `expected_binding?`, `response?` |
635
635
  | `agent_models` | List the models and reasoning efforts a harness offers now, read from that harness's own catalog without sending a prompt | `harness`, `cwd?`, `include_hidden?` |
636
636
  | `agent_approval` | Inspect a Codex approval and submit a one-time approval or denial | `action`, `session_id`, `approval_choice?`, `observed_prompt_digest?` |
637
637
  | `claude_agent` / `codex_agent` / `grok_agent` | Deprecated compatibility aliases (`composer_agent` was removed in 0.41.0; run Composer with `agent_launch` on `cursor-cli`) | legacy launcher arguments |
@@ -680,7 +680,10 @@ enables collection on that host.
680
680
 
681
681
  Claude Codeの席では、`/model`の選ぶ画面の`s`(use this session only)で替える。`/model <名前>`・`/effort <値>`は選んだ値を端末の`~/.claude/settings.json`へ新しい会話の既定として保存するので、打たない(ADR 0111)。その選び方が無い版、選ぶ画面に無いmodel、そのmodelで選べないeffortは、何も変えずに断る(`CLAUDE_MODEL_SESSION_ONLY_UNAVAILABLE`・`CLAUDE_MODEL_NOT_IN_PICKER`・`CLAUDE_EFFORT_NOT_SELECTABLE`)。席を閉じて立て直すと端末の既定へ戻るので、立て直す時は`agent_launch`へ同じ`model`・`reasoning_effort`を渡す。
682
682
 
683
- `agent_auth({ harness, action:"start"|"status"|"cancel", session_id?, cwd?, env_vars?, relogin? })`は、各harnessの公式CLIで認証を進める。Claudeは`claude auth login`、CodexとGrokは`login --device-auth`、Cursorは`NO_OPEN_BROWSER=1 cursor-agent login`を使い、資格情報は各CLIだけが保存する。Aitermは資格情報を読取り・copy・編集せず、独自OAuthも実装しない。`remote`は他toolと同じ標準対応。
683
+ `agent_auth({ harness, action:"start"|"status"|"cancel"|"respond", session_id?, cwd?, env_vars?, relogin?, expected_binding?, response? })`は、各harnessの公式CLIで認証を進める。Claudeは`claude auth login`、CodexとGrokは`login --device-auth`、Cursorは`NO_OPEN_BROWSER=1 cursor-agent login`を使い、資格情報は各CLIだけが保存する。Aitermは資格情報を読取り・copy・編集せず、独自OAuthも実装しない。`remote`は他toolと同じ標準対応。
684
+
685
+ 認証UIは追加の`auth_ui`を使う。`stage`は`open_url` / `device_code` / `paste_code` / `select` / `waiting` / `complete` / `failed` / `unavailable`。公式URL・表示code・具体的な選択ラベル・入力種別をNativeが返し、`input_required`だけから入力内容や端末キーを推測しない。`respond`は直近の`auth_ui.binding`を`expected_binding`へ渡し、`expected_prompt_digest`には同じ`auth_ui.prompt_digest`を渡す。`response:{kind:"authorization_code",value}`または`{kind:"choice",id}`を同じ認証flowへ送る。認証コードを`pty_send`へ渡さない。専用入力はlastcmd・通常turn・paneログ・argv・一時fileへコードを保存せず、CLIの画面echoもpaneログへ保存しない。未発行と入力後不明は構造化errorの`effect:not_issued|unknown`で区別し、自動再送しない。legacy flowの`launch_id:null`は実native識別情報で照合し、UUIDを補わない。Windowsの専用入力は前面所有確認が未受入のため`not_issued`で拒否する。
686
+
684
687
 
685
688
  `start`は、公式の状態が認証済みなら何も起こさず`authenticated`(`session_id:null`)を返す。`relogin:true`を付けると、認証済みに見えても公式ログインを開始し、`session_id`付きの結果を返す。別のアカウントへ入り直す時と、ログインの期限切れを状態から見抜けない時に使う。Aitermは資格情報を消さず、置き換えは公式CLIが行う。ただし、公式CLIがログインを始めた時点で元のログインを消す事がある(Codex 0.160.0の`codex login --device-auth`は、始めた時点で`auth.json`を消す。途中で`cancel`しても戻らない)。Claude CodeとCursorは、途中で`cancel`すれば元の資格情報が残る(無効な資格情報で確認)。
686
689
 
@@ -693,9 +696,9 @@ Claude Codeの席では、`/model`の選ぶ画面の`s`(use this session only
693
696
  | Grok | `grok models`(状態の命令は無い) | `blocked`(`You are not authenticated.`)。Grokは使えない資格情報を自分で消す |
694
697
  | Cursor | `cursor-agent status` | `failed`(`Logged in (unable to fetch user details)`。期限切れか通信できないかは区別できない)。`relogin:true`で入り直す |
695
698
 
696
- 結果は`aiterm.agent-auth-result.v1`。`status`は`waiting`/`authenticated`/`blocked`/`failed`、`session_id`・`url`・`user_code`・`input_required`・`message`を返す。`start`のsession IDを保存して`status`へ渡す。公式HTTPS URLと明示device codeだけを返し、CLIの生出力・token・OAuth callback codeを結果へ載せない。`input_required:true`なら同じsessionの`pty_read(screen:true)`で公式画面を表示し、`pty_send`/`pty_key`で人の入力を中継する。
699
+ 結果は`aiterm.agent-auth-result.v1`。`status`は`waiting`/`authenticated`/`blocked`/`failed`、`session_id`・`url`・`user_code`・`input_required`・`message`を返す。`start`のsession IDを保存して`status`へ渡す。公式HTTPS URLと明示device codeだけを返し、CLIの生出力・token・OAuth callback codeを結果へ載せない。`auth_ui`の安全な案内だけを表示し、入力は同じbindingとprompt_digestを持つ専用respondへ渡す。生画面や通常PTY入力へ認証コードを中継しない。
697
700
 
698
- Grokには公式認証status commandが無いため、session付きの確認は公式loginのexit 0を正本にし、session無しの確認は`blocked`を返す。他harnessは公式statusも照合する。Claudeは認証後の公式初回案内を同じsessionで進め、選択待ちは`blocked`/`input_required:true`で返す。`authenticated`は認証結果であり、`agent_launch`の起動準備完了は別途確認する。`cancel`は指定した認証sessionだけを閉じ、資格情報を削除しない。
701
+ Grokには公式認証status commandが無いため、session付きの確認は公式loginのexit 0を正本にし、session無しの確認は公式`models`の認証要求を照合し、不明なら成功扱いしない。他harnessは公式statusも照合する。Claudeは認証後の公式初回案内を同じsessionで進め、選択待ちは`blocked`/`input_required:true`で返す。`authenticated`は認証結果であり、`agent_launch`の起動準備完了は別途確認する。`cancel`は指定した認証sessionだけを閉じ、資格情報を削除しない。
699
702
 
700
703
  PTYが消失した場合は、相関記録の有無にかかわらず`status`が`failed`、`cancel`が既に終了・取消済みを示す`blocked`を返し、どちらも`session_id:null`となる。保存したsession IDを解除して`start`で再開始できる。生存中の通常PTYやharness不一致、記録の破損・読取り失敗はエラーを返す。
701
704
 
@@ -791,22 +794,14 @@ agent_launch({ "harness": "codex-cli", "remote": { "host": "rabbit" }, "cwd": "/
791
794
 
792
795
  ### Input and output
793
796
 
797
+ Ordinary delivery and parent-return hooks do not acquire or wait for the shared execution tracking lock. The legacy native entry now serves ordinary delivery plus leaf-first `agent_stop`, without `agent_execution` registration. Hosts using that entry must remove mandatory ExecutionBridge registration; recording failure must not prevent delivery.
798
+
794
799
  `pty_send` does not interpret command or prompt meaning; it delivers the requested text to the terminal. By default it sanitizes ESC and bracketed-paste terminators, while `pty_read` neutralizes control characters in returned output (`raw: true` keeps them unchanged). The shell, remote endpoint, or launched harness owns command authorization.
795
800
 
796
- Each `pty_send` accepts at most 64 KiB of UTF-8 text. Sends to the same session are serialized across aiterm processes so chunks cannot interleave. Every OS pastes through its multiplexer in UTF-8-safe 256-byte chunks with a 10 ms drain interval; macOS, Linux, and WSL2 have all demonstrated silent middle/trailing loss when a long input is pushed without that boundary. Sanitized multiline text sent while a POSIX shell or PowerShell is in the foreground is encoded as one newline-free input (`eval` for POSIX shells; a dot-sourced, UTF-8 Base64-decoded scriptblock for PowerShell): the shell receives the complete script before it runs the first line, so a pager or REPL started mid-script cannot consume later lines as interactive keystrokes. Single-line input, `raw:true`, and non-shell frontends remain direct PTY pastes. Agent dispatches additionally wrap the whole text once in `ESC[200~/201~` and stream the wrapped text in the same chunks, so the agent TUI sees one paste (an image path is never split across pastes), hardening prompt injection against mid-word key-interpretation corruption and dropped submits. If a later chunk fails, aiterm reports the partial-send state and does not press Enter automatically. A lock left by a terminated sender fails closed before sending; use `pty_list` to confirm the affected session, close it with `pty_close`, then recreate the same session ID. There is no public kill-all tool. In the opt-in POSIX native execution entry, an unknown send can also be recovered by explicitly closing that one session with `pty_close`, then recreating it. The original request remains unknown and must not be replayed. Issued work, unknown management closes, and uncertain native identity remain blocked.
801
+ Each `pty_send` accepts at most 64 KiB of UTF-8 text. Sends to the same session are serialized across aiterm processes so chunks cannot interleave. Every OS pastes through its multiplexer in UTF-8-safe 256-byte chunks with a 10 ms drain interval; macOS, Linux, and WSL2 have all demonstrated silent middle/trailing loss when a long input is pushed without that boundary. Sanitized multiline text sent while a POSIX shell or PowerShell is in the foreground is encoded as one newline-free input (`eval` for POSIX shells; a dot-sourced, UTF-8 Base64-decoded scriptblock for PowerShell): the shell receives the complete script before it runs the first line, so a pager or REPL started mid-script cannot consume later lines as interactive keystrokes. Single-line input, `raw:true`, and non-shell frontends remain direct PTY pastes. Agent dispatches additionally wrap the whole text once in `ESC[200~/201~` and stream the wrapped text in the same chunks, so the agent TUI sees one paste (an image path is never split across pastes), hardening prompt injection against mid-word key-interpretation corruption and dropped submits. If a later chunk fails, aiterm reports the partial-send state and does not press Enter automatically. A lock left by a terminated sender fails closed before sending; use `pty_list` to confirm the affected session, close it with `pty_close`, then recreate the same session ID. There is no public kill-all tool.
797
802
 
798
803
  ## A human can watch
799
804
 
800
- In the opt-in POSIX native execution entry, an ordinary message arriving at a verified caller can overlap that caller's outgoing send. It does not replace the caller's generation or clear either request's slot. A newly connected participant also waits within the existing bounded metadata admission to commit its declared roles before its first asynchronous request; that wait never replays input. Agent paste/submit segments wait for metadata mutex contention before input; uncertain operations, generation changes and stops still prevent input. BellTeam may explicitly recreate a closed session with a promptless management `agent_launch`; sending a prompt still requires its original execution reference.
801
-
802
- An explicit `agent_execution` action `reconcile_launch` can bind an existing empty launch after a lost launch result in this opt-in entry. Supply the original `request` and exact `target: {session_id, launch_id}`. The server checks the original native/server identity, current managed generation, raw no-prompt state and a live ready harness under metadata admission, then releases only that unknown launch slot. Repeating an accepted empty launch before its send also rechecks the current generation, readiness and absence of competing session operations; a saved acceptance alone cannot confirm a later generation. It never launches, types, closes or resends. `issued` RPCs, unknown input and changed or unverified generations remain blocked. Any subsequent body uses the original reference through normal send admission once; a successful reconciliation does not prove the lost RPC receipt.
803
-
804
- For an empty unknown launch whose harness has exited, the BellTeam native entry accepts `agent_execution({action: "close_launch", request, target: {session_id, launch_id}})`. The original request and generation are atomic expectations, checked within admission and immediately before the native close. Raw initial prompt must be `none`, initial delivery absent, send unissued, and the original native/server and managed identity must match. The private runtime proof covers Claude Code and Codex; live, issued, sent, missing or replaced targets remain refused. No session-only close fallback or automatic relaunch/send occurs. Older servers reject this action.
805
-
806
- Success retains the original unknown record and returns the serving SDK's distinct management execution record in `structuredContent`. `_meta["aiterm/closed_launch"]` is a strict receipt with `schema: "aiterm.closed-launch.v1"`, `version: 1`, `action: "close_launch"`, `outcome: "closed"`, the original `request`, exact `target`, and `management_ref` (the serving MCP request, `tool: "pty_close"`). Its `evidence` contains original `native` binding, `initial_prompt: "none"`, `initial_prompt_delivery: null`, `harness_alive: false`, `absence_confirmed: true`, and `metadata_committed: true`. Only confirmed close, absence and committed slot release produce this receipt. Errors or lost/uncertain results do not. Hosts must match the original pending operation and local stop/version state before clearing its uncertainty; historical receipts cannot clear later uncertainty.
807
-
808
- For a returned unknown launch whose harness has exited, the same native entry admits explicit `pty_close` only after synchronous proof of the original native/server and managed generation, raw no-initial-prompt state, an unissued body send and an exited harness. It releases the failed launch slot after successful close and confirmed absence, preserving the original unknown record. Issued RPCs, live harnesses and missing input or generation evidence remain blocked. The old request is never replayed; a later ordinary request may create a fresh generation.
809
-
810
805
 
811
806
  On native Windows, non-raw commands sent to a PowerShell shell use UTF-8 for Console input/output and native pipelines. Runtime additions to the caller PATH are passed to new panes; raw PTY input is unchanged.
812
807
  Agent launches use a PowerShell 7 pane and pass the harness argv directly; accepted shebang wrappers use noninteractive Git Bash in that same pane. The owned startup script is sent as one ASCII line, while agent TUI bodies retain their literal text.
@@ -814,10 +809,6 @@ Agent launches use a PowerShell 7 pane and pass the harness argv directly; accep
814
809
  Sessions live on a shared tmux socket on POSIX or a shared psmux namespace on native Windows. The attach line printed by `pty_open` and `agent_launch` lets a human attach to the same terminal and intervene, including a Claude/Codex/Grok/Cursor harness session: `tmux -S … attach -t <id>` on POSIX, or `psmux -L <namespace> attach -t <id>` on native Windows.
815
810
  On POSIX, the first `agent_launch` can create the tmux server when its socket does not yet exist. Aiterm verifies the exact created session and server before accepting that launch; an uncertain result is reported without retrying the input.
816
811
 
817
- BellTeam model, effort and fast changes use a native management request bound to the existing session generation. Later stages stop if that generation, its admission slot or stop state changes. A partial change remains an uncertain effect and must not be blindly repeated.
818
-
819
- In the opt-in POSIX native entry, `agent_auth` also supports omitted session names, authenticated no-op starts and the normal v1 status/onboarding/cancel receipts. Authentication PTYs are bound to their actual native creator and generation. Ref-less BellTeam input or keys are allowed only for those proven authentication PTYs; ordinary agent messages still require their delivery reference. Legacy authentication metadata without creator proof is not automatically enrolled.
820
-
821
812
  ## Requirements
822
813
 
823
814
  - **Node.js >= 18**
@@ -887,3 +878,6 @@ started the persistent multiplexer server (since 0.50.0). Every harness still ac
887
878
  ## License
888
879
 
889
880
  MIT
881
+
882
+
883
+ Claude authentication URL receipts recognize the official claude.com/cai/oauth/authorize PKCE authorization request and its nonsecret code=true display flag. Callback authorization codes and tokens remain excluded; the common URL credential filter is unchanged. This auth-only patch contains no native goal feature.
@@ -0,0 +1,52 @@
1
+ import { createHash } from "node:crypto";
2
+ import { AitermError } from "./errors.js";
3
+ import { z } from "zod";
4
+ // Existing native identities, not an application flow ID. Legacy launch_id stays null.
5
+ export const authBindingSchema = z.object({
6
+ session_id: z.string(), launch_id: z.string().nullable(),
7
+ metadata_identity: z.string(), native_id: z.string(), pane_id: z.string(),
8
+ server_pid: z.number().int().positive(), server_started: z.string(),
9
+ pane_pid: z.number().int().positive(), pane_started: z.string(),
10
+ }).strict();
11
+ export const authResponseSchema = z.discriminatedUnion("kind", [
12
+ z.object({ kind: z.literal("authorization_code"), value: z.string().min(1).max(8192).regex(/^[!-~]+$/) }).strict(),
13
+ z.object({ kind: z.literal("choice"), id: z.string().regex(/^option-[1-9]$/) }).strict(),
14
+ ]);
15
+ export const authUiSchema = z.object({
16
+ schema: z.literal("aiterm.auth-ui.v1"),
17
+ stage: z.enum(["open_url", "device_code", "paste_code", "select", "waiting", "complete", "failed", "unavailable"]),
18
+ title: z.string(), message: z.string(), binding: authBindingSchema.nullable(),
19
+ url: z.string().nullable(), user_code: z.string().nullable(),
20
+ input: z.enum(["authorization_code", "choice"]).nullable(),
21
+ prompt_digest: z.string().nullable(),
22
+ choices: z.array(z.object({ id: z.string(), label: z.string() }).strict()),
23
+ }).strict();
24
+ export function authPromptDigest(pane) {
25
+ return pane.ui ? createHash("sha256").update(JSON.stringify({ ui: pane.ui, url: pane.url })).digest("hex") : null;
26
+ }
27
+ export function authUi(result, pane, binding) {
28
+ const base = { schema: "aiterm.auth-ui.v1", binding, url: result.url, user_code: result.user_code,
29
+ choices: [], input: null, prompt_digest: pane ? authPromptDigest(pane) : null };
30
+ if (result.status === "authenticated")
31
+ return { ...base, stage: "complete", title: "認証完了", message: "公式CLIで認証完了を確認しました。" };
32
+ if (result.status === "failed")
33
+ return { ...base, stage: "failed", title: "認証を完了できません", message: result.message ?? "公式CLIの状態を確認してください。" };
34
+ if (pane?.ui && binding)
35
+ return { ...base, ...pane.ui };
36
+ if (binding && result.url)
37
+ return { ...base, stage: result.user_code ? "device_code" : "open_url", title: "公式認証ページを開く",
38
+ message: result.user_code ? "公式ページで表示のデバイスコードを入力してください。" : "公式ページで認証し、この画面で完了をお待ちください。" };
39
+ return { ...base, stage: result.input_required ? "unavailable" : "waiting", title: "認証状態を確認中",
40
+ message: result.input_required ? "公式画面の入力段階を安全に識別できません。入力は送信できません。" : "公式CLIの認証結果を待っています。" };
41
+ }
42
+ export class AuthResponseError extends AitermError {
43
+ reason_code;
44
+ effect;
45
+ constructor(reason_code, effect, message) {
46
+ super(`${reason_code}: ${message}`, 2);
47
+ this.reason_code = reason_code;
48
+ this.effect = effect;
49
+ }
50
+ }
51
+ export const authResponseFailureSchema = z.object({ schema: z.literal("aiterm.auth-response-error.v1"),
52
+ harness: z.string(), session_id: z.string().nullable(), code: z.string(), effect: z.enum(["not_issued", "unknown"]), message: z.string() }).strict();
package/dist/core.js CHANGED
@@ -27,8 +27,10 @@ import { unfinishedDeliveriesOwnedBy } from "./parent-delivery-owners.js";
27
27
  import { readRuntimeProcesses, readProcessIdentities, processSubtree, parentProcess, processIdentity, backgroundProcesses, lazyRelayProcess, aitermProcessProbe, windowsConsoleHost, processElapsedSeconds } from "./process-runtime.js";
28
28
  import { liveNodeExecutable } from "./live-node.js";
29
29
  import { AitermError, telemetryOwnedFailure, ownTelemetryFailure } from "./errors.js";
30
- import { isWin, SOCKDIR, SOCK, tmuxCommand, sendPsmuxPayload, loadPtyBufferChunk, pasteBufferBaseArgs, tmuxNewSession, openPsmuxServerToDesktop, registeredSessionEnvironment, attachCommand, normalizePaneCommand, atomicShellMultiline, appendMarkSentinel, paneShellForSubmission, adaptShellSubmission, settlePaneLog, paneCwdArgument, sessionEnvironmentLaunch, } from "./tmux-runtime.js";
30
+ import { isWin, SOCKDIR, SOCK, tmuxCommand, tmuxCommandWithInput, sendPsmuxPayload, loadPtyBufferChunk, pasteBufferBaseArgs, tmuxNewSession, openPsmuxServerToDesktop, registeredSessionEnvironment, attachCommand, normalizePaneCommand, atomicShellMultiline, appendMarkSentinel, paneShellForSubmission, adaptShellSubmission, settlePaneLog, paneCwdArgument, sessionEnvironmentLaunch, } from "./tmux-runtime.js";
31
31
  import { AGENT_DEFAULT_PREFACE, sleep, currentUid, runtimeStateBase, safeStatSize, readFileRange, writeJson0600, createEmpty0600, shq, LAUNCH_ID_RE, AGENT_DONE_POLL_MS, AGENT_RATE_LIMIT_POLL_MS, pollGate, AGENT_EVENT_MAX_BYTES, assertSessionName, agentsDir, agentEventPath, agentMetadataPath, writeAgentMetadata, AGENT_EVENT_TAIL_BYTES, agentLabel, agentHarness, subagentInstruction, agentLineageFields, } from "./agent-shared.js";
32
+ import { AuthResponseError, authUi, authPromptDigest, authBindingSchema, authResponseSchema } from "./agent-auth-ui.js";
33
+ import { processStartIdentity } from "./runtime-error-os.js";
32
34
  import { catalogUnavailable } from "./model-catalog.js";
33
35
  import { FastModeError, fastModeFailure } from "./fast-mode.js";
34
36
  import { readFileOrNull, restoreJsonSetting, withUserConfigLock } from "./user-config.js";
@@ -1805,6 +1807,14 @@ export function listSessions(result = listSessionsResult()) {
1805
1807
  }
1806
1808
  return "(セッション無し)";
1807
1809
  }
1810
+ /** Existing agent lineage is enough to forward a stop from leaves to parent.
1811
+ * This reads ordinary session metadata and never opens the execution graph. */
1812
+ export function agentSessionChildren() {
1813
+ return listSessionsResult().sessions.flatMap(({ session_id }) => {
1814
+ const parent = tryLoadAgentMetadata(session_id)?.parent_session_id;
1815
+ return parent ? [{ parent, child: session_id }] : [];
1816
+ });
1817
+ }
1808
1818
  /**
1809
1819
  * `pty_list` 相当を read-only に照会し、内容を返さず session 件数だけを返す。
1810
1820
  * session 名・前面コマンド・PTY 出力を診断応答へ持ち出さないため、factory が安全に readiness を
@@ -4349,6 +4359,131 @@ function authSessionEnvironment(name, keys) {
4349
4359
  }
4350
4360
  return env;
4351
4361
  }
4362
+ /** Dedicated authentication panes never persist terminal output, including a CLI echo. */
4363
+ function disableAuthLogging(name) {
4364
+ if (tmux("pipe-pane", "-t", name).code !== 0)
4365
+ throw new AitermError("AGENT_AUTH_LOGGING_UNCONFIRMED: 認証入力を送信していません。", 2);
4366
+ }
4367
+ function readAuthBinding(name, kind) {
4368
+ const meta = loadAuthMetadata(name, kind);
4369
+ if (!meta)
4370
+ return null;
4371
+ const stat = fs.statSync(authMetadataPath(name));
4372
+ const native = tmux("display-message", "-p", "-t", `=${name}:`, "#{pid}\t#{session_id}\t#{pane_id}\t#{pane_pid}\t#{pane_dead}");
4373
+ if (native.code !== 0)
4374
+ return null;
4375
+ const [server, session, pane, pid, dead] = native.stdout.trim().split("\t");
4376
+ if (dead !== "0" || !/^\$\d+$/.test(session ?? "") || !/^%\d+$/.test(pane ?? ""))
4377
+ return null;
4378
+ const serverStarted = processStartIdentity(Number(server), process.platform);
4379
+ const paneStarted = processStartIdentity(Number(pid), process.platform);
4380
+ if (!serverStarted || !paneStarted)
4381
+ return null;
4382
+ return authBindingSchema.parse({ session_id: name, launch_id: meta.launch_id ?? null,
4383
+ metadata_identity: `${stat.dev}:${stat.ino}:${stat.birthtimeMs}:${stat.mtimeMs}`, native_id: session, pane_id: pane,
4384
+ server_pid: Number(server), server_started: serverStarted, pane_pid: Number(pid), pane_started: paneStarted });
4385
+ }
4386
+ async function respondAgentAuth(kind, options) {
4387
+ const name = options.session_id;
4388
+ const expected = authBindingSchema.safeParse(options.expected_binding);
4389
+ const response = authResponseSchema.safeParse(options.response);
4390
+ if (!name || !expected.success || !response.success || expected.data.session_id !== name || !/^[a-f0-9]{64}$/.test(options.expected_prompt_digest ?? ""))
4391
+ throw new AuthResponseError("AGENT_AUTH_RESPONSE_INVALID", "not_issued", "入力と対象の指定が不正です。入力は未送信です。");
4392
+ const release = acquireSessionSendFileLock(name);
4393
+ let issued = false;
4394
+ try {
4395
+ const check = () => {
4396
+ const binding = readAuthBinding(name, kind);
4397
+ if (!binding || JSON.stringify(binding) !== JSON.stringify(expected.data))
4398
+ throw new AitermError("AGENT_AUTH_STALE_BINDING: 認証の世代が一致しません。", 2);
4399
+ // The current native process, not a shell/background job, must own the foreground tty.
4400
+ if (!isWin) {
4401
+ const checkedProcess = spawnSync("/bin/ps", ["-p", String(binding.pane_pid), "-o", "stat="], { encoding: "utf8", timeout: 1000 });
4402
+ if (checkedProcess.status !== 0 || !checkedProcess.stdout.includes("+") || /^T/.test(checkedProcess.stdout.trim()))
4403
+ throw new AitermError("AGENT_AUTH_INPUT_UNAVAILABLE: 公式認証processが端末の前面にありません。", 2);
4404
+ }
4405
+ else {
4406
+ // Native psmux transport exists, but foreground ownership has not yet been accepted on Windows.
4407
+ throw new AitermError("AGENT_AUTH_INPUT_UNAVAILABLE: Windows認証入力の所有確認が未対応です。", 2);
4408
+ }
4409
+ return binding;
4410
+ };
4411
+ const binding = check();
4412
+ const meta = loadAuthMetadata(name, kind);
4413
+ let pane = authPane(meta, stripControl(captureScreen(name, 0)));
4414
+ const answer = response.data;
4415
+ if (authPromptDigest(pane) !== options.expected_prompt_digest)
4416
+ throw new AitermError("AGENT_AUTH_STAGE_MISMATCH: 入力画面が変わりました。", 2);
4417
+ if (answer.kind === "authorization_code" && pane.ui?.input !== "authorization_code")
4418
+ throw new AitermError("AGENT_AUTH_STAGE_MISMATCH: 認証コードの入力待ちではありません。", 2);
4419
+ if (answer.kind === "choice" && !pane.ui?.choices.some(choice => choice.id === answer.id))
4420
+ throw new AitermError("AGENT_AUTH_STAGE_MISMATCH: 表示された選択肢と一致しません。", 2);
4421
+ disableAuthLogging(binding.pane_id);
4422
+ check();
4423
+ if (answer.kind === "authorization_code") {
4424
+ const buffer = `auth-${process.pid}-${randomBytes(8).toString("hex")}`;
4425
+ try {
4426
+ if (tmuxCommandWithInput(true, answer.value, "load-buffer", "-b", buffer, "-").code !== 0)
4427
+ throw new AitermError("AGENT_AUTH_INPUT_UNAVAILABLE: 入力を準備できません。", 2);
4428
+ check();
4429
+ // Fresh phase and empty field immediately before the first native input effect.
4430
+ pane = authPane(meta, stripControl(captureScreen(name, 0)));
4431
+ if (pane.ui?.input !== "authorization_code" || authPromptDigest(pane) !== options.expected_prompt_digest)
4432
+ throw new AitermError("AGENT_AUTH_STAGE_MISMATCH: 入力段階が変わりました。", 2);
4433
+ issued = true;
4434
+ if (tmux("paste-buffer", "-d", "-r", "-b", buffer, "-t", binding.pane_id).code !== 0)
4435
+ throw new Error("native input unconfirmed");
4436
+ check();
4437
+ if (tmux("send-keys", "-t", binding.pane_id, "Enter").code !== 0)
4438
+ throw new Error("native submit unconfirmed");
4439
+ }
4440
+ finally {
4441
+ tmux("delete-buffer", "-b", buffer);
4442
+ }
4443
+ }
4444
+ else {
4445
+ // Move only over freshly observed choices. Never expose raw keys to the host UI.
4446
+ for (let step = 0; step < 8; step++) {
4447
+ check();
4448
+ const screen = stripControl(captureScreen(name, 0));
4449
+ pane = authPane(meta, screen);
4450
+ if (authPromptDigest(pane) !== options.expected_prompt_digest || !pane.ui?.choices.some(choice => choice.id === answer.id))
4451
+ throw new Error("native choice changed");
4452
+ const current = pane.native_input?.kind === "choice" ? pane.native_input.selected_id : null;
4453
+ if (!current)
4454
+ throw new Error("native selection unknown");
4455
+ issued = true;
4456
+ if (tmux("send-keys", "-t", binding.pane_id, current === answer.id ? "Enter" : "Down").code !== 0)
4457
+ throw new Error("native choice unconfirmed");
4458
+ if (current === answer.id)
4459
+ break;
4460
+ if (step === 7)
4461
+ throw new Error("native choice unconfirmed");
4462
+ await sleep(80);
4463
+ }
4464
+ }
4465
+ }
4466
+ catch (error) {
4467
+ if (issued)
4468
+ throw new AuthResponseError("AGENT_AUTH_RESPONSE_UNKNOWN", "unknown", "入力後の結果を確認できません。自動再送しないでください。");
4469
+ if (error instanceof AitermError)
4470
+ throw new AuthResponseError(/^AGENT_AUTH_[A-Z_]+/.exec(error.message)?.[0] ?? "AGENT_AUTH_RESPONSE_NOT_ISSUED", "not_issued", error.message);
4471
+ throw new AuthResponseError("AGENT_AUTH_RESPONSE_NOT_ISSUED", "not_issued", "入力を送信できません。");
4472
+ }
4473
+ finally {
4474
+ release();
4475
+ }
4476
+ await sleep(100);
4477
+ try {
4478
+ const result = await authenticateAgent(kind, { action: "status", session_id: name });
4479
+ if (result.auth_ui?.input === "authorization_code" && response.data.kind === "authorization_code")
4480
+ throw new Error("native input consumption unconfirmed");
4481
+ return result;
4482
+ }
4483
+ catch {
4484
+ throw new AuthResponseError("AGENT_AUTH_RESPONSE_UNKNOWN", "unknown", "入力後の状態を確認できません。自動再送しないでください。");
4485
+ }
4486
+ }
4352
4487
  function launchAuthProcess(name, meta) {
4353
4488
  const plan = authPlan(meta.kind, meta.phase);
4354
4489
  const env = plan.env.map(([key, value]) => shq(`${key}=${value}`));
@@ -4363,9 +4498,18 @@ function launchAuthProcess(name, meta) {
4363
4498
  export async function authenticateAgent(kind, options) {
4364
4499
  if (options.action !== "status" || options.session_id !== undefined)
4365
4500
  assertExecutionContext(options.session_id);
4366
- const result = (status, session, message, pane) => ({ schema: "aiterm.agent-auth-result.v1", harness: agentHarness(kind),
4367
- status, session_id: session, url: pane?.url ?? null, user_code: pane?.user_code ?? null,
4368
- input_required: pane?.input_required ?? false, message: message ?? pane?.message ?? null });
4501
+ const result = (status, session, message, pane) => {
4502
+ const receipt = { schema: "aiterm.agent-auth-result.v1", harness: agentHarness(kind), status,
4503
+ session_id: session, url: pane?.url ?? null, user_code: pane?.user_code ?? null,
4504
+ input_required: pane?.input_required ?? false, message: message ?? pane?.message ?? null };
4505
+ receipt.auth_ui = authUi(receipt, pane, session ? readAuthBinding(session, kind) : null);
4506
+ if (isWin && receipt.auth_ui.input !== null)
4507
+ receipt.auth_ui = { ...receipt.auth_ui, stage: "unavailable", input: null, choices: [],
4508
+ message: "Windowsの認証入力は端末の前面所有の受入が未完了です。入力は送信できません。" };
4509
+ return receipt;
4510
+ };
4511
+ if (options.action === "respond")
4512
+ return respondAgentAuth(kind, options);
4369
4513
  if (options.action === "cancel") {
4370
4514
  if (!options.session_id)
4371
4515
  throw new AitermError("AGENT_AUTH_SESSION_REQUIRED: cancelにはsession_idが必要です。", 2);
@@ -4403,8 +4547,9 @@ export async function authenticateAgent(kind, options) {
4403
4547
  const kept = tmux("set-option", "-t", name, "remain-on-exit", "on");
4404
4548
  if (kept.code !== 0)
4405
4549
  throw new AitermError("AGENT_AUTH_PANE_CONFIG_FAILED: 認証processの終了観測を設定できません。", 2);
4550
+ disableAuthLogging(name);
4551
+ meta.launch_id = randomBytes(16).toString("hex");
4406
4552
  if (hasExecutionCreationScope()) {
4407
- meta.launch_id = randomBytes(16).toString("hex");
4408
4553
  meta.execution_native = executionCreatedNativeBinding(name);
4409
4554
  }
4410
4555
  withExecutionPreparation(name, () => writeJson0600(authMetadataPath(name), meta));
@@ -4444,7 +4589,7 @@ export async function authenticateAgent(kind, options) {
4444
4589
  if (observed.code !== 0)
4445
4590
  return result("failed", session, "認証processの終了状態を取得できません。");
4446
4591
  const [dead, exit] = observed.stdout.trim().split("\t");
4447
- const pane = authPane(meta, stripControl(captureScreen(session, 120)));
4592
+ const pane = authPane(meta, stripControl(captureScreen(session, 0)));
4448
4593
  if (dead === "1") {
4449
4594
  if (exit !== "0")
4450
4595
  return result("failed", session, `公式認証processが失敗しました(exit=${/^\d+$/.test(exit ?? "") ? exit : "unknown"})。`);
@@ -12,9 +12,12 @@ export const immediateStopResultSchema = z.object({
12
12
  }).strict();
13
13
  export function interruptKnownSessions(input, dependencies) {
14
14
  const args = immediateStopInputSchema.parse(input), roots = [...new Set(args.session_ids ?? [args.session_id])];
15
- let relations = [], graphUnavailable = false;
15
+ let relations = [], children = [], graphUnavailable = false;
16
16
  try {
17
- relations = unfinishedExecutionRelations(dependencies.relations());
17
+ if (dependencies.children)
18
+ children = dependencies.children();
19
+ else if (dependencies.relations)
20
+ relations = unfinishedExecutionRelations(dependencies.relations());
18
21
  }
19
22
  catch {
20
23
  graphUnavailable = true;
@@ -25,6 +28,9 @@ export function interruptKnownSessions(input, dependencies) {
25
28
  if (visited.has(key))
26
29
  return;
27
30
  visited.add(key);
31
+ for (const child of children)
32
+ if (child.parent === name)
33
+ visit(child.child);
28
34
  for (const edge of relations)
29
35
  if (edge.source?.launch.session_id === name
30
36
  && (!launchId || edge.source.launch.launch_id === launchId) && edge.target)
@@ -14,6 +14,28 @@ import { AITERM_PROFILE } from "./steer-profile.js";
14
14
  import { EXECUTION_SOURCE_ENV_NAMES, EXECUTION_SCOPE_ENV_NAMES } from "./execution-runtime.js";
15
15
  import { observeExecutionActor } from "./process-runtime.js";
16
16
  import { ExecutionError } from "./execution-wire.js";
17
+ import { createAitermServer } from "./index.js";
18
+ import { immediateStopInputSchema, immediateStopResultSchema } from "./execution-immediate-stop.js";
19
+ /** Ordinary delivery and leaf-first cancel keys have no execution-store
20
+ * registration, participant gate, shared transaction or graph write. */
21
+ export function createNativeDeliveryServer(dependencies = {}) {
22
+ const server = createAitermServer();
23
+ server.registerTool("agent_stop", {
24
+ description: "指定sessionとその呼出先へ末端から通常の停止キーを送る。sentはキー送信結果であり終了の証明ではない。",
25
+ inputSchema: immediateStopInputSchema.shape,
26
+ outputSchema: immediateStopResultSchema.shape,
27
+ }, async (args) => {
28
+ const result = interruptKnownSessions(args, {
29
+ children: dependencies.children ?? core.agentSessionChildren,
30
+ currentLaunchId: dependencies.currentLaunchId ?? core.interruptionLaunchId,
31
+ sendKey: dependencies.sendKey ?? core.interruptSessionNow,
32
+ });
33
+ return { content: [{ type: "text", text: JSON.stringify(result) }], structuredContent: result };
34
+ });
35
+ return server;
36
+ }
37
+ /** Historical injected tracking composition. Normal CLI delivery does not
38
+ * construct this participant or register its execution controls. */
17
39
  export function createNativeExecutionServer(input) {
18
40
  if (input.interruptCurrentRun !== true || !input.expectedScope || !path.isAbsolute(input.expectedScope.state_root)
19
41
  || input.expectedScope.multiplexer?.kind !== "socket" || !path.isAbsolute(input.expectedScope.multiplexer.key))
@@ -90,6 +112,7 @@ function nativeParentDependencies(provider, profile) {
90
112
  },
91
113
  processes: readProcessIdentities, exists: nativeProcessExists, now: () => performance.now(), wall: () => Date.now() };
92
114
  }
115
+ /** Historical injected worker; normal parent-hook CLI no longer uses it. */
93
116
  export function createNativeExecutionWorker(input) {
94
117
  if (input.interruptCurrentRun !== true || !input.expectedScope || !path.isAbsolute(input.expectedScope.state_root)
95
118
  || input.expectedScope.multiplexer?.kind !== "socket" || !path.isAbsolute(input.expectedScope.multiplexer.key))
@@ -116,23 +139,22 @@ export function parseNativeExecutionArguments(args) {
116
139
  }
117
140
  async function main() {
118
141
  const options = parseNativeExecutionArguments(process.argv.slice(2));
119
- const provider = await import(pathToFileURL(options.providerFile).href);
120
142
  if (options.mode === "mcp") {
121
- const participant = createNativeExecutionServer({ interruptCurrentRun: true, expectedScope: options.expectedScope, provider });
122
- await participant.server.connect(new StdioServerTransport());
143
+ await createNativeDeliveryServer().connect(new StdioServerTransport());
123
144
  }
124
145
  else {
146
+ const provider = await import(pathToFileURL(options.providerFile).href);
125
147
  const method = options.mode === "claude-hook" ? "runClaudeHookMain" : options.mode === "cursor-hook" ? "runCursorHookMain" : "runCodexHookMain";
126
148
  const hookProvider = provider;
127
149
  if (typeof hookProvider[method] !== "function")
128
150
  throw new ExecutionError("EXECUTION_UNSUPPORTED");
129
- const worker = createNativeExecutionWorker({ interruptCurrentRun: true, expectedScope: options.expectedScope, provider, codexHome: options.codexHome });
151
+ const profile = { ...AITERM_PROFILE, state_root: () => options.expectedScope.state_root };
130
152
  if (options.mode === "claude-hook")
131
- await hookProvider.runClaudeHookMain(worker.profile, path.join(worker.scope.state_root, "claude-parent-hooks"));
153
+ await hookProvider.runClaudeHookMain(profile, path.join(options.expectedScope.state_root, "claude-parent-hooks"));
132
154
  else if (options.mode === "cursor-hook")
133
- await hookProvider.runCursorHookMain(worker.profile, path.join(worker.scope.state_root, "cursor-parent-hooks"));
155
+ await hookProvider.runCursorHookMain(profile, path.join(options.expectedScope.state_root, "cursor-parent-hooks"));
134
156
  else
135
- await hookProvider.runCodexHookMain(worker.profile, options.hookDirectory);
157
+ await hookProvider.runCodexHookMain(profile, options.hookDirectory);
136
158
  }
137
159
  }
138
160
  let entry = false;
@@ -5,60 +5,55 @@ import { pathToFileURL } from "node:url";
5
5
  import { parseNativeExecutionArguments } from "./execution-native-server.js";
6
6
  import { AITERM_PROFILE } from "./steer-profile.js";
7
7
  import { ExecutionError } from "./execution-wire.js";
8
+ import { codexHookCommand } from "./setup-codex-hooks.js";
8
9
  export async function configureNativeExecutionHooks(home, options) {
9
10
  if (process.platform === "win32" || !path.isAbsolute(home) || !path.isAbsolute(options.registration.command)
10
11
  || !path.isAbsolute(options.registration.args[0] ?? "") || path.basename(options.registration.args[0]) !== "execution-native-server.js")
11
12
  throw new ExecutionError("EXECUTION_INVALID");
12
- const registration = structuredClone(options.registration), scope = structuredClone(options.expectedScope);
13
- const parsed = parseNativeExecutionArguments(registration.args.slice(1));
13
+ const previousRegistration = structuredClone(options.registration), scope = structuredClone(options.expectedScope);
14
+ const parsed = parseNativeExecutionArguments(previousRegistration.args.slice(1));
14
15
  if (parsed.mode !== "mcp" || JSON.stringify(parsed.expectedScope) !== JSON.stringify(scope)
15
16
  || path.resolve(parsed.providerFile) !== path.resolve(options.providerFile))
16
17
  throw new ExecutionError("EXECUTION_CONFLICT");
17
18
  const provider = options.provider ?? await import(pathToFileURL(options.providerFile).href);
18
- if (provider.DELIVERY_ADMISSION_CONTRACT?.native_hook_args !== true)
19
- throw new ExecutionError("EXECUTION_UNSUPPORTED");
20
19
  const clients = [...new Set(options.clients ?? ["claude", "cursor", "codex"])];
21
20
  if (clients.some(client => !["claude", "cursor", "codex"].includes(client))
22
21
  || (clients.includes("codex") && (!options.codexBinary || !path.isAbsolute(options.codexBinary))))
23
22
  throw new ExecutionError("EXECUTION_INVALID");
23
+ const dist = path.dirname(previousRegistration.args[0]);
24
+ const registration = { command: previousRegistration.command, args: [path.join(dist, "index.js")] };
24
25
  const profile = { ...AITERM_PROFILE, state_root: () => scope.state_root,
25
- config_root: () => path.join(home, ".config", "aiterm-mcp"), hooks: {
26
- claude: path.basename(registration.args[0]), cursor: path.basename(registration.args[0]), codex: path.basename(registration.args[0]),
26
+ config_root: () => path.join(home, ".config", "aiterm-mcp") };
27
+ const previousProfile = { ...profile, hooks: {
28
+ claude: path.basename(previousRegistration.args[0]), cursor: path.basename(previousRegistration.args[0]), codex: path.basename(previousRegistration.args[0]),
27
29
  } };
28
- // Hook processes may inherit a reduced CLI environment. Set only the two
29
- // non-secret scope roots before Node loads its native constants; preserve
30
- // the caller's dynamic source/launch env instead of stamping a setup-time ID.
31
- const prefix = ["AITERM_STATE_BASE=" + path.dirname(scope.state_root),
32
- "TMPDIR=" + path.dirname(path.dirname(scope.multiplexer.key)), registration.command, registration.args[0],
33
- "--execution-interrupt", "--state-root", scope.state_root, "--socket", scope.multiplexer.key,
34
- "--steer-provider", options.providerFile];
35
- const hook = (mode, extra = []) => ({ command: "/usr/bin/env", script: prefix[0], args: [...prefix.slice(1), "--mode", mode, ...extra] });
30
+ const hook = (file) => ({ command: registration.command, script: path.join(dist, file) });
36
31
  const quote = (word) => "'" + word.replace(/'/g, "'\"'\"'") + "'";
37
- const command = (mode, extra = []) => ["/usr/bin/env", ...prefix, "--mode", mode, ...extra].map(quote).join(" ");
32
+ const command = (file) => [registration.command, path.join(dist, file)].map(quote).join(" ");
38
33
  const result = {}, commands = {};
39
34
  if (clients.includes("claude")) {
40
35
  const file = path.join(home, ".claude", "settings.json");
41
- const removed = provider.removeClaudeParentHooks(AITERM_PROFILE, file);
42
- result.claude = provider.mergeClaudeParentHooks(profile, file, hook("claude-hook"));
36
+ const removed = provider.removeClaudeParentHooks(previousProfile, file);
37
+ result.claude = provider.mergeClaudeParentHooks(profile, file, hook(profile.hooks.claude));
43
38
  if (removed === "removed" && result.claude === "unchanged")
44
39
  result.claude = "configured";
45
- commands.claude = "exec " + command("claude-hook");
40
+ commands.claude = "exec " + command(profile.hooks.claude);
46
41
  }
47
42
  if (clients.includes("cursor")) {
48
43
  const file = path.join(home, ".cursor", "hooks.json");
49
- const removed = provider.removeCursorParentHooks(AITERM_PROFILE, file);
50
- result.cursor = provider.mergeCursorParentHooks(profile, file, hook("cursor-hook"));
44
+ const removed = provider.removeCursorParentHooks(previousProfile, file);
45
+ result.cursor = provider.mergeCursorParentHooks(profile, file, hook(profile.hooks.cursor));
51
46
  if (removed === "removed" && result.cursor === "unchanged")
52
47
  result.cursor = "configured";
53
- commands.cursor = command("cursor-hook");
48
+ commands.cursor = command(profile.hooks.cursor);
54
49
  }
55
50
  if (clients.includes("codex")) {
56
51
  const directory = path.join(home, ".config", "aiterm-mcp", "codex-parent-hooks"), codexHome = path.join(home, ".codex");
57
- const nativeCommand = command("codex-hook", ["--codex-home", codexHome, "--hook-directory", directory]);
52
+ const hookFile = path.join(dist, profile.hooks.codex);
58
53
  const runtime = { ...options.codexRuntime, directory, codex_home: codexHome, node: registration.command,
59
- hook: registration.args[0], findBinary: () => options.codexBinary, hookCommand: () => nativeCommand };
54
+ hook: hookFile, findBinary: () => options.codexBinary };
60
55
  result.codex = await provider.configureCodexSteer(profile, "enable", runtime);
61
- commands.codex = nativeCommand;
56
+ commands.codex = codexHookCommand(registration.command, hookFile, directory);
62
57
  }
63
- return { results: result, commands, registration, relation_scope: "recorded" };
58
+ return { results: result, commands, registration, relation_scope: "untracked" };
64
59
  }
@@ -704,13 +704,88 @@ export function claudeAuthStatus(bin, cwd, env = process.env) {
704
704
  return { status: "unauthenticated", message: null };
705
705
  return { status: "failed", message: "Claude Codeの公式認証状態の確認に失敗しました。" };
706
706
  }
707
+ /** Current official Claude authorization request. `code=true` requests a
708
+ * displayed authorization code; a callback's `code=<credential>` remains
709
+ * private. Keep this exception at the vendor owner, not the shared filter. */
710
+ export function claudeAuthorizationUrl(screen) {
711
+ const legacy = authUrl(screen, ["claude.ai", "console.anthropic.com", "platform.claude.com"]);
712
+ if (legacy)
713
+ return legacy;
714
+ for (const match of screen.matchAll(/https:\/\/[^\s<>"']+/gu)) {
715
+ const candidate = match[0].replace(/[).,;]+$/u, "");
716
+ let url;
717
+ try {
718
+ url = new URL(candidate);
719
+ }
720
+ catch {
721
+ continue;
722
+ }
723
+ if (url.hostname !== "claude.com" || url.pathname !== "/cai/oauth/authorize"
724
+ || url.searchParams.getAll("code").length !== 1 || url.searchParams.get("code") !== "true"
725
+ || url.searchParams.get("response_type") !== "code"
726
+ || url.searchParams.get("code_challenge_method") !== "S256"
727
+ || !/^[A-Za-z0-9_-]{43}$/u.test(url.searchParams.get("code_challenge") ?? ""))
728
+ continue;
729
+ url.searchParams.delete("code");
730
+ if (authUrl(url.href, ["claude.com"]) !== null)
731
+ return candidate;
732
+ }
733
+ return null;
734
+ }
735
+ /** One current-phase parse owns UI choices, selected item and composer occupancy. */
736
+ export function claudeAuthInput(screen) {
737
+ const lines = screen.split("\n");
738
+ let phase = null;
739
+ for (const [at, line] of lines.entries()) {
740
+ if (line.includes("Choose the text style that looks best with your terminal"))
741
+ phase = { kind: "theme", at };
742
+ else if (line.includes("Select login method:"))
743
+ phase = { kind: "login", at };
744
+ else if (/^\s*(?:Paste (?:the )?code|Enter (?:the )?(?:authorization )?code)/i.test(line))
745
+ phase = { kind: "code", at };
746
+ else if (/Press (?:Enter|Return)|do you trust|trust this folder/i.test(line))
747
+ phase = { kind: "unknown", at };
748
+ }
749
+ if (!phase || phase.kind === "unknown")
750
+ return {};
751
+ if (phase.kind === "code") {
752
+ const empty = /^(?:Paste (?:the )?code(?: here)?(?: if prompted)?|Enter (?:the )?(?:authorization )?code)\s*[:>]?\s*$/i.test(lines[phase.at].trim())
753
+ && lines.slice(phase.at + 1).every(line => !line.trim());
754
+ return { native_input: { kind: "authorization_code", empty }, ...(empty ? { ui: {
755
+ stage: "paste_code", title: "公式ページの認証コードを入力", message: "公式ページを開いて認証し、表示されたコードをこの画面の伏せ字欄へ貼り付けてください。",
756
+ input: "authorization_code", choices: [],
757
+ } } : {}) };
758
+ }
759
+ const theme = phase.kind === "theme";
760
+ const rows = [];
761
+ for (const line of lines.slice(phase.at + 1)) {
762
+ const match = /^\s*(❯)?\s*([1-7])\.\s*(.+?)\s*$/.exec(line);
763
+ if (!match) {
764
+ // A later composer/cursor or unrecognised numbered row is not this menu.
765
+ if (/^\s*❯|^\s*\d+[.)]/.test(line))
766
+ return {};
767
+ continue;
768
+ }
769
+ const allowed = theme ? /^(?:Auto \(match terminal\)|(?:Dark|Light) mode(?: \([^\n]+\))?)$/ : /^(?:Claude account with subscription|Anthropic Console account|3rd-party platform)$/;
770
+ if (!allowed.test(match[3]))
771
+ return {};
772
+ rows.push({ id: `option-${match[2]}`, label: match[3], cursor: !!match[1] });
773
+ }
774
+ const selected = rows.filter(row => row.cursor);
775
+ if (!rows.length || selected.length !== 1 || new Set(rows.map(row => row.id)).size !== rows.length)
776
+ return {};
777
+ return { native_input: { kind: "choice", selected_id: selected[0].id }, ui: {
778
+ stage: "select", title: theme ? "表示テーマを選ぶ" : "Claudeのログイン方法を選ぶ", message: "公式CLIに表示された選択肢を選んでください。",
779
+ input: "choice", choices: rows.map(({ id, label }) => ({ id, label })),
780
+ } };
781
+ }
707
782
  export function claudeAuthPane(screen, onboarding = false) {
708
- const url = authUrl(screen, ["claude.ai", "console.anthropic.com", "platform.claude.com"]);
783
+ const url = claudeAuthorizationUrl(screen);
709
784
  const input = /Paste (?:the )?code|Enter (?:the )?(?:authorization )?code|Choose the text style|Select login method:|Press (?:Enter|Return)|do you trust|trust this folder/i.test(screen);
710
785
  const complete = onboarding && (claudeTuiReady(screen) || /Is this a project you created or one you trust/.test(screen));
711
786
  return { url, user_code: null, input_required: input && !complete,
712
787
  message: complete ? null : input ? "Claude Codeの公式画面で入力または初回案内の選択を完了してください。" : null,
713
- onboarding_complete: complete };
788
+ onboarding_complete: complete, ...(complete ? {} : claudeAuthInput(screen)) };
714
789
  }
715
790
  /** `/fast on|off` を送る前の画面と後の画面から、増えた結果の行を読む。まだ出ていなければnull。 */
716
791
  export function claudeFastResult(before, after) {
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ import { AuthResponseError, authUiSchema, authBindingSchema, authResponseSchema } from "./agent-auth-ui.js";
2
3
  /**
3
4
  * aiterm-mcp — AI が握るローカル永続端末を stdio MCP サーバとして公開する(Node/TS 版)。
4
5
  *
@@ -882,14 +883,17 @@ export function createAitermServer(options = {}) {
882
883
  });
883
884
  registerRemoteAwareTool("agent_auth", {
884
885
  description: "各harnessの公式CLI認証を開始・確認・取消する。CLIごとのコマンドと認証URL/codeの抽出はAitermが所有し、資格情報は各CLIだけが保存する。" +
885
- "waitingのURL/codeを人へ表示し、input_requiredなら同じsessionのpty_send/pty_keyで公式画面へ入力する。" +
886
+ "auth_uiの段階と公式URL・選択肢を表示する。入力はrespondで同じbindingへ送り、pty_send/pty_keyへ認証コードを渡さない。" +
886
887
  "authenticatedは認証の結果であり、agent_launchの起動準備完了とは別。remoteは他toolと同じ標準対応。" +
887
888
  "startは公式の状態が認証済みなら何も起こさずauthenticatedを返す。relogin:trueを付けると、認証済みに見えても公式ログインを開始する" +
888
889
  "(別のアカウントへ入り直す時、ログインの期限切れを状態から見抜けない時)。Aitermは資格情報を消さず、置き換えは公式CLIが行う。" +
889
890
  "Codexの公式ログインは、始めた時点で元のログインを消す(途中でcancelしても戻らない)。",
890
891
  inputSchema: {
891
892
  harness: z.enum(["claude-code", "codex-cli", "grok-cli", "cursor-cli"]),
892
- action: z.enum(["start", "status", "cancel"]),
893
+ action: z.enum(["start", "status", "cancel", "respond"]),
894
+ expected_binding: authBindingSchema.optional(),
895
+ expected_prompt_digest: z.string().regex(/^[a-f0-9]{64}$/).optional(),
896
+ response: authResponseSchema.optional(),
893
897
  session_id: z.string().regex(/^[A-Za-z0-9_-]{1,64}$/).optional(),
894
898
  cwd: z.string().optional().describe("公式CLIを実行する作業ディレクトリの絶対パス"),
895
899
  env_vars: z.array(z.string().regex(/^[A-Za-z_][A-Za-z0-9_]*$/)).optional().describe("startで認証PTYへ引き継ぐ環境変数名。値はreceiptへ返さない"),
@@ -897,6 +901,7 @@ export function createAitermServer(options = {}) {
897
901
  },
898
902
  outputSchema: {
899
903
  schema: z.literal("aiterm.agent-auth-result.v1"),
904
+ auth_ui: authUiSchema.optional(),
900
905
  harness: z.enum(["claude-code", "codex-cli", "grok-cli", "cursor-cli"]),
901
906
  status: z.enum(["waiting", "authenticated", "blocked", "failed"]),
902
907
  session_id: z.string().nullable(),
@@ -911,6 +916,11 @@ export function createAitermServer(options = {}) {
911
916
  return { content: [{ type: "text", text: JSON.stringify(result) }], structuredContent: { ...result } };
912
917
  }
913
918
  catch (error) {
919
+ if (error instanceof AuthResponseError)
920
+ return { ...fail(error), structuredContent: {
921
+ schema: "aiterm.auth-response-error.v1", harness, session_id: options.session_id ?? null,
922
+ code: error.reason_code, effect: error.effect, message: error.message,
923
+ } };
914
924
  return fail(error);
915
925
  }
916
926
  });
package/docs/DESIGN.md CHANGED
@@ -1,93 +1,22 @@
1
1
  # Aiterm design
2
2
 
3
- ## 開発中: 連鎖停止
4
-
5
- 2026-10-21夜の指揮官訂正を優先し、停止ボタン/連鎖/全体停止は現在処理を中断してsession/launch/会話を残す。
6
- [ADR0125](https://github.com/kitepon/aiterm-mcp/blob/main/docs/adr/0125-session-preserving-interruption.md)が現契約。
7
- 正規native入口のagent_stopは `{session_id}` / `{session_ids:[...]}` を受け、Aitermが記録する未完了呼出関係を
8
- leaf→rootで辿り、対象ごとに通常中断キーを1回送る。Codex/ClaudeはEscape、Cursor/GrokはCtrl+C。
9
- Ctrl+Cは既存paneのbusy時だけ、idleは送信せずidle、不明/拒否/送信失敗はfailedを返す。
10
- 結果のsentはキー送信受付でありnative終了観測ではない。会話をclose/killしない。
11
- 停止経路はgraph snapshot読取だけで、writer lock/fsync/drain/journal/全actor・native終了proofを待たず、
12
- 同sessionの次入力を永久に抑止しない。旧v2の確認controllerは停止ボタンから外した。
13
- 通常main/稼働登録への反映は別で、native hooksは同scope/providerのexecution-native-setupが生成する。
14
-
15
- 実行関係のsource/target世代、Bridgeの既存参照、未完了関係の寿命と混在先も含む停止計画は
16
- [ADR 0124の契約案](https://github.com/kitepon/aiterm-mcp/blob/main/docs/adr/0124-cascade-stop-execution-contract.md)で設計中。
17
- `execution-*.ts`は隔離枝のwire、snapshot計画、共有metadata、受付/停止サービスと注入可能なMCP口。
18
- fake MCPで検証中であり、runtime証拠adapterと全変更口への接続は未了。現行公開toolの停止契約は変更していない。
19
- native親配送は、要求時に固定したSDK metadata・検証済みsource世代・MCP/native harness identityを結ぶ。
20
- serving producerは最初のawait前にnative decoder/session/tool/ownerを元requestへ固定し、欠測を後で補完しない。Claude ownerは同じharness PID/startで、別のMCP/hook descendantを代用しない。source入力のnative受理/turn aliasではない。
21
- 別processのhookは同じscopeと既存delivery bindingを照合し、callback欠測時に旧配送へfallbackしない。
22
- processごとのrequired profile factoryは隔離実装中であり、static profileや稼働hookへは未接続。
23
- scope一致やhook返却だけで全参加者の被覆・native turn alias・終了fenceを証明したことにはしない。
24
- Claudeのproducerは実PreToolUse owner/tool/sessionと配送bindingを作用直前にも確認する隔離factoryを持つ。
25
- 既存parent returnが未解決なら局所doneのfenceを閉じず、元dispatch receiptの遅着は入力を再送せずmetadataだけを待つ。
26
- native emit直前に照合したhook参照は既存deliveryへ保存する。CodexのStop/PostToolUse、Claudeのtool、Cursorのconversationを
27
- 受取executionの完了と同一視せず、canonical cursorを作らない。受取番の対応と終了fenceの証拠は別に必要。
28
- POSIXのraw tty回復もnative作用であり、tmux打鍵と同じ受付を通す。ttyは正確なsession selectorで読み、
29
- 実stty直前にも同じdeviceを確認する。停止や世代/tty交代後に古いdeviceへ回復を発行しない。
30
- 一つのRPCが複数のnative作用を発行するときも、各回に同じissued slotとsource/target世代・停止を再確認する。
31
- agentの貼付け・Enterの同期区間は、既存metadata mutexを非同期に取得してから実行する。
32
- native作成後に再開する起動準備も同じ非同期取得を通す。pane所有検査、fg/stty回復、
33
- 起動メニューの各キーを短い同期区間で実行し、待機後の停止・世代・slot検査を省略しない。
34
- mutexをawait中へ持ち越さず、同じ同期stack内の各gateが発行記録を永続化してからnative作用へ進む。
35
- 検証済みsourceと同世代への通常sendがissuedである間も、そのsourceの送信先が別ならoutgoing RPCは継続できる。
36
- incomingのslotを解放・書換・成功扱いせず、unknown・管理操作・世代不一致は従来どおり拒否する。
37
- incomingの完了待ちをoutgoingへ課して相互送信を循環待ちにしない(ADR 0127)。
38
- BellTeamのpromptなし明示管理launchは、管理close同様にserving process/request identityへ結合する。
39
- promptやThroughline読出しを含む未登録送信、auto-name、remoteはこの管理例外へ含めない。
40
- BellTeamのrefなしagent_configureも同じnative process/request identityの管理RPCとして受ける。
41
- model/effort/fastの各stageは同じ既存世代・slot・source・停止のgateを通り、途中で変われば続きの打鍵を拒否する。
42
- 発行後の途中失敗はpossible/unknownを保持する。CLI結果待ち後のfast metadata書込も同じ準備受付で再検査する(ADR 0129)。
43
- 新launchは、既存POSIX serverのnative owner、またはserver未作成の完全な空観測を受付時と発行直前に確認する。
44
- 冷間時は最初のnew-session自身でserverを作り、その実receiptのnative IDとserver PIDを、作成後の安定したnative一覧とprocess開始識別子で照合する。
45
- 既存server時も冷間時も、作成したsessionのnative ID・server owner・core内部のlaunch世代を同じ要求slotへ段階結合する。
46
- 物理shell準備はnative IDへ固定し、未結合の同名世代を採用しない。冷間起動のための補助sessionや再送は作らない。
47
- 作成時のnative ID/server ownerをmanaged metadataへ保存し、private runtimeの世代読取と期待closeでも再照合する。
48
- 古いmetadataの欠測を現観測で補わず、既存席の自動登録は行わない。
49
- 証拠の無い新server起動、Windows固有のserver作成、一般のauto-name/通常PTY等はまだunsupported。認証専用PTYはADR 0130の限定経路で受け付ける。
50
- 実18 toolsのhandlerを作るconstructorは、private dispatcher/親execution callbackを明示注入して隔離SDKで検証できる。
51
- 通常のstdio mainは注入せず、全参加process/全core作用口・native fenceが揃うまで公開有効化しない。
52
- private participant factoryはproducer/service/dispatcher/同process用worker profileを同じscopeとnative actorで組む。
53
- source環境の既存8名は一度だけ読み、必要な値だけを固定してproducerとdispatcherへ渡す。
54
- 呼出側のprovider/profile/runtime methodの差替えは構築済みparticipantへ混入させず、現在のscope/actorの変化は作用前に拒否する。
55
- 親managerの保存先とownerも同じscope/actorへ照合する。別processのhookはactorを流用せず、自分のworker factoryで接続する。
56
- native runtimeの自己actorは、現在実行中のprocessのPIDと開始identityを一度だけOSで確定して保持する。
57
- 同processの生存中に開始identityは変わらないため、scope確認ごとの自己PID照会を行わない。
58
- 初回の欠測・失敗は確定値として保持せず、返すobjectも複製する。注入runtimeのactor、他process・server・
59
- source/target世代の観測は保持せず、従来どおり各受付で再照合する。
60
- 注入runtimeへ欠けたparent依存を既定OS処理で補わず、完全なfake依存を要求する。このfactoryも通常mainには未注入。
61
- 同producer serviceの停止closeは、既存のissued close slotを持つ間だけ同期の期待世代primitiveへ渡す。
62
- 実killの直前にもruntimeの同期世代読取を通し、scope/actor/期待launchの交代を拒否する。
63
- 注入runtimeのclose欠測をcore既定処理で補わず、作用後の応答欠測は消失観測があってもunknown slotを保持する。
64
- 完了の照会点は固定し、終了fenceの非同期応答後にも現在世代を確認する。実受取番/終了fenceの証拠adapterは未了。
65
- 通常のcompletion終了確認は元request/Pointを共有lock内で固定し、native/transcript観測をlock外で行う。
66
- 反映時に同じrecord全体・issued/unknown・元slot・親作用を再照合し、途中変更があれば反映しない。
67
- 親返却のpending判定は反映時のstateで行い、元unknownの解除や送信の再実行には使わない。
68
- 実行登録も同じrequestの既存予約を短い共有lock内で確認してから、source/targetの非同期観測をlock外で行う。
69
- 登録反映時に同じ予約・取消・source/targetの受付条件を再確認し、実作用直前の同期世代gateは維持する。
70
- launch/sendの返却結果は元issued slotを固定してlock外で世代観測し、同じslotとissued状態だけに結合する。
71
- 途中のunknown化・slot交代・観測した世代の不一致はpossibleとして保持し、元作用を再実行しない。
72
- 遅延した要求取消は本物の受付結果を消さず、共有lock内へ非同期の観測待ちを戻さない。
73
- private producer/worker factoryを構築したprocessではnative effect scopeを必須にする。
74
- coreの公開変更入口も、打鍵や設定・認証・lock/metadata準備に入る前に対象のrequest contextを要求する。
75
- この入口確認はissuedへの昇格ではない。実作用時には既存の同期受付を別に通す。
76
- 通常stdio mainはprivate factoryを使わず既定挙動を維持する。別processの未登録参加者や非同期準備中の全metadata作用を
77
- この入口だけで覆ったとはしない。全参加者inventoryと各実作用の接続を確認するまでcoverage incompleteを維持する。
78
- deferred frameには既存requestと期待世代を再照合する同期の準備callbackを渡す。
79
- core入口の再確認と、managed metadata/hook生成・設定model保存は同じsource/target/slot条件を使う。
80
- 準備callbackは短い共有metadata lock内で動き、execution graphはread-onlyのまま、preparingをissuedへ変えない。
81
- native作用やawaitをcallbackへ入れず、停止・世代/actor/scope交代・RPC終了後の遅着を拒否する。
82
- これらの明示接続以外の設定復元や親配送metadata経路はまだ未了であり、既定mainへの有効化条件を満たさない。
83
- private factoryは同scopeの共有stateへ既存PID/開始identityと役割を宣言し、各受付の同じlock内で欠測を拒否する。
84
- 遅延登録する参加者の最初の非同期要求は、既存の上限付きmetadata受付内で宣言を保存してから進む。
85
- 待機後にもscope/actorを照合し、read-only要求でも宣言だけは独立して保存する。
86
- 同期入口の即時拒否、登録削除後の拒否、停止snapshotの登録不要条件は変えず、入力や要求を再送しない。
87
- MCP/parent-effectの既存参照とnative identityを読むinventoryはread-onlyで、役割やenvから世代・終了を補完しない。
88
- 開始identity交代や未登録actorを示しても旧slotを消さず、parent受付だけで参照を退役させない。
89
- 全行のidentity一致でも参加者の全発見/native終了fence/全経路の接続を証明せず、coverage incompleteを維持する。
90
- 作用後のmetadata受付欠測では成功receiptとpossibleを保ち、issued slotを再構築や消失観測で再実行可能へ変えない。
3
+ ## 通常配送と停止
4
+
5
+ 通常MCPの18 toolsは既存のterminal/harnessへ直接渡す。execution graphへの登録、participant宣言、
6
+ shared writer lockの取得・待機を配送条件にしない。記録の欠測や旧lock fileで本文を拒否しない。
7
+ 旧execution-native-serverのMCP互換入口も標準の配送handlerを使い、agent_stopだけを追加する。
8
+ agent_executionとagent_execution_rootsをこの入口へ登録しない。本体は旧ExecutionBridgeの先行登録を要求しない。
9
+
10
+ 親hookのClaude/Cursor/Codex入口は通常providerを直接呼ぶ。execution workerやdelivery_admissionを渡さない。
11
+ 旧native hook設定は通常のparent-hook scriptへ移行し、他製品のhook・履歴を保持する。
12
+
13
+ agent_stopは指定sessionと既存agent metadataのparent_session_idを使い、末端から通常の中断キーを送る。
14
+ 共有execution graphの読書き、writer lock、fsync、journal、記録整合性の確認待ちは行わない。
15
+ 親子情報を読めなくても指定rootへはキーを送る。各宛先の送信結果を返し、キー受付を終了証明にしない。
16
+ 会話をclose/killせず、本文の自動再送や追跡記録の未送信化を行わない。
17
+
18
+ 旧共有受付の設計は撤回した履歴として
19
+ [archive](https://github.com/kitepon/aiterm-mcp/blob/main/docs/archive/20261024-retired-shared-execution-admission.md)へ移した。
91
20
 
92
21
  ## 目的と所有
93
22
 
@@ -499,44 +428,6 @@ ADR 0074(repositoryの`docs/adr/0074-agent-model-catalog.md`)。
499
428
  境界失敗は明示errorにする。retry、別model、別harness、別backendへ自動fallbackしない。
500
429
  stale send lockは並行processとのABAを避けるため自動削除せず、公開APIでは対象sessionを`pty_close`して
501
430
  同じIDで再作成する。全session一括停止は公開しない。
502
- 実行ラッパーの要求参照の検証とMCP receipt整形は`execution-tools.ts`、native作用の進行は
503
- `execution-invocation.ts`が所有する。実行・保存の判断は既存`ExecutionService`へ委ねる。
504
- receiptの観測は、通常経路ではbody返却直後、deferred経路ではnative frameの正常返却後に行う。
505
- その後の結合失敗でも観測済みreceiptを保持し、frame内のnative拒否を成功へ変換しない。
506
- native一覧は終了コード1・空stdout・単独の`server exited unexpectedly`だけを20ms間隔、最長500msで再観測する。
507
- 再観測は呼出元の同じissued枠と世代検査の中で行う。不在の証拠は既存の同socket・終了コード1・空stdoutの
508
- 厳密な診断に限定し、正常一覧は従来のnative/server識別検査を通す。上限・別error・曖昧な一覧はunknownを保つ。
509
- close・送信・その他native作用は再実行しない(ADR 0133)。
510
- POSIXの明示native execution構成では、返却済みsendのunknown枠が対象席の管理closeまで
511
- 永久に拒否しないよう、通常`pty_close`だけを同じ期待launch/native IDで受付できる。
512
- unknown send枠を保持したまま既存core closeへ進み、native close成功と席の不在を確認した後だけ
513
- 該当send枠を解放する。元recordのphase/completion/sendはunknownのまま残し、再送や成功化をしない。
514
- 旧callbackは未知/消失した元slotで拒否され、同IDの新launchへnative作用を発行できない。
515
- issued、管理close自体のunknown、親作用のslot、世代/native/serverの不明・交代は対象外。
516
- 既存の送信・待機lockの拒否も維持し、他席やissuerの停止を復旧条件へ追加しない。
517
- 返却済みunknown launchも、元native/server・managed世代が一致し、raw initial_prompt:none・初期配送なし・
518
- send:not_issuedで、harnessの終了を現在の同期primitiveが証明できる場合だけ、同じ明示管理closeを許す(ADR 0131)。
519
- 受付時とnative作用直前にこの証拠を検査し、closeと不在確認後に該当slotを外す。元unknown recordを保ち、
520
- 旧requestのlaunch/sendは再実行しない。生存harnessはreconcile_launchの対象として維持する。
521
- 本体からの明示復旧には既存agent_executionのclose_launch actionを使う(ADR 0132)。元requestと
522
- target(session_id,launch_id)を原子的な期待条件として受付と作用直前に照合し、応答SDK自身の管理refで
523
- 既存close primitiveへ進む。呼出元がobserveしたsessionだけをcloseする手順で代用しない。
524
- close・不在確認・metadata commit後だけ_meta["aiterm/closed_launch"]のversion1 receiptを返す。
525
- structuredContentは別管理requestのrecord、元unknown recordは不変。結果不明や保存失敗にはclosed証明なし。
526
- 本体は元pending操作・stop/versionを別途照合し、後発不明を過去receiptで解除しない。
527
- 非同期launcherはCLIの検証をgraph mutex外で行い、実create→metadata公開→世代結合→CLI入力の同期区間を
528
- 一度のnative admission内で通す。起動準備のawaitをmutex内に持ち込まず、待機後には通常の世代・停止・slot検査を行う。
529
- 空起動の結果不明は明示 `agent_execution/reconcile_launch` で既存世代へ結合できる(ADR 0128)。
530
- 対象は返却済みunknown launch、send:not_issued、元native/server stamp付きの単独slotだけ。
531
- 同じmetadata transaction内のprivate同期primitiveで現在のnative/managed世代、生のinitial_prompt:none・初期配送なし、
532
- harness生存とidleを照合する。既存recordのlaunchをaccepted、targetを当該世代へ結合し、該当slotだけを外す。
533
- これは現在世代の明示採用で、失われたRPC receiptの復元ではない。本文・初期prompt・claim・nativeには作用しない。
534
- 同じaccepted targetへの再照合もsend:not_issuedなら現在のmanaged世代・生の未入力状態・入力待ちを再確認し、
535
- 同じsessionへの競合slotや親の未確定作用があれば拒否する。保存済みacceptedを後の世代の不明解除の証拠にしない。
536
- issued、入力済み・不明、停止、別slot、世代/native/serverの不一致は拒否。後続sendは元source世代を含め通常受付を通る。
537
- 遅着した元callbackはまだ所有するissued stage/slotがある時だけunknownを書け、照合後や別stageを上書きしない。
538
- native handlerのisErrorは元の本文を保持し、`aiterm/execution_error`のcode/effectを併記する。
539
- native作用後の不明はpossibleのままで、原因説明を再送可否や成功receiptへ変換しない。
540
431
  agent sessionへの送信1本を守る`<name>.agent-send.lock`は持つ時間が長いので、持ち主が終了した残骸を次の送信が片付ける。消す役を`<name>.agent-send.lock.reap`の排他作成で1つに絞り、
541
432
  その中でもう一度確かめてから消すので、確かめた後で別のlockに入れ替わらない。消す役の印そのものが残った時だけは自動で消さず、`pty_close`を案内する。
542
433
  終了した持ち主のpidを別のprocessが使い回すと、pidだけでは生きて見える。生きて見える持ち主は、processが始まってからの経過とlockを作ってからの時間を比べ、
@@ -603,3 +494,16 @@ transcript、path、credentialを保存・公開しない。
603
494
  公開schema、完了境界、state ownership、platform backendを変える時は、原因の最小再現、focused test、
604
495
  関連ADR、日英README、CHANGELOGを同じ変更で同期する。現行制御から外れた完了・棄却・中断・失効・置換済みの設計planはcurrentへ残さずarchiveへ移す。
605
496
  旧設計draftは[`archive/01_design-plan.md`](https://github.com/kitepon/aiterm-mcp/blob/main/docs/archive/01_design-plan.md)に保存する。
497
+
498
+
499
+ Claude authentication URL receipts recognize the official claude.com/cai/oauth/authorize PKCE authorization request and its nonsecret code=true display flag. Callback authorization codes and tokens remain excluded; the common URL credential filter is unchanged. This auth-only patch contains no native goal feature.
500
+
501
+
502
+ ### 認証画面と専用応答
503
+
504
+ `agent-auth-ui.ts`が既v1 receiptに追加する`auth_ui`とrespondの型を所有する。Native harnessが公式URL・device code・空code欄・現在のラベル付き選択肢を識別し、Rootはキー列を作らない。不明な入力段階はunavailable。coreが既auth metadataのdev/ino/作成・更新時刻、native session/pane/server/PID開始識別を照合する。legacyのlaunch UUID欠測はnullのまま扱い、無関係なflowをadoptしない。新startは既launch_id欄を必ず設定する。
505
+
506
+ respondは既session send lock内で現在の空欄/段階と世代を再検証し、専用auth paneのpipe loggingを入力前に停止する。新auth paneは公式CLI起動前にloggingを停止する。通常send・lastcmd・mark・turn・journalを通さず、POSIX tmux load-bufferへstdinで送り、immutable pane IDへpaste/Enterを一回送る。secretはargv/fileへ渡さずbufferを消す。CLIの独自資格保存はCLI所有で、Aitermの非保存保証とは区別する。入力前の拒否はnot_issued、入力開始後の所有/結果欠測はunknown。元画面へ勝手に戻す・再送する回復はしない。Windowsは既psmux送信だけで前面所有を証明できないため専用respondを未対応として拒否する。macOSはPOSIX経路のsource実装であり実OS受入は別gate。
507
+
508
+
509
+ Claude authのcurrent menuはharnessの単一parseがphase/rows/selected/入力占有を所有する。古いthemeと新loginが同viewportに残っても最後の現在phaseだけを採り、coreはそのselected_idでnavigateする。code欄が現在phaseならoccupied状態から古いmenu UIへ戻さない。cursor欠測/複数cursor/未知row/後続の未知promptは入力前拒否。Native内部の入力観測は公開auth_uiへ直接返さず、公開schemaを拡張しない。
package/docs/RELEASE.md CHANGED
@@ -117,10 +117,11 @@ dotagentsの導入・更新は不要である。
117
117
  利用環境は直前の正常npm versionを明示installして戻す。公開済みtagは動かさず、npm packageは上書きしない。
118
118
  state schemaやmigrationを変更するreleaseでは、旧versionへ戻せる条件をCHANGELOGへ明記してから公開する。
119
119
 
120
- ### Native interrupt platform scope
121
-
122
- The opt-in `execution-native-server.js` socket/graph entry currently supports POSIX (Linux/macOS).
123
- Its private graph checkpoint requires directory fsync and explicitly rejects Windows before state writes.
124
- The Windows full gate continues to run the ordinary 18-tool product regression suite, portable graph planning/key dispatch tests,
125
- and the explicit unsupported checkpoint case. POSIX checkpoint/participant integration cases carry individual Windows skips;
126
- those skips do not establish native Windows interrupt support. Do not describe three-OS CI as four-harness/native Windows acceptance.
120
+ ### Ordinary delivery and historical graph fixtures
121
+
122
+ The normal MCP entry and parent hooks do not construct the shared execution
123
+ tracking runtime. The legacy native CLI serves ordinary delivery plus direct
124
+ leaf-first stop, without graph registration or writer-lock admission.
125
+ The retained experimental graph/checkpoint fixtures are historical POSIX-only
126
+ library tests, not ordinary delivery or stop requirements. Their Windows skips
127
+ do not establish native Windows interrupt support.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aiterm-mcp",
3
- "version": "0.61.16",
3
+ "version": "0.61.18",
4
4
  "mcpName": "io.github.kitepon/aiterm-mcp",
5
5
  "description": "Persistent terminal MCP with one harness-based launcher for Claude Code, Codex CLI, Grok CLI, and Cursor Agent CLI, plus durable PTYs for SSH, containers, and REPLs.",
6
6
  "keywords": [