aiterm-mcp 0.30.0 → 0.31.1
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 +4 -2
- package/README.md +4 -2
- package/dist/aiterm-wait-cli.js +2 -1
- package/dist/core.js +31 -5
- 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 +17 -3
- 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.31.1] - 2026-09-06
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- Grok/Composerがread-only sandboxの適用に失敗して終了した時、入力受付を待ち続けて再送を案内していた。初回promptと通常dispatchの入力受付確認で`GROK_SANDBOX_STARTUP_FAILED`を即時に返し、CLIの原因と未送信を明示する。設定とsandboxは変更しない。
|
|
15
|
+
|
|
16
|
+
## [0.31.0] - 2026-09-04
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- 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`にエラー本文)で返す。
|
|
21
|
+
- Grokの`turn_ended outcome=error`を完了境界として扱わず待ち続けていた。同じくoutcome `error`で返す。Cursorは`turn_ended`のstatusを問わず既に終了を返していたため変更なし。Codexはエラー終了の記録形が実測できておらず未対応。
|
|
22
|
+
|
|
10
23
|
## [0.30.0] - 2026-09-04
|
|
11
24
|
|
|
12
25
|
### Added
|
|
@@ -1546,7 +1559,9 @@ prototype (preserved under `prototype/python/` as the porting source and referen
|
|
|
1546
1559
|
`ubuntu-latest` for Node 18/20/22, publishing to npm on `v*` tags with
|
|
1547
1560
|
provenance.
|
|
1548
1561
|
|
|
1549
|
-
[Unreleased]: https://github.com/kitepon/aiterm-mcp/compare/v0.
|
|
1562
|
+
[Unreleased]: https://github.com/kitepon/aiterm-mcp/compare/v0.31.1...HEAD
|
|
1563
|
+
[0.31.1]: https://github.com/kitepon/aiterm-mcp/compare/v0.31.0...v0.31.1
|
|
1564
|
+
[0.31.0]: https://github.com/kitepon/aiterm-mcp/compare/v0.30.0...v0.31.0
|
|
1550
1565
|
[0.30.0]: https://github.com/kitepon/aiterm-mcp/compare/v0.29.31...v0.30.0
|
|
1551
1566
|
[0.29.31]: https://github.com/kitepon/aiterm-mcp/compare/v0.29.30...v0.29.31
|
|
1552
1567
|
[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.1** · 動作対象は Linux · WSL2 · macOS · Windows ネイティブ · MIT · [変更履歴](CHANGELOG.md)。
|
|
157
157
|
|
|
158
158
|
### 更新と巻き戻し
|
|
159
159
|
|
|
@@ -193,6 +193,8 @@ pty_read(id, { wait: true }) → 削減済みの出力を読む(完了
|
|
|
193
193
|
|
|
194
194
|
`agent_launch`は任意の`write_scope`も受ける。Codex/Grokのread-onlyは`--sandbox read-only`、Cursorは公式`--mode ask`で実効化する。path説明は同等CLI引数がないためdeclaration-only。
|
|
195
195
|
|
|
196
|
+
Grok/Composerがread-only sandboxの適用を拒否した場合、prompt送信時に`GROK_SANDBOX_STARTUP_FAILED`とCLIの原因を返す。例えばhookのパスにシンボリックリンクがあるとGrok CLIは起動を拒否する。設定の管理元で原因を修正し、対象sessionを`pty_close`して起動し直す。Aitermはsandboxを解除したりhookをコピーしたりしない。
|
|
197
|
+
|
|
196
198
|
```text
|
|
197
199
|
agent_launch({ harness: "codex-cli", session_name: "codex1", cwd: "/repo",
|
|
198
200
|
prompt: "port test/legacy.py to vitest",
|
|
@@ -201,7 +203,7 @@ agent_launch({ harness: "codex-cli", session_name: "codex1", cwd: "/repo",
|
|
|
201
203
|
→ { session_id: "codex1", … } # Codex が永続端末で稼働開始
|
|
202
204
|
pty_read("codex1", { screen: true }) → 何をしているか読む(トークン削減)
|
|
203
205
|
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)
|
|
206
|
+
$ aiterm-wait --session codex1 --cursor <event_cursor> # exit 0=done / 3=timeout(未完了) / 4=closed / 7=error(APIエラー等でturn打ち切り)。回収は pty_read(agent_transcript:true)
|
|
205
207
|
→ 操舵し、Codex の次の入力境界で返る
|
|
206
208
|
```
|
|
207
209
|
|
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.1** · 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
|
|
|
@@ -215,6 +215,8 @@ The human-readable launch text is accompanied by an `aiterm.agent-launch-result.
|
|
|
215
215
|
|
|
216
216
|
`agent_launch` accepts an optional `write_scope`: either `"read-only"` or a human-readable description of writable paths. Codex/Grok use `--sandbox read-only`; Cursor uses its official read-only `--mode ask`. A path description remains declaration-only because these CLI launch surfaces provide no equivalent path allowlist flag.
|
|
217
217
|
|
|
218
|
+
Grok/Composerがread-only sandboxの適用を拒否した場合、prompt送信時に`GROK_SANDBOX_STARTUP_FAILED`とCLIの原因を返す。hookパスのシンボリックリンクなど、CLIが示した原因を設定の管理元で修正し、対象sessionを`pty_close`して起動し直す。Aitermはsandboxを解除したりhookをコピーしたりしない。
|
|
219
|
+
|
|
218
220
|
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.
|
|
219
221
|
|
|
220
222
|
```text
|
|
@@ -226,7 +228,7 @@ agent_launch({ harness: "codex-cli", session_name: "codex1", cwd: "/repo",
|
|
|
226
228
|
pty_read("codex1", { screen: true }) → read what it's doing (token-reduced)
|
|
227
229
|
pty_send("codex1", "also fix the imports it broke")
|
|
228
230
|
→ 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
|
|
231
|
+
$ 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
232
|
pty_read("codex1", { agent_transcript: true }) → collect the full answer
|
|
231
233
|
```
|
|
232
234
|
|
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
|
@@ -16,9 +16,9 @@ import * as rtk from "./rtk.js";
|
|
|
16
16
|
import { AitermError, telemetryOwnedFailure, ownTelemetryFailure } from "./errors.js";
|
|
17
17
|
import { isWin, SOCKDIR, tmuxCommand, sendPsmuxPayload, loadPtyBufferChunk, pasteBufferBaseArgs, TMUX_EMPTY_CONFIG, attachCommand, normalizePaneCommand, appendMarkSentinel, settlePaneLog, paneCwdArgument, } from "./tmux-runtime.js";
|
|
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
|
-
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";
|
|
19
|
+
import { GROK_MODEL_DEFAULTS, realGrokHome, resolveAndValidateGrokAuth, assertGrokModelAvailable, grokEventsTranscript, latestGrokCompletion, observeGrokDone, buildGrokAgentCmd, grokLaunchNote, grokEnvTokens, grokTuiReady, assertGrokSandboxNotRejected, 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
|
{
|
|
@@ -2518,6 +2542,8 @@ async function waitAgentTuiReadyImpl(kind, sample, sleepFn, opts = {}) {
|
|
|
2518
2542
|
for (;;) {
|
|
2519
2543
|
lastScreen = sample();
|
|
2520
2544
|
samples++;
|
|
2545
|
+
if (kind === "grok" || kind === "composer")
|
|
2546
|
+
assertGrokSandboxNotRejected(lastScreen);
|
|
2521
2547
|
if (isAgentTuiIdleReady(kind, lastScreen)) {
|
|
2522
2548
|
readyStreak++;
|
|
2523
2549
|
if (readyStreak >= stableSamples)
|
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++;
|
|
@@ -259,6 +262,17 @@ export function grokEnvTokens(meta) {
|
|
|
259
262
|
"GROK_DISABLE_AUTOUPDATER=1",
|
|
260
263
|
];
|
|
261
264
|
}
|
|
265
|
+
export function assertGrokSandboxNotRejected(screen) {
|
|
266
|
+
// Grok 1.0.13はsandboxを適用できないと入力欄を作らず終了する。
|
|
267
|
+
// CLIが確定した拒否だけを拾い、警告単独や入力欄の引用では判定しない。
|
|
268
|
+
const failure = screen.match(/^[ \t]*error: could not apply the 'read-only' sandbox profile;[^\n]*/m);
|
|
269
|
+
if (!failure)
|
|
270
|
+
return;
|
|
271
|
+
const warning = screen.match(/^[ \t]*warning: sandbox could not be applied:[\s\S]*?(?=\n[ \t]*error:)/m);
|
|
272
|
+
throw new AitermError("GROK_SANDBOX_STARTUP_FAILED: Grok CLIがread-only sandboxを適用できず起動を拒否しました。文字列は送信していません。\n" +
|
|
273
|
+
`${warning?.[0] ?? ""}\n${failure[0]}\n` +
|
|
274
|
+
"CLIが示した設定の問題を、その設定の管理元で修正してください。修正後はpty_closeで対象sessionを閉じ、agent_launchで起動し直してください。", 2);
|
|
275
|
+
}
|
|
262
276
|
export function grokTuiReady(screen) {
|
|
263
277
|
// Grok Build 0.2.117 は起動完了後に製品名を消し、model footerだけを残す。
|
|
264
278
|
// Composerも同じfrontendでmodel名だけが異なるため、両方をharness UIの根拠にする。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aiterm-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.31.1",
|
|
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": [
|