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 +9 -9
- package/README.md +13 -13
- package/dist/agent-shared.js +1 -1
- package/dist/claude-stop-hook.js +1 -1
- package/dist/core.js +43 -0
- package/dist/harnesses/claude.js +9 -1
- package/dist/index.js +26 -10
- package/dist/state-root.js +7 -0
- package/package.json +1 -1
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
|
-
>
|
|
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**
|
|
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
|
|
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へ渡す。永続
|
|
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
|
|
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、
|
|
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-層) / [既知の制約](#既知の制約バグではなく仕様))。同じ
|
|
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: "
|
|
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 は
|
|
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
|
|
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
|
|
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
|
|
115
|
-
process, so a stale
|
|
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
|
|
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,
|
|
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
|
|
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: "
|
|
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.
|
|
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
|
|
560
|
-
belongs to the current MCP process rather than the older persistent
|
|
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
|
|
package/dist/agent-shared.js
CHANGED
|
@@ -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 が与える
|
|
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 は検査ではなく妥当な既定として残す。経路の異常は以降の
|
package/dist/claude-stop-hook.js
CHANGED
|
@@ -22,7 +22,7 @@ function hasAitermEnv() {
|
|
|
22
22
|
process.env.AITERM_AGENT_LAUNCH_ID);
|
|
23
23
|
}
|
|
24
24
|
function agentsDir() {
|
|
25
|
-
// state root は OS が与える
|
|
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;
|
package/dist/harnesses/claude.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
"完了通知は `
|
|
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
|
-
"
|
|
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: "
|
|
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
|
|
461
|
-
//
|
|
472
|
+
// 全launcher共通の完了受信ガイド。machine callerはreceiptのwait_processをそのまま別process APIへ渡す。
|
|
473
|
+
// wait_commandは人間向け互換表示。文型は NON_BLOCKING_RULE と同じく「待たない」が先。
|
|
462
474
|
const agentCompletionDesc = `起動して投げたら投げっぱなしでよい=親はここで待たない。` +
|
|
463
|
-
|
|
464
|
-
|
|
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
|
-
|
|
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
|
-
// 起動時
|
|
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(),
|
package/dist/state-root.js
CHANGED
|
@@ -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.
|
|
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": [
|