aiterm-mcp 0.29.6 → 0.29.8

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 CHANGED
@@ -20,7 +20,7 @@
20
20
  >
21
21
  > **これは何か:** AI が握る 1 本の永続 MCP 端末——その中に他のコーディングエージェントも起動できる。`ssh`・`docker exec`・REPL・別エージェントの TUI は、すべてその 1 本の端末の中へ「送るだけのテキスト」として入れ子になる。仕組みはあえて素朴——MCP クライアントが相手エージェントの端末を 1 ターンずつ操作するだけ。隠れたプロトコルも・aiterm独自の共有メモリ層も・自律的な交渉も無い。起動したagentは、直接CLIと同じproject/harnessの通常memory・設定を読む。
22
22
  >
23
- > **人が tmux に張り付く必要はない。** aiterm は MCP 越しにプログラムから駆動されるので、「AI が別のエージェントを起動して操作する」のに端末の前に誰も座らなくていい——オーケストレーションのループ・CI ステップ・cron から動かせる。
23
+ > **人が端末に張り付く必要はない。** aiterm は MCP 越しにプログラムから駆動されるので、「AI が別のエージェントを起動して操作する」のに端末の前に誰も座らなくていい——オーケストレーションのループ・CI ステップ・cron から動かせる。
24
24
  >
25
25
  > *MCP = Model Context Protocol — Claude Code のようなツールが AI に機能を差し込むためのオープン標準。*
26
26
 
@@ -35,7 +35,7 @@ cloneもビルドも不要。どのクライアントでも公開パッケージ
35
35
  npx -y aiterm-mcp
36
36
  ```
37
37
 
38
- **Node.js ≥ 18** と **tmux** が必要。Codexを操作する場合は、Codex CLIの導入と認証も必要。
38
+ **Node.js ≥ 18** と対応multiplexer backend(POSIXは**tmux**、Windows nativeは**psmux 3.3.8以上**)が必要。Codexを操作する場合は、Codex CLIの導入と認証も必要。
39
39
 
40
40
  ### Claude Code
41
41
 
@@ -94,7 +94,7 @@ host統合は、kitepon.devの製品開発を支える内部基盤
94
94
 
95
95
  **言葉でなく実測で:** 記録済み203テストのベンチマークでは、`pty_read` はコンテキストに載るトークンを生ログの **約 7.1 分の 1** に減らす。しかも pass/fail の判定は畳んでも残る。→ [組み込みシェルツールとの使い分け](#組み込みシェルツールとの使い分け)
96
96
 
97
- 15ツール: 6つのPTYツール、正規のagent起動入口`agent_launch`、移行用の旧4alias、`agent_configure`、`claude_turn`、`claude_approval`、`diagnostics`。バックエンドはtmuxなので、MCPサーバやAIクライアントが再起動してもsessionは生き残る。
97
+ 15ツール: 6つのPTYツール、正規のagent起動入口`agent_launch`、移行用の旧4alias、`agent_configure`、`claude_turn`、`claude_approval`、`diagnostics`。backendはPOSIXのtmux/Windows nativeのpsmuxなので、MCPサーバやAIクライアントが再起動してもsessionは生き残る。
98
98
 
99
99
  **v0.28.0では実行基盤harnessとmodelを分離した。** harnessはagent loop・認証・hook・session・transcriptを所有し、modelはその上で選ぶ。Cursor Agent CLIでGPT/Claude/Grokを選んでも完了契約はCursor方式のまま。Composerは別harnessではなく、`harness:"grok-cli", model:"grok-composer-2.5-fast"`で表す。旧4起動ツールは同じ実装へ流れる互換alias。
100
100
 
@@ -109,7 +109,7 @@ model/effort変更に対応した。明示したGrok/Composer modelとCompos
109
109
  現在の`grok models` catalogへ照合し、不在時は別modelへ黙ってfallbackせず明示失敗する。
110
110
 
111
111
  **v0.24.3ではlauncherへ渡す環境変数を現在のMCP processから明示選択できる。** `env_vars`へ
112
- 変数名だけを指定すると、aitermは起動時の現在値を読み、存在する値だけをそのagentへ渡す。永続tmux
112
+ 変数名だけを指定すると、aitermは起動時の現在値を読み、存在する値だけをそのagentへ渡す。永続multiplexer
113
113
  serverがMCP processより先に起動していても、古いserver環境に席identityやworkflow変数を消されない。
114
114
  あわせてCodex v0.147が長寿命footerへ加える任意`fast`を認識し、idleな`medium fast ·` sessionでも
115
115
  再描画・再試行・再起動なしに`agent_configure`できる。
@@ -163,7 +163,7 @@ Publishing)で公開し、GitHub Release が Official MCP Registry を再登
163
163
 
164
164
  ### 1. SSH・コンテナ・REPL を 1 本の永続端末で操作する — 土台
165
165
 
166
- これが土台で、tmux だけで動く——他の CLI は要らない。`pty_open` がローカル端末を 1 個握り、`ssh host`・`docker exec -it x bash`・REPL は、その中へ `pty_send` で打ち込む「ただのテキスト」——**一度だけ**。以降のコマンドは同じ認証済みセッションを通る。セッション種別をツールで区別しない。
166
+ これが土台で、platform backend(POSIXのtmux/Windows nativeのpsmux)だけで動く——他の CLI は要らない。`pty_open` がローカル端末を 1 個握り、`ssh host`・`docker exec -it x bash`・REPL は、その中へ `pty_send` で打ち込む「ただのテキスト」——**一度だけ**。以降のコマンドは同じ認証済みセッションを通る。セッション種別をツールで区別しない。
167
167
 
168
168
  ```
169
169
  pty_open() → ローカル端末を 1 個握る
@@ -208,7 +208,7 @@ Cursorの`model`は`gpt-5.6-luna`のようなbase model、`reasoning_effort`は`
208
208
  `env_vars`は環境変数の**名前**だけを並べるallowlistであり、name/value mapではない。aitermは
209
209
  launcher起動時に現在のMCP processから各名前を読み、存在する値をshell quoteして、その1回のvendor
210
210
  起動コマンドへ入れる。未設定名は省略し、shell変数名として不正な名前はsession作成前に失敗する。
211
- 全環境の暗黙copy、tmux server再起動、retry、fallbackは行わない。値はMCP tool引数には入らないが、
211
+ 全環境の暗黙copy、backend server再起動、retry、fallbackは行わない。値はMCP tool引数には入らないが、
212
212
  PTYの起動コマンドとして送られ、sessionの`.lastcmd`にも保持されるため、起動先vendorと同じOS userへ
213
213
  到達する。秘密転送路ではなく、席identityやworkflow用の非secret変数だけに使う。
214
214
 
@@ -268,7 +268,7 @@ Throughline自体が不要である。
268
268
  ← 499999500000 [is_complete=True via until]
269
269
  ```
270
270
 
271
- 上の採取で私が触ったのは 2 本の `⋮` 行(長い head/tail を README 用に省略)と長すぎる grep 行 1 本の truncate だけ——`〈…〉` マーカー・トークン数・各 `is_complete` はツールが出した通り。(`until` は末尾スペース無しの `">>>"` を使う——採取されるプロンプトは末尾が削られるので `">>> "` だと外れて `timeout` に落ちる。)ネスト中は `until`(内側プロンプト)か `mark: true` を渡すこと——そこでは quiescence が原理的に効かないため([完了検出](#完了検出5-層) / [既知の制約](#既知の制約バグではなく仕様))。同じ tmux ソケットに人が `attach` すれば、これらをライブで覗ける([人が覗く](#人が覗く))。
271
+ 上の採取で私が触ったのは 2 本の `⋮` 行(長い head/tail を README 用に省略)と長すぎる grep 行 1 本の truncate だけ——`〈…〉` マーカー・トークン数・各 `is_complete` はツールが出した通り。(`until` は末尾スペース無しの `">>>"` を使う——採取されるプロンプトは末尾が削られるので `">>> "` だと外れて `timeout` に落ちる。)ネスト中は `until`(内側プロンプト)か `mark: true` を渡すこと——そこでは quiescence が原理的に効かないため([完了検出](#完了検出5-層) / [既知の制約](#既知の制約バグではなく仕様))。同じmultiplexer backendに人が `attach` すれば、これらをライブで覗ける([人が覗く](#人が覗く))。
272
272
 
273
273
  ## 最初の実行(約60秒)
274
274
 
@@ -281,7 +281,7 @@ Claude Code を再起動して、接続を確認:
281
281
  最初のセッション——4 回の呼び出しで、1 個の永続端末:
282
282
 
283
283
  ```text
284
- pty_open() → { session_id: "t1", attach: "tmux -S … attach -t t1" }
284
+ pty_open() → { session_id: "t1", attach: "<platform attach command>" }
285
285
  pty_send("t1", "echo hello") → PTY にコマンドを送る
286
286
  pty_read("t1", { wait: true }) → "hello" (トークン削減・完了検出つき)
287
287
  pty_close("t1") → 端末を解放
@@ -323,7 +323,7 @@ flowchart LR
323
323
  P -->|"launches a fresh PTY per agent"| A["another coding-agent TUI<br/>Claude · Codex · Grok · Cursor"]
324
324
  ```
325
325
 
326
- primitive は「PTY を 1 個握る」ことだけ。それ以外——SSH・コンテナ・REPL・起動したエージェント TUI——は、永続端末の中で動く「対話的な何か」に過ぎず、同じ `pty_send` / `pty_read` で操作する。各起動ツールは自分専用の新しい PTY を開く。PTY は tmux 上にあるので、MCP サーバや AI クライアントが再起動してもセッションは生き残る。
326
+ primitive は「PTY を 1 個握る」ことだけ。それ以外——SSH・コンテナ・REPL・起動したエージェント TUI——は、永続端末の中で動く「対話的な何か」に過ぎず、同じ `pty_send` / `pty_read` で操作する。各起動ツールは自分専用の新しい PTY を開く。PTY はPOSIXのtmux/Windows nativeのpsmux上にあるので、MCP サーバや AI クライアントが再起動してもセッションは生き残る。
327
327
 
328
328
  ## 組み込みシェルツールとの使い分け
329
329
 
package/README.md CHANGED
@@ -20,7 +20,7 @@
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 harness memory/configuration that a direct CLI launch would use.
22
22
  >
23
- > **No human at a tmux required.** aiterm is driven programmatically over MCP, so an AI can launch and drive another agent with no one sitting in the terminal — from an orchestration loop, a CI step, or a cron job.
23
+ > **No human at a terminal required.** aiterm is driven programmatically over MCP, so an AI can launch and drive another agent with no one sitting in the terminal — from an orchestration loop, a CI step, or a cron job.
24
24
  >
25
25
  > *MCP = Model Context Protocol — the open standard that lets tools like Claude Code plug capabilities into an AI.*
26
26
 
@@ -34,7 +34,7 @@ No clone or build is required. Each client launches the published package with:
34
34
  npx -y aiterm-mcp
35
35
  ```
36
36
 
37
- Requires **Node.js ≥ 18** and **tmux**. Driving Codex also requires the Codex CLI to be installed and authenticated.
37
+ Requires **Node.js ≥ 18** and a supported multiplexer backend: **tmux** on POSIX or **psmux 3.3.8+** on native Windows. Driving Codex also requires the Codex CLI to be installed and authenticated.
38
38
 
39
39
  ### Claude Code
40
40
 
@@ -94,7 +94,7 @@ 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
- Fifteen tools: six **PTY tools** — `pty_open` / `pty_send` / `pty_read` / `pty_key` / `pty_close` / `pty_list` — to open, drive, and read one persistent terminal; one canonical **agent launcher**, `agent_launch`, which selects `claude-code`, `codex-cli`, `grok-cli`, or `cursor-cli` as the execution harness; four deprecated launcher aliases kept for migration; `agent_configure`; `claude_turn`; `claude_approval`; and `diagnostics`. The backend is **tmux**, so sessions survive even if the MCP server or the AI client restarts.
97
+ Fifteen tools: six **PTY tools** — `pty_open` / `pty_send` / `pty_read` / `pty_key` / `pty_close` / `pty_list` — to open, drive, and read one persistent terminal; one canonical **agent launcher**, `agent_launch`, which selects `claude-code`, `codex-cli`, `grok-cli`, or `cursor-cli` as the execution harness; four deprecated launcher aliases kept for migration; `agent_configure`; `claude_turn`; `claude_approval`; and `diagnostics`. The backend is **tmux on POSIX and psmux on native Windows**, so sessions survive even if the MCP server or the AI client restarts.
98
98
 
99
99
  **v0.28.0 separates the execution harness from the model.** The harness owns the agent loop, authentication, hooks, session, and transcript; `model` is what that harness runs. Cursor Agent CLI can therefore select GPT, Claude, or Grok without changing the completion contract from Cursor hooks to another harness's. Grok Composer is a Grok CLI model preset, not another harness: use `harness: "grok-cli", model: "grok-composer-2.5-fast"`. The old four launcher tools are thin compatibility aliases over the same implementation.
100
100
 
@@ -111,8 +111,8 @@ An unavailable model fails visibly instead of letting the harness CLI fall back
111
111
 
112
112
  **v0.24.3 forwards explicitly selected launcher environment variables from the current MCP process.**
113
113
  Pass variable names in `env_vars`; aiterm reads their current values at launch and injects only the
114
- present ones into that agent. This works even when the persistent tmux server predates the MCP
115
- process, so a stale tmux-server environment cannot erase per-seat identity or workflow variables.
114
+ present ones into that agent. This works even when the persistent multiplexer server predates the MCP
115
+ process, so a stale backend-server environment cannot erase per-seat identity or workflow variables.
116
116
  It also recognizes Codex v0.147's optional `fast` token in long-lived model/effort footers, keeping
117
117
  `agent_configure` available on an idle `medium fast ·` session without redraw, retry, or restart.
118
118
 
@@ -183,7 +183,7 @@ I used **Codex with GPT-5.6** as an engineering collaborator: it inspected the i
183
183
 
184
184
  ### 1. Drive SSH, containers, and REPLs in one persistent terminal — the primitive
185
185
 
186
- This is the base, and it works with just tmux — no other CLI. `pty_open` grabs one local terminal; `ssh host`, `docker exec -it x bash`, or a REPL are just text you `pty_send` into it — **once**. Every command after that rides the same already-authenticated session. Session kind is never a tool-level distinction.
186
+ This is the base, and it works with just the platform backend — tmux on POSIX or psmux on native Windows. `pty_open` grabs one local terminal; `ssh host`, `docker exec -it x bash`, or a REPL are just text you `pty_send` into it — **once**. Every command after that rides the same already-authenticated session. Session kind is never a tool-level distinction.
187
187
 
188
188
  ```
189
189
  pty_open() → grab one local terminal
@@ -229,7 +229,7 @@ The canonical harness choices are:
229
229
  `env_vars` is an allowlist of environment-variable **names**, not a name/value map. At launch,
230
230
  aiterm reads each valid name from its current MCP process, shell-quotes present values, and places
231
231
  them on that one harness launch command. Missing names are omitted; invalid shell variable names
232
- fail before session creation. There is no implicit whole-environment copy, tmux-server restart,
232
+ fail before session creation. There is no implicit whole-environment copy, backend-server restart,
233
233
  retry, or fallback. Values do not enter the MCP tool arguments, but they are delivered through the
234
234
  PTY launch command and retained in aiterm's per-session `.lastcmd`; the launched harness and other
235
235
  processes with access to the same OS user may read them. Use this for non-secret seat identity and
@@ -294,7 +294,7 @@ Nesting is just text you send in — here a Python REPL *inside* the same PTY (a
294
294
  ← 499999500000 [is_complete=True via until]
295
295
  ```
296
296
 
297
- The only edits to the captures above are the two `⋮` lines (a long head/tail run abbreviated for the README) and one over-long grep line truncated to fit — the `⟨…⟩` marker, the token counts, and every `is_complete` verdict are exactly what the tool printed. (Use `until: ">>>"` without a trailing space — the captured prompt is trimmed, so `">>> "` would miss and fall through to `timeout`.) While nested, pass `until` (the inner prompt) or `mark: true`, because quiescence cannot fire there by design — see [Completion detection](#completion-detection-5-layers) and [Known constraints](#known-constraints-by-design-not-bugs). A human can `attach` to the same tmux socket and watch any of this live (see [A human can watch](#a-human-can-watch)).
297
+ The only edits to the captures above are the two `⋮` lines (a long head/tail run abbreviated for the README) and one over-long grep line truncated to fit — the `⟨…⟩` marker, the token counts, and every `is_complete` verdict are exactly what the tool printed. (Use `until: ">>>"` without a trailing space — the captured prompt is trimmed, so `">>> "` would miss and fall through to `timeout`.) While nested, pass `until` (the inner prompt) or `mark: true`, because quiescence cannot fire there by design — see [Completion detection](#completion-detection-5-layers) and [Known constraints](#known-constraints-by-design-not-bugs). A human can `attach` to the same multiplexer backend and watch any of this live (see [A human can watch](#a-human-can-watch)).
298
298
 
299
299
  ## First run (≈60 seconds)
300
300
 
@@ -307,7 +307,7 @@ Restart Claude Code, then verify the connection:
307
307
  Your first session — four calls, one persistent terminal:
308
308
 
309
309
  ```text
310
- pty_open() → { session_id: "t1", attach: "tmux -S … attach -t t1" }
310
+ pty_open() → { session_id: "t1", attach: "<platform attach command>" }
311
311
  pty_send("t1", "echo hello") → command sent into the PTY
312
312
  pty_read("t1", { wait: true }) → "hello" (token-reduced, completion detected)
313
313
  pty_close("t1") → terminal released
@@ -349,7 +349,7 @@ flowchart LR
349
349
  P -->|"launches a fresh PTY per agent"| A["another coding-agent harness<br/>Claude Code · Codex CLI · Grok CLI · Cursor CLI"]
350
350
  ```
351
351
 
352
- One PTY is the only primitive. Everything else — SSH, containers, REPLs, and the launched agent TUIs — is just something interactive running inside a persistent terminal, driven with the same `pty_send` / `pty_read`. Each launcher opens its own fresh PTY. Because the PTYs live in tmux, sessions outlive the MCP server and the AI client.
352
+ One PTY is the only primitive. Everything else — SSH, containers, REPLs, and the launched agent TUIs — is just something interactive running inside a persistent terminal, driven with the same `pty_send` / `pty_read`. Each launcher opens its own fresh PTY. Because the PTYs live in tmux on POSIX or psmux on native Windows, sessions outlive the MCP server and the AI client.
353
353
 
354
354
  ## When to reach for it vs. the built-in shell
355
355
 
@@ -472,7 +472,7 @@ When an agent's answer is longer than the on-screen tail (pane height ≈ 24 lin
472
472
  As of v0.16 a parent agent **never blocks** on aiterm — there is no wait parameter anywhere (v0.17 makes the waiter's exit codes mirror its outcome). The whole flow is dispatch + one universal waiter:
473
473
 
474
474
  1. Launch the child with `agent_launch({ harness: ... })`; every launch shares the normal project/user environment and adds only completion correlation plus lineage. Send a turn with plain `pty_send` (or `claude_turn issue` for durable Claude operations). The call returns immediately with an `event_cursor` in its structured receipt.
475
- 2. Run `aiterm-wait --session <id> --cursor <event_cursor> [--operation sha256:<64hex>] [--timeout <sec>]`. It observes the harness-owned completion source, plus Claude's additive launch hook, as a **pure reader** and exits with a one-line `aiterm.agent-wait-result.v1` receipt. **Exit ≠ done**: the receipt's `outcome` is authoritative (`0` = `done`, `3` = `timeout`, `4` = `closed`, `1` = error).
475
+ 2. Pass the receipt's `wait_process.executable` and `wait_process.args` unchanged to a true argv process API. PowerShell 7's `Start-Process` is the exception because it joins `-ArgumentList` arrays; pass `windows_start_process_argument_list` as its one ready-made argument string instead. This invokes the bundled waiter through the exact Node runtime that is already running aiterm, including on native Windows where npm's human-facing bin is a PowerShell script shim and install paths may contain spaces. `wait_command` remains a compatibility display string for humans. The waiter observes the harness-owned completion source, plus Claude's additive launch hook, as a **pure reader** and exits with a one-line `aiterm.agent-wait-result.v1` receipt. **Exit ≠ done**: the receipt's `outcome` is authoritative (`0` = `done`, `3` = `timeout`, `4` = `closed`, `1` = error).
476
476
  3. **The parent never runs the waiter in its own foreground.** Waiting is correct — but the waiter is a separate process, not the parent's turn. A harness that re-invokes its agent when a background task exits (Claude Code) runs the waiter **in the background** and gets woken with zero polling. So that this is not left to interpretation, aiterm reads `clientInfo.name` from the MCP `initialize` handshake and its receipts name the concrete invocation for the detected host — for Claude Code, literally `Bash(command: "aiterm-wait …", run_in_background: true)`. Unknown or undeclared hosts get the generic "start it as a process that does not block the parent's turn" wording; nothing else about the contract changes. Every receipt leads with the same rule: dispatch and let go, then go do something else or end the turn.
477
477
  4. Collect the result exactly as before: `pty_read(agent_transcript: true)`, or `claude_turn recover` for durable Claude operations. The waiter carries the signal, never the payload.
478
478
 
@@ -556,8 +556,8 @@ symlink, filter, or replace harness configuration, authentication, MCP, plugin,
556
556
  trust, memory, or history stores. Cleanup removes only aiterm-owned launch metadata and completion
557
557
  correlation files.
558
558
 
559
- The ordinary environment still comes from the shell/tmux session. When a caller needs a value that
560
- belongs to the current MCP process rather than the older persistent tmux server, every harness
559
+ The ordinary environment still comes from the persistent shell session. When a caller needs a value that
560
+ belongs to the current MCP process rather than the older persistent multiplexer server, every harness
561
561
  accepts `env_vars: ["NAME", ...]`. Only those names are refreshed at launch; this is a narrow
562
562
  per-launch overlay, not a replacement environment or configuration snapshot.
563
563
 
@@ -100,7 +100,7 @@ export function stateRoot() {
100
100
  return path.join(base, `aiterm-mcp-${uid}`);
101
101
  }
102
102
  export function ensureStateRoot() {
103
- // state root は OS が与える per-user runtime dir(XDG_RUNTIME_DIR / os.tmpdir())の下に作る。
103
+ // state root は OS が与えるper-user runtime dir(Windows隔離時TMPDIR/XDG_RUNTIME_DIR/os.tmpdir())の下に作る。
104
104
  // 以前はここで symlink・owner・mode を検査していたが、共有 /tmp に敵対的な同居主体がいる
105
105
  // 前提の防御であり、対応 OS の既定配置では成立しない(オーナー裁定 2026-08-19)。
106
106
  // 作成時の 0o700 は検査ではなく妥当な既定として残す。経路の異常は以降の
@@ -22,7 +22,7 @@ function hasAitermEnv() {
22
22
  process.env.AITERM_AGENT_LAUNCH_ID);
23
23
  }
24
24
  function agentsDir() {
25
- // state root は OS が与える per-user runtime dir(XDG_RUNTIME_DIR / os.tmpdir())の下にある。
25
+ // state root は OS が与えるper-user runtime dir(Windows隔離時TMPDIR/XDG_RUNTIME_DIR/os.tmpdir())の下にある。
26
26
  // 以前はここで symlink・owner・mode を検査していたが、共有 /tmp に敵対的な同居主体がいる
27
27
  // 前提の防御であり、対応 OS の既定配置では成立しない(オーナー裁定 2026-08-19)。
28
28
  // 経路の異常は open/stat の OS エラーとしてそのまま露出させる。
package/dist/core.js CHANGED
@@ -11,6 +11,7 @@ import { spawnSync } from "node:child_process";
11
11
  import * as fs from "node:fs";
12
12
  import * as path from "node:path";
13
13
  import { createHash, randomBytes } from "node:crypto";
14
+ import { fileURLToPath } from "node:url";
14
15
  import * as rtk from "./rtk.js";
15
16
  import { AitermError, telemetryOwnedFailure, ownTelemetryFailure } from "./errors.js";
16
17
  import { isWin, SOCKDIR, tmuxCommand, sendPsmuxPayload, loadPtyBufferChunk, pasteBufferBaseArgs, TMUX_EMPTY_CONFIG, attachCommand, normalizePaneCommand, appendMarkSentinel, settlePaneLog, paneCwdArgument, } from "./tmux-runtime.js";
@@ -2144,6 +2145,48 @@ function assertInitialPromptNotPendingForSend(name, force) {
2144
2145
  }
2145
2146
  // aiterm-wait の exit 契約(CLI と各所の案内文で共有する正)。exit≠完了: outcome が done の時だけ完了。
2146
2147
  export const AITERM_WAIT_OUTCOME_NOTE = `exit 0=done / 3=timeout(既定${DEFAULT_AGENT_DONE_TIMEOUT}秒・未完了) / 4=closed。receiptのoutcomeが正で、done以外は未完了`;
2148
+ function quoteWindowsProcessArgument(value) {
2149
+ if (value !== "" && !/[\s"]/u.test(value))
2150
+ return value;
2151
+ let quoted = '"';
2152
+ let backslashes = 0;
2153
+ for (const char of value) {
2154
+ if (char === "\\") {
2155
+ backslashes += 1;
2156
+ }
2157
+ else if (char === '"') {
2158
+ quoted += "\\".repeat(backslashes * 2 + 1) + '"';
2159
+ backslashes = 0;
2160
+ }
2161
+ else {
2162
+ quoted += "\\".repeat(backslashes) + char;
2163
+ backslashes = 0;
2164
+ }
2165
+ }
2166
+ return quoted + "\\".repeat(backslashes * 2) + '"';
2167
+ }
2168
+ export function windowsStartProcessArgumentList(args) {
2169
+ return args.map(quoteWindowsProcessArgument).join(" ");
2170
+ }
2171
+ // npmのplatform別bin shimをcallerに解釈させず、現在稼働中のNodeと同梱CLIを直接起動する。
2172
+ // Windowsでもbackendはpsmux、対話shellはPowerShell 7のまま。これはwaiter processの入口だけを所有する。
2173
+ export function agentWaitProcess(session, cursor, runtime = {}) {
2174
+ const executable = runtime.executable ?? process.execPath;
2175
+ const args = [
2176
+ runtime.cliPath ?? fileURLToPath(new URL("./aiterm-wait-cli.js", import.meta.url)),
2177
+ "--session",
2178
+ session,
2179
+ "--cursor",
2180
+ String(cursor),
2181
+ ];
2182
+ return {
2183
+ executable,
2184
+ args,
2185
+ windows_start_process_argument_list: (runtime.platform ?? process.platform) === "win32"
2186
+ ? windowsStartProcessArgumentList(args)
2187
+ : null,
2188
+ };
2189
+ }
2147
2190
  // 親ホストの識別(MCP initialize の clientInfo.name)。完了待ちコマンドを「親のターンを塞がない
2148
2191
  // 起動形」で名指しするためだけに使う。分からない時は汎用文へ落ち、機能は一切変えない。
2149
2192
  let parentClientName = null;
@@ -177,7 +177,15 @@ export function claudeLaunchNote(model, effort, meta) {
177
177
  return `起動設定: model=${model ?? "CLI既定"} effort=${effort ?? "CLI既定"}。${writeScopeNote}`;
178
178
  }
179
179
  export function claudeTuiReady(screen) {
180
- return screen.includes("Claude Code") && /(^|\n)\s*❯/.test(screen);
180
+ if (!screen.includes("Claude Code"))
181
+ return false;
182
+ const lastMarker = screen.split(/\r?\n/u).filter((line) => /^\s*❯/u.test(line)).at(-1)?.trim();
183
+ if (!lastMarker)
184
+ return false;
185
+ // Claude Code 2.1.251 のworkspace trust UIも選択カーソルに❯を使う。
186
+ // 最後のmarker行だけを見ることで、古いtrust表示がscrollbackに残っていても
187
+ // その下に描画された現在のcomposerを優先する。
188
+ return !/^❯\s*(?:\d+\.\s*)?(?:No,\s*exit|Yes,\s*I trust this folder)(?:\s|$)/iu.test(lastMarker);
181
189
  }
182
190
  // submit座礁観測のcomposer領域マーカー(ready判定と同じ記号を行頭基準で探す)。
183
191
  export const CLAUDE_COMPOSER_MARKER_RE = /^\s*❯/;
package/dist/index.js CHANGED
@@ -27,9 +27,17 @@ const server = new McpServer({ name: "aiterm", version: pkg.version });
27
27
  * ここでは「待つな」を断定形で先に置き、foreground 実行の禁止までを説明の側に含める。
28
28
  */
29
29
  const NON_BLOCKING_RULE = "dispatch した子は投げっぱなしでよい=親はここで待たない。" +
30
- "完了通知は `aiterm-wait --session <id> --cursor <event_cursor>` を親のターンを塞がない別プロセスとして起動して受け、" +
30
+ "完了通知はreceiptの `wait_process.executable` と `wait_process.args` をそのまま親のターンを塞がない別プロセスAPIへ渡して受け、" +
31
+ "PowerShell 7のStart-Processだけは `windows_start_process_argument_list` を単一文字列として渡す。" +
31
32
  `exit を完了通知として扱う(${core.AITERM_WAIT_OUTCOME_NOTE}。ポーリング不要)。` +
32
- "この待ちコマンドを foreground で実行して親のターンを塞ぐことはしない(receipt が実際の起動形を示す)。";
33
+ "`wait_command` は人間向け互換表示でありprocess境界へ使わない。foreground実行で親のターンを塞がない。";
34
+ const waitProcessOutputSchema = z
35
+ .object({
36
+ executable: z.string(),
37
+ args: z.array(z.string()),
38
+ windows_start_process_argument_list: z.string().nullable(),
39
+ })
40
+ .nullable();
33
41
  function ok(s) {
34
42
  return { content: [{ type: "text", text: s }] };
35
43
  }
@@ -86,7 +94,7 @@ server.registerTool("diagnostics", {
86
94
  }, async () => ok(await factoryDiagnostics()));
87
95
  const DEFAULT_PTY_SHELL = process.platform === "win32" ? "pwsh" : "bash";
88
96
  server.registerTool("pty_open", {
89
- description: "ローカル永続端末(tmux セッション)を1個開き、session_id を返す。tmux サーバ常駐ゆえ本サーバや " +
97
+ description: "ローカル永続端末(POSIXはtmux、Windows nativeはpsmux 3.3.8以上)を1個開き、session_id を返す。backend server常駐ゆえ本サーバや " +
90
98
  "クライアントが再起動してもセッションは生存する。リモート操作は専用ツールにせず、開いた端末の中で " +
91
99
  'pty_send(session_id, "ssh host") と打って入る。',
92
100
  inputSchema: {
@@ -134,6 +142,7 @@ server.registerTool("pty_send", {
134
142
  mode: z.enum(["sent", "agent_dispatch"]),
135
143
  session_id: z.string(),
136
144
  event_cursor: z.number().int().nullable(),
145
+ wait_process: waitProcessOutputSchema,
137
146
  launch_id: z.string().nullable(),
138
147
  vendor: z.enum(["claude", "codex", "grok", "composer", "cursor"]).nullable(),
139
148
  harness: z.enum(["claude-code", "codex-cli", "grok-cli", "cursor-cli"]).nullable(),
@@ -151,6 +160,7 @@ server.registerTool("pty_send", {
151
160
  if (rtk)
152
161
  throw new Error("agent session への dispatch は rtk:true と併用できません");
153
162
  const receipt = await core.dispatchAgentTurn(session_id, text, { raw });
163
+ const waitProcess = core.agentWaitProcess(receipt.session_id, receipt.event_cursor);
154
164
  return {
155
165
  content: [
156
166
  {
@@ -165,6 +175,7 @@ server.registerTool("pty_send", {
165
175
  mode: "agent_dispatch",
166
176
  session_id: receipt.session_id,
167
177
  event_cursor: receipt.event_cursor,
178
+ wait_process: waitProcess,
168
179
  launch_id: receipt.launch_id,
169
180
  vendor: receipt.vendor,
170
181
  harness: receipt.harness,
@@ -180,6 +191,7 @@ server.registerTool("pty_send", {
180
191
  mode: "sent",
181
192
  session_id,
182
193
  event_cursor: null,
194
+ wait_process: null,
183
195
  launch_id: null,
184
196
  vendor: null,
185
197
  harness: null,
@@ -457,13 +469,14 @@ const agentEffortDesc = (kind) => kind === "claude"
457
469
  : kind === "cursor"
458
470
  ? "Cursor catalogのeffort。指定時はmodel必須で、adapterが model-effort の正規IDへ変換してlive catalogに照合する。"
459
471
  : "Grok Build reasoning effort。利用可能値はCLI/modelのlive catalogに従う。省略時はCLI/model既定。";
460
- // 全launcher共通の完了受信ガイド。待ちコマンドは起動応答の wait_command(初回prompt時)または
461
- // pty_send dispatch の event_cursor から組む。文型は NON_BLOCKING_RULE と同じく「待たない」が先。
472
+ // 全launcher共通の完了受信ガイド。machine callerはreceiptのwait_processをそのまま別process APIへ渡す。
473
+ // wait_commandは人間向け互換表示。文型は NON_BLOCKING_RULE と同じく「待たない」が先。
462
474
  const agentCompletionDesc = `起動して投げたら投げっぱなしでよい=親はここで待たない。` +
463
- `完了通知は起動応答の wait_command(初回prompt時)または pty_send dispatch 後の ` +
464
- `aiterm-wait --session <id> --cursor <event_cursor> を親のターンを塞がない別プロセスとして起動して受ける` +
475
+ `完了通知は起動応答またはpty_send dispatch receiptの wait_processを、親のターンを塞がない` +
476
+ `別プロセスAPIへexecutable/argsの境界を保ったまま渡して受ける` +
477
+ `(PowerShell 7のStart-Processはwindows_start_process_argument_listを使う)` +
465
478
  `(${core.AITERM_WAIT_OUTCOME_NOTE}。ポーリング不要・foreground実行はしない)。` +
466
- `結果回収は pty_read(agent_transcript:true)。`;
479
+ `wait_commandは人間向け互換表示。結果回収は pty_read(agent_transcript:true)。`;
467
480
  const agentEnvironmentDesc = `通常CLIと同じHOME・cwd・project/user/local設定・MCP・plugin・skill・permission/trustを共有する。` +
468
481
  `aitermは完了相関stateだけをlaunch単位で所有する。起動されたagentにはsub-agent自己認識、親session、` +
469
482
  `delegation depth/lineage、delegation_allowed=trueを注入し、必要な追加委譲は許可する。`;
@@ -492,6 +505,7 @@ async function launchAgent(kind, args) {
492
505
  session_id: sid,
493
506
  managed_completion: true,
494
507
  event_cursor: eventCursor,
508
+ wait_process: eventCursor === null ? null : core.agentWaitProcess(sid, eventCursor),
495
509
  wait_command: eventCursor === null ? null : `aiterm-wait --session ${sid} --cursor ${eventCursor}`,
496
510
  submit_residue: submitResidue,
497
511
  ...(supportsWriteScope && write_scope !== undefined
@@ -558,9 +572,10 @@ function registerAgentTool(toolName, kind, desc) {
558
572
  harness: z.literal(core.agentHarness(kind)),
559
573
  session_id: z.string().regex(/^[A-Za-z0-9_-]{1,64}$/),
560
574
  managed_completion: z.boolean().describe("後方互換field。trueはaiterm完了相関が有効という意味で、project/user環境の隔離を意味しない"),
561
- // 起動時 prompt でturnが走っている時だけ非null(additive拡張)。wait_command はそのままホストの
562
- // バックグラウンドタスクとして実行できる完了待ちコマンド。
575
+ // 起動時promptでturnが走っている時だけ非null。wait_processがmachine向けprocess境界、
576
+ // wait_commandは人間向け互換表示。
563
577
  event_cursor: z.number().int().nullable(),
578
+ wait_process: waitProcessOutputSchema,
564
579
  wait_command: z.string().nullable(),
565
580
  // 初回prompt dispatch後のsubmit座礁観測(additive)。true=composerに残存を確認(submit未成立の疑い)/
566
581
  // false=残存を観測せず(submit成立の保証ではない)/ null=promptなし・判定不能。
@@ -593,6 +608,7 @@ server.registerTool("agent_launch", {
593
608
  session_id: z.string().regex(/^[A-Za-z0-9_-]{1,64}$/),
594
609
  managed_completion: z.boolean(),
595
610
  event_cursor: z.number().int().nullable(),
611
+ wait_process: waitProcessOutputSchema,
596
612
  wait_command: z.string().nullable(),
597
613
  submit_residue: z.boolean().nullable(),
598
614
  write_scope: z.string().optional(),
@@ -16,6 +16,13 @@ export function currentUid() {
16
16
  return process.getuid();
17
17
  }
18
18
  export function runtimeStateBase() {
19
+ // Windows Node の os.tmpdir() は TMPDIR を参照せず TEMP を返す。一方、psmux namespace は
20
+ // tmux-runtime.ts で TMPDIR を最優先する。test/隔離processがTMPDIRだけを変えた時に
21
+ // PTYは隔離されてもmanaged metadataが本番TEMPへ残ると、隔離側killAllが本番相関だけを
22
+ // 消してしまう。Windowsだけ同じTMPDIR境界へ揃える。通常serverはTMPDIR未設定なので
23
+ // 従来どおりos.tmpdir()(%TEMP%)を使う。
24
+ if (process.platform === "win32" && process.env.TMPDIR)
25
+ return process.env.TMPDIR;
19
26
  const xdg = process.env.XDG_RUNTIME_DIR;
20
27
  if (xdg) {
21
28
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aiterm-mcp",
3
- "version": "0.29.6",
3
+ "version": "0.29.8",
4
4
  "mcpName": "io.github.kitepon/aiterm-mcp",
5
5
  "description": "Persistent terminal MCP with one harness-based launcher for Claude Code, Codex CLI, Grok CLI, and Cursor Agent CLI, plus durable PTYs for SSH, containers, and REPLs.",
6
6
  "keywords": [