aiterm-mcp 0.27.3 → 0.27.6
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/dist/aiterm-wait-cli.js +32 -7
- package/dist/core.js +63 -3
- package/package.json +2 -2
package/dist/aiterm-wait-cli.js
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
// aiterm-wait — agent turn 完了eventの純リーダー観測CLI。
|
|
3
3
|
// 完了/timeout/close を1行のJSON receiptで返してexitする。lock・PTY・dispatch状態には一切触れない。
|
|
4
4
|
// 親AIホストのバックグラウンドタスクとして起動し、exitを「観測終了の通知」として使う。
|
|
5
|
-
// exit≠完了: exit code は outcome を映す(0=done / 5=running=未完了 / 3=timeout=未完了 / 4=closed / 1=エラー)。
|
|
5
|
+
// exit≠完了: exit code は outcome を映す(0=done / 5=running=未完了 / 3=timeout=未完了 / 4=closed / 6=rate_limited=vendor利用上限 / 1=エラー)。
|
|
6
6
|
// --timeout 0 は待たずに一度だけ観測する照会で、未完了は running(timeout と混同させない)。
|
|
7
7
|
// receipt の outcome が正で、done 以外は未完了。timeout の既定は core の DEFAULT_AGENT_DONE_TIMEOUT(600秒)。
|
|
8
8
|
import { fileURLToPath } from "node:url";
|
|
9
9
|
import * as fs from "node:fs";
|
|
10
|
-
import { AitermError, observeAgentDone } from "./core.js";
|
|
10
|
+
import { AitermError, detectAgentRateLimit, observeAgentDone } from "./core.js";
|
|
11
11
|
const SESSION_RE = /^[A-Za-z0-9_-]{1,64}$/;
|
|
12
12
|
const OPERATION_RE = /^sha256:[0-9a-f]{64}$/;
|
|
13
13
|
const USAGE = "usage: aiterm-wait --session <name> [--cursor <event_cursor>] [--operation sha256:<64hex>] [--timeout <sec>]" +
|
|
@@ -74,14 +74,39 @@ const OUTCOME_EXIT_CODES = {
|
|
|
74
74
|
running: 5,
|
|
75
75
|
timeout: 3,
|
|
76
76
|
closed: 4,
|
|
77
|
+
rate_limited: 6,
|
|
77
78
|
};
|
|
78
79
|
export async function main(argv) {
|
|
79
80
|
const cmd = parseArgs(argv);
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
81
|
+
let result;
|
|
82
|
+
try {
|
|
83
|
+
result = await observeAgentDone(cmd.session, {
|
|
84
|
+
operation_id: cmd.operationId,
|
|
85
|
+
timeout: cmd.timeout,
|
|
86
|
+
cursor: cmd.cursor,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
catch (e) {
|
|
90
|
+
// Grok は利用上限に達すると auth 正本を自壊させることがあり(2026-08-22 実測)、
|
|
91
|
+
// metadata 検証が「未ログイン」という嘘の原因で落ちる。pane log に上限バナーが
|
|
92
|
+
// あるなら、誤診でなく rate_limited として typed に回答する。
|
|
93
|
+
if (e instanceof AitermError && /Grok 認証正本/.test(e.message)) {
|
|
94
|
+
const limited = detectAgentRateLimit("grok", cmd.session);
|
|
95
|
+
if (limited) {
|
|
96
|
+
emit({
|
|
97
|
+
ok: false,
|
|
98
|
+
code: "AGENT_RATE_LIMITED",
|
|
99
|
+
vendor: "grok",
|
|
100
|
+
rate_limit: limited,
|
|
101
|
+
message: "Grok が利用上限に達している。別のベンダーのモデルに切り替えてください。" +
|
|
102
|
+
"(認証正本の不在は上限時に CLI が auth を自壊させた痕跡で、未ログインの誤診に注意)",
|
|
103
|
+
});
|
|
104
|
+
process.exitCode = 6;
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
throw e;
|
|
109
|
+
}
|
|
85
110
|
emit(result);
|
|
86
111
|
process.exitCode = OUTCOME_EXIT_CODES[result.outcome];
|
|
87
112
|
}
|
package/dist/core.js
CHANGED
|
@@ -2998,6 +2998,48 @@ export function agentWaitGuide(session) {
|
|
|
2998
2998
|
const cmd = `aiterm-wait --session ${session ?? "<session_id>"} --cursor 0`;
|
|
2999
2999
|
return `完了通知は ${agentWaitLaunchForm(cmd)} で受ける(親はここで待たない・polling 不要)。receipt の outcome=done を確認してから再取得する。`;
|
|
3000
3000
|
}
|
|
3001
|
+
// vendor 別の利用上限バナー。検知は「報告」専用で、完了判定や自動復旧には使わない。
|
|
3002
|
+
// 出典(2026-08-22): grok は live 実バナーで検証、codex/claude はインストール済み実バイナリの
|
|
3003
|
+
// 埋込文字列から抽出(codex: "You've hit your usage limit for" / claude: "Usage limit reached ·
|
|
3004
|
+
// continuing automatically when it resets"。Claude Code はリセット時に自動継続する設計なので、
|
|
3005
|
+
// この報告は「今は上限で止まっている」の観測であり恒久停止を意味しない)。
|
|
3006
|
+
const AGENT_RATE_LIMIT_PATTERNS = {
|
|
3007
|
+
grok: [/You hit your weekly limit/i, /Weekly limit left:\s*0%/i],
|
|
3008
|
+
composer: [/You hit your weekly limit/i, /Weekly limit left:\s*0%/i],
|
|
3009
|
+
codex: [/You'?ve hit your usage limit/i],
|
|
3010
|
+
claude: [/Usage limit reached/i],
|
|
3011
|
+
};
|
|
3012
|
+
const AGENT_RATE_LIMIT_SCAN_BYTES = 16 * 1024;
|
|
3013
|
+
// pane log の末尾から上限バナーを探す。読めない・無い・対象 vendor でないは全て null(誤検知より取りこぼし側へ倒す)。
|
|
3014
|
+
export function detectAgentRateLimit(kind, aitermSession) {
|
|
3015
|
+
const patterns = AGENT_RATE_LIMIT_PATTERNS[kind];
|
|
3016
|
+
if (!patterns)
|
|
3017
|
+
return null;
|
|
3018
|
+
const file = logpath(aitermSession);
|
|
3019
|
+
let size;
|
|
3020
|
+
try {
|
|
3021
|
+
size = fs.statSync(file).size;
|
|
3022
|
+
}
|
|
3023
|
+
catch {
|
|
3024
|
+
return null;
|
|
3025
|
+
}
|
|
3026
|
+
if (size === 0)
|
|
3027
|
+
return null;
|
|
3028
|
+
let text;
|
|
3029
|
+
try {
|
|
3030
|
+
text = readFileRange(file, Math.max(0, size - AGENT_RATE_LIMIT_SCAN_BYTES), size).toString("utf8");
|
|
3031
|
+
}
|
|
3032
|
+
catch {
|
|
3033
|
+
return null;
|
|
3034
|
+
}
|
|
3035
|
+
const clean = text.replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "");
|
|
3036
|
+
for (const re of patterns) {
|
|
3037
|
+
const match = clean.match(re);
|
|
3038
|
+
if (match)
|
|
3039
|
+
return match[0];
|
|
3040
|
+
}
|
|
3041
|
+
return null;
|
|
3042
|
+
}
|
|
3001
3043
|
async function observeCodexDone(meta, timeout, requestedCursor) {
|
|
3002
3044
|
const metadataFile = agentMetadataPath(meta.aiterm_session, meta.launch_id);
|
|
3003
3045
|
let transcript = codexRootTranscript(meta);
|
|
@@ -3008,7 +3050,7 @@ async function observeCodexDone(meta, timeout, requestedCursor) {
|
|
|
3008
3050
|
let discardLeadingFragment = false;
|
|
3009
3051
|
let initializedBoundary = false;
|
|
3010
3052
|
const deadline = performance.now() + timeout * 1000;
|
|
3011
|
-
const observation = (outcome, ev = null) => ({
|
|
3053
|
+
const observation = (outcome, ev = null, rateLimit = null) => ({
|
|
3012
3054
|
schema: "aiterm.agent-wait-result.v1",
|
|
3013
3055
|
session_id: meta.aiterm_session,
|
|
3014
3056
|
launch_id: meta.launch_id,
|
|
@@ -3019,6 +3061,7 @@ async function observeCodexDone(meta, timeout, requestedCursor) {
|
|
|
3019
3061
|
turn_id: ev?.turn_id ?? null,
|
|
3020
3062
|
malformed_events: malformedEvents,
|
|
3021
3063
|
at: ev?.at ?? null,
|
|
3064
|
+
rate_limit: rateLimit,
|
|
3022
3065
|
});
|
|
3023
3066
|
for (;;) {
|
|
3024
3067
|
if (!fs.existsSync(metadataFile))
|
|
@@ -3067,6 +3110,11 @@ async function observeCodexDone(meta, timeout, requestedCursor) {
|
|
|
3067
3110
|
}
|
|
3068
3111
|
}
|
|
3069
3112
|
}
|
|
3113
|
+
{
|
|
3114
|
+
const limited = detectAgentRateLimit(meta.kind, meta.aiterm_session);
|
|
3115
|
+
if (limited)
|
|
3116
|
+
return observation("rate_limited", null, limited);
|
|
3117
|
+
}
|
|
3070
3118
|
if (performance.now() >= deadline)
|
|
3071
3119
|
return observation(timeout === 0 ? "running" : "timeout");
|
|
3072
3120
|
await sleep(AGENT_DONE_POLL_MS);
|
|
@@ -3084,7 +3132,7 @@ async function observeGrokDone(meta, timeout, requestedCursor) {
|
|
|
3084
3132
|
let discardLeadingFragment = false;
|
|
3085
3133
|
let initializedBoundary = false;
|
|
3086
3134
|
const deadline = performance.now() + timeout * 1000;
|
|
3087
|
-
const observation = (outcome, ev = null) => ({
|
|
3135
|
+
const observation = (outcome, ev = null, rateLimit = null) => ({
|
|
3088
3136
|
schema: "aiterm.agent-wait-result.v1",
|
|
3089
3137
|
session_id: meta.aiterm_session,
|
|
3090
3138
|
launch_id: meta.launch_id,
|
|
@@ -3095,6 +3143,7 @@ async function observeGrokDone(meta, timeout, requestedCursor) {
|
|
|
3095
3143
|
turn_id: ev?.turn_id ?? null,
|
|
3096
3144
|
malformed_events: malformedEvents,
|
|
3097
3145
|
at: ev?.at ?? null,
|
|
3146
|
+
rate_limit: rateLimit,
|
|
3098
3147
|
});
|
|
3099
3148
|
for (;;) {
|
|
3100
3149
|
if (!fs.existsSync(metadataFile))
|
|
@@ -3141,6 +3190,11 @@ async function observeGrokDone(meta, timeout, requestedCursor) {
|
|
|
3141
3190
|
}
|
|
3142
3191
|
}
|
|
3143
3192
|
}
|
|
3193
|
+
{
|
|
3194
|
+
const limited = detectAgentRateLimit(meta.kind, meta.aiterm_session);
|
|
3195
|
+
if (limited)
|
|
3196
|
+
return observation("rate_limited", null, limited);
|
|
3197
|
+
}
|
|
3144
3198
|
if (performance.now() >= deadline)
|
|
3145
3199
|
return observation(timeout === 0 ? "running" : "timeout");
|
|
3146
3200
|
await sleep(AGENT_DONE_POLL_MS);
|
|
@@ -3173,7 +3227,7 @@ export async function observeAgentDone(name, o = {}) {
|
|
|
3173
3227
|
let cursor = startOffset;
|
|
3174
3228
|
let carry = "";
|
|
3175
3229
|
let malformedEvents = 0;
|
|
3176
|
-
const observation = (outcome, ev = null) => ({
|
|
3230
|
+
const observation = (outcome, ev = null, rateLimit = null) => ({
|
|
3177
3231
|
schema: "aiterm.agent-wait-result.v1",
|
|
3178
3232
|
session_id: meta.aiterm_session,
|
|
3179
3233
|
launch_id: meta.launch_id,
|
|
@@ -3184,6 +3238,7 @@ export async function observeAgentDone(name, o = {}) {
|
|
|
3184
3238
|
turn_id: ev?.turn_id ?? null,
|
|
3185
3239
|
malformed_events: malformedEvents,
|
|
3186
3240
|
at: ev?.at ?? null,
|
|
3241
|
+
rate_limit: rateLimit,
|
|
3187
3242
|
});
|
|
3188
3243
|
for (;;) {
|
|
3189
3244
|
if (!fs.existsSync(metadataFile))
|
|
@@ -3211,6 +3266,11 @@ export async function observeAgentDone(name, o = {}) {
|
|
|
3211
3266
|
}
|
|
3212
3267
|
// timeout=0 は「待たずに一度だけ見る」照会=未完了は失敗ではなく running。
|
|
3213
3268
|
// 1秒以上を指定した待機の未完了は従来どおり timeout で、待ち方の意味は変えない。
|
|
3269
|
+
{
|
|
3270
|
+
const limited = detectAgentRateLimit(meta.kind, meta.aiterm_session);
|
|
3271
|
+
if (limited)
|
|
3272
|
+
return observation("rate_limited", null, limited);
|
|
3273
|
+
}
|
|
3214
3274
|
if (performance.now() >= deadline)
|
|
3215
3275
|
return observation(timeout === 0 ? "running" : "timeout");
|
|
3216
3276
|
await sleep(AGENT_DONE_POLL_MS);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aiterm-mcp",
|
|
3
|
-
"version": "0.27.
|
|
3
|
+
"version": "0.27.6",
|
|
4
4
|
"mcpName": "io.github.kitepon/aiterm-mcp",
|
|
5
5
|
"description": "Persistent tmux terminal MCP for launching and driving Claude, Codex, Grok, or Composer from any MCP client, cross-vendor or same-vendor. Also runs durable PTY sessions for SSH, containers, and REPLs.",
|
|
6
6
|
"keywords": [
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
],
|
|
27
27
|
"license": "MIT",
|
|
28
28
|
"author": {
|
|
29
|
-
"name": "Quo /
|
|
29
|
+
"name": "Quo / \u30af\u30aa at kitepon.dev",
|
|
30
30
|
"url": "https://kitepon.dev/"
|
|
31
31
|
},
|
|
32
32
|
"repository": {
|