aiterm-mcp 0.30.0 → 0.31.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 +9 -1
- package/README.ja.md +2 -2
- package/README.md +2 -2
- package/dist/aiterm-wait-cli.js +2 -1
- package/dist/core.js +28 -4
- package/dist/harnesses/claude.js +43 -0
- package/dist/harnesses/codex.js +1 -0
- package/dist/harnesses/cursor.js +1 -0
- package/dist/harnesses/grok.js +6 -3
- package/package.json +1 -1
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.31.0] - 2026-09-04
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- Claude Codeのturnが529 Overloaded等のAPIエラーで打ち切られるとStop hookが走らず、`aiterm-wait`が永久に未完了を返し続けていた(実被弾 2026-09-03: BellTeamのClaude席で70分の待機)。会話記録(`<config dir>/projects/<cwd slug>/<session-id>.jsonl`)に観測開始後に増えた`isApiErrorMessage:true`の行を読み、新しいoutcome `error`(exit 7、`error`にエラー本文)で返す。
|
|
15
|
+
- Grokの`turn_ended outcome=error`を完了境界として扱わず待ち続けていた。同じくoutcome `error`で返す。Cursorは`turn_ended`のstatusを問わず既に終了を返していたため変更なし。Codexはエラー終了の記録形が実測できておらず未対応。
|
|
16
|
+
|
|
10
17
|
## [0.30.0] - 2026-09-04
|
|
11
18
|
|
|
12
19
|
### Added
|
|
@@ -1546,7 +1553,8 @@ prototype (preserved under `prototype/python/` as the porting source and referen
|
|
|
1546
1553
|
`ubuntu-latest` for Node 18/20/22, publishing to npm on `v*` tags with
|
|
1547
1554
|
provenance.
|
|
1548
1555
|
|
|
1549
|
-
[Unreleased]: https://github.com/kitepon/aiterm-mcp/compare/v0.
|
|
1556
|
+
[Unreleased]: https://github.com/kitepon/aiterm-mcp/compare/v0.31.0...HEAD
|
|
1557
|
+
[0.31.0]: https://github.com/kitepon/aiterm-mcp/compare/v0.30.0...v0.31.0
|
|
1550
1558
|
[0.30.0]: https://github.com/kitepon/aiterm-mcp/compare/v0.29.31...v0.30.0
|
|
1551
1559
|
[0.29.31]: https://github.com/kitepon/aiterm-mcp/compare/v0.29.30...v0.29.31
|
|
1552
1560
|
[0.29.30]: https://github.com/kitepon/aiterm-mcp/compare/v0.29.29...v0.29.30
|
package/README.ja.md
CHANGED
|
@@ -153,7 +153,7 @@ runtime-error store は canonical dotagents config の `collection.enabled: true
|
|
|
153
153
|
場合だけ収集し、既定OFF、network送信は行いません。tag起点CIのnpm provenance(OIDC Trusted
|
|
154
154
|
Publishing)で公開し、GitHub Release が Official MCP Registry を再登録します。
|
|
155
155
|
|
|
156
|
-
**状態:** 開発継続中 · 現行公開版 **v0.
|
|
156
|
+
**状態:** 開発継続中 · 現行公開版 **v0.31.0** · 動作対象は Linux · WSL2 · macOS · Windows ネイティブ · MIT · [変更履歴](CHANGELOG.md)。
|
|
157
157
|
|
|
158
158
|
### 更新と巻き戻し
|
|
159
159
|
|
|
@@ -201,7 +201,7 @@ agent_launch({ harness: "codex-cli", session_name: "codex1", cwd: "/repo",
|
|
|
201
201
|
→ { session_id: "codex1", … } # Codex が永続端末で稼働開始
|
|
202
202
|
pty_read("codex1", { screen: true }) → 何をしているか読む(トークン削減)
|
|
203
203
|
pty_send("codex1", "also fix the imports it broke") # 非ブロックdispatch=event_cursor入りreceipt
|
|
204
|
-
$ aiterm-wait --session codex1 --cursor <event_cursor> # exit 0=done / 3=timeout(未完了) / 4=closed。回収は pty_read(agent_transcript:true)
|
|
204
|
+
$ aiterm-wait --session codex1 --cursor <event_cursor> # exit 0=done / 3=timeout(未完了) / 4=closed / 7=error(APIエラー等でturn打ち切り)。回収は pty_read(agent_transcript:true)
|
|
205
205
|
→ 操舵し、Codex の次の入力境界で返る
|
|
206
206
|
```
|
|
207
207
|
|
package/README.md
CHANGED
|
@@ -169,7 +169,7 @@ collection is off by default and performs no network I/O. It ships via
|
|
|
169
169
|
tag-triggered CI with npm provenance (OIDC Trusted Publishing); the GitHub
|
|
170
170
|
Release re-registers the Official MCP Registry entry.
|
|
171
171
|
|
|
172
|
-
**Status:** actively maintained · current public release **v0.
|
|
172
|
+
**Status:** actively maintained · current public release **v0.31.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
173
|
|
|
174
174
|
### Update and rollback
|
|
175
175
|
|
|
@@ -226,7 +226,7 @@ agent_launch({ harness: "codex-cli", session_name: "codex1", cwd: "/repo",
|
|
|
226
226
|
pty_read("codex1", { screen: true }) → read what it's doing (token-reduced)
|
|
227
227
|
pty_send("codex1", "also fix the imports it broke")
|
|
228
228
|
→ non-blocking dispatch; receipt carries event_cursor
|
|
229
|
-
$ aiterm-wait --session codex1 --cursor <event_cursor> # never in the parent's foreground; exit 0=done, 3=timeout (not done), 4=closed
|
|
229
|
+
$ aiterm-wait --session codex1 --cursor <event_cursor> # never in the parent's foreground; exit 0=done, 3=timeout (not done), 4=closed, 7=error (turn aborted by an API error)
|
|
230
230
|
pty_read("codex1", { agent_transcript: true }) → collect the full answer
|
|
231
231
|
```
|
|
232
232
|
|
package/dist/aiterm-wait-cli.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
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 / 6=rate_limited=harness利用上限 / 1
|
|
5
|
+
// exit≠完了: exit code は outcome を映す(0=done / 5=running=未完了 / 3=timeout=未完了 / 4=closed / 6=rate_limited=harness利用上限 / 7=error=turnがエラー終了 / 1=waiter自身のエラー)。
|
|
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";
|
|
@@ -75,6 +75,7 @@ const OUTCOME_EXIT_CODES = {
|
|
|
75
75
|
timeout: 3,
|
|
76
76
|
closed: 4,
|
|
77
77
|
rate_limited: 6,
|
|
78
|
+
error: 7,
|
|
78
79
|
};
|
|
79
80
|
export async function main(argv) {
|
|
80
81
|
const cmd = parseArgs(argv);
|
package/dist/core.js
CHANGED
|
@@ -18,7 +18,7 @@ import { isWin, SOCKDIR, tmuxCommand, sendPsmuxPayload, loadPtyBufferChunk, past
|
|
|
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
19
|
import { GROK_MODEL_DEFAULTS, realGrokHome, resolveAndValidateGrokAuth, assertGrokModelAvailable, grokEventsTranscript, latestGrokCompletion, observeGrokDone, buildGrokAgentCmd, grokLaunchNote, grokEnvTokens, grokTuiReady, 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
|
-
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, } from "./harnesses/claude.js";
|
|
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";
|
|
23
23
|
import { resolveAgentBin, resolveThroughlineBin, runThroughlineHandoffContext, isWindowsNativeExecutable, agentBinForPaneShell, resolveWinPaneShell } from "./agent-resolver.js";
|
|
24
24
|
export { AitermError } from "./errors.js";
|
|
@@ -2251,7 +2251,7 @@ function assertInitialPromptNotPendingForSend(name, force) {
|
|
|
2251
2251
|
`${agentWaitGuide(name)}完了後に再度 pty_send するか、手動介入が必要な場合だけ force:true を明示してください。`, 2);
|
|
2252
2252
|
}
|
|
2253
2253
|
// aiterm-wait の exit 契約(CLI と各所の案内文で共有する正)。exit≠完了: outcome が done の時だけ完了。
|
|
2254
|
-
export const AITERM_WAIT_OUTCOME_NOTE = `exit 0=done / 3=timeout(既定${DEFAULT_AGENT_DONE_TIMEOUT}秒・未完了) / 4=closed
|
|
2254
|
+
export const AITERM_WAIT_OUTCOME_NOTE = `exit 0=done / 3=timeout(既定${DEFAULT_AGENT_DONE_TIMEOUT}秒・未完了) / 4=closed / 7=error(harnessの記録でturnがAPIエラー等で打ち切られた。結果は無い)。receiptのoutcomeが正で、done以外は未完了`;
|
|
2255
2255
|
function quoteWindowsProcessArgument(value) {
|
|
2256
2256
|
if (value !== "" && !/[\s"]/u.test(value))
|
|
2257
2257
|
return value;
|
|
@@ -2393,7 +2393,12 @@ export async function observeAgentDone(name, o = {}) {
|
|
|
2393
2393
|
let cursor = startOffset;
|
|
2394
2394
|
let carry = "";
|
|
2395
2395
|
let malformedEvents = 0;
|
|
2396
|
-
|
|
2396
|
+
// Claudeの会話記録はこの観測開始時点の末尾から先だけを読む。過去turnのAPIエラー行を今回の
|
|
2397
|
+
// 終了と誤認しないため。dispatch直後に待機を始める親(receiptのwait_process)を前提にする。
|
|
2398
|
+
const transcriptFile = claudeSessionTranscriptPath(meta);
|
|
2399
|
+
let transcriptCursor = transcriptFile ? safeStatSize(transcriptFile) : 0;
|
|
2400
|
+
let transcriptCarry = "";
|
|
2401
|
+
const observation = (outcome, ev = null, rateLimit = null, apiError = null) => ({
|
|
2397
2402
|
schema: "aiterm.agent-wait-result.v1",
|
|
2398
2403
|
session_id: meta.aiterm_session,
|
|
2399
2404
|
launch_id: meta.launch_id,
|
|
@@ -2404,8 +2409,9 @@ export async function observeAgentDone(name, o = {}) {
|
|
|
2404
2409
|
vendor_session_id: ev?.vendor_session_id ?? meta.vendor_session_id ?? null,
|
|
2405
2410
|
turn_id: ev?.turn_id ?? null,
|
|
2406
2411
|
malformed_events: malformedEvents,
|
|
2407
|
-
at: ev?.at ?? null,
|
|
2412
|
+
at: ev?.at ?? apiError?.at ?? null,
|
|
2408
2413
|
rate_limit: rateLimit,
|
|
2414
|
+
error: apiError?.text ?? null,
|
|
2409
2415
|
});
|
|
2410
2416
|
for (;;) {
|
|
2411
2417
|
if (!fs.existsSync(metadataFile))
|
|
@@ -2431,6 +2437,24 @@ export async function observeAgentDone(name, o = {}) {
|
|
|
2431
2437
|
if (scanned.event)
|
|
2432
2438
|
return observation("done", scanned.event);
|
|
2433
2439
|
}
|
|
2440
|
+
if (transcriptFile && fs.existsSync(transcriptFile)) {
|
|
2441
|
+
const transcriptSize = safeStatSize(transcriptFile);
|
|
2442
|
+
if (transcriptSize < transcriptCursor) {
|
|
2443
|
+
transcriptCursor = 0;
|
|
2444
|
+
transcriptCarry = "";
|
|
2445
|
+
}
|
|
2446
|
+
if (transcriptSize > transcriptCursor) {
|
|
2447
|
+
transcriptCarry += readFileRange(transcriptFile, transcriptCursor, transcriptSize).toString("utf8");
|
|
2448
|
+
transcriptCursor = transcriptSize;
|
|
2449
|
+
const lines = transcriptCarry.split("\n");
|
|
2450
|
+
transcriptCarry = lines.pop() ?? "";
|
|
2451
|
+
for (const line of lines) {
|
|
2452
|
+
const apiError = claudeApiErrorFromLine(line);
|
|
2453
|
+
if (apiError)
|
|
2454
|
+
return observation("error", null, null, apiError);
|
|
2455
|
+
}
|
|
2456
|
+
}
|
|
2457
|
+
}
|
|
2434
2458
|
// timeout=0 は「待たずに一度だけ見る」照会=未完了は失敗ではなく running。
|
|
2435
2459
|
// 1秒以上を指定した待機の未完了は従来どおり timeout で、待ち方の意味は変えない。
|
|
2436
2460
|
{
|
package/dist/harnesses/claude.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// core 所有のサービス(transcript 不在エラー)は引数で注入し、
|
|
3
3
|
// 依存方向を core → harnesses → agent-shared の一方向に保つ。
|
|
4
4
|
import * as fs from "node:fs";
|
|
5
|
+
import * as os from "node:os";
|
|
5
6
|
import * as path from "node:path";
|
|
6
7
|
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
7
8
|
import { fileURLToPath } from "node:url";
|
|
@@ -216,3 +217,45 @@ export function createClaudeAgentMetadata(name, cwd, initialPrompt, launchOperat
|
|
|
216
217
|
writeAgentMetadata(meta);
|
|
217
218
|
return meta;
|
|
218
219
|
}
|
|
220
|
+
// ---------------------------------------------------------------- APIエラー終了の検知(会話記録)
|
|
221
|
+
// Claude CodeはAPIエラー(529 Overloaded等)でturnを打ち切る時、Stop hookを走らせない。
|
|
222
|
+
// 代わりに会話記録(<config dir>/projects/<cwd slug>/<session-id>.jsonl)へ
|
|
223
|
+
// `type:"assistant", isApiErrorMessage:true, apiErrorStatus:<code>` の1行を書く(実測 2026-09-03、
|
|
224
|
+
// BellTeamのチャイム席で70分の待機を生んだ529)。完了eventだけを待つと永久に running になるため、
|
|
225
|
+
// dispatch後に増えた記録行からこの印を読み、outcome=error として親へ返す。
|
|
226
|
+
export function claudeConfigDir() {
|
|
227
|
+
return process.env.CLAUDE_CONFIG_DIR ?? path.join(process.env.HOME ?? os.homedir(), ".claude");
|
|
228
|
+
}
|
|
229
|
+
// Claude Codeのproject slug: cwdの英数字以外を1文字ずつ "-" にする(実測: "/Users/kite/.throughline-x" → "-Users-kite--throughline-x")。
|
|
230
|
+
export function claudeProjectSlug(cwd) {
|
|
231
|
+
return cwd.replace(/[^a-zA-Z0-9]/g, "-");
|
|
232
|
+
}
|
|
233
|
+
export function claudeSessionTranscriptPath(meta) {
|
|
234
|
+
if (meta.kind !== "claude" || !meta.vendor_session_id)
|
|
235
|
+
return null;
|
|
236
|
+
return path.join(claudeConfigDir(), "projects", claudeProjectSlug(meta.cwd ?? process.cwd()), `${meta.vendor_session_id}.jsonl`);
|
|
237
|
+
}
|
|
238
|
+
export function claudeApiErrorFromLine(line) {
|
|
239
|
+
if (!line.trim())
|
|
240
|
+
return null;
|
|
241
|
+
let record;
|
|
242
|
+
try {
|
|
243
|
+
record = JSON.parse(line);
|
|
244
|
+
}
|
|
245
|
+
catch {
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
if (record?.type !== "assistant" || record?.isApiErrorMessage !== true)
|
|
249
|
+
return null;
|
|
250
|
+
const content = record?.message?.content;
|
|
251
|
+
const text = typeof content === "string"
|
|
252
|
+
? content
|
|
253
|
+
: Array.isArray(content)
|
|
254
|
+
? content.map((part) => (typeof part?.text === "string" ? part.text : "")).join("")
|
|
255
|
+
: "";
|
|
256
|
+
const status = record?.apiErrorStatus;
|
|
257
|
+
return {
|
|
258
|
+
text: text.trim() || (status != null ? `API Error: ${String(status)}` : "API Error"),
|
|
259
|
+
at: typeof record?.timestamp === "string" ? record.timestamp : null,
|
|
260
|
+
};
|
|
261
|
+
}
|
package/dist/harnesses/codex.js
CHANGED
package/dist/harnesses/cursor.js
CHANGED
package/dist/harnesses/grok.js
CHANGED
|
@@ -81,7 +81,7 @@ export function grokEventsTranscript(meta) {
|
|
|
81
81
|
export function grokCompletionEvent(meta, record) {
|
|
82
82
|
if ((meta.kind !== "grok" && meta.kind !== "composer") ||
|
|
83
83
|
record?.type !== "turn_ended" ||
|
|
84
|
-
(record?.outcome !== "completed" && record?.outcome !== "cancelled"))
|
|
84
|
+
(record?.outcome !== "completed" && record?.outcome !== "cancelled" && record?.outcome !== "error"))
|
|
85
85
|
return null;
|
|
86
86
|
const turnId = typeof record?.ts === "string" || typeof record?.ts === "number" ? String(record.ts) : null;
|
|
87
87
|
return {
|
|
@@ -93,7 +93,9 @@ export function grokCompletionEvent(meta, record) {
|
|
|
93
93
|
turn_id: turnId,
|
|
94
94
|
operation_id: null,
|
|
95
95
|
reason: `Grok transcript turn_ended:${record.outcome}`,
|
|
96
|
-
|
|
96
|
+
// Grok CLIはAPIエラー等でturnを打ち切る時も turn_ended を書き、outcome だけが error になる
|
|
97
|
+
// (実測: 手元の events.jsonl に outcome=error が17件)。完了と同じ境界だが結果は無い。
|
|
98
|
+
done_status: record.outcome === "error" ? "turn_error" : "turn_done",
|
|
97
99
|
stop_hook_active: false,
|
|
98
100
|
at: typeof record?.ts === "string" ? record.ts : new Date().toISOString(),
|
|
99
101
|
};
|
|
@@ -166,6 +168,7 @@ export async function observeGrokDone(meta, timeout, requestedCursor, detectRate
|
|
|
166
168
|
malformed_events: malformedEvents,
|
|
167
169
|
at: ev?.at ?? null,
|
|
168
170
|
rate_limit: rateLimit,
|
|
171
|
+
error: ev?.done_status === "turn_error" ? ev.reason : null,
|
|
169
172
|
});
|
|
170
173
|
for (;;) {
|
|
171
174
|
if (!fs.existsSync(metadataFile))
|
|
@@ -204,7 +207,7 @@ export async function observeGrokDone(meta, timeout, requestedCursor, detectRate
|
|
|
204
207
|
try {
|
|
205
208
|
const done = grokCompletionEvent(meta, JSON.parse(line));
|
|
206
209
|
if (done)
|
|
207
|
-
return observation("done", done);
|
|
210
|
+
return observation(done.done_status === "turn_error" ? "error" : "done", done);
|
|
208
211
|
}
|
|
209
212
|
catch {
|
|
210
213
|
malformedEvents++;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aiterm-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.31.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": [
|