aiterm-mcp 0.31.1 → 0.32.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/CHANGELOG.md +27 -1
- package/README.ja.md +32 -9
- package/README.md +33 -10
- package/dist/core.js +8 -7
- package/dist/harnesses/grok.js +31 -8
- package/dist/setup-cli.js +15 -0
- package/dist/setup-integrations.js +107 -0
- package/dist/setup-platform.js +77 -0
- package/dist/setup.js +88 -0
- package/dist/tmux-runtime.js +14 -2
- package/dist/windows-powershell.js +2 -3
- package/docs/00_overview.md +4 -1
- package/docs/DESIGN.md +26 -0
- package/docs/RELEASE.md +24 -8
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.32.0] - 2026-09-09
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- `aiterm-setup`を追加した。公式package managerによる依存準備、MCP経由の端末実行、検出したClaude Code・Codex・Grok・Cursorへの登録と確認を製品が所有する。対応外・未検出・失敗は公開JSONで区別する。
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- Windowsでスラッシュ区切りのcwdを指定したGrok/Composerの完了通知と回答を取得できない問題を修正した。Grok CLIと同じ絶対パスへ正規化して両方の記録を読む。
|
|
19
|
+
- Grok/Composerの次turn開始後に、途中の回答を前turnの完了ID付きで返す問題を修正した。
|
|
20
|
+
- psmuxの最低版を実際のpsmux版数で検証し、winget導入直後の既存PATHからも公式配置を解決する。
|
|
21
|
+
- Windowsでrelease中のnpm起動がENOENTになる問題を修正し、起動元npmのJS入口をNodeで呼ぶ。
|
|
22
|
+
|
|
23
|
+
## [0.31.2] - 2026-09-08
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
|
|
27
|
+
- Grok/Composerの無人起動で公式`--trust`を使い、フォルダ信頼の確認画面に初回promptのEnterが消費される問題を修正した。確認画面はshellの`>`がscrollbackにあっても入力受付と判定しない。
|
|
28
|
+
- Grokの完了済みhook結果`[hooks: 成功/失敗]`を実行中表示と誤認し、入力待ちのまま初回送信や追加送信が停止する問題を修正した。判定はGrok専用アダプターが所有する。
|
|
29
|
+
|
|
30
|
+
### Changed
|
|
31
|
+
|
|
32
|
+
- Grok/Composerのsandbox起動拒否について、日英README、DESIGN、AGENTS、文書地図、公開後smokeの説明を同期した。検出はGrok専用アダプターが所有し、共通処理はその呼出しだけを担うこと、promptなしの起動応答は入力受付完了を示さないことを明記した。
|
|
33
|
+
|
|
10
34
|
## [0.31.1] - 2026-09-06
|
|
11
35
|
|
|
12
36
|
### Fixed
|
|
@@ -1559,7 +1583,9 @@ prototype (preserved under `prototype/python/` as the porting source and referen
|
|
|
1559
1583
|
`ubuntu-latest` for Node 18/20/22, publishing to npm on `v*` tags with
|
|
1560
1584
|
provenance.
|
|
1561
1585
|
|
|
1562
|
-
[Unreleased]: https://github.com/kitepon/aiterm-mcp/compare/v0.
|
|
1586
|
+
[Unreleased]: https://github.com/kitepon/aiterm-mcp/compare/v0.32.0...HEAD
|
|
1587
|
+
[0.32.0]: https://github.com/kitepon/aiterm-mcp/compare/v0.31.2...v0.32.0
|
|
1588
|
+
[0.31.2]: https://github.com/kitepon/aiterm-mcp/compare/v0.31.1...v0.31.2
|
|
1563
1589
|
[0.31.1]: https://github.com/kitepon/aiterm-mcp/compare/v0.31.0...v0.31.1
|
|
1564
1590
|
[0.31.0]: https://github.com/kitepon/aiterm-mcp/compare/v0.30.0...v0.31.0
|
|
1565
1591
|
[0.30.0]: https://github.com/kitepon/aiterm-mcp/compare/v0.29.31...v0.30.0
|
package/README.ja.md
CHANGED
|
@@ -28,6 +28,24 @@
|
|
|
28
28
|
|
|
29
29
|
## MCPクライアントへ導入
|
|
30
30
|
|
|
31
|
+
検出したClaude Code・Codex・Grok・Cursorのユーザー設定へ登録する標準入口:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npm install -g aiterm-mcp@latest
|
|
35
|
+
aiterm-setup --json
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
`aiterm-setup`は端末の依存準備、MCP経由の端末実行、登録と読戻しまでを一回で行う。
|
|
39
|
+
WindowsはwingetでPowerShell 7・Git for Windows・psmux、macOSはHomebrewでtmux、
|
|
40
|
+
Ubuntu/Debianはsudoとaptでtmuxを準備する。必要な公式package managerと実行権限は事前に必要。
|
|
41
|
+
他のLinuxでも既存tmuxを利用できるが、自動導入は`unsupported`で停止する。
|
|
42
|
+
既存設定の他サーバーを保持し、JSON設定は変更前の`.aiterm-backup`を残す。
|
|
43
|
+
結果の`status`は`ready`/`unsupported`/`failed`。未検出のAIは`not_detected`とし、全AI未検出は成功にしない。
|
|
44
|
+
登録先はglobal packageのNodeとMCP入口の絶対パスで、npm一時cacheやsource checkoutは登録しない。
|
|
45
|
+
更新後も同じ入口を実行し、MCP clientを再起動する。npm install自体はユーザー設定を変更しない。
|
|
46
|
+
公開JSONは`schema: "aiterm.setup-result.v1"`、全体の`status`、端末の`backend`、
|
|
47
|
+
AI別の`integrations`を持つ。失敗時は`reason_code`を付け、終了コードはreadyなら0、それ以外は2となる。
|
|
48
|
+
|
|
31
49
|
cloneもビルドも不要。どのクライアントでも公開パッケージを次のコマンドで起動する:
|
|
32
50
|
|
|
33
51
|
```bash
|
|
@@ -153,13 +171,13 @@ runtime-error store は canonical dotagents config の `collection.enabled: true
|
|
|
153
171
|
場合だけ収集し、既定OFF、network送信は行いません。tag起点CIのnpm provenance(OIDC Trusted
|
|
154
172
|
Publishing)で公開し、GitHub Release が Official MCP Registry を再登録します。
|
|
155
173
|
|
|
156
|
-
**状態:** 開発継続中 · 現行公開版 **v0.
|
|
174
|
+
**状態:** 開発継続中 · 現行公開版 **v0.32.0** · 動作対象は Linux · WSL2 · macOS · Windows ネイティブ · MIT · [変更履歴](CHANGELOG.md)。
|
|
157
175
|
|
|
158
176
|
### 更新と巻き戻し
|
|
159
177
|
|
|
160
178
|
npm packageが単独配布の正本であり、dotagentsは介在しません。global installは
|
|
161
|
-
`npm install -g aiterm-mcp@latest
|
|
162
|
-
`npm install -g "aiterm-mcp@<known-good-version>"`のように既知の正常version
|
|
179
|
+
`npm install -g aiterm-mcp@latest`で更新し、`aiterm-setup --json`を再実行します。巻き戻す時は
|
|
180
|
+
`npm install -g "aiterm-mcp@<known-good-version>"`のように既知の正常versionを明示します。setupを持つ版では同じ入口を再実行し、MCP clientを再起動します。
|
|
163
181
|
`npx`設定では`aiterm-mcp@latest`へ変えると更新でき、`aiterm-mcp@<version>`へ変えると固定・巻き戻し
|
|
164
182
|
できます。downgrade前に[変更履歴](CHANGELOG.md)でstate/schema互換を確認してください。maintainer向けの
|
|
165
183
|
公開物とreleaseの巻き戻しは、製品所有の[release手順](docs/RELEASE.md)を正とします。
|
|
@@ -193,8 +211,12 @@ pty_read(id, { wait: true }) → 削減済みの出力を読む(完了
|
|
|
193
211
|
|
|
194
212
|
`agent_launch`は任意の`write_scope`も受ける。Codex/Grokのread-onlyは`--sandbox read-only`、Cursorは公式`--mode ask`で実効化する。path説明は同等CLI引数がないためdeclaration-only。
|
|
195
213
|
|
|
214
|
+
Grok/Composerの無人起動は公式`--trust`で指定された作業フォルダを信頼登録し、確認画面を完了してから初回promptを送る。この登録はGrok CLIの信頼ストアへ保存され、フォルダ内のhook・MCP・LSPにも適用される。read-only sandboxの制限は維持する。画面に残る完了済みhookの結果は実行中と判定しない。
|
|
215
|
+
|
|
196
216
|
Grok/Composerがread-only sandboxの適用を拒否した場合、prompt送信時に`GROK_SANDBOX_STARTUP_FAILED`とCLIの原因を返す。例えばhookのパスにシンボリックリンクがあるとGrok CLIは起動を拒否する。設定の管理元で原因を修正し、対象sessionを`pty_close`して起動し直す。Aitermはsandboxを解除したりhookをコピーしたりしない。
|
|
197
217
|
|
|
218
|
+
この判定はGrok専用アダプターが所有し、同じCLIを使うComposerにも適用する。初回prompt付きの`agent_launch`と通常の`pty_send`で、入力受付待ち中に拒否を検出すると未送信のエラーを返す。promptなしの`agent_launch`は起動要求を返すため、その応答だけでは入力受付済みと判断しない。実装の責務分担は[DESIGN](docs/DESIGN.md#failure-and-recovery)を参照。
|
|
219
|
+
|
|
198
220
|
```text
|
|
199
221
|
agent_launch({ harness: "codex-cli", session_name: "codex1", cwd: "/repo",
|
|
200
222
|
prompt: "port test/legacy.py to vitest",
|
|
@@ -289,7 +311,7 @@ Throughline自体が不要である。
|
|
|
289
311
|
|
|
290
312
|
## 最初の実行(約60秒)
|
|
291
313
|
|
|
292
|
-
Claude Code
|
|
314
|
+
`aiterm-setup --json`が`ready`になったら、利用するMCP clientを再起動して接続を確認する。Claude Codeの場合:
|
|
293
315
|
|
|
294
316
|
```bash
|
|
295
317
|
/mcp # aiterm が connected・16 ツール公開、と出る
|
|
@@ -500,9 +522,9 @@ npm test # build してから node:test 回帰スイート(tmux ま
|
|
|
500
522
|
npm link # ローカルで `aiterm-mcp` を PATH に
|
|
501
523
|
```
|
|
502
524
|
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
525
|
+
開発中は変更に直結する試験を先に実行する。GitHub Actionsは共通実装・CI自身・未分類の変更をMac・Linux・Windowsで検証し、
|
|
526
|
+
Windows固有だけの変更はLinuxとWindowsを選ぶ。版番号だけの変更はLinuxの配布情報・pack確認、文書だけなら文書検査を行う。
|
|
527
|
+
試験内容とOSの選択、週次・手動実行の範囲は[公開手順](docs/RELEASE.md)に従う。
|
|
506
528
|
`npm run release -- <version>`がversion同期・commit・tag・GitHub Releaseを一回で行い、tag起点のnpm公開は
|
|
507
529
|
tagged commitが`origin/main`の祖先であることだけを確認して、他のCI結果を待ちません。
|
|
508
530
|
|
|
@@ -510,10 +532,11 @@ tagged commitが`origin/main`の祖先であることだけを確認して、他
|
|
|
510
532
|
|
|
511
533
|
## 試す
|
|
512
534
|
|
|
513
|
-
|
|
535
|
+
公開packageを導入して、検出したAIへ登録する。cloneやビルドは不要:
|
|
514
536
|
|
|
515
537
|
```bash
|
|
516
|
-
|
|
538
|
+
npm install -g aiterm-mcp@latest
|
|
539
|
+
aiterm-setup --json
|
|
517
540
|
```
|
|
518
541
|
|
|
519
542
|
aiterm が、あなたの AI に別のエージェントへ仕事を渡させたなら——あるいはトークンの往復を 1 回でも省けたなら——**[リポジトリに star](https://github.com/kitepon/aiterm-mcp)** を。他の人に見つけてもらう一番安い方法です。
|
package/README.md
CHANGED
|
@@ -28,6 +28,24 @@ Built and maintained by [Quo at kitepon.dev](https://kitepon.dev/en).
|
|
|
28
28
|
|
|
29
29
|
## Install in your MCP client
|
|
30
30
|
|
|
31
|
+
検出したClaude Code・Codex・Grok・Cursorのユーザー設定へ登録する標準入口:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npm install -g aiterm-mcp@latest
|
|
35
|
+
aiterm-setup --json
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
`aiterm-setup`は端末の依存準備、MCP経由の端末実行、登録と読戻しまでを一回で行う。
|
|
39
|
+
WindowsはwingetでPowerShell 7・Git for Windows・psmux、macOSはHomebrewでtmux、
|
|
40
|
+
Ubuntu/Debianはsudoとaptでtmuxを準備する。必要な公式package managerと実行権限は事前に必要。
|
|
41
|
+
他のLinuxでも既存tmuxを利用できるが、自動導入は`unsupported`で停止する。
|
|
42
|
+
既存設定の他サーバーを保持し、JSON設定は変更前の`.aiterm-backup`を残す。
|
|
43
|
+
結果の`status`は`ready`/`unsupported`/`failed`。未検出のAIは`not_detected`とし、全AI未検出は成功にしない。
|
|
44
|
+
登録先はglobal packageのNodeとMCP入口の絶対パスで、npm一時cacheやsource checkoutは登録しない。
|
|
45
|
+
更新後も同じ入口を実行し、MCP clientを再起動する。npm install自体はユーザー設定を変更しない。
|
|
46
|
+
公開JSONは`schema: "aiterm.setup-result.v1"`、全体の`status`、端末の`backend`、
|
|
47
|
+
AI別の`integrations`を持つ。失敗時は`reason_code`を付け、終了コードはreadyなら0、それ以外は2となる。
|
|
48
|
+
|
|
31
49
|
No clone or build is required. Each client launches the published package with:
|
|
32
50
|
|
|
33
51
|
```bash
|
|
@@ -169,13 +187,13 @@ collection is off by default and performs no network I/O. It ships via
|
|
|
169
187
|
tag-triggered CI with npm provenance (OIDC Trusted Publishing); the GitHub
|
|
170
188
|
Release re-registers the Official MCP Registry entry.
|
|
171
189
|
|
|
172
|
-
**Status:** actively maintained · current public release **v0.
|
|
190
|
+
**Status:** actively maintained · current public release **v0.32.0** · runs on Linux · WSL2 · macOS · native Windows (tmux on POSIX, the tmux-CLI-compatible [psmux](https://github.com/psmux/psmux) on native Windows — no WSL required) · MIT · see the [CHANGELOG](CHANGELOG.md).
|
|
173
191
|
|
|
174
192
|
### Update and rollback
|
|
175
193
|
|
|
176
194
|
The npm package is the standalone distribution; dotagents is not involved. For a global install,
|
|
177
|
-
update with `npm install -g aiterm-mcp@latest`. To roll back, install a known-good immutable version,
|
|
178
|
-
for example `npm install -g "aiterm-mcp@<known-good-version>"`, then restart the MCP client. For an `npx` configuration,
|
|
195
|
+
update with `npm install -g aiterm-mcp@latest` and `aiterm-setup --json`. To roll back, install a known-good immutable version,
|
|
196
|
+
for example `npm install -g "aiterm-mcp@<known-good-version>"`, then restart the MCP client. setupを持つ版では再起動前に`aiterm-setup --json`を再実行する。For an `npx` configuration,
|
|
179
197
|
use `aiterm-mcp@latest` to update or replace it with `aiterm-mcp@<version>` to pin or roll back.
|
|
180
198
|
Check the [CHANGELOG](CHANGELOG.md) for state/schema compatibility before downgrading. Maintainer
|
|
181
199
|
release and artifact rollback are specified in the product-owned [release procedure](docs/RELEASE.md).
|
|
@@ -215,8 +233,12 @@ The human-readable launch text is accompanied by an `aiterm.agent-launch-result.
|
|
|
215
233
|
|
|
216
234
|
`agent_launch` accepts an optional `write_scope`: either `"read-only"` or a human-readable description of writable paths. Codex/Grok use `--sandbox read-only`; Cursor uses its official read-only `--mode ask`. A path description remains declaration-only because these CLI launch surfaces provide no equivalent path allowlist flag.
|
|
217
235
|
|
|
236
|
+
Grok/Composerの無人起動は公式`--trust`で指定された作業フォルダを信頼登録し、確認画面を完了してから初回promptを送る。この登録はGrok CLIの信頼ストアへ保存され、フォルダ内のhook・MCP・LSPにも適用される。read-only sandboxの制限は維持する。画面に残る完了済みhookの結果は実行中と判定しない。
|
|
237
|
+
|
|
218
238
|
Grok/Composerがread-only sandboxの適用を拒否した場合、prompt送信時に`GROK_SANDBOX_STARTUP_FAILED`とCLIの原因を返す。hookパスのシンボリックリンクなど、CLIが示した原因を設定の管理元で修正し、対象sessionを`pty_close`して起動し直す。Aitermはsandboxを解除したりhookをコピーしたりしない。
|
|
219
239
|
|
|
240
|
+
この判定はGrok専用アダプターが所有し、同じCLIを使うComposerにも適用する。初回prompt付きの`agent_launch`と通常の`pty_send`で、入力受付待ち中に拒否を検出すると未送信のエラーを返す。promptなしの`agent_launch`は起動要求を返すため、その応答だけでは入力受付済みと判断しない。実装の責務分担は[DESIGN](docs/DESIGN.md#failure-and-recovery)を参照。
|
|
241
|
+
|
|
220
242
|
For a correlated Claude turn stopped at `Do you want to proceed?`, use `claude_approval(action: "inspect", ...)` to capture the active operation and SHA-256 screen digest, review the displayed command, then call `respond` with that exact digest and either `approve_once` or `deny`. The relay rechecks the operation and screen under the send lock, never exposes arbitrary input or permanent approval, keeps the active marker intact, and records a prompt-free owner-only receipt. `pty_send(force: true)` does not bypass this boundary.
|
|
221
243
|
|
|
222
244
|
```text
|
|
@@ -319,7 +341,7 @@ The only edits to the captures above are the two `⋮` lines (a long head/tail r
|
|
|
319
341
|
|
|
320
342
|
## First run (≈60 seconds)
|
|
321
343
|
|
|
322
|
-
|
|
344
|
+
`aiterm-setup --json`が`ready`になったら、利用するMCP clientを再起動して接続を確認する。Claude Codeの場合:
|
|
323
345
|
|
|
324
346
|
```bash
|
|
325
347
|
/mcp # aiterm should show as connected, exposing 16 tools
|
|
@@ -546,10 +568,10 @@ npm test # build, then the node:test regression suite (requires tmux o
|
|
|
546
568
|
npm link # put `aiterm-mcp` on PATH locally
|
|
547
569
|
```
|
|
548
570
|
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
571
|
+
開発中は変更に直結する試験を先に実行する。GitHub Actionsは共通実装・CI自身・未分類の変更をMac・Linux・Windowsで検証し、
|
|
572
|
+
Windows固有だけの変更はLinuxとWindowsを選ぶ。版番号だけの変更はLinuxの配布情報・pack確認、文書だけなら文書検査を行う。
|
|
573
|
+
試験内容とOSの選択、週次・手動実行の範囲は[公開手順](docs/RELEASE.md)に従う。
|
|
574
|
+
`npm run release -- <version>` syncs the version, commits, tags, and publishes the GitHub Release in
|
|
553
575
|
one command; tag-triggered npm publishing checks only that the tagged commit is on `origin/main` and does not
|
|
554
576
|
wait for another CI run. The native
|
|
555
577
|
Windows runner needs psmux ≥ 3.3.8 and Git for Windows on its PATH, and must run as an
|
|
@@ -560,10 +582,11 @@ Logic lives in `src/core.ts` (tmux control, reduction, completion detection, saf
|
|
|
560
582
|
|
|
561
583
|
## Try it
|
|
562
584
|
|
|
563
|
-
|
|
585
|
+
公開packageを導入して、検出したAIへ登録する。cloneやビルドは不要:
|
|
564
586
|
|
|
565
587
|
```bash
|
|
566
|
-
|
|
588
|
+
npm install -g aiterm-mcp@latest
|
|
589
|
+
aiterm-setup --json
|
|
567
590
|
```
|
|
568
591
|
|
|
569
592
|
If aiterm let your AI hand a task to another agent — or saved you a round-trip of tokens — **[star the repo](https://github.com/kitepon/aiterm-mcp)**. It's the cheapest way to help others find it.
|
package/dist/core.js
CHANGED
|
@@ -16,7 +16,7 @@ import * as rtk from "./rtk.js";
|
|
|
16
16
|
import { AitermError, telemetryOwnedFailure, ownTelemetryFailure } from "./errors.js";
|
|
17
17
|
import { isWin, SOCKDIR, tmuxCommand, sendPsmuxPayload, loadPtyBufferChunk, pasteBufferBaseArgs, TMUX_EMPTY_CONFIG, attachCommand, normalizePaneCommand, appendMarkSentinel, settlePaneLog, paneCwdArgument, } from "./tmux-runtime.js";
|
|
18
18
|
import { sleep, currentUid, runtimeStateBase, safeStatSize, readFileRange, writeJson0600, createEmpty0600, shq, LAUNCH_ID_RE, AGENT_DONE_POLL_MS, AGENT_EVENT_MAX_BYTES, assertSessionName, agentsDir, agentEventPath, agentMetadataPath, writeAgentMetadata, AGENT_EVENT_TAIL_BYTES, agentLabel, agentHarness, subagentInstruction, agentLineageFields, } from "./agent-shared.js";
|
|
19
|
-
import { GROK_MODEL_DEFAULTS, realGrokHome, resolveAndValidateGrokAuth, assertGrokModelAvailable, grokEventsTranscript, latestGrokCompletion, observeGrokDone, buildGrokAgentCmd, grokLaunchNote, grokEnvTokens, grokTuiReady, assertGrokSandboxNotRejected, GROK_COMPOSER_MARKER_RE, grokFooterHasConfiguration, grokTranscriptText, createGrokAgentMetadata, } from "./harnesses/grok.js";
|
|
19
|
+
import { GROK_MODEL_DEFAULTS, realGrokHome, resolveAndValidateGrokAuth, assertGrokModelAvailable, grokEventsTranscript, latestGrokCompletion, observeGrokDone, buildGrokAgentCmd, grokLaunchNote, grokEnvTokens, grokTuiReady, grokTuiBusy, grokLaunchBlockingDialog, assertGrokSandboxNotRejected, GROK_COMPOSER_MARKER_RE, grokFooterHasConfiguration, grokTranscriptText, createGrokAgentMetadata, } from "./harnesses/grok.js";
|
|
20
20
|
import { bindCodexTranscriptSession, latestCodexCompletion, observeCodexDone, buildCodexAgentCmd, codexLaunchNote, codexTuiReady, codexLaunchBlockingDialog, CODEX_COMPOSER_MARKER_RE, codexModelChoice, codexEffortChoice, codexMoreReasoningChoice, codexTranscriptText, createCodexAgentMetadata, } from "./harnesses/codex.js";
|
|
21
21
|
import { OPERATION_ID_RE, CLAUDE_RESULT_MAX_BYTES, CLAUDE_EFFORTS, agentManagedClaudeSettingsPath, agentClaudeResultPath, agentClaudeOperationPath, agentClaudeApprovalReceiptPath, agentClaudeDispatchReceiptPath, validateOperationId, readClaudeResultText, assertClaudeAuthenticationReady, buildClaudeAgentCmd, claudeLaunchNote, claudeTuiReady, CLAUDE_COMPOSER_MARKER_RE, createClaudeAgentMetadata, claudeSessionTranscriptPath, claudeApiErrorFromLine, } from "./harnesses/claude.js";
|
|
22
22
|
import { bindCursorTranscriptSession, cursorTurnBoundary, latestCursorCompletion, observeCursorDone, cursorTranscriptText, assertCursorAuthenticationReady, assertCursorModelAvailable, buildCursorAgentCmd, cursorPromptWithLineage, createCursorAgentMetadata, cursorLaunchNote, cursorEffortNavigation, cursorTuiReady, CURSOR_SUBMIT_SEQUENCE, CURSOR_COMPOSER_CONTENT_MARKER_RE, validateCursorModelEffort, } from "./harnesses/cursor.js";
|
|
@@ -2169,6 +2169,8 @@ export async function readAgentTranscriptResult(name, o = {}) {
|
|
|
2169
2169
|
text = codexTranscriptText(meta, turnId, readTranscriptLines, transcriptUnavailable);
|
|
2170
2170
|
}
|
|
2171
2171
|
else {
|
|
2172
|
+
if (!done)
|
|
2173
|
+
transcriptUnavailable();
|
|
2172
2174
|
text = grokTranscriptText(meta, readTranscriptLines, transcriptUnavailable);
|
|
2173
2175
|
}
|
|
2174
2176
|
if (!text.trim())
|
|
@@ -2486,11 +2488,7 @@ function isAgentTuiBusy(kind, screen) {
|
|
|
2486
2488
|
if (kind === "codex" || kind === "claude")
|
|
2487
2489
|
return /esc to interrupt/i.test(screen);
|
|
2488
2490
|
if (kind === "grok" || kind === "composer") {
|
|
2489
|
-
return screen
|
|
2490
|
-
|| screen.includes("Responding…")
|
|
2491
|
-
|| screen.includes("Responding...")
|
|
2492
|
-
|| screen.includes("[stop]")
|
|
2493
|
-
|| /\[hooks:\s*\d+\/\d+\]/u.test(screen);
|
|
2491
|
+
return grokTuiBusy(screen);
|
|
2494
2492
|
}
|
|
2495
2493
|
return false;
|
|
2496
2494
|
}
|
|
@@ -2504,6 +2502,8 @@ function isAgentTuiIdleReady(kind, screen) {
|
|
|
2504
2502
|
// 起動側が明示応答すべき既知UI。ここで自動承認せず、ready timeoutを待たずに
|
|
2505
2503
|
// `initial_prompt=not_sent`を返してsessionを生かしたままcallerへ制御を戻す。
|
|
2506
2504
|
function isAgentTuiActionRequired(kind, screen) {
|
|
2505
|
+
if (kind === "grok" || kind === "composer")
|
|
2506
|
+
return grokLaunchBlockingDialog(screen) !== null;
|
|
2507
2507
|
if (kind === "codex") {
|
|
2508
2508
|
return codexLaunchBlockingDialog(screen) !== null
|
|
2509
2509
|
|| screen.includes("Hooks need review")
|
|
@@ -2823,7 +2823,8 @@ export async function sendInitialAgentPrompt(name, text, o = {}) {
|
|
|
2823
2823
|
if (!ready.ready) {
|
|
2824
2824
|
// ready失敗は成功形で返さず明示エラーにする(実被弾 2026-08-25: Codexのupdate確認ダイアログで
|
|
2825
2825
|
// 未送信のまま成功形receiptが返り、呼び出し側が40分気づけなかった)。sessionは調査/復旧用に残る。
|
|
2826
|
-
const dialog = meta.kind === "codex" ? codexLaunchBlockingDialog(ready.lastScreen)
|
|
2826
|
+
const dialog = meta.kind === "codex" ? codexLaunchBlockingDialog(ready.lastScreen)
|
|
2827
|
+
: meta.kind === "grok" || meta.kind === "composer" ? grokLaunchBlockingDialog(ready.lastScreen) : null;
|
|
2827
2828
|
const causeNote = dialog
|
|
2828
2829
|
? `${dialog}が入力を塞いでいます。pty_read(screen:true)で画面を確認し、pty_keyでダイアログに応答してから、pty_sendでpromptを送ってください。`
|
|
2829
2830
|
: `pty_read(screen:true)で画面を確認し、入力受付になってからpty_sendでpromptを送ってください。`;
|
package/dist/harnesses/grok.js
CHANGED
|
@@ -71,7 +71,8 @@ export function assertGrokModelAvailable(bin, cwd, model) {
|
|
|
71
71
|
export function grokSessionDirectory(meta) {
|
|
72
72
|
if ((meta.kind !== "grok" && meta.kind !== "composer") || !meta.grok_home || !meta.vendor_session_id)
|
|
73
73
|
return null;
|
|
74
|
-
|
|
74
|
+
// Grok CLIは起動cwdをOSの絶対パスへ正規化して保存する。
|
|
75
|
+
const cwd = path.resolve(meta.cwd ?? process.cwd());
|
|
75
76
|
return path.join(meta.grok_home, "sessions", encodeURIComponent(cwd), meta.vendor_session_id);
|
|
76
77
|
}
|
|
77
78
|
export function grokEventsTranscript(meta) {
|
|
@@ -109,7 +110,9 @@ export function latestGrokCompletion(meta, readTranscriptLines) {
|
|
|
109
110
|
if (!line.trim())
|
|
110
111
|
continue;
|
|
111
112
|
try {
|
|
112
|
-
|
|
113
|
+
const record = JSON.parse(line);
|
|
114
|
+
// 次のturn開始後は前の完了eventを現在の回答に結び付けない。
|
|
115
|
+
latest = record?.type === "turn_started" ? null : grokCompletionEvent(meta, record) ?? latest;
|
|
113
116
|
}
|
|
114
117
|
catch {
|
|
115
118
|
// 末尾書込み中のlineは次の観測で完結してから読む。
|
|
@@ -229,8 +232,9 @@ export function buildGrokAgentCmd(kind, bin, model, effort, prompt, meta) {
|
|
|
229
232
|
const parts = [shq(bin)];
|
|
230
233
|
// grok / composer は同じ grok CLI をモデル違いで起動する。
|
|
231
234
|
parts.push("--no-auto-update");
|
|
235
|
+
// 無人起動の対象cwdはCLIの公式folder trust指定で登録し、確認画面にpromptを消費させない。
|
|
232
236
|
if (meta?.kind === "grok" || meta?.kind === "composer")
|
|
233
|
-
parts.push("--no-alt-screen");
|
|
237
|
+
parts.push("--no-alt-screen", "--trust");
|
|
234
238
|
parts.push("--model", shq(model ?? GROK_MODEL_DEFAULTS[kind]));
|
|
235
239
|
if (effort)
|
|
236
240
|
parts.push("--reasoning-effort", shq(effort));
|
|
@@ -273,13 +277,34 @@ export function assertGrokSandboxNotRejected(screen) {
|
|
|
273
277
|
`${warning?.[0] ?? ""}\n${failure[0]}\n` +
|
|
274
278
|
"CLIが示した設定の問題を、その設定の管理元で修正してください。修正後はpty_closeで対象sessionを閉じ、agent_launchで起動し直してください。", 2);
|
|
275
279
|
}
|
|
280
|
+
export function grokLaunchBlockingDialog(screen) {
|
|
281
|
+
const trustAt = screen.lastIndexOf("Do you trust the contents of this directory?");
|
|
282
|
+
if (trustAt < 0)
|
|
283
|
+
return null;
|
|
284
|
+
const afterTrust = screen.slice(trustAt);
|
|
285
|
+
if (!afterTrust.includes("Yes, proceed") || !afterTrust.includes("No, quit"))
|
|
286
|
+
return null;
|
|
287
|
+
// 古い確認画面より後に現在の入力欄がある場合は、scrollbackだけを根拠に停止しない。
|
|
288
|
+
if (/(?:^|\n)[ \t]*(?:│[ \t]*)?[❯>]/u.test(afterTrust))
|
|
289
|
+
return null;
|
|
290
|
+
return "folder trust確認ダイアログ";
|
|
291
|
+
}
|
|
276
292
|
export function grokTuiReady(screen) {
|
|
293
|
+
if (grokLaunchBlockingDialog(screen))
|
|
294
|
+
return false;
|
|
277
295
|
// Grok Build 0.2.117 は起動完了後に製品名を消し、model footerだけを残す。
|
|
278
296
|
// Composerも同じfrontendでmodel名だけが異なるため、両方をharness UIの根拠にする。
|
|
279
297
|
// Windows native grok.exe(1.0.4 実測)は入力欄markerを `❯` でなく `>` で描画するため両方を受ける。
|
|
280
298
|
const grokFrontend = screen.includes("Grok Build") || /\b(?:Grok|Composer)\s+[\w.()-]+/.test(screen);
|
|
281
299
|
return grokFrontend && /(^|\n|\s)[❯>]/.test(screen);
|
|
282
300
|
}
|
|
301
|
+
export function grokTuiBusy(screen) {
|
|
302
|
+
// [hooks: 成功/失敗]は完了後も残る結果表示であり、実行中の根拠にはならない。
|
|
303
|
+
return screen.includes("Waiting for response")
|
|
304
|
+
|| screen.includes("Responding…")
|
|
305
|
+
|| screen.includes("Responding...")
|
|
306
|
+
|| screen.includes("[stop]");
|
|
307
|
+
}
|
|
283
308
|
// submit座礁観測のcomposer領域マーカー(Windows native描画の `>` も ready 判定と同様に受ける)。
|
|
284
309
|
export const GROK_COMPOSER_MARKER_RE = /(^|\s)[❯>]/;
|
|
285
310
|
export function grokFooterHasConfiguration(screen, model, effort) {
|
|
@@ -299,12 +324,10 @@ export function grokFooterHasConfiguration(screen, model, effort) {
|
|
|
299
324
|
}
|
|
300
325
|
// 最後のuser発話以降に確定した最後のassistantメッセージをchat_history.jsonlから抽出する。
|
|
301
326
|
export function grokTranscriptText(meta, readTranscriptLines, transcriptUnavailable) {
|
|
302
|
-
|
|
327
|
+
const directory = grokSessionDirectory(meta);
|
|
328
|
+
if (!directory)
|
|
303
329
|
transcriptUnavailable();
|
|
304
|
-
|
|
305
|
-
// launch との互換のため、その実際の起動 cwd を path 導出に使う(launch 側は変更しない)。
|
|
306
|
-
const cwd = meta.cwd ?? process.cwd();
|
|
307
|
-
const transcript = path.join(meta.grok_home, "sessions", encodeURIComponent(cwd), meta.vendor_session_id, "chat_history.jsonl");
|
|
330
|
+
const transcript = path.join(directory, "chat_history.jsonl");
|
|
308
331
|
const lines = readTranscriptLines(transcript);
|
|
309
332
|
let lastUser = -1;
|
|
310
333
|
const records = [];
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { runSetup } from "./setup.js";
|
|
3
|
+
const args = process.argv.slice(2);
|
|
4
|
+
if (args.length === 1 && ["--help", "-h"].includes(args[0])) {
|
|
5
|
+
process.stdout.write("使い方: aiterm-setup [--json]\n製品の依存準備、検出したAIへの登録、MCPと端末の実動作確認を行います。結果はJSONで返します。\n");
|
|
6
|
+
}
|
|
7
|
+
else if (args.length > 1 || (args.length === 1 && args[0] !== "--json")) {
|
|
8
|
+
process.stderr.write("使い方: aiterm-setup [--json]\n");
|
|
9
|
+
process.exitCode = 2;
|
|
10
|
+
}
|
|
11
|
+
else {
|
|
12
|
+
const result = await runSetup();
|
|
13
|
+
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
14
|
+
process.exitCode = result.status === "ready" ? 0 : 2;
|
|
15
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// AI clientごとの登録形式をここに閉じ込め、setup共通処理へ漏らさない。
|
|
2
|
+
import { copyFileSync, existsSync, lstatSync, mkdirSync, readFileSync, realpathSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { randomUUID } from "node:crypto";
|
|
5
|
+
import { isDeepStrictEqual } from "node:util";
|
|
6
|
+
import { resolveAgentBin } from "./agent-resolver.js";
|
|
7
|
+
import { SetupError, runSetupCommand } from "./setup-platform.js";
|
|
8
|
+
export { powershellInvocation } from "./setup-platform.js";
|
|
9
|
+
const record = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
10
|
+
export function mergeJsonMcp(file, registration) {
|
|
11
|
+
let current = {};
|
|
12
|
+
let target = file;
|
|
13
|
+
if (existsSync(file)) {
|
|
14
|
+
target = realpathSync(file);
|
|
15
|
+
try {
|
|
16
|
+
current = JSON.parse(readFileSync(target, "utf8"));
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
throw new SetupError("config_invalid", "既存設定のJSONを読めません");
|
|
20
|
+
}
|
|
21
|
+
if (!record(current) || (current.mcpServers !== undefined && !record(current.mcpServers))) {
|
|
22
|
+
throw new SetupError("config_invalid", "既存設定とmcpServersはobjectである必要があります");
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
try {
|
|
27
|
+
if (lstatSync(file).isSymbolicLink())
|
|
28
|
+
throw new SetupError("config_invalid", "設定symlinkの参照先がありません");
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
if (error.code !== "ENOENT")
|
|
32
|
+
throw error;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const servers = (current.mcpServers ?? {});
|
|
36
|
+
const previous = record(servers.aiterm) ? servers.aiterm : {};
|
|
37
|
+
const updated = { ...previous, ...registration };
|
|
38
|
+
if (isDeepStrictEqual(servers.aiterm, updated))
|
|
39
|
+
return "unchanged";
|
|
40
|
+
const next = { ...current, mcpServers: { ...servers, aiterm: updated } };
|
|
41
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
42
|
+
const temporary = `${target}.aiterm-${randomUUID()}`;
|
|
43
|
+
try {
|
|
44
|
+
writeFileSync(temporary, `${JSON.stringify(next, null, 2)}\n`, { mode: 0o600, flag: "wx" });
|
|
45
|
+
if (existsSync(target))
|
|
46
|
+
copyFileSync(target, `${target}.aiterm-backup`);
|
|
47
|
+
renameSync(temporary, target);
|
|
48
|
+
}
|
|
49
|
+
finally {
|
|
50
|
+
if (existsSync(temporary))
|
|
51
|
+
unlinkSync(temporary);
|
|
52
|
+
}
|
|
53
|
+
const observed = JSON.parse(readFileSync(target, "utf8"));
|
|
54
|
+
if (!isDeepStrictEqual(observed.mcpServers?.aiterm, updated)) {
|
|
55
|
+
throw new SetupError("config_readback_failed", "aiterm登録の読戻しが一致しません");
|
|
56
|
+
}
|
|
57
|
+
return "configured";
|
|
58
|
+
}
|
|
59
|
+
export function configureIntegrations(home, registration, run = runSetupCommand, resolveClient = resolveAgentBin) {
|
|
60
|
+
const results = {};
|
|
61
|
+
for (const client of ["claude", "codex", "grok", "cursor"]) {
|
|
62
|
+
try {
|
|
63
|
+
const executable = resolveClient(client);
|
|
64
|
+
if (!executable && !(client === "cursor" && existsSync(join(home, ".cursor")))) {
|
|
65
|
+
results[client] = { status: "not_detected" };
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (client === "claude" || client === "cursor") {
|
|
69
|
+
const file = client === "cursor" ? join(home, ".cursor", "mcp.json")
|
|
70
|
+
: process.env.CLAUDE_CONFIG_DIR ? join(process.env.CLAUDE_CONFIG_DIR, ".claude.json") : join(home, ".claude.json");
|
|
71
|
+
mergeJsonMcp(file, client === "claude" ? { type: "stdio", ...registration } : registration);
|
|
72
|
+
}
|
|
73
|
+
else if (client === "codex") {
|
|
74
|
+
const servers = JSON.parse(run(executable, ["mcp", "list", "--json"]));
|
|
75
|
+
if (!Array.isArray(servers))
|
|
76
|
+
throw new SetupError("config_readback_failed", "CodexのMCP一覧形式を確認できません");
|
|
77
|
+
const existing = servers.find((entry) => entry.name === "aiterm");
|
|
78
|
+
const envArgs = Object.entries(existing?.transport?.env ?? {}).flatMap(([key, value]) => ["--env", `${key}=${value}`]);
|
|
79
|
+
run(executable, ["mcp", "add", "aiterm", ...envArgs, "--", registration.command, ...registration.args]);
|
|
80
|
+
const value = JSON.parse(run(executable, ["mcp", "get", "aiterm", "--json"]));
|
|
81
|
+
if (value.transport?.command !== registration.command || !isDeepStrictEqual(value.transport?.args, registration.args)) {
|
|
82
|
+
throw new SetupError("config_readback_failed", "Codexのaiterm登録が一致しません");
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
const servers = JSON.parse(run(executable, ["mcp", "list", "--json"]));
|
|
87
|
+
if (!Array.isArray(servers))
|
|
88
|
+
throw new SetupError("config_readback_failed", "GrokのMCP一覧形式を確認できません");
|
|
89
|
+
const existing = servers.find((entry) => entry.name === "aiterm" && entry.scope === "user");
|
|
90
|
+
const envArgs = Object.entries(existing?.env ?? {}).flatMap(([key, value]) => ["--env", `${key}=${value}`]);
|
|
91
|
+
run(executable, ["mcp", "add", "--scope", "user", "aiterm", ...envArgs, "--", registration.command, ...registration.args]);
|
|
92
|
+
const value = JSON.parse(run(executable, ["mcp", "list", "--json"]));
|
|
93
|
+
// 公開CLIのJSON応答を照合する。未知schemaを成功へ丸めない。
|
|
94
|
+
const item = Array.isArray(value) ? value.find((entry) => entry.name === "aiterm") : null;
|
|
95
|
+
if (!item || item.command !== registration.command || !isDeepStrictEqual(item.args, registration.args)) {
|
|
96
|
+
throw new SetupError("config_readback_failed", "Grokのaiterm登録が一致しません");
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
results[client] = { status: "ready" };
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
process.stderr.write(`aiterm-setup: ${client}: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
103
|
+
results[client] = { status: "failed", reason_code: error instanceof SetupError ? error.code : "integration_failed" };
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return results;
|
|
107
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// setupのOS差と公式package managerの呼出しを所有する。
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
3
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
4
|
+
import { resolveWindowsPowerShell7 } from "./windows-powershell.js";
|
|
5
|
+
import { resolveWinPaneShell } from "./agent-resolver.js";
|
|
6
|
+
import { ensureWinPsmux, resolveTmux } from "./tmux-runtime.js";
|
|
7
|
+
export class SetupError extends Error {
|
|
8
|
+
code;
|
|
9
|
+
constructor(code, message) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.code = code;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export function powershellInvocation(command, args) {
|
|
15
|
+
const literal = (value) => `'${value.replaceAll("'", "''")}'`;
|
|
16
|
+
const script = `& ${[command, ...args].map(literal).join(" ")}; if ($null -eq $LASTEXITCODE) { exit 1 }; exit $LASTEXITCODE`;
|
|
17
|
+
return ["-NoLogo", "-NoProfile", "-NonInteractive", "-EncodedCommand", Buffer.from(script, "utf16le").toString("base64")];
|
|
18
|
+
}
|
|
19
|
+
export const runSetupCommand = (command, args) => {
|
|
20
|
+
const batch = process.platform === "win32" && /\.(?:cmd|bat)$/iu.test(command);
|
|
21
|
+
const result = spawnSync(batch ? resolveWindowsPowerShell7() : command, batch ? powershellInvocation(command, args) : args, { encoding: "utf8", windowsHide: true, timeout: 300_000, maxBuffer: 4 * 1024 * 1024 });
|
|
22
|
+
if (result.error || result.status !== 0) {
|
|
23
|
+
throw new SetupError("command_failed", `${command}: ${result.error?.message ?? result.stderr?.trim() ?? `exit ${result.status}`}`);
|
|
24
|
+
}
|
|
25
|
+
return result.stdout;
|
|
26
|
+
};
|
|
27
|
+
export function dependencyInstallCommand(platform, dependency, linuxId = "") {
|
|
28
|
+
if (platform === "win32") {
|
|
29
|
+
const ids = { psmux: "marlocarlo.psmux", pwsh: "Microsoft.PowerShell", git: "Git.Git" };
|
|
30
|
+
if (ids[dependency])
|
|
31
|
+
return ["winget.exe", ["install", "--id", ids[dependency], "--exact", "--source", "winget", "--accept-source-agreements", "--accept-package-agreements", "--disable-interactivity"]];
|
|
32
|
+
}
|
|
33
|
+
if (dependency === "tmux" && platform === "darwin") {
|
|
34
|
+
const brew = ["/opt/homebrew/bin/brew", "/usr/local/bin/brew"].find(existsSync) ?? "brew";
|
|
35
|
+
return [brew, ["install", "tmux"]];
|
|
36
|
+
}
|
|
37
|
+
if (dependency === "tmux" && platform === "linux" && ["ubuntu", "debian"].includes(linuxId)) {
|
|
38
|
+
return ["sudo", ["-n", "apt-get", "install", "--no-remove", "-y", "tmux"]];
|
|
39
|
+
}
|
|
40
|
+
throw new SetupError("platform_unsupported", `${platform}/${linuxId}の${dependency}自動導入には対応していません`);
|
|
41
|
+
}
|
|
42
|
+
export function prepareBackend(run = runSetupCommand) {
|
|
43
|
+
const platform = process.platform;
|
|
44
|
+
const linuxId = platform === "linux" ? /^ID=["']?([^"'\r\n]+)/mu.exec(readFileSync("/etc/os-release", "utf8"))?.[1] ?? "" : "";
|
|
45
|
+
const ensure = (name, probe) => {
|
|
46
|
+
try {
|
|
47
|
+
probe();
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
// 失敗を隠さず、製品の正規導入で修復してから同じprobeを再実行する。
|
|
52
|
+
process.stderr.write(`aiterm-setup: ${name}を準備します(${error instanceof Error ? error.message : String(error)})\n`);
|
|
53
|
+
}
|
|
54
|
+
const [command, args] = dependencyInstallCommand(platform, name, linuxId);
|
|
55
|
+
if (platform === "linux")
|
|
56
|
+
run("sudo", ["-n", "apt-get", "update"]);
|
|
57
|
+
run(command, args);
|
|
58
|
+
probe();
|
|
59
|
+
};
|
|
60
|
+
if (platform === "win32") {
|
|
61
|
+
ensure("pwsh", () => resolveWindowsPowerShell7());
|
|
62
|
+
ensure("git", () => resolveWinPaneShell("bash"));
|
|
63
|
+
if (process.env.AITERM_PSMUX)
|
|
64
|
+
ensureWinPsmux(false);
|
|
65
|
+
else
|
|
66
|
+
ensure("psmux", () => ensureWinPsmux(false));
|
|
67
|
+
}
|
|
68
|
+
else if (platform === "darwin" || platform === "linux") {
|
|
69
|
+
if (process.env.AITERM_TMUX)
|
|
70
|
+
resolveTmux(false);
|
|
71
|
+
else
|
|
72
|
+
ensure("tmux", () => resolveTmux(false));
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
throw new SetupError("platform_unsupported", `${platform}には対応していません`);
|
|
76
|
+
}
|
|
77
|
+
}
|
package/dist/setup.js
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { realpathSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { dirname, join, isAbsolute } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { randomUUID } from "node:crypto";
|
|
6
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
7
|
+
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
8
|
+
import { CallToolResultSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
9
|
+
import { prepareBackend, runSetupCommand, SetupError } from "./setup-platform.js";
|
|
10
|
+
import { configureIntegrations } from "./setup-integrations.js";
|
|
11
|
+
export function globalRegistration(run = runSetupCommand) {
|
|
12
|
+
const root = run(process.platform === "win32" ? "npm.cmd" : "npm", ["root", "-g"]).trim();
|
|
13
|
+
if (!isAbsolute(root) || /[\r\n]/u.test(root))
|
|
14
|
+
throw new SetupError("global_package_required", "npm global rootを確認できません");
|
|
15
|
+
const packageRoot = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
16
|
+
const installedRoot = join(root, "aiterm-mcp");
|
|
17
|
+
if (realpathSync(installedRoot) !== realpathSync(packageRoot)) {
|
|
18
|
+
throw new SetupError("global_package_required", "npm install -g aiterm-mcp後にaiterm-setupを実行してください。一時npm cacheやsource checkoutは登録しません");
|
|
19
|
+
}
|
|
20
|
+
return { command: process.execPath, args: [join(installedRoot, "dist", "index.js")] };
|
|
21
|
+
}
|
|
22
|
+
export async function verifySetupRuntime(registration) {
|
|
23
|
+
const client = new Client({ name: "aiterm-setup", version: "1.0.0" });
|
|
24
|
+
const transport = new StdioClientTransport({ ...registration, stderr: "pipe" });
|
|
25
|
+
transport.stderr?.on("data", (chunk) => process.stderr.write(chunk));
|
|
26
|
+
const name = `setup-${randomUUID().slice(0, 12)}`;
|
|
27
|
+
const call = async (tool, args) => {
|
|
28
|
+
const result = await client.request({ method: "tools/call", params: { name: tool, arguments: args } }, CallToolResultSchema, { timeout: 25_000 });
|
|
29
|
+
if (result.isError)
|
|
30
|
+
throw new SetupError("runtime_probe_failed", `${tool}が失敗しました`);
|
|
31
|
+
return result;
|
|
32
|
+
};
|
|
33
|
+
await client.connect(transport);
|
|
34
|
+
try {
|
|
35
|
+
await call("pty_open", { name, shell: process.platform === "win32" ? "pwsh" : "bash" });
|
|
36
|
+
try {
|
|
37
|
+
const text = process.platform === "win32" ? "Write-Output ('aiterm-' + 'ready')" : "printf 'aiterm-%s\\n' ready";
|
|
38
|
+
await call("pty_send", { session_id: name, text, mark: true });
|
|
39
|
+
const result = await call("pty_read", { session_id: name, wait: true, until: "aiterm-ready", timeout: 15, raw: true });
|
|
40
|
+
const output = result.content.filter((item) => item.type === "text").map((item) => item.text).join("\n");
|
|
41
|
+
if (!output.includes("aiterm-ready"))
|
|
42
|
+
throw new SetupError("runtime_probe_failed", "端末の実行結果を確認できません");
|
|
43
|
+
}
|
|
44
|
+
finally {
|
|
45
|
+
await call("pty_close", { session_id: name });
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
finally {
|
|
49
|
+
await client.close();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
export async function runSetup(options = {}) {
|
|
53
|
+
const result = { schema: "aiterm.setup-result.v1", status: "failed", backend: { kind: process.platform === "win32" ? "psmux" : "tmux", status: "failed" }, integrations: {} };
|
|
54
|
+
const progress = options.progress ?? ((message) => process.stderr.write(`aiterm-setup: ${message}\n`));
|
|
55
|
+
let stage = "backend";
|
|
56
|
+
try {
|
|
57
|
+
progress("端末の依存製品を確認します");
|
|
58
|
+
(options.prepare ?? prepareBackend)();
|
|
59
|
+
// Windowsのnpm.cmd実行に必要なPowerShell 7も、global root照会より先に導入する。
|
|
60
|
+
stage = "global_package";
|
|
61
|
+
const registration = (options.registration ?? globalRegistration)();
|
|
62
|
+
stage = "backend";
|
|
63
|
+
progress("MCP経由で端末を開き、実行結果と終了を確認します");
|
|
64
|
+
await (options.verify ?? verifySetupRuntime)(registration);
|
|
65
|
+
result.backend.status = "ready";
|
|
66
|
+
stage = "integrations";
|
|
67
|
+
progress("検出したAI clientのaiterm登録を更新して確認します");
|
|
68
|
+
result.integrations = (options.configure ?? configureIntegrations)(process.env.HOME ?? homedir(), registration);
|
|
69
|
+
if (Object.values(result.integrations).some((item) => item.status === "failed")) {
|
|
70
|
+
result.reason_code = "integration_failed";
|
|
71
|
+
}
|
|
72
|
+
else if (!Object.values(result.integrations).some((item) => item.status === "ready")) {
|
|
73
|
+
result.status = "unsupported";
|
|
74
|
+
result.reason_code = "clients_not_detected";
|
|
75
|
+
}
|
|
76
|
+
else
|
|
77
|
+
result.status = "ready";
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
const code = error instanceof SetupError ? error.code : `${stage}_failed`;
|
|
81
|
+
result.status = code === "platform_unsupported" ? "unsupported" : "failed";
|
|
82
|
+
result.reason_code = code;
|
|
83
|
+
if (stage === "backend")
|
|
84
|
+
result.backend = { ...result.backend, status: result.status, reason_code: code };
|
|
85
|
+
progress(error instanceof Error ? error.message : String(error));
|
|
86
|
+
}
|
|
87
|
+
return result;
|
|
88
|
+
}
|
package/dist/tmux-runtime.js
CHANGED
|
@@ -24,10 +24,20 @@ export const WIN_NS = `aiterm-${createHash("sha1").update(SOCKDIR).digest("hex")
|
|
|
24
24
|
// psmux は tmux CLI 互換の Windows ネイティブ実装(ConPTY・WSL 不要)。AITERM_PSMUX で
|
|
25
25
|
// バイナリを明示上書きできる(POSIX の AITERM_TMUX に対応)。
|
|
26
26
|
function psmuxBin() {
|
|
27
|
-
|
|
27
|
+
if (process.env.AITERM_PSMUX)
|
|
28
|
+
return process.env.AITERM_PSMUX;
|
|
29
|
+
const wingetLink = process.env.LOCALAPPDATA && path.join(process.env.LOCALAPPDATA, "Microsoft", "WinGet", "Links", "psmux.exe");
|
|
30
|
+
return wingetLink && fs.existsSync(wingetLink) ? wingetLink : "psmux";
|
|
31
|
+
}
|
|
32
|
+
export function psmuxVersionSupported(output) {
|
|
33
|
+
const match = /^psmux (\d+)\.(\d+)\.(\d+)(?:\s|$)/mu.exec(output);
|
|
34
|
+
if (!match)
|
|
35
|
+
return false;
|
|
36
|
+
const [major, minor, patch] = match.slice(1).map(Number);
|
|
37
|
+
return major > 3 || (major === 3 && (minor > 3 || (minor === 3 && patch >= 8)));
|
|
28
38
|
}
|
|
29
39
|
let winPsmuxOk = false;
|
|
30
|
-
function ensureWinPsmux(observe = true) {
|
|
40
|
+
export function ensureWinPsmux(observe = true) {
|
|
31
41
|
if (winPsmuxOk)
|
|
32
42
|
return;
|
|
33
43
|
const r = spawnSync(psmuxBin(), ["-V"], { encoding: "utf8", timeout: 10000 });
|
|
@@ -39,6 +49,8 @@ function ensureWinPsmux(observe = true) {
|
|
|
39
49
|
}
|
|
40
50
|
if (r.status !== 0)
|
|
41
51
|
ptyDependencyError("psmux -V が失敗しました。`psmux -V` が通るか確認してください。", observe);
|
|
52
|
+
if (!psmuxVersionSupported(r.stdout ?? ""))
|
|
53
|
+
ptyDependencyError("psmux 3.3.8以上が必要です。winget upgrade --id marlocarlo.psmux --source winget で更新してください。", observe);
|
|
42
54
|
winPsmuxOk = true;
|
|
43
55
|
}
|
|
44
56
|
// tmux が見つからないときの説明。macOS は tmux を同梱せず、Homebrew の bin は GUI 起動時の PATH に
|
|
@@ -16,12 +16,11 @@ export function resolveWindowsPowerShell7(probe = defaultProbe) {
|
|
|
16
16
|
const located = probe("where.exe", [WINDOWS_POWERSHELL_7_COMMAND]);
|
|
17
17
|
const resolved = located.stdout?.split(/\r?\n/u)
|
|
18
18
|
.find(candidate => path.win32.isAbsolute(candidate)
|
|
19
|
-
&& path.win32.basename(candidate).toLowerCase() === WINDOWS_POWERSHELL_7_COMMAND)
|
|
19
|
+
&& path.win32.basename(candidate).toLowerCase() === WINDOWS_POWERSHELL_7_COMMAND)
|
|
20
|
+
?? path.win32.join(process.env.ProgramFiles ?? "C:\\Program Files", "PowerShell", "7", WINDOWS_POWERSHELL_7_COMMAND);
|
|
20
21
|
const fail = () => {
|
|
21
22
|
throw new AitermError(`PowerShell 7が必要です。Microsoft公式経路で導入してください: ${WINDOWS_POWERSHELL_7_INSTALL}`, 2);
|
|
22
23
|
};
|
|
23
|
-
if (located.status !== 0 || typeof resolved !== "string")
|
|
24
|
-
fail();
|
|
25
24
|
const executable = resolved;
|
|
26
25
|
const version = probe(executable, ["-NoLogo", "-NoProfile", "-NonInteractive", "-Command",
|
|
27
26
|
'[ordered]@{ edition = $PSVersionTable.PSEdition; major = $PSVersionTable.PSVersion.Major } | ConvertTo-Json -Compress']);
|
package/docs/00_overview.md
CHANGED
|
@@ -5,7 +5,7 @@ dotagentsは任意の工場統合を担うが、Aitermの製品正典や実行
|
|
|
5
5
|
|
|
6
6
|
## 現行正典
|
|
7
7
|
|
|
8
|
-
- [README](../README.md)/[日本語README](../README.ja.md): 公開API
|
|
8
|
+
- [README](../README.md)/[日本語README](../README.ja.md): 公開API、`aiterm-setup`による依存準備とAI登録、利用、復旧。
|
|
9
9
|
- [AGENTS](https://github.com/kitepon/aiterm-mcp/blob/main/AGENTS.md): AI作業者向けの製品境界と変更規律。
|
|
10
10
|
- [DESIGN](DESIGN.md): 現行アーキテクチャと不変条件。
|
|
11
11
|
- [RELEASE](RELEASE.md): version同期、検証、公開、公開後smoke、巻き戻し。
|
|
@@ -14,6 +14,9 @@ dotagentsは任意の工場統合を担うが、Aitermの製品正典や実行
|
|
|
14
14
|
- [benchmarks](https://github.com/kitepon/aiterm-mcp/blob/main/docs/benchmarks.md): 出力削減の実測根拠。
|
|
15
15
|
- [CHANGELOG](../CHANGELOG.md): 版別変更履歴。
|
|
16
16
|
|
|
17
|
+
Grok/Composerのsandbox起動拒否については、[DESIGNの失敗と復旧](DESIGN.md#failure-and-recovery)に
|
|
18
|
+
検出の所有と適用範囲、[RELEASEの公開後smoke](RELEASE.md#公開後smoke)に検証条件を置く。
|
|
19
|
+
|
|
17
20
|
## 履歴と証拠
|
|
18
21
|
|
|
19
22
|
- [`archive/`](https://github.com/kitepon/aiterm-mcp/tree/main/docs/archive): 完了・棄却・中断・失効・置換により現行制御から外れたsnapshot。
|
package/docs/DESIGN.md
CHANGED
|
@@ -6,6 +6,16 @@ Aitermは、AIがローカルshell、SSH、container、REPL、別agentの対話T
|
|
|
6
6
|
操作するstdio MCP serverである。install、session、state、schema、diagnostics、recovery、releaseは
|
|
7
7
|
このrepositoryが所有し、外部の工場管理製品がなくても単独で動く。
|
|
8
8
|
|
|
9
|
+
## 導入と登録
|
|
10
|
+
|
|
11
|
+
`aiterm-setup`はglobal packageからだけ実行し、依存準備、公開MCP経由の端末実行、
|
|
12
|
+
検出したAIのユーザー設定への登録と読戻しを連続実行する。npm lifecycleでユーザー設定を変更しない。
|
|
13
|
+
共通の順序と結果は`src/setup.ts`、公式package managerとOS差は`src/setup-platform.ts`、
|
|
14
|
+
各AIの登録形式は`src/setup-integrations.ts`が所有する。既存の他サーバーは保持し、
|
|
15
|
+
Claude/CursorのJSONは参照先を原子的に更新して変更前backupを残す。Codex/Grokは公式CLIで登録・確認する。
|
|
16
|
+
各AIの読戻しは登録内容の確認であり、端末の実動作はその前の公開MCP試験で確認する。
|
|
17
|
+
失敗は理由付きJSONと非ゼロ終了で返す。対応外の自動導入と全AI未検出を成功扱いしない。
|
|
18
|
+
|
|
9
19
|
## Terminal model
|
|
10
20
|
|
|
11
21
|
プリミティブはlocal PTYを1つ開き、text/keyを送り、画面を読み、閉じることだけである。
|
|
@@ -26,6 +36,7 @@ Throughlineの補足記憶はpathを透過搬送するだけで、内容、proje
|
|
|
26
36
|
agent turnは常に非ブロックdispatchである。receiptの`event_cursor`がturn境界、`wait_process`が
|
|
27
37
|
platform nativeな別process起動情報を返す。waiterは純readerで、親のforeground turnを塞がない。
|
|
28
38
|
回答はharness所有transcriptから同じturnへ相関して回収し、欠落・曖昧・timeout時にpromptを再送しない。
|
|
39
|
+
Grok/Composerの記録先はCLIと同じOS絶対パスへcwdを正規化して導出し、完了通知と回答で同じ関数を使う。
|
|
29
40
|
`agent_steer`は実行中のCodex/Grok turnへ追加textを差し込み、idleなら送信せず状態を返す。
|
|
30
41
|
Cursorのsubmitはadapterがextended keyboard protocolのEnterへ変換し、呼び出し側は通常のdispatchだけを使う。
|
|
31
42
|
起動直後のClaude sessionへの初回dispatchは、他harnessと同じくTUIの入力受付を確認してから貼付とEnterを送る。
|
|
@@ -55,6 +66,21 @@ shell、接続先、各harnessの公式CLIが所有する。
|
|
|
55
66
|
stale send lockは並行processとのABAを避けるため自動削除せず、公開APIでは対象sessionを`pty_close`して
|
|
56
67
|
同じIDで再作成する。全session一括停止は公開しない。
|
|
57
68
|
|
|
69
|
+
Grok/Composerのread-only sandbox起動拒否は、`src/harnesses/grok.ts`の
|
|
70
|
+
`assertGrokSandboxNotRejected`がCLIのエラー表示から検出する。`src/core.ts`の共通入力受付待機は
|
|
71
|
+
Grok/Composerの場合だけこの判定を呼び、`GROK_SANDBOX_STARTUP_FAILED`で原因と未送信を返す。
|
|
72
|
+
初回prompt付き起動と通常dispatchに適用され、他harnessの入力受付判定には適用しない。
|
|
73
|
+
promptなしの起動応答はPTYへの起動要求を示し、入力受付の確認は後続の送信時に行う。
|
|
74
|
+
|
|
75
|
+
hookパスのシンボリックリンク等を拒否する判断はGrok CLIが所有する。AitermはCLIが出した拒否を伝え、
|
|
76
|
+
hookのコピー、設定の置換、sandboxの解除は行わない。原因を設定の管理元で修正した後、対象sessionを
|
|
77
|
+
閉じて起動し直す。検出の回帰試験は`test/grok-startup.test.mjs`に置く。
|
|
78
|
+
|
|
79
|
+
Grok/Composerのmanaged起動は公式`--trust`を渡し、指定cwdの信頼状態はGrok CLIが管理する。
|
|
80
|
+
`grokLaunchBlockingDialog`は信頼確認を入力受付から除外し、scrollbackのshell promptを取り違えない。
|
|
81
|
+
`grokTuiBusy`は応答中の表示だけを実行中の根拠にし、完了後も残る`[hooks: 成功/失敗]`を含めない。
|
|
82
|
+
これらのCLI固有判定は`src/harnesses/grok.ts`が所有し、共通処理は判定を呼び出す。
|
|
83
|
+
|
|
58
84
|
## Platform contract
|
|
59
85
|
|
|
60
86
|
- macOS/Linux/WSL2: tmux。
|
package/docs/RELEASE.md
CHANGED
|
@@ -5,16 +5,15 @@ Aitermのreleaseはこのrepositoryが所有する。`.github/workflows/product-
|
|
|
5
5
|
|
|
6
6
|
## CIの範囲
|
|
7
7
|
|
|
8
|
-
- push/pull request:
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
-
|
|
12
|
-
|
|
8
|
+
- push/pull request: 共通実装・CI自身・未分類の変更は`macos-native`、`linux-workstation`、`windows-native`を選ぶ。
|
|
9
|
+
Windows固有ファイル(`src/windows-powershell.ts`、`src/psmux-send-worker.ts`、`test/windows-*.test.mjs`)だけの変更は
|
|
10
|
+
LinuxとWindowsを選ぶ。共通変更が混ざっても対象OSを落とさない。試験は依存graphから選び、依存を確定できない変更だけ全テストへ広げる。
|
|
11
|
+
- 版番号だけの変更はJSONの実差分で識別し、Linuxでbuildと配布metadata・pack・文書確認を行う。依存や実行設定の変更は省略しない。
|
|
12
|
+
文書だけなら文書検査、実装と文書の混在なら関連試験と文書検査を行う。
|
|
13
|
+
- 週1回の定期実行(月曜 03:00 JST)と手動実行は、指定された環境で全テストを回す。定期実行の対象は3環境である。
|
|
13
14
|
- tag push: 所有確認と、tagged commitが`origin/main`の祖先であることの確認だけを行い、npmへprovenance付きで
|
|
14
15
|
publishする。同じcommitのmain CIの結果は待たない。
|
|
15
16
|
|
|
16
|
-
実測(2026-09-02): Linux 2分、macOS 2分、Windows 6分。全環境展開ではWindowsが常にcritical pathになる。
|
|
17
|
-
|
|
18
17
|
## Release手順
|
|
19
18
|
|
|
20
19
|
1. 変更に直結するfocused testを手元で通す。full regressionは手元で回さず、CIに任せる。
|
|
@@ -36,18 +35,35 @@ Aitermのreleaseはこのrepositoryが所有する。`.github/workflows/product-
|
|
|
36
35
|
|
|
37
36
|
## 公開後smoke
|
|
38
37
|
|
|
38
|
+
setupを変更した場合は、公開packageのglobal install後に`aiterm-setup --json`を実行し、
|
|
39
|
+
端末実行と検出した各AIの登録結果を確認する。初回と再実行は一時設定領域でも試験し、所有外の設定保持を確かめる。
|
|
40
|
+
WindowsのGrokパス変更ではスラッシュ区切りcwdで起動し、同じturnの完了通知と回答回収を確認する。
|
|
41
|
+
|
|
39
42
|
公式npm packageを隔離またはglobal installし、変更に触れたharnessの起動、non-blocking dispatch、wait outcome、
|
|
40
43
|
transcript回収、`pty_close`後の残骸ゼロを確認する。
|
|
41
44
|
|
|
45
|
+
Grok/Composerのsandbox起動拒否を変更した場合は、対象環境のCLIが拒否する設定で初回prompt付き起動と
|
|
46
|
+
通常dispatchの未送信エラーを確認する。`GROK_SANDBOX_STARTUP_FAILED`がCLIの原因を保持し、入力受付の
|
|
47
|
+
timeoutや再送案内へ変わらないことを確認する。拒否を検証した結果は起動成功の証拠にはしない。
|
|
48
|
+
設定の管理元による修理は別途確認し、smokeのためにsandbox解除やhookのコピーを行わない。
|
|
49
|
+
|
|
42
50
|
## 利用者の更新と巻き戻し
|
|
43
51
|
|
|
44
|
-
|
|
52
|
+
更新はnpmの公開packageから行う。
|
|
45
53
|
|
|
46
54
|
```bash
|
|
47
55
|
npm install -g aiterm-mcp@latest
|
|
56
|
+
aiterm-setup --json
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
巻き戻しは既知の正常版を指定する。
|
|
60
|
+
|
|
61
|
+
```bash
|
|
48
62
|
npm install -g "aiterm-mcp@<known-good-version>"
|
|
49
63
|
```
|
|
50
64
|
|
|
65
|
+
setupを持つ版では`aiterm-setup --json`を再実行する。どちらもMCP clientを再起動する。
|
|
66
|
+
|
|
51
67
|
`npx`をMCP設定から使う場合は、package引数を`aiterm-mcp@latest`へ変えると更新でき、
|
|
52
68
|
`aiterm-mcp@<known-good-version>`へ変えると固定・巻き戻しできる。変更後はMCP clientを再起動する。
|
|
53
69
|
dotagentsの導入・更新は不要である。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aiterm-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.32.0",
|
|
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": [
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
"type": "module",
|
|
41
41
|
"bin": {
|
|
42
42
|
"aiterm-mcp": "dist/index.js",
|
|
43
|
+
"aiterm-setup": "dist/setup-cli.js",
|
|
43
44
|
"aiterm-runtime-errors": "dist/runtime-errors-cli.js",
|
|
44
45
|
"aiterm-wait": "dist/aiterm-wait-cli.js"
|
|
45
46
|
},
|