aiterm-mcp 0.24.3 → 0.25.2
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/README.ja.md +31 -16
- package/README.md +39 -21
- package/dist/core.js +219 -37
- package/dist/index.js +17 -15
- package/package.json +6 -6
package/README.ja.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
>
|
|
1
|
+
> **任意のMCPクライアントから、Claude・Codex・Grok・Composerをクロスベンダーでも同一ベンダーでも永続対話TUIへ起動する。Codexのスラッシュコマンドや[`$imagegen`](https://learn.chatgpt.com/docs/image-generation#generate-or-edit-an-image)のような固有機能もそのまま使える。**
|
|
2
2
|
|
|
3
3
|
<p align="center">
|
|
4
4
|
<img src=".github/og.png" alt="Aiterm — 異なる知性が一つの持続する実行現場を共有する森の観測拠点" width="100%">
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
# Aiterm
|
|
10
10
|
|
|
11
|
-
[](https://github.com/kitepon/aiterm-mcp/actions/workflows/ci.yml)
|
|
12
12
|
[](https://www.npmjs.com/package/aiterm-mcp)
|
|
13
13
|
[](https://www.npmjs.com/package/aiterm-mcp)
|
|
14
14
|
[](https://nodejs.org)
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
> *(English: [README.md](README.md))*
|
|
18
18
|
|
|
19
|
-
> **あなたの AI に、ほかの AI を操らせる。** 任意の MCP クライアントから 1 回の呼び出しで、コーディングエージェント(Claude・Codex・Grok・Composer
|
|
19
|
+
> **あなたの AI に、ほかの AI を操らせる。** 任意の MCP クライアントから 1 回の呼び出しで、コーディングエージェント(Claude・Codex・Grok・Composer)を永続端末の中に起動し、操作用のセッションを手渡す。何をしているかをトークン削減して読み、次の指示を送る。呼び出し元と起動先のベンダーは独立しており、ClaudeからClaude/Codexを、CodexからClaude/Codexを起動できる。
|
|
20
20
|
>
|
|
21
21
|
> **これは何か:** AI が握る 1 本の永続 MCP 端末——その中に他のコーディングエージェントも起動できる。`ssh`・`docker exec`・REPL・別エージェントの TUI は、すべてその 1 本の端末の中へ「送るだけのテキスト」として入れ子になる。仕組みはあえて素朴——MCP クライアントが相手エージェントの端末を 1 ターンずつ操作するだけ。隠れたプロトコルも・aiterm独自の共有メモリ層も・自律的な交渉も無い。起動したagentは、直接CLIと同じproject/vendorの通常memory・設定を読む。
|
|
22
22
|
>
|
|
@@ -90,11 +90,21 @@ claude mcp add --scope user --transport stdio aiterm -- npx -y aiterm-mcp
|
|
|
90
90
|
|
|
91
91
|
**所有境界:** 本repositoryは永続PTYと外部agent実行レーンを所有します。製品横断の導入と
|
|
92
92
|
host統合は、kitepon.devの製品開発を支える内部基盤
|
|
93
|
-
[dotagents](https://github.com/kitepon
|
|
93
|
+
[dotagents](https://github.com/kitepon/dotagents)が担当します。
|
|
94
94
|
|
|
95
95
|
**言葉でなく実測で:** 記録済み203テストのベンチマークでは、`pty_read` はコンテキストに載るトークンを生ログの **約 7.1 分の 1** に減らす。しかも pass/fail の判定は畳んでも残る。→ [組み込みシェルツールとの使い分け](#組み込みシェルツールとの使い分け)
|
|
96
96
|
|
|
97
|
-
14 ツール: 6 つの **PTY ツール**(`pty_open` / `pty_send` / `pty_read` / `pty_key` / `pty_close` / `pty_list`)で 1 本の永続端末を開き・操作し・読む。加えて 4 つの **エージェント起動ツール**(`claude_agent` / `codex_agent` / `grok_agent` / `composer_agent`)が別のコーディングエージェントの TUI を新しい端末の中に起動し、`agent_configure`が起動中のCodex/
|
|
97
|
+
14 ツール: 6 つの **PTY ツール**(`pty_open` / `pty_send` / `pty_read` / `pty_key` / `pty_close` / `pty_list`)で 1 本の永続端末を開き・操作し・読む。加えて 4 つの **エージェント起動ツール**(`claude_agent` / `codex_agent` / `grok_agent` / `composer_agent`)が別のコーディングエージェントの TUI を新しい端末の中に起動し、`agent_configure`が起動中のClaude/Codex/Grok/Composerのmodel・effortを再起動なしで変更し、`claude_turn`がdurable caller向けの構造化issue/recoveryを、`claude_approval`が相関済みClaude承認UI中継を、`diagnostics`が安全なfactory readinessを返す。バックエンドは **tmux** なので、MCP サーバや AI クライアントが再起動してもセッションは生き残る。
|
|
98
|
+
|
|
99
|
+
**v0.25.2ではGrok 4.6を含む同一sessionの連続設定変更を安定化。** Grok Build 1.0.3で
|
|
100
|
+
`/model`の成功通知が再描画により消えても、変更前には無かった要求model/effortが常駐footerへ現れた
|
|
101
|
+
最終状態を確認する。caller側のretry、再起動、失敗の成功丸めは不要で、`grok-4.6`は従来どおり
|
|
102
|
+
明示`model`としてlive catalog照合後に起動・変更できる。
|
|
103
|
+
|
|
104
|
+
**v0.25.0ではGrok/Composerへ共通launcher制御を同等実装。** 起動時`reasoning_effort`、
|
|
105
|
+
`write_scope:"read-only"`の`--sandbox read-only`強制、`agent_configure`による同一session内の
|
|
106
|
+
model/effort変更に対応した。明示したGrok/Composer modelとComposer既定modelはPTY作成前に
|
|
107
|
+
現在の`grok models` catalogへ照合し、不在時は別modelへ黙ってfallbackせず明示失敗する。
|
|
98
108
|
|
|
99
109
|
**v0.24.3ではlauncherへ渡す環境変数を現在のMCP processから明示選択できる。** `env_vars`へ
|
|
100
110
|
変数名だけを指定すると、aitermは起動時の現在値を読み、存在する値だけをそのagentへ渡す。永続tmux
|
|
@@ -168,7 +178,7 @@ pty_read(id, { wait: true }) → 削減済みの出力を読む(完了
|
|
|
168
178
|
|
|
169
179
|
既存の人間向けtextに加えて`aiterm.agent-launch-result.v1` structured receiptも返すため、durable callerは表示文字列を解析せずsession handleを取得できる。Codexは通常rollout transcriptの`task_complete`、Grok/Composerは通常session event、Claudeは通常settingsへ加算したlaunch固有Stop hookを完了正本に使う。agent sessionへの`pty_send`は非ブロックの **dispatch** になり`event_cursor`入りreceiptを即返す。完了通知は`aiterm-wait --session <id> --cursor <event_cursor>`を親のターンを塞がない別processで受ける。durable machine callerは`claude_turn`を使い、recoveryは再送せず、検証済み完了だけがexact `raw_output`を持つ。
|
|
170
180
|
|
|
171
|
-
`codex_agent`・`grok_agent`・`composer_agent`は任意の`write_scope`(`"read-only"`または書込み許可パスの説明)も受ける。指定値はlaunch receipt・session metadata・`pty_list`へ保存する。
|
|
181
|
+
`codex_agent`・`grok_agent`・`composer_agent`は任意の`write_scope`(`"read-only"`または書込み許可パスの説明)も受ける。指定値はlaunch receipt・session metadata・`pty_list`へ保存する。3 launcherすべてで`write_scope:"read-only"`は実効能力壁となり、aitermがCLIの`--sandbox read-only`を付ける。パス説明をallowlistへ変換する同等CLI引数はないため、そちらだけは`write_scope_enforcement:"declaration_only_unsupported"`を返す。`write_scope`を省略した起動は従来どおりである。
|
|
172
182
|
|
|
173
183
|
```text
|
|
174
184
|
codex_agent({ session_name: "codex1", cwd: "/repo",
|
|
@@ -188,8 +198,8 @@ $ aiterm-wait --session codex1 --cursor <event_cursor> # exit 0=done / 3=timeo
|
|
|
188
198
|
| --- | --- | --- |
|
|
189
199
|
| `claude_agent` | Claude Code CLI(Anthropic) | `prompt?`, `throughline_source_session?`, `model?`, `reasoning_effort?`(`low`/`medium`/`high`/`xhigh`/`max`), `env_vars?`, `cwd?`, `session_name?` |
|
|
190
200
|
| `codex_agent` | Codex CLI(OpenAI・端末設定/CLI既定、`model?`で上書き) | `prompt?`, `throughline_source_session?`, `model?`, `reasoning_effort?`(`low`/`medium`/`high`/`xhigh`/`max`/`ultra`), `env_vars?`, `cwd?`, `session_name?`, `write_scope?` |
|
|
191
|
-
| `grok_agent` | Grok Build(xAI、既定`grok-4.5`、`model?`で上書き) | `prompt?`, `throughline_source_session?`, `model?`, `reasoning_effort
|
|
192
|
-
| `composer_agent` | Grok Build(xAI、既定`grok-composer-2.5-fast`、`model
|
|
201
|
+
| `grok_agent` | Grok Build(xAI、既定`grok-4.5`、`model?`で上書き) | `prompt?`, `throughline_source_session?`, `model?`, `reasoning_effort?`(vendor対応値), `env_vars?`, `cwd?`, `session_name?`, `write_scope?` |
|
|
202
|
+
| `composer_agent` | Grok Build(xAI、既定`grok-composer-2.5-fast`、`model?`で上書き。全modelをlive catalog照合) | `prompt?`, `throughline_source_session?`, `model?`, `reasoning_effort?`(vendor対応値), `env_vars?`, `cwd?`, `session_name?`, `write_scope?` |
|
|
193
203
|
|
|
194
204
|
`env_vars`は環境変数の**名前**だけを並べるallowlistであり、name/value mapではない。aitermは
|
|
195
205
|
launcher起動時に現在のMCP processから各名前を読み、存在する値をshell quoteして、その1回のvendor
|
|
@@ -290,9 +300,9 @@ claude mcp add --scope user --transport stdio aiterm -- aiterm-mcp
|
|
|
290
300
|
|
|
291
301
|
## ヘッドレス: 端末に人が居ない
|
|
292
302
|
|
|
293
|
-
MCP クライアントが aiterm を stdio 越しにプログラムから駆動するので、上のすべては **tmux
|
|
303
|
+
MCP クライアントが aiterm を stdio 越しにプログラムから駆動するので、上のすべては **tmux に誰も座らないまま**動く。Claude/Codexのどちらからでも、自分自身を含む任意のlauncherを呼び、`pty_read`で結果を読んで次へ進める——無人で。これは、人が操作する端末が向かない場所にこそ aiterm が合うということ:
|
|
294
304
|
|
|
295
|
-
- **複数エージェントのオーケストレーション** — 統括役がサブタスクを Codex / Grok / Composer に渡し、各々を専用の永続セッションに置き、全部を読み戻す。
|
|
305
|
+
- **複数エージェントのオーケストレーション** — 統括役がサブタスクを Claude / Codex / Grok / Composer に渡し、各々を専用の永続セッションに置き、全部を読み戻す。
|
|
296
306
|
- **CI** — ジョブのステップがエージェントを起こし、操作し、片付けられる。
|
|
297
307
|
- **cron** — スケジュール実行がエージェントを起動して出力を回収できる。
|
|
298
308
|
|
|
@@ -383,7 +393,7 @@ aiterm は同じ核心の洞察——端末を出会いの場にする——を
|
|
|
383
393
|
| `pty_key` | 制御キーを送る | `session_id`, `key`(`C-c`/`Enter`/`Up`…) |
|
|
384
394
|
| `pty_close` | 冪等に閉じ、`closed` / `already_closed`を返す | `session_id` |
|
|
385
395
|
| `pty_list` | セッション一覧 | (なし) |
|
|
386
|
-
| `agent_configure` | 起動中のCodex/
|
|
396
|
+
| `agent_configure` | 起動中のClaude/Codex/Grok/Composerを再起動せずmodel/effort変更 | `session_id`, `model?`, `reasoning_effort?` |
|
|
387
397
|
| `claude_turn` | 相関済みClaude operationをdispatch(issue)または回収(recover) | `action`, `session_id`, `operation_id`, `text?` |
|
|
388
398
|
| `claude_approval` | 現在表示中の相関済みClaude承認UIを検査または応答 | `action`, `session_id`, `operation_id?`, `approval_choice?`, `observed_prompt_digest?` |
|
|
389
399
|
| `diagnostics` | 機械可読 JSON による read-only factory readiness | (なし) |
|
|
@@ -400,14 +410,14 @@ consumer は `aiterm-runtime-errors snapshot` を読み、durable ingestion 後
|
|
|
400
410
|
|
|
401
411
|
各ツールは特定ベンダーの対話型コーディングエージェント TUI を新しい永続 PTY の中に起動し、`session_id` を返す。以後は他のセッションと同様に `pty_read` / `pty_send` で操作する。モデルごとに 1 ツール=ツール名を見ればどのモデルか分かる。TUI は全画面アプリなので、`pty_read({ screen: true })` で描画済みの画面を読む。
|
|
402
412
|
|
|
403
|
-
`agent_configure({ session_id, model?, reasoning_effort? })`はvendor標準操作で起動中のCodex/
|
|
413
|
+
`agent_configure({ session_id, model?, reasoning_effort? })`はvendor標準操作で起動中のClaude/Codex/Grok/Composerを変更し、PTYと会話contextを維持する。Grok/Composerは同じlive model catalog照合後に`/model <model> [effort]`または`/effort <effort>`を使う。Claude Code標準の`/model`・`/effort`は、新しいClaude sessionの既定値も同時に保存する。
|
|
404
414
|
|
|
405
415
|
| ツール | 起動するもの | 主な引数 |
|
|
406
416
|
| --- | --- | --- |
|
|
407
417
|
| `claude_agent` | Claude Code CLI(Anthropic) | `prompt?`, `throughline_source_session?`, `model?`, `reasoning_effort?`(`low`/`medium`/`high`/`xhigh`/`max`), `env_vars?`, `cwd?`, `session_name?` |
|
|
408
418
|
| `codex_agent` | Codex CLI(OpenAI・端末設定/CLI既定、`model?`で上書き) | `prompt?`, `throughline_source_session?`, `model?`, `reasoning_effort?`(`low`/`medium`/`high`/`xhigh`/`max`/`ultra`), `env_vars?`, `cwd?`, `session_name?`, `write_scope?` |
|
|
409
|
-
| `grok_agent` | Grok Build(xAI、既定`grok-4.5`、`model?`で上書き) | `prompt?`, `throughline_source_session?`, `model?`, `reasoning_effort
|
|
410
|
-
| `composer_agent` | Grok Build(xAI、既定`grok-composer-2.5-fast`、`model
|
|
419
|
+
| `grok_agent` | Grok Build(xAI、既定`grok-4.5`、`model?`で上書き) | `prompt?`, `throughline_source_session?`, `model?`, `reasoning_effort?`(vendor対応値), `env_vars?`, `cwd?`, `session_name?`, `write_scope?` |
|
|
420
|
+
| `composer_agent` | Grok Build(xAI、既定`grok-composer-2.5-fast`、`model?`で上書き。全modelをlive catalog照合) | `prompt?`, `throughline_source_session?`, `model?`, `reasoning_effort?`(vendor対応値), `env_vars?`, `cwd?`, `session_name?`, `write_scope?` |
|
|
411
421
|
|
|
412
422
|
対応するCLI(`claude` / `codex` / `grok`)の導入・認証が必要。前提違反はsession作成前に明示失敗する。4 launcherすべてが通常project/user環境と同じ非ブロックdispatch契約を使う。Claude/Codex/Grok/Composerのdepth 1 live smokeと、Claude親→Claude孫のdepth 2 nested delegation smokeはgreenであり、fixtureによる検証とは区別して記録する。
|
|
413
423
|
|
|
@@ -467,6 +477,11 @@ npm test # build してから node:test 回帰スイート(tmux 必
|
|
|
467
477
|
npm link # ローカルで `aiterm-mcp` を PATH に
|
|
468
478
|
```
|
|
469
479
|
|
|
480
|
+
開発中は変更に直結するfocused testを先にローカルで実行します。GitHub Actionsの最終gateは
|
|
481
|
+
self-hostedのmacOS native・Linux native・Windows native・WSL2で同じ`npm test`を同時実行し、
|
|
482
|
+
OS別の縮小suiteで代用しません。tag起点のnpm公開は4環境greenとtagged commitの`origin/main`
|
|
483
|
+
祖先確認を通過した後だけ実行します。
|
|
484
|
+
|
|
470
485
|
ロジックは `src/core.ts`(tmux 制御・削減・完了検出・安全・エージェント起動)と `src/rtk.ts`(コマンド別 reducer)、公開は `src/index.ts`。設計の出発点と reducer の移植元(pytest reducer は本家 rtk 0.42.0 と一致するよう移植・ただし上記の `FAILED` 行の差異は意図的・回帰テストで固定)は `prototype/python/` を参照。
|
|
471
486
|
|
|
472
487
|
## 試す
|
|
@@ -477,10 +492,10 @@ npm link # ローカルで `aiterm-mcp` を PATH に
|
|
|
477
492
|
claude mcp add --scope user --transport stdio aiterm -- npx -y aiterm-mcp
|
|
478
493
|
```
|
|
479
494
|
|
|
480
|
-
aiterm が、あなたの AI に別のエージェントへ仕事を渡させたなら——あるいはトークンの往復を 1 回でも省けたなら——**[リポジトリに star](https://github.com/kitepon
|
|
495
|
+
aiterm が、あなたの AI に別のエージェントへ仕事を渡させたなら——あるいはトークンの往復を 1 回でも省けたなら——**[リポジトリに star](https://github.com/kitepon/aiterm-mcp)** を。他の人に見つけてもらう一番安い方法です。
|
|
481
496
|
|
|
482
497
|
- **npm:** https://www.npmjs.com/package/aiterm-mcp
|
|
483
|
-
- **Issue / バグ報告:** https://github.com/kitepon
|
|
498
|
+
- **Issue / バグ報告:** https://github.com/kitepon/aiterm-mcp/issues
|
|
484
499
|
|
|
485
500
|
## ライセンス
|
|
486
501
|
|
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
> **
|
|
1
|
+
> **From any MCP client, launch Claude, Codex, Grok, or Composer — cross-vendor or same-vendor — inside a persistent interactive TUI, with native features such as Codex slash commands and [`$imagegen`](https://learn.chatgpt.com/docs/image-generation#generate-or-edit-an-image) available.**
|
|
2
2
|
|
|
3
3
|
<p align="center">
|
|
4
4
|
<img src=".github/og.png" alt="Aiterm — a shared forest observatory where different intelligences work in one persistent execution space" width="100%">
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
# Aiterm
|
|
10
10
|
|
|
11
|
-
[](https://github.com/kitepon/aiterm-mcp/actions/workflows/ci.yml)
|
|
12
12
|
[](https://www.npmjs.com/package/aiterm-mcp)
|
|
13
13
|
[](https://www.npmjs.com/package/aiterm-mcp)
|
|
14
14
|
[](https://nodejs.org)
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
> *(日本語: [README.ja.md](README.ja.md))*
|
|
18
18
|
|
|
19
|
-
> **Let your AI orchestrate other AIs.** From any MCP client, one call spawns a coding agent (Claude, Codex, Grok, or Composer) inside a persistent terminal and hands you a session to drive: read what it's doing token-reduced, send it the next instruction.
|
|
19
|
+
> **Let your AI orchestrate other AIs.** From any MCP client, one call spawns a coding agent (Claude, Codex, Grok, or Composer) inside a persistent terminal and hands you a session to drive: read what it's doing token-reduced, send it the next instruction. The caller and launched vendor are independent: Claude can launch Claude or Codex, and Codex can launch Claude or Codex.
|
|
20
20
|
>
|
|
21
21
|
> **What it is:** one persistent MCP terminal your AI drives — and can launch other coding agents into. `ssh`, `docker exec`, a REPL, or another agent's TUI all nest inside that one terminal as just text you send in. The mechanism is deliberately plain — your MCP client drives the other agent's terminal turn by turn: no hidden protocol, no separate aiterm-owned shared-memory layer, no autonomous negotiation. Launched agents still read the normal project and vendor memory/configuration that a direct CLI launch would use.
|
|
22
22
|
>
|
|
@@ -89,12 +89,23 @@ Save this as `.cursor/mcp.json` for the project, or `~/.cursor/mcp.json` globall
|
|
|
89
89
|
|
|
90
90
|
**Ownership boundary:** this repository owns the persistent PTY and external-agent
|
|
91
91
|
execution lane. Cross-product installation and host integration are handled by
|
|
92
|
-
[dotagents](https://github.com/kitepon
|
|
92
|
+
[dotagents](https://github.com/kitepon/dotagents), the internal development
|
|
93
93
|
toolchain behind kitepon.dev's products.
|
|
94
94
|
|
|
95
95
|
**Measured, not claimed:** in the recorded 203-test benchmark, a `pty_read` puts **~7.1× fewer tokens** in your context than the raw log — and the pass/fail verdict survives the fold. → [When to reach for it vs. the built-in shell](#when-to-reach-for-it-vs-the-built-in-shell)
|
|
96
96
|
|
|
97
|
-
Fourteen tools: six **PTY tools** — `pty_open` / `pty_send` / `pty_read` / `pty_key` / `pty_close` / `pty_list` — to open, drive, and read one persistent terminal, four **agent launchers** — `claude_agent` / `codex_agent` / `grok_agent` / `composer_agent` — that each start another coding agent's TUI inside a fresh one, `agent_configure` to change a running Codex/
|
|
97
|
+
Fourteen tools: six **PTY tools** — `pty_open` / `pty_send` / `pty_read` / `pty_key` / `pty_close` / `pty_list` — to open, drive, and read one persistent terminal, four **agent launchers** — `claude_agent` / `codex_agent` / `grok_agent` / `composer_agent` — that each start another coding agent's TUI inside a fresh one, `agent_configure` to change a running Claude/Codex/Grok/Composer session's model and effort without restarting it, `claude_turn` for durable structured issue/recovery, `claude_approval` for correlated Claude approval prompts, and `diagnostics` for safe factory readiness. The backend is **tmux**, so sessions survive even if the MCP server or the AI client restarts.
|
|
98
|
+
|
|
99
|
+
**v0.25.2 stabilizes repeated in-place configuration changes, including Grok 4.6.** If Grok Build
|
|
100
|
+
1.0.3 redraws before its `/model` success notice can be observed, aiterm confirms the requested model/effort
|
|
101
|
+
from the persistent footer when that state was absent before the command. Callers do not retry, restart, or
|
|
102
|
+
round a failure into success; explicit `grok-4.6` launch and configuration still pass the live catalog check.
|
|
103
|
+
|
|
104
|
+
**v0.25.0 gives Grok and Composer the same shared launcher controls.** Their launchers now pass
|
|
105
|
+
`reasoning_effort`, enforce `write_scope: "read-only"` with `--sandbox read-only`, and support
|
|
106
|
+
in-place model/effort changes through `agent_configure`. Before creating a PTY, aiterm checks an
|
|
107
|
+
explicit Grok/Composer model—and Composer's default model—against the live `grok models` catalog.
|
|
108
|
+
An unavailable model fails visibly instead of letting the vendor CLI fall back to another model.
|
|
98
109
|
|
|
99
110
|
**v0.24.3 forwards explicitly selected launcher environment variables from the current MCP process.**
|
|
100
111
|
Pass variable names in `env_vars`; aiterm reads their current values at launch and injects only the
|
|
@@ -162,7 +173,7 @@ A lot of 2026's agent tooling is converging on orchestration: a lead model deleg
|
|
|
162
173
|
|
|
163
174
|
## Built with Codex and GPT-5.6 for OpenAI Build Week 2026
|
|
164
175
|
|
|
165
|
-
aiterm predates Build Week, so the event work is kept visible in dated commits. During the submission window (July 14–16, 2026), I extended it with safe serialized delivery for long PTY input, correlated operation IDs and bounded result recovery, machine-readable launch and idempotent close receipts, and a hardened readiness gate that prevents prompts from disappearing during TUI startup redraws. The public comparison from the pre-event release is [`v0.12.2...main`](https://github.com/kitepon
|
|
176
|
+
aiterm predates Build Week, so the event work is kept visible in dated commits. During the submission window (July 14–16, 2026), I extended it with safe serialized delivery for long PTY input, correlated operation IDs and bounded result recovery, machine-readable launch and idempotent close receipts, and a hardened readiness gate that prevents prompts from disappearing during TUI startup redraws. The public comparison from the pre-event release is [`v0.12.2...main`](https://github.com/kitepon/aiterm-mcp/compare/v0.12.2...main).
|
|
166
177
|
|
|
167
178
|
I used **Codex with GPT-5.6** as an engineering collaborator: it inspected the implementation, challenged the API and recovery contracts, generated focused regression cases, and helped verify race, security, timeout, and malformed-event paths. I reviewed the diffs and test evidence and retained the final product and architecture decisions. At that Build Week checkpoint, the regression suite contained 262 tests covering normal operation as well as failure and recovery behavior; current release receipts live in the [CHANGELOG](CHANGELOG.md) and release ADRs.
|
|
168
179
|
|
|
@@ -187,7 +198,7 @@ The same primitive hosts another agent's TUI. Four launchers each start one vend
|
|
|
187
198
|
|
|
188
199
|
The human-readable launch text is accompanied by an `aiterm.agent-launch-result.v1` structured receipt, so durable callers never parse display text for the session handle; when the launch carries an initial `prompt`, the receipt also includes the `event_cursor`, a ready-made `wait_command` for the completion waiter, and a `submit_residue` observation (`true` = the prompt is likely still sitting unsubmitted in the composer — the hint explains recovery; `false` = no residue observed, not a proof of submission; `null` = not applicable). From there you drive it with the same `pty_read` / `pty_send` you'd use on any shell. Codex completion comes from its normal durable rollout transcript's `task_complete`; Grok/Composer use their normal session events; Claude receives a launch-specific Stop hook settings addition while still loading normal user/project/local settings. Sending to an agent session is a non-blocking **dispatch** — the call returns immediately with an `event_cursor`, and completion arrives via [`aiterm-wait`](#completion-push-for-parent-agents-aiterm-wait). Durable machine callers use `claude_turn({ action: "issue" | "recover", session_id, operation_id, ... })`: it returns fixed `accepted` / `pending` / `completed` / `unknown` states without parsing human-facing errors, never resends during recovery, and includes exact `raw_output` only for a verified completion. An initial `prompt` on `claude_agent`/`codex_agent` is submitted through the same ready gate and the launcher returns without waiting; on Grok/Composer it is passed on the CLI's argv. This needs the vendor's own CLI installed and authenticated — see [Requirements](#requirements).
|
|
189
200
|
|
|
190
|
-
`codex_agent`, `grok_agent`, and `composer_agent` also accept an optional `write_scope`: either `"read-only"` or a human-readable description of writable paths. A supplied value is retained in the launch receipt, session metadata, and `pty_list`. For
|
|
201
|
+
`codex_agent`, `grok_agent`, and `composer_agent` also accept an optional `write_scope`: either `"read-only"` or a human-readable description of writable paths. A supplied value is retained in the launch receipt, session metadata, and `pty_list`. For all three launchers, `write_scope: "read-only"` is an effective boundary: aiterm adds the CLI's `--sandbox read-only` flag. A path description remains declaration-only because none of these CLI launch surfaces provides an equivalent path allowlist flag; that case returns `write_scope_enforcement: "declaration_only_unsupported"`. Omitting `write_scope` preserves prior behavior.
|
|
191
202
|
|
|
192
203
|
For a correlated Claude turn stopped at `Do you want to proceed?`, use `claude_approval(action: "inspect", ...)` to capture the active operation and SHA-256 screen digest, review the displayed command, then call `respond` with that exact digest and either `approve_once` or `deny`. The relay rechecks the operation and screen under the send lock, never exposes arbitrary input or permanent approval, keeps the active marker intact, and records a prompt-free owner-only receipt. `pty_send(force: true)` does not bypass this boundary.
|
|
193
204
|
|
|
@@ -210,8 +221,8 @@ One call per model, so the tool name itself tells you which model you get:
|
|
|
210
221
|
| --- | --- | --- |
|
|
211
222
|
| `claude_agent` | Claude Code CLI (Anthropic) | `prompt?`, `throughline_source_session?`, `model?`, `reasoning_effort?` (`low`/`medium`/`high`/`xhigh`/`max`), `env_vars?`, `cwd?`, `session_name?`, `launch_operation_id?` |
|
|
212
223
|
| `codex_agent` | Codex CLI (OpenAI; terminal config/CLI default unless overridden) | `prompt?`, `throughline_source_session?`, `model?`, `reasoning_effort?` (`low`/`medium`/`high`/`xhigh`/`max`/`ultra`; ultra enables proactive automatic delegation), `env_vars?`, `cwd?`, `session_name?`, `write_scope?` |
|
|
213
|
-
| `grok_agent` | Grok Build, model `grok-4.5` by default (`model?` overrides) (xAI) | `prompt?`, `throughline_source_session?`, `model?`, `reasoning_effort?`
|
|
214
|
-
| `composer_agent` | Grok Build, model `grok-composer-2.5-fast` by default (`model?` overrides) (xAI) | `prompt?`, `throughline_source_session?`, `model?`, `reasoning_effort?`
|
|
224
|
+
| `grok_agent` | Grok Build, model `grok-4.5` by default (`model?` overrides) (xAI) | `prompt?`, `throughline_source_session?`, `model?`, `reasoning_effort?` (vendor-supported value), `env_vars?`, `cwd?`, `session_name?`, `write_scope?` |
|
|
225
|
+
| `composer_agent` | Grok Build, model `grok-composer-2.5-fast` by default (`model?` overrides); every model is live-catalog checked (xAI) | `prompt?`, `throughline_source_session?`, `model?`, `reasoning_effort?` (vendor-supported value), `env_vars?`, `cwd?`, `session_name?`, `write_scope?` |
|
|
215
226
|
|
|
216
227
|
`env_vars` is an allowlist of environment-variable **names**, not a name/value map. At launch,
|
|
217
228
|
aiterm reads each valid name from its current MCP process, shell-quotes present values, and places
|
|
@@ -231,7 +242,7 @@ places its returned context before a fixed separator and the mission. This route
|
|
|
231
242
|
`throughline >= 0.9.0`; it reads the source memory without changing that database's session
|
|
232
243
|
ownership. No Throughline dependency is needed when the field is omitted.
|
|
233
244
|
|
|
234
|
-
|
|
245
|
+
All four launchers forward `model` and `reasoning_effort` through public CLI flags. Explicit Grok/Composer models and Composer's default model are checked against the current `grok models` catalog before a PTY exists; missing models are errors, with no cache, retry, or fallback to another model. Pass an absolute path for `cwd` — `~` is not expanded. Durable callers can make a promptless Claude launch exactly replayable by passing an explicit `session_name` and `launch_operation_id`. Claude adds a launch-local settings file only for the correlated Stop hook and loads it together with normal `user,project,local` setting sources; it does not replace normal hooks, MCPs, plugins, permissions, or trust state. The hook event contains no answer body, and the bounded owner-only result is returned by `pty_read({ agent_transcript:true })` without reading Claude's private transcript. While a correlated Claude turn is active, raw sends and non-interrupt keys are rejected. Exact `/login` and `/logout` dispatches are rejected so shared authentication is repaired once in a normal terminal. Codex reads its normal rollout store; Grok/Composer read their normal session event/history files. Before dispatch, Codex waits for an idle TUI, while Grok/Composer additionally require the vendor's structured `mcp_init_completed` event so a visible input box cannot accept a prompt too early. Correlated completion requires POSIX filesystem semantics (Linux, WSL2, macOS).
|
|
235
246
|
|
|
236
247
|
There is no hidden protocol between agents: a launched Claude, Codex, Grok, or Composer is another user-visible persistent terminal session. The MCP client drives that TUI with ordinary PTY operations, and a human can attach to watch or take over.
|
|
237
248
|
|
|
@@ -317,9 +328,9 @@ This registers it in `~/.claude.json`; you'll get an approval prompt the first t
|
|
|
317
328
|
|
|
318
329
|
## Headless: no human at the terminal
|
|
319
330
|
|
|
320
|
-
Because an MCP client drives aiterm programmatically over stdio, everything above can run with **nobody sitting at a tmux**.
|
|
331
|
+
Because an MCP client drives aiterm programmatically over stdio, everything above can run with **nobody sitting at a tmux**. A Claude or Codex session can call any launcher — including another instance of itself — then `pty_read` the result and act on it unattended. That makes aiterm a fit for exactly the places a human-driven terminal isn't:
|
|
321
332
|
|
|
322
|
-
- **Multi-agent orchestration** — an orchestrator hands sub-tasks to Codex / Grok / Composer, each in its own persistent session, and reads them all back.
|
|
333
|
+
- **Multi-agent orchestration** — an orchestrator hands sub-tasks to Claude / Codex / Grok / Composer, each in its own persistent session, and reads them all back.
|
|
323
334
|
- **CI** — a job step can spin up an agent, drive it, and tear it down.
|
|
324
335
|
- **cron** — a scheduled run can launch an agent and collect its output.
|
|
325
336
|
|
|
@@ -412,7 +423,7 @@ On top of that sits a productized layer a raw tmux bridge doesn't have: **token-
|
|
|
412
423
|
| `pty_key` | Send a control key | `session_id`, `key` (`C-c`/`Enter`/`Up`…) |
|
|
413
424
|
| `pty_close` | Close idempotently; return `closed` / `already_closed` | `session_id` |
|
|
414
425
|
| `pty_list` | List sessions (agent rows carry `agent=<kind>` metadata) | (none) |
|
|
415
|
-
| `agent_configure` | Change model/effort in a running Codex or
|
|
426
|
+
| `agent_configure` | Change model/effort in a running Claude, Codex, Grok, or Composer session without restarting it | `session_id`, `model?`, `reasoning_effort?` |
|
|
416
427
|
| `claude_turn` | Issue (dispatch-only) or recover one correlated Claude operation | `action`, `session_id`, `operation_id`, `text?` |
|
|
417
428
|
| `claude_approval` | Inspect or answer the current correlated Claude approval prompt | `action`, `session_id`, `operation_id?`, `approval_choice?`, `observed_prompt_digest?` |
|
|
418
429
|
| `diagnostics` | Read-only factory readiness as machine-readable JSON | (none) |
|
|
@@ -429,16 +440,16 @@ Consumer flow is `aiterm-runtime-errors snapshot`, then `aiterm-runtime-errors a
|
|
|
429
440
|
|
|
430
441
|
Each launcher starts a specific vendor's interactive coding-agent TUI inside a fresh persistent PTY and returns its `session_id` — from there you drive it with plain `pty_read` / `pty_send`, exactly like any other session. One tool per model, so the tool name itself tells you which model you get. The TUI is a full-screen app, so read it with `pty_read({ screen: true })` for the rendered view.
|
|
431
442
|
|
|
432
|
-
`agent_configure({ session_id, model?, reasoning_effort? })` changes a running Codex or
|
|
443
|
+
`agent_configure({ session_id, model?, reasoning_effort? })` changes a running Claude, Codex, Grok, or Composer TUI through the vendor's standard controls, preserving the PTY and conversation context. Grok/Composer use `/model <model> [effort]` or `/effort <effort>` after the same live model-catalog check. Claude Code's native `/model` and `/effort` commands also save those choices as defaults for new Claude sessions.
|
|
433
444
|
|
|
434
445
|
| Tool | Launches | Key args |
|
|
435
446
|
| --- | --- | --- |
|
|
436
447
|
| `claude_agent` | Claude Code CLI (Anthropic) | `prompt?`, `throughline_source_session?`, `model?`, `reasoning_effort?` (`low`/`medium`/`high`/`xhigh`/`max`), `env_vars?`, `cwd?`, `session_name?`, `launch_operation_id?` |
|
|
437
448
|
| `codex_agent` | Codex CLI (OpenAI; terminal config/CLI default unless overridden) | `prompt?`, `throughline_source_session?`, `model?`, `reasoning_effort?` (`low`/`medium`/`high`/`xhigh`/`max`/`ultra`; ultra enables proactive automatic delegation), `env_vars?`, `cwd?`, `session_name?`, `write_scope?` |
|
|
438
|
-
| `grok_agent` | Grok Build, model `grok-4.5` by default (`model?` overrides) (xAI) | `prompt?`, `throughline_source_session?`, `model?`, `reasoning_effort?`
|
|
439
|
-
| `composer_agent` | Grok Build, model `grok-composer-2.5-fast` by default (`model?` overrides) (xAI) | `prompt?`, `throughline_source_session?`, `model?`, `reasoning_effort?`
|
|
449
|
+
| `grok_agent` | Grok Build, model `grok-4.5` by default (`model?` overrides) (xAI) | `prompt?`, `throughline_source_session?`, `model?`, `reasoning_effort?` (vendor-supported value), `env_vars?`, `cwd?`, `session_name?`, `write_scope?` |
|
|
450
|
+
| `composer_agent` | Grok Build, model `grok-composer-2.5-fast` by default (`model?` overrides); every model is live-catalog checked (xAI) | `prompt?`, `throughline_source_session?`, `model?`, `reasoning_effort?` (vendor-supported value), `env_vars?`, `cwd?`, `session_name?`, `write_scope?` |
|
|
440
451
|
|
|
441
|
-
The vendor CLI must be installed and authenticated (`claude` for `claude_agent`; `codex` for `codex_agent`; `grok` for both Grok tools). Binary resolution uses `CLAUDE_BIN` / `CODEX_BIN` / `GROK_BIN`, then each documented default location, then `PATH`. Missing binaries, invalid model/effort values, and nonexistent `cwd` fail before a session is created. Claude additionally requires a structured healthy authentication status before any PTY exists, and correlated Claude sessions reject `/login` and `/logout`; repair authentication once in a normal terminal. All four launchers share the normal project/user environment and the same non-blocking dispatch contract. Claude, Codex, Grok, and Composer depth-1 live smokes and a Claude depth-2 nested-delegation smoke are green; fixture coverage remains a separate claim. Native Windows can launch agents but correlated completion is not supported yet.
|
|
452
|
+
The vendor CLI must be installed and authenticated (`claude` for `claude_agent`; `codex` for `codex_agent`; `grok` for both Grok tools). Binary resolution uses `CLAUDE_BIN` / `CODEX_BIN` / `GROK_BIN`, then each documented default location, then `PATH`. Missing binaries, invalid model/effort values, unavailable Grok/Composer catalog models, and nonexistent `cwd` fail before a session is created. Claude additionally requires a structured healthy authentication status before any PTY exists, and correlated Claude sessions reject `/login` and `/logout`; repair authentication once in a normal terminal. All four launchers share the normal project/user environment and the same non-blocking dispatch contract. Claude, Codex, Grok, and Composer depth-1 live smokes and a Claude depth-2 nested-delegation smoke are green; fixture coverage remains a separate claim. Native Windows can launch agents but correlated completion is not supported yet.
|
|
442
453
|
|
|
443
454
|
Set `throughline_source_session` together with a non-empty mission in `prompt` to prepend
|
|
444
455
|
Throughline's read-only handoff context. This optional route requires `throughline >= 0.9.0`,
|
|
@@ -450,7 +461,7 @@ When an agent's answer is longer than the on-screen tail (pane height ≈ 24 lin
|
|
|
450
461
|
|
|
451
462
|
### Completion detection (5 layers)
|
|
452
463
|
|
|
453
|
-
`pty_read({ wait: true })` decides "is the command done?" via five layers: process exit / a `mark:true` sentinel / an `until` match / output quiescence with shell return / timeout. Agent sessions add a sixth exact layer: Codex observes normal rollout `task_complete`; Grok/Composer observe normal session `turn_ended`; Claude observes its additive launch-correlated Stop event. `aiterm-wait --cursor` performs that vendor-specific observation without the parent blocking or polling. Pre-send readiness failures are MCP errors, and late completion remains recoverable without resending.
|
|
464
|
+
`pty_read({ wait: true })` decides "is the command done?" via five layers: process exit / a `mark:true` sentinel / an `until` match / output quiescence with shell return / timeout. When `mark` or `until` is active, that requested evidence takes precedence and a momentarily quiet shell cannot complete the read as quiescent. Agent sessions add a sixth exact layer: Codex observes normal rollout `task_complete`; Grok/Composer observe normal session `turn_ended`; Claude observes its additive launch-correlated Stop event. `aiterm-wait --cursor` performs that vendor-specific observation without the parent blocking or polling. Pre-send readiness failures are MCP errors, and late completion remains recoverable without resending.
|
|
454
465
|
|
|
455
466
|
### Completion push for parent agents (`aiterm-wait`)
|
|
456
467
|
|
|
@@ -475,7 +486,7 @@ As of v0.16 a parent agent **never blocks** on aiterm — there is no wait param
|
|
|
475
486
|
|
|
476
487
|
Before sending, `pty_send` blocks destructive commands (`rm -rf /`, `mkfs`, `dd of=/dev/…`, `DROP TABLE`, …) — pass `force: true` to override — and sanitizes ESC / bracketed-paste terminators. `pty_read` neutralizes control characters in what it returns by default (`raw: true` returns the bytes verbatim). This is a **tripwire, not a sandbox** (see [Known constraints](#known-constraints-by-design-not-bugs)).
|
|
477
488
|
|
|
478
|
-
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.
|
|
489
|
+
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 tmux 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 is in the foreground is encoded as one newline-free `eval` input: 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 paste with tmux bracketed paste (`paste-buffer -p`): panes that requested bracketed-paste mode (the vendor TUIs) receive each chunk wrapped in `ESC[200~/201~`, 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; close and recreate that session (or use `pty_kill_all` when every session is disposable) to clean it up safely.
|
|
479
490
|
|
|
480
491
|
## A human can watch
|
|
481
492
|
|
|
@@ -510,6 +521,13 @@ npm test # build, then the node:test regression suite (requires tmux)
|
|
|
510
521
|
npm link # put `aiterm-mcp` on PATH locally
|
|
511
522
|
```
|
|
512
523
|
|
|
524
|
+
Development uses focused local tests first. The final GitHub Actions gate starts the same full
|
|
525
|
+
`npm test` concurrently on self-hosted macOS native, Linux native, Windows native, and WSL2
|
|
526
|
+
runners; it does not replace any OS with a reduced suite. Tag-triggered npm publishing runs only
|
|
527
|
+
after all four environments pass and the tagged commit is confirmed on `origin/main`. The native
|
|
528
|
+
Windows runner must run as the interactive Windows user that owns the initialized WSL distro;
|
|
529
|
+
`NETWORK SERVICE` cannot see that user's WSL/tmux environment and is not a valid runner identity.
|
|
530
|
+
|
|
513
531
|
Logic lives in `src/core.ts` (tmux control, reduction, completion detection, safety, agent launch) and `src/rtk.ts` (per-command reducers); `src/index.ts` is the MCP surface. The design origin and the reducer's porting source (the pytest reducer is ported to match upstream rtk 0.42.0, except the deliberate `FAILED`-line difference noted above, and is locked by regression tests) are in `prototype/python/`.
|
|
514
532
|
|
|
515
533
|
## Try it
|
|
@@ -520,10 +538,10 @@ One command, no clone, no build:
|
|
|
520
538
|
claude mcp add --scope user --transport stdio aiterm -- npx -y aiterm-mcp
|
|
521
539
|
```
|
|
522
540
|
|
|
523
|
-
If aiterm let your AI hand a task to another agent — or saved you a round-trip of tokens — **[star the repo](https://github.com/kitepon
|
|
541
|
+
If aiterm let your AI hand a task to another agent — or saved you a round-trip of tokens — **[star the repo](https://github.com/kitepon/aiterm-mcp)**. It's the cheapest way to help others find it.
|
|
524
542
|
|
|
525
543
|
- **npm:** https://www.npmjs.com/package/aiterm-mcp
|
|
526
|
-
- **Issues / bug reports:** https://github.com/kitepon
|
|
544
|
+
- **Issues / bug reports:** https://github.com/kitepon/aiterm-mcp/issues
|
|
527
545
|
|
|
528
546
|
## Shared agent environment
|
|
529
547
|
|
package/dist/core.js
CHANGED
|
@@ -73,6 +73,8 @@ const AGENT_SUBMIT_RESIDUE_MAX_SAMPLES = 5;
|
|
|
73
73
|
const AGENT_SUBMIT_RESIDUE_TAIL_CHARS = 32;
|
|
74
74
|
const AGENT_SUBMIT_RESIDUE_MIN_TAIL_CHARS = 8;
|
|
75
75
|
const GROK_AUTH_MAX_BYTES = 64 * 1024;
|
|
76
|
+
const GROK_MODELS_MAX_BYTES = 1024 * 1024;
|
|
77
|
+
const GROK_MODELS_TIMEOUT_MS = 15_000;
|
|
76
78
|
const CLAUDE_RESULT_MAX_BYTES = 4 * 1024 * 1024;
|
|
77
79
|
// 出力削減(RTK の CAP 思想を移植)
|
|
78
80
|
const MAX_LINES_BEFORE_ELIDE = 60;
|
|
@@ -84,9 +86,11 @@ const LINE_TAIL_CHARS = 600;
|
|
|
84
86
|
const DEDUP_MIN_RUN = 3; // 同一行がこれ以上連続したら 1 行+件数に畳む
|
|
85
87
|
const MAX_FULL_BYTES = 8 * 1024 * 1024; // full/range 読取で一度にメモリへ載せる上限(B7)
|
|
86
88
|
const MAX_SEND_BYTES = 64 * 1024;
|
|
87
|
-
//
|
|
89
|
+
// PTY入力queueはOSを問わず、tmuxが長文を1回で流すと後半を落とすことがある。
|
|
88
90
|
// UTF-8境界を守って小さいtmux client roundtripに分け、各回にserver event loopがPTYへdrainできる境界を作る。
|
|
89
|
-
const PTY_PASTE_CHUNK_BYTES =
|
|
91
|
+
const PTY_PASTE_CHUNK_BYTES = 256;
|
|
92
|
+
const PTY_PASTE_CHUNK_PAUSE_MS = 10;
|
|
93
|
+
const PTY_PASTE_PAUSE_BUFFER = new Int32Array(new SharedArrayBuffer(4));
|
|
90
94
|
const SESSION_SEND_LOCK_WAIT_MS = 10_000;
|
|
91
95
|
const SESSION_SEND_LOCK_POLL_MS = 25;
|
|
92
96
|
// 安全: send 前に弾く破壊的コマンド(外部システム境界の防御)
|
|
@@ -785,7 +789,7 @@ async function waitCompletion(name, untilStr, untilRegex, timeout) {
|
|
|
785
789
|
if (safeStatSize(logpath(name)) !== size) {
|
|
786
790
|
stable = 0;
|
|
787
791
|
}
|
|
788
|
-
else if (SHELLS.has(fg)) {
|
|
792
|
+
else if (!until && !markActive && SHELLS.has(fg)) {
|
|
789
793
|
if (isWin)
|
|
790
794
|
await settleWinLog(name);
|
|
791
795
|
return [true, "quiescent"]; // 出力静止 ∧ シェル復帰 = 確証つき完了
|
|
@@ -794,7 +798,8 @@ async function waitCompletion(name, untilStr, untilRegex, timeout) {
|
|
|
794
798
|
// ネスト中(前面が ssh/docker/REPL 等でシェル集合外)は quiescence の「シェル復帰」条件を
|
|
795
799
|
// 原理的に満たせない。until も mark も無ければこれ以上待っても確証は増えない(until/dead/
|
|
796
800
|
// quiescent/mark のいずれも発火し得ない)ので、出力静止時点で「未確定」のまま早期返却する。
|
|
797
|
-
// markActive
|
|
801
|
+
// until/markActive のときは指定した証拠を待つべく早期返却せず、shell builtinや
|
|
802
|
+
// 非シェル前面(sleep 等)でも待ち続ける。
|
|
798
803
|
// fg==="" は前面コマンド取得失敗=ネスト断定不可なので早期返却せず従来どおり timeout まで待つ。
|
|
799
804
|
if (isWin)
|
|
800
805
|
await settleWinLog(name);
|
|
@@ -1002,9 +1007,9 @@ export function send(name, text, o = {}) {
|
|
|
1002
1007
|
/* noop */
|
|
1003
1008
|
}
|
|
1004
1009
|
}
|
|
1005
|
-
// `send-keys -l`と単発`paste-buffer`は、長文をPTY入力queue
|
|
1006
|
-
// 途中以降が欠落しても tmux 自体は code=0
|
|
1007
|
-
//
|
|
1010
|
+
// `send-keys -l`と単発`paste-buffer`は、長文をPTY入力queueへ一度に流すと、OSを問わず
|
|
1011
|
+
// 途中以降が欠落しても tmux 自体は code=0 を返す。UTF-8を壊さない256byte以下に分け、
|
|
1012
|
+
// 全chunk+Enterをsession単位のcross-process lock内で直列化する。
|
|
1008
1013
|
const pasteSupportsNoSanitize = pasteBufferSupportsNoSanitizeFlag();
|
|
1009
1014
|
const chunks = splitPtyText(text);
|
|
1010
1015
|
const bufferBase = `aiterm-${process.pid}-${randomBytes(8).toString("hex")}`;
|
|
@@ -1034,6 +1039,11 @@ export function send(name, text, o = {}) {
|
|
|
1034
1039
|
throw new AitermError(`tmux bufferのPTY送信に失敗しました` +
|
|
1035
1040
|
`(chunk ${i + 1}/${chunks.length}): ${pasted.stderr.trim() || `code=${pasted.code}`}.${partial}`, 2);
|
|
1036
1041
|
}
|
|
1042
|
+
if (i + 1 < chunks.length) {
|
|
1043
|
+
// tmux clientの終了だけでは、serverが前chunkをPTYへdrainしたことを保証しない。
|
|
1044
|
+
// 次chunkまで短く間を空け、外部PTY入力queueの飽和による黙った末尾欠落を防ぐ。
|
|
1045
|
+
Atomics.wait(PTY_PASTE_PAUSE_BUFFER, 0, 0, PTY_PASTE_CHUNK_PAUSE_MS);
|
|
1046
|
+
}
|
|
1037
1047
|
}
|
|
1038
1048
|
if (enter) {
|
|
1039
1049
|
const entered = tmux("send-keys", "-t", name, "Enter");
|
|
@@ -1659,6 +1669,41 @@ function resolveAndValidateGrokAuth(srcHome) {
|
|
|
1659
1669
|
fs.closeSync(fd);
|
|
1660
1670
|
}
|
|
1661
1671
|
}
|
|
1672
|
+
function grokModelCatalog(bin, cwd) {
|
|
1673
|
+
const result = spawnAgentControlCommand(bin, ["models"], cwd, {
|
|
1674
|
+
cwd,
|
|
1675
|
+
encoding: "utf8",
|
|
1676
|
+
env: process.env,
|
|
1677
|
+
timeout: GROK_MODELS_TIMEOUT_MS,
|
|
1678
|
+
maxBuffer: GROK_MODELS_MAX_BYTES,
|
|
1679
|
+
windowsHide: true,
|
|
1680
|
+
});
|
|
1681
|
+
if (result.error || result.status !== 0) {
|
|
1682
|
+
const detail = result.error?.message || result.stderr?.trim() || `exit=${result.status ?? "unknown"}`;
|
|
1683
|
+
throw new AitermError(`Grok model catalog を取得できません: ${detail}`, 2);
|
|
1684
|
+
}
|
|
1685
|
+
const text = result.stdout.replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "");
|
|
1686
|
+
const marker = "Available models:";
|
|
1687
|
+
const start = text.indexOf(marker);
|
|
1688
|
+
if (start < 0)
|
|
1689
|
+
throw new AitermError("Grok model catalog の出力形式が不正です(Available models がありません)", 2);
|
|
1690
|
+
const models = [];
|
|
1691
|
+
for (const line of text.slice(start + marker.length).split(/\r?\n/)) {
|
|
1692
|
+
const match = line.match(/^\s{2}[-*]\s+(.+?)(?:\s+\(default\))?\s*$/);
|
|
1693
|
+
if (match?.[1])
|
|
1694
|
+
models.push(match[1]);
|
|
1695
|
+
}
|
|
1696
|
+
if (models.length === 0)
|
|
1697
|
+
throw new AitermError("Grok model catalog に利用可能なmodelがありません", 2);
|
|
1698
|
+
return models;
|
|
1699
|
+
}
|
|
1700
|
+
function assertGrokModelAvailable(bin, cwd, model) {
|
|
1701
|
+
const models = grokModelCatalog(bin, cwd);
|
|
1702
|
+
if (!models.includes(model)) {
|
|
1703
|
+
throw new AitermError(`Grok model catalog に ${JSON.stringify(model)} がありません。利用可能: ${models.join(", ")}。` +
|
|
1704
|
+
"別modelへfallbackせず起動を中止しました", 2);
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1662
1707
|
function writeAgentMetadata(meta) {
|
|
1663
1708
|
writeJson0600(agentMetadataPath(meta.aiterm_session, meta.launch_id), meta);
|
|
1664
1709
|
}
|
|
@@ -3577,6 +3622,55 @@ async function waitForScreenText(name, text, timeoutMs = 3_000) {
|
|
|
3577
3622
|
} while (performance.now() < deadline);
|
|
3578
3623
|
throw new AitermError(`${agentLabel(loadAgentMetadata(name).kind)} の ${text} 画面を確認できません`, 2);
|
|
3579
3624
|
}
|
|
3625
|
+
function lineCounts(screen) {
|
|
3626
|
+
const counts = new Map();
|
|
3627
|
+
for (const line of screen.split("\n").map((value) => value.trim()).filter(Boolean)) {
|
|
3628
|
+
counts.set(line, (counts.get(line) ?? 0) + 1);
|
|
3629
|
+
}
|
|
3630
|
+
return counts;
|
|
3631
|
+
}
|
|
3632
|
+
function grokFooterHasConfiguration(screen, model, effort) {
|
|
3633
|
+
const modelMatch = model?.match(/^grok-(\d+(?:\.\d+)*)$/) ?? null;
|
|
3634
|
+
if (model && !modelMatch)
|
|
3635
|
+
return false;
|
|
3636
|
+
const modelLabel = modelMatch ? `Grok ${modelMatch[1]}` : null;
|
|
3637
|
+
return screen.split("\n").some((line) => {
|
|
3638
|
+
if (!line.includes("·"))
|
|
3639
|
+
return false;
|
|
3640
|
+
if (modelLabel && !line.includes(`${modelLabel} (`))
|
|
3641
|
+
return false;
|
|
3642
|
+
if (effort && !line.includes(`(${effort})`))
|
|
3643
|
+
return false;
|
|
3644
|
+
return modelLabel !== null || effort !== null;
|
|
3645
|
+
});
|
|
3646
|
+
}
|
|
3647
|
+
async function waitForGrokConfigurationResult(name, before, model, effort, timeoutMs = 3_000) {
|
|
3648
|
+
const beforeCounts = lineCounts(before);
|
|
3649
|
+
const footerAlreadyMatched = grokFooterHasConfiguration(before, model, effort);
|
|
3650
|
+
const deadline = performance.now() + timeoutMs;
|
|
3651
|
+
do {
|
|
3652
|
+
const screen = captureScreen(name, AGENT_TUI_READY_LINES);
|
|
3653
|
+
const seen = new Map();
|
|
3654
|
+
const added = screen.split("\n").map((value) => value.trim()).filter((line) => {
|
|
3655
|
+
if (!line)
|
|
3656
|
+
return false;
|
|
3657
|
+
const count = (seen.get(line) ?? 0) + 1;
|
|
3658
|
+
seen.set(line, count);
|
|
3659
|
+
return count > (beforeCounts.get(line) ?? 0);
|
|
3660
|
+
});
|
|
3661
|
+
const error = added.find((line) => /^(?:Unknown model:|unknown effort level|Usage: \/(?:model|effort)\b|Invalid (?:model|reasoning effort)|.*does not support reasoning effort)/i.test(line));
|
|
3662
|
+
if (error)
|
|
3663
|
+
throw new AitermError(`Grokの設定変更に失敗しました: ${error}`, 2);
|
|
3664
|
+
if (added.some((line) => /^(?:Switched to |✓?\s*Default model:)/.test(line)))
|
|
3665
|
+
return;
|
|
3666
|
+
// Grok Build 1.0.3では成功通知が次の再描画で消えることがある。変更前には無かった
|
|
3667
|
+
// target model/effortが常駐footerへ現れた場合も、vendor自身の最終状態として受理する。
|
|
3668
|
+
if (!footerAlreadyMatched && grokFooterHasConfiguration(screen, model, effort))
|
|
3669
|
+
return;
|
|
3670
|
+
await sleep(100);
|
|
3671
|
+
} while (performance.now() < deadline);
|
|
3672
|
+
throw new AitermError(`${agentLabel(loadAgentMetadata(name).kind)} の設定変更完了を確認できません`, 2);
|
|
3673
|
+
}
|
|
3580
3674
|
function sendMenuChoice(name, choice) {
|
|
3581
3675
|
const sent = tmux("send-keys", "-t", name, choice);
|
|
3582
3676
|
if (sent.code !== 0) {
|
|
@@ -3625,10 +3719,10 @@ export async function configureAgent(name, opts) {
|
|
|
3625
3719
|
const effort = opts.reasoning_effort?.trim() || null;
|
|
3626
3720
|
if (!model && !effort)
|
|
3627
3721
|
throw new AitermError("model または reasoning_effort を指定してください", 2);
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
throw new AitermError("agent_configure はClaudeとCodexのsessionだけに対応します", 2);
|
|
3722
|
+
if ((model && /[\r\n]/.test(model)) || (effort && /[\r\n]/.test(effort))) {
|
|
3723
|
+
throw new AitermError("model/reasoning_effort に改行を含められません", 2);
|
|
3631
3724
|
}
|
|
3725
|
+
const meta = loadAgentMetadata(name);
|
|
3632
3726
|
bindCompletedInitialPrompt(meta);
|
|
3633
3727
|
const ready = await waitAgentTuiReady(name, meta, AGENT_TUI_READY_TIMEOUT_MS);
|
|
3634
3728
|
if (!ready.ready)
|
|
@@ -3654,6 +3748,27 @@ export async function configureAgent(name, opts) {
|
|
|
3654
3748
|
reasoning_effort: effort,
|
|
3655
3749
|
};
|
|
3656
3750
|
}
|
|
3751
|
+
if (meta.kind === "grok" || meta.kind === "composer") {
|
|
3752
|
+
if (model) {
|
|
3753
|
+
const bin = resolveAgentBin(meta.kind);
|
|
3754
|
+
if (!bin)
|
|
3755
|
+
throw new AitermError(`${agentLabel(meta.kind)} の CLI が見つかりません`, 2);
|
|
3756
|
+
assertGrokModelAvailable(bin, meta.cwd ?? process.cwd(), model);
|
|
3757
|
+
}
|
|
3758
|
+
const command = model
|
|
3759
|
+
? `/model ${model}${effort ? ` ${effort}` : ""}`
|
|
3760
|
+
: `/effort ${effort}`;
|
|
3761
|
+
const before = captureScreen(name, AGENT_TUI_READY_LINES);
|
|
3762
|
+
await sendAgentPromptText(name, command);
|
|
3763
|
+
await waitForGrokConfigurationResult(name, before, model, effort);
|
|
3764
|
+
return {
|
|
3765
|
+
schema: "aiterm.agent-configure-result.v1",
|
|
3766
|
+
session_id: name,
|
|
3767
|
+
provider: meta.kind,
|
|
3768
|
+
model,
|
|
3769
|
+
reasoning_effort: effort,
|
|
3770
|
+
};
|
|
3771
|
+
}
|
|
3657
3772
|
await sendAgentPromptText(name, "/model");
|
|
3658
3773
|
const modelScreen = await waitForScreenText(name, "Select Model and Effort");
|
|
3659
3774
|
if (model) {
|
|
@@ -3828,27 +3943,32 @@ function resolveAgentBin(kind) {
|
|
|
3828
3943
|
// 明示指定 env は実在を検証する。存在しないパスを黙って返すと、session を作って
|
|
3829
3944
|
// `'/typo' ...` を送信し bash が command not found を出すだけで openAgent は「起動した」と
|
|
3830
3945
|
// 偽成功を返す(既定パス/PATH 経路は検証するのに env だけ無検証だった非対称の解消・A3)。
|
|
3831
|
-
if (
|
|
3832
|
-
return fromEnv;
|
|
3946
|
+
if (isUsableAgentExecutableFile(fromEnv))
|
|
3947
|
+
return resolveWindowsCodexShim(kind, fromEnv);
|
|
3833
3948
|
throw new AitermError(`${envVar} に指定された ${name} が存在しません: ${fromEnv}`, 2);
|
|
3834
3949
|
}
|
|
3835
3950
|
const cand = path.join(home, ...rel);
|
|
3836
|
-
if (
|
|
3837
|
-
return cand;
|
|
3951
|
+
if (isUsableAgentExecutableFile(cand))
|
|
3952
|
+
return resolveWindowsCodexShim(kind, cand);
|
|
3838
3953
|
const w = spawnSync(isWin ? "where" : "which", [name], {
|
|
3839
3954
|
encoding: "utf8",
|
|
3840
3955
|
timeout: 5000,
|
|
3841
3956
|
});
|
|
3842
3957
|
if (w.status === 0 && (w.stdout ?? "").trim()) {
|
|
3843
|
-
const
|
|
3844
|
-
|
|
3845
|
-
|
|
3958
|
+
const found = w.stdout.trim().split(/\r?\n/).filter(Boolean);
|
|
3959
|
+
const ordered = isWin
|
|
3960
|
+
? [...found.filter((p) => /\.(?:exe|com|cmd|bat)$/i.test(p)), ...found.filter((p) => !/\.(?:exe|com|cmd|bat)$/i.test(p))]
|
|
3961
|
+
: found;
|
|
3962
|
+
for (const resolved of ordered) {
|
|
3963
|
+
if (isUsableAgentExecutableFile(resolved))
|
|
3964
|
+
return resolveWindowsCodexShim(kind, resolved);
|
|
3965
|
+
}
|
|
3846
3966
|
}
|
|
3847
3967
|
return null;
|
|
3848
3968
|
}
|
|
3849
3969
|
const CLAUDE_AUTH_STATUS_TIMEOUT_MS = 5_000;
|
|
3850
3970
|
function assertClaudeAuthenticationReady(bin) {
|
|
3851
|
-
const result =
|
|
3971
|
+
const result = spawnAgentControlCommand(bin, ["auth", "status", "--json"], process.cwd(), {
|
|
3852
3972
|
encoding: "utf8",
|
|
3853
3973
|
timeout: CLAUDE_AUTH_STATUS_TIMEOUT_MS,
|
|
3854
3974
|
maxBuffer: 64 * 1024,
|
|
@@ -3893,6 +4013,71 @@ function isUsableExecutableFile(candidate) {
|
|
|
3893
4013
|
return false;
|
|
3894
4014
|
}
|
|
3895
4015
|
}
|
|
4016
|
+
function isWindowsDrivePath(candidate) {
|
|
4017
|
+
return /^[A-Za-z]:[\\/]/.test(candidate);
|
|
4018
|
+
}
|
|
4019
|
+
function isWindowsNativeExecutable(candidate) {
|
|
4020
|
+
return isWindowsDrivePath(candidate) && /\.(?:exe|com|cmd|bat)$/i.test(candidate);
|
|
4021
|
+
}
|
|
4022
|
+
function isUsableWslExecutable(candidate) {
|
|
4023
|
+
if (!isWin)
|
|
4024
|
+
return false;
|
|
4025
|
+
let wslPath = candidate;
|
|
4026
|
+
if (isWindowsDrivePath(candidate)) {
|
|
4027
|
+
try {
|
|
4028
|
+
if (!fs.statSync(candidate).isFile())
|
|
4029
|
+
return false;
|
|
4030
|
+
wslPath = toWslPath(candidate);
|
|
4031
|
+
}
|
|
4032
|
+
catch {
|
|
4033
|
+
return false;
|
|
4034
|
+
}
|
|
4035
|
+
}
|
|
4036
|
+
else if (!candidate.startsWith("/")) {
|
|
4037
|
+
return false;
|
|
4038
|
+
}
|
|
4039
|
+
const checked = spawnSync("wsl.exe", ["-e", "test", "-f", wslPath, "-a", "-x", wslPath], {
|
|
4040
|
+
encoding: "utf8",
|
|
4041
|
+
timeout: 5000,
|
|
4042
|
+
});
|
|
4043
|
+
return checked.status === 0;
|
|
4044
|
+
}
|
|
4045
|
+
function isUsableAgentExecutableFile(candidate) {
|
|
4046
|
+
if (!isWin)
|
|
4047
|
+
return isUsableExecutableFile(candidate);
|
|
4048
|
+
if (isWindowsNativeExecutable(candidate))
|
|
4049
|
+
return isUsableExecutableFile(candidate);
|
|
4050
|
+
return isUsableWslExecutable(candidate);
|
|
4051
|
+
}
|
|
4052
|
+
function resolveWindowsCodexShim(kind, candidate) {
|
|
4053
|
+
if (!isWin || kind !== "codex" || !/\.(?:cmd|bat)$/i.test(candidate))
|
|
4054
|
+
return candidate;
|
|
4055
|
+
const packageRoot = path.join(path.dirname(candidate), "node_modules", "@openai", "codex", "node_modules", "@openai");
|
|
4056
|
+
try {
|
|
4057
|
+
for (const platformPackage of fs.readdirSync(packageRoot).filter((name) => name.startsWith("codex-win32-"))) {
|
|
4058
|
+
const vendorRoot = path.join(packageRoot, platformPackage, "vendor");
|
|
4059
|
+
for (const target of fs.readdirSync(vendorRoot)) {
|
|
4060
|
+
const executable = path.join(vendorRoot, target, "bin", "codex.exe");
|
|
4061
|
+
if (isUsableExecutableFile(executable))
|
|
4062
|
+
return executable;
|
|
4063
|
+
}
|
|
4064
|
+
}
|
|
4065
|
+
}
|
|
4066
|
+
catch {
|
|
4067
|
+
/* 下の明示エラーへ */
|
|
4068
|
+
}
|
|
4069
|
+
throw new AitermError(`CODEX_BIN のnpm shimからWindows native codex.exeを解決できません: ${candidate}。` +
|
|
4070
|
+
"@openai/codexを再インストールするか、CODEX_BINへcodex.exeを指定してください", 2);
|
|
4071
|
+
}
|
|
4072
|
+
function agentBinForWslShell(bin) {
|
|
4073
|
+
return isWin && isWindowsDrivePath(bin) ? toWslPath(bin) : bin;
|
|
4074
|
+
}
|
|
4075
|
+
function spawnAgentControlCommand(bin, args, cwd, options) {
|
|
4076
|
+
if (!isWin || isWindowsNativeExecutable(bin))
|
|
4077
|
+
return spawnSync(bin, args, options);
|
|
4078
|
+
const wslCwd = isWindowsDrivePath(cwd) ? toWslPath(cwd) : cwd;
|
|
4079
|
+
return spawnSync("wsl.exe", ["--cd", wslCwd, "-e", agentBinForWslShell(bin), ...args], options);
|
|
4080
|
+
}
|
|
3896
4081
|
const THROUGHLINE_HANDOFF_CONTEXT_SCHEMA = "throughline.handoff_context.v1";
|
|
3897
4082
|
const PORTABLE_FORK_MISSION_SEPARATOR = "\n\n---\n\n## Portable fork mission\n\n";
|
|
3898
4083
|
function resolveThroughlineBin() {
|
|
@@ -4004,12 +4189,16 @@ function buildAgentCmd(kind, bin, model, effort, prompt, meta = null) {
|
|
|
4004
4189
|
}
|
|
4005
4190
|
}
|
|
4006
4191
|
else {
|
|
4007
|
-
// grok / composer は同じ grok CLI
|
|
4008
|
-
// 対話 TUI では警告の上無視されるため渡さない(openAgent が指定を事前拒否する)。
|
|
4192
|
+
// grok / composer は同じ grok CLI をモデル違いで起動する。
|
|
4009
4193
|
parts.push("--no-auto-update");
|
|
4010
4194
|
if (meta?.kind === "grok" || meta?.kind === "composer")
|
|
4011
4195
|
parts.push("--no-alt-screen");
|
|
4012
4196
|
parts.push("--model", shq(model ?? GROK_MODEL_DEFAULTS[kind]));
|
|
4197
|
+
if (effort)
|
|
4198
|
+
parts.push("--reasoning-effort", shq(effort));
|
|
4199
|
+
if ((meta?.kind === "grok" || meta?.kind === "composer") && meta.write_scope === "read-only") {
|
|
4200
|
+
parts.push("--sandbox", "read-only");
|
|
4201
|
+
}
|
|
4013
4202
|
if ((meta?.kind === "grok" || meta?.kind === "composer") && meta.hook_route === "shared_grok_home") {
|
|
4014
4203
|
parts.push("--session-id", shq(meta.vendor_session_id ?? ""), "--rules", shq(subagentInstruction(meta)));
|
|
4015
4204
|
}
|
|
@@ -4066,15 +4255,15 @@ function agentLabel(kind) {
|
|
|
4066
4255
|
function buildAgentLaunchNote(kind, model, effort, meta) {
|
|
4067
4256
|
const writeScopeNote = meta?.write_scope === undefined
|
|
4068
4257
|
? ""
|
|
4069
|
-
: kind === "codex" && meta.write_scope === "read-only"
|
|
4070
|
-
? `\n能力宣言: write_scope=${JSON.stringify(meta.write_scope)}
|
|
4071
|
-
: `\n能力宣言: write_scope=${JSON.stringify(meta.write_scope)}
|
|
4258
|
+
: (kind === "codex" || kind === "grok" || kind === "composer") && meta.write_scope === "read-only"
|
|
4259
|
+
? `\n能力宣言: write_scope=${JSON.stringify(meta.write_scope)}。${agentLabel(kind)} CLIへ --sandbox read-only を付与し、書込みを実効禁止。`
|
|
4260
|
+
: `\n能力宣言: write_scope=${JSON.stringify(meta.write_scope)}。パス単位のsandbox allowlistに対応するCLI引数がないため宣言の記録のみ(構造的unsupported)。`;
|
|
4072
4261
|
if (kind === "claude") {
|
|
4073
4262
|
return `起動設定: model=${model ?? "CLI既定"} effort=${effort ?? "CLI既定"}。${writeScopeNote}`;
|
|
4074
4263
|
}
|
|
4075
4264
|
if (kind !== "codex") {
|
|
4076
4265
|
return (`起動設定: model=${model ?? GROK_MODEL_DEFAULTS[kind]}(${model ? "引数" : "ツール既定"})。` +
|
|
4077
|
-
|
|
4266
|
+
`effort=${effort ?? "CLI/model既定"}。` + writeScopeNote);
|
|
4078
4267
|
}
|
|
4079
4268
|
const configPath = meta?.kind === "codex" && meta.codex_home
|
|
4080
4269
|
? path.join(meta.codex_home, "config.toml")
|
|
@@ -4145,18 +4334,6 @@ export function openAgent(kind, opts = {}) {
|
|
|
4145
4334
|
if (effort && kind === "claude" && !CLAUDE_EFFORTS.has(effort)) {
|
|
4146
4335
|
throw new AitermError("Claude Code の reasoning_effort は low/medium/high/xhigh/max のいずれかです", 2);
|
|
4147
4336
|
}
|
|
4148
|
-
// grok CLI の --effort は headless(grok -p)専用で、対話 TUI では警告の上無視される。
|
|
4149
|
-
// 黙って no-op の引数を受けない=起動前に明示エラーで拒否する(codex は CLI 側の値集合が
|
|
4150
|
-
// 版で変わるため縛らず送信まで通す)。
|
|
4151
|
-
if (effort && kind === "grok") {
|
|
4152
|
-
throw new AitermError(`${label} は reasoning_effort を指定できません。grok CLI の --effort は headless(grok -p)専用で、` +
|
|
4153
|
-
"対話 TUI では警告の上無視されます(grok-4.5 の TUI 既定 effort は high)。" +
|
|
4154
|
-
"effort 制御が必要なら通常 PTY で `grok -p --effort low|medium|high ...` を使ってください", 2);
|
|
4155
|
-
}
|
|
4156
|
-
if (effort && kind === "composer") {
|
|
4157
|
-
throw new AitermError(`${label} は reasoning_effort を指定できません。grok-composer-2.5-fast は reasoning effort 非対応です` +
|
|
4158
|
-
"(モデルカタログ supports_reasoning_effort=false)", 2);
|
|
4159
|
-
}
|
|
4160
4337
|
const agentDone = !!opts.agent_done;
|
|
4161
4338
|
const launchOperationId = opts.launch_operation_id == null
|
|
4162
4339
|
? null
|
|
@@ -4231,9 +4408,14 @@ export function openAgent(kind, opts = {}) {
|
|
|
4231
4408
|
// パスで解決)。toWslPath は session を作る前に呼ぶ=変換失敗(非ドライブパス)で残骸 session を残さない。
|
|
4232
4409
|
// 未検証リスク: npm グローバル導入の codex.cmd/.bat シムや WSL interop 上の対話 TUI 描画は実 Windows
|
|
4233
4410
|
// でしか確認できない(CI 非対象。docs/03_audit-sweep-2026-07.md 参照)。
|
|
4234
|
-
const binForCmd =
|
|
4411
|
+
const binForCmd = agentBinForWslShell(bin);
|
|
4235
4412
|
const cwdForCmd = cwd && isWin ? toWslPath(cwd) : cwd;
|
|
4236
4413
|
const grokAuthPath = agentDone && (kind === "grok" || kind === "composer") ? resolveAndValidateGrokAuth(realGrokHome()) : null;
|
|
4414
|
+
if (kind === "grok" || kind === "composer") {
|
|
4415
|
+
const requestedModel = model ?? (kind === "composer" ? GROK_MODEL_DEFAULTS.composer : null);
|
|
4416
|
+
if (requestedModel)
|
|
4417
|
+
assertGrokModelAvailable(bin, cwd ?? process.cwd(), requestedModel);
|
|
4418
|
+
}
|
|
4237
4419
|
let sid;
|
|
4238
4420
|
let hint;
|
|
4239
4421
|
try {
|
package/dist/index.js
CHANGED
|
@@ -397,8 +397,8 @@ server.registerTool("claude_approval", {
|
|
|
397
397
|
}
|
|
398
398
|
});
|
|
399
399
|
server.registerTool("agent_configure", {
|
|
400
|
-
description: "起動済みのCodex/
|
|
401
|
-
"ClaudeはCLI標準の/model・/effort、CodexはCLI標準の/model選択画面を使う。",
|
|
400
|
+
description: "起動済みのClaude/Codex/Grok/Composer agent sessionを再起動せず、会話contextを保ったままmodel/reasoning effortを変更する。" +
|
|
401
|
+
"Claude/Grok/ComposerはCLI標準の/model・/effort、CodexはCLI標準の/model選択画面を使う。",
|
|
402
402
|
inputSchema: {
|
|
403
403
|
session_id: z.string().regex(/^[A-Za-z0-9_-]{1,64}$/),
|
|
404
404
|
model: z.string().min(1).nullish().describe("変更後のmodel。省略時はmodelを変更しない"),
|
|
@@ -407,7 +407,7 @@ server.registerTool("agent_configure", {
|
|
|
407
407
|
outputSchema: {
|
|
408
408
|
schema: z.literal("aiterm.agent-configure-result.v1"),
|
|
409
409
|
session_id: z.string().regex(/^[A-Za-z0-9_-]{1,64}$/),
|
|
410
|
-
provider: z.enum(["claude", "codex"]),
|
|
410
|
+
provider: z.enum(["claude", "codex", "grok", "composer"]),
|
|
411
411
|
model: z.string().nullable(),
|
|
412
412
|
reasoning_effort: z.string().nullable(),
|
|
413
413
|
},
|
|
@@ -430,14 +430,16 @@ const agentModelDesc = (kind) => kind === "claude"
|
|
|
430
430
|
: kind === "codex"
|
|
431
431
|
? "起動モデル(例: gpt-5.6-sol / gpt-5.6-terra / gpt-5.6-luna)。省略時は端末 config/CLI 既定を継承" +
|
|
432
432
|
"(端末側のピンがそのまま効く。実効値は起動応答に明示される)"
|
|
433
|
-
: `起動モデル。省略時は ${kind === "grok" ? "grok-4.5" : "grok-composer-2.5-fast"}
|
|
433
|
+
: `起動モデル。省略時は ${kind === "grok" ? "grok-4.5" : "grok-composer-2.5-fast"}。` +
|
|
434
|
+
(kind === "composer"
|
|
435
|
+
? "既定/explicit modelを起動前にlive catalogへ照合し、不在ならfallbackせずエラー"
|
|
436
|
+
: "explicit modelを起動前にlive catalogへ照合し、不在ならfallbackせずエラー");
|
|
434
437
|
const agentEffortDesc = (kind) => kind === "claude"
|
|
435
438
|
? "Claude Code reasoning effort。low/medium/high/xhigh/max。省略時はCLI既定"
|
|
436
|
-
: kind === "
|
|
437
|
-
? "
|
|
438
|
-
"
|
|
439
|
-
: "reasoning effort
|
|
440
|
-
"ultra は max 推論+proactive 自動委譲 ON=使用量急増注意(明示要求時のみ)。省略時は端末 config/CLI 既定。";
|
|
439
|
+
: kind === "codex"
|
|
440
|
+
? "reasoning effort(思考レベル)。low/medium/high/xhigh/max/ultra(CLI/model 版依存)。" +
|
|
441
|
+
"ultra は max 推論+proactive 自動委譲 ON=使用量急増注意(明示要求時のみ)。省略時は端末 config/CLI 既定。"
|
|
442
|
+
: "Grok Build reasoning effort。利用可能値はCLI/modelのlive catalogに従う。省略時はCLI/model既定。";
|
|
441
443
|
// 全launcher共通の完了受信ガイド。待ちコマンドは起動応答の wait_command(初回prompt時)または
|
|
442
444
|
// pty_send dispatch の event_cursor から組む。文型は NON_BLOCKING_RULE と同じく「待たない」が先。
|
|
443
445
|
const agentCompletionDesc = `起動して投げたら投げっぱなしでよい=親はここで待たない。` +
|
|
@@ -453,7 +455,7 @@ function registerAgentTool(toolName, kind, desc) {
|
|
|
453
455
|
const supportsWriteScope = kind === "codex" || kind === "grok" || kind === "composer";
|
|
454
456
|
const writeScopeInputSchema = supportsWriteScope
|
|
455
457
|
? {
|
|
456
|
-
write_scope: z.string().min(1).optional().describe("能力宣言。read-only、または書込みを許可するパスの説明文字列。Codexのread-only
|
|
458
|
+
write_scope: z.string().min(1).optional().describe("能力宣言。read-only、または書込みを許可するパスの説明文字列。Codex/Grok/Composerのread-onlyはCLI sandboxで実効禁止する"),
|
|
457
459
|
}
|
|
458
460
|
: {};
|
|
459
461
|
const writeScopeOutputSchema = supportsWriteScope
|
|
@@ -480,8 +482,7 @@ function registerAgentTool(toolName, kind, desc) {
|
|
|
480
482
|
.optional()
|
|
481
483
|
.describe("同一端末のThroughline sessionから所有権を変えずに記憶を読み、promptのmissionより前へ注入する"),
|
|
482
484
|
model: z.string().nullish().describe(agentModelDesc(kind)),
|
|
483
|
-
//
|
|
484
|
-
// codex は CLI 側の値集合が版で変わるため縛らない(core 側も同方針)。
|
|
485
|
+
// CLI/model側の値集合が版で変わるため公開enumでは縛らない(core側も同方針)。
|
|
485
486
|
reasoning_effort: z.string().nullish().describe(agentEffortDesc(kind)),
|
|
486
487
|
env_vars: z.array(z.string()).optional().describe("起動したagentへ現在のMCP processから継承する環境変数名。値はtool引数へ渡さない"),
|
|
487
488
|
cwd: z.string().nullish().describe("作業ディレクトリ(対象リポのルート等・任意)"),
|
|
@@ -527,7 +528,7 @@ function registerAgentTool(toolName, kind, desc) {
|
|
|
527
528
|
...(supportsWriteScope && write_scope !== undefined
|
|
528
529
|
? {
|
|
529
530
|
write_scope,
|
|
530
|
-
write_scope_enforcement: kind === "codex" && write_scope === "read-only"
|
|
531
|
+
write_scope_enforcement: (kind === "codex" || kind === "grok" || kind === "composer") && write_scope === "read-only"
|
|
531
532
|
? "enforced_read_only"
|
|
532
533
|
: "declaration_only_unsupported",
|
|
533
534
|
}
|
|
@@ -563,12 +564,13 @@ registerAgentTool("grok_agent", "grok", "【Grok Build の Grok モデル (既
|
|
|
563
564
|
agentEnvironmentDesc +
|
|
564
565
|
"turn は pty_send で送る(自動で非ブロック dispatch になる)。" +
|
|
565
566
|
agentCompletionDesc +
|
|
566
|
-
"model
|
|
567
|
+
"model/reasoning_effortを引数で指定可。read-only sandboxとagent_configureに対応。");
|
|
567
568
|
registerAgentTool("composer_agent", "composer", "【Grok Build の Composer モデル (既定 grok-composer-2.5-fast)】の対話エージェント TUI を永続端末に起動する。" +
|
|
568
569
|
agentEnvironmentDesc +
|
|
569
570
|
"turn は pty_send で送る(自動で非ブロック dispatch になる)。" +
|
|
570
571
|
agentCompletionDesc +
|
|
571
|
-
"model
|
|
572
|
+
"model/reasoning_effortを引数で指定可。live catalogにComposer modelがなければGrokへfallbackせず明示エラー。" +
|
|
573
|
+
"read-only sandboxとagent_configureに対応。");
|
|
572
574
|
async function main() {
|
|
573
575
|
// 親ホストを initialize の clientInfo.name から確定させ、receipt の完了待ちコマンドを
|
|
574
576
|
// そのホストの実際の起動形で名指しする(実測: claude-code は initialize → notifications/initialized
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aiterm-mcp",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"mcpName": "io.github.kitepon
|
|
5
|
-
"description": "Persistent tmux terminal MCP
|
|
3
|
+
"version": "0.25.2",
|
|
4
|
+
"mcpName": "io.github.kitepon/aiterm-mcp",
|
|
5
|
+
"description": "Persistent tmux terminal MCP for launching and driving Claude, Codex, Grok, or Composer from any MCP client, cross-vendor or same-vendor. Also runs durable PTY sessions for SSH, containers, and REPLs.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"mcp",
|
|
8
8
|
"mcp-server",
|
|
@@ -31,12 +31,12 @@
|
|
|
31
31
|
},
|
|
32
32
|
"repository": {
|
|
33
33
|
"type": "git",
|
|
34
|
-
"url": "git+https://github.com/kitepon
|
|
34
|
+
"url": "git+https://github.com/kitepon/aiterm-mcp.git"
|
|
35
35
|
},
|
|
36
36
|
"bugs": {
|
|
37
|
-
"url": "https://github.com/kitepon
|
|
37
|
+
"url": "https://github.com/kitepon/aiterm-mcp/issues"
|
|
38
38
|
},
|
|
39
|
-
"homepage": "https://github.com/kitepon
|
|
39
|
+
"homepage": "https://github.com/kitepon/aiterm-mcp#readme",
|
|
40
40
|
"type": "module",
|
|
41
41
|
"bin": {
|
|
42
42
|
"aiterm-mcp": "dist/index.js",
|