aiterm-mcp 0.37.8 → 0.37.9

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 CHANGED
@@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.37.9] - 2026-09-21
11
+
12
+ ### 修正
13
+
14
+ - Grok/Composerで前のターンのweekly-limitパネルが残っていても、通常の`pty_send`が一度閉じて今回の本文を送れるようにした。同じsessionを保ち、解除記録をreceiptへ返す。
15
+ - Grokの利用上限は現在の画面から判定する。古い上限logによる新しいターンの待機打切りと、入力欄に重なったprivacy案内による誤停止を修正した。
16
+
10
17
  ## [0.37.8] - 2026-09-19
11
18
 
12
19
  ### 修正
@@ -1719,7 +1726,8 @@ prototype (preserved under `prototype/python/` as the porting source and referen
1719
1726
  `ubuntu-latest` for Node 18/20/22, publishing to npm on `v*` tags with
1720
1727
  provenance.
1721
1728
 
1722
- [Unreleased]: https://github.com/kitepon/aiterm-mcp/compare/v0.37.8...HEAD
1729
+ [Unreleased]: https://github.com/kitepon/aiterm-mcp/compare/v0.37.9...HEAD
1730
+ [0.37.9]: https://github.com/kitepon/aiterm-mcp/compare/v0.37.8...v0.37.9
1723
1731
  [0.37.8]: https://github.com/kitepon/aiterm-mcp/compare/v0.37.7...v0.37.8
1724
1732
  [0.37.7]: https://github.com/kitepon/aiterm-mcp/compare/v0.37.6...v0.37.7
1725
1733
  [0.37.6]: https://github.com/kitepon/aiterm-mcp/compare/v0.37.5...v0.37.6
package/README.ja.md CHANGED
@@ -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.37.8** · 動作対象は Linux · WSL2 · macOS · Windows ネイティブ · MIT · [変更履歴](CHANGELOG.md)。
205
+ **状態:** 開発継続中 · 現行公開版 **v0.37.9** · 動作対象は Linux · WSL2 · macOS · Windows ネイティブ · MIT · [変更履歴](CHANGELOG.md)。
206
206
 
207
207
  ### 更新と巻き戻し
208
208
 
@@ -244,6 +244,8 @@ pty_read(id, { wait: true }) → 削減済みの出力を読む(完了
244
244
 
245
245
  Grok/Composerの無人起動は公式`--trust`で指定された作業フォルダを信頼登録し、確認画面を完了してから初回promptを送る。この登録はGrok CLIの信頼ストアへ保存され、フォルダ内のhook・MCP・LSPにも適用される。read-only sandboxの制限は維持する。画面に残る完了済みhookの結果は実行中と判定しない。
246
246
 
247
+ 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の上限観測には現在の画面だけを使う。
248
+
247
249
  Grok/Composerがread-only sandboxの適用を拒否した場合、prompt送信時に`GROK_SANDBOX_STARTUP_FAILED`とCLIの原因を返す。例えばhookのパスにシンボリックリンクがあるとGrok CLIは起動を拒否する。設定の管理元で原因を修正し、対象sessionを`pty_close`して起動し直す。Aitermはsandboxを解除したりhookをコピーしたりしない。
248
250
 
249
251
  この判定は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)を参照。
package/README.md CHANGED
@@ -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.37.8** · 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).
219
+ **Status:** actively maintained · current public release **v0.37.9** · 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
 
@@ -264,6 +264,8 @@ The same primitive hosts another agent's TUI. `agent_launch` starts a selected e
264
264
 
265
265
  Grok/Composerの無人起動は公式`--trust`で指定された作業フォルダを信頼登録し、確認画面を完了してから初回promptを送る。この登録はGrok CLIの信頼ストアへ保存され、フォルダ内のhook・MCP・LSPにも適用される。read-only sandboxの制限は維持する。画面に残る完了済みhookの結果は実行中と判定しない。
266
266
 
267
+ 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の上限観測には現在の画面だけを使う。
268
+
267
269
  Grok/Composerがread-only sandboxの適用を拒否した場合、prompt送信時に`GROK_SANDBOX_STARTUP_FAILED`とCLIの原因を返す。hookパスのシンボリックリンクなど、CLIが示した原因を設定の管理元で修正し、対象sessionを`pty_close`して起動し直す。Aitermはsandboxを解除したりhookをコピーしたりしない。
268
270
 
269
271
  この判定は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)を参照。
@@ -89,8 +89,8 @@ export async function main(argv) {
89
89
  }
90
90
  catch (e) {
91
91
  // Grok は利用上限に達すると auth 正本を自壊させることがあり(2026-08-22 実測)、
92
- // metadata 検証が「未ログイン」という嘘の原因で落ちる。pane log に上限バナーが
93
- // あるなら、誤診でなく rate_limited として typed に回答する。
92
+ // metadata検証が認証不在で止まる。現在のviewportに上限パネルがある場合だけ
93
+ // rate_limitedとして返し、古いlogで現在の認証エラーを置き換えない。
94
94
  if (e instanceof AitermError && /Grok 認証正本/.test(e.message)) {
95
95
  const limited = detectAgentRateLimit("grok", cmd.session);
96
96
  if (limited) {
package/dist/core.js CHANGED
@@ -18,7 +18,7 @@ import { readRuntimeProcesses, processSubtree, processIdentity, backgroundProces
18
18
  import { AitermError, telemetryOwnedFailure, ownTelemetryFailure } from "./errors.js";
19
19
  import { isWin, SOCKDIR, tmuxCommand, sendPsmuxPayload, loadPtyBufferChunk, pasteBufferBaseArgs, TMUX_EMPTY_CONFIG, attachCommand, normalizePaneCommand, atomicShellMultiline, appendMarkSentinel, markShellCommand, settlePaneLog, paneCwdArgument, sessionEnvironmentLaunch, } from "./tmux-runtime.js";
20
20
  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";
21
- import { GROK_MODEL_DEFAULTS, realGrokHome, resolveAndValidateGrokAuth, assertGrokModelAvailable, grokEventsTranscript, latestGrokCompletion, observeGrokDone, buildGrokAgentCmd, grokLaunchNote, grokEnvTokens, grokTuiReady, grokTuiBusy, grokPaneObservation, grokStartupAction, grokLaunchBlockingDialog, assertGrokSandboxNotRejected, GROK_COMPOSER_MARKER_RE, grokFooterHasConfiguration, grokTranscriptText, createGrokAgentMetadata, } from "./harnesses/grok.js";
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, 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, CLAUDE_COMPOSER_MARKER_RE, createClaudeAgentMetadata, claudeSessionTranscriptPath, claudeApiErrorFromLine, } from "./harnesses/claude.js";
24
24
  import { bindCursorTranscriptSession, cursorTurnBoundary, latestCursorCompletion, observeCursorDone, cursorTranscriptText, assertCursorAuthenticationReady, assertCursorModelAvailable, buildCursorAgentCmd, cursorPromptWithLineage, createCursorAgentMetadata, cursorLaunchNote, cursorEffortNavigation, cursorTuiReady, cursorPaneObservation, CURSOR_SUBMIT_SEQUENCE, CURSOR_COMPOSER_CONTENT_MARKER_RE, validateCursorModelEffort, } from "./harnesses/cursor.js";
@@ -2590,20 +2590,21 @@ export function agentWaitGuide(session) {
2590
2590
  const cmd = `aiterm-wait --session ${session ?? "<session_id>"} --cursor 0`;
2591
2591
  return `完了通知は ${agentWaitLaunchForm(cmd)} で受ける(親はここで待たない・polling 不要)。receipt の outcome=done を確認してから再取得する。`;
2592
2592
  }
2593
- // harness 別の利用上限バナー。検知は「報告」専用で、完了判定や自動復旧には使わない。
2593
+ // harness別の利用上限観測。Grok/Composerは現在の質問カード、他harnessは既存logを使う。
2594
2594
  // 出典(2026-08-22): grok は live 実バナーで検証、codex/claude はインストール済み実バイナリの
2595
2595
  // 埋込文字列から抽出(codex: "You've hit your usage limit for" / claude: "Usage limit reached ·
2596
2596
  // continuing automatically when it resets"。Claude Code はリセット時に自動継続する設計なので、
2597
2597
  // この報告は「今は上限で止まっている」の観測であり恒久停止を意味しない)。
2598
2598
  const AGENT_RATE_LIMIT_PATTERNS = {
2599
- grok: [/You hit your weekly limit/i, /Weekly limit left:\s*0%/i],
2600
- composer: [/You hit your weekly limit/i, /Weekly limit left:\s*0%/i],
2601
2599
  codex: [/You'?ve hit your usage limit/i],
2602
2600
  claude: [/Usage limit reached/i],
2603
2601
  };
2604
2602
  const AGENT_RATE_LIMIT_SCAN_BYTES = 16 * 1024;
2605
2603
  // pane log の末尾から上限バナーを探す。読めない・無い・対象 harness でないは全て null(誤検知より取りこぼし側へ倒す)。
2606
2604
  export function detectAgentRateLimit(kind, aitermSession) {
2605
+ if (kind === "grok" || kind === "composer") {
2606
+ return grokRateLimitDialog(captureScreen(aitermSession, 0))?.message ?? null;
2607
+ }
2607
2608
  const patterns = AGENT_RATE_LIMIT_PATTERNS[kind];
2608
2609
  if (!patterns)
2609
2610
  return null;
@@ -3440,13 +3441,30 @@ export async function dispatchAgentTurn(name, text, o = {}) {
3440
3441
  && agentCompletionCursor(meta) === 0
3441
3442
  && readClaudeOperationMarker(meta) === null;
3442
3443
  const paneInputRecovery = await ensureAgentOwnsPaneInput(name, meta.kind);
3444
+ const limitDialog = meta.kind === "grok" || meta.kind === "composer"
3445
+ ? grokRateLimitDialog(captureScreen(name, 0)) : null;
3446
+ if (limitDialog) {
3447
+ const live = observeSession(name);
3448
+ const done = latestAgentDoneEvent(meta);
3449
+ if (live.harness_alive !== true || done?.done_status !== "turn_error") {
3450
+ const reason = live.harness_alive !== true ? live.reason : "latest_turn_not_error";
3451
+ throw new AitermError(`GROK_RATE_LIMIT_RECOVERY_BLOCKED: ${reason}。終了済み上限パネルと確認できません。今回の文字列は送信していません。`, 2);
3452
+ }
3453
+ sendKey(name, limitDialog.dismissKey);
3454
+ }
3443
3455
  if (meta.kind !== "claude" || claudeColdStart) {
3444
3456
  const ready = await waitAgentTuiReady(name, meta, o.ready_timeout ?? AGENT_TUI_READY_TIMEOUT_MS);
3445
3457
  if (!ready.ready) {
3458
+ if (limitDialog) {
3459
+ const reason = grokPaneObservation(ready.lastScreen).reason;
3460
+ throw new AitermError(`GROK_RATE_LIMIT_RECOVERY_FAILED: ${reason}。上限パネル解除後の入力受付を確認できません。今回の文字列は送信していません。`, 2);
3461
+ }
3446
3462
  throw new AitermError(`agent session '${name}' の ${agentLabel(meta.kind)} TUI が入力受付状態になりません。文字列は送信していません。` +
3447
3463
  "少し後で pty_read(screen:true) を確認し、TUI が起動済みなら再度 pty_send してください。", 2);
3448
3464
  }
3449
3465
  }
3466
+ if (limitDialog)
3467
+ paneInputRecovery.push("grok_rate_limit_dialog_dismissed");
3450
3468
  const startOffset = agentCompletionCursor(meta);
3451
3469
  // promptなしで起動したCursorは、最初のdispatch時点ではtranscriptとの相関markerをまだ持たない。
3452
3470
  // その1回だけlaunch contextを加え、以後はbind済みconversationへ通常textだけを送る。
@@ -210,8 +210,11 @@ export async function observeGrokDone(meta, timeout, requestedCursor, detectRate
210
210
  }
211
211
  try {
212
212
  const done = grokCompletionEvent(meta, JSON.parse(line));
213
- if (done)
214
- return observation(done.done_status === "turn_error" ? "error" : "done", done);
213
+ if (done) {
214
+ const limited = done.done_status === "turn_error"
215
+ ? detectRateLimit(meta.kind, meta.aiterm_session) : null;
216
+ return observation(limited ? "rate_limited" : done.done_status === "turn_error" ? "error" : "done", done, limited);
217
+ }
215
218
  }
216
219
  catch {
217
220
  malformedEvents++;
@@ -307,12 +310,34 @@ export function grokTuiBusy(screen) {
307
310
  || screen.includes("Responding...")
308
311
  || screen.includes("[stop]");
309
312
  }
313
+ // 現在の質問カードだけを認識する。選択肢が空でも操作欄は残る。
314
+ // footer後に描画があれば過去のカードなので、会話履歴からキー操作を起こさない。
315
+ export function grokRateLimitDialog(screen) {
316
+ const clean = screen.replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "");
317
+ const headings = [...clean.matchAll(/^[ \t]*[┃│]?[ \t]*You hit your weekly limit\.[ \t]*[┃│]?[ \t]*$/gm)];
318
+ const heading = headings.at(-1);
319
+ if (!heading)
320
+ return null;
321
+ const afterHeading = clean.slice(heading.index + heading[0].length);
322
+ const footer = /Tab\s*:\s*next\s+answer\s*[│┃]?\s*Esc\s*:\s*scrollback\s*[│┃]?\s*Shift\+x\s*:\s*dismiss/i.exec(afterHeading);
323
+ if (!footer || afterHeading.slice(footer.index + footer[0].length).trim())
324
+ return null;
325
+ const body = afterHeading.slice(0, footer.index);
326
+ if (grokTuiBusy(body) || /(?:^|\n)[ \t]*(?:│[ \t]*)?[❯>]/u.test(body))
327
+ return null;
328
+ return { message: "You hit your weekly limit.", dismissKey: "X" };
329
+ }
330
+ function grokFramedComposer(screen) {
331
+ return /^[ \t]*│[ \t]*[❯>][^\n]*\n(?:[ \t]*│[^\n]*\n)*[ \t]*╰[^\n]*\b(?:Grok|Composer)\s+[\w.()-]+[^\n]*╯[ \t]*(?:\n|$)/mu.test(screen);
332
+ }
310
333
  export function grokPaneObservation(screen) {
311
334
  // 通信失敗後もWaitingが残る実画面を、稼働中として返さない。
312
335
  const tail = screen.split("\n").slice(-32).join("\n");
313
336
  if (/Connection failed|reqwest error stream|Check your network and try again/i.test(tail))
314
337
  return { state: "blocked", reason: "connection_failed" };
315
- if (/Help improve Grok/.test(tail))
338
+ if (grokRateLimitDialog(tail))
339
+ return { state: "blocked", reason: "rate_limited" };
340
+ if (/Help improve Grok/.test(tail) && !grokFramedComposer(tail.slice(tail.lastIndexOf("Help improve Grok"))))
316
341
  return { state: "blocked", reason: "privacy_choice" };
317
342
  if (grokLaunchBlockingDialog(tail))
318
343
  return { state: "blocked", reason: "startup_dialog" };
@@ -20,6 +20,10 @@ Grok/Composerのsandbox起動拒否については、[DESIGNの失敗と復旧
20
20
  Codex親への回答の自動配送は[DESIGN](DESIGN.md#codex親への自動配送)に、対応範囲と失敗時の状態を置く。
21
21
  Claude Code親の非同期hookによる受信は[DESIGN](DESIGN.md#claude-code親への自動配送)を参照する。
22
22
 
23
+ ## 実装待ちの設計
24
+
25
+ - [Grokの過去の利用上限表示からの復帰](https://github.com/kitepon/aiterm-mcp/blob/main/docs/grok-rate-limit-recovery-plan.md): 設計完了・実装未着手。通常dispatchでのパネル解除、現在の画面による上限観測、試験と実機受入を定める。
26
+
23
27
  ## 履歴と証拠
24
28
 
25
29
  - [`archive/`](https://github.com/kitepon/aiterm-mcp/tree/main/docs/archive): 完了・棄却・中断・失効・置換により現行制御から外れたsnapshot。
package/docs/DESIGN.md CHANGED
@@ -200,6 +200,19 @@ Grok/Composerのmanaged起動は公式`--trust`を渡し、指定cwdの信頼
200
200
  `grokTuiBusy`は応答中の表示だけを実行中の根拠にし、完了後も残る`[hooks: 成功/失敗]`を含めない。
201
201
  これらのCLI固有判定は`src/harnesses/grok.ts`が所有し、共通処理は判定を呼び出す。
202
202
 
203
+ Grok/Composerの終了済みerrorターンにweekly-limit質問カードが残る場合、通常dispatchだけが
204
+ 現在のviewportを読み、harness生存と最新turnのエラー完了を確かめて`X`を一回送る。
205
+ 既存の入力受付待機を通した後に完了cursorを取得し、今回の本文だけを送る。
206
+ 実施した解除は既存receiptの`pane_input_recovery`に`grok_rate_limit_dialog_dismissed`として載る。
207
+ 解除条件の不成立は`GROK_RATE_LIMIT_RECOVERY_BLOCKED`、解除後の入力受付失敗は
208
+ `GROK_RATE_LIMIT_RECOVERY_FAILED`で未送信を返す。読取・観測・設定・steerはこの解除を行わない。
209
+
210
+ `grokRateLimitDialog`が見出しと操作footerの組を所有し、過去logや後続UIのあるカードは採用しない。
211
+ privacy案内は現在の枠付きcomposerとmodel footerが見える場合だけ入力受付を妨げない。
212
+ 完了観測は成功eventを優先し、今回のerror eventと現在のカードが揃えばturn情報付きの`rate_limited`を返す。
213
+ 新turnの完了前に古いlogだけで上限を返さない。購入・再認証・過去prompt再送・定期再試行は行わない。
214
+ 画面判定と模擬CLIの実PTY試験は`test/grok-rate-limit.test.mjs`に置く。
215
+
203
216
  ## Platform contract
204
217
 
205
218
  - macOS/Linux/WSL2: tmux。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aiterm-mcp",
3
- "version": "0.37.8",
3
+ "version": "0.37.9",
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": [