aiterm-mcp 0.42.0 → 0.42.2

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,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.42.2] - 2026-09-28
11
+
12
+ ### 修正
13
+
14
+ - Codexの利用上限を、pane logの末尾16KBに「You've hit your usage limit」があるかで判定していた。上限が明けた後の古い知らせだけでなく、道具の出力や依頼文にこの文字が入っただけでも`rate_limited`と誤判定していた(BellTeamの記録で4件)。Codexはturnが上限で終わると`task_complete`の`error`へ本文と`codex_error_info: "usage_limit_exceeded"`を書くので、完了待ちの中でこれを見て`rate_limited`を返す。`token_count`の`used_percent`は100%のまま返事が続くことがあるので使わない。判断はrepositoryの`docs/adr/0072-claude-codex-usage-limit.md`。
15
+
16
+ ### 変更
17
+
18
+ - BellTeam向けの合間の言葉に、Codexがturnをエラーで終えた時の本文を`kind: "error"`で含める。
19
+
20
+ ## [0.42.1] - 2026-09-28
21
+
22
+ ### 修正
23
+
24
+ - Claude Codeの利用上限の知らせを、pane logの末尾16KBから探していた。上限が明けても古い知らせがlogに残るため、次のturnの最初の見回りで`wait_process`が`rate_limited`を返し、実際には動いているturnやAPIエラーで終わったturnを取り落としていた。入力欄の下に出る知らせを今の画面から探す。この知らせは次のturnが始まると消える。会話欄や依頼文に残る上限の文は数えない。
25
+
10
26
  ## [0.42.0] - 2026-09-28
11
27
 
12
28
  ### 追加
@@ -1838,7 +1854,9 @@ prototype (preserved under `prototype/python/` as the porting source and referen
1838
1854
  `ubuntu-latest` for Node 18/20/22, publishing to npm on `v*` tags with
1839
1855
  provenance.
1840
1856
 
1841
- [Unreleased]: https://github.com/kitepon/aiterm-mcp/compare/v0.42.0...HEAD
1857
+ [Unreleased]: https://github.com/kitepon/aiterm-mcp/compare/v0.42.2...HEAD
1858
+ [0.42.2]: https://github.com/kitepon/aiterm-mcp/compare/v0.42.1...v0.42.2
1859
+ [0.42.1]: https://github.com/kitepon/aiterm-mcp/compare/v0.42.0...v0.42.1
1842
1860
  [0.42.0]: https://github.com/kitepon/aiterm-mcp/compare/v0.41.3...v0.42.0
1843
1861
  [0.41.3]: https://github.com/kitepon/aiterm-mcp/compare/v0.41.2...v0.41.3
1844
1862
  [0.41.2]: https://github.com/kitepon/aiterm-mcp/compare/v0.41.1...v0.41.2
package/README.ja.md CHANGED
@@ -203,7 +203,7 @@ runtime-error store は canonical dotagents config の `collection.enabled: true
203
203
  場合だけ収集し、既定OFF、network送信は行いません。tag起点CIのnpm provenance(OIDC Trusted
204
204
  Publishing)で公開し、GitHub Release が Official MCP Registry を再登録します。
205
205
 
206
- **状態:** 開発継続中 · 現行公開版 **v0.42.0** · 動作対象は Linux · WSL2 · macOS · Windows ネイティブ · MIT · [変更履歴](CHANGELOG.md)。
206
+ **状態:** 開発継続中 · 現行公開版 **v0.42.2** · 動作対象は Linux · WSL2 · macOS · Windows ネイティブ · MIT · [変更履歴](CHANGELOG.md)。
207
207
 
208
208
  ### 更新と巻き戻し
209
209
 
@@ -259,7 +259,7 @@ ClaudeがAPIエラーや安全判定の拒否で終了した時は、Stop hook
259
259
 
260
260
  Grokの無人起動は公式`--trust`で指定された作業フォルダを信頼登録し、確認画面を完了してから初回promptを送る。この登録はGrok CLIの信頼ストアへ保存され、フォルダ内のhook・MCP・LSPにも適用される。read-only sandboxの制限は維持する。画面に残る完了済みhookの結果は実行中と判定しない。
261
261
 
262
- Grokで終了済みターンの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の上限観測には現在の画面だけを使う。
262
+ Grokで終了済みターンの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の上限観測には現在の画面だけを使う。Claude Codeの上限は現在の画面の入力欄の下に出る知らせで、Codexの上限はturnを終えた記録(`task_complete`の`codex_error_info: "usage_limit_exceeded"`)で見分ける。pane logや道具の出力に残る上限の文字では判定しない。
263
263
 
264
264
  Cursorの送信前hook(`beforeSubmitPrompt`と、互換読込するClaude Codeの`UserPromptSubmit`)がpromptを拒否すると、Cursorはpromptを捨て、turnも完了も起きない。Aitermはこの拒否の表示を見分け、起動時promptは`initial_prompt=failed`、`pty_send`は成功receiptを返さず、どちらも`USER_HOOK_BLOCKED`とhookの出力を返す。確認時間(3秒)より後の拒否は、完了待ちが`outcome=error`(`aiterm-wait`はexit 7)で返す。
265
265
 
package/README.md CHANGED
@@ -217,7 +217,7 @@ collection is off by default and performs no network I/O. It ships via
217
217
  tag-triggered CI with npm provenance (OIDC Trusted Publishing); the GitHub
218
218
  Release re-registers the Official MCP Registry entry.
219
219
 
220
- **Status:** actively maintained · current public release **v0.42.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).
220
+ **Status:** actively maintained · current public release **v0.42.2** · 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).
221
221
 
222
222
  ### Update and rollback
223
223
 
@@ -280,7 +280,7 @@ The same primitive hosts another agent's TUI. `agent_launch` starts a selected e
280
280
 
281
281
  Grokの無人起動は公式`--trust`で指定された作業フォルダを信頼登録し、確認画面を完了してから初回promptを送る。この登録はGrok CLIの信頼ストアへ保存され、フォルダ内のhook・MCP・LSPにも適用される。read-only sandboxの制限は維持する。画面に残る完了済みhookの結果は実行中と判定しない。
282
282
 
283
- Grokで終了済みターンの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の上限観測には現在の画面だけを使う。
283
+ Grokで終了済みターンの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の上限観測には現在の画面だけを使う。Claude Codeの上限は現在の画面の入力欄の下に出る知らせで、Codexの上限はturnを終えた記録(`task_complete`の`codex_error_info: "usage_limit_exceeded"`)で見分ける。pane logや道具の出力に残る上限の文字では判定しない。
284
284
 
285
285
  When a Cursor pre-submit hook (`beforeSubmitPrompt`, or a Claude Code `UserPromptSubmit` hook that Cursor loads for compatibility) rejects the prompt, Cursor drops it and no turn or completion follows. Aiterm recognizes the rejection: an initial prompt returns `initial_prompt=failed`, and `pty_send` returns an error instead of a success receipt, both with `USER_HOOK_BLOCKED` and the hook's output. A rejection that comes after the 3-second start check is reported by the completion wait as `outcome=error` (`aiterm-wait` exit 7).
286
286
 
package/dist/core.js CHANGED
@@ -20,7 +20,7 @@ import { isWin, SOCKDIR, tmuxCommand, sendPsmuxPayload, loadPtyBufferChunk, past
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
21
  import { 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, codexRateLimitModelSwitchDialog, codexApprovalDialog, codexStartupAction, CODEX_COMPOSER_MARKER_RE, codexModelChoice, codexEffortChoice, codexMoreReasoningChoice, codexTranscriptText, createCodexAgentMetadata, } from "./harnesses/codex.js";
23
- import { OPERATION_ID_RE, CLAUDE_RESULT_MAX_BYTES, CLAUDE_EFFORTS, agentManagedClaudeSettingsPath, agentClaudeResultPath, agentClaudeOperationPath, agentClaudeApprovalReceiptPath, agentClaudeDispatchReceiptPath, validateOperationId, readClaudeResultText, assertClaudeAuthenticationReady, buildClaudeAgentCmd, claudeLaunchNote, claudeTuiReady, claudePaneObservation, claudeStartupAction, claudeLoginMethodMenu, CLAUDE_COMPOSER_MARKER_RE, createClaudeAgentMetadata, claudeSessionTranscriptPath, claudeApiErrorFromLine, claudeApiErrorAfter, } from "./harnesses/claude.js";
23
+ import { OPERATION_ID_RE, CLAUDE_RESULT_MAX_BYTES, CLAUDE_EFFORTS, agentManagedClaudeSettingsPath, agentClaudeResultPath, agentClaudeOperationPath, agentClaudeApprovalReceiptPath, agentClaudeDispatchReceiptPath, validateOperationId, readClaudeResultText, assertClaudeAuthenticationReady, buildClaudeAgentCmd, claudeLaunchNote, claudeTuiReady, claudePaneObservation, claudeUsageLimit, claudeStartupAction, claudeLoginMethodMenu, CLAUDE_COMPOSER_MARKER_RE, createClaudeAgentMetadata, claudeSessionTranscriptPath, claudeApiErrorFromLine, claudeApiErrorAfter, } from "./harnesses/claude.js";
24
24
  import { bindCursorTranscriptSession, cursorTurnBoundary, latestCursorCompletion, observeCursorDone, cursorTranscriptText, assertCursorAuthenticationReady, assertCursorModelAvailable, buildCursorAgentCmd, cursorAgentArgv, cursorPwshLaunchLine, cursorPromptWithLineage, createCursorAgentMetadata, cursorLaunchNote, cursorEffortNavigation, cursorTuiReady, cursorPaneObservation, cursorPromptHooksRunning, cursorUsageLimit, CURSOR_SUBMIT_SEQUENCE, CURSOR_COMPOSER_CONTENT_MARKER_RE, validateCursorModelEffort, } from "./harnesses/cursor.js";
25
25
  import { readInterimWords, recordInterimBoundary } from "./interim-words.js";
26
26
  import { resolveAgentBin, resolveThroughlineBin, runThroughlineHandoffContext, isWindowsNativeExecutable, agentBinForPaneShell, resolveWinPaneShell } from "./agent-resolver.js";
@@ -2641,49 +2641,20 @@ export function agentWaitGuide(session) {
2641
2641
  const cmd = `aiterm-wait --session ${session ?? "<session_id>"} --cursor 0`;
2642
2642
  return `完了通知は ${agentWaitLaunchForm(cmd)} で受ける(親はここで待たない・polling 不要)。receipt の outcome=done を確認してから再取得する。`;
2643
2643
  }
2644
- // harness別の利用上限観測。Grokは現在の質問カード、Cursorは現在の画面、他harnessは既存logを使う。
2645
- // 出典(2026-08-22): grok は live 実バナーで検証、codex/claude はインストール済み実バイナリの
2646
- // 埋込文字列から抽出(codex: "You've hit your usage limit for" / claude: "Usage limit reached ·
2647
- // continuing automatically when it resets"。Claude Code はリセット時に自動継続する設計なので、
2648
- // この報告は「今は上限で止まっている」の観測であり恒久停止を意味しない)。
2649
- const AGENT_RATE_LIMIT_PATTERNS = {
2650
- codex: [/You'?ve hit your usage limit/i],
2651
- claude: [/Usage limit reached/i],
2652
- };
2653
- const AGENT_RATE_LIMIT_SCAN_BYTES = 16 * 1024;
2654
- // pane log の末尾から上限バナーを探す。読めない・無い・対象 harness でないは全て null(誤検知より取りこぼし側へ倒す)。
2644
+ // harness別の利用上限観測。Grokは現在の質問カード、CursorとClaudeは現在の画面を見る。
2645
+ // Codexはturnの記録(task_completeのcodex_error_info)で完了待ちの中で見分けるので、ここでは見ない。
2646
+ // Claude Code はリセット時に自動継続する設計なので、この報告は「今は上限で止まっている」の観測であり
2647
+ // 恒久停止を意味しない。
2655
2648
  export function detectAgentRateLimit(kind, aitermSession) {
2656
2649
  if (kind === "grok") {
2657
2650
  return grokRateLimitDialog(captureScreen(aitermSession, 0))?.message ?? null;
2658
2651
  }
2659
2652
  if (kind === "cursor")
2660
2653
  return cursorUsageLimit(captureScreen(aitermSession, 0))?.message ?? null;
2661
- const patterns = AGENT_RATE_LIMIT_PATTERNS[kind];
2662
- if (!patterns)
2663
- return null;
2664
- const file = logpath(aitermSession);
2665
- let size;
2666
- try {
2667
- size = fs.statSync(file).size;
2668
- }
2669
- catch {
2670
- return null;
2671
- }
2672
- if (size === 0)
2673
- return null;
2674
- let text;
2675
- try {
2676
- text = readFileRange(file, Math.max(0, size - AGENT_RATE_LIMIT_SCAN_BYTES), size).toString("utf8");
2677
- }
2678
- catch {
2679
- return null;
2680
- }
2681
- const clean = text.replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "");
2682
- for (const re of patterns) {
2683
- const match = clean.match(re);
2684
- if (match)
2685
- return match[0];
2686
- }
2654
+ // Claudeの知らせはpane logに上限が明けた後も残り、次のturnの最初の見回りで誤って拾っていた(2026-09-28)。
2655
+ // 足元の知らせは次のturnで消えるので、今の画面だけを見る。
2656
+ if (kind === "claude")
2657
+ return claudeUsageLimit(captureScreen(aitermSession, 0))?.message ?? null;
2687
2658
  return null;
2688
2659
  }
2689
2660
  // 外部waiterプロセス用の純リーダー観測。lock・PTY・metadata書込・dispatch状態には一切触れない。
@@ -2700,7 +2671,7 @@ export async function observeAgentDone(name, o = {}) {
2700
2671
  }
2701
2672
  const timeout = o.timeout ?? DEFAULT_AGENT_DONE_TIMEOUT;
2702
2673
  if (meta.kind === "codex" && meta.completion_route === "codex_transcript") {
2703
- return observeCodexDone(meta, timeout, o.cursor, detectAgentRateLimit, o.signal);
2674
+ return observeCodexDone(meta, timeout, o.cursor, o.signal);
2704
2675
  }
2705
2676
  if (meta.kind === "cursor" && meta.completion_route === "cursor_transcript") {
2706
2677
  return observeCursorDone(meta, timeout, o.cursor, detectAgentRateLimit, (session) => captureScreen(session, 0), o.signal);
@@ -206,6 +206,30 @@ export function claudePaneObservation(screen) {
206
206
  return { state: "blocked", reason: "startup_dialog" };
207
207
  return { state: "unknown", reason: "unrecognized_screen" };
208
208
  }
209
+ // 利用上限で止まっているClaude Codeは、入力欄の下の枠線と権限表示の間へ知らせを出す(BellTeamコンテナの実画面 2026-09-28)。
210
+ // ❯
211
+ // ────────
212
+ // ⚠ Usage limit reached · continuing automatically at 4:10pm · esc to cancel
213
+ // ⏵⏵ bypass permissions on (shift+tab to cycle)
214
+ // 次のturnが始まると足元は描き直されて知らせは消える。会話欄の「● Usage limit reached」や依頼文の引用は
215
+ // 上限が明けても画面やpane logに残るので、今の状態として数えない。
216
+ export function claudeUsageLimit(screen) {
217
+ const lines = screen.replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "").split("\n");
218
+ let composer = lines.length - 1;
219
+ while (composer >= 0 && !CLAUDE_COMPOSER_MARKER_RE.test(lines[composer]))
220
+ composer--;
221
+ if (composer < 0)
222
+ return null;
223
+ const rule = lines.findIndex((line, i) => i > composer && /^\s*─{8,}\s*$/u.test(line));
224
+ if (rule < 0)
225
+ return null;
226
+ for (const line of lines.slice(rule + 1)) {
227
+ const hit = /^\s*(?:⚠\s*)?(Usage limit reached\b.*?)\s*(?:·\s*esc to cancel\s*)?$/iu.exec(line);
228
+ if (hit)
229
+ return { message: hit[1] };
230
+ }
231
+ return null;
232
+ }
209
233
  // Claude Code 2.1.282 は選択肢へ「1.」などの番号を付ける。番号の有無のどちらも受ける。
210
234
  export function claudeLoginMethodMenu(screen) {
211
235
  const tail = screen.split("\n").slice(-32).join("\n");
@@ -231,6 +231,31 @@ export function codexCompletionEvent(meta, harnessSessionId, record) {
231
231
  at: typeof record.timestamp === "string" ? record.timestamp : new Date().toISOString(),
232
232
  };
233
233
  }
234
+ // Codexはturnがエラーで終わると、task_completeへ本文と種類を書く(macbookの実記録 2026-08-07、codex 0.158にも同じ欄)。
235
+ // {"type":"task_complete","last_agent_message":null,
236
+ // "error":{"message":"You've hit your usage limit. Visit … or try again at Aug 8th, 2026 12:35 PM.","codex_error_info":"usage_limit_exceeded"}}
237
+ // 種類は値の無いものが文字列、値を持つものが{"種類":{…}}になる。
238
+ export function codexTurnError(record) {
239
+ if (record?.type !== "event_msg" || record?.payload?.type !== "task_complete")
240
+ return null;
241
+ const error = record.payload.error;
242
+ if (error === null || typeof error !== "object")
243
+ return null;
244
+ const raw = error.codex_error_info;
245
+ const info = typeof raw === "string" ? raw
246
+ : raw !== null && typeof raw === "object" && !Array.isArray(raw) ? Object.keys(raw)[0] ?? null : null;
247
+ const message = typeof error.message === "string" ? error.message.trim() : "";
248
+ if (!message && !info)
249
+ return null;
250
+ return { message: message || info, info };
251
+ }
252
+ // 利用上限はtask_completeのcodex_error_infoで見分ける。token_countのused_percentは100%のまま
253
+ // 返事が続くことがある(macbookの実記録で7千件以上)ので上限の印にしない。
254
+ // rate_limit_exceededは短い間の混雑で、Codexが自分で再試行するので上限に数えない。
255
+ export function codexUsageLimit(record) {
256
+ const error = codexTurnError(record);
257
+ return error?.info === "usage_limit_exceeded" ? error.message : null;
258
+ }
234
259
  export function latestCodexCompletion(meta, readTranscriptLines) {
235
260
  const transcript = codexRootTranscript(meta);
236
261
  if (!transcript)
@@ -249,7 +274,7 @@ export function latestCodexCompletion(meta, readTranscriptLines) {
249
274
  }
250
275
  return latest;
251
276
  }
252
- export async function observeCodexDone(meta, timeout, requestedCursor, detectRateLimit, signal) {
277
+ export async function observeCodexDone(meta, timeout, requestedCursor, signal) {
253
278
  const metadataFile = agentMetadataPath(meta.aiterm_session, meta.launch_id);
254
279
  let transcript = codexRootTranscript(meta);
255
280
  const startOffset = requestedCursor ?? (transcript ? safeStatSize(transcript) : 0);
@@ -312,9 +337,14 @@ export async function observeCodexDone(meta, timeout, requestedCursor, detectRat
312
337
  continue;
313
338
  }
314
339
  try {
315
- const done = codexCompletionEvent(meta, harnessSessionId, JSON.parse(line));
316
- if (done)
317
- return observation("done", done);
340
+ const record = JSON.parse(line);
341
+ const done = codexCompletionEvent(meta, harnessSessionId, record);
342
+ if (done) {
343
+ // 上限の知らせはこのturnの終わりにだけ書かれる。画面やpane logの文字は、上限が明けた後も
344
+ // 残り、道具の出力や依頼文にも現れるので見ない(2026-09-28)。
345
+ const limited = codexUsageLimit(record);
346
+ return limited ? observation("rate_limited", done, limited) : observation("done", done);
347
+ }
318
348
  }
319
349
  catch {
320
350
  malformedEvents++;
@@ -322,11 +352,6 @@ export async function observeCodexDone(meta, timeout, requestedCursor, detectRat
322
352
  }
323
353
  }
324
354
  }
325
- {
326
- const limited = detectRateLimit(meta.kind, meta.aiterm_session);
327
- if (limited)
328
- return observation("rate_limited", null, limited);
329
- }
330
355
  if (performance.now() >= deadline)
331
356
  return observation(timeout === 0 ? "running" : "timeout");
332
357
  await sleep(AGENT_DONE_POLL_MS);
@@ -6,7 +6,7 @@
6
6
  // 最後の回答と思考は含めない。APIエラーやsession limitの知らせは、記録に書かれていれば含める。
7
7
  // ハーネスごとの見分け方(2026-09-28 記録で確認):
8
8
  // Claude Code: text block で message.stop_reason="tool_use"。isApiErrorMessage の行は知らせ。
9
- // Codex: assistant message の phase="commentary"。
9
+ // Codex: assistant message の phase="commentary"。task_complete の error はturnを終えた知らせ。
10
10
  // Grok: content が空でなく tool_calls を持つ assistant。
11
11
  // Cursor: assistant の text のあとに tool_use が続くもの(Cursorは道具が終わってから行を書くので遅れて届く)。
12
12
  import * as fs from "node:fs";
@@ -14,7 +14,7 @@ import * as path from "node:path";
14
14
  import { AitermError } from "./errors.js";
15
15
  import { agentHarness, agentsDir, assertSessionName, LAUNCH_ID_RE, readFileRange, safeStatSize, writeJson0600, } from "./agent-shared.js";
16
16
  import { claudeSessionTranscriptPath } from "./harnesses/claude.js";
17
- import { codexRootTranscript } from "./harnesses/codex.js";
17
+ import { codexRootTranscript, codexTurnError } from "./harnesses/codex.js";
18
18
  import { grokSessionDirectory } from "./harnesses/grok.js";
19
19
  import { cursorTranscript } from "./harnesses/cursor.js";
20
20
  /** BellTeamが要求の_metaへ入れる呼び手の印。値が "BellTeam" の時だけ合間の言葉を返す。 */
@@ -86,6 +86,17 @@ export function codexInterimWords(lines) {
86
86
  for (const line of lines) {
87
87
  const record = parse(line);
88
88
  const payload = record?.payload;
89
+ // エラーで終わったturnは、task_completeへ本文を書く(利用上限もここ)。
90
+ const error = codexTurnError(record);
91
+ if (error) {
92
+ words.push({
93
+ text: error.message,
94
+ kind: "error",
95
+ at: typeof record.timestamp === "string" ? record.timestamp : null,
96
+ turn_id: typeof payload.turn_id === "string" && payload.turn_id ? payload.turn_id : null,
97
+ });
98
+ continue;
99
+ }
89
100
  if (record?.type !== "response_item" || payload?.type !== "message" || payload?.role !== "assistant"
90
101
  || payload?.phase !== "commentary" || !Array.isArray(payload?.content))
91
102
  continue;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aiterm-mcp",
3
- "version": "0.42.0",
3
+ "version": "0.42.2",
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": [