aiterm-mcp 0.37.8 → 0.37.10
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 +5 -1
- package/README.md +5 -1
- package/dist/aiterm-wait-cli.js +2 -2
- package/dist/core.js +64 -9
- package/dist/harnesses/codex.js +25 -2
- package/dist/harnesses/grok.js +28 -3
- package/docs/00_overview.md +2 -0
- package/docs/DESIGN.md +13 -0
- 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.37.10] - 2026-09-21
|
|
11
|
+
|
|
12
|
+
### 修正
|
|
13
|
+
|
|
14
|
+
- Codex 0.155.1 の「Approaching rate limits」model切替dialogを専用に認識する。通常の`pty_send`と`agent_configure`は同じsessionで一時的な「Keep current model」だけを選び、入力受付を再確認してから進める。model変更と今後の表示抑止は選ばない。復旧不能時はtyped errorで本文・設定変更を送らない。
|
|
15
|
+
|
|
16
|
+
## [0.37.9] - 2026-09-21
|
|
17
|
+
|
|
18
|
+
### 修正
|
|
19
|
+
|
|
20
|
+
- Grok/Composerで前のターンのweekly-limitパネルが残っていても、通常の`pty_send`が一度閉じて今回の本文を送れるようにした。同じsessionを保ち、解除記録をreceiptへ返す。
|
|
21
|
+
- Grokの利用上限は現在の画面から判定する。古い上限logによる新しいターンの待機打切りと、入力欄に重なったprivacy案内による誤停止を修正した。
|
|
22
|
+
|
|
10
23
|
## [0.37.8] - 2026-09-19
|
|
11
24
|
|
|
12
25
|
### 修正
|
|
@@ -1719,7 +1732,9 @@ prototype (preserved under `prototype/python/` as the porting source and referen
|
|
|
1719
1732
|
`ubuntu-latest` for Node 18/20/22, publishing to npm on `v*` tags with
|
|
1720
1733
|
provenance.
|
|
1721
1734
|
|
|
1722
|
-
[Unreleased]: https://github.com/kitepon/aiterm-mcp/compare/v0.37.
|
|
1735
|
+
[Unreleased]: https://github.com/kitepon/aiterm-mcp/compare/v0.37.10...HEAD
|
|
1736
|
+
[0.37.10]: https://github.com/kitepon/aiterm-mcp/compare/v0.37.9...v0.37.10
|
|
1737
|
+
[0.37.9]: https://github.com/kitepon/aiterm-mcp/compare/v0.37.8...v0.37.9
|
|
1723
1738
|
[0.37.8]: https://github.com/kitepon/aiterm-mcp/compare/v0.37.7...v0.37.8
|
|
1724
1739
|
[0.37.7]: https://github.com/kitepon/aiterm-mcp/compare/v0.37.6...v0.37.7
|
|
1725
1740
|
[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.
|
|
205
|
+
**状態:** 開発継続中 · 現行公開版 **v0.37.10** · 動作対象は Linux · WSL2 · macOS · Windows ネイティブ · MIT · [変更履歴](CHANGELOG.md)。
|
|
206
206
|
|
|
207
207
|
### 更新と巻き戻し
|
|
208
208
|
|
|
@@ -244,10 +244,14 @@ 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)を参照。
|
|
250
252
|
|
|
253
|
+
Codex 0.155.1の「Approaching rate limits」model切替dialogは、通常の`pty_send`と`agent_configure`で同じsessionのまま一時的な**2. Keep current model**だけを選ぶ。入力受付を再確認してから本文または設定変更を進め、dispatch receiptの`pane_input_recovery`には`codex_rate_limit_model_switch_kept_current`を記録する。model切替と今後の表示抑止は選ばない。入力受付へ戻らなければ`CODEX_RATE_LIMIT_MODEL_SWITCH_RECOVERY_FAILED`となり、本文・設定変更は未送信。このdialogは`agent_approval`の対象ではなく、inspectは`reason="rate_limit_model_switch"`だけを返し、prompt digestとchoicesを出さない。
|
|
254
|
+
|
|
251
255
|
```text
|
|
252
256
|
agent_launch({ harness: "codex-cli", session_name: "codex1", cwd: "/repo",
|
|
253
257
|
prompt: "port test/legacy.py to vitest",
|
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.
|
|
219
|
+
**Status:** actively maintained · current public release **v0.37.10** · 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,10 +264,14 @@ 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)を参照。
|
|
270
272
|
|
|
273
|
+
Codex 0.155.1の「Approaching rate limits」model切替dialogは、通常の`pty_send`と`agent_configure`で同じsessionのまま一時的な**2. Keep current model**だけを選ぶ。入力受付を再確認してから本文または設定変更を進め、dispatch receiptの`pane_input_recovery`には`codex_rate_limit_model_switch_kept_current`を記録する。model切替と今後の表示抑止は選ばない。入力受付へ戻らなければ`CODEX_RATE_LIMIT_MODEL_SWITCH_RECOVERY_FAILED`となり、本文・設定変更は未送信。このdialogは`agent_approval`の対象ではなく、inspectは`reason="rate_limit_model_switch"`だけを返し、prompt digestとchoicesを出さない。
|
|
274
|
+
|
|
271
275
|
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.
|
|
272
276
|
|
|
273
277
|
```text
|
package/dist/aiterm-wait-cli.js
CHANGED
|
@@ -89,8 +89,8 @@ export async function main(argv) {
|
|
|
89
89
|
}
|
|
90
90
|
catch (e) {
|
|
91
91
|
// Grok は利用上限に達すると auth 正本を自壊させることがあり(2026-08-22 実測)、
|
|
92
|
-
// metadata
|
|
93
|
-
//
|
|
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,8 +18,8 @@ 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";
|
|
22
|
-
import { bindCodexTranscriptSession, latestCodexCompletion, observeCodexDone, buildCodexAgentCmd, codexLaunchNote, codexTuiReady, codexPaneObservation, codexApprovalDialog, codexStartupAction, CODEX_COMPOSER_MARKER_RE, codexModelChoice, codexEffortChoice, codexMoreReasoningChoice, codexTranscriptText, createCodexAgentMetadata, } from "./harnesses/codex.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
|
+
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, 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";
|
|
25
25
|
import { resolveAgentBin, resolveThroughlineBin, runThroughlineHandoffContext, isWindowsNativeExecutable, agentBinForPaneShell, resolveWinPaneShell } from "./agent-resolver.js";
|
|
@@ -1414,7 +1414,7 @@ export function runAgentApproval(options) {
|
|
|
1414
1414
|
const dialog = codexApprovalDialog(screen);
|
|
1415
1415
|
if (!dialog) {
|
|
1416
1416
|
result.status = state.state === "blocked" || state.state === "unknown" || action === "respond" ? "blocked" : "none";
|
|
1417
|
-
result.reason = state.state === "blocked" ?
|
|
1417
|
+
result.reason = state.state === "blocked" ? state.reason : state.state === "unknown" ? "unrecognized_screen" : "no_current_dialog";
|
|
1418
1418
|
return result;
|
|
1419
1419
|
}
|
|
1420
1420
|
result.kind = dialog.kind;
|
|
@@ -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;
|
|
@@ -3241,6 +3242,31 @@ function sendMenuChoice(name, choice) {
|
|
|
3241
3242
|
throw new AitermError(`agent設定の選択を送れませんでした: ${sent.stderr.trim() || `code=${sent.code}`}`, 2);
|
|
3242
3243
|
}
|
|
3243
3244
|
}
|
|
3245
|
+
/** Codexの上限接近modalは承認APIへ出さず、一時keepの2だけを選んで共通ready gateへ戻す。 */
|
|
3246
|
+
async function recoverCodexRateLimitModelSwitch(name, meta) {
|
|
3247
|
+
const dialog = meta.kind === "codex"
|
|
3248
|
+
? codexRateLimitModelSwitchDialog(captureScreen(name, AGENT_TUI_READY_LINES))
|
|
3249
|
+
: null;
|
|
3250
|
+
if (!dialog)
|
|
3251
|
+
return false;
|
|
3252
|
+
const live = observeSession(name);
|
|
3253
|
+
if (live.harness_alive !== true) {
|
|
3254
|
+
throw new AitermError(`CODEX_RATE_LIMIT_MODEL_SWITCH_RECOVERY_BLOCKED: ${live.reason}。今回の文字列は送信していません。`, 2);
|
|
3255
|
+
}
|
|
3256
|
+
sendMenuChoice(name, String(dialog.keepCurrentIndex));
|
|
3257
|
+
return true;
|
|
3258
|
+
}
|
|
3259
|
+
/** ready待機中に現れたCodex利用上限modalも、同じ一時keep復旧へ戻す。 */
|
|
3260
|
+
async function waitAgentTuiReadyAfterCodexRateLimitRecovery(name, meta, timeoutMs) {
|
|
3261
|
+
let codexRateLimitModelSwitch = await recoverCodexRateLimitModelSwitch(name, meta);
|
|
3262
|
+
let ready = await waitAgentTuiReady(name, meta, timeoutMs);
|
|
3263
|
+
if (!ready.ready && !codexRateLimitModelSwitch) {
|
|
3264
|
+
codexRateLimitModelSwitch = await recoverCodexRateLimitModelSwitch(name, meta);
|
|
3265
|
+
if (codexRateLimitModelSwitch)
|
|
3266
|
+
ready = await waitAgentTuiReady(name, meta, timeoutMs);
|
|
3267
|
+
}
|
|
3268
|
+
return { ready, codexRateLimitModelSwitch };
|
|
3269
|
+
}
|
|
3244
3270
|
/** 同じ対話sessionを保ったまま、harness標準の操作でmodel/effortを変更する。 */
|
|
3245
3271
|
export async function configureAgent(name, opts) {
|
|
3246
3272
|
assertSessionName(name);
|
|
@@ -3253,9 +3279,13 @@ export async function configureAgent(name, opts) {
|
|
|
3253
3279
|
}
|
|
3254
3280
|
const meta = loadAgentMetadata(name);
|
|
3255
3281
|
bindCompletedInitialPrompt(meta);
|
|
3256
|
-
const ready = await
|
|
3257
|
-
if (!ready.ready)
|
|
3282
|
+
const { ready, codexRateLimitModelSwitch } = await waitAgentTuiReadyAfterCodexRateLimitRecovery(name, meta, AGENT_TUI_READY_TIMEOUT_MS);
|
|
3283
|
+
if (!ready.ready) {
|
|
3284
|
+
if (codexRateLimitModelSwitch) {
|
|
3285
|
+
throw new AitermError(`CODEX_RATE_LIMIT_MODEL_SWITCH_RECOVERY_FAILED: ${codexPaneObservation(ready.lastScreen).reason}。設定変更は送信していません。`, 2);
|
|
3286
|
+
}
|
|
3258
3287
|
throw new AitermError(`agent session '${name}' は入力待ちではありません`, 2);
|
|
3288
|
+
}
|
|
3259
3289
|
if (meta.kind === "claude") {
|
|
3260
3290
|
if (model) {
|
|
3261
3291
|
await sendAgentPromptText(name, `/model ${model}`);
|
|
@@ -3440,13 +3470,38 @@ export async function dispatchAgentTurn(name, text, o = {}) {
|
|
|
3440
3470
|
&& agentCompletionCursor(meta) === 0
|
|
3441
3471
|
&& readClaudeOperationMarker(meta) === null;
|
|
3442
3472
|
const paneInputRecovery = await ensureAgentOwnsPaneInput(name, meta.kind);
|
|
3473
|
+
let codexRateLimitModelSwitch = false;
|
|
3474
|
+
const limitDialog = meta.kind === "grok" || meta.kind === "composer"
|
|
3475
|
+
? grokRateLimitDialog(captureScreen(name, 0)) : null;
|
|
3476
|
+
if (limitDialog) {
|
|
3477
|
+
const live = observeSession(name);
|
|
3478
|
+
const done = latestAgentDoneEvent(meta);
|
|
3479
|
+
if (live.harness_alive !== true || done?.done_status !== "turn_error") {
|
|
3480
|
+
const reason = live.harness_alive !== true ? live.reason : "latest_turn_not_error";
|
|
3481
|
+
throw new AitermError(`GROK_RATE_LIMIT_RECOVERY_BLOCKED: ${reason}。終了済み上限パネルと確認できません。今回の文字列は送信していません。`, 2);
|
|
3482
|
+
}
|
|
3483
|
+
sendKey(name, limitDialog.dismissKey);
|
|
3484
|
+
}
|
|
3443
3485
|
if (meta.kind !== "claude" || claudeColdStart) {
|
|
3444
|
-
const
|
|
3486
|
+
const recovery = await waitAgentTuiReadyAfterCodexRateLimitRecovery(name, meta, o.ready_timeout ?? AGENT_TUI_READY_TIMEOUT_MS);
|
|
3487
|
+
codexRateLimitModelSwitch = recovery.codexRateLimitModelSwitch;
|
|
3488
|
+
const { ready } = recovery;
|
|
3445
3489
|
if (!ready.ready) {
|
|
3490
|
+
if (codexRateLimitModelSwitch) {
|
|
3491
|
+
throw new AitermError(`CODEX_RATE_LIMIT_MODEL_SWITCH_RECOVERY_FAILED: ${codexPaneObservation(ready.lastScreen).reason}。今回の文字列は送信していません。`, 2);
|
|
3492
|
+
}
|
|
3493
|
+
if (limitDialog) {
|
|
3494
|
+
const reason = grokPaneObservation(ready.lastScreen).reason;
|
|
3495
|
+
throw new AitermError(`GROK_RATE_LIMIT_RECOVERY_FAILED: ${reason}。上限パネル解除後の入力受付を確認できません。今回の文字列は送信していません。`, 2);
|
|
3496
|
+
}
|
|
3446
3497
|
throw new AitermError(`agent session '${name}' の ${agentLabel(meta.kind)} TUI が入力受付状態になりません。文字列は送信していません。` +
|
|
3447
3498
|
"少し後で pty_read(screen:true) を確認し、TUI が起動済みなら再度 pty_send してください。", 2);
|
|
3448
3499
|
}
|
|
3449
3500
|
}
|
|
3501
|
+
if (codexRateLimitModelSwitch)
|
|
3502
|
+
paneInputRecovery.push("codex_rate_limit_model_switch_kept_current");
|
|
3503
|
+
if (limitDialog)
|
|
3504
|
+
paneInputRecovery.push("grok_rate_limit_dialog_dismissed");
|
|
3450
3505
|
const startOffset = agentCompletionCursor(meta);
|
|
3451
3506
|
// promptなしで起動したCursorは、最初のdispatch時点ではtranscriptとの相関markerをまだ持たない。
|
|
3452
3507
|
// その1回だけlaunch contextを加え、以後はbind済みconversationへ通常textだけを送る。
|
package/dist/harnesses/codex.js
CHANGED
|
@@ -390,7 +390,7 @@ function currentCodexDialog(screen) {
|
|
|
390
390
|
// 過去の完了footerより前の質問・選択番号を、現在のdialogへ持ち込まない。
|
|
391
391
|
const previousFooter = footers.at(-2);
|
|
392
392
|
const current = previousFooter ? screen.slice(previousFooter.index + previousFooter[0].length) : screen;
|
|
393
|
-
const heading = [...current.matchAll(/Would you like to run the following command\?|Allow the [^\n]+ MCP server to run tool|Hooks need review|Do you trust the contents of this directory|Update available
|
|
393
|
+
const heading = [...current.matchAll(/Would you like to run the following command\?|Allow the [^\n]+ MCP server to run tool|Hooks need review|Do you trust the contents of this directory|Update available!|Approaching rate limits/g)].at(-1);
|
|
394
394
|
return heading ? current.slice(heading.index) : current;
|
|
395
395
|
}
|
|
396
396
|
export function codexPaneObservation(screen) {
|
|
@@ -400,7 +400,7 @@ export function codexPaneObservation(screen) {
|
|
|
400
400
|
const tail = screen.split("\n").slice(-24).join("\n");
|
|
401
401
|
// 現在のmodal footerがある時だけ、折返しで上へ出た質問を画面全体から探す。
|
|
402
402
|
const lastComposer = [...tail.matchAll(/(?:^|\n)[ \t]*[›>](?![ \t]*\d+\.)/g)].at(-1)?.index ?? -1;
|
|
403
|
-
const lastDialog = [...tail.matchAll(/Press enter to confirm or esc to (?:cancel|go back)|enter to submit\s*\|\s*esc to cancel|Would you like to run the following command\?|Allow the [^\n]+ MCP server to run tool|Hooks need review|Do you trust the contents of this directory|Update available
|
|
403
|
+
const lastDialog = [...tail.matchAll(/Press enter to confirm or esc to (?:cancel|go back)|enter to submit\s*\|\s*esc to cancel|Would you like to run the following command\?|Allow the [^\n]+ MCP server to run tool|Hooks need review|Do you trust the contents of this directory|Update available!|Approaching rate limits/gi)].at(-1)?.index ?? -1;
|
|
404
404
|
const modal = lastDialog > lastComposer;
|
|
405
405
|
if (!modal) {
|
|
406
406
|
if (/esc to interrupt/i.test(tail))
|
|
@@ -415,12 +415,35 @@ export function codexPaneObservation(screen) {
|
|
|
415
415
|
return { state: "blocked", reason: "mcp_approval" };
|
|
416
416
|
if (/Hooks need review/.test(current))
|
|
417
417
|
return { state: "blocked", reason: "hooks_review" };
|
|
418
|
+
if (codexRateLimitModelSwitchDialog(current))
|
|
419
|
+
return { state: "blocked", reason: "rate_limit_model_switch" };
|
|
418
420
|
if (codexLaunchBlockingDialog(current))
|
|
419
421
|
return { state: "blocked", reason: "startup_dialog" };
|
|
420
422
|
if (modal)
|
|
421
423
|
return { state: "blocked", reason: "unknown_dialog" };
|
|
422
424
|
return { state: "unknown", reason: "unrecognized_screen" };
|
|
423
425
|
}
|
|
426
|
+
/**
|
|
427
|
+
* Codex 0.155.1 の利用上限接近modal。agent_approvalの対象ではないため、選択肢は公開しない。
|
|
428
|
+
* 1はmodel切替、3は今後の表示抑止なので、Aitermが選んでよいのは一時keepの2だけである。
|
|
429
|
+
*/
|
|
430
|
+
export function codexRateLimitModelSwitchDialog(screen) {
|
|
431
|
+
const current = currentCodexDialog(screen);
|
|
432
|
+
const footer = current.lastIndexOf("Press enter to confirm or esc to go back");
|
|
433
|
+
const composer = [...current.matchAll(/(?:^|\n)[ \t]*[›>](?![ \t]*\d+\.)/g)].at(-1)?.index ?? -1;
|
|
434
|
+
// 現在のcomposerがmodal footerより後なら、scrollbackの古いmodalである。
|
|
435
|
+
if (composer > footer)
|
|
436
|
+
return null;
|
|
437
|
+
if (!/(?:^|\n)\s*Approaching rate limits\s*(?:\n|$)/.test(current)
|
|
438
|
+
|| !/(?:^|\n)\s*Switch to gpt-5\.6-luna for lower credit usage\?\s*(?:\n|$)/.test(current)
|
|
439
|
+
|| !/(?:^|\n)[ \t]*(?:›[ \t]*)?1\. Switch to gpt-5\.6-luna(?:[ \t]{2,}[^\n]*)?[ \t]*(?:\n|$)/.test(current)
|
|
440
|
+
|| !/(?:^|\n)[ \t]*(?:›[ \t]*)?2\. Keep current model[ \t]*(?:\n|$)/.test(current)
|
|
441
|
+
|| !/(?:^|\n)[ \t]*(?:›[ \t]*)?3\. Keep current model \(never show again\)(?:[ \t]{2,}[^\n]*)?[ \t]*(?:\n|$)/.test(current)
|
|
442
|
+
|| !/Press enter to confirm or esc to go back/i.test(current))
|
|
443
|
+
return null;
|
|
444
|
+
const selected = current.match(/(?:^|\n)[ \t]*›[ \t]*([123])\. (?:Switch to gpt-5\.6-luna|Keep current model(?: \(never show again\))?)(?:[ \t]{2,}[^\n]*)?[ \t]*(?:\n|$)/)?.[1];
|
|
445
|
+
return { keepCurrentIndex: 2, selectedIndex: selected ? Number(selected) : null };
|
|
446
|
+
}
|
|
424
447
|
export function codexStartupFailure(screen) {
|
|
425
448
|
return /(?:^|\n)[ \t]*[›>]?[ \t]*Error loading config\.toml:/m.test(screen)
|
|
426
449
|
? "configuration_error" : null;
|
package/dist/harnesses/grok.js
CHANGED
|
@@ -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
|
-
|
|
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 (
|
|
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" };
|
package/docs/00_overview.md
CHANGED
|
@@ -22,6 +22,8 @@ Claude Code親の非同期hookによる受信は[DESIGN](DESIGN.md#claude-code
|
|
|
22
22
|
|
|
23
23
|
## 履歴と証拠
|
|
24
24
|
|
|
25
|
+
- [Grokの上限パネル復帰の受入](https://github.com/kitepon/aiterm-mcp/blob/main/docs/adr/0069-grok-rate-limit-recovery.md): 通常送信による解除、公開package試験、本番導入と実機未検証範囲。
|
|
26
|
+
|
|
25
27
|
- [`archive/`](https://github.com/kitepon/aiterm-mcp/tree/main/docs/archive): 完了・棄却・中断・失効・置換により現行制御から外れたsnapshot。
|
|
26
28
|
- [`adr/`](https://github.com/kitepon/aiterm-mcp/tree/main/docs/adr): 採択・棄却した設計判断とrelease受入。
|
|
27
29
|
- [`evidence/`](https://github.com/kitepon/aiterm-mcp/tree/main/docs/evidence): Lattice/campaignの検証証拠。
|
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.
|
|
3
|
+
"version": "0.37.10",
|
|
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": [
|