aiterm-mcp 0.61.17 → 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,13 @@ 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
+
10
17
  ## [0.61.17] - 2026-10-24
11
18
 
12
19
  ### Changed
@@ -2861,7 +2868,8 @@ prototype (preserved under `prototype/python/` as the porting source and referen
2861
2868
  `ubuntu-latest` for Node 18/20/22, publishing to npm on `v*` tags with
2862
2869
  provenance.
2863
2870
 
2864
- [Unreleased]: https://github.com/kitepon/aiterm-mcp/compare/v0.61.17...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
2865
2873
  [0.61.17]: https://github.com/kitepon/aiterm-mcp/compare/v0.61.16...v0.61.17
2866
2874
  [0.61.16]: https://github.com/kitepon/aiterm-mcp/compare/v0.61.15...v0.61.16
2867
2875
  [0.61.15]: https://github.com/kitepon/aiterm-mcp/compare/v0.61.14...v0.61.15
@@ -3065,3 +3073,6 @@ prototype (preserved under `prototype/python/` as the porting source and referen
3065
3073
  [0.3.0]: https://github.com/kitepon/aiterm-mcp/compare/v0.2.0...v0.3.0
3066
3074
  [0.2.0]: https://github.com/kitepon/aiterm-mcp/compare/v0.1.0...v0.2.0
3067
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.17** · 動作対象は 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
 
@@ -804,3 +807,6 @@ aiterm が、あなたの AI に別のエージェントへ仕事を渡させた
804
807
  ## ライセンス
805
808
 
806
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.17** · 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
 
@@ -875,3 +878,6 @@ started the persistent multiplexer server (since 0.50.0). Every harness still ac
875
878
  ## License
876
879
 
877
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";
@@ -4357,6 +4359,131 @@ function authSessionEnvironment(name, keys) {
4357
4359
  }
4358
4360
  return env;
4359
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
+ }
4360
4487
  function launchAuthProcess(name, meta) {
4361
4488
  const plan = authPlan(meta.kind, meta.phase);
4362
4489
  const env = plan.env.map(([key, value]) => shq(`${key}=${value}`));
@@ -4371,9 +4498,18 @@ function launchAuthProcess(name, meta) {
4371
4498
  export async function authenticateAgent(kind, options) {
4372
4499
  if (options.action !== "status" || options.session_id !== undefined)
4373
4500
  assertExecutionContext(options.session_id);
4374
- const result = (status, session, message, pane) => ({ schema: "aiterm.agent-auth-result.v1", harness: agentHarness(kind),
4375
- status, session_id: session, url: pane?.url ?? null, user_code: pane?.user_code ?? null,
4376
- 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);
4377
4513
  if (options.action === "cancel") {
4378
4514
  if (!options.session_id)
4379
4515
  throw new AitermError("AGENT_AUTH_SESSION_REQUIRED: cancelにはsession_idが必要です。", 2);
@@ -4411,8 +4547,9 @@ export async function authenticateAgent(kind, options) {
4411
4547
  const kept = tmux("set-option", "-t", name, "remain-on-exit", "on");
4412
4548
  if (kept.code !== 0)
4413
4549
  throw new AitermError("AGENT_AUTH_PANE_CONFIG_FAILED: 認証processの終了観測を設定できません。", 2);
4550
+ disableAuthLogging(name);
4551
+ meta.launch_id = randomBytes(16).toString("hex");
4414
4552
  if (hasExecutionCreationScope()) {
4415
- meta.launch_id = randomBytes(16).toString("hex");
4416
4553
  meta.execution_native = executionCreatedNativeBinding(name);
4417
4554
  }
4418
4555
  withExecutionPreparation(name, () => writeJson0600(authMetadataPath(name), meta));
@@ -4452,7 +4589,7 @@ export async function authenticateAgent(kind, options) {
4452
4589
  if (observed.code !== 0)
4453
4590
  return result("failed", session, "認証processの終了状態を取得できません。");
4454
4591
  const [dead, exit] = observed.stdout.trim().split("\t");
4455
- const pane = authPane(meta, stripControl(captureScreen(session, 120)));
4592
+ const pane = authPane(meta, stripControl(captureScreen(session, 0)));
4456
4593
  if (dead === "1") {
4457
4594
  if (exit !== "0")
4458
4595
  return result("failed", session, `公式認証processが失敗しました(exit=${/^\d+$/.test(exit ?? "") ? exit : "unknown"})。`);
@@ -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
@@ -494,3 +494,16 @@ transcript、path、credentialを保存・公開しない。
494
494
  公開schema、完了境界、state ownership、platform backendを変える時は、原因の最小再現、focused test、
495
495
  関連ADR、日英README、CHANGELOGを同じ変更で同期する。現行制御から外れた完了・棄却・中断・失効・置換済みの設計planはcurrentへ残さずarchiveへ移す。
496
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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aiterm-mcp",
3
- "version": "0.61.17",
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": [