aiterm-mcp 0.40.0 → 0.40.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -1
- package/README.ja.md +7 -5
- package/README.md +7 -5
- package/dist/core.js +39 -4
- package/dist/harnesses/cursor.js +41 -3
- package/dist/harnesses/grok.js +2 -1
- package/dist/index.js +2 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.40.2] - 2026-09-27
|
|
11
|
+
|
|
12
|
+
### 修正
|
|
13
|
+
|
|
14
|
+
- Composerの指定の案内が古かった。`agent_launch`の説明とREADMEは`harness=grok-cli, model=grok-composer-2.5-fast`を案内していたが、現行のGrok CLI(grok 1.0.41)のcatalogにComposerは無く、CLI自体も`unknown model id`で拒否する。その案内どおりに起動すると、catalog照合で必ず止まっていた。Composerは今Cursor Agent CLIのcatalogにあるので、`harness=cursor-cli, model=composer-2.5-fast`(または`composer-2.5`)を案内する。Grok catalogにComposer modelが無いというエラーにも、同じ指定を添える。別modelへのfallbackはしない。
|
|
15
|
+
|
|
16
|
+
## [0.40.1] - 2026-09-27
|
|
17
|
+
|
|
18
|
+
### 修正
|
|
19
|
+
|
|
20
|
+
- Cursorの送信前hookがpromptを拒否した時に、`pty_send`が成功receiptを返していた。Cursorはpromptを捨てて入力欄を空に戻すため、入力欄の残留検査では見分けられず、親は来ない完了を待ち続けた。起動時promptは理由の無い`submitted_unconfirmed`になっていた。拒否の表示(`Hook blocked with message:`)を見分け、起動時promptは`initial_prompt=failed`、`pty_send`はエラーとし、どちらも`USER_HOOK_BLOCKED`とhookの出力を返す。確認時間より後の拒否は、完了待ちが`outcome=error`で返す。`pty_observe`は`blocked`/`user_hook_blocked`を返す。
|
|
21
|
+
- Cursorの送信前hookが3秒の確認時間より長く動くと、拒否されても起動時promptは理由の無い`submitted_unconfirmed`で返っていた。Windowsではhookごとの起動が遅く、数本続くと3秒を超える。hookの実行中の画面(「Working」だけで`ctrl+c to stop`が無い)が続く間は、最長60秒まで結果を待つ。
|
|
22
|
+
|
|
10
23
|
## [0.40.0] - 2026-09-27
|
|
11
24
|
|
|
12
25
|
### 変更(互換性なし)
|
|
@@ -1785,7 +1798,9 @@ prototype (preserved under `prototype/python/` as the porting source and referen
|
|
|
1785
1798
|
`ubuntu-latest` for Node 18/20/22, publishing to npm on `v*` tags with
|
|
1786
1799
|
provenance.
|
|
1787
1800
|
|
|
1788
|
-
[Unreleased]: https://github.com/kitepon/aiterm-mcp/compare/v0.40.
|
|
1801
|
+
[Unreleased]: https://github.com/kitepon/aiterm-mcp/compare/v0.40.2...HEAD
|
|
1802
|
+
[0.40.2]: https://github.com/kitepon/aiterm-mcp/compare/v0.40.1...v0.40.2
|
|
1803
|
+
[0.40.1]: https://github.com/kitepon/aiterm-mcp/compare/v0.40.0...v0.40.1
|
|
1789
1804
|
[0.40.0]: https://github.com/kitepon/aiterm-mcp/compare/v0.39.1...v0.40.0
|
|
1790
1805
|
[0.39.1]: https://github.com/kitepon/aiterm-mcp/compare/v0.39.0...v0.39.1
|
|
1791
1806
|
[0.39.0]: https://github.com/kitepon/aiterm-mcp/compare/v0.38.2...v0.39.0
|
package/README.ja.md
CHANGED
|
@@ -145,7 +145,7 @@ diagnostics、recovery、update、releaseを所有します。このREADMEと[
|
|
|
145
145
|
|
|
146
146
|
17ツール: 7つのPTYツール、正規のagent起動入口`agent_launch`、移行用の旧4alias、`agent_configure`、`agent_approval`、`claude_turn`、`claude_approval`、`diagnostics`。backendはPOSIXのtmux/Windows nativeのpsmuxなので、MCPサーバやAIクライアントが再起動してもsessionは生き残る。
|
|
147
147
|
|
|
148
|
-
**v0.28.0では実行基盤harnessとmodelを分離した。** harnessはagent loop・認証・hook・session・transcriptを所有し、modelはその上で選ぶ。Cursor Agent CLIでGPT/Claude/Grokを選んでも完了契約はCursor方式のまま。Composerは別harness
|
|
148
|
+
**v0.28.0では実行基盤harnessとmodelを分離した。** harnessはagent loop・認証・hook・session・transcriptを所有し、modelはその上で選ぶ。Cursor Agent CLIでGPT/Claude/Grokを選んでも完了契約はCursor方式のまま。Composerは別harnessではなくmodelである。現行のGrok CLI catalog(grok 1.0.41)には無く、Cursor catalogにあるので`harness:"cursor-cli", model:"composer-2.5-fast"`(または`composer-2.5`)で表す。旧4起動ツールは同じ実装へ流れる互換alias。
|
|
149
149
|
|
|
150
150
|
**v0.25.2ではGrok 4.6を含む同一sessionの連続設定変更を安定化。** Grok Build 1.0.3で
|
|
151
151
|
`/model`の成功通知が再描画により消えても、変更前には無かった要求model/effortが常駐footerへ現れた
|
|
@@ -202,7 +202,7 @@ runtime-error store は canonical dotagents config の `collection.enabled: true
|
|
|
202
202
|
場合だけ収集し、既定OFF、network送信は行いません。tag起点CIのnpm provenance(OIDC Trusted
|
|
203
203
|
Publishing)で公開し、GitHub Release が Official MCP Registry を再登録します。
|
|
204
204
|
|
|
205
|
-
**状態:** 開発継続中 · 現行公開版 **v0.40.
|
|
205
|
+
**状態:** 開発継続中 · 現行公開版 **v0.40.2** · 動作対象は Linux · WSL2 · macOS · Windows ネイティブ · MIT · [変更履歴](CHANGELOG.md)。
|
|
206
206
|
|
|
207
207
|
### 更新と巻き戻し
|
|
208
208
|
|
|
@@ -258,6 +258,8 @@ Grok/Composerの無人起動は公式`--trust`で指定された作業フォ
|
|
|
258
258
|
|
|
259
259
|
Grok/Composerで終了済みターンのweekly-limitパネルが残っている場合、次の通常`pty_send`が`Shift+X`で一度閉じ、入力受付を確認して今回の本文を送る。同じsessionと会話を保ち、receiptの`pane_input_recovery`に`grok_rate_limit_dialog_dismissed`を記録する。ターン未終了・harness不在は`GROK_RATE_LIMIT_RECOVERY_BLOCKED`、解除後の入力受付失敗は`GROK_RATE_LIMIT_RECOVERY_FAILED`となり、本文は未送信。上限の継続は`rate_limited`として返し、過去promptは再送しない。Grokの上限観測には現在の画面だけを使う。
|
|
260
260
|
|
|
261
|
+
Cursorの送信前hook(`beforeSubmitPrompt`と、互換読込するClaude Codeの`UserPromptSubmit`)がpromptを拒否すると、Cursorはpromptを捨て、turnも完了も起きない。Aitermはこの拒否の表示を見分け、起動時promptは`initial_prompt=failed`、`pty_send`は成功receiptを返さず、どちらも`USER_HOOK_BLOCKED`とhookの出力を返す。確認時間(3秒)より後の拒否は、完了待ちが`outcome=error`(`aiterm-wait`はexit 7)で返す。
|
|
262
|
+
|
|
261
263
|
Grok/Composerがread-only sandboxの適用を拒否した場合、prompt送信時に`GROK_SANDBOX_STARTUP_FAILED`とCLIの原因を返す。例えばhookのパスにシンボリックリンクがあるとGrok CLIは起動を拒否する。設定の管理元で原因を修正し、対象sessionを`pty_close`して起動し直す。Aitermはsandboxを解除したりhookをコピーしたりしない。
|
|
262
264
|
|
|
263
265
|
この判定はGrok専用アダプターが所有し、同じCLIを使うComposerにも適用する。初回prompt付きの`agent_launch`と通常の`pty_send`で、入力受付待ち中に拒否を検出すると未送信のエラーを返す。promptなし・`trust_project`指定なしの起動応答は入力受付を保証しない。`trust_project:true`では入力受付まで確認し、`startup.status`を返す。Grokのprivacy notice起動設定も同アダプターが所有する。実装の責務分担は[DESIGN](docs/DESIGN.md#failure-and-recovery)を参照。
|
|
@@ -283,7 +285,7 @@ $ aiterm-wait --session codex1 --cursor <event_cursor> # exit 0=done / 3=timeo
|
|
|
283
285
|
| --- | --- | --- |
|
|
284
286
|
| `claude-code` | Claude Code CLI | Claude model/effort |
|
|
285
287
|
| `codex-cli` | Codex CLI | OpenAI model/effort |
|
|
286
|
-
| `grok-cli` | Grok Build CLI | Grok
|
|
288
|
+
| `grok-cli` | Grok Build CLI | Grok model、live catalog照合(Composerは現行catalogに無い。`cursor-cli`を使う) |
|
|
287
289
|
| `cursor-cli` | Cursor Agent CLI | Cursor catalog上のGPT/Claude/Grok等 |
|
|
288
290
|
|
|
289
291
|
Cursorの`model`は`gpt-5.6-luna`のようなbase model、`reasoning_effort`は`high`のように別指定する。adapterは現行`model-effort` IDを`cursor-agent models`へ照合し、起動中変更はCursor標準model pickerのparameter editorを使う。不在時は別modelへfallbackしない。
|
|
@@ -393,7 +395,7 @@ claude mcp add --scope user --transport stdio aiterm -- aiterm-mcp
|
|
|
393
395
|
|
|
394
396
|
MCP クライアントが aiterm を stdio 越しにプログラムから駆動するので、上のすべては **端末に誰も座らないまま**動く。任意のMCP対応統括役が、自分と同じharnessを含む`agent_launch`を呼び、`pty_read`で結果を読んで次へ進める——無人で。これは、人が操作する端末が向かない場所にこそ aiterm が合うということ:
|
|
395
397
|
|
|
396
|
-
- **複数エージェントのオーケストレーション** — 統括役がサブタスクを Claude Code / Codex / Grok / Cursor harnessへ渡し、各々を専用の永続セッションに置き、全部を読み戻す。Composerは
|
|
398
|
+
- **複数エージェントのオーケストレーション** — 統括役がサブタスクを Claude Code / Codex / Grok / Cursor harnessへ渡し、各々を専用の永続セッションに置き、全部を読み戻す。ComposerはCursor catalogのmodel(`composer-2.5-fast`)として起動する。
|
|
397
399
|
- **CI** — ジョブのステップがエージェントを起こし、操作し、片付けられる。
|
|
398
400
|
- **cron** — スケジュール実行がエージェントを起動して出力を回収できる。
|
|
399
401
|
|
|
@@ -544,7 +546,7 @@ consumer は `aiterm-runtime-errors snapshot` を読み、durable ingestion 後
|
|
|
544
546
|
| --- | --- | --- |
|
|
545
547
|
| `claude-code` | Claude Code CLI | Claude model/effort |
|
|
546
548
|
| `codex-cli` | Codex CLI | OpenAI model/effort |
|
|
547
|
-
| `grok-cli` | Grok Build CLI | Grok
|
|
549
|
+
| `grok-cli` | Grok Build CLI | Grok model、live catalog照合(Composerは現行catalogに無い。`cursor-cli`を使う) |
|
|
548
550
|
| `cursor-cli` | Cursor Agent CLI | Cursor catalog上のGPT/Claude/Grok等 |
|
|
549
551
|
|
|
550
552
|
対応するCLI(`claude`/`codex`/`grok`/`cursor-agent`)の公式導入・認証が必要。前提違反はsession作成前に明示失敗する。全harnessが通常project/user環境と同じ非ブロックdispatch契約を使う。
|
package/README.md
CHANGED
|
@@ -147,7 +147,7 @@ Aiterm and is not a runtime dependency.
|
|
|
147
147
|
|
|
148
148
|
Seventeen tools: seven **PTY tools** — `pty_open` / `pty_send` / `pty_read` / `pty_key` / `pty_close` / `pty_list` / `pty_observe` — to open, drive, read, and observe 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`; `agent_approval`; `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.
|
|
149
149
|
|
|
150
|
-
**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.
|
|
150
|
+
**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. Composer is a model, not another harness. The current Grok CLI catalog (grok 1.0.41) no longer lists it; Cursor Agent CLI does, so use `harness: "cursor-cli", model: "composer-2.5-fast"` (or `composer-2.5`). The old four launcher tools are thin compatibility aliases over the same implementation.
|
|
151
151
|
|
|
152
152
|
**v0.25.2 stabilizes repeated in-place configuration changes, including Grok 4.6.** If Grok Build
|
|
153
153
|
1.0.3 redraws before its `/model` success notice can be observed, aiterm confirms the requested model/effort
|
|
@@ -216,7 +216,7 @@ collection is off by default and performs no network I/O. It ships via
|
|
|
216
216
|
tag-triggered CI with npm provenance (OIDC Trusted Publishing); the GitHub
|
|
217
217
|
Release re-registers the Official MCP Registry entry.
|
|
218
218
|
|
|
219
|
-
**Status:** actively maintained · current public release **v0.40.
|
|
219
|
+
**Status:** actively maintained · current public release **v0.40.2** · 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).
|
|
220
220
|
|
|
221
221
|
### Update and rollback
|
|
222
222
|
|
|
@@ -279,6 +279,8 @@ Grok/Composerの無人起動は公式`--trust`で指定された作業フォ
|
|
|
279
279
|
|
|
280
280
|
Grok/Composerで終了済みターンのweekly-limitパネルが残っている場合、次の通常`pty_send`が`Shift+X`で一度閉じ、入力受付を確認して今回の本文を送る。同じsessionと会話を保ち、receiptの`pane_input_recovery`に`grok_rate_limit_dialog_dismissed`を記録する。ターン未終了・harness不在は`GROK_RATE_LIMIT_RECOVERY_BLOCKED`、解除後の入力受付失敗は`GROK_RATE_LIMIT_RECOVERY_FAILED`となり、本文は未送信。上限の継続は`rate_limited`として返し、過去promptは再送しない。Grokの上限観測には現在の画面だけを使う。
|
|
281
281
|
|
|
282
|
+
When a Cursor pre-submit hook (`beforeSubmitPrompt`, or a Claude Code `UserPromptSubmit` hook that Cursor loads for compatibility) rejects the prompt, Cursor drops it and no turn or completion follows. Aiterm recognizes the rejection: an initial prompt returns `initial_prompt=failed`, and `pty_send` returns an error instead of a success receipt, both with `USER_HOOK_BLOCKED` and the hook's output. A rejection that comes after the 3-second start check is reported by the completion wait as `outcome=error` (`aiterm-wait` exit 7).
|
|
283
|
+
|
|
282
284
|
Grok/Composerがread-only sandboxの適用を拒否した場合、prompt送信時に`GROK_SANDBOX_STARTUP_FAILED`とCLIの原因を返す。hookパスのシンボリックリンクなど、CLIが示した原因を設定の管理元で修正し、対象sessionを`pty_close`して起動し直す。Aitermはsandboxを解除したりhookをコピーしたりしない。
|
|
283
285
|
|
|
284
286
|
この判定はGrok専用アダプターが所有し、同じCLIを使うComposerにも適用する。初回prompt付きの`agent_launch`と通常の`pty_send`で、入力受付待ち中に拒否を検出すると未送信のエラーを返す。promptなし・`trust_project`指定なしの起動応答は入力受付を保証しない。`trust_project:true`では入力受付まで確認し、`startup.status`を返す。Grokのprivacy notice起動設定も同アダプターが所有する。実装の責務分担は[DESIGN](docs/DESIGN.md#failure-and-recovery)を参照。
|
|
@@ -307,7 +309,7 @@ The canonical harness choices are:
|
|
|
307
309
|
| --- | --- | --- |
|
|
308
310
|
| `claude-code` | Claude Code CLI | Claude model and effort controls; correlated Stop hook |
|
|
309
311
|
| `codex-cli` | Codex CLI | OpenAI model and effort controls; durable rollout completion |
|
|
310
|
-
| `grok-cli` | Grok Build CLI | Grok
|
|
312
|
+
| `grok-cli` | Grok Build CLI | Grok model selected with `model`; live catalog check (Composer is not in the current catalog; use `cursor-cli`) |
|
|
311
313
|
| `cursor-cli` | Cursor Agent CLI | GPT, Claude, Grok, or another Cursor catalog model; normal transcript completion |
|
|
312
314
|
|
|
313
315
|
`env_vars` is an allowlist of environment-variable **names**, not a name/value map. At launch,
|
|
@@ -422,7 +424,7 @@ This registers it in `~/.claude.json`; you'll get an approval prompt the first t
|
|
|
422
424
|
|
|
423
425
|
Because an MCP client drives aiterm programmatically over stdio, everything above can run with **nobody sitting at the terminal**. Any MCP-capable orchestrator can call `agent_launch` — including a harness matching itself — then `pty_read` the result and act on it unattended. That makes aiterm a fit for exactly the places a human-driven terminal isn't:
|
|
424
426
|
|
|
425
|
-
- **Multi-agent orchestration** — an orchestrator hands sub-tasks to Claude Code / Codex / Grok / Cursor harnesses, each in its own persistent session, and reads them all back. Composer
|
|
427
|
+
- **Multi-agent orchestration** — an orchestrator hands sub-tasks to Claude Code / Codex / Grok / Cursor harnesses, each in its own persistent session, and reads them all back. Composer runs as a Cursor catalog model (`composer-2.5-fast`).
|
|
426
428
|
- **CI** — a job step can spin up an agent, drive it, and tear it down.
|
|
427
429
|
- **cron** — a scheduled run can launch an agent and collect its output.
|
|
428
430
|
|
|
@@ -579,7 +581,7 @@ Consumer flow is `aiterm-runtime-errors snapshot`, then `aiterm-runtime-errors a
|
|
|
579
581
|
| --- | --- | --- |
|
|
580
582
|
| `claude-code` | Claude Code CLI | Claude catalog model; native effort controls |
|
|
581
583
|
| `codex-cli` | Codex CLI | OpenAI catalog model; native effort controls |
|
|
582
|
-
| `grok-cli` | Grok Build CLI | Grok
|
|
584
|
+
| `grok-cli` | Grok Build CLI | Grok catalog model; Composer is not in the current catalog |
|
|
583
585
|
| `cursor-cli` | Cursor Agent CLI | Cursor catalog model, including GPT/Claude/Grok; effort uses model parameter override |
|
|
584
586
|
|
|
585
587
|
The selected harness CLI must be installed and authenticated. Use each product owner's official installer and updater; Aiterm does not distribute alternate CLI tarballs. For Cursor Agent CLI, use `curl https://cursor.com/install -fsS | bash` on macOS/Linux/WSL or `irm 'https://cursor.com/install?win32=true' | iex` on native Windows, authenticate once with `agent login`, and update with `agent update`; Aiterm invokes the unambiguous `cursor-agent` binary. Missing binaries, invalid model/effort values, unavailable Grok catalog models, and nonexistent `cwd` fail before a session exists.
|
package/dist/core.js
CHANGED
|
@@ -21,7 +21,7 @@ import { sleep, currentUid, runtimeStateBase, safeStatSize, readFileRange, write
|
|
|
21
21
|
import { GROK_MODEL_DEFAULTS, realGrokHome, resolveAndValidateGrokAuth, assertGrokModelAvailable, grokEventsTranscript, latestGrokCompletion, observeGrokDone, buildGrokAgentCmd, grokLaunchNote, grokEnvTokens, grokTuiReady, grokTuiBusy, grokPaneObservation, grokRateLimitDialog, grokStartupAction, grokLaunchBlockingDialog, assertGrokSandboxNotRejected, GROK_COMPOSER_MARKER_RE, grokFooterHasConfiguration, grokTranscriptText, createGrokAgentMetadata, } from "./harnesses/grok.js";
|
|
22
22
|
import { bindCodexTranscriptSession, latestCodexCompletion, observeCodexDone, buildCodexAgentCmd, codexLaunchNote, codexTuiReady, codexPaneObservation, codexRateLimitModelSwitchDialog, codexApprovalDialog, codexStartupAction, CODEX_COMPOSER_MARKER_RE, codexModelChoice, codexEffortChoice, codexMoreReasoningChoice, codexTranscriptText, createCodexAgentMetadata, } from "./harnesses/codex.js";
|
|
23
23
|
import { OPERATION_ID_RE, CLAUDE_RESULT_MAX_BYTES, CLAUDE_EFFORTS, agentManagedClaudeSettingsPath, agentClaudeResultPath, agentClaudeOperationPath, agentClaudeApprovalReceiptPath, agentClaudeDispatchReceiptPath, validateOperationId, readClaudeResultText, assertClaudeAuthenticationReady, buildClaudeAgentCmd, claudeLaunchNote, claudeTuiReady, claudePaneObservation, claudeStartupAction, claudeLoginMethodMenu, CLAUDE_COMPOSER_MARKER_RE, createClaudeAgentMetadata, claudeSessionTranscriptPath, claudeApiErrorFromLine, } from "./harnesses/claude.js";
|
|
24
|
-
import { bindCursorTranscriptSession, cursorTurnBoundary, latestCursorCompletion, observeCursorDone, cursorTranscriptText, assertCursorAuthenticationReady, assertCursorModelAvailable, buildCursorAgentCmd, cursorAgentArgv, cursorPwshLaunchLine, cursorPromptWithLineage, createCursorAgentMetadata, cursorLaunchNote, cursorEffortNavigation, cursorTuiReady, cursorPaneObservation, cursorUsageLimit, CURSOR_SUBMIT_SEQUENCE, CURSOR_COMPOSER_CONTENT_MARKER_RE, validateCursorModelEffort, } from "./harnesses/cursor.js";
|
|
24
|
+
import { bindCursorTranscriptSession, cursorTurnBoundary, latestCursorCompletion, observeCursorDone, cursorTranscriptText, assertCursorAuthenticationReady, assertCursorModelAvailable, buildCursorAgentCmd, cursorAgentArgv, cursorPwshLaunchLine, cursorPromptWithLineage, createCursorAgentMetadata, cursorLaunchNote, cursorEffortNavigation, cursorTuiReady, cursorPaneObservation, cursorPromptHooksRunning, cursorUsageLimit, CURSOR_SUBMIT_SEQUENCE, CURSOR_COMPOSER_CONTENT_MARKER_RE, validateCursorModelEffort, } from "./harnesses/cursor.js";
|
|
25
25
|
import { resolveAgentBin, resolveThroughlineBin, runThroughlineHandoffContext, isWindowsNativeExecutable, agentBinForPaneShell, resolveWinPaneShell } from "./agent-resolver.js";
|
|
26
26
|
export { AitermError } from "./errors.js";
|
|
27
27
|
export { tmuxSpawnEnv } from "./tmux-runtime.js";
|
|
@@ -2669,7 +2669,7 @@ export async function observeAgentDone(name, o = {}) {
|
|
|
2669
2669
|
return observeCodexDone(meta, timeout, o.cursor, detectAgentRateLimit, o.signal);
|
|
2670
2670
|
}
|
|
2671
2671
|
if (meta.kind === "cursor" && meta.completion_route === "cursor_transcript") {
|
|
2672
|
-
return observeCursorDone(meta, timeout, o.cursor, detectAgentRateLimit, o.signal);
|
|
2672
|
+
return observeCursorDone(meta, timeout, o.cursor, detectAgentRateLimit, (session) => captureScreen(session, 0), o.signal);
|
|
2673
2673
|
}
|
|
2674
2674
|
if ((meta.kind === "grok" || meta.kind === "composer") && meta.completion_route === "grok_transcript") {
|
|
2675
2675
|
return observeGrokDone(meta, timeout, o.cursor, detectAgentRateLimit, o.signal);
|
|
@@ -3213,8 +3213,10 @@ export async function sendInitialAgentPrompt(name, text, o = {}) {
|
|
|
3213
3213
|
}
|
|
3214
3214
|
let delivery = { status: "submitted_unconfirmed", reason: "start_unconfirmed", turn_started: null };
|
|
3215
3215
|
const deadline = performance.now() + 3000;
|
|
3216
|
+
const hookDeadline = performance.now() + CURSOR_PROMPT_HOOK_WAIT_MS;
|
|
3217
|
+
let screen = "";
|
|
3216
3218
|
do {
|
|
3217
|
-
|
|
3219
|
+
screen = captureScreen(name, AGENT_TUI_READY_LINES);
|
|
3218
3220
|
const state = meta.kind === "grok" || meta.kind === "composer" ? grokPaneObservation(screen)
|
|
3219
3221
|
: meta.kind === "codex" ? codexPaneObservation(screen)
|
|
3220
3222
|
: meta.kind === "claude" ? claudePaneObservation(screen) : cursorPaneObservation(screen);
|
|
@@ -3226,6 +3228,11 @@ export async function sendInitialAgentPrompt(name, text, o = {}) {
|
|
|
3226
3228
|
delivery = { status: "started", reason: "approval_required", turn_started: true };
|
|
3227
3229
|
break;
|
|
3228
3230
|
}
|
|
3231
|
+
if (state.state === "blocked" && state.reason === "user_hook_blocked") {
|
|
3232
|
+
setInitialDelivery(meta, { status: "submitted_unconfirmed", reason: "user_hook_blocked", turn_started: false }, startOffset);
|
|
3233
|
+
setInitialPromptState(meta, "failed");
|
|
3234
|
+
throw new AitermError(userHookBlockedMessage(`initial_prompt=failed vendor=${meta.kind} harness=${agentHarness(meta.kind)}`, "起動時prompt", state.detail), 2);
|
|
3235
|
+
}
|
|
3229
3236
|
const completed = await observeAgentDone(name, { cursor: startOffset, timeout: 0 });
|
|
3230
3237
|
if (completed.outcome === "done") {
|
|
3231
3238
|
delivery = { status: "started", reason: "turn_completed", turn_started: true };
|
|
@@ -3236,7 +3243,7 @@ export async function sendInitialAgentPrompt(name, text, o = {}) {
|
|
|
3236
3243
|
break;
|
|
3237
3244
|
}
|
|
3238
3245
|
await sleep(100);
|
|
3239
|
-
} while (performance.now() < deadline);
|
|
3246
|
+
} while (performance.now() < deadline || (meta.kind === "cursor" && performance.now() < hookDeadline && cursorPromptHooksRunning(screen)));
|
|
3240
3247
|
setInitialDelivery(meta, delivery, startOffset);
|
|
3241
3248
|
return {
|
|
3242
3249
|
text: `initial_prompt=pending vendor=${meta.kind} event_cursor=${startOffset} harness=${agentHarness(meta.kind)}\n` +
|
|
@@ -3510,6 +3517,32 @@ export function attachImages(text, images) {
|
|
|
3510
3517
|
const body = text.trim().length > 0 ? text : "添付画像を確認してください。";
|
|
3511
3518
|
return `${body}\n\n${lines.join("\n")}\n添付画像は上のファイルを読んで確認する。`;
|
|
3512
3519
|
}
|
|
3520
|
+
function userHookBlockedMessage(head, what, detail) {
|
|
3521
|
+
return `${head}\nUSER_HOOK_BLOCKED: 利用者のhookが${what}を拒否したため、turnは始まっていません。完了通知も来ません。` +
|
|
3522
|
+
`hookを直すか外してから送り直してください。hookの出力: ${detail ?? "(不明)"}`;
|
|
3523
|
+
}
|
|
3524
|
+
const CURSOR_START_CONFIRM_MS = 3000;
|
|
3525
|
+
// 送信前hookが動いている間は、確認時間を過ぎても結果を待つ。Windowsではhookごとの起動が遅く、数本続くと3秒を超える
|
|
3526
|
+
// (fox実測 2026-09-27)。上限は利用者のhookに付く最長のtimeout(60秒)に合わせる。
|
|
3527
|
+
const CURSOR_PROMPT_HOOK_WAIT_MS = 60_000;
|
|
3528
|
+
// Cursorは送信前hookの実行中も「Working」だけを出し、拒否されると入力欄を空に戻す。入力欄の残留検査では
|
|
3529
|
+
// 拒否を見分けられず、成功receiptを返すと親は来ない完了を待ち続ける。turnの開始か拒否の表示を確かめる。
|
|
3530
|
+
// hookが上限を過ぎてから拒否した時は、完了待ち(observeCursorDone)がoutcome=errorで返す。
|
|
3531
|
+
async function assertCursorPromptNotHookBlocked(name) {
|
|
3532
|
+
const deadline = performance.now() + CURSOR_START_CONFIRM_MS;
|
|
3533
|
+
const hookDeadline = performance.now() + CURSOR_PROMPT_HOOK_WAIT_MS;
|
|
3534
|
+
let screen = "";
|
|
3535
|
+
do {
|
|
3536
|
+
screen = captureScreen(name, AGENT_TUI_READY_LINES);
|
|
3537
|
+
const state = cursorPaneObservation(screen);
|
|
3538
|
+
if (state.state === "busy")
|
|
3539
|
+
return;
|
|
3540
|
+
if (state.state === "blocked" && state.reason === "user_hook_blocked") {
|
|
3541
|
+
throw new AitermError(userHookBlockedMessage(`vendor=cursor session=${name}`, "送信した文", state.detail), 2);
|
|
3542
|
+
}
|
|
3543
|
+
await sleep(100);
|
|
3544
|
+
} while (performance.now() < deadline || (performance.now() < hookDeadline && cursorPromptHooksRunning(screen)));
|
|
3545
|
+
}
|
|
3513
3546
|
export async function dispatchAgentTurn(name, text, o = {}) {
|
|
3514
3547
|
assertSessionName(name);
|
|
3515
3548
|
const meta = loadAgentMetadata(name);
|
|
@@ -3608,6 +3641,8 @@ export async function dispatchAgentTurn(name, text, o = {}) {
|
|
|
3608
3641
|
// 陽性観測した場合だけ同じEnterを一度再送し、再検査後も残る時は失敗として返す。
|
|
3609
3642
|
residue = await retryCursorSubmitIfResidue(name, meta.kind, dispatchText, residue);
|
|
3610
3643
|
assertAgentSubmitDelivered(name, meta.kind, residue);
|
|
3644
|
+
if (meta.kind === "cursor")
|
|
3645
|
+
await assertCursorPromptNotHookBlocked(name);
|
|
3611
3646
|
return {
|
|
3612
3647
|
schema: "aiterm.agent-dispatch.v1",
|
|
3613
3648
|
session_id: meta.aiterm_session,
|
package/dist/harnesses/cursor.js
CHANGED
|
@@ -183,13 +183,13 @@ export function latestCursorCompletion(meta, readTranscriptLines) {
|
|
|
183
183
|
? cursorCompletionEvent(meta, harnessSessionId, state.terminalRecord, `cursor:${state.userTurns}`)
|
|
184
184
|
: null;
|
|
185
185
|
}
|
|
186
|
-
export async function observeCursorDone(meta, timeout, requestedCursor, detectRateLimit, signal) {
|
|
186
|
+
export async function observeCursorDone(meta, timeout, requestedCursor, detectRateLimit, readScreen, signal) {
|
|
187
187
|
const metadataFile = agentMetadataPath(meta.aiterm_session, meta.launch_id);
|
|
188
188
|
let transcript = cursorTranscript(meta);
|
|
189
189
|
const startBoundary = requestedCursor ?? (transcript ? cursorTranscriptState(transcript).userTurns : 0);
|
|
190
190
|
let malformedEvents = 0;
|
|
191
191
|
const deadline = performance.now() + timeout * 1000;
|
|
192
|
-
const observation = (outcome, ev = null, rateLimit = null) => ({
|
|
192
|
+
const observation = (outcome, ev = null, rateLimit = null, error = null) => ({
|
|
193
193
|
schema: "aiterm.agent-wait-result.v1",
|
|
194
194
|
session_id: meta.aiterm_session,
|
|
195
195
|
launch_id: meta.launch_id,
|
|
@@ -202,7 +202,7 @@ export async function observeCursorDone(meta, timeout, requestedCursor, detectRa
|
|
|
202
202
|
malformed_events: malformedEvents,
|
|
203
203
|
at: ev?.at ?? null,
|
|
204
204
|
rate_limit: rateLimit,
|
|
205
|
-
error
|
|
205
|
+
error,
|
|
206
206
|
});
|
|
207
207
|
for (;;) {
|
|
208
208
|
signal?.throwIfAborted();
|
|
@@ -222,6 +222,10 @@ export async function observeCursorDone(meta, timeout, requestedCursor, detectRa
|
|
|
222
222
|
const limited = detectRateLimit(meta.kind, meta.aiterm_session);
|
|
223
223
|
if (limited)
|
|
224
224
|
return observation("rate_limited", null, limited);
|
|
225
|
+
// 拒否されたpromptのturnは始まらず、完了も来ない。拒否の表示は数秒で消えるので、見えている間に終わらせる。
|
|
226
|
+
const hookBlocked = cursorHookBlocked(readScreen(meta.aiterm_session));
|
|
227
|
+
if (hookBlocked)
|
|
228
|
+
return observation("error", null, null, `USER_HOOK_BLOCKED: ${hookBlocked.message}`);
|
|
225
229
|
if (performance.now() >= deadline)
|
|
226
230
|
return observation(timeout === 0 ? "running" : "timeout");
|
|
227
231
|
await sleep(AGENT_DONE_POLL_MS);
|
|
@@ -452,10 +456,44 @@ export function cursorUsageLimit(screen) {
|
|
|
452
456
|
}
|
|
453
457
|
return { message: [`${heading[1]}.`, ...detail].join(" ") };
|
|
454
458
|
}
|
|
459
|
+
// Cursor Agentはpromptを送る前のhook(beforeSubmitPromptと、互換読込したClaude CodeのUserPromptSubmit)が
|
|
460
|
+
// 拒否すると、promptを捨てて入力欄を空に戻し、その下に「Hook blocked with message:」を数秒だけ出す。turnは始まらず、
|
|
461
|
+
// transcriptにもuser turnは残らない(v2026.09.26、Windows実機採取 2026-09-27)。
|
|
462
|
+
const CURSOR_HOOK_BLOCKED_RE = /^[ \t]*Hook blocked with message:[ \t]*(.*)$/gim;
|
|
463
|
+
const CURSOR_HOOK_BLOCKED_MESSAGE_LIMIT = 600;
|
|
464
|
+
export function cursorHookBlocked(screen) {
|
|
465
|
+
const clean = screen.replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "");
|
|
466
|
+
const hit = [...clean.matchAll(CURSOR_HOOK_BLOCKED_RE)].at(-1);
|
|
467
|
+
if (!hit)
|
|
468
|
+
return null;
|
|
469
|
+
const after = clean.slice(hit.index + hit[0].length);
|
|
470
|
+
// 拒否の後に新しいturnが動いている画面は、古い表示の名残として数えない。
|
|
471
|
+
if (/ctrl\+c to stop/i.test(after) || CURSOR_FOLLOWUP_MARKER_RE.test(after))
|
|
472
|
+
return null;
|
|
473
|
+
const lines = [hit[1].trim()];
|
|
474
|
+
for (const line of after.split("\n").slice(1)) {
|
|
475
|
+
if (!line.trim())
|
|
476
|
+
break;
|
|
477
|
+
lines.push(line.trim());
|
|
478
|
+
}
|
|
479
|
+
// hookを動かしたNode自身の警告は拒否の理由ではない。
|
|
480
|
+
const message = lines.filter(line => line && !/ExperimentalWarning|--trace-warnings|^any time$/.test(line)).join(" ");
|
|
481
|
+
return { message: (message || "(hookは理由を出していません)").slice(0, CURSOR_HOOK_BLOCKED_MESSAGE_LIMIT) };
|
|
482
|
+
}
|
|
483
|
+
// 送信前hookの実行中、Cursorは入力欄の上に「Working」の回転表示だけを出し、入力欄はまだ「Plan, search, build anything」の
|
|
484
|
+
// ままで「ctrl+c to stop」も無い(v2026.09.26、Windows実機採取 2026-09-27)。turnはまだ始まっておらず、hookが拒否すればここで終わる。
|
|
485
|
+
const CURSOR_SPINNER_WORKING_RE = /^[ \t]*[⠀-⣿]+[ \t]+Working\b/m;
|
|
486
|
+
export function cursorPromptHooksRunning(screen) {
|
|
487
|
+
const tail = screen.replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "").split("\n").slice(-32).join("\n");
|
|
488
|
+
return CURSOR_SPINNER_WORKING_RE.test(tail) && CURSOR_START_PROMPT_MARKER_RE.test(tail) && !/ctrl\+c to stop/i.test(tail);
|
|
489
|
+
}
|
|
455
490
|
export function cursorPaneObservation(screen) {
|
|
456
491
|
const tail = screen.split("\n").slice(-32).join("\n");
|
|
457
492
|
if (cursorUsageLimit(tail))
|
|
458
493
|
return { state: "blocked", reason: "rate_limited" };
|
|
494
|
+
const hookBlocked = cursorHookBlocked(tail);
|
|
495
|
+
if (hookBlocked)
|
|
496
|
+
return { state: "blocked", reason: "user_hook_blocked", detail: hookBlocked.message };
|
|
459
497
|
if (/ctrl\+c to stop/i.test(tail))
|
|
460
498
|
return { state: "busy", reason: "turn_running" };
|
|
461
499
|
if (cursorTuiReady(tail))
|
package/dist/harnesses/grok.js
CHANGED
|
@@ -65,7 +65,8 @@ export function assertGrokModelAvailable(bin, cwd, model) {
|
|
|
65
65
|
const models = grokModelCatalog(bin, cwd);
|
|
66
66
|
if (!models.includes(model)) {
|
|
67
67
|
throw new AitermError(`Grok model catalog に ${JSON.stringify(model)} がありません。利用可能: ${models.join(", ")}。` +
|
|
68
|
-
"別modelへfallbackせず起動を中止しました"
|
|
68
|
+
"別modelへfallbackせず起動を中止しました" +
|
|
69
|
+
(/composer/i.test(model) ? "。ComposerはCursor catalogにあります: harness=cursor-cli, model=composer-2.5-fast" : ""), 2);
|
|
69
70
|
}
|
|
70
71
|
}
|
|
71
72
|
export function grokSessionDirectory(meta) {
|
package/dist/index.js
CHANGED
|
@@ -939,7 +939,7 @@ function registerAgentTool(toolName, kind, desc) {
|
|
|
939
939
|
server.registerTool("agent_launch", {
|
|
940
940
|
description: "エージェントを単一の標準入口から永続sessionへ起動する。harnessはagent loop・認証・hook・transcriptを所有する実行基盤、" +
|
|
941
941
|
"modelはそのharnessが選ぶ推論モデルであり別軸。Cursor harnessからGPT/Claude/Grok等を選んでも完了相関はCursor方式のまま。" +
|
|
942
|
-
"
|
|
942
|
+
"Composerは別harnessではなくmodelである。現行のGrok CLI catalog(grok 1.0.41)には無く、Cursor catalogにあるので harness=cursor-cli と model=composer-2.5-fast(またはcomposer-2.5)で指定する。" +
|
|
943
943
|
"remoteを付けると、SSHで入った別端末のAitermで同じ起動を行い、完了は同じ形で親へ届く。" +
|
|
944
944
|
agentEnvironmentDesc + agentCompletionDesc,
|
|
945
945
|
inputSchema: {
|
|
@@ -1006,7 +1006,7 @@ registerAgentTool("grok_agent", "grok", "【旧互換alias。新規連携は age
|
|
|
1006
1006
|
"turn は pty_send で送る(自動で非ブロック dispatch になる)。" +
|
|
1007
1007
|
agentCompletionDesc +
|
|
1008
1008
|
"model/reasoning_effortを引数で指定可。read-only sandboxとagent_configureに対応。");
|
|
1009
|
-
registerAgentTool("composer_agent", "composer", "【旧互換alias
|
|
1009
|
+
registerAgentTool("composer_agent", "composer", "【旧互換alias。Composerは現行Grok CLI catalogに無いため、新規連携は agent_launch(harness=cursor-cli, model=composer-2.5-fast)】Grok BuildのComposerモデルを永続端末に起動する。" +
|
|
1010
1010
|
agentEnvironmentDesc +
|
|
1011
1011
|
"turn は pty_send で送る(自動で非ブロック dispatch になる)。" +
|
|
1012
1012
|
agentCompletionDesc +
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aiterm-mcp",
|
|
3
|
-
"version": "0.40.
|
|
3
|
+
"version": "0.40.2",
|
|
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": [
|