aiterm-mcp 0.23.0 → 0.24.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/README.ja.md CHANGED
@@ -94,7 +94,10 @@ host統合は、kitepon.devの製品開発を支える内部基盤
94
94
 
95
95
  **言葉でなく実測で:** 記録済み203テストのベンチマークでは、`pty_read` はコンテキストに載るトークンを生ログの **約 7.1 分の 1** に減らす。しかも pass/fail の判定は畳んでも残る。→ [組み込みシェルツールとの使い分け](#組み込みシェルツールとの使い分け)
96
96
 
97
- 13 ツール: 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 を新しい端末の中に起動し、`claude_turn`がdurable caller向けの構造化issue/recoveryを、`claude_approval`が相関済みClaude承認UI中継を、`diagnostics`が安全なfactory readinessを返す。バックエンドは **tmux** なので、MCP サーバや AI クライアントが再起動してもセッションは生き残る。
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
+
99
+ **v0.24.0では起動中agentの設定変更を追加。** `agent_configure`はvendor標準操作を使って、
100
+ 起動中のCodex/Claudeのmodelとreasoning effortを変更する。PTY、vendor session、会話contextは維持する。
98
101
 
99
102
  **v0.23.0では、ローカル完結の別vendor向けportable forkを追加した。** どのlauncherでも
100
103
  `throughline_source_session`と新しいミッションを`prompt`へ渡すと、PTY作成前にローカルの
@@ -241,7 +244,7 @@ Throughline自体が不要である。
241
244
  Claude Code を再起動して、接続を確認:
242
245
 
243
246
  ```bash
244
- /mcp # aiterm が connected・13 ツール公開、と出る
247
+ /mcp # aiterm が connected・14 ツール公開、と出る
245
248
  ```
246
249
 
247
250
  最初のセッション——4 回の呼び出しで、1 個の永続端末:
@@ -282,7 +285,7 @@ MCP クライアントが aiterm を stdio 越しにプログラムから駆動
282
285
 
283
286
  ```mermaid
284
287
  flowchart LR
285
- AI["AI / MCP client<br/>(the orchestrator)"] -->|"pty_send · claude_agent · claude_turn · claude_approval · codex_agent<br/>grok_agent · composer_agent · diagnostics"| S["aiterm-mcp<br/>stdio MCP · 13 tools"]
288
+ AI["AI / MCP client<br/>(the orchestrator)"] -->|"pty_send · agent_configure · claude_agent · claude_turn · claude_approval · codex_agent<br/>grok_agent · composer_agent · diagnostics"| S["aiterm-mcp<br/>stdio MCP · 14 tools"]
286
289
  S -->|"pty_read<br/>token-reduced"| AI
287
290
  S -->|"tmux send-keys<br/>capture-pane"| P["persistent PTYs<br/>tmux · survive restarts"]
288
291
  P -->|"ssh · docker · repl"| R["nested<br/>remote · container · REPL"]
@@ -363,6 +366,7 @@ aiterm は同じ核心の洞察——端末を出会いの場にする——を
363
366
  | `pty_key` | 制御キーを送る | `session_id`, `key`(`C-c`/`Enter`/`Up`…) |
364
367
  | `pty_close` | 冪等に閉じ、`closed` / `already_closed`を返す | `session_id` |
365
368
  | `pty_list` | セッション一覧 | (なし) |
369
+ | `agent_configure` | 起動中のCodex/Claudeを再起動せずmodel/effort変更 | `session_id`, `model?`, `reasoning_effort?` |
366
370
  | `claude_turn` | 相関済みClaude operationをdispatch(issue)または回収(recover) | `action`, `session_id`, `operation_id`, `text?` |
367
371
  | `claude_approval` | 現在表示中の相関済みClaude承認UIを検査または応答 | `action`, `session_id`, `operation_id?`, `approval_choice?`, `observed_prompt_digest?` |
368
372
  | `diagnostics` | 機械可読 JSON による read-only factory readiness | (なし) |
@@ -379,6 +383,8 @@ consumer は `aiterm-runtime-errors snapshot` を読み、durable ingestion 後
379
383
 
380
384
  各ツールは特定ベンダーの対話型コーディングエージェント TUI を新しい永続 PTY の中に起動し、`session_id` を返す。以後は他のセッションと同様に `pty_read` / `pty_send` で操作する。モデルごとに 1 ツール=ツール名を見ればどのモデルか分かる。TUI は全画面アプリなので、`pty_read({ screen: true })` で描画済みの画面を読む。
381
385
 
386
+ `agent_configure({ session_id, model?, reasoning_effort? })`はvendor標準操作で起動中のCodex/Claudeを変更し、PTYと会話contextを維持する。Claude Code標準の`/model`・`/effort`は、新しいClaude sessionの既定値も同時に保存する。
387
+
382
388
  | ツール | 起動するもの | 主な引数 |
383
389
  | --- | --- | --- |
384
390
  | `claude_agent` | Claude Code CLI(Anthropic) | `prompt?`, `throughline_source_session?`, `model?`, `reasoning_effort?`(`low`/`medium`/`high`/`xhigh`/`max`), `cwd?`, `session_name?` |
package/README.md CHANGED
@@ -94,7 +94,11 @@ toolchain behind kitepon.dev's products.
94
94
 
95
95
  **Measured, not claimed:** in the recorded 203-test benchmark, a `pty_read` puts **~7.1× fewer tokens** in your context than the raw log — and the pass/fail verdict survives the fold. → [When to reach for it vs. the built-in shell](#when-to-reach-for-it-vs-the-built-in-shell)
96
96
 
97
- Thirteen 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, `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.
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
+
99
+ **v0.24.0 adds in-place agent configuration.** `agent_configure` uses each vendor's
100
+ native controls to change the model and/or reasoning effort of a running Codex or Claude
101
+ session while preserving its PTY, vendor session, and conversation context.
98
102
 
99
103
  **v0.23.0 adds a local, cross-vendor portable fork.** Pass `throughline_source_session`
100
104
  with a mission in `prompt` to any launcher, and aiterm asks the locally installed Throughline
@@ -263,7 +267,7 @@ The only edits to the captures above are the two `⋮` lines (a long head/tail r
263
267
  Restart Claude Code, then verify the connection:
264
268
 
265
269
  ```bash
266
- /mcp # aiterm should show as connected, exposing 13 tools
270
+ /mcp # aiterm should show as connected, exposing 14 tools
267
271
  ```
268
272
 
269
273
  Your first session — four calls, one persistent terminal:
@@ -304,7 +308,7 @@ The terminal is real and shared, so a human *can* jump in ([A human can watch](#
304
308
 
305
309
  ```mermaid
306
310
  flowchart LR
307
- AI["AI / MCP client<br/>(the orchestrator)"] -->|"pty_send · claude_agent · claude_turn · claude_approval · codex_agent<br/>grok_agent · composer_agent · diagnostics"| S["aiterm-mcp<br/>stdio MCP · 13 tools"]
311
+ AI["AI / MCP client<br/>(the orchestrator)"] -->|"pty_send · agent_configure · claude_agent · claude_turn · claude_approval · codex_agent<br/>grok_agent · composer_agent · diagnostics"| S["aiterm-mcp<br/>stdio MCP · 14 tools"]
308
312
  S -->|"pty_read<br/>token-reduced"| AI
309
313
  S -->|"tmux send-keys<br/>capture-pane"| P["persistent PTYs<br/>tmux · survive restarts"]
310
314
  P -->|"ssh · docker · repl"| R["nested<br/>remote · container · REPL"]
@@ -387,6 +391,7 @@ On top of that sits a productized layer a raw tmux bridge doesn't have: **token-
387
391
  | `pty_key` | Send a control key | `session_id`, `key` (`C-c`/`Enter`/`Up`…) |
388
392
  | `pty_close` | Close idempotently; return `closed` / `already_closed` | `session_id` |
389
393
  | `pty_list` | List sessions (agent rows carry `agent=<kind>` metadata) | (none) |
394
+ | `agent_configure` | Change model/effort in a running Codex or Claude session without restarting it | `session_id`, `model?`, `reasoning_effort?` |
390
395
  | `claude_turn` | Issue (dispatch-only) or recover one correlated Claude operation | `action`, `session_id`, `operation_id`, `text?` |
391
396
  | `claude_approval` | Inspect or answer the current correlated Claude approval prompt | `action`, `session_id`, `operation_id?`, `approval_choice?`, `observed_prompt_digest?` |
392
397
  | `diagnostics` | Read-only factory readiness as machine-readable JSON | (none) |
@@ -403,6 +408,8 @@ Consumer flow is `aiterm-runtime-errors snapshot`, then `aiterm-runtime-errors a
403
408
 
404
409
  Each launcher starts a specific vendor's interactive coding-agent TUI inside a fresh persistent PTY and returns its `session_id` — from there you drive it with plain `pty_read` / `pty_send`, exactly like any other session. One tool per model, so the tool name itself tells you which model you get. The TUI is a full-screen app, so read it with `pty_read({ screen: true })` for the rendered view.
405
410
 
411
+ `agent_configure({ session_id, model?, reasoning_effort? })` changes a running Codex or Claude TUI through the vendor's standard controls, preserving the PTY and conversation context. Claude Code's native `/model` and `/effort` commands also save those choices as defaults for new Claude sessions.
412
+
406
413
  | Tool | Launches | Key args |
407
414
  | --- | --- | --- |
408
415
  | `claude_agent` | Claude Code CLI (Anthropic) | `prompt?`, `throughline_source_session?`, `model?`, `reasoning_effort?` (`low`/`medium`/`high`/`xhigh`/`max`), `cwd?`, `session_name?`, `launch_operation_id?` |
package/dist/core.js CHANGED
@@ -342,9 +342,14 @@ function assertSessionName(name) {
342
342
  throw new AitermError(`session 名は英数字と _ - のみ・64文字以内にしてください: ${JSON.stringify(name)}`, 2);
343
343
  }
344
344
  function currentUid() {
345
- if (typeof process.getuid !== "function") {
346
- throw new AitermError("agent_done は POSIX/macOS/Linux のみ対応です(native Windows は未対応)", 2);
347
- }
345
+ // Windows(native)は process.getuid を持たない。以前はここで throw していたが、
346
+ // agent metadata の存在確認経由で素の pty_send まで巻き込んで全 send を殺していた。
347
+ // Windows の fs.Stats.uid は常に 0 なので、ここも 0 を返せば owner 比較
348
+ // (st.uid !== currentUid()) は自然に通過する。Windows は POSIX owner 検証を
349
+ // 持たない(NTFS ACL は別体系)という既知の制約の明示的受容であり、POSIX 側は
350
+ // getuid をそのまま返すため挙動不変。
351
+ if (typeof process.getuid !== "function")
352
+ return 0;
348
353
  return process.getuid();
349
354
  }
350
355
  function runtimeStateBase() {
@@ -3558,6 +3563,138 @@ export function isAgentSession(name) {
3558
3563
  assertSessionName(name);
3559
3564
  return tryLoadAgentMetadata(name) !== null;
3560
3565
  }
3566
+ async function waitForScreenText(name, text, timeoutMs = 3_000) {
3567
+ const deadline = performance.now() + timeoutMs;
3568
+ do {
3569
+ const screen = captureScreen(name, AGENT_TUI_READY_LINES);
3570
+ if (screen.includes(text))
3571
+ return screen;
3572
+ await sleep(100);
3573
+ } while (performance.now() < deadline);
3574
+ throw new AitermError(`${agentLabel(loadAgentMetadata(name).kind)} の ${text} 画面を確認できません`, 2);
3575
+ }
3576
+ function sendMenuChoice(name, choice) {
3577
+ const sent = tmux("send-keys", "-t", name, choice);
3578
+ if (sent.code !== 0) {
3579
+ throw new AitermError(`agent設定の選択を送れませんでした: ${sent.stderr.trim() || `code=${sent.code}`}`, 2);
3580
+ }
3581
+ }
3582
+ function codexModelChoice(screen, model) {
3583
+ for (const line of screen.slice(screen.lastIndexOf("Select Model and Effort")).split("\n")) {
3584
+ const match = line.match(/^\s*(?:›\s*)?(\d+)\.\s+(\S+)/);
3585
+ if (match?.[2] === model)
3586
+ return match[1];
3587
+ }
3588
+ return null;
3589
+ }
3590
+ function codexEffortChoice(screen, effort) {
3591
+ const labels = {
3592
+ low: /^Low\b/i,
3593
+ medium: /^Medium\b/i,
3594
+ high: /^High\b/i,
3595
+ xhigh: /^Extra high\b/i,
3596
+ max: /^Max\b/i,
3597
+ ultra: /^Ultra\b/i,
3598
+ };
3599
+ const wanted = labels[effort.toLowerCase()];
3600
+ if (!wanted)
3601
+ return null;
3602
+ for (const line of screen.split("\n")) {
3603
+ const match = line.match(/^\s*(?:›\s*)?(\d+)\.\s+(.+?)\s{2,}/);
3604
+ if (match && wanted.test(match[2]))
3605
+ return match[1];
3606
+ }
3607
+ return null;
3608
+ }
3609
+ function codexMoreReasoningChoice(screen) {
3610
+ for (const line of screen.split("\n")) {
3611
+ const match = line.match(/^\s*(?:›\s*)?(\d+)\.\s+More reasoning/);
3612
+ if (match)
3613
+ return match[1];
3614
+ }
3615
+ return null;
3616
+ }
3617
+ /** 同じ対話sessionを保ったまま、vendor標準の操作でmodel/effortを変更する。 */
3618
+ export async function configureAgent(name, opts) {
3619
+ assertSessionName(name);
3620
+ const model = opts.model?.trim() || null;
3621
+ const effort = opts.reasoning_effort?.trim() || null;
3622
+ if (!model && !effort)
3623
+ throw new AitermError("model または reasoning_effort を指定してください", 2);
3624
+ const meta = loadAgentMetadata(name);
3625
+ if (meta.kind !== "claude" && meta.kind !== "codex") {
3626
+ throw new AitermError("agent_configure はClaudeとCodexのsessionだけに対応します", 2);
3627
+ }
3628
+ bindCompletedInitialPrompt(meta);
3629
+ const ready = await waitAgentTuiReady(name, meta, AGENT_TUI_READY_TIMEOUT_MS);
3630
+ if (!ready.ready)
3631
+ throw new AitermError(`agent session '${name}' は入力待ちではありません`, 2);
3632
+ if (meta.kind === "claude") {
3633
+ if (model) {
3634
+ await sendAgentPromptText(name, `/model ${model}`);
3635
+ const modelReady = await waitAgentTuiReady(name, meta, AGENT_TUI_READY_TIMEOUT_MS);
3636
+ if (!modelReady.ready)
3637
+ throw new AitermError(`Claudeのmodel変更完了を確認できません`, 2);
3638
+ }
3639
+ if (effort) {
3640
+ await sendAgentPromptText(name, `/effort ${effort}`);
3641
+ const effortReady = await waitAgentTuiReady(name, meta, AGENT_TUI_READY_TIMEOUT_MS);
3642
+ if (!effortReady.ready)
3643
+ throw new AitermError(`Claudeのeffort変更完了を確認できません`, 2);
3644
+ }
3645
+ return {
3646
+ schema: "aiterm.agent-configure-result.v1",
3647
+ session_id: name,
3648
+ provider: "claude",
3649
+ model,
3650
+ reasoning_effort: effort,
3651
+ };
3652
+ }
3653
+ await sendAgentPromptText(name, "/model");
3654
+ const modelScreen = await waitForScreenText(name, "Select Model and Effort");
3655
+ if (model) {
3656
+ const choice = codexModelChoice(modelScreen, model);
3657
+ if (!choice)
3658
+ throw new AitermError(`Codexの/modelに ${model} がありません`, 2);
3659
+ sendMenuChoice(name, choice);
3660
+ }
3661
+ else {
3662
+ sendMenuChoice(name, "Enter");
3663
+ }
3664
+ let effortScreen = await waitForScreenText(name, "Select Reasoning Level");
3665
+ if (effort) {
3666
+ let choice = codexEffortChoice(effortScreen, effort);
3667
+ if (!choice && (effort === "max" || effort === "ultra")) {
3668
+ const more = codexMoreReasoningChoice(effortScreen);
3669
+ if (more) {
3670
+ sendMenuChoice(name, more);
3671
+ effortScreen = await waitForScreenText(name, "Advanced Reasoning");
3672
+ choice = codexEffortChoice(effortScreen, effort);
3673
+ }
3674
+ }
3675
+ if (!choice)
3676
+ throw new AitermError(`Codexの/modelに reasoning_effort=${effort} がありません`, 2);
3677
+ sendMenuChoice(name, choice);
3678
+ }
3679
+ else {
3680
+ sendMenuChoice(name, "Enter");
3681
+ }
3682
+ await waitForScreenText(name, "Model changed to");
3683
+ return {
3684
+ schema: "aiterm.agent-configure-result.v1",
3685
+ session_id: name,
3686
+ provider: "codex",
3687
+ model,
3688
+ reasoning_effort: effort,
3689
+ };
3690
+ }
3691
+ export function __testCodexConfigureChoices(screen, model, effort) {
3692
+ return {
3693
+ model: codexModelChoice(screen, model),
3694
+ effort: codexEffortChoice(screen, effort),
3695
+ more: codexMoreReasoningChoice(screen),
3696
+ };
3697
+ }
3561
3698
  // v0.16.0: 親をブロックする wait 経路は廃止した。send は ready gate と submit 分離を内蔵した
3562
3699
  // dispatch として即返り、event_cursor(送信直前のvendor完了正本境界)を receipt で返す。
3563
3700
  // 完了通知は aiterm-wait(--cursor で境界を渡す)、回収は pty_read / claude_turn recover が担う。
package/dist/index.js CHANGED
@@ -396,6 +396,33 @@ server.registerTool("claude_approval", {
396
396
  return fail(e);
397
397
  }
398
398
  });
399
+ server.registerTool("agent_configure", {
400
+ description: "起動済みのCodex/Claude agent sessionを再起動せず、会話contextを保ったままmodel/reasoning effortを変更する。" +
401
+ "ClaudeはCLI標準の/model・/effort、CodexはCLI標準の/model選択画面を使う。",
402
+ inputSchema: {
403
+ session_id: z.string().regex(/^[A-Za-z0-9_-]{1,64}$/),
404
+ model: z.string().min(1).nullish().describe("変更後のmodel。省略時はmodelを変更しない"),
405
+ reasoning_effort: z.string().min(1).nullish().describe("変更後のreasoning effort。省略時はeffortを変更しない"),
406
+ },
407
+ outputSchema: {
408
+ schema: z.literal("aiterm.agent-configure-result.v1"),
409
+ session_id: z.string().regex(/^[A-Za-z0-9_-]{1,64}$/),
410
+ provider: z.enum(["claude", "codex"]),
411
+ model: z.string().nullable(),
412
+ reasoning_effort: z.string().nullable(),
413
+ },
414
+ }, async ({ session_id, model, reasoning_effort }) => {
415
+ try {
416
+ const result = await core.configureAgent(session_id, { model, reasoning_effort });
417
+ return {
418
+ content: [{ type: "text", text: JSON.stringify(result) }],
419
+ structuredContent: { ...result },
420
+ };
421
+ }
422
+ catch (e) {
423
+ return fail(e);
424
+ }
425
+ });
399
426
  // 対話型エージェント起動ツール(モデルごとに1つ=ツール名/説明でどのモデルか一目で分かる)。
400
427
  // いずれも永続端末に TUI を起動し session_id を返す。以後 pty_read/pty_send で対話操作する。
401
428
  const agentModelDesc = (kind) => kind === "claude"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aiterm-mcp",
3
- "version": "0.23.0",
3
+ "version": "0.24.0",
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": [