aiterm-mcp 0.24.0 → 0.24.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/README.ja.md CHANGED
@@ -96,6 +96,10 @@ host統合は、kitepon.devの製品開発を支える内部基盤
96
96
 
97
97
  14 ツール: 6 つの **PTY ツール**(`pty_open` / `pty_send` / `pty_read` / `pty_key` / `pty_close` / `pty_list`)で 1 本の永続端末を開き・操作し・読む。加えて 4 つの **エージェント起動ツール**(`claude_agent` / `codex_agent` / `grok_agent` / `composer_agent`)が別のコーディングエージェントの TUI を新しい端末の中に起動し、`agent_configure`が起動中のCodex/Claudeのmodel・effortを再起動なしで変更し、`claude_turn`がdurable caller向けの構造化issue/recoveryを、`claude_approval`が相関済みClaude承認UI中継を、`diagnostics`が安全なfactory readinessを返す。バックエンドは **tmux** なので、MCP サーバや AI クライアントが再起動してもセッションは生き残る。
98
98
 
99
+ **v0.24.2では長寿命Codexでも設定変更を維持。** 起動時headerがcapture範囲外へ流れた後は、
100
+ 常駐するmodel/effort footerと入力欄でCodexを識別する。idle sessionをそのまま変更でき、
101
+ caller側の画面再描画、再試行、agent再起動は不要。
102
+
99
103
  **v0.24.0では起動中agentの設定変更を追加。** `agent_configure`はvendor標準操作を使って、
100
104
  起動中のCodex/Claudeのmodelとreasoning effortを変更する。PTY、vendor session、会話contextは維持する。
101
105
 
@@ -377,7 +381,7 @@ aiterm は同じ核心の洞察——端末を出会いの場にする——を
377
381
 
378
382
  `aiterm-runtime-errors snapshot` は dotagents factory adapter 向けに、製品所有のローカル snapshot を機械可読 JSON で返す。canonical dotagents factory-reporter config が schema-exact、host profile が実行 OS と一致し、`collection.enabled` が JSON boolean `true` の時だけ収集する。reporting field は schema 検証するが endpoint/credential file へ接続・読取せず network I/O も行わない。観測 API は core owner layer の固定3 code(PTY dependency・persistence・任意 vendor launcher)だけを受け、保存するのも固定 template と aggregate metadata(SHA-256 fingerprint、count、first/last、status、monotonic sequence)だけ。exception、stderr/stdout、stack、prompt、PTY/transcript/event body、path、任意 context は受け付けない。保存済み JSON も top/record exact・固定定義一致・fingerprint 再計算を通し、明示 DTO だけを返す。
379
383
 
380
- consumer は `aiterm-runtime-errors snapshot` を読み、durable ingestion 後に `aiterm-runtime-errors ack --cursor N` を呼ぶ。運用上の明示操作は `resolve|reopen --fingerprint SHA256`。MCP からの収集・diagnostic read は timeout 付き child process に隔離し、FIFOや停止 filesystem が端末本体を止めない。store mutation は期限付き bakery ticket queue で直列化する。各waiterは PID+process start identity+owner token を持つ再利用されない固有ticketを所有するため、死んだownerだけを固有名で除去でき、固定path回収のABAを作らない。POSIX state は `$XDG_STATE_HOME/aiterm-mcp/`(既定 `~/.local/state/aiterm-mcp/`)へ atomic replacement で置き、every read で owner/mode を再検証する。Windows native は `%LOCALAPPDATA%\aiterm-mcp\` で current SID の非継承 FullControl ACE 1件だけへ DACL を再構築し readback する。今回 Windows は path/DACL/timeout の純粋テストだけであり、新しい実機統合成功は主張しない。
384
+ consumer は `aiterm-runtime-errors snapshot` を読み、durable ingestion 後に `aiterm-runtime-errors ack --cursor N` を呼ぶ。運用上の明示操作は `resolve|reopen --fingerprint SHA256`。MCP からの収集・diagnostic read は timeout 付き child process に隔離し、FIFOや停止 filesystem が端末本体を止めない。store mutation は期限付き bakery ticket queue で直列化する。各waiterは PID+process start identity+owner token を持つ再利用されない固有ticketを所有するため、死んだownerだけを固有名で除去でき、固定path回収のABAを作らない。queueの期限は正常な前任者を含む総待ち時間ではなく、同じ先頭ownerが進まない時間を測る。通常pollはprocessの生存確認だけを行い、process start identityはblockerがstallした時に照合する。POSIX state は `$XDG_STATE_HOME/aiterm-mcp/`(既定 `~/.local/state/aiterm-mcp/`)へ atomic replacement で置き、every read で owner/mode を再検証する。Windows native は `%LOCALAPPDATA%\aiterm-mcp\` で current SID の非継承 FullControl ACE 1件だけへ DACL を再構築し readback する。今回 Windows は path/DACL/timeout の純粋テストだけであり、新しい実機統合成功は主張しない。
381
385
 
382
386
  ### 対話エージェント起動ツール
383
387
 
package/README.md CHANGED
@@ -96,6 +96,11 @@ toolchain behind kitepon.dev's products.
96
96
 
97
97
  Fourteen tools: six **PTY tools** — `pty_open` / `pty_send` / `pty_read` / `pty_key` / `pty_close` / `pty_list` — to open, drive, and read one persistent terminal, four **agent launchers** — `claude_agent` / `codex_agent` / `grok_agent` / `composer_agent` — that each start another coding agent's TUI inside a fresh one, `agent_configure` to change a running Codex/Claude session's model and effort without restarting it, `claude_turn` for durable structured issue/recovery, `claude_approval` for correlated Claude approval prompts, and `diagnostics` for safe factory readiness. The backend is **tmux**, so sessions survive even if the MCP server or the AI client restarts.
98
98
 
99
+ **v0.24.2 keeps in-place configuration working in long-lived Codex sessions.** Once the
100
+ startup header has scrolled out of the captured pane, aiterm recognizes Codex by its persistent
101
+ model/effort footer together with the input prompt. An idle session is therefore configured
102
+ directly; callers do not need to redraw the TUI, retry, or restart the agent.
103
+
99
104
  **v0.24.0 adds in-place agent configuration.** `agent_configure` uses each vendor's
100
105
  native controls to change the model and/or reasoning effort of a running Codex or Claude
101
106
  session while preserving its PTY, vendor session, and conversation context.
@@ -402,7 +407,7 @@ On top of that sits a productized layer a raw tmux bridge doesn't have: **token-
402
407
 
403
408
  `aiterm-runtime-errors snapshot` exposes a machine-readable, product-owned local snapshot for the dotagents factory adapter. Collection is fail-closed unless the canonical dotagents factory-reporter config is schema-exact, its host profile matches the executing OS, and it contains the JSON boolean `collection.enabled: true`; reporting fields are schema-validated but endpoints and credential files are never contacted, and the store performs no network I/O. The only accepted observations are three fixed codes owned by the core boundary (PTY dependency, persistence, and optional vendor launcher). Stored data is limited to fixed templates and aggregate metadata (SHA-256 fingerprint, count, first/last seen, status, and monotonic sequence); exceptions, stderr/stdout, stacks, prompts, terminal/transcript/event bodies, paths, and arbitrary context cannot enter the API. Persisted JSON is revalidated with exact top/record fields and a recomputed fingerprint before explicit DTO projection.
404
409
 
405
- Consumer flow is `aiterm-runtime-errors snapshot`, then `aiterm-runtime-errors ack --cursor N` after durable ingestion. Operators can use `resolve|reopen --fingerprint SHA256`. MCP collection and diagnostic reads run in timeout-bounded child processes, so a FIFO or stalled filesystem cannot block terminal work; child failure emits only the fixed store diagnostic. Store mutation uses a bounded bakery ticket queue: every waiter owns a never-reused ticket containing PID, process-start identity, and an owner token, so dead owners are removed by unique filename without fixed-path reclaim ABA. Worker deadlines use forced termination so a SIGTERM-ignoring child cannot mutate state after timeout. POSIX state is atomically replaced under `$XDG_STATE_HOME/aiterm-mcp/` (default `~/.local/state/aiterm-mcp/`) with owner/mode rechecked on every read. Windows native uses `%LOCALAPPDATA%\aiterm-mcp\`; each DACL is rebuilt and read back as one non-inherited FullControl ACE for the current SID. Windows path/DACL/timeout behavior is covered by pure tests in this change; no new Windows integration success is claimed.
410
+ Consumer flow is `aiterm-runtime-errors snapshot`, then `aiterm-runtime-errors ack --cursor N` after durable ingestion. Operators can use `resolve|reopen --fingerprint SHA256`. MCP collection and diagnostic reads run in timeout-bounded child processes, so a FIFO or stalled filesystem cannot block terminal work; child failure emits only the fixed store diagnostic. Store mutation uses a bounded bakery ticket queue: every waiter owns a never-reused ticket containing PID, process-start identity, and an owner token, so dead owners are removed by unique filename without fixed-path reclaim ABA. The queue deadline measures lack of progress by the same head owner, not total wait behind healthy predecessors; normal polling uses the native process-liveness check and validates process-start identity only when a blocker stalls. Worker deadlines use forced termination so a SIGTERM-ignoring child cannot mutate state after timeout. POSIX state is atomically replaced under `$XDG_STATE_HOME/aiterm-mcp/` (default `~/.local/state/aiterm-mcp/`) with owner/mode rechecked on every read. Windows native uses `%LOCALAPPDATA%\aiterm-mcp\`; each DACL is rebuilt and read back as one non-inherited FullControl ACE for the current SID. Windows path/DACL/timeout behavior is covered by pure tests in this change; no new Windows integration success is claimed.
406
411
 
407
412
  ### Interactive agent launchers
408
413
 
package/dist/core.js CHANGED
@@ -3323,7 +3323,11 @@ function isAgentTuiReady(kind, screen) {
3323
3323
  return screen.includes("Claude Code") && /(^|\n)\s*❯/.test(screen);
3324
3324
  }
3325
3325
  if (kind === "codex") {
3326
- return screen.includes("OpenAI Codex") && /(^|\n)\s*[›>]/.test(screen);
3326
+ // 起動直後は製品header、長寿命sessionでは常駐footerがCodex TUIの識別子になる。
3327
+ // capture-paneは直近45行だけなので、会話が進むとheaderは正常に画面外へ流れる。
3328
+ const codexFrontend = screen.includes("OpenAI Codex")
3329
+ || /(^|\n)\s*\S+\s+(?:low|medium|high|xhigh|max|ultra)\s+·\s+\S.*$/m.test(screen);
3330
+ return codexFrontend && /(^|\n)\s*[›>]/.test(screen);
3327
3331
  }
3328
3332
  // Grok Build 0.2.117 は起動完了後に製品名を消し、model footerだけを残す。
3329
3333
  // Composerも同じfrontendでmodel名だけが異なるため、両方をvendor UIの根拠にする。
@@ -532,10 +532,15 @@ export class RuntimeErrorStore {
532
532
  ticket = path.join(queue, ticketName);
533
533
  this.publishOwnerFile(ticket, owner);
534
534
  fs.unlinkSync(choosing);
535
- const deadline = Date.now() + 1_500;
535
+ // 期限はqueue全体の総待ち時間ではなく、同じ先頭ownerが進まない時間を測る。
536
+ // 正常な前任者がticketを順に解放するたびに予算を更新し、長いqueueをbusyと誤認しない。
537
+ let deadline = Date.now() + 1_500;
538
+ let blockingTicket = null;
539
+ let choosingBlockers = new Set();
536
540
  for (;;) {
537
541
  const names = fs.readdirSync(queue).sort();
538
542
  let hasLiveChoosing = false;
543
+ const liveChoosing = new Set();
539
544
  for (const name of names.filter((candidate) => choosingPattern.test(candidate))) {
540
545
  const currentPath = path.join(queue, name);
541
546
  let current;
@@ -549,10 +554,17 @@ export class RuntimeErrorStore {
549
554
  }
550
555
  if (name !== `choosing-${current.token}.json`)
551
556
  throw new Error("runtime error choosing entry が不正です");
552
- const identity = processStartIdentity(current.pid, this.platform, identityTimeoutMs);
553
- const live = identity === current.start_id || (!identity && processExists(current.pid));
554
- if (live)
557
+ // 通常待機ではkill(0)だけを使う。macOSのprocess start identityは外部psを起動するため、
558
+ // 全waiterが全pollで呼ぶとqueue自身が進めなくなる。PID再利用の照合はstall時だけ行う。
559
+ let live = processExists(current.pid);
560
+ if (live && Date.now() >= deadline) {
561
+ const identity = processStartIdentity(current.pid, this.platform, identityTimeoutMs);
562
+ live = identity === current.start_id || (!identity && processExists(current.pid));
563
+ }
564
+ if (live) {
555
565
  hasLiveChoosing = true;
566
+ liveChoosing.add(name);
567
+ }
556
568
  else {
557
569
  try {
558
570
  fs.unlinkSync(currentPath);
@@ -564,11 +576,17 @@ export class RuntimeErrorStore {
564
576
  }
565
577
  }
566
578
  if (hasLiveChoosing) {
579
+ if (choosingBlockers.size === 0
580
+ || [...choosingBlockers].some((name) => !liveChoosing.has(name))) {
581
+ deadline = Date.now() + 1_500;
582
+ }
583
+ choosingBlockers = liveChoosing;
567
584
  if (Date.now() >= deadline)
568
585
  throw new Error("runtime error store is busy");
569
586
  Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 10);
570
587
  continue;
571
588
  }
589
+ choosingBlockers = new Set();
572
590
  let firstLive = null;
573
591
  const ticketNames = fs.readdirSync(queue).sort();
574
592
  for (const name of ticketNames.filter((candidate) => ticketPattern.test(candidate))) {
@@ -584,8 +602,11 @@ export class RuntimeErrorStore {
584
602
  }
585
603
  if (!name.endsWith(`-${current.token}.ticket`))
586
604
  throw new Error("runtime error lock ticket が不正です");
587
- const identity = processStartIdentity(current.pid, this.platform, identityTimeoutMs);
588
- const live = identity === current.start_id || (!identity && processExists(current.pid));
605
+ let live = processExists(current.pid);
606
+ if (live && name === blockingTicket && Date.now() >= deadline) {
607
+ const identity = processStartIdentity(current.pid, this.platform, identityTimeoutMs);
608
+ live = identity === current.start_id || (!identity && processExists(current.pid));
609
+ }
589
610
  if (!live) {
590
611
  try {
591
612
  fs.unlinkSync(currentPath);
@@ -601,6 +622,10 @@ export class RuntimeErrorStore {
601
622
  }
602
623
  if (firstLive === ticketName)
603
624
  break;
625
+ if (firstLive !== blockingTicket) {
626
+ blockingTicket = firstLive;
627
+ deadline = Date.now() + 1_500;
628
+ }
604
629
  if (Date.now() >= deadline)
605
630
  throw new Error("runtime error store is busy");
606
631
  Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 10);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aiterm-mcp",
3
- "version": "0.24.0",
3
+ "version": "0.24.2",
4
4
  "mcpName": "io.github.kitepon-rgb/aiterm-mcp",
5
5
  "description": "Persistent tmux terminal MCP that lets Claude Code drive Codex CLI's interactive TUI, including slash commands and $imagegen. Also runs durable PTY sessions for SSH, containers, REPLs, and coding agents.",
6
6
  "keywords": [