aiterm-mcp 0.58.2 → 0.58.3
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 +29 -1
- package/README.ja.md +1 -1
- package/README.md +4 -2
- package/dist/core.js +171 -9
- package/dist/harnesses/claude.js +91 -0
- package/dist/index.js +6 -1
- package/docs/DESIGN.md +2 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,33 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.58.3] - 2026-10-12
|
|
11
|
+
|
|
12
|
+
### 修正
|
|
13
|
+
|
|
14
|
+
- `agent_configure`が、Claude Codeの席のmodel/effortを替える時に、端末の`~/.claude/settings.json`の既定を書き換えていた件を直す(ADR 0111)。
|
|
15
|
+
今までは`/model <名前>`と`/effort <値>`を打っていた。Claude Code(2.1.293・2.1.296で見た)は、この2つで選んだ値を
|
|
16
|
+
「新しい会話の既定」として保存する(`model`と、`modelSettings.<機種>.effortLevel`)。1つの席を替えただけで、その端末でこの後に
|
|
17
|
+
引数なしで立つ全部のClaude Codeの既定が変わり、利用者が自分で決めた既定も上書きしていた。
|
|
18
|
+
- `/model`を引数なしで開いた選ぶ画面を読み、行とeffortを合わせて、`s`(use this session only)で選ぶ。Enter(set as default)は押さない。
|
|
19
|
+
- 会話がcacheされている席で出る「Switch model?」の確かめは、1番(Yes, switch to …)が選ばれている時だけ進める。
|
|
20
|
+
- `model`は、catalogのID(`claude-opus-5-5`)、別名(`opus`)、表示名(`Opus 5.5`)のどれでも受ける。
|
|
21
|
+
- 選べない時は、選ぶ画面をEscで閉じて、何も変えずに断る。`s`の選び方が無い版では、保存する選び方へ落ちずに断る
|
|
22
|
+
(`CLAUDE_MODEL_SESSION_ONLY_UNAVAILABLE`)。選ぶ画面に無いmodelは`CLAUDE_MODEL_NOT_IN_PICKER`(文に選べる機種を並べる)、
|
|
23
|
+
行はあるが選べないmodel(席のClaude Codeが古い時の「Update Claude Code to use this model」)は`CLAUDE_MODEL_NOT_SELECTABLE`、
|
|
24
|
+
そのmodelで選べないeffortは`CLAUDE_EFFORT_NOT_SELECTABLE`。今までは、Claude Codeが受け付けない値でも成功で返っていた。
|
|
25
|
+
- `reasoning_effort`が`low`/`medium`/`high`/`xhigh`/`max`/`ultracode`のどれでもない時は、打つ前に断る。
|
|
26
|
+
- 引数と返りの形(`aiterm.agent-configure-result.v1`)、Codex・Grok・Cursorの席の動きは変わらない。
|
|
27
|
+
- 「この会話だけ」の替えなので、席を閉じて立て直すと端末の既定へ戻る。立て直す時は`agent_launch`へ同じ値を渡す。
|
|
28
|
+
- 選ぶ画面に無いmodel(`opus[1m]`のような名前、古い機種のID)は、動いている席では替えられなくなる。今までは`/model <名前>`で通っていた
|
|
29
|
+
(端末の既定へ保存されていた)。席を閉じて、`agent_launch`の`model`で渡して立て直す。
|
|
30
|
+
|
|
31
|
+
### 追加
|
|
32
|
+
|
|
33
|
+
- `pty_list`の構造の返り(`aiterm.pty-list-result.v1`)の席ごとの項目に、harness側の会話ID`vendor_session_id`を足す。
|
|
34
|
+
Claude CodeとGrokは起動時に`--session-id`で渡した値(席の中で会話を移した後は追わない)、CodexとCursorは掴んだ後の値。
|
|
35
|
+
通常PTYと、まだ分からない席は`null`。文字の返りには前から載っていた。`remote`の先が0.58.2以前の時は、項目そのものが無い。
|
|
36
|
+
|
|
10
37
|
## [0.58.2] - 2026-10-11
|
|
11
38
|
|
|
12
39
|
### 修正
|
|
@@ -2497,7 +2524,8 @@ prototype (preserved under `prototype/python/` as the porting source and referen
|
|
|
2497
2524
|
`ubuntu-latest` for Node 18/20/22, publishing to npm on `v*` tags with
|
|
2498
2525
|
provenance.
|
|
2499
2526
|
|
|
2500
|
-
[Unreleased]: https://github.com/kitepon/aiterm-mcp/compare/v0.58.
|
|
2527
|
+
[Unreleased]: https://github.com/kitepon/aiterm-mcp/compare/v0.58.3...HEAD
|
|
2528
|
+
[0.58.3]: https://github.com/kitepon/aiterm-mcp/compare/v0.58.2...v0.58.3
|
|
2501
2529
|
[0.58.2]: https://github.com/kitepon/aiterm-mcp/compare/v0.58.1...v0.58.2
|
|
2502
2530
|
[0.58.1]: https://github.com/kitepon/aiterm-mcp/compare/v0.58.0...v0.58.1
|
|
2503
2531
|
[0.58.0]: https://github.com/kitepon/aiterm-mcp/compare/v0.57.4...v0.58.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.58.
|
|
212
|
+
**状態:** 開発継続中 · 現行公開版 **v0.58.3** · 動作対象は Linux · WSL2 · macOS · Windows ネイティブ · MIT · [変更履歴](CHANGELOG.md)。
|
|
213
213
|
|
|
214
214
|
### 更新と巻き戻し
|
|
215
215
|
|
package/README.md
CHANGED
|
@@ -227,7 +227,7 @@ collection is off by default and performs no network I/O. It ships via
|
|
|
227
227
|
tag-triggered CI with npm provenance (OIDC Trusted Publishing); the GitHub
|
|
228
228
|
Release re-registers the Official MCP Registry entry.
|
|
229
229
|
|
|
230
|
-
**Status:** actively maintained · current public release **v0.58.
|
|
230
|
+
**Status:** actively maintained · current public release **v0.58.3** · 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).
|
|
231
231
|
|
|
232
232
|
### Update and rollback
|
|
233
233
|
|
|
@@ -622,7 +622,7 @@ continue to use `claude_approval`.
|
|
|
622
622
|
| `pty_read` | Read output, token-reduced (incremental by default) | `session_id`, `wait`, `until`, `until_regex`, `timeout`, `screen`, `full`, `lines`, `line_range`, `raw`, `rtk`, `agent_transcript`, `operation_id` |
|
|
623
623
|
| `pty_key` | Send a control key | `session_id`, `key` (`C-c`/`Enter`/`Up`…) |
|
|
624
624
|
| `pty_close` | Close idempotently; return `closed` / `already_closed` | `session_id` |
|
|
625
|
-
| `pty_list` | Text and structured session list, with explicitly requested non-secret environment values | `env_keys?` |
|
|
625
|
+
| `pty_list` | Text and structured session list (each agent session carries its harness conversation ID as `vendor_session_id`), with explicitly requested non-secret environment values | `env_keys?` |
|
|
626
626
|
| `pty_observe` | Pane/harness liveness, native process identity, state, and activity | `session_id`, `cursor?` |
|
|
627
627
|
| `agent_launch` | Canonical agent launch; harness and model are independent | `harness`, `prompt?`, `model?`, `reasoning_effort?`, `cwd?`, `write_scope?`, `trust_project?`, `env_vars?`, `throughline_source_session?`, `throughline_supplement_file?` |
|
|
628
628
|
| `agent_auth` | 公式CLIの認証を開始・確認・取消し、公式URL・device code・入力待ちを返す | `harness`, `action`, `session_id?`, `cwd?`, `env_vars?`, `relogin?` |
|
|
@@ -664,6 +664,8 @@ enables collection on that host.
|
|
|
664
664
|
|
|
665
665
|
`agent_configure({ session_id, model?, reasoning_effort? })` changes a running Claude, Codex, Grok, or Cursor TUI through the harness's standard controls, preserving the PTY and conversation context.
|
|
666
666
|
|
|
667
|
+
Claude Codeの席では、`/model`の選ぶ画面の`s`(use this session only)で替える。`/model <名前>`・`/effort <値>`は選んだ値を端末の`~/.claude/settings.json`へ新しい会話の既定として保存するので、打たない(ADR 0111)。その選び方が無い版、選ぶ画面に無いmodel、そのmodelで選べないeffortは、何も変えずに断る(`CLAUDE_MODEL_SESSION_ONLY_UNAVAILABLE`・`CLAUDE_MODEL_NOT_IN_PICKER`・`CLAUDE_EFFORT_NOT_SELECTABLE`)。席を閉じて立て直すと端末の既定へ戻るので、立て直す時は`agent_launch`へ同じ`model`・`reasoning_effort`を渡す。
|
|
668
|
+
|
|
667
669
|
`agent_auth({ harness, action:"start"|"status"|"cancel", session_id?, cwd?, env_vars?, relogin? })`は、各harnessの公式CLIで認証を進める。Claudeは`claude auth login`、CodexとGrokは`login --device-auth`、Cursorは`NO_OPEN_BROWSER=1 cursor-agent login`を使い、資格情報は各CLIだけが保存する。Aitermは資格情報を読取り・copy・編集せず、独自OAuthも実装しない。`remote`は他toolと同じ標準対応。
|
|
668
670
|
|
|
669
671
|
`start`は、公式の状態が認証済みなら何も起こさず`authenticated`(`session_id:null`)を返す。`relogin:true`を付けると、認証済みに見えても公式ログインを開始し、`session_id`付きの結果を返す。別のアカウントへ入り直す時と、ログインの期限切れを状態から見抜けない時に使う。Aitermは資格情報を消さず、置き換えは公式CLIが行う。ただし、公式CLIがログインを始めた時点で元のログインを消す事がある(Codex 0.160.0の`codex login --device-auth`は、始めた時点で`auth.json`を消す。途中で`cancel`しても戻らない)。Claude CodeとCursorは、途中で`cancel`すれば元の資格情報が残る(無効な資格情報で確認)。
|
package/dist/core.js
CHANGED
|
@@ -23,7 +23,7 @@ import { sleep, currentUid, runtimeStateBase, safeStatSize, readFileRange, write
|
|
|
23
23
|
import { catalogUnavailable } from "./model-catalog.js";
|
|
24
24
|
import { grokAuthPlan, grokAuthStatus, grokAuthPane, realGrokHome, resolveAndValidateGrokAuth, assertGrokModelAvailable, grokModelChoices, grokEventsTranscript, latestGrokCompletion, grokTurnSettled, observeGrokDone, buildGrokAgentCmd, grokLaunchNote, grokEnvTokens, grokTuiReady, grokTuiBusy, grokPaneObservation, grokRateLimitDialog, grokPromptAwaitingAccept, grokUnacceptedPromptRestored, grokStartupAction, grokLaunchBlockingDialog, assertGrokSandboxNotRejected, GROK_COMPOSER_MARKER_RE, grokFooterHasConfiguration, grokTranscriptText, createGrokAgentMetadata, } from "./harnesses/grok.js";
|
|
25
25
|
import { codexAuthPlan, codexAuthStatus, codexAuthPane, bindCodexTranscriptSession, latestCodexCompletion, codexTurnSettled, codexRunningTurn, 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
|
-
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";
|
|
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, claudeModelPicker, claudeModelPickerRow, claudeModelSwitchConfirm, claudeModelSwitchResult, 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";
|
|
29
29
|
import { resolveAgentBin, resolveThroughlineBin, runThroughlineHandoffContext, isWindowsNativeExecutable, agentBinForPaneShell, resolveWinPaneShell } from "./agent-resolver.js";
|
|
@@ -1338,7 +1338,8 @@ export function listSessionsResult(envKeys = []) {
|
|
|
1338
1338
|
}
|
|
1339
1339
|
const meta = tryLoadAgentMetadata(name);
|
|
1340
1340
|
sessions.push({ session_id: name, current_command: normalizePaneCommand(command), attached: Number(attached) > 0,
|
|
1341
|
-
width: Number(width), height: Number(height), harness: meta ? agentHarness(meta.kind) : null,
|
|
1341
|
+
width: Number(width), height: Number(height), harness: meta ? agentHarness(meta.kind) : null,
|
|
1342
|
+
vendor_session_id: meta?.vendor_session_id ?? null, environment });
|
|
1342
1343
|
}
|
|
1343
1344
|
return { schema: "aiterm.pty-list-result.v1", observed_at: new Date().toISOString(), sessions };
|
|
1344
1345
|
}
|
|
@@ -4119,6 +4120,166 @@ export async function listAgentModels(kind, options = {}) {
|
|
|
4119
4120
|
case "cursor": return cursorModelChoices(bin, cwd);
|
|
4120
4121
|
}
|
|
4121
4122
|
}
|
|
4123
|
+
const CLAUDE_MODEL_PICKER_TIMEOUT_MS = 8_000;
|
|
4124
|
+
const CLAUDE_MODEL_PICKER_KEY_TIMEOUT_MS = 1_500;
|
|
4125
|
+
const CLAUDE_MODEL_SWITCH_TIMEOUT_MS = 15_000;
|
|
4126
|
+
const CLAUDE_EFFORT_ORDER = ["low", "medium", "high", "xhigh", "max"];
|
|
4127
|
+
/**
|
|
4128
|
+
* Claude Codeの席のmodel/effortを、この会話だけで替える(ADR 0111)。
|
|
4129
|
+
* `/model <名前>` と `/effort <値>` は、選んだ値を端末の ~/.claude/settings.json へ新しい会話の既定として保存する
|
|
4130
|
+
* (2.1.296で確かめた)。引数なしの `/model` の選ぶ画面で s を押すと保存されないので、画面を1歩ずつ読んでそちらを打つ。
|
|
4131
|
+
* 保存しない選び方が画面に無い時、行やeffortを選べない時は、Escで閉じて何も変えずに断る。
|
|
4132
|
+
*/
|
|
4133
|
+
async function configureClaudeForSessionOnly(name, meta, model, effort) {
|
|
4134
|
+
const screen = () => captureScreen(name, AGENT_TUI_READY_LINES);
|
|
4135
|
+
const until = async (read, timeoutMs) => {
|
|
4136
|
+
const deadline = performance.now() + timeoutMs;
|
|
4137
|
+
do {
|
|
4138
|
+
const got = read();
|
|
4139
|
+
if (got)
|
|
4140
|
+
return got;
|
|
4141
|
+
await sleep(100);
|
|
4142
|
+
} while (performance.now() < deadline);
|
|
4143
|
+
return null;
|
|
4144
|
+
};
|
|
4145
|
+
// Escは、選ぶ画面か確かめの画面が出ている間だけ打つ(入力欄へ戻った後のEscは、ほかの操作を取り消してしまう)。
|
|
4146
|
+
const refuse = async (code, reason) => {
|
|
4147
|
+
for (let i = 0; i < 3; i++) {
|
|
4148
|
+
const now = screen();
|
|
4149
|
+
if (!claudeModelPicker(now) && !claudeModelSwitchConfirm(now))
|
|
4150
|
+
break;
|
|
4151
|
+
sendMenuChoice(name, "Escape");
|
|
4152
|
+
await sleep(300);
|
|
4153
|
+
}
|
|
4154
|
+
throw new AitermError(`${code} vendor=claude session=${name}\n${reason}。modelとreasoning_effortは変えていません。`, 2);
|
|
4155
|
+
};
|
|
4156
|
+
const picker = () => claudeModelPicker(screen());
|
|
4157
|
+
const cursorOf = (p) => p?.rows.find((row) => row.cursor) ?? null;
|
|
4158
|
+
// 1回押して、画面の今の行かeffortが変わるまで待つ。変わらなければnull(端で止まった・キーが落ちた)。
|
|
4159
|
+
// キーが落ちる事があるので、変わらなければ1度だけ押し直す。
|
|
4160
|
+
const press = async (key, changed) => {
|
|
4161
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
4162
|
+
sendMenuChoice(name, key);
|
|
4163
|
+
const next = await until(() => { const p = picker(); return p && changed(p) ? p : null; }, CLAUDE_MODEL_PICKER_KEY_TIMEOUT_MS);
|
|
4164
|
+
if (next)
|
|
4165
|
+
return next;
|
|
4166
|
+
}
|
|
4167
|
+
return null;
|
|
4168
|
+
};
|
|
4169
|
+
await sendAgentPromptText(name, "/model");
|
|
4170
|
+
let now = await until(picker, CLAUDE_MODEL_PICKER_TIMEOUT_MS);
|
|
4171
|
+
if (!now) {
|
|
4172
|
+
// 読めない形の画面が開いたままだと、席が次の文を受け取れない。入力待ちでも動作中でもない時だけ、Escを1度打って閉じる。
|
|
4173
|
+
let state = claudePaneObservation(screen());
|
|
4174
|
+
if (state.state === "unknown") {
|
|
4175
|
+
sendMenuChoice(name, "Escape");
|
|
4176
|
+
await sleep(500);
|
|
4177
|
+
state = claudePaneObservation(screen());
|
|
4178
|
+
}
|
|
4179
|
+
throw new AitermError(`CLAUDE_MODEL_PICKER_NOT_SHOWN vendor=claude session=${name} state=${state.state}/${state.reason}\n` +
|
|
4180
|
+
"Claude Codeの /model の選ぶ画面を読めません(Claude Codeの版で画面の形が変わった見込み)。modelとreasoning_effortは変えていません。", 2);
|
|
4181
|
+
}
|
|
4182
|
+
if (!now.session_only) {
|
|
4183
|
+
return refuse("CLAUDE_MODEL_SESSION_ONLY_UNAVAILABLE", "このClaude Codeの /model の選ぶ画面に、この会話だけで使う選び方(s to use this session only)がありません。" +
|
|
4184
|
+
"端末の既定を書き換える選び方は使いません");
|
|
4185
|
+
}
|
|
4186
|
+
let targetIndex = null;
|
|
4187
|
+
if (model) {
|
|
4188
|
+
// 見えていない行は、下へ送りながら集める(一周するか、端で止まるまで)。
|
|
4189
|
+
const known = new Map(now.rows.map((row) => [row.index, row]));
|
|
4190
|
+
const start = cursorOf(now);
|
|
4191
|
+
if (!start)
|
|
4192
|
+
return refuse("CLAUDE_MODEL_PICKER_UNREADABLE", "選ぶ画面の今の行を読めません");
|
|
4193
|
+
let at = start.index;
|
|
4194
|
+
const seen = new Set([at]);
|
|
4195
|
+
for (let i = 0; i < 80; i++) {
|
|
4196
|
+
const before = at;
|
|
4197
|
+
const next = await press("Down", (p) => { const c = cursorOf(p); return !!c && c.index !== before; });
|
|
4198
|
+
if (!next)
|
|
4199
|
+
break;
|
|
4200
|
+
now = next;
|
|
4201
|
+
for (const row of next.rows)
|
|
4202
|
+
known.set(row.index, row);
|
|
4203
|
+
at = cursorOf(next).index;
|
|
4204
|
+
if (seen.has(at))
|
|
4205
|
+
break;
|
|
4206
|
+
seen.add(at);
|
|
4207
|
+
}
|
|
4208
|
+
const target = claudeModelPickerRow([...known.values()], model);
|
|
4209
|
+
if (!target) {
|
|
4210
|
+
const names = [...known.values()].sort((a, b) => a.index - b.index).map((row) => row.label).join(", ");
|
|
4211
|
+
return refuse("CLAUDE_MODEL_NOT_IN_PICKER", `Claude Codeの /model の選ぶ画面に ${model} がありません(選べる機種: ${names})`);
|
|
4212
|
+
}
|
|
4213
|
+
for (let i = 0; i < 80 && at !== target.index; i++) {
|
|
4214
|
+
const before = at;
|
|
4215
|
+
const next = await press(target.index > at ? "Down" : "Up", (p) => { const c = cursorOf(p); return !!c && c.index !== before; });
|
|
4216
|
+
if (!next)
|
|
4217
|
+
break;
|
|
4218
|
+
now = next;
|
|
4219
|
+
at = cursorOf(next).index;
|
|
4220
|
+
}
|
|
4221
|
+
if (at !== target.index)
|
|
4222
|
+
return refuse("CLAUDE_MODEL_PICKER_UNREADABLE", `選ぶ画面で ${target.label} の行へ動けません`);
|
|
4223
|
+
// 起きた後にClaude Codeが更新された席では、「Update Claude Code to use this model」の行が出る。その行にはEnterもsも無い。
|
|
4224
|
+
if (!now.session_only) {
|
|
4225
|
+
return refuse("CLAUDE_MODEL_NOT_SELECTABLE", `${target.label} は、この席のClaude Codeでは選べません(選ぶ画面のその行に、選ぶためのキーが出ていません。席のClaude Codeが古い見込み)`);
|
|
4226
|
+
}
|
|
4227
|
+
targetIndex = target.index;
|
|
4228
|
+
}
|
|
4229
|
+
if (effort) {
|
|
4230
|
+
const wanted = CLAUDE_EFFORT_ORDER.indexOf(effort);
|
|
4231
|
+
const row = cursorOf(now)?.label ?? model ?? "このmodel";
|
|
4232
|
+
// そのmodelに無い段は飛ばされて行き来になるので、回数で打ち切る。
|
|
4233
|
+
for (let i = 0; i < CLAUDE_EFFORT_ORDER.length + 3 && now.effort !== effort; i++) {
|
|
4234
|
+
const current = now.effort;
|
|
4235
|
+
const from = current === null ? -1 : CLAUDE_EFFORT_ORDER.indexOf(current);
|
|
4236
|
+
if (from < 0)
|
|
4237
|
+
return refuse("CLAUDE_EFFORT_NOT_SELECTABLE", `${row} では reasoning_effort を選べません`);
|
|
4238
|
+
const next = await press(wanted > from ? "Right" : "Left", (p) => p.effort !== current);
|
|
4239
|
+
if (!next)
|
|
4240
|
+
break;
|
|
4241
|
+
now = next;
|
|
4242
|
+
}
|
|
4243
|
+
if (now.effort !== effort)
|
|
4244
|
+
return refuse("CLAUDE_EFFORT_NOT_SELECTABLE", `${row} では reasoning_effort=${effort} を選べません`);
|
|
4245
|
+
}
|
|
4246
|
+
// 打つ直前に、もう1度読んで確かめる。
|
|
4247
|
+
const last = picker();
|
|
4248
|
+
const lastRow = cursorOf(last);
|
|
4249
|
+
if (!last || !lastRow || !last.session_only
|
|
4250
|
+
|| (targetIndex !== null && lastRow.index !== targetIndex)
|
|
4251
|
+
|| (effort && last.effort !== effort)) {
|
|
4252
|
+
return refuse("CLAUDE_MODEL_PICKER_UNREADABLE", "選ぶ直前の画面が、選んだ行・effortと合いません");
|
|
4253
|
+
}
|
|
4254
|
+
sendMenuChoice(name, "s");
|
|
4255
|
+
let confirmed = false;
|
|
4256
|
+
const result = await until(() => {
|
|
4257
|
+
const text = screen();
|
|
4258
|
+
const confirm = claudeModelSwitchConfirm(text);
|
|
4259
|
+
if (confirm === "yes_selected" && !confirmed) {
|
|
4260
|
+
// 会話が今のmodelでcacheされている時の確かめ。1番(Yes, switch)が選ばれている時だけ進める。
|
|
4261
|
+
confirmed = true;
|
|
4262
|
+
sendMenuChoice(name, "Enter");
|
|
4263
|
+
return null;
|
|
4264
|
+
}
|
|
4265
|
+
if (confirm || claudeModelPicker(text))
|
|
4266
|
+
return null;
|
|
4267
|
+
return claudeModelSwitchResult(text);
|
|
4268
|
+
}, CLAUDE_MODEL_SWITCH_TIMEOUT_MS);
|
|
4269
|
+
if (!result)
|
|
4270
|
+
return refuse("CLAUDE_MODEL_SWITCH_UNCONFIRMED", "Claude Codeの切り替えの知らせを確認できません");
|
|
4271
|
+
if (result.scope === "saved_as_default") {
|
|
4272
|
+
throw new AitermError(`CLAUDE_MODEL_SAVED_AS_DEFAULT vendor=claude session=${name}\n` +
|
|
4273
|
+
`Claude Codeが、選んだ値を端末の既定として保存したと返しました(${result.line})。~/.claude/settings.json を確かめてください。`, 2);
|
|
4274
|
+
}
|
|
4275
|
+
if (result.scope !== "session_only") {
|
|
4276
|
+
throw new AitermError(`CLAUDE_MODEL_SWITCH_REFUSED vendor=claude session=${name}\n` +
|
|
4277
|
+
`Claude Codeがmodelを切り替えませんでした(${result.line})。modelとreasoning_effortは変えていません。`, 2);
|
|
4278
|
+
}
|
|
4279
|
+
const ready = await waitAgentTuiReady(name, meta, AGENT_TUI_READY_TIMEOUT_MS);
|
|
4280
|
+
if (!ready.ready)
|
|
4281
|
+
throw new AitermError("Claudeのmodel変更完了を確認できません", 2);
|
|
4282
|
+
}
|
|
4122
4283
|
export async function configureAgent(name, opts) {
|
|
4123
4284
|
assertSessionName(name);
|
|
4124
4285
|
const model = opts.model?.trim() || null;
|
|
@@ -4138,13 +4299,14 @@ export async function configureAgent(name, opts) {
|
|
|
4138
4299
|
throw new AitermError(`agent session '${name}' は入力待ちではありません`, 2);
|
|
4139
4300
|
}
|
|
4140
4301
|
if (meta.kind === "claude") {
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
4302
|
+
// ultracodeはClaude Codeが自分で「this session only」と返すsession設定で、選ぶ画面には出ない。
|
|
4303
|
+
const level = effort && CLAUDE_EFFORTS.has(effort) ? effort : null;
|
|
4304
|
+
if (effort && !level && effort !== "ultracode") {
|
|
4305
|
+
throw new AitermError("Claude Code の reasoning_effort は low/medium/high/xhigh/max/ultracode のいずれかです。設定は変えていません。", 2);
|
|
4306
|
+
}
|
|
4307
|
+
if (model || level)
|
|
4308
|
+
await configureClaudeForSessionOnly(name, meta, model, level);
|
|
4309
|
+
if (effort && !level) {
|
|
4148
4310
|
await sendAgentPromptText(name, `/effort ${effort}`);
|
|
4149
4311
|
const effortReady = await waitAgentTuiReady(name, meta, AGENT_TUI_READY_TIMEOUT_MS);
|
|
4150
4312
|
if (!effortReady.ready)
|
package/dist/harnesses/claude.js
CHANGED
|
@@ -373,6 +373,97 @@ export function claudeUsageLimit(screen) {
|
|
|
373
373
|
}
|
|
374
374
|
return null;
|
|
375
375
|
}
|
|
376
|
+
function lastLineIndex(lines, re) {
|
|
377
|
+
for (let i = lines.length - 1; i >= 0; i--)
|
|
378
|
+
if (re.test(lines[i]))
|
|
379
|
+
return i;
|
|
380
|
+
return -1;
|
|
381
|
+
}
|
|
382
|
+
export function claudeModelPicker(screen) {
|
|
383
|
+
const lines = screen.replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "").split(/\r?\n/u);
|
|
384
|
+
const head = lastLineIndex(lines, /^\s*Select model\s*$/u);
|
|
385
|
+
if (head < 0)
|
|
386
|
+
return null;
|
|
387
|
+
const body = lines.slice(head + 1);
|
|
388
|
+
const foot = body.findIndex((line) => /\bEsc to cancel\b/u.test(line));
|
|
389
|
+
if (foot < 0)
|
|
390
|
+
return null;
|
|
391
|
+
const rows = [];
|
|
392
|
+
let effort = null;
|
|
393
|
+
for (const line of body.slice(0, foot)) {
|
|
394
|
+
const row = /^\s*([❯↓↑]\s+)?(\d+)\.\s+(.*?)\s*$/u.exec(line);
|
|
395
|
+
if (row) {
|
|
396
|
+
const name = row[3].split(/\s{2,}/u)[0];
|
|
397
|
+
rows.push({
|
|
398
|
+
index: Number(row[2]),
|
|
399
|
+
label: name.replace(/\s*✔$/u, ""),
|
|
400
|
+
cursor: row[1]?.includes("❯") === true,
|
|
401
|
+
current: /✔$/u.test(name),
|
|
402
|
+
});
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
405
|
+
const level = /(?:^|\s)(low|medium|high|xhigh|max) effort\b/iu.exec(line);
|
|
406
|
+
if (level)
|
|
407
|
+
effort = level[1].toLowerCase();
|
|
408
|
+
}
|
|
409
|
+
if (rows.length === 0)
|
|
410
|
+
return null;
|
|
411
|
+
return { rows, effort, session_only: /\bs to use this session only\b/u.test(body[foot]) };
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* 指定されたmodel(catalogのID・別名・表示名)が、選ぶ画面のどの行かを決める。
|
|
415
|
+
* `claude-opus-5-5` は「Opus 5.5」、別名の `opus` はその系統で番号が一番若い行(catalogの並びで最新)。
|
|
416
|
+
*/
|
|
417
|
+
export function claudeModelPickerRow(rows, model) {
|
|
418
|
+
const want = model.trim().toLowerCase();
|
|
419
|
+
const sorted = [...rows].sort((a, b) => a.index - b.index);
|
|
420
|
+
const id = /^claude-([a-z]+)-(\d+)(?:-(\d{1,2}))?(?:-\d{8})?$/u.exec(want);
|
|
421
|
+
const name = id ? `${id[1]} ${id[2]}${id[3] ? `.${id[3]}` : ""}` : want;
|
|
422
|
+
return sorted.find((row) => row.label.toLowerCase() === name)
|
|
423
|
+
?? (want === "default" ? sorted.find((row) => /^default\b/iu.test(row.label)) : undefined)
|
|
424
|
+
?? (/^[a-z]+$/u.test(want) ? sorted.find((row) => row.label.toLowerCase().startsWith(`${want} `) && /^\S+ \d/u.test(row.label)) : undefined)
|
|
425
|
+
?? null;
|
|
426
|
+
}
|
|
427
|
+
// 会話が今のmodelでcacheされている時だけ、sの後に出る確かめ(2.1.293・2.1.296)。
|
|
428
|
+
// Switch model?
|
|
429
|
+
// Your next response will be slower and use more tokens
|
|
430
|
+
// ❯ 1. Yes, switch to Opus 5.5
|
|
431
|
+
// 2. No, go back
|
|
432
|
+
export function claudeModelSwitchConfirm(screen) {
|
|
433
|
+
const lines = screen.replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "").split(/\r?\n/u);
|
|
434
|
+
const head = lastLineIndex(lines, /^\s*Switch model\?\s*$/u);
|
|
435
|
+
if (head < 0)
|
|
436
|
+
return null;
|
|
437
|
+
const body = lines.slice(head + 1);
|
|
438
|
+
if (!body.some((line) => /^\s*(?:❯\s*)?1\.\s*Yes, switch to\b/u.test(line)))
|
|
439
|
+
return null;
|
|
440
|
+
return body.some((line) => /^\s*❯\s*1\.\s*Yes, switch to\b/u.test(line)) ? "yes_selected" : "other_selected";
|
|
441
|
+
}
|
|
442
|
+
export function claudeModelSwitchResult(screen) {
|
|
443
|
+
const lines = screen.replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "").split(/\r?\n/u);
|
|
444
|
+
const command = lastLineIndex(lines, /^\s*❯\s*\/model\s*$/u);
|
|
445
|
+
if (command < 0)
|
|
446
|
+
return null;
|
|
447
|
+
const start = lines.findIndex((line, i) => i > command && /^\s*⎿\s+\S/u.test(line));
|
|
448
|
+
if (start < 0)
|
|
449
|
+
return null;
|
|
450
|
+
const parts = [lines[start].replace(/^\s*⎿\s+/u, "").trim()];
|
|
451
|
+
for (const line of lines.slice(start + 1)) {
|
|
452
|
+
if (!/^\s{3,}\S/u.test(line) || /^\s*⎿/u.test(line))
|
|
453
|
+
break;
|
|
454
|
+
parts.push(line.trim());
|
|
455
|
+
}
|
|
456
|
+
const line = parts.join(" ");
|
|
457
|
+
if (/\bblocked by a PreModelSwitch hook\b/u.test(line))
|
|
458
|
+
return { scope: "blocked", line };
|
|
459
|
+
if (/^Kept model as\b/u.test(line))
|
|
460
|
+
return { scope: "kept", line };
|
|
461
|
+
if (!/^(?:Set model to|Model set to)\b/u.test(line))
|
|
462
|
+
return null;
|
|
463
|
+
if (/\bsaved as your default\b/u.test(line))
|
|
464
|
+
return { scope: "saved_as_default", line };
|
|
465
|
+
return /\bfor this session only\b/u.test(line) ? { scope: "session_only", line } : null;
|
|
466
|
+
}
|
|
376
467
|
// Claude Code 2.1.282 は選択肢へ「1.」などの番号を付ける。番号の有無のどちらも受ける。
|
|
377
468
|
export function claudeLoginMethodMenu(screen) {
|
|
378
469
|
const tail = screen.split("\n").slice(-32).join("\n");
|
package/dist/index.js
CHANGED
|
@@ -618,6 +618,9 @@ registerRemoteAwareTool("pty_list", {
|
|
|
618
618
|
session_id: z.string(), current_command: z.string(), attached: z.boolean(),
|
|
619
619
|
width: z.number(), height: z.number(),
|
|
620
620
|
harness: z.enum(["claude-code", "codex-cli", "grok-cli", "cursor-cli"]).nullable(),
|
|
621
|
+
// 0.58.3より前の現地(remote)は返さないので、無い事も受ける。
|
|
622
|
+
vendor_session_id: z.string().nullable().optional().describe("agent sessionのharness側の会話ID。Claude CodeとGrokは起動時に--session-idで渡した値(席の中で会話を移した後は追わない)、" +
|
|
623
|
+
"CodexとCursorは分かった後の値。通常PTYと、まだ分からない席はnull"),
|
|
621
624
|
environment: z.record(z.string(), z.string().nullable()),
|
|
622
625
|
})),
|
|
623
626
|
},
|
|
@@ -833,7 +836,9 @@ registerRemoteAwareTool("agent_auth", {
|
|
|
833
836
|
});
|
|
834
837
|
registerRemoteAwareTool("agent_configure", {
|
|
835
838
|
description: "起動済みのClaude/Codex/Grok/Cursor agent sessionを再起動せず、会話contextを保ったままmodel/reasoning effortを変更する。" +
|
|
836
|
-
"各harnessのCLI標準model操作を使う。Cursorのreasoning_effort変更はmodelと同時指定する。"
|
|
839
|
+
"各harnessのCLI標準model操作を使う。Cursorのreasoning_effort変更はmodelと同時指定する。" +
|
|
840
|
+
"Claude Codeの席では /model の選ぶ画面の「この会話だけ」(s)で替え、端末の ~/.claude/settings.json の既定は書き換えない。" +
|
|
841
|
+
"その選び方が無い版、選ぶ画面に無いmodel、そのmodelで選べないeffortは、何も変えずに断る。",
|
|
837
842
|
inputSchema: {
|
|
838
843
|
session_id: z.string().regex(/^[A-Za-z0-9_-]{1,64}$/),
|
|
839
844
|
model: z.string().min(1).nullish().describe("変更後のmodel。省略時はmodelを変更しない"),
|
package/docs/DESIGN.md
CHANGED
|
@@ -37,7 +37,8 @@ UTF-8のBase64を復元したscriptblockのdot-sourceを使う。変数と作業
|
|
|
37
37
|
sentinelの方言は端末runtimeが実効shellから決める。SSH先の現在の標準PowerShell promptを
|
|
38
38
|
検出した場合は、Aiterm hostのOSに関係なくPowerShell構文を使う。過去のpromptでは切り替えない。
|
|
39
39
|
|
|
40
|
-
`pty_list`はtextと構造化したsession
|
|
40
|
+
`pty_list`はtextと構造化したsession一覧を返す。agent sessionには、harness側の会話ID(`vendor_session_id`。Claude CodeとGrokは
|
|
41
|
+
起動時に`--session-id`で渡した値、CodexとCursorは掴んだ後の値)を載せる。`env_keys`は帰属等の非秘密キーの明示照会だけで、
|
|
41
42
|
psmuxが出力した余分な環境値を返さない。通常PTYとagentへ`AITERM_SESSION_ID`を注入し、
|
|
42
43
|
`env_vars`の継承とsessionへの登録もAitermが所有する。古いtmuxは子へのenv注入とsession登録を使う。
|
|
43
44
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aiterm-mcp",
|
|
3
|
-
"version": "0.58.
|
|
3
|
+
"version": "0.58.3",
|
|
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": [
|