aiterm-mcp 0.9.1 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja.md +7 -7
- package/README.md +11 -11
- package/dist/core.js +364 -32
- package/dist/index.js +37 -15
- package/package.json +1 -1
package/README.ja.md
CHANGED
|
@@ -45,11 +45,11 @@ pty_read(id, { wait: true }) → 削減済みの出力を読む(完了
|
|
|
45
45
|
|
|
46
46
|
### 2. その端末の中に他のコーディングエージェントを起動する — オーケストレーションの旗艦
|
|
47
47
|
|
|
48
|
-
同じ primitive が別エージェントの TUI を宿す。3 つの起動ツールが、各ベンダーの対話型コーディングエージェント TUI を新しい永続端末の中に起動し、`session_id` を返す。以後はどのシェルとも同じ `pty_read` / `pty_send` で操作する——出力をトークン削減して読み、次の一手を送る。(TUI は全画面アプリなので `pty_read({ screen: true })` で描画済みの画面が得られる。)`agent_done: true` を付けると hook ベースのターン完了待ちが有効になり、`pty_send({ wait: "agent_done" })` が agent
|
|
48
|
+
同じ primitive が別エージェントの TUI を宿す。3 つの起動ツールが、各ベンダーの対話型コーディングエージェント TUI を新しい永続端末の中に起動し、`session_id` を返す。以後はどのシェルとも同じ `pty_read` / `pty_send` で操作する——出力をトークン削減して読み、次の一手を送る。(TUI は全画面アプリなので `pty_read({ screen: true })` で描画済みの画面が得られる。)`agent_done: true` を付けると hook ベースのターン完了待ちが有効になり、`pty_send({ wait: "agent_done" })` が agent のターン終了後に返る。`codex_agent` は起動時 `prompt` も待てる——`prompt`、`agent_done:true`、`wait:"agent_done"` を渡すと、まず TUI を起動し、入力欄の ready を待ってから初回 prompt を送り、その初回ターンの Stop hook 後に返る。Codex/Grok/Composer は、各ベンダー CLI が認証済みなら後続 `pty_send(wait:"agent_done")` は実 smoke 済み。Codex の初回 prompt wait も実 smoke 済み。Grok/Composer の初回 prompt wait は post-OAuth smoke が通るまで意図的に未公開。これはベンダー自身の CLI が導入・認証済みであることが必要——[要件](#要件)参照。
|
|
49
49
|
|
|
50
50
|
```text
|
|
51
51
|
codex_agent({ session_name: "codex1", cwd: "/repo", agent_done: true,
|
|
52
|
-
prompt: "port test/legacy.py to vitest" })
|
|
52
|
+
wait: "agent_done", prompt: "port test/legacy.py to vitest" })
|
|
53
53
|
→ { session_id: "codex1", … } # Codex が永続端末で稼働開始
|
|
54
54
|
pty_read("codex1", { screen: true }) → 何をしているか読む(トークン削減)
|
|
55
55
|
pty_send("codex1", "also fix the imports it broke", { wait: "agent_done" })
|
|
@@ -60,11 +60,11 @@ pty_send("codex1", "also fix the imports it broke", { wait: "agent_done" })
|
|
|
60
60
|
|
|
61
61
|
| ツール | 起動するもの | 主な引数 |
|
|
62
62
|
| --- | --- | --- |
|
|
63
|
-
| `codex_agent` | Codex CLI(OpenAI・モデルは CLI の既定) | `prompt?`, `reasoning_effort?`, `cwd?`, `session_name?`, `agent_done?` |
|
|
63
|
+
| `codex_agent` | Codex CLI(OpenAI・モデルは CLI の既定) | `prompt?`, `reasoning_effort?`, `cwd?`, `session_name?`, `agent_done?`, `wait?`, `timeout?`, `screen?`, `lines?` |
|
|
64
64
|
| `grok_agent` | Grok Build の `grok-build` モデル(xAI) | `prompt?`, `reasoning_effort?`(`low`/`medium`/`high`/`xhigh`/`max`), `cwd?`, `session_name?`, `agent_done?` |
|
|
65
65
|
| `composer_agent` | Grok Build の `grok-composer-2.5-fast` モデル(xAI) | `grok_agent` と同じ |
|
|
66
66
|
|
|
67
|
-
各ベンダーの CLI が導入・認証済みであること(`codex_agent` は `codex`、Grok 系 2 つは `grok`)。バイナリは `CODEX_BIN` / `GROK_BIN` → `~/.local/bin/codex` / `~/.grok/bin/grok` → `PATH` の順で解決する。前提は **session を作る前に**検証する——`grok_agent` / `composer_agent` は範囲外の `reasoning_effort` を先に弾き(値集合は固定=`low`/`medium`/`high`/`xhigh`/`max`)、CLI バイナリ不在・実在しない `cwd` は 3 つとも失敗する。前提(`reasoning_effort`・バイナリ・`cwd`)で弾かれた起動は **session の残骸をゼロ**にして返し、起動キーストロークを送れなければ session
|
|
67
|
+
各ベンダーの CLI が導入・認証済みであること(`codex_agent` は `codex`、Grok 系 2 つは `grok`)。バイナリは `CODEX_BIN` / `GROK_BIN` → `~/.local/bin/codex` / `~/.grok/bin/grok` → `PATH` の順で解決する。前提は **session を作る前に**検証する——`grok_agent` / `composer_agent` は範囲外の `reasoning_effort` を先に弾き(値集合は固定=`low`/`medium`/`high`/`xhigh`/`max`)、CLI バイナリ不在・実在しない `cwd` は 3 つとも失敗する。前提(`reasoning_effort`・バイナリ・`cwd`)で弾かれた起動は **session の残骸をゼロ**にして返し、起動キーストロークを送れなければ session を畳む。(初回 prompt wait を使わない場合、その先でベンダー CLI が実際に立ち上がったか・認証できたかは aiterm は検証しない——起動できたかは session を読んで確認する。)(`codex_agent` は Codex の受理値が版で変わるため、`reasoning_effort` を検証せず config override〔`-c model_reasoning_effort=…`〕として Codex CLI へ渡す。)`cwd` は絶対パスで渡す——`~` は展開されない。`agent_done` は launch ごとの managed vendor home を使い、通常の hook file は書き換えない。Codex は managed `CODEX_HOME` に `auth.json` だけを symlink し、`config.toml` はコピーする。Grok/Composer はさらに `GROK_HOME` と `HOME` を隔離し、compat hook/plugin 混入を抑える。一方で Grok OAuth は通常 Grok home の `auth.json` と `auth.json.lock` をセットで共有し、複数セッション間で refresh lock が分裂しないようにする。初回の未 bind `pty_send({ wait: "agent_done" })` は、vendor TUI の入力欄が出るまで待ち、入力受付状態でなければ文字列を送らず送信前エラーにする。`codex_agent` launcher の `wait:"agent_done"` も同じく `prompt` と `agent_done:true` が必須。TUI が入力欄 ready にならない場合は prompt を送らず、`initial_prompt=not_sent` として session を調査可能なまま残す。起動時 prompt が `pending`/`sent` の間は、混入防止のため通常 `pty_send` も拒否する。手動介入は `pty_key`、または意図的な `pty_send(..., force:true)` で行う。`agent_done` は `getuid`・安全な一時ファイル・hard link 検査など POSIX ファイルシステム前提を使うため、対応は Linux / WSL2 / macOS。Windows ネイティブでは core PTY と agent launcher は使えるが、`agent_done` はまだ未対応。
|
|
68
68
|
|
|
69
69
|
**Claude launcher はあえて無い**し、エージェント間のプロトコルも無い: aiterm の役目は、あなたの Claude(や任意の MCP クライアント)が*他の*エージェントに手を伸ばして端末を操作できるようにすること。起動したエージェントは単なるもう 1 本の永続セッションなので、この README の他のすべて(トークン削減読取・完了検出・人の `attach` での監視/介入)がそのまま効く。
|
|
70
70
|
|
|
@@ -219,15 +219,15 @@ aiterm は同じ核心の洞察——端末を出会いの場にする——を
|
|
|
219
219
|
|
|
220
220
|
| ツール | 起動するもの | 主な引数 |
|
|
221
221
|
| --- | --- | --- |
|
|
222
|
-
| `codex_agent` | Codex CLI(OpenAI・モデルは CLI の既定) | `prompt?`, `reasoning_effort?`, `cwd?`, `session_name?`, `agent_done?` |
|
|
222
|
+
| `codex_agent` | Codex CLI(OpenAI・モデルは CLI の既定) | `prompt?`, `reasoning_effort?`, `cwd?`, `session_name?`, `agent_done?`, `wait?`, `timeout?`, `screen?`, `lines?` |
|
|
223
223
|
| `grok_agent` | Grok Build の `grok-build` モデル(xAI) | `prompt?`, `reasoning_effort?`(`low`/`medium`/`high`/`xhigh`/`max`), `cwd?`, `session_name?`, `agent_done?` |
|
|
224
224
|
| `composer_agent` | Grok Build の `grok-composer-2.5-fast` モデル(xAI) | `grok_agent` と同じ |
|
|
225
225
|
|
|
226
|
-
各ベンダーの CLI が導入・認証済みであること(`codex_agent` は `codex`、Grok 系 2 つは `grok`)。バイナリは `CODEX_BIN` / `GROK_BIN` → `~/.local/bin/codex` / `~/.grok/bin/grok` → `PATH` の順で解決する。前提はセッション作成前に検証し(grok/composer は範囲外の `reasoning_effort` を弾く。CLI 不在・実在しない `cwd` は 3 つとも失敗)、起動に失敗してもセッションの残骸は残さない。詳細は [その端末の中に他のコーディングエージェントを起動する](#2-その端末の中に他のコーディングエージェントを起動する--オーケストレーションの旗艦)。`cwd` は絶対パスで渡す——`~` は展開されない。`agent_done` は hook-backed で、Codex/Grok/Composer は Linux/WSL2/macOS で実 smoke 済み。Windows ネイティブでは agent launcher は使えるが `agent_done` はまだ未対応。Codex の managed `CODEX_HOME` は `auth.json` だけを symlink し、`config.toml` をコピーし、`hooks.json` は aiterm が所有する。初回の未 bind agent send では `pty_send({ wait:"agent_done" })` が vendor TUI の入力欄を待ち、未 ready
|
|
226
|
+
各ベンダーの CLI が導入・認証済みであること(`codex_agent` は `codex`、Grok 系 2 つは `grok`)。バイナリは `CODEX_BIN` / `GROK_BIN` → `~/.local/bin/codex` / `~/.grok/bin/grok` → `PATH` の順で解決する。前提はセッション作成前に検証し(grok/composer は範囲外の `reasoning_effort` を弾く。CLI 不在・実在しない `cwd` は 3 つとも失敗)、起動に失敗してもセッションの残骸は残さない。詳細は [その端末の中に他のコーディングエージェントを起動する](#2-その端末の中に他のコーディングエージェントを起動する--オーケストレーションの旗艦)。`cwd` は絶対パスで渡す——`~` は展開されない。`agent_done` は hook-backed で、Codex/Grok/Composer は Linux/WSL2/macOS の後続 `pty_send(wait:"agent_done")` で実 smoke 済み。Codex は launcher の `prompt + wait:"agent_done"` も実 smoke 済み。Windows ネイティブでは agent launcher は使えるが `agent_done` はまだ未対応。Codex の managed `CODEX_HOME` は `auth.json` だけを symlink し、`config.toml` をコピーし、`hooks.json` は aiterm が所有する。初回の未 bind agent send では `pty_send({ wait:"agent_done" })` が vendor TUI の入力欄を待ち、未 ready なら送信前エラーにする。`codex_agent` launcher の `wait:"agent_done"` は `prompt` と `agent_done:true` が必須で、TUI ready 後にだけ初回 prompt を送る。入力欄 ready にならない場合は prompt を送らず、`initial_prompt=not_sent` を返して session を残す。Grok OAuth mode では hook/config 隔離は launch ごとに維持しつつ、通常 Grok home の `auth.json` と `auth.json.lock` をセットで共有する。OAuth auth 不在ならセッション残骸なしで失敗する。
|
|
227
227
|
|
|
228
228
|
### 完了検出(5 層)
|
|
229
229
|
|
|
230
|
-
`pty_read({ wait: true })` は、プロセス終了 / `mark:true` sentinel の自動検出(後述)/ `until` 一致(**既定はリテラル部分一致**、`until_regex: true` で正規表現)/ 出力静止 ∧ シェル復帰(quiescence)/ timeout の 5 層で「コマンドが終わったか」を判定する。ネスト中(SSH・コンテナ・REPL・起動したエージェントの TUI の中)はシェル復帰判定が効かないので、`until` で内側プロンプトを指定するか、`mark: true` で送れば `pty_read({ wait: true })` が sentinel を自動検出する(until 不要・ネストでも効く)——全画面のエージェント TUI なら、出力が落ち着いた時点で `{ screen: true }` を読む。`agent_done:true` で起動したセッションは代わりに `pty_send({ wait:"agent_done" })` を使える。必要な場合はまず agent TUI の入力欄を待ち、その後 vendor Stop hook
|
|
230
|
+
`pty_read({ wait: true })` は、プロセス終了 / `mark:true` sentinel の自動検出(後述)/ `until` 一致(**既定はリテラル部分一致**、`until_regex: true` で正規表現)/ 出力静止 ∧ シェル復帰(quiescence)/ timeout の 5 層で「コマンドが終わったか」を判定する。ネスト中(SSH・コンテナ・REPL・起動したエージェントの TUI の中)はシェル復帰判定が効かないので、`until` で内側プロンプトを指定するか、`mark: true` で送れば `pty_read({ wait: true })` が sentinel を自動検出する(until 不要・ネストでも効く)——全画面のエージェント TUI なら、出力が落ち着いた時点で `{ screen: true }` を読む。`agent_done:true` で起動したセッションは代わりに `pty_send({ wait:"agent_done" })` を使える。必要な場合はまず agent TUI の入力欄を待ち、その後 vendor Stop hook を待ってターン境界後の画面を返す。`codex_agent` launcher の `wait:"agent_done"` は起動時 `prompt` に同じ待機を行う。`pty_send` の送信前 ready 失敗は MCP エラー、launcher の初回 prompt ready 失敗は `initial_prompt=not_sent` を返す。送信後 timeout は `is_complete=False via agent_timeout` で返り、成功扱いしない。agent session の通常 `pty_read` には `agent_event_seen=true completion_attribution=none` のような補助 metadata が付くことがあるが、古い hook event を `is_complete=True` に昇格しない。完結した hook JSONL 行が壊れていた場合は、切り分け用に timeout suffix へ `malformed_events=N` が付く。ターンは完了したが端末 screen/log が flush 窓内で安定しなかった場合は、`agent_done_but_screen_unstable` が付く。
|
|
231
231
|
|
|
232
232
|
### トークン削減
|
|
233
233
|
|
package/README.md
CHANGED
|
@@ -45,11 +45,11 @@ pty_read(id, { wait: true }) → read the token-reduced output, completion
|
|
|
45
45
|
|
|
46
46
|
### 2. Launch other coding agents into that terminal — the orchestration flagship
|
|
47
47
|
|
|
48
|
-
The same primitive hosts another agent's TUI. Three launchers each start one vendor's interactive coding-agent TUI inside a fresh persistent terminal and return a `session_id`. From there you drive it with the same `pty_read` / `pty_send` you'd use on any shell: read its output token-reduced, send it the next step. (The TUIs are full-screen apps, so `pty_read({ screen: true })` gives you the rendered view.) Agent launchers can also opt into hook-backed turn completion with `agent_done: true`, letting `pty_send({ wait: "agent_done" })` return after the agent turn ends. Codex/Grok/Composer all have passing live smokes when their vendor CLI is authenticated. This needs the vendor's own CLI installed and authenticated — see [Requirements](#requirements).
|
|
48
|
+
The same primitive hosts another agent's TUI. Three launchers each start one vendor's interactive coding-agent TUI inside a fresh persistent terminal and return a `session_id`. From there you drive it with the same `pty_read` / `pty_send` you'd use on any shell: read its output token-reduced, send it the next step. (The TUIs are full-screen apps, so `pty_read({ screen: true })` gives you the rendered view.) Agent launchers can also opt into hook-backed turn completion with `agent_done: true`, letting `pty_send({ wait: "agent_done" })` return after the agent turn ends. `codex_agent` can additionally wait for its initial `prompt`: pass `prompt`, `agent_done: true`, and `wait: "agent_done"`; aiterm starts the TUI first, waits until its input area is ready, submits the prompt, then returns after that first turn's Stop hook. Codex/Grok/Composer all have passing live smokes for follow-up `pty_send(wait:"agent_done")` when their vendor CLI is authenticated; Codex initial-prompt wait is also live-smoked. Grok/Composer initial-prompt wait is intentionally not exposed until the post-OAuth smoke passes. This needs the vendor's own CLI installed and authenticated — see [Requirements](#requirements).
|
|
49
49
|
|
|
50
50
|
```text
|
|
51
51
|
codex_agent({ session_name: "codex1", cwd: "/repo", agent_done: true,
|
|
52
|
-
prompt: "port test/legacy.py to vitest" })
|
|
52
|
+
wait: "agent_done", prompt: "port test/legacy.py to vitest" })
|
|
53
53
|
→ { session_id: "codex1", … } # Codex now live in a persistent terminal
|
|
54
54
|
pty_read("codex1", { screen: true }) → read what it's doing (token-reduced)
|
|
55
55
|
pty_send("codex1", "also fix the imports it broke", { wait: "agent_done" })
|
|
@@ -60,11 +60,11 @@ One call per model, so the tool name itself tells you which model you get:
|
|
|
60
60
|
|
|
61
61
|
| Tool | Launches | Key args |
|
|
62
62
|
| --- | --- | --- |
|
|
63
|
-
| `codex_agent` | Codex CLI (OpenAI;
|
|
64
|
-
| `grok_agent` | Grok Build, model `grok-
|
|
65
|
-
| `composer_agent` | Grok Build, model `grok-composer-2.5-fast` (xAI) |
|
|
63
|
+
| `codex_agent` | Codex CLI (OpenAI; terminal config/CLI default unless overridden) | `prompt?`, `model?`, `reasoning_effort?` (`low`/`medium`/`high`/`xhigh`/`max`/`ultra`; ultra enables proactive automatic delegation), `cwd?`, `session_name?`, `agent_done?`, `wait?`, `timeout?`, `screen?`, `lines?` |
|
|
64
|
+
| `grok_agent` | Grok Build, model `grok-4.5` by default (`model?` overrides) (xAI) | `prompt?`, `model?`, `reasoning_effort?` unsupported (an explicit value is an error; Grok CLI `--effort` is headless-only), `cwd?`, `session_name?`, `agent_done?` |
|
|
65
|
+
| `composer_agent` | Grok Build, model `grok-composer-2.5-fast` by default (`model?` overrides) (xAI) | `prompt?`, `model?`, `reasoning_effort?` unsupported (an explicit value is an error), `cwd?`, `session_name?`, `agent_done?` |
|
|
66
66
|
|
|
67
|
-
The vendor CLI must be installed and authenticated (`codex` for `codex_agent`; `grok` for both Grok tools). aiterm resolves the binary via `CODEX_BIN` / `GROK_BIN`, then `~/.local/bin/codex` / `~/.grok/bin/grok`, then `PATH`. Prerequisites are checked **before** a session exists:
|
|
67
|
+
The vendor CLI must be installed and authenticated (`codex` for `codex_agent`; `grok` for both Grok tools). aiterm resolves the binary via `CODEX_BIN` / `GROK_BIN`, then `~/.local/bin/codex` / `~/.grok/bin/grok`, then `PATH`. Prerequisites are checked **before** a session exists: empty `model` values and any `reasoning_effort` for `grok_agent` / `composer_agent` are rejected up front; a missing CLI binary or a nonexistent `cwd` fails for all three. A rejected launch leaves **zero leftover session** behind, and if the launch keystroke itself can't be delivered aiterm tears the session down. (Without an initial prompt wait, aiterm does *not* then verify the vendor CLI actually started or authenticated — read the session to confirm the agent came up.) `codex_agent` forwards `model` as `-m` and `reasoning_effort` as `-c model_reasoning_effort=…` without validating the effort value, since Codex's accepted values vary by version. Grok/Composer reject `reasoning_effort`: Grok CLI `--effort` is only for headless `grok -p`, and the interactive TUI ignores it with a warning. Pass an absolute path for `cwd` — `~` is not expanded. Codex launch responses always report the effective model/effort and where each came from (argument, terminal-config inheritance, or CLI default), with an explicit warning when the effective effort is `ultra` (max reasoning plus proactive automatic delegation). `agent_done` uses launch-local managed vendor homes and does not edit your normal hook files; Codex links only `auth.json` and copies `config.toml` into its managed `CODEX_HOME` — explicitly passed `model` / `reasoning_effort` values also rewrite the matching top-level pins in that copy, so terminal pins don't silently leak into the child — while Grok/Composer additionally isolate `GROK_HOME` and `HOME` to suppress compat hook/plugin contamination. Grok OAuth uses the normal Grok home's `auth.json` and `auth.json.lock` as a pair so refresh locking does not split across sessions. Before the first unbound `pty_send({ wait: "agent_done" })`, aiterm waits for the vendor TUI's input prompt and fails before sending if the prompt is not ready, avoiding dropped startup input. `codex_agent` launcher `wait:"agent_done"` has the same requirement and is only valid with both `prompt` and `agent_done:true`; if the TUI is not ready, the prompt is not sent and the session is left inspectable with `initial_prompt=not_sent`. While an initial prompt is still `pending`/`sent`, ordinary `pty_send` is rejected to prevent mixed input; manual takeover can still use `pty_key`, or `pty_send(..., force:true)` if you intentionally want to type into that live TUI. `agent_done` currently requires POSIX filesystem semantics (`getuid`, secure temp files, hard-link checks), so it is supported on Linux, WSL2, and macOS; native Windows can still use the core PTY tools and agent launchers without `agent_done`.
|
|
68
68
|
|
|
69
69
|
There is deliberately **no Claude launcher**, and no protocol between the agents: aiterm's job is to let your Claude (or any MCP client) reach for *other* agents and drive their terminals. Because a launched agent is just another persistent session, everything else in this README applies to it: token-reduced reads, completion detection, and a human `attach` to watch or take over.
|
|
70
70
|
|
|
@@ -219,15 +219,15 @@ Each launcher starts a specific vendor's interactive coding-agent TUI inside a f
|
|
|
219
219
|
|
|
220
220
|
| Tool | Launches | Key args |
|
|
221
221
|
| --- | --- | --- |
|
|
222
|
-
| `codex_agent` | Codex CLI (OpenAI;
|
|
223
|
-
| `grok_agent` | Grok Build, model `grok-
|
|
224
|
-
| `composer_agent` | Grok Build, model `grok-composer-2.5-fast` (xAI) |
|
|
222
|
+
| `codex_agent` | Codex CLI (OpenAI; terminal config/CLI default unless overridden) | `prompt?`, `model?`, `reasoning_effort?` (`low`/`medium`/`high`/`xhigh`/`max`/`ultra`; ultra enables proactive automatic delegation), `cwd?`, `session_name?`, `agent_done?`, `wait?`, `timeout?`, `screen?`, `lines?` |
|
|
223
|
+
| `grok_agent` | Grok Build, model `grok-4.5` by default (`model?` overrides) (xAI) | `prompt?`, `model?`, `reasoning_effort?` unsupported (an explicit value is an error; Grok CLI `--effort` is headless-only), `cwd?`, `session_name?`, `agent_done?` |
|
|
224
|
+
| `composer_agent` | Grok Build, model `grok-composer-2.5-fast` by default (`model?` overrides) (xAI) | `prompt?`, `model?`, `reasoning_effort?` unsupported (an explicit value is an error), `cwd?`, `session_name?`, `agent_done?` |
|
|
225
225
|
|
|
226
|
-
The vendor CLI must be installed and authenticated (`codex` for `codex_agent`; `grok` for both Grok tools). aiterm resolves the binary via `CODEX_BIN` / `GROK_BIN`, then `~/.local/bin/codex` / `~/.grok/bin/grok`, then `PATH`. Prerequisites are validated before a session is created
|
|
226
|
+
The vendor CLI must be installed and authenticated (`codex` for `codex_agent`; `grok` for both Grok tools). aiterm resolves the binary via `CODEX_BIN` / `GROK_BIN`, then `~/.local/bin/codex` / `~/.grok/bin/grok`, then `PATH`. Prerequisites are validated before a session is created: empty `model` and any Grok/Composer `reasoning_effort` are errors, while a missing CLI or nonexistent `cwd` fails for all three; a failed launch leaves no session behind. Codex forwards `model` as `-m` and `reasoning_effort` as `-c model_reasoning_effort=…`; Grok/Composer reject effort because interactive TUI does not support it (Grok CLI `--effort` is headless-only). Full details under [Launch other coding agents into that terminal](#2-launch-other-coding-agents-into-that-terminal--the-orchestration-flagship). Pass an absolute path for `cwd` — `~` is not expanded. `agent_done` is hook-backed for agent launchers; Codex/Grok/Composer have passing live smokes on Linux/WSL2/macOS for follow-up `pty_send(wait:"agent_done")`, and Codex has a live smoke for launcher `prompt + wait:"agent_done"`. Native Windows can launch agents but `agent_done` is not supported yet. Codex managed `CODEX_HOME` links only `auth.json`, copies `config.toml`, and owns its own `hooks.json`. Before the first unbound agent send, `pty_send({ wait:"agent_done" })` waits for the vendor TUI input prompt and fails before sending if it is not ready. `codex_agent` launcher `wait:"agent_done"` requires `prompt` plus `agent_done:true`; it sends the initial prompt only after the TUI is ready, and returns `initial_prompt=not_sent` without sending if the TUI is blocked before input. In Grok OAuth mode, aiterm keeps hook/config isolation per launch but shares both `auth.json` and `auth.json.lock` with the normal Grok home; missing OAuth auth fails without leaving a session behind.
|
|
227
227
|
|
|
228
228
|
### Completion detection (5 layers)
|
|
229
229
|
|
|
230
|
-
`pty_read({ wait: true })` decides "is the command done?" via five layers: process exit / a `mark:true` sentinel (auto-detected — see below) / an `until` match (a literal substring by default; pass `until_regex: true` for a regex) / output is quiescent ∧ the shell is back (quiescence) / timeout. While nested (inside SSH, a container, a REPL, or a launched agent's TUI), the "shell is back" check cannot fire, so pass `until` with the inner prompt — or send with `mark: true` and `pty_read({ wait: true })` auto-detects the completion sentinel (no `until` needed, works nested too) — or, for a full-screen agent TUI, read `{ screen: true }` once its output settles. Sessions launched with `agent_done:true` can instead use `pty_send({ wait:"agent_done" })`, which first waits for the agent TUI input prompt when needed, then waits for the vendor Stop hook and returns the screen after the turn boundary;
|
|
230
|
+
`pty_read({ wait: true })` decides "is the command done?" via five layers: process exit / a `mark:true` sentinel (auto-detected — see below) / an `until` match (a literal substring by default; pass `until_regex: true` for a regex) / output is quiescent ∧ the shell is back (quiescence) / timeout. While nested (inside SSH, a container, a REPL, or a launched agent's TUI), the "shell is back" check cannot fire, so pass `until` with the inner prompt — or send with `mark: true` and `pty_read({ wait: true })` auto-detects the completion sentinel (no `until` needed, works nested too) — or, for a full-screen agent TUI, read `{ screen: true }` once its output settles. Sessions launched with `agent_done:true` can instead use `pty_send({ wait:"agent_done" })`, which first waits for the agent TUI input prompt when needed, then waits for the vendor Stop hook and returns the screen after the turn boundary; `codex_agent` launcher `wait:"agent_done"` does the same for the initial `prompt`. Pre-send readiness failures are MCP errors for `pty_send`, while launch-time initial prompt readiness failures return the session with `initial_prompt=not_sent`; timeouts after sending are reported as `is_complete=False via agent_timeout`, not as success. Normal `pty_read` on an agent session can append auxiliary metadata such as `agent_event_seen=true completion_attribution=none`, but a stale hook event is not promoted to `is_complete=True`. If a complete hook JSONL line is malformed, the timeout suffix includes `malformed_events=N` for diagnosis. If the turn is done but the terminal screen/log does not settle within the flush window, aiterm appends `agent_done_but_screen_unstable`.
|
|
231
231
|
|
|
232
232
|
### Token reduction
|
|
233
233
|
|
package/dist/core.js
CHANGED
|
@@ -711,6 +711,7 @@ export function send(name, text, o = {}) {
|
|
|
711
711
|
const enter = o.enter ?? true;
|
|
712
712
|
if (!sessionExists(name))
|
|
713
713
|
throw new AitermError(`session '${name}' が無い(open してください)`, 2);
|
|
714
|
+
assertInitialPromptNotPendingForSend(name, !!o.force);
|
|
714
715
|
if (!o.raw) {
|
|
715
716
|
text = text.replace(PASTE_MARKERS_RE, "").replace(ANSI_RE, "").replace(CTRL_RE, "");
|
|
716
717
|
}
|
|
@@ -812,7 +813,7 @@ export async function readOutput(name, o = {}) {
|
|
|
812
813
|
if (o.raw)
|
|
813
814
|
return rawTxt;
|
|
814
815
|
const [body, meta] = reduceOutput(rawTxt, name, true);
|
|
815
|
-
return body + "\n" + meta + (status ? completionSuffix(status) : "");
|
|
816
|
+
return body + "\n" + meta + agentReadMetadataSuffix(name, rawTxt) + (status ? completionSuffix(status) : "");
|
|
816
817
|
}
|
|
817
818
|
// ログ全体を毎回メモリに載せず、必要な範囲だけ fd で読む(B7)。size は statSync で取る。
|
|
818
819
|
let size = 0;
|
|
@@ -892,17 +893,19 @@ export async function readOutput(name, o = {}) {
|
|
|
892
893
|
const [reduced, rname] = rtk.reduce(cmd, framed);
|
|
893
894
|
if (reduced !== null) {
|
|
894
895
|
const meta = `[aiterm ${name}: rtk:${rname} 適用 / ~${estimateTokens(reduced)} tok (raw ~${estimateTokens(text)} tok)]`;
|
|
896
|
+
const agentMeta = agentReadMetadataSuffix(name);
|
|
895
897
|
if (status)
|
|
896
|
-
return reduced + "\n" + meta + completionSuffix(status);
|
|
897
|
-
return reduced + "\n" + meta;
|
|
898
|
+
return reduced + "\n" + meta + agentMeta + completionSuffix(status);
|
|
899
|
+
return reduced + "\n" + meta + agentMeta;
|
|
898
900
|
}
|
|
899
901
|
}
|
|
900
902
|
// reducer 非該当 → 汎用削減へフォールバック
|
|
901
903
|
}
|
|
902
904
|
const [body, meta] = reduceOutput(text, name, !o.range);
|
|
905
|
+
const agentMeta = agentReadMetadataSuffix(name);
|
|
903
906
|
if (status)
|
|
904
|
-
return body + "\n" + meta + completionSuffix(status);
|
|
905
|
-
return body + "\n" + meta;
|
|
907
|
+
return body + "\n" + meta + agentMeta + completionSuffix(status);
|
|
908
|
+
return body + "\n" + meta + agentMeta;
|
|
906
909
|
}
|
|
907
910
|
export function listSessions() {
|
|
908
911
|
const r = tmux("list-sessions", "-F", "#{session_name}\t#{pane_current_command}\t#{?session_attached,attached,detached}\t#{window_width}x#{window_height}");
|
|
@@ -975,6 +978,13 @@ export function killAll() {
|
|
|
975
978
|
}
|
|
976
979
|
const DEFAULT_AGENT_DONE_TIMEOUT = 600;
|
|
977
980
|
const agentWaitLocks = new Set();
|
|
981
|
+
function normalizeAgentLauncherWait(v) {
|
|
982
|
+
if (v == null || v === "none")
|
|
983
|
+
return "none";
|
|
984
|
+
if (v === "agent_done")
|
|
985
|
+
return "agent_done";
|
|
986
|
+
throw new AitermError('wait は "none" または "agent_done" を指定してください', 2);
|
|
987
|
+
}
|
|
978
988
|
function codexHookScriptPath() {
|
|
979
989
|
return path.join(path.dirname(fileURLToPath(import.meta.url)), "codex-stop-hook.js");
|
|
980
990
|
}
|
|
@@ -1040,7 +1050,61 @@ function createEmpty0600NoFollow(p) {
|
|
|
1040
1050
|
function realCodexHome() {
|
|
1041
1051
|
return process.env.CODEX_HOME || path.join(process.env.HOME ?? os.homedir(), ".codex");
|
|
1042
1052
|
}
|
|
1043
|
-
|
|
1053
|
+
// 引数で model/effort を渡された時は managed config のピンを上書きする(端末 config の
|
|
1054
|
+
// model/model_reasoning_effort ピン——例: effort=ultra は proactive 自動委譲 ON——が対話子へ
|
|
1055
|
+
// 黙って波及するのを防ぐ)。TOML の top-level キーは最初のテーブルヘッダより前にしか置けないため、
|
|
1056
|
+
// 既存行の除去は先頭領域に限定し、上書き行はファイル先頭へ置く。
|
|
1057
|
+
function applyCodexConfigOverrides(body, overrides) {
|
|
1058
|
+
const lines = [];
|
|
1059
|
+
if (overrides.model)
|
|
1060
|
+
lines.push(`model = ${JSON.stringify(overrides.model)}`);
|
|
1061
|
+
if (overrides.effort)
|
|
1062
|
+
lines.push(`model_reasoning_effort = ${JSON.stringify(overrides.effort)}`);
|
|
1063
|
+
if (!lines.length)
|
|
1064
|
+
return body;
|
|
1065
|
+
if (body == null)
|
|
1066
|
+
return lines.join("\n") + "\n";
|
|
1067
|
+
const rows = body.split(/\r?\n/);
|
|
1068
|
+
let firstTable = rows.findIndex((l) => /^\s*\[/.test(l));
|
|
1069
|
+
if (firstTable === -1)
|
|
1070
|
+
firstTable = rows.length;
|
|
1071
|
+
const head = rows.slice(0, firstTable).filter((l) => {
|
|
1072
|
+
if (overrides.model && /^\s*model\s*=/.test(l))
|
|
1073
|
+
return false;
|
|
1074
|
+
if (overrides.effort && /^\s*model_reasoning_effort\s*=/.test(l))
|
|
1075
|
+
return false;
|
|
1076
|
+
return true;
|
|
1077
|
+
});
|
|
1078
|
+
let out = [...lines, ...head, ...rows.slice(firstTable)].join("\n");
|
|
1079
|
+
if (!out.endsWith("\n"))
|
|
1080
|
+
out += "\n";
|
|
1081
|
+
return out;
|
|
1082
|
+
}
|
|
1083
|
+
function readCodexConfigPins(configPath) {
|
|
1084
|
+
let body;
|
|
1085
|
+
try {
|
|
1086
|
+
body = fs.readFileSync(configPath, "utf8");
|
|
1087
|
+
}
|
|
1088
|
+
catch {
|
|
1089
|
+
return { model: { present: false, value: null }, effort: { present: false, value: null } };
|
|
1090
|
+
}
|
|
1091
|
+
const rows = body.split(/\r?\n/);
|
|
1092
|
+
let firstTable = rows.findIndex((l) => /^\s*\[/.test(l));
|
|
1093
|
+
if (firstTable === -1)
|
|
1094
|
+
firstTable = rows.length;
|
|
1095
|
+
const pick = (key) => {
|
|
1096
|
+
for (const l of rows.slice(0, firstTable)) {
|
|
1097
|
+
const m = l.match(new RegExp(`^\\s*${key}\\s*=\\s*(.*)$`));
|
|
1098
|
+
if (m) {
|
|
1099
|
+
const v = m[1].trim().match(/^"([^"\\]*)"\s*(?:#.*)?$/);
|
|
1100
|
+
return { present: true, value: v ? v[1] : null };
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
return { present: false, value: null };
|
|
1104
|
+
};
|
|
1105
|
+
return { model: pick("model"), effort: pick("model_reasoning_effort") };
|
|
1106
|
+
}
|
|
1107
|
+
function createManagedCodexHome(name, launchId, overrides = {}) {
|
|
1044
1108
|
const srcHome = realCodexHome();
|
|
1045
1109
|
let srcSt;
|
|
1046
1110
|
try {
|
|
@@ -1068,17 +1132,22 @@ function createManagedCodexHome(name, launchId) {
|
|
|
1068
1132
|
}
|
|
1069
1133
|
fs.symlinkSync(authSrc, path.join(managedHome, "auth.json"));
|
|
1070
1134
|
const configSrc = path.join(srcHome, "config.toml");
|
|
1135
|
+
let configBody = null;
|
|
1071
1136
|
try {
|
|
1072
1137
|
const st = fs.statSync(configSrc);
|
|
1073
|
-
if (st.isFile())
|
|
1074
|
-
fs.
|
|
1075
|
-
fs.chmodSync(path.join(managedHome, "config.toml"), 0o600);
|
|
1076
|
-
}
|
|
1138
|
+
if (st.isFile())
|
|
1139
|
+
configBody = fs.readFileSync(configSrc, "utf8");
|
|
1077
1140
|
}
|
|
1078
1141
|
catch (e) {
|
|
1079
1142
|
if (e.code !== "ENOENT")
|
|
1080
1143
|
throw e;
|
|
1081
1144
|
}
|
|
1145
|
+
const configOut = applyCodexConfigOverrides(configBody, overrides);
|
|
1146
|
+
if (configOut != null) {
|
|
1147
|
+
const configDst = path.join(managedHome, "config.toml");
|
|
1148
|
+
fs.writeFileSync(configDst, configOut, { mode: 0o600 });
|
|
1149
|
+
fs.chmodSync(configDst, 0o600);
|
|
1150
|
+
}
|
|
1082
1151
|
const hookScript = codexHookScriptPath();
|
|
1083
1152
|
if (!fs.existsSync(hookScript)) {
|
|
1084
1153
|
throw new AitermError(`Codex Stop hook wrapper が見つかりません。npm run build を実行してください: ${hookScript}`, 2);
|
|
@@ -1242,6 +1311,25 @@ function createManagedGrokHome(name, launchId) {
|
|
|
1242
1311
|
function writeAgentMetadata(meta) {
|
|
1243
1312
|
writeJson0600(agentMetadataPath(meta.aiterm_session, meta.launch_id), meta);
|
|
1244
1313
|
}
|
|
1314
|
+
function normalizeInitialPromptState(v) {
|
|
1315
|
+
if (v === true)
|
|
1316
|
+
return "pending";
|
|
1317
|
+
if (v === false || v == null)
|
|
1318
|
+
return "none";
|
|
1319
|
+
if (v === "none" ||
|
|
1320
|
+
v === "not_sent" ||
|
|
1321
|
+
v === "sent" ||
|
|
1322
|
+
v === "pending" ||
|
|
1323
|
+
v === "done" ||
|
|
1324
|
+
v === "failed") {
|
|
1325
|
+
return v;
|
|
1326
|
+
}
|
|
1327
|
+
return "none";
|
|
1328
|
+
}
|
|
1329
|
+
function setInitialPromptState(meta, state) {
|
|
1330
|
+
meta.initial_prompt = state;
|
|
1331
|
+
writeAgentMetadata(meta);
|
|
1332
|
+
}
|
|
1245
1333
|
function acquireAgentWaitFileLock(meta) {
|
|
1246
1334
|
const p = agentWaitLockPath(meta.aiterm_session, meta.launch_id);
|
|
1247
1335
|
const nofollow = fs.constants.O_NOFOLLOW ?? 0;
|
|
@@ -1278,11 +1366,11 @@ function acquireAgentWaitFileLock(meta) {
|
|
|
1278
1366
|
}
|
|
1279
1367
|
};
|
|
1280
1368
|
}
|
|
1281
|
-
function createCodexAgentMetadata(name, cwd, initialPrompt) {
|
|
1369
|
+
function createCodexAgentMetadata(name, cwd, initialPrompt, overrides = {}) {
|
|
1282
1370
|
const launchId = randomBytes(16).toString("hex");
|
|
1283
1371
|
const eventFile = agentEventPath(name, launchId);
|
|
1284
1372
|
createEmpty0600NoFollow(eventFile);
|
|
1285
|
-
const codexHome = createManagedCodexHome(name, launchId);
|
|
1373
|
+
const codexHome = createManagedCodexHome(name, launchId, overrides);
|
|
1286
1374
|
const meta = {
|
|
1287
1375
|
kind: "codex",
|
|
1288
1376
|
aiterm_session: name,
|
|
@@ -1364,7 +1452,7 @@ function loadAgentMetadata(name) {
|
|
|
1364
1452
|
created_at: typeof m.created_at === "string" ? m.created_at : "",
|
|
1365
1453
|
cwd: typeof m.cwd === "string" ? m.cwd : null,
|
|
1366
1454
|
vendor_session_id: typeof m.vendor_session_id === "string" ? m.vendor_session_id : null,
|
|
1367
|
-
initial_prompt: m.initial_prompt
|
|
1455
|
+
initial_prompt: normalizeInitialPromptState(m.initial_prompt),
|
|
1368
1456
|
hook_route: "managed_codex_home",
|
|
1369
1457
|
node_platform: process.platform,
|
|
1370
1458
|
codex_home: expectedHome,
|
|
@@ -1383,7 +1471,7 @@ function loadAgentMetadata(name) {
|
|
|
1383
1471
|
created_at: typeof m.created_at === "string" ? m.created_at : "",
|
|
1384
1472
|
cwd: typeof m.cwd === "string" ? m.cwd : null,
|
|
1385
1473
|
vendor_session_id: typeof m.vendor_session_id === "string" ? m.vendor_session_id : null,
|
|
1386
|
-
initial_prompt: m.initial_prompt
|
|
1474
|
+
initial_prompt: normalizeInitialPromptState(m.initial_prompt),
|
|
1387
1475
|
hook_route: "managed_grok_home",
|
|
1388
1476
|
node_platform: process.platform,
|
|
1389
1477
|
grok_home: expectedGrokHome,
|
|
@@ -1461,8 +1549,12 @@ function bindAgentVendorSession(meta, ev) {
|
|
|
1461
1549
|
}
|
|
1462
1550
|
}
|
|
1463
1551
|
function bindCompletedInitialPrompt(meta) {
|
|
1464
|
-
if (
|
|
1552
|
+
if (meta.initial_prompt !== "pending" && meta.initial_prompt !== "sent")
|
|
1465
1553
|
return;
|
|
1554
|
+
if (meta.vendor_session_id) {
|
|
1555
|
+
setInitialPromptState(meta, "done");
|
|
1556
|
+
return;
|
|
1557
|
+
}
|
|
1466
1558
|
const size = safeStatSize(meta.event_file);
|
|
1467
1559
|
if (size === 0) {
|
|
1468
1560
|
throw new AitermError(`agent session '${meta.aiterm_session}' は起動時 prompt の完了待ちです。初回応答完了後に再度 pty_send(wait:"agent_done") してください。`, 2);
|
|
@@ -1480,8 +1572,69 @@ function bindCompletedInitialPrompt(meta) {
|
|
|
1480
1572
|
throw new AitermError(`agent session '${meta.aiterm_session}' は起動時 prompt の完了 event をまだ確認できません。初回応答完了後に再度 pty_send(wait:"agent_done") してください。${malformed}${partial}`, 2);
|
|
1481
1573
|
}
|
|
1482
1574
|
bindAgentVendorSession(meta, scanned.event);
|
|
1483
|
-
meta
|
|
1484
|
-
|
|
1575
|
+
setInitialPromptState(meta, "done");
|
|
1576
|
+
}
|
|
1577
|
+
function tryLoadAgentMetadata(name) {
|
|
1578
|
+
try {
|
|
1579
|
+
return loadAgentMetadata(name);
|
|
1580
|
+
}
|
|
1581
|
+
catch {
|
|
1582
|
+
return null;
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
function latestAgentDoneEvent(meta) {
|
|
1586
|
+
const size = safeStatSize(meta.event_file);
|
|
1587
|
+
if (size === 0 || size > AGENT_EVENT_MAX_BYTES)
|
|
1588
|
+
return null;
|
|
1589
|
+
const text = readFileRange(meta.event_file, 0, size).toString("utf8");
|
|
1590
|
+
let latest = null;
|
|
1591
|
+
for (const line of text.split("\n")) {
|
|
1592
|
+
if (!line.trim() || Buffer.byteLength(line, "utf8") > 64 * 1024)
|
|
1593
|
+
continue;
|
|
1594
|
+
const parsed = parseAgentDoneEvent(line, meta);
|
|
1595
|
+
if (parsed.event)
|
|
1596
|
+
latest = parsed.event;
|
|
1597
|
+
}
|
|
1598
|
+
return latest;
|
|
1599
|
+
}
|
|
1600
|
+
function inferAgentFrontend(name, meta, screen) {
|
|
1601
|
+
const fg = paneCurrentCommand(name);
|
|
1602
|
+
const view = screen ?? captureScreen(name, AGENT_TUI_READY_LINES);
|
|
1603
|
+
if (isAgentTuiReady(meta.kind, view))
|
|
1604
|
+
return "agent_tui";
|
|
1605
|
+
if (SHELLS.has(fg)) {
|
|
1606
|
+
if (/(^|\n)\s*>\s/.test(view))
|
|
1607
|
+
return "shell_continuation";
|
|
1608
|
+
return "shell";
|
|
1609
|
+
}
|
|
1610
|
+
return "unknown";
|
|
1611
|
+
}
|
|
1612
|
+
function agentReadMetadataSuffix(name, screen) {
|
|
1613
|
+
const meta = tryLoadAgentMetadata(name);
|
|
1614
|
+
if (!meta)
|
|
1615
|
+
return "";
|
|
1616
|
+
const ev = latestAgentDoneEvent(meta);
|
|
1617
|
+
const bits = [
|
|
1618
|
+
"agent",
|
|
1619
|
+
`vendor=${meta.kind}`,
|
|
1620
|
+
`initial_prompt=${meta.initial_prompt}`,
|
|
1621
|
+
`agent_event_seen=${ev ? "true" : "false"}`,
|
|
1622
|
+
"completion_attribution=none",
|
|
1623
|
+
ev?.turn_id ? `last_turn_id=${ev.turn_id}` : null,
|
|
1624
|
+
`frontend=${inferAgentFrontend(name, meta, screen)}`,
|
|
1625
|
+
].filter(Boolean);
|
|
1626
|
+
return ` [${bits.join(" ")}]`;
|
|
1627
|
+
}
|
|
1628
|
+
function assertInitialPromptNotPendingForSend(name, force) {
|
|
1629
|
+
if (force)
|
|
1630
|
+
return;
|
|
1631
|
+
const meta = tryLoadAgentMetadata(name);
|
|
1632
|
+
if (!meta)
|
|
1633
|
+
return;
|
|
1634
|
+
if (meta.initial_prompt !== "pending" && meta.initial_prompt !== "sent")
|
|
1635
|
+
return;
|
|
1636
|
+
throw new AitermError(`agent session '${name}' は起動時 prompt の完了待ちです。通常 pty_send は混入防止のため送信しません。` +
|
|
1637
|
+
`完了後に pty_send(wait:"agent_done") するか、手動介入が必要な場合だけ force:true を明示してください。`, 2);
|
|
1485
1638
|
}
|
|
1486
1639
|
async function waitAgentDoneEvent(meta, startOffset, timeout) {
|
|
1487
1640
|
const deadline = performance.now() + timeout * 1000;
|
|
@@ -1560,6 +1713,9 @@ async function waitAgentTuiReadyImpl(kind, sample, sleepFn, opts = {}) {
|
|
|
1560
1713
|
async function waitAgentTuiReady(name, meta, timeoutMs = AGENT_TUI_READY_TIMEOUT_MS) {
|
|
1561
1714
|
return waitAgentTuiReadyImpl(meta.kind, () => captureScreen(name, AGENT_TUI_READY_LINES), sleep, { timeoutMs });
|
|
1562
1715
|
}
|
|
1716
|
+
async function waitAgentTuiReadyByKind(name, kind, timeoutMs = AGENT_TUI_READY_TIMEOUT_MS) {
|
|
1717
|
+
return waitAgentTuiReadyImpl(kind, () => captureScreen(name, AGENT_TUI_READY_LINES), sleep, { timeoutMs });
|
|
1718
|
+
}
|
|
1563
1719
|
async function settleAgentDoneScreenImpl(sample, sleepFn, opts = {}) {
|
|
1564
1720
|
const minDelayMs = opts.minDelayMs ?? AGENT_DONE_SETTLE_MIN_MS;
|
|
1565
1721
|
const pollMs = opts.pollMs ?? AGENT_DONE_SCREEN_SETTLE_POLL_MS;
|
|
@@ -1616,6 +1772,76 @@ export async function __testWaitAgentTuiReady(kind, samples, opts = {}) {
|
|
|
1616
1772
|
}, opts);
|
|
1617
1773
|
return { ...result, sleeps };
|
|
1618
1774
|
}
|
|
1775
|
+
async function sendAgentPromptText(name, text) {
|
|
1776
|
+
send(name, text, {
|
|
1777
|
+
enter: false,
|
|
1778
|
+
force: true,
|
|
1779
|
+
raw: false,
|
|
1780
|
+
mark: false,
|
|
1781
|
+
rtk: false,
|
|
1782
|
+
});
|
|
1783
|
+
await sleep(AGENT_SUBMIT_DELAY_MS);
|
|
1784
|
+
sendKey(name, "Enter");
|
|
1785
|
+
}
|
|
1786
|
+
export async function sendInitialAgentPrompt(name, text, o = {}) {
|
|
1787
|
+
assertSessionName(name);
|
|
1788
|
+
const waitMode = normalizeAgentLauncherWait(o.wait);
|
|
1789
|
+
const meta = loadAgentMetadata(name);
|
|
1790
|
+
if (agentWaitLocks.has(name))
|
|
1791
|
+
throw new AitermError(`agent session '${name}' は別の agent_done 待機中です`, 2);
|
|
1792
|
+
if (meta.initial_prompt === "done") {
|
|
1793
|
+
throw new AitermError(`agent session '${name}' の起動時 prompt は既に完了しています`, 2);
|
|
1794
|
+
}
|
|
1795
|
+
if (meta.initial_prompt === "pending" || meta.initial_prompt === "sent") {
|
|
1796
|
+
throw new AitermError(`agent session '${name}' は起動時 prompt の完了待ちです。初回応答完了後に再度操作してください。`, 2);
|
|
1797
|
+
}
|
|
1798
|
+
const releaseFileLock = acquireAgentWaitFileLock(meta);
|
|
1799
|
+
const timeout = o.timeout ?? DEFAULT_AGENT_DONE_TIMEOUT;
|
|
1800
|
+
const screen = o.screen ?? true;
|
|
1801
|
+
agentWaitLocks.add(name);
|
|
1802
|
+
try {
|
|
1803
|
+
setInitialPromptState(meta, "not_sent");
|
|
1804
|
+
const ready = await waitAgentTuiReady(name, meta, o.ready_timeout ?? AGENT_TUI_READY_TIMEOUT_MS);
|
|
1805
|
+
if (!ready.ready) {
|
|
1806
|
+
return (`initial_prompt=not_sent vendor=${meta.kind} ready=false samples=${ready.samples}\n` +
|
|
1807
|
+
`agent session '${name}' の ${agentLabel(meta.kind)} TUI が入力受付状態になりません。prompt は送信していません。`);
|
|
1808
|
+
}
|
|
1809
|
+
const startOffset = safeStatSize(meta.event_file);
|
|
1810
|
+
try {
|
|
1811
|
+
await sendAgentPromptText(name, text);
|
|
1812
|
+
setInitialPromptState(meta, "pending");
|
|
1813
|
+
}
|
|
1814
|
+
catch (e) {
|
|
1815
|
+
setInitialPromptState(meta, "failed");
|
|
1816
|
+
throw e;
|
|
1817
|
+
}
|
|
1818
|
+
if (waitMode === "none") {
|
|
1819
|
+
return (`initial_prompt=pending vendor=${meta.kind}\n` +
|
|
1820
|
+
`起動時 prompt を送信しました。完了後の follow-up は pty_send(wait:"agent_done") を使ってください。`);
|
|
1821
|
+
}
|
|
1822
|
+
const wait = await waitAgentDoneEvent(meta, startOffset, timeout);
|
|
1823
|
+
if (wait.event) {
|
|
1824
|
+
setInitialPromptState(meta, "done");
|
|
1825
|
+
}
|
|
1826
|
+
else {
|
|
1827
|
+
setInitialPromptState(meta, "pending");
|
|
1828
|
+
}
|
|
1829
|
+
const settled = wait.event
|
|
1830
|
+
? await settleAgentDoneScreen(name, o.lines ?? 0)
|
|
1831
|
+
: { unstable: false, samples: 0 };
|
|
1832
|
+
const out = await readOutput(name, {
|
|
1833
|
+
screen,
|
|
1834
|
+
lines: o.lines ?? null,
|
|
1835
|
+
timeout: 0,
|
|
1836
|
+
});
|
|
1837
|
+
writeOffset(name, safeStatSize(logpath(name)));
|
|
1838
|
+
return out + agentDoneSuffix(wait, meta.kind) + (settled.unstable ? " [agent_done_but_screen_unstable]" : "");
|
|
1839
|
+
}
|
|
1840
|
+
finally {
|
|
1841
|
+
agentWaitLocks.delete(name);
|
|
1842
|
+
releaseFileLock();
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1619
1845
|
export async function sendAndWaitAgentDone(name, text, o = {}) {
|
|
1620
1846
|
assertSessionName(name);
|
|
1621
1847
|
if (o.enter === false)
|
|
@@ -1701,23 +1927,31 @@ function resolveAgentBin(kind) {
|
|
|
1701
1927
|
function shq(s) {
|
|
1702
1928
|
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
1703
1929
|
}
|
|
1704
|
-
|
|
1930
|
+
// grok CLI はモデル未指定だと端末側 default に従うため、ツール契約として既定 slug を固定する。
|
|
1931
|
+
// codex は既定 slug を持たず端末 config/CLI 既定に委ねる(起動応答で実効値を報告する)。
|
|
1932
|
+
const GROK_MODEL_DEFAULTS = {
|
|
1933
|
+
grok: "grok-4.5",
|
|
1934
|
+
composer: "grok-composer-2.5-fast",
|
|
1935
|
+
};
|
|
1936
|
+
function buildAgentCmd(kind, bin, model, effort, prompt, meta = null) {
|
|
1705
1937
|
const parts = [shq(bin)];
|
|
1706
1938
|
if (kind === "codex") {
|
|
1707
1939
|
if (meta?.kind === "codex")
|
|
1708
1940
|
parts.push("--dangerously-bypass-hook-trust");
|
|
1709
|
-
//
|
|
1941
|
+
// model/effort は CLI 引数で明示(config 継承より優先)。agent_done 時は managed home 側
|
|
1942
|
+
// config.toml も同値で上書き済み(applyCodexConfigOverrides)。
|
|
1943
|
+
if (model)
|
|
1944
|
+
parts.push("-m", shq(model));
|
|
1710
1945
|
if (effort)
|
|
1711
1946
|
parts.push("-c", `model_reasoning_effort=${shq(effort)}`);
|
|
1712
1947
|
}
|
|
1713
1948
|
else {
|
|
1714
|
-
// grok / composer は同じ grok CLI
|
|
1949
|
+
// grok / composer は同じ grok CLI をモデル違いで起動。--effort は headless(grok -p)専用で
|
|
1950
|
+
// 対話 TUI では警告の上無視されるため渡さない(openAgent が指定を事前拒否する)。
|
|
1715
1951
|
parts.push("--no-auto-update");
|
|
1716
1952
|
if (meta?.kind === "grok" || meta?.kind === "composer")
|
|
1717
1953
|
parts.push("--no-alt-screen");
|
|
1718
|
-
parts.push("--model",
|
|
1719
|
-
if (effort)
|
|
1720
|
-
parts.push("--effort", shq(effort));
|
|
1954
|
+
parts.push("--model", shq(model ?? GROK_MODEL_DEFAULTS[kind]));
|
|
1721
1955
|
if ((meta?.kind === "grok" || meta?.kind === "composer") && prompt)
|
|
1722
1956
|
parts.push("--verbatim");
|
|
1723
1957
|
}
|
|
@@ -1762,15 +1996,53 @@ function agentLabel(kind) {
|
|
|
1762
1996
|
? "Grok Build(Grok)"
|
|
1763
1997
|
: "Codex";
|
|
1764
1998
|
}
|
|
1765
|
-
//
|
|
1766
|
-
|
|
1999
|
+
// 起動応答にモデル/effort の実効値と出所を明示する。codex は端末 config のピン(model /
|
|
2000
|
+
// model_reasoning_effort)が対話子へ波及する構造のため、引数・端末config継承・CLI既定の
|
|
2001
|
+
// どれで起動したかを起動時点で可視化し、実効 effort=ultra は proactive 自動委譲 ON を警告する。
|
|
2002
|
+
function buildAgentLaunchNote(kind, model, effort, meta) {
|
|
2003
|
+
if (kind !== "codex") {
|
|
2004
|
+
return (`起動設定: model=${model ?? GROK_MODEL_DEFAULTS[kind]}(${model ? "引数" : "ツール既定"})。` +
|
|
2005
|
+
"reasoning effort は対話 TUI 非対応=未指定で起動。");
|
|
2006
|
+
}
|
|
2007
|
+
const configPath = meta?.kind === "codex" && meta.codex_home
|
|
2008
|
+
? path.join(meta.codex_home, "config.toml")
|
|
2009
|
+
: path.join(realCodexHome(), "config.toml");
|
|
2010
|
+
const pins = readCodexConfigPins(configPath);
|
|
2011
|
+
const describePin = (arg, pin) => arg
|
|
2012
|
+
? `${arg}(引数)`
|
|
2013
|
+
: pin.present
|
|
2014
|
+
? pin.value
|
|
2015
|
+
? `${pin.value}(端末config継承)`
|
|
2016
|
+
: "端末config継承(値未解析)"
|
|
2017
|
+
: "CLI既定";
|
|
2018
|
+
const effectiveEffort = effort ?? (pins.effort.present ? pins.effort.value : null);
|
|
2019
|
+
return (`起動設定: model=${describePin(model, pins.model)} effort=${describePin(effort, pins.effort)}。` +
|
|
2020
|
+
(effectiveEffort === "ultra"
|
|
2021
|
+
? "⚠ effort=ultra は max 推論+proactive 自動委譲 ON(子エージェント自動生成・使用量急増に注意)。"
|
|
2022
|
+
: ""));
|
|
2023
|
+
}
|
|
1767
2024
|
export function openAgent(kind, opts = {}) {
|
|
1768
2025
|
const label = agentLabel(kind);
|
|
1769
2026
|
// 前提検証は session を作る前に全部済ませる(失敗の残骸 session を作らない)。
|
|
1770
|
-
// effort → bin → cwd の順: effort 検証は CLI 不在の端末でも同じ結果になる(テスト可能性)。
|
|
2027
|
+
// model/effort → bin → cwd の順: model/effort 検証は CLI 不在の端末でも同じ結果になる(テスト可能性)。
|
|
2028
|
+
let model = null;
|
|
2029
|
+
if (opts.model != null) {
|
|
2030
|
+
model = opts.model.trim();
|
|
2031
|
+
if (!model)
|
|
2032
|
+
throw new AitermError("model が空文字です(省略するか有効なモデル名を指定してください)", 2);
|
|
2033
|
+
}
|
|
1771
2034
|
const effort = opts.reasoning_effort ?? null;
|
|
1772
|
-
|
|
1773
|
-
|
|
2035
|
+
// grok CLI の --effort は headless(grok -p)専用で、対話 TUI では警告の上無視される。
|
|
2036
|
+
// 黙って no-op の引数を受けない=起動前に明示エラーで拒否する(codex は CLI 側の値集合が
|
|
2037
|
+
// 版で変わるため縛らず送信まで通す)。
|
|
2038
|
+
if (effort && kind === "grok") {
|
|
2039
|
+
throw new AitermError(`${label} は reasoning_effort を指定できません。grok CLI の --effort は headless(grok -p)専用で、` +
|
|
2040
|
+
"対話 TUI では警告の上無視されます(grok-4.5 の TUI 既定 effort は high)。" +
|
|
2041
|
+
"effort 制御が必要なら通常 PTY で `grok -p --effort low|medium|high ...` を使ってください", 2);
|
|
2042
|
+
}
|
|
2043
|
+
if (effort && kind === "composer") {
|
|
2044
|
+
throw new AitermError(`${label} は reasoning_effort を指定できません。grok-composer-2.5-fast は reasoning effort 非対応です` +
|
|
2045
|
+
"(モデルカタログ supports_reasoning_effort=false)", 2);
|
|
1774
2046
|
}
|
|
1775
2047
|
const agentDone = !!opts.agent_done;
|
|
1776
2048
|
const bin = resolveAgentBin(kind);
|
|
@@ -1808,13 +2080,15 @@ export function openAgent(kind, opts = {}) {
|
|
|
1808
2080
|
const binForCmd = isWin ? toWslPath(bin) : bin;
|
|
1809
2081
|
const cwdForCmd = cwd && isWin ? toWslPath(cwd) : cwd;
|
|
1810
2082
|
const [sid, hint] = openSession(opts.session_name ?? null, "bash");
|
|
2083
|
+
let launchNote = "";
|
|
1811
2084
|
try {
|
|
1812
2085
|
const meta = agentDone
|
|
1813
2086
|
? kind === "codex"
|
|
1814
|
-
? createCodexAgentMetadata(sid, cwd,
|
|
1815
|
-
: createGrokAgentMetadata(kind, sid, cwd,
|
|
2087
|
+
? createCodexAgentMetadata(sid, cwd, opts.prompt ? "pending" : "none", { model, effort })
|
|
2088
|
+
: createGrokAgentMetadata(kind, sid, cwd, opts.prompt ? "pending" : "none")
|
|
1816
2089
|
: null;
|
|
1817
|
-
|
|
2090
|
+
launchNote = buildAgentLaunchNote(kind, model, effort, meta);
|
|
2091
|
+
const cmd = buildAgentCmd(kind, binForCmd, model, effort, opts.prompt ?? null, meta);
|
|
1818
2092
|
const envPrefix = agentEnvPrefix(meta, sid);
|
|
1819
2093
|
const full = cwdForCmd ? `cd ${shq(cwdForCmd)} && ${envPrefix}${cmd}` : `${envPrefix}${cmd}`;
|
|
1820
2094
|
// force:true で送る。起動骨格は `bin '...'` の固定形で、prompt/cwd/effort は shq でクオート済みの
|
|
@@ -1834,10 +2108,68 @@ export function openAgent(kind, opts = {}) {
|
|
|
1834
2108
|
}
|
|
1835
2109
|
return [
|
|
1836
2110
|
sid,
|
|
1837
|
-
`${label} を session ${sid} で起動した。${agentDone ? "agent_done 待機が有効。" : ""}` +
|
|
2111
|
+
`${label} を session ${sid} で起動した。${launchNote}${agentDone ? "agent_done 待機が有効。" : ""}` +
|
|
1838
2112
|
`${agentDone && kind !== "codex" ? " hook 汚染防止のため Grok 実行中は一時 HOME を使う。" : ""}\n${hint}\n` +
|
|
1839
2113
|
`TUI の描画には数秒かかる。少し置いてから pty_read(${sid}, screen:true) で画面を読み、` +
|
|
1840
2114
|
`pty_send(${sid}, "...") で入力・pty_key(${sid}, "Enter"/"Up"/"C-c" 等) で操作する(対話)。` +
|
|
1841
2115
|
`起動直後に増分 pty_read すると空/半描画になり得るので screen:true を使う。`,
|
|
1842
2116
|
];
|
|
1843
2117
|
}
|
|
2118
|
+
async function sendInitialPromptWithoutAgentDone(name, kind, text, opts = {}) {
|
|
2119
|
+
const ready = await waitAgentTuiReadyByKind(name, kind, opts.ready_timeout ?? AGENT_TUI_READY_TIMEOUT_MS);
|
|
2120
|
+
if (!ready.ready) {
|
|
2121
|
+
return (`initial_prompt=not_sent vendor=${kind} ready=false samples=${ready.samples}\n` +
|
|
2122
|
+
`agent session '${name}' の ${agentLabel(kind)} TUI が入力受付状態になりません。prompt は送信していません。`);
|
|
2123
|
+
}
|
|
2124
|
+
await sendAgentPromptText(name, text);
|
|
2125
|
+
return `initial_prompt=sent vendor=${kind}\n起動時 prompt を送信しました。完了待ちは通常の pty_read で行ってください。`;
|
|
2126
|
+
}
|
|
2127
|
+
export async function openAgentWithInitialPrompt(kind, opts = {}) {
|
|
2128
|
+
const waitMode = normalizeAgentLauncherWait(opts.wait);
|
|
2129
|
+
const prompt = opts.prompt ?? null;
|
|
2130
|
+
const agentDone = !!opts.agent_done;
|
|
2131
|
+
if (waitMode === "agent_done" && !prompt) {
|
|
2132
|
+
throw new AitermError('wait:"agent_done" は prompt 指定時だけ使えます', 2);
|
|
2133
|
+
}
|
|
2134
|
+
if (waitMode === "agent_done" && !agentDone) {
|
|
2135
|
+
throw new AitermError('wait:"agent_done" には agent_done:true が必要です', 2);
|
|
2136
|
+
}
|
|
2137
|
+
if (!prompt) {
|
|
2138
|
+
return openAgent(kind, opts);
|
|
2139
|
+
}
|
|
2140
|
+
if (kind !== "codex") {
|
|
2141
|
+
if (waitMode === "agent_done") {
|
|
2142
|
+
throw new AitermError(`${agentLabel(kind)} の起動時 prompt wait は未対応です。` +
|
|
2143
|
+
`agent_done:true で prompt なし起動後、TUI のログイン/ready を確認してから pty_send(wait:"agent_done") を使ってください。`, 2);
|
|
2144
|
+
}
|
|
2145
|
+
return openAgent(kind, opts);
|
|
2146
|
+
}
|
|
2147
|
+
const [sid, hint] = openAgent(kind, {
|
|
2148
|
+
session_name: opts.session_name ?? null,
|
|
2149
|
+
model: opts.model ?? null,
|
|
2150
|
+
reasoning_effort: opts.reasoning_effort ?? null,
|
|
2151
|
+
cwd: opts.cwd ?? null,
|
|
2152
|
+
prompt: null,
|
|
2153
|
+
agent_done: agentDone,
|
|
2154
|
+
});
|
|
2155
|
+
try {
|
|
2156
|
+
const initial = agentDone
|
|
2157
|
+
? await sendInitialAgentPrompt(sid, prompt, {
|
|
2158
|
+
wait: waitMode,
|
|
2159
|
+
timeout: opts.timeout ?? undefined,
|
|
2160
|
+
ready_timeout: opts.ready_timeout ?? undefined,
|
|
2161
|
+
screen: opts.screen ?? undefined,
|
|
2162
|
+
lines: opts.lines ?? null,
|
|
2163
|
+
})
|
|
2164
|
+
: await sendInitialPromptWithoutAgentDone(sid, kind, prompt, {
|
|
2165
|
+
ready_timeout: opts.ready_timeout ?? undefined,
|
|
2166
|
+
});
|
|
2167
|
+
return [sid, `${hint}\n${initial}`];
|
|
2168
|
+
}
|
|
2169
|
+
catch (e) {
|
|
2170
|
+
const code = e instanceof AitermError ? e.code : 1;
|
|
2171
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
2172
|
+
throw new AitermError(`session_id: ${sid}\n` +
|
|
2173
|
+
`起動後の初回 prompt 処理で失敗しました。session は調査/復旧用に残しています。\n${message}`, code);
|
|
2174
|
+
}
|
|
2175
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -180,34 +180,55 @@ server.registerTool("pty_list", {
|
|
|
180
180
|
});
|
|
181
181
|
// 対話型エージェント起動ツール(モデルごとに1つ=ツール名/説明でどのモデルか一目で分かる)。
|
|
182
182
|
// いずれも永続端末に TUI を起動し session_id を返す。以後 pty_read/pty_send で対話操作する。
|
|
183
|
-
const
|
|
184
|
-
|
|
185
|
-
|
|
183
|
+
const agentModelDesc = (kind) => kind === "codex"
|
|
184
|
+
? "起動モデル(例: gpt-5.6-sol / gpt-5.6-terra / gpt-5.6-luna)。省略時は端末 config/CLI 既定を継承" +
|
|
185
|
+
"(端末側のピンがそのまま効く。実効値は起動応答に明示される)"
|
|
186
|
+
: `起動モデル。省略時は ${kind === "grok" ? "grok-4.5" : "grok-composer-2.5-fast"}`;
|
|
187
|
+
const agentEffortDesc = (grokLike) => grokLike
|
|
188
|
+
? "指定不可(grok CLI の --effort は headless 専用で、対話 TUI では警告の上無視される。" +
|
|
189
|
+
"composer は effort 自体非対応)。指定すると起動前にエラーを返す"
|
|
190
|
+
: "reasoning effort(思考レベル)。low/medium/high/xhigh/max/ultra(CLI 版依存)。" +
|
|
191
|
+
"ultra は max 推論+proactive 自動委譲 ON=使用量急増注意(明示要求時のみ)。省略時は端末 config/CLI 既定。";
|
|
186
192
|
function registerAgentTool(toolName, kind, desc, grokLike) {
|
|
193
|
+
const initialPromptWaitSchema = {};
|
|
194
|
+
if (kind === "codex") {
|
|
195
|
+
initialPromptWaitSchema.wait = z
|
|
196
|
+
.enum(["none", "agent_done"])
|
|
197
|
+
.default("none")
|
|
198
|
+
.describe("none=従来通り起動/初回prompt送信のみ。agent_done=起動時promptのStop hookまで待つ(agent_done:true必須)");
|
|
199
|
+
initialPromptWaitSchema.timeout = z.number().default(600).describe("wait:'agent_done' の最大待ち秒数");
|
|
200
|
+
initialPromptWaitSchema.screen = z.boolean().default(true).describe("wait:'agent_done' の返り値を描画済みスクリーンにする");
|
|
201
|
+
initialPromptWaitSchema.lines = z.number().int().nullish().describe("wait:'agent_done' で返す末尾 N 行");
|
|
202
|
+
}
|
|
187
203
|
server.registerTool(toolName, {
|
|
188
204
|
description: desc,
|
|
189
205
|
inputSchema: {
|
|
190
206
|
prompt: z.string().nullish().describe("起動時に渡す初手プロンプト(任意)。省略で素のTUI起動"),
|
|
191
|
-
|
|
207
|
+
model: z.string().nullish().describe(agentModelDesc(kind)),
|
|
208
|
+
// grok/composer の effort は対話 TUI で無効(headless 専用)=core 側が起動前に明示エラーで拒否。
|
|
192
209
|
// codex は CLI 側の値集合が版で変わるため縛らない(core 側も同方針)。
|
|
193
|
-
reasoning_effort:
|
|
194
|
-
.nullish()
|
|
195
|
-
.describe(agentEffortDesc(grokLike)),
|
|
210
|
+
reasoning_effort: z.string().nullish().describe(agentEffortDesc(grokLike)),
|
|
196
211
|
cwd: z.string().nullish().describe("作業ディレクトリ(対象リポのルート等・任意)"),
|
|
197
212
|
session_name: z.string().nullish().describe("セッション名(省略で自動採番)"),
|
|
198
213
|
agent_done: z
|
|
199
214
|
.boolean()
|
|
200
215
|
.default(false)
|
|
201
216
|
.describe("managed Stop hook を有効化し、pty_send(wait:'agent_done') を使えるようにする"),
|
|
217
|
+
...initialPromptWaitSchema,
|
|
202
218
|
},
|
|
203
|
-
}, async ({ prompt, reasoning_effort, cwd, session_name, agent_done }) => {
|
|
219
|
+
}, async ({ prompt, model, reasoning_effort, cwd, session_name, agent_done, wait, timeout, screen, lines }) => {
|
|
204
220
|
try {
|
|
205
|
-
const [sid, hint] = core.
|
|
221
|
+
const [sid, hint] = await core.openAgentWithInitialPrompt(kind, {
|
|
206
222
|
prompt: prompt ?? undefined,
|
|
223
|
+
model: model ?? undefined,
|
|
207
224
|
reasoning_effort: reasoning_effort ?? undefined,
|
|
208
225
|
cwd: cwd ?? undefined,
|
|
209
226
|
session_name: session_name ?? undefined,
|
|
210
227
|
agent_done: agent_done ?? false,
|
|
228
|
+
wait: wait ?? "none",
|
|
229
|
+
timeout,
|
|
230
|
+
screen,
|
|
231
|
+
lines,
|
|
211
232
|
});
|
|
212
233
|
return ok(`session_id: ${sid}\n${hint}`);
|
|
213
234
|
}
|
|
@@ -216,12 +237,13 @@ function registerAgentTool(toolName, kind, desc, grokLike) {
|
|
|
216
237
|
}
|
|
217
238
|
});
|
|
218
239
|
}
|
|
219
|
-
registerAgentTool("codex_agent", "codex", "【Codex (OpenAI
|
|
220
|
-
"起動後は pty_read で画面を読み pty_send で操作する。reasoning_effort
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
240
|
+
registerAgentTool("codex_agent", "codex", "【Codex (OpenAI)】の対話エージェント TUI を永続端末に起動する。実装・レビュー・調査を対話で回す。" +
|
|
241
|
+
"起動後は pty_read で画面を読み pty_send で操作する。model / reasoning_effort を引数で指定可" +
|
|
242
|
+
"(省略時は端末 config/CLI 既定を継承。実効値は起動応答に明示)。", false);
|
|
243
|
+
registerAgentTool("grok_agent", "grok", "【Grok Build の Grok モデル (既定 grok-4.5)】の対話エージェント TUI を永続端末に起動する。" +
|
|
244
|
+
"起動後は pty_read/pty_send で対話操作。model を引数で指定可。reasoning_effort は対話 TUI 非対応(指定はエラー)。", true);
|
|
245
|
+
registerAgentTool("composer_agent", "composer", "【Grok Build の Composer モデル (既定 grok-composer-2.5-fast)】の対話エージェント TUI を永続端末に起動する。" +
|
|
246
|
+
"起動後は pty_read/pty_send で対話操作。model を引数で指定可。reasoning_effort は非対応(指定はエラー)。", true);
|
|
225
247
|
async function main() {
|
|
226
248
|
const transport = new StdioServerTransport();
|
|
227
249
|
await server.connect(transport);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aiterm-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"mcpName": "io.github.kitepon-rgb/aiterm-mcp",
|
|
5
5
|
"description": "AI-driven persistent terminal as a local stdio MCP server (tmux-backed). Holds one local PTY; SSH and containers are just commands you send into it. Also launches interactive Codex/Grok/Composer agent TUIs in a persistent terminal. Token-reducing reads.",
|
|
6
6
|
"keywords": [
|