aiterm-mcp 0.55.2 → 0.55.4

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,37 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.55.4] - 2026-10-06
11
+
12
+ ### 変更
13
+
14
+ - 止まっているCodex・Grok・Cursorの席へ`pty_send`で新しいturnを送る時、起動直後のための5秒の確かめを待たない
15
+ (ADR 0097)。受け付けを返すまで毎回5.4〜5.7秒かかっていた。画面を500msおきに11回続けて読む入力受付の確かめ
16
+ (ADR 0014)を、2通目以降の文にも毎回通していた。連携元の本番で、Codexの席への新しいturn17通が
17
+ 5,686〜5,858msに揃っているのを見た。
18
+ - 席のharnessが自分の記録(Codexのrollout、Grokの`events.jsonl`、Cursorのtranscript)で「この起動でturnを1つ終えていて、
19
+ 次のturnを始めていない」と示している時は、100msおきに2回続けて画面が入力待ちなら送る。
20
+ - 実物の席での受け付けまでの時間: Codex 5.6秒→0.66秒、Grok 5.6秒→0.65秒、Cursor 5.4秒→0.41秒(Linux)。
21
+ Macは5.8秒→0.75秒、Windowsは6.5秒→1.4秒(Codex)。
22
+ - 起動直後の最初の文と、記録の上でturnが動いている間に届いた文は、今までどおり11回の確かめを通る。
23
+ - 起動時prompt、`agent_configure`の後、利用上限のmodalを閉じた直後の確かめは変わらない。Claudeの席は元から
24
+ この確かめを通らない。
25
+ - 道具の返りの形と断りの文面は変わらない。
26
+
27
+ ## [0.55.3] - 2026-10-06
28
+
29
+ ### 修正
30
+
31
+ - Claude Code親からの呼び出しを誤りで返す時、その呼び出しの置き場(Aiterm stateの`claude-parent-hooks/<tool_use_id>/`)を
32
+ 返す前に消す(ADR 0096、`aiterm-steer-delivery` 0.2.3)。Claude Codeは誤りの返りで`PostToolUse`のhookを走らせないので、
33
+ `request.json`だけの置き場が、1日後の見回りまで残っていた。連携元の本番で、`harness: "claude-code"`へ`write_scope`を
34
+ 付けて断られた`agent_launch`の置き場が残っているのを見た。
35
+ - 消すのは、配送を結んでいない置き場だけ。配送を結んだ後に誤りで返した呼び出しの置き場は、今までどおり残す。
36
+ - 入力の検査で断る返りと、handlerの例外から作られる返りでも消す。
37
+ - Claude Codeが自分で断った呼び出し(引数が道具の定義に合わない)と、Claude側で打ち切られた呼び出しは、Aitermへ
38
+ 返りが渡らないので、今までどおり1日後の見回りが消す。
39
+ - 道具の返り、配送の記録、hookの登録は変わらない。`aiterm-setup`の流し直しは要らない。
40
+
10
41
  ## [0.55.2] - 2026-10-06
11
42
 
12
43
  ### 修正
@@ -2283,7 +2314,9 @@ prototype (preserved under `prototype/python/` as the porting source and referen
2283
2314
  `ubuntu-latest` for Node 18/20/22, publishing to npm on `v*` tags with
2284
2315
  provenance.
2285
2316
 
2286
- [Unreleased]: https://github.com/kitepon/aiterm-mcp/compare/v0.55.2...HEAD
2317
+ [Unreleased]: https://github.com/kitepon/aiterm-mcp/compare/v0.55.4...HEAD
2318
+ [0.55.4]: https://github.com/kitepon/aiterm-mcp/compare/v0.55.3...v0.55.4
2319
+ [0.55.3]: https://github.com/kitepon/aiterm-mcp/compare/v0.55.2...v0.55.3
2287
2320
  [0.55.2]: https://github.com/kitepon/aiterm-mcp/compare/v0.55.1...v0.55.2
2288
2321
  [0.55.1]: https://github.com/kitepon/aiterm-mcp/compare/v0.55.0...v0.55.1
2289
2322
  [0.55.0]: https://github.com/kitepon/aiterm-mcp/compare/v0.54.0...v0.55.0
package/README.ja.md CHANGED
@@ -209,7 +209,7 @@ runtime-error store は canonical dotagents config の `collection.enabled: true
209
209
  場合だけ収集し、既定OFF、network送信は行いません。tag起点CIのnpm provenance(OIDC Trusted
210
210
  Publishing)で公開し、GitHub Release が Official MCP Registry を再登録します。
211
211
 
212
- **状態:** 開発継続中 · 現行公開版 **v0.55.2** · 動作対象は Linux · WSL2 · macOS · Windows ネイティブ · MIT · [変更履歴](CHANGELOG.md)。
212
+ **状態:** 開発継続中 · 現行公開版 **v0.55.4** · 動作対象は Linux · WSL2 · macOS · Windows ネイティブ · MIT · [変更履歴](CHANGELOG.md)。
213
213
 
214
214
  ### 更新と巻き戻し
215
215
 
package/README.md CHANGED
@@ -223,7 +223,7 @@ collection is off by default and performs no network I/O. It ships via
223
223
  tag-triggered CI with npm provenance (OIDC Trusted Publishing); the GitHub
224
224
  Release re-registers the Official MCP Registry entry.
225
225
 
226
- **Status:** actively maintained · current public release **v0.55.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).
226
+ **Status:** actively maintained · current public release **v0.55.4** · 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).
227
227
 
228
228
  ### Update and rollback
229
229
 
@@ -16,6 +16,23 @@ export function safeStatSize(p) {
16
16
  return 0;
17
17
  }
18
18
  }
19
+ /**
20
+ * 記録の末尾から、行を新しい順に返す。書いている途中の行(改行で終わっていない末尾)がある時はnullを返す。
21
+ * 読む範囲の先頭で切れた行は捨てる。
22
+ */
23
+ export function readTailLinesNewestFirst(file, maxBytes) {
24
+ const size = safeStatSize(file);
25
+ if (size === 0)
26
+ return [];
27
+ const from = Math.max(0, size - maxBytes);
28
+ const text = readFileRange(file, from, size).toString("utf8");
29
+ if (!text.endsWith("\n"))
30
+ return null;
31
+ const lines = text.split("\n");
32
+ if (from > 0)
33
+ lines.shift();
34
+ return lines.filter(line => line.trim() !== "").reverse();
35
+ }
19
36
  export function readFileRange(p, from, to) {
20
37
  const len = Math.max(0, to - from);
21
38
  if (len === 0)
@@ -103,6 +120,8 @@ export function pollGate(intervalMs, now = () => performance.now()) {
103
120
  }
104
121
  export const AGENT_EVENT_MAX_BYTES = 1024 * 1024;
105
122
  export const AGENT_EVENT_TAIL_BYTES = 64 * 1024;
123
+ // turnの境界(開始・終わり)を、harnessの記録の末尾から探す時に読む大きさ。終わった席では境界は末尾の近くにある。
124
+ export const AGENT_TURN_BOUNDARY_TAIL_BYTES = 1024 * 1024;
106
125
  export const CODEX_TRANSCRIPT_INCREMENT_MAX_BYTES = 16 * 1024 * 1024;
107
126
  export const GROK_TRANSCRIPT_INCREMENT_MAX_BYTES = 16 * 1024 * 1024;
108
127
  // session 名はファイルパス(logpath 等)と pipe-pane の /bin/sh 文字列へ流れる。英数 _ - のみ・64字に
@@ -17,6 +17,10 @@ export function claudeParentFromRequest(clientName, metadata, root = defaultRoot
17
17
  }
18
18
  export function verifyClaudeParent(parent) { steer.verifyClaudeParent(AITERM_PROFILE, parent); }
19
19
  export function bindClaudeParentDelivery(parent, deliveryId) { steer.bindClaudeParentDelivery(AITERM_PROFILE, parent, deliveryId); }
20
+ /** 誤りで返す呼び出しの置き場を消す。配送を結んだ置き場は消さない。 */
21
+ export function discardClaudeHookRequest(clientName, metadata, root = defaultRoot()) {
22
+ return steer.discardClaudeHookRequest(clientName, metadata, root);
23
+ }
20
24
  /** SessionEndはその時点の依頼だけを終了する。同じ会話をresumeした新規依頼は別requestになる。 */
21
25
  export function closeClaudeParentSession(input, root = defaultRoot()) { steer.closeClaudeParentSession(input, root); }
22
26
  export function submitClaudeParentAnswer(parent, deliveryId, text) {
package/dist/core.js CHANGED
@@ -21,8 +21,8 @@ import { AitermError, telemetryOwnedFailure, ownTelemetryFailure } from "./error
21
21
  import { isWin, SOCKDIR, tmuxCommand, sendPsmuxPayload, loadPtyBufferChunk, pasteBufferBaseArgs, tmuxNewSession, registeredSessionEnvironment, attachCommand, normalizePaneCommand, atomicShellMultiline, appendMarkSentinel, markShellCommand, settlePaneLog, paneCwdArgument, sessionEnvironmentLaunch, } from "./tmux-runtime.js";
22
22
  import { sleep, currentUid, runtimeStateBase, safeStatSize, readFileRange, writeJson0600, createEmpty0600, shq, LAUNCH_ID_RE, AGENT_DONE_POLL_MS, AGENT_RATE_LIMIT_POLL_MS, pollGate, AGENT_EVENT_MAX_BYTES, assertSessionName, agentsDir, agentEventPath, agentMetadataPath, writeAgentMetadata, AGENT_EVENT_TAIL_BYTES, agentLabel, agentHarness, subagentInstruction, agentLineageFields, } from "./agent-shared.js";
23
23
  import { catalogUnavailable } from "./model-catalog.js";
24
- import { grokAuthPlan, grokAuthStatus, grokAuthPane, realGrokHome, resolveAndValidateGrokAuth, assertGrokModelAvailable, grokModelChoices, grokEventsTranscript, latestGrokCompletion, observeGrokDone, buildGrokAgentCmd, grokLaunchNote, grokEnvTokens, grokTuiReady, grokTuiBusy, grokPaneObservation, grokRateLimitDialog, grokStartupAction, grokLaunchBlockingDialog, assertGrokSandboxNotRejected, GROK_COMPOSER_MARKER_RE, grokFooterHasConfiguration, grokTranscriptText, createGrokAgentMetadata, } from "./harnesses/grok.js";
25
- import { codexAuthPlan, codexAuthStatus, codexAuthPane, bindCodexTranscriptSession, latestCodexCompletion, observeCodexDone, buildCodexAgentCmd, codexLaunchNote, codexTuiReady, codexPaneObservation, codexTuiBusy, codexHelperProcess, codexRateLimitModelSwitchDialog, codexApprovalDialog, codexStartupAction, CODEX_COMPOSER_MARKER_RE, codexModelChoice, codexEffortChoice, codexMoreReasoningChoice, codexTranscriptText, createCodexAgentMetadata, codexModelChoices, } from "./harnesses/codex.js";
24
+ import { grokAuthPlan, grokAuthStatus, grokAuthPane, realGrokHome, resolveAndValidateGrokAuth, assertGrokModelAvailable, grokModelChoices, grokEventsTranscript, latestGrokCompletion, grokTurnSettled, observeGrokDone, buildGrokAgentCmd, grokLaunchNote, grokEnvTokens, grokTuiReady, grokTuiBusy, grokPaneObservation, grokRateLimitDialog, grokStartupAction, grokLaunchBlockingDialog, assertGrokSandboxNotRejected, GROK_COMPOSER_MARKER_RE, grokFooterHasConfiguration, grokTranscriptText, createGrokAgentMetadata, } from "./harnesses/grok.js";
25
+ import { codexAuthPlan, codexAuthStatus, codexAuthPane, bindCodexTranscriptSession, latestCodexCompletion, codexTurnSettled, observeCodexDone, buildCodexAgentCmd, codexLaunchNote, codexTuiReady, codexPaneObservation, codexTuiBusy, codexHelperProcess, codexRateLimitModelSwitchDialog, codexApprovalDialog, codexStartupAction, CODEX_COMPOSER_MARKER_RE, codexModelChoice, codexEffortChoice, codexMoreReasoningChoice, codexTranscriptText, createCodexAgentMetadata, codexModelChoices, } from "./harnesses/codex.js";
26
26
  import { claudeAuthPlan, claudeAuthStatus, claudeAuthPane, OPERATION_ID_RE, CLAUDE_RESULT_MAX_BYTES, CLAUDE_EFFORTS, agentManagedClaudeSettingsPath, agentClaudeResultPath, agentClaudeOperationPath, agentClaudeApprovalReceiptPath, agentClaudeDispatchReceiptPath, validateOperationId, readClaudeResultText, assertClaudeAuthenticationReady, claudeModelChoices, buildClaudeAgentCmd, claudeLaunchNote, claudeTuiReady, claudePaneObservation, claudeUsageLimit, claudeStartupAction, claudeLoginMethodMenu, CLAUDE_COMPOSER_MARKER_RE, createClaudeAgentMetadata, claudeSessionTranscriptPath, claudeApiErrorFromLine, claudeApiErrorAfter, } from "./harnesses/claude.js";
27
27
  import { cursorAuthPlan, cursorAuthStatus, cursorAuthPane, bindCursorTranscriptSession, cursorTurnBoundary, latestCursorCompletion, observeCursorDone, cursorTranscriptText, assertCursorAuthenticationReady, assertCursorModelAvailable, cursorModelChoices, buildCursorAgentCmd, cursorAgentArgv, cursorPwshLaunchLine, cursorPromptWithLineage, createCursorAgentMetadata, cursorLaunchNote, cursorEffortNavigation, cursorTuiReady, cursorPaneObservation, cursorPromptHooksRunning, cursorUsageLimit, CURSOR_SUBMIT_SEQUENCE, CURSOR_COMPOSER_CONTENT_MARKER_RE, validateCursorModelEffort, } from "./harnesses/cursor.js";
28
28
  import { readInterimWords, recordInterimBoundary } from "./interim-words.js";
@@ -59,6 +59,11 @@ const LAUNCH_ECHO_TAIL_CHARS = 24;
59
59
  const LAUNCH_ECHO_MIN_CHARS = 8;
60
60
  const AGENT_TUI_READY_POLL_MS = 500;
61
61
  const AGENT_TUI_READY_STABLE_SAMPLES = 11;
62
+ // 上の11回は、起動の途中に一瞬だけ出る入力欄を採らないための確かめ(ADR 0014)。席のharnessが自分の記録で
63
+ // 「この起動でturnを1つ終えていて、次のturnを始めていない」と示している時は起動の途中ではないので、短い間隔で
64
+ // 続けて確かめるだけで送る(ADR 0097)。記録で示せない時は、今までどおり11回。
65
+ const AGENT_TUI_SETTLED_STABLE_SAMPLES = 2;
66
+ const AGENT_TUI_SETTLED_POLL_MS = 100;
62
67
  const AGENT_TUI_READY_LINES = 45;
63
68
  const CLAUDE_APPROVAL_SCREEN_LINES = 80;
64
69
  // submit座礁観測(dispatch後にcomposerへ送信textが残存していないかの有界チェック)
@@ -3172,6 +3177,8 @@ async function waitAgentTuiReadyImpl(kind, sample, sleepFn, opts = {}) {
3172
3177
  const timeoutMs = opts.timeoutMs ?? AGENT_TUI_READY_TIMEOUT_MS;
3173
3178
  const pollMs = opts.pollMs ?? AGENT_TUI_READY_POLL_MS;
3174
3179
  const stableSamples = opts.stableSamples ?? agentTuiReadyStableSamplesTestOverride ?? AGENT_TUI_READY_STABLE_SAMPLES;
3180
+ const settledSamples = opts.settledSamples ?? AGENT_TUI_SETTLED_STABLE_SAMPLES;
3181
+ const settledPollMs = opts.settledPollMs ?? AGENT_TUI_SETTLED_POLL_MS;
3175
3182
  const now = opts.now ?? (() => performance.now());
3176
3183
  const start = now();
3177
3184
  let deadline = start + timeoutMs;
@@ -3179,6 +3186,7 @@ async function waitAgentTuiReadyImpl(kind, sample, sleepFn, opts = {}) {
3179
3186
  const firstDrawDeadline = timeoutMs >= AGENT_TUI_READY_TIMEOUT_MS ? start + AGENT_TUI_FIRST_DRAW_TIMEOUT_MS : deadline;
3180
3187
  let samples = 0;
3181
3188
  let readyStreak = 0;
3189
+ let settledStreak = 0;
3182
3190
  let lastScreen = "";
3183
3191
  for (;;) {
3184
3192
  lastScreen = sample();
@@ -3189,9 +3197,20 @@ async function waitAgentTuiReadyImpl(kind, sample, sleepFn, opts = {}) {
3189
3197
  readyStreak++;
3190
3198
  if (readyStreak >= stableSamples)
3191
3199
  return { ready: true, samples, lastScreen };
3200
+ if (opts.turnSettled?.()) {
3201
+ settledStreak++;
3202
+ if (settledStreak >= settledSamples)
3203
+ return { ready: true, samples, lastScreen };
3204
+ }
3205
+ else if (settledStreak > 0) {
3206
+ // 記録の上でturnが始まった。短い間隔で数えた分を、11回の確かめへ持ち越さない。
3207
+ settledStreak = 0;
3208
+ readyStreak = 1;
3209
+ }
3192
3210
  }
3193
3211
  else {
3194
3212
  readyStreak = 0;
3213
+ settledStreak = 0;
3195
3214
  if (isAgentTuiActionRequired(kind, lastScreen))
3196
3215
  return { ready: false, samples, lastScreen };
3197
3216
  }
@@ -3202,11 +3221,30 @@ async function waitAgentTuiReadyImpl(kind, sample, sleepFn, opts = {}) {
3202
3221
  return { ready: false, samples, lastScreen };
3203
3222
  deadline = firstDrawDeadline;
3204
3223
  }
3205
- await sleepFn(pollMs);
3224
+ await sleepFn(settledStreak > 0 ? settledPollMs : pollMs);
3206
3225
  }
3207
3226
  }
3208
- async function waitAgentTuiReady(name, meta, timeoutMs = AGENT_TUI_READY_TIMEOUT_MS) {
3209
- const result = await waitAgentTuiReadyImpl(meta.kind, () => captureScreen(name, AGENT_TUI_READY_LINES), sleep, { timeoutMs, launchLine: agentLaunchLines.get(name) });
3227
+ /**
3228
+ * 席のharnessが、この起動でturnを1つ終えていて、次のturnを始めていない事を、自分の記録で示しているか。
3229
+ * 記録はこの起動に結び付いた物だけを読む(Codexはlaunch markerかsession ID、Grokは起動時に決めたsession、
3230
+ * Cursorは最初の文に入れたmarker)。起動直後の席は記録が無いのでfalse。読めない時もfalse。
3231
+ * Claudeはturnの印で差し込みか新しいturnかを決めていて、この確かめを通らない。
3232
+ */
3233
+ function agentTurnSettled(meta) {
3234
+ try {
3235
+ if (meta.kind === "codex")
3236
+ return codexTurnSettled(meta);
3237
+ if (meta.kind === "grok")
3238
+ return meta.completion_route === "grok_transcript" && grokTurnSettled(meta);
3239
+ if (meta.kind === "cursor") {
3240
+ return meta.completion_route === "cursor_transcript" && latestCursorCompletion(meta, readTranscriptLines) !== null;
3241
+ }
3242
+ }
3243
+ catch { /* 読めない時は、11回の確かめで決める */ }
3244
+ return false;
3245
+ }
3246
+ async function waitAgentTuiReady(name, meta, timeoutMs = AGENT_TUI_READY_TIMEOUT_MS, o = {}) {
3247
+ const result = await waitAgentTuiReadyImpl(meta.kind, () => captureScreen(name, AGENT_TUI_READY_LINES), sleep, { timeoutMs, launchLine: agentLaunchLines.get(name), ...(o.settledTurn ? { turnSettled: () => agentTurnSettled(meta) } : {}) });
3210
3248
  if (result.ready)
3211
3249
  agentLaunchLines.delete(name);
3212
3250
  return result;
@@ -3403,7 +3441,10 @@ export async function __testWaitAgentTuiReady(kind, samples, opts = {}) {
3403
3441
  const sleeps = [];
3404
3442
  // virtualClockは待った分だけ進む時計。実時間を使わずに締切の扱いを確かめる。
3405
3443
  let clock = 0;
3406
- const { virtualClock, ...waitOpts } = opts;
3444
+ const { virtualClock, settled, ...rest } = opts;
3445
+ let asked = 0;
3446
+ const waitOpts = settled && settled.length > 0
3447
+ ? { ...rest, turnSettled: () => settled[Math.min(asked++, settled.length - 1)] } : rest;
3407
3448
  const result = await waitAgentTuiReadyImpl(kind, () => samples[Math.min(i++, samples.length - 1)], async (ms) => {
3408
3449
  sleeps.push(ms);
3409
3450
  clock += ms;
@@ -3714,9 +3755,10 @@ async function recoverCodexRateLimitModelSwitch(name, meta) {
3714
3755
  return true;
3715
3756
  }
3716
3757
  /** ready待機中に現れたCodex利用上限modalも、同じ一時keep復旧へ戻す。 */
3717
- async function waitAgentTuiReadyAfterCodexRateLimitRecovery(name, meta, timeoutMs) {
3758
+ async function waitAgentTuiReadyAfterCodexRateLimitRecovery(name, meta, timeoutMs, o = {}) {
3718
3759
  let codexRateLimitModelSwitch = await recoverCodexRateLimitModelSwitch(name, meta);
3719
- let ready = await waitAgentTuiReady(name, meta, timeoutMs);
3760
+ // 上限のmodalを閉じた直後は、画面が落ち着くのを11回の確かめで待つ。
3761
+ let ready = await waitAgentTuiReady(name, meta, timeoutMs, codexRateLimitModelSwitch ? {} : o);
3720
3762
  if (!ready.ready && !codexRateLimitModelSwitch) {
3721
3763
  codexRateLimitModelSwitch = await recoverCodexRateLimitModelSwitch(name, meta);
3722
3764
  if (codexRateLimitModelSwitch)
@@ -4189,7 +4231,8 @@ async function dispatchAgentTurnInLock(name, text, o) {
4189
4231
  sendKey(name, limitDialog.dismissKey);
4190
4232
  }
4191
4233
  if (meta.kind !== "claude" || claudeColdStart) {
4192
- const recovery = await waitAgentTuiReadyAfterCodexRateLimitRecovery(name, meta, o.ready_timeout ?? AGENT_TUI_READY_TIMEOUT_MS);
4234
+ // 上限のpanelを閉じた直後(Grok)は、画面が落ち着くのを11回の確かめで待つ。
4235
+ const recovery = await waitAgentTuiReadyAfterCodexRateLimitRecovery(name, meta, o.ready_timeout ?? AGENT_TUI_READY_TIMEOUT_MS, { settledTurn: !limitDialog });
4193
4236
  codexRateLimitModelSwitch = recovery.codexRateLimitModelSwitch;
4194
4237
  const { ready } = recovery;
4195
4238
  if (!ready.ready) {
@@ -11,7 +11,7 @@ import { spawnAgentControlCommand } from "../agent-resolver.js";
11
11
  import * as steer from "aiterm-steer-delivery";
12
12
  import { AITERM_PROFILE } from "../steer-profile.js";
13
13
  import { catalogInvalid, catalogUnavailable, checkedCatalog } from "../model-catalog.js";
14
- import { shq, subagentInstruction, writeScopeLaunchNote, safeStatSize, readFileRange, sleep, agentMetadataPath, writeAgentMetadata, agentEventPath, createEmpty0600, agentLineageFields, AGENT_DONE_POLL_MS, AGENT_EVENT_MAX_BYTES, AGENT_EVENT_TAIL_BYTES, CODEX_TRANSCRIPT_INCREMENT_MAX_BYTES, agentHarness, } from "../agent-shared.js";
14
+ import { shq, subagentInstruction, writeScopeLaunchNote, safeStatSize, readFileRange, sleep, agentMetadataPath, writeAgentMetadata, agentEventPath, createEmpty0600, agentLineageFields, AGENT_DONE_POLL_MS, AGENT_EVENT_MAX_BYTES, AGENT_EVENT_TAIL_BYTES, AGENT_TURN_BOUNDARY_TAIL_BYTES, readTailLinesNewestFirst, CODEX_TRANSCRIPT_INCREMENT_MAX_BYTES, agentHarness, } from "../agent-shared.js";
15
15
  export function realCodexHome() {
16
16
  return process.env.CODEX_HOME || path.join(process.env.HOME ?? os.homedir(), ".codex");
17
17
  }
@@ -261,6 +261,47 @@ export function codexUsageLimit(record) {
261
261
  const error = codexTurnError(record);
262
262
  return error?.info === "usage_limit_exceeded" ? error.message : null;
263
263
  }
264
+ /**
265
+ * この起動のroot rolloutで、最後のturnが終わっていて、次のturnが始まっていないか。
266
+ * Codexはturnの開始でtask_started、終わりでtask_complete、Escで止めた時はturn_aborted(reasonはinterrupted)を書く
267
+ * (0.160.1の実記録)。末尾から見て最初の境界が、task_completeか、止めた時のturn_abortedの時だけtrue。
268
+ * rolloutが無い、この起動に結び付かない、書いている途中、境界が読む範囲に無い時はfalse(分からない時は「終わった」と数えない)。
269
+ */
270
+ export function codexTurnSettled(meta) {
271
+ let transcript;
272
+ try {
273
+ transcript = codexRootTranscript(meta);
274
+ }
275
+ catch {
276
+ return false;
277
+ }
278
+ if (!transcript)
279
+ return false;
280
+ const lines = readTailLinesNewestFirst(transcript, AGENT_TURN_BOUNDARY_TAIL_BYTES);
281
+ if (!lines)
282
+ return false;
283
+ for (const line of lines) {
284
+ if (!line.includes("task_started") && !line.includes("task_complete") && !line.includes("turn_aborted"))
285
+ continue;
286
+ let record;
287
+ try {
288
+ record = JSON.parse(line);
289
+ }
290
+ catch {
291
+ return false;
292
+ }
293
+ if (record?.type !== "event_msg")
294
+ continue;
295
+ if (record?.payload?.type === "task_started")
296
+ return false;
297
+ if (record?.payload?.type === "task_complete")
298
+ return typeof record.payload.turn_id === "string" && record.payload.turn_id !== "";
299
+ // 見たのはEscで止めた時の形だけ。他の理由(別のturnへの置き換え等)は、次のturnが続くかも知れないので数えない。
300
+ if (record?.payload?.type === "turn_aborted")
301
+ return record.payload.reason === "interrupted";
302
+ }
303
+ return false;
304
+ }
264
305
  export function latestCodexCompletion(meta, readTranscriptLines) {
265
306
  const transcript = codexRootTranscript(meta);
266
307
  if (!transcript)
@@ -10,7 +10,7 @@ import { randomBytes, randomUUID } from "node:crypto";
10
10
  import { AitermError } from "../errors.js";
11
11
  import { runAgentProtocolCommand, spawnAgentControlCommand } from "../agent-resolver.js";
12
12
  import { catalogInvalid, catalogUnavailable, checkedCatalog, findJsonLine, processSummary } from "../model-catalog.js";
13
- import { shq, subagentInstruction, writeScopeLaunchNote, safeStatSize, readFileRange, sleep, agentMetadataPath, writeAgentMetadata, agentEventPath, createEmpty0600, agentLineageFields, AGENT_DONE_POLL_MS, AGENT_RATE_LIMIT_POLL_MS, pollGate, AGENT_EVENT_MAX_BYTES, GROK_TRANSCRIPT_INCREMENT_MAX_BYTES, agentHarness, } from "../agent-shared.js";
13
+ import { shq, subagentInstruction, writeScopeLaunchNote, safeStatSize, readFileRange, sleep, agentMetadataPath, writeAgentMetadata, agentEventPath, createEmpty0600, agentLineageFields, AGENT_DONE_POLL_MS, AGENT_RATE_LIMIT_POLL_MS, pollGate, AGENT_EVENT_MAX_BYTES, AGENT_TURN_BOUNDARY_TAIL_BYTES, readTailLinesNewestFirst, GROK_TRANSCRIPT_INCREMENT_MAX_BYTES, agentHarness, } from "../agent-shared.js";
14
14
  const GROK_MODELS_MAX_BYTES = 1024 * 1024;
15
15
  const GROK_MODELS_TIMEOUT_MS = 15_000;
16
16
  // grok CLI はモデル未指定だと端末側 default に従うため、ツール契約として既定 slug を固定する。
@@ -191,6 +191,35 @@ export function latestGrokCompletion(meta, readTranscriptLines) {
191
191
  }
192
192
  return latest;
193
193
  }
194
+ /**
195
+ * この起動のevents.jsonlで、最後のturnが終わっていて、次のturnが始まっていないか。
196
+ * 末尾から見て最初の境界が、完了と数えるturn_ended(grokCompletionEvent)の時だけtrue。差し込みの継ぎ目
197
+ * (cancelledかつsend_now)とturn_startedはfalse。記録が無い、書いている途中、境界が読む範囲に無い時もfalse。
198
+ */
199
+ export function grokTurnSettled(meta) {
200
+ const transcript = grokEventsTranscript(meta);
201
+ if (!transcript || !fs.existsSync(transcript))
202
+ return false;
203
+ const lines = readTailLinesNewestFirst(transcript, AGENT_TURN_BOUNDARY_TAIL_BYTES);
204
+ if (!lines)
205
+ return false;
206
+ for (const line of lines) {
207
+ if (!line.includes("turn_started") && !line.includes("turn_ended"))
208
+ continue;
209
+ let record;
210
+ try {
211
+ record = JSON.parse(line);
212
+ }
213
+ catch {
214
+ return false;
215
+ }
216
+ if (record?.type === "turn_started")
217
+ return false;
218
+ if (record?.type === "turn_ended")
219
+ return grokCompletionEvent(meta, record) !== null;
220
+ }
221
+ return false;
222
+ }
194
223
  export function grokInitializationComplete(meta) {
195
224
  const transcript = grokEventsTranscript(meta);
196
225
  if (!transcript || !fs.existsSync(transcript))
package/dist/index.js CHANGED
@@ -11,6 +11,7 @@
11
11
  */
12
12
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
13
13
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
14
+ import { CallToolRequestSchema } from "@modelcontextprotocol/sdk/types.js";
14
15
  import { z } from "zod";
15
16
  import { agentModelsResult } from "./model-catalog.js";
16
17
  import * as core from "./core.js";
@@ -20,7 +21,7 @@ import { createRequire } from "node:module";
20
21
  import { ParentDeliveryManager, deliveryKey } from "./parent-delivery.js";
21
22
  import { acceptRemote, callRemoteTool, observeRemoteAgentDone, remoteInputDescription, remoteInputSchema, remoteLabel, remoteWaitProcess } from "./remote.js";
22
23
  import { codexParentFromRequest } from "./codex-parent-receiver.js";
23
- import { claudeParentFromRequest } from "./claude-parent-receiver.js";
24
+ import { claudeParentFromRequest, discardClaudeHookRequest } from "./claude-parent-receiver.js";
24
25
  import { parentDeliveryDiagnostic } from "./parent-hook-diagnostic.js";
25
26
  import { cursorParentFromRequest, isCursorMcpClient } from "./cursor-parent-receiver.js";
26
27
  import { waitProcessCommandLine } from "./cursor-parent-receive.js";
@@ -31,6 +32,20 @@ import { INTERIM_RESULT_META_KEY, interimRequestFromMeta } from "./interim-words
31
32
  // 除去済み=どちらの静的構文も 18〜22 全域を満たせないため(実行時 require が唯一全域で動く)。
32
33
  const pkg = createRequire(import.meta.url)("../package.json");
33
34
  const server = new McpServer({ name: "aiterm", version: pkg.version });
35
+ // 誤りの返りでは、Claude Code親は受け口のPostToolUse hookを走らせず、配送を結ばなかった呼び出しの置き場が残る。
36
+ // 返す前にここで消す(ADR 0096)。McpServerは入力の検査の失敗やhandlerの例外も誤りの返りへ変えるので、
37
+ // toolごとのhandlerではなくtools/callの返りを見る。McpServerがそのhandlerを置くのは最初のtool登録の時で、その前に包む。
38
+ const setRequestHandler = server.server.setRequestHandler.bind(server.server);
39
+ server.server.setRequestHandler = ((schema, handler) => setRequestHandler(schema, schema !== CallToolRequestSchema ? handler : async (request, extra) => {
40
+ const result = await handler(request, extra);
41
+ // 片付けの失敗で返りを変えない。残った置き場は、後の依頼のhookが見回って消す。
42
+ if (result?.isError)
43
+ try {
44
+ await discardClaudeHookRequest(server.server.getClientVersion()?.name, extra._meta);
45
+ }
46
+ catch { /* 見回りへ回す */ }
47
+ return result;
48
+ }));
34
49
  let parentDelivery = null;
35
50
  // 別端末の子の配送。記録の保存場所を分けるため、この端末の子とは別のmanagerにする。
36
51
  let remoteParentDelivery = null;
package/docs/DESIGN.md CHANGED
@@ -104,6 +104,9 @@ Grokは待ち行列へ入れた後に「send now」を押す。旧turnは`cancel
104
104
  新turnが作業を継ぐので、完了判定はこの継ぎ目を完了と数えない。Grokが待ち行列へ入れない時とCursorの入力欄に本文が残る時は`steered`を返さない。
105
105
  Cursorのsubmitはadapterがextended keyboard protocolのEnterへ変換し、呼び出し側は通常のdispatchだけを使う。
106
106
  起動直後のClaude sessionへの初回dispatchは、他harnessと同じくTUIの入力受付を確認してから貼付とEnterを送る。
107
+ Codex・Grok・Cursorの席は、新しいturnを送るたびに入力受付を確かめる。起動の途中の入力欄を採らないよう、500msおきに11回続けて
108
+ 入力待ちを読む(ADR 0014)。席のharnessが自分の記録で「この起動でturnを1つ終えていて、次のturnを始めていない」と示している時は、
109
+ 100msおきに2回で通す(ADR 0097)。記録の上でturnが動いている間は、画面が入力待ちに見えても11回の確かめを通る。
107
110
 
108
111
  ### 親配送の共通モジュール化(設計)
109
112
 
@@ -187,6 +190,7 @@ hookはNodeの実行ファイルと引数配列で直接起動し、shellやWind
187
190
  hookとの受け渡しはAiterm stateの`claude-parent-hooks`へ、道具の呼び出しごとの置き場として置く。
188
191
  届いた置き場は、送り手がhookの出し終えた記録を確かめた時に消す。届かなかった置き場と、配送を結ばなかった置き場は1日残し、
189
192
  次の依頼の`PreToolUse`のhookが消す。依頼元が居て配送を待っている置き場は残す(ADR 0094)。
193
+ 誤りで返す呼び出しは、Claude Codeが`PostToolUse`を走らせないので、配送を結んでいない置き場をAitermが返す前に消す(ADR 0096)。
190
194
 
191
195
  hookの登録は、`args`を使わずshellを通す1行で書く(ADR 0089)。POSIXは`exec '<node>' '<hook>'`、Windowsは
192
196
  `& '<node>' '<hook>'; exit (Get-Variable LASTEXITCODE -ValueOnly)`と`shell: "powershell"`。同じ設定をGrokも読み、
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aiterm-mcp",
3
- "version": "0.55.2",
3
+ "version": "0.55.4",
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": [
@@ -71,7 +71,7 @@
71
71
  },
72
72
  "dependencies": {
73
73
  "@modelcontextprotocol/sdk": "^1.29.0",
74
- "aiterm-steer-delivery": "^0.2.2",
74
+ "aiterm-steer-delivery": "^0.2.3",
75
75
  "ws": "8.21.3",
76
76
  "zod": "^4.4.3"
77
77
  },