aiterm-mcp 0.23.1 → 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 +14 -4
- package/README.md +16 -4
- package/dist/core.js +137 -1
- package/dist/index.js +27 -0
- package/dist/runtime-error-store.js +31 -6
- package/package.json +1 -1
package/README.ja.md
CHANGED
|
@@ -94,7 +94,14 @@ host統合は、kitepon.devの製品開発を支える内部基盤
|
|
|
94
94
|
|
|
95
95
|
**言葉でなく実測で:** 記録済み203テストのベンチマークでは、`pty_read` はコンテキストに載るトークンを生ログの **約 7.1 分の 1** に減らす。しかも pass/fail の判定は畳んでも残る。→ [組み込みシェルツールとの使い分け](#組み込みシェルツールとの使い分け)
|
|
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
|
+
|
|
99
|
+
**v0.24.2では長寿命Codexでも設定変更を維持。** 起動時headerがcapture範囲外へ流れた後は、
|
|
100
|
+
常駐するmodel/effort footerと入力欄でCodexを識別する。idle sessionをそのまま変更でき、
|
|
101
|
+
caller側の画面再描画、再試行、agent再起動は不要。
|
|
102
|
+
|
|
103
|
+
**v0.24.0では起動中agentの設定変更を追加。** `agent_configure`はvendor標準操作を使って、
|
|
104
|
+
起動中のCodex/Claudeのmodelとreasoning effortを変更する。PTY、vendor session、会話contextは維持する。
|
|
98
105
|
|
|
99
106
|
**v0.23.0では、ローカル完結の別vendor向けportable forkを追加した。** どのlauncherでも
|
|
100
107
|
`throughline_source_session`と新しいミッションを`prompt`へ渡すと、PTY作成前にローカルの
|
|
@@ -241,7 +248,7 @@ Throughline自体が不要である。
|
|
|
241
248
|
Claude Code を再起動して、接続を確認:
|
|
242
249
|
|
|
243
250
|
```bash
|
|
244
|
-
/mcp # aiterm が connected・
|
|
251
|
+
/mcp # aiterm が connected・14 ツール公開、と出る
|
|
245
252
|
```
|
|
246
253
|
|
|
247
254
|
最初のセッション——4 回の呼び出しで、1 個の永続端末:
|
|
@@ -282,7 +289,7 @@ MCP クライアントが aiterm を stdio 越しにプログラムから駆動
|
|
|
282
289
|
|
|
283
290
|
```mermaid
|
|
284
291
|
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 ·
|
|
292
|
+
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
293
|
S -->|"pty_read<br/>token-reduced"| AI
|
|
287
294
|
S -->|"tmux send-keys<br/>capture-pane"| P["persistent PTYs<br/>tmux · survive restarts"]
|
|
288
295
|
P -->|"ssh · docker · repl"| R["nested<br/>remote · container · REPL"]
|
|
@@ -363,6 +370,7 @@ aiterm は同じ核心の洞察——端末を出会いの場にする——を
|
|
|
363
370
|
| `pty_key` | 制御キーを送る | `session_id`, `key`(`C-c`/`Enter`/`Up`…) |
|
|
364
371
|
| `pty_close` | 冪等に閉じ、`closed` / `already_closed`を返す | `session_id` |
|
|
365
372
|
| `pty_list` | セッション一覧 | (なし) |
|
|
373
|
+
| `agent_configure` | 起動中のCodex/Claudeを再起動せずmodel/effort変更 | `session_id`, `model?`, `reasoning_effort?` |
|
|
366
374
|
| `claude_turn` | 相関済みClaude operationをdispatch(issue)または回収(recover) | `action`, `session_id`, `operation_id`, `text?` |
|
|
367
375
|
| `claude_approval` | 現在表示中の相関済みClaude承認UIを検査または応答 | `action`, `session_id`, `operation_id?`, `approval_choice?`, `observed_prompt_digest?` |
|
|
368
376
|
| `diagnostics` | 機械可読 JSON による read-only factory readiness | (なし) |
|
|
@@ -373,12 +381,14 @@ aiterm は同じ核心の洞察——端末を出会いの場にする——を
|
|
|
373
381
|
|
|
374
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 だけを返す。
|
|
375
383
|
|
|
376
|
-
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 の純粋テストだけであり、新しい実機統合成功は主張しない。
|
|
377
385
|
|
|
378
386
|
### 対話エージェント起動ツール
|
|
379
387
|
|
|
380
388
|
各ツールは特定ベンダーの対話型コーディングエージェント TUI を新しい永続 PTY の中に起動し、`session_id` を返す。以後は他のセッションと同様に `pty_read` / `pty_send` で操作する。モデルごとに 1 ツール=ツール名を見ればどのモデルか分かる。TUI は全画面アプリなので、`pty_read({ screen: true })` で描画済みの画面を読む。
|
|
381
389
|
|
|
390
|
+
`agent_configure({ session_id, model?, reasoning_effort? })`はvendor標準操作で起動中のCodex/Claudeを変更し、PTYと会話contextを維持する。Claude Code標準の`/model`・`/effort`は、新しいClaude sessionの既定値も同時に保存する。
|
|
391
|
+
|
|
382
392
|
| ツール | 起動するもの | 主な引数 |
|
|
383
393
|
| --- | --- | --- |
|
|
384
394
|
| `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,16 @@ 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
|
-
|
|
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.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
|
+
|
|
104
|
+
**v0.24.0 adds in-place agent configuration.** `agent_configure` uses each vendor's
|
|
105
|
+
native controls to change the model and/or reasoning effort of a running Codex or Claude
|
|
106
|
+
session while preserving its PTY, vendor session, and conversation context.
|
|
98
107
|
|
|
99
108
|
**v0.23.0 adds a local, cross-vendor portable fork.** Pass `throughline_source_session`
|
|
100
109
|
with a mission in `prompt` to any launcher, and aiterm asks the locally installed Throughline
|
|
@@ -263,7 +272,7 @@ The only edits to the captures above are the two `⋮` lines (a long head/tail r
|
|
|
263
272
|
Restart Claude Code, then verify the connection:
|
|
264
273
|
|
|
265
274
|
```bash
|
|
266
|
-
/mcp # aiterm should show as connected, exposing
|
|
275
|
+
/mcp # aiterm should show as connected, exposing 14 tools
|
|
267
276
|
```
|
|
268
277
|
|
|
269
278
|
Your first session — four calls, one persistent terminal:
|
|
@@ -304,7 +313,7 @@ The terminal is real and shared, so a human *can* jump in ([A human can watch](#
|
|
|
304
313
|
|
|
305
314
|
```mermaid
|
|
306
315
|
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 ·
|
|
316
|
+
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
317
|
S -->|"pty_read<br/>token-reduced"| AI
|
|
309
318
|
S -->|"tmux send-keys<br/>capture-pane"| P["persistent PTYs<br/>tmux · survive restarts"]
|
|
310
319
|
P -->|"ssh · docker · repl"| R["nested<br/>remote · container · REPL"]
|
|
@@ -387,6 +396,7 @@ On top of that sits a productized layer a raw tmux bridge doesn't have: **token-
|
|
|
387
396
|
| `pty_key` | Send a control key | `session_id`, `key` (`C-c`/`Enter`/`Up`…) |
|
|
388
397
|
| `pty_close` | Close idempotently; return `closed` / `already_closed` | `session_id` |
|
|
389
398
|
| `pty_list` | List sessions (agent rows carry `agent=<kind>` metadata) | (none) |
|
|
399
|
+
| `agent_configure` | Change model/effort in a running Codex or Claude session without restarting it | `session_id`, `model?`, `reasoning_effort?` |
|
|
390
400
|
| `claude_turn` | Issue (dispatch-only) or recover one correlated Claude operation | `action`, `session_id`, `operation_id`, `text?` |
|
|
391
401
|
| `claude_approval` | Inspect or answer the current correlated Claude approval prompt | `action`, `session_id`, `operation_id?`, `approval_choice?`, `observed_prompt_digest?` |
|
|
392
402
|
| `diagnostics` | Read-only factory readiness as machine-readable JSON | (none) |
|
|
@@ -397,12 +407,14 @@ On top of that sits a productized layer a raw tmux bridge doesn't have: **token-
|
|
|
397
407
|
|
|
398
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.
|
|
399
409
|
|
|
400
|
-
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.
|
|
401
411
|
|
|
402
412
|
### Interactive agent launchers
|
|
403
413
|
|
|
404
414
|
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
415
|
|
|
416
|
+
`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.
|
|
417
|
+
|
|
406
418
|
| Tool | Launches | Key args |
|
|
407
419
|
| --- | --- | --- |
|
|
408
420
|
| `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
|
@@ -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
|
-
|
|
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の根拠にする。
|
|
@@ -3563,6 +3567,138 @@ export function isAgentSession(name) {
|
|
|
3563
3567
|
assertSessionName(name);
|
|
3564
3568
|
return tryLoadAgentMetadata(name) !== null;
|
|
3565
3569
|
}
|
|
3570
|
+
async function waitForScreenText(name, text, timeoutMs = 3_000) {
|
|
3571
|
+
const deadline = performance.now() + timeoutMs;
|
|
3572
|
+
do {
|
|
3573
|
+
const screen = captureScreen(name, AGENT_TUI_READY_LINES);
|
|
3574
|
+
if (screen.includes(text))
|
|
3575
|
+
return screen;
|
|
3576
|
+
await sleep(100);
|
|
3577
|
+
} while (performance.now() < deadline);
|
|
3578
|
+
throw new AitermError(`${agentLabel(loadAgentMetadata(name).kind)} の ${text} 画面を確認できません`, 2);
|
|
3579
|
+
}
|
|
3580
|
+
function sendMenuChoice(name, choice) {
|
|
3581
|
+
const sent = tmux("send-keys", "-t", name, choice);
|
|
3582
|
+
if (sent.code !== 0) {
|
|
3583
|
+
throw new AitermError(`agent設定の選択を送れませんでした: ${sent.stderr.trim() || `code=${sent.code}`}`, 2);
|
|
3584
|
+
}
|
|
3585
|
+
}
|
|
3586
|
+
function codexModelChoice(screen, model) {
|
|
3587
|
+
for (const line of screen.slice(screen.lastIndexOf("Select Model and Effort")).split("\n")) {
|
|
3588
|
+
const match = line.match(/^\s*(?:›\s*)?(\d+)\.\s+(\S+)/);
|
|
3589
|
+
if (match?.[2] === model)
|
|
3590
|
+
return match[1];
|
|
3591
|
+
}
|
|
3592
|
+
return null;
|
|
3593
|
+
}
|
|
3594
|
+
function codexEffortChoice(screen, effort) {
|
|
3595
|
+
const labels = {
|
|
3596
|
+
low: /^Low\b/i,
|
|
3597
|
+
medium: /^Medium\b/i,
|
|
3598
|
+
high: /^High\b/i,
|
|
3599
|
+
xhigh: /^Extra high\b/i,
|
|
3600
|
+
max: /^Max\b/i,
|
|
3601
|
+
ultra: /^Ultra\b/i,
|
|
3602
|
+
};
|
|
3603
|
+
const wanted = labels[effort.toLowerCase()];
|
|
3604
|
+
if (!wanted)
|
|
3605
|
+
return null;
|
|
3606
|
+
for (const line of screen.split("\n")) {
|
|
3607
|
+
const match = line.match(/^\s*(?:›\s*)?(\d+)\.\s+(.+?)\s{2,}/);
|
|
3608
|
+
if (match && wanted.test(match[2]))
|
|
3609
|
+
return match[1];
|
|
3610
|
+
}
|
|
3611
|
+
return null;
|
|
3612
|
+
}
|
|
3613
|
+
function codexMoreReasoningChoice(screen) {
|
|
3614
|
+
for (const line of screen.split("\n")) {
|
|
3615
|
+
const match = line.match(/^\s*(?:›\s*)?(\d+)\.\s+More reasoning/);
|
|
3616
|
+
if (match)
|
|
3617
|
+
return match[1];
|
|
3618
|
+
}
|
|
3619
|
+
return null;
|
|
3620
|
+
}
|
|
3621
|
+
/** 同じ対話sessionを保ったまま、vendor標準の操作でmodel/effortを変更する。 */
|
|
3622
|
+
export async function configureAgent(name, opts) {
|
|
3623
|
+
assertSessionName(name);
|
|
3624
|
+
const model = opts.model?.trim() || null;
|
|
3625
|
+
const effort = opts.reasoning_effort?.trim() || null;
|
|
3626
|
+
if (!model && !effort)
|
|
3627
|
+
throw new AitermError("model または reasoning_effort を指定してください", 2);
|
|
3628
|
+
const meta = loadAgentMetadata(name);
|
|
3629
|
+
if (meta.kind !== "claude" && meta.kind !== "codex") {
|
|
3630
|
+
throw new AitermError("agent_configure はClaudeとCodexのsessionだけに対応します", 2);
|
|
3631
|
+
}
|
|
3632
|
+
bindCompletedInitialPrompt(meta);
|
|
3633
|
+
const ready = await waitAgentTuiReady(name, meta, AGENT_TUI_READY_TIMEOUT_MS);
|
|
3634
|
+
if (!ready.ready)
|
|
3635
|
+
throw new AitermError(`agent session '${name}' は入力待ちではありません`, 2);
|
|
3636
|
+
if (meta.kind === "claude") {
|
|
3637
|
+
if (model) {
|
|
3638
|
+
await sendAgentPromptText(name, `/model ${model}`);
|
|
3639
|
+
const modelReady = await waitAgentTuiReady(name, meta, AGENT_TUI_READY_TIMEOUT_MS);
|
|
3640
|
+
if (!modelReady.ready)
|
|
3641
|
+
throw new AitermError(`Claudeのmodel変更完了を確認できません`, 2);
|
|
3642
|
+
}
|
|
3643
|
+
if (effort) {
|
|
3644
|
+
await sendAgentPromptText(name, `/effort ${effort}`);
|
|
3645
|
+
const effortReady = await waitAgentTuiReady(name, meta, AGENT_TUI_READY_TIMEOUT_MS);
|
|
3646
|
+
if (!effortReady.ready)
|
|
3647
|
+
throw new AitermError(`Claudeのeffort変更完了を確認できません`, 2);
|
|
3648
|
+
}
|
|
3649
|
+
return {
|
|
3650
|
+
schema: "aiterm.agent-configure-result.v1",
|
|
3651
|
+
session_id: name,
|
|
3652
|
+
provider: "claude",
|
|
3653
|
+
model,
|
|
3654
|
+
reasoning_effort: effort,
|
|
3655
|
+
};
|
|
3656
|
+
}
|
|
3657
|
+
await sendAgentPromptText(name, "/model");
|
|
3658
|
+
const modelScreen = await waitForScreenText(name, "Select Model and Effort");
|
|
3659
|
+
if (model) {
|
|
3660
|
+
const choice = codexModelChoice(modelScreen, model);
|
|
3661
|
+
if (!choice)
|
|
3662
|
+
throw new AitermError(`Codexの/modelに ${model} がありません`, 2);
|
|
3663
|
+
sendMenuChoice(name, choice);
|
|
3664
|
+
}
|
|
3665
|
+
else {
|
|
3666
|
+
sendMenuChoice(name, "Enter");
|
|
3667
|
+
}
|
|
3668
|
+
let effortScreen = await waitForScreenText(name, "Select Reasoning Level");
|
|
3669
|
+
if (effort) {
|
|
3670
|
+
let choice = codexEffortChoice(effortScreen, effort);
|
|
3671
|
+
if (!choice && (effort === "max" || effort === "ultra")) {
|
|
3672
|
+
const more = codexMoreReasoningChoice(effortScreen);
|
|
3673
|
+
if (more) {
|
|
3674
|
+
sendMenuChoice(name, more);
|
|
3675
|
+
effortScreen = await waitForScreenText(name, "Advanced Reasoning");
|
|
3676
|
+
choice = codexEffortChoice(effortScreen, effort);
|
|
3677
|
+
}
|
|
3678
|
+
}
|
|
3679
|
+
if (!choice)
|
|
3680
|
+
throw new AitermError(`Codexの/modelに reasoning_effort=${effort} がありません`, 2);
|
|
3681
|
+
sendMenuChoice(name, choice);
|
|
3682
|
+
}
|
|
3683
|
+
else {
|
|
3684
|
+
sendMenuChoice(name, "Enter");
|
|
3685
|
+
}
|
|
3686
|
+
await waitForScreenText(name, "Model changed to");
|
|
3687
|
+
return {
|
|
3688
|
+
schema: "aiterm.agent-configure-result.v1",
|
|
3689
|
+
session_id: name,
|
|
3690
|
+
provider: "codex",
|
|
3691
|
+
model,
|
|
3692
|
+
reasoning_effort: effort,
|
|
3693
|
+
};
|
|
3694
|
+
}
|
|
3695
|
+
export function __testCodexConfigureChoices(screen, model, effort) {
|
|
3696
|
+
return {
|
|
3697
|
+
model: codexModelChoice(screen, model),
|
|
3698
|
+
effort: codexEffortChoice(screen, effort),
|
|
3699
|
+
more: codexMoreReasoningChoice(screen),
|
|
3700
|
+
};
|
|
3701
|
+
}
|
|
3566
3702
|
// v0.16.0: 親をブロックする wait 経路は廃止した。send は ready gate と submit 分離を内蔵した
|
|
3567
3703
|
// dispatch として即返り、event_cursor(送信直前のvendor完了正本境界)を receipt で返す。
|
|
3568
3704
|
// 完了通知は 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"
|
|
@@ -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
|
-
|
|
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
|
-
|
|
553
|
-
|
|
554
|
-
|
|
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
|
-
|
|
588
|
-
|
|
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.
|
|
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": [
|