aiterm-mcp 0.44.0 → 0.44.1

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,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.44.1] - 2026-09-30
11
+
12
+ ### 修正
13
+
14
+ - `agent_models`のGrokが、ときどき`MODEL_CATALOG_INVALID: initializeの応答がありません(exit=0 … stdout=0bytes)`で失敗していた。要求を書いてすぐ標準入力を閉じていたため、grok 1.0.41がinitializeの処理中に入力の終わりを受けて、応答せずに終わっていた。ClaudeとGrokは、応答が出力に現れるまで標準入力を開けておき、現れてから閉じる。macOSのAqua外(launchd経由)でも同じ待ち方をする。
15
+
10
16
  ## [0.44.0] - 2026-09-30
11
17
 
12
18
  ### 追加
@@ -1918,7 +1924,8 @@ prototype (preserved under `prototype/python/` as the porting source and referen
1918
1924
  `ubuntu-latest` for Node 18/20/22, publishing to npm on `v*` tags with
1919
1925
  provenance.
1920
1926
 
1921
- [Unreleased]: https://github.com/kitepon/aiterm-mcp/compare/v0.44.0...HEAD
1927
+ [Unreleased]: https://github.com/kitepon/aiterm-mcp/compare/v0.44.1...HEAD
1928
+ [0.44.1]: https://github.com/kitepon/aiterm-mcp/compare/v0.44.0...v0.44.1
1922
1929
  [0.44.0]: https://github.com/kitepon/aiterm-mcp/compare/v0.43.7...v0.44.0
1923
1930
  [0.43.7]: https://github.com/kitepon/aiterm-mcp/compare/v0.43.6...v0.43.7
1924
1931
  [0.43.6]: https://github.com/kitepon/aiterm-mcp/compare/v0.43.5...v0.43.6
package/README.ja.md CHANGED
@@ -203,7 +203,7 @@ runtime-error store は canonical dotagents config の `collection.enabled: true
203
203
  場合だけ収集し、既定OFF、network送信は行いません。tag起点CIのnpm provenance(OIDC Trusted
204
204
  Publishing)で公開し、GitHub Release が Official MCP Registry を再登録します。
205
205
 
206
- **状態:** 開発継続中 · 現行公開版 **v0.44.0** · 動作対象は Linux · WSL2 · macOS · Windows ネイティブ · MIT · [変更履歴](CHANGELOG.md)。
206
+ **状態:** 開発継続中 · 現行公開版 **v0.44.1** · 動作対象は Linux · WSL2 · macOS · Windows ネイティブ · MIT · [変更履歴](CHANGELOG.md)。
207
207
 
208
208
  ### 更新と巻き戻し
209
209
 
package/README.md CHANGED
@@ -217,7 +217,7 @@ collection is off by default and performs no network I/O. It ships via
217
217
  tag-triggered CI with npm provenance (OIDC Trusted Publishing); the GitHub
218
218
  Release re-registers the Official MCP Registry entry.
219
219
 
220
- **Status:** actively maintained · current public release **v0.44.0** · 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).
220
+ **Status:** actively maintained · current public release **v0.44.1** · 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).
221
221
 
222
222
  ### Update and rollback
223
223
 
@@ -1,6 +1,6 @@
1
1
  // エージェント CLI(claude / codex / grok / cursor-agent)・Throughline・pane shell(Windows は Git Bash)の
2
2
  // 実行ファイルをどう見つけ、どう起動するかの所有者(OS 分岐の所有者。tmux とは独立)。
3
- import { spawnSync } from "node:child_process";
3
+ import { spawn, spawnSync } from "node:child_process";
4
4
  import * as fs from "node:fs";
5
5
  import * as path from "node:path";
6
6
  import * as os from "node:os";
@@ -75,11 +75,7 @@ export function spawnAgentControlCommand(bin, args, _cwd, options) {
75
75
  if (/\.(?:cmd|bat)$/i.test(bin)) {
76
76
  // Node は CVE-2024-27980 対処以降、.cmd/.bat の直接 spawn を EINVAL で拒否する。
77
77
  // 受入が .cmd/.bat を許す以上、control 経路は shell 経由で実行する(args は固定語彙とAiterm所有の一時path)。
78
- // shell経由ではNodeが引数を引用しないので、空白を含むpathだけ二重引用符でくくる。引用符とshell記号は受け付けない。
79
- if (args.some(arg => /["%^&|<>!]/.test(arg))) {
80
- throw new AitermError(`${path.basename(bin)} のcontrol commandへ渡せない文字を含む引数があります`, 2);
81
- }
82
- return spawnSync(bin, args.map(arg => /\s/.test(arg) ? `"${arg}"` : arg), { ...options, shell: true });
78
+ return spawnSync(bin, windowsControlArgs(bin, args), { ...options, shell: true });
83
79
  }
84
80
  // 受入は pane shell(Git Bash)が shebang で実行できる script も許す。Windows の
85
81
  // CreateProcess は shebang を解さないため、control command も同じ Git Bash で実行する。
@@ -88,6 +84,72 @@ export function spawnAgentControlCommand(bin, args, _cwd, options) {
88
84
  }
89
85
  return spawnInMacGuiWhenOutsideAqua(bin, args, options) ?? spawnSync(bin, args, options);
90
86
  }
87
+ function windowsControlArgs(bin, args) {
88
+ // shell経由ではNodeが引数を引用しないので、空白を含むpathだけ二重引用符でくくる。引用符とshell記号は受け付けない。
89
+ if (args.some(arg => /["%^&|<>!]/.test(arg))) {
90
+ throw new AitermError(`${path.basename(bin)} のcontrol commandへ渡せない文字を含む引数があります`, 2);
91
+ }
92
+ return args.map(arg => /\s/.test(arg) ? `"${arg}"` : arg);
93
+ }
94
+ /**
95
+ * 標準入出力で要求と応答をやりとりするcontrol command(`claude -p --input-format stream-json`、`grok agent stdio`)。
96
+ * inputを書いた後、untilの文字列がstdoutに現れるまで標準入力を開けておき、現れたら閉じて終了を待つ。
97
+ * 応答前に標準入力を閉じると、応答せずに正常終了するCLIがある(grok 1.0.41で実測)。
98
+ * 起動方法のOS差はspawnAgentControlCommandと同じ。macOSのAqua外はlaunchdの仕事の中で同じ待ち方をする。
99
+ */
100
+ export async function runAgentProtocolCommand(bin, args, options) {
101
+ const gui = spawnInMacGuiWhenOutsideAqua(bin, args, options);
102
+ if (gui)
103
+ return { status: gui.status, signal: gui.signal, stdout: gui.stdout, stderr: gui.stderr, ...(gui.error ? { error: gui.error } : {}) };
104
+ let command = bin;
105
+ let argv = args;
106
+ let shell = false;
107
+ if (isWin && !/\.(?:exe|com)$/i.test(bin)) {
108
+ if (/\.(?:cmd|bat)$/i.test(bin)) {
109
+ argv = windowsControlArgs(bin, args);
110
+ shell = true;
111
+ }
112
+ else {
113
+ command = resolveWinPaneShell("bash");
114
+ argv = [bin, ...args];
115
+ }
116
+ }
117
+ return new Promise((resolve) => {
118
+ const child = spawn(command, argv, { cwd: options.cwd, env: options.env, shell, windowsHide: true, stdio: ["pipe", "pipe", "pipe"] });
119
+ let stdout = "";
120
+ let stderr = "";
121
+ let error;
122
+ let settled = false;
123
+ const finish = (status, signal) => {
124
+ if (settled)
125
+ return;
126
+ settled = true;
127
+ clearTimeout(timer);
128
+ resolve({ status, signal, stdout, stderr, ...(error ? { error } : {}) });
129
+ };
130
+ const stop = (reason) => {
131
+ error ??= reason;
132
+ child.stdin.destroy();
133
+ child.kill();
134
+ };
135
+ const timer = setTimeout(() => stop(Object.assign(new Error(`${path.basename(bin)} ${args.join(" ")} が${options.timeout}ms以内に応答しませんでした`), { code: "ETIMEDOUT" })), options.timeout);
136
+ child.on("error", (reason) => { error ??= reason; finish(null, null); });
137
+ child.on("close", (status, signal) => finish(status, signal));
138
+ child.stdin.on("error", () => { });
139
+ child.stderr.setEncoding("utf8");
140
+ child.stderr.on("data", (chunk) => { if (stderr.length < options.maxBuffer)
141
+ stderr += chunk; });
142
+ child.stdout.setEncoding("utf8");
143
+ child.stdout.on("data", (chunk) => {
144
+ stdout += chunk;
145
+ if (stdout.length > options.maxBuffer)
146
+ return stop(new Error(`${path.basename(bin)} の出力が${options.maxBuffer}bytesを超えました`));
147
+ if (child.stdin.writable && stdout.includes(options.until))
148
+ child.stdin.end();
149
+ });
150
+ child.stdin.write(options.input);
151
+ });
152
+ }
91
153
  export function resolveWinPaneShell(shell) {
92
154
  if (!isWin)
93
155
  return shell;
@@ -8,7 +8,7 @@ import { createHash, randomBytes, randomUUID } from "node:crypto";
8
8
  import { fileURLToPath } from "node:url";
9
9
  import { AitermError } from "../errors.js";
10
10
  import { modeBitsWorldAccessible } from "../tmux-runtime.js";
11
- import { spawnAgentControlCommand } from "../agent-resolver.js";
11
+ import { runAgentProtocolCommand, spawnAgentControlCommand } from "../agent-resolver.js";
12
12
  import { catalogInvalid, catalogUnavailable, checkedCatalog, findJsonLine, processSummary } from "../model-catalog.js";
13
13
  import { currentUid, writeJson0600, shq, subagentInstruction, writeScopeLaunchNote, agentEventPath, createEmpty0600, writeAgentMetadata, agentLineageFields, assertSessionName, agentsDir, LAUNCH_ID_RE, AGENT_EVENT_TAIL_BYTES, readFileRange, } from "../agent-shared.js";
14
14
  export const OPERATION_ID_RE = /^sha256:[0-9a-f]{64}$/;
@@ -146,24 +146,23 @@ const CLAUDE_ULTRACODE_NOTE = "Claude Codeの--effortが受け付けるsession
146
146
  * (SDKの公開型 SDKControlRequest/SDKControlInitializeResponse)の応答にある models を読む。
147
147
  * promptは送らず、sessionを保存せず、利用者のhookとMCP serverを起動しない。
148
148
  */
149
- export function claudeModelChoices(bin, cwd) {
149
+ export async function claudeModelChoices(bin, cwd) {
150
150
  const dir = fs.mkdtempSync(path.join(os.tmpdir(), "aiterm-claude-models-"));
151
151
  try {
152
152
  const settings = path.join(dir, "settings.json");
153
153
  fs.writeFileSync(settings, JSON.stringify({ disableAllHooks: true }), { mode: 0o600 });
154
154
  const requestId = `aiterm-models-${randomUUID()}`;
155
155
  const request = { type: "control_request", request_id: requestId, request: { subtype: "initialize" } };
156
- const result = spawnAgentControlCommand(bin, [
156
+ const result = await runAgentProtocolCommand(bin, [
157
157
  "-p", "--output-format", "stream-json", "--verbose", "--input-format", "stream-json",
158
158
  "--no-session-persistence", "--strict-mcp-config", "--settings", settings,
159
- ], cwd, {
159
+ ], {
160
160
  cwd,
161
- encoding: "utf8",
162
161
  env: process.env,
163
162
  input: JSON.stringify(request) + "\n",
163
+ until: requestId,
164
164
  timeout: CLAUDE_MODELS_TIMEOUT_MS,
165
165
  maxBuffer: CLAUDE_MODELS_MAX_BYTES,
166
- windowsHide: true,
167
166
  });
168
167
  if (result.error || result.status !== 0) {
169
168
  throw catalogUnavailable("Claude Code", result.error?.message || result.stderr?.trim() || `exit=${result.status ?? "unknown"}`);
@@ -7,7 +7,7 @@ import * as os from "node:os";
7
7
  import * as path from "node:path";
8
8
  import { randomBytes, randomUUID } from "node:crypto";
9
9
  import { AitermError } from "../errors.js";
10
- import { spawnAgentControlCommand } from "../agent-resolver.js";
10
+ import { runAgentProtocolCommand, spawnAgentControlCommand } from "../agent-resolver.js";
11
11
  import { catalogInvalid, catalogUnavailable, checkedCatalog, findJsonLine, processSummary } from "../model-catalog.js";
12
12
  import { shq, subagentInstruction, writeScopeLaunchNote, safeStatSize, readFileRange, sleep, agentMetadataPath, writeAgentMetadata, agentEventPath, createEmpty0600, agentLineageFields, AGENT_DONE_POLL_MS, AGENT_EVENT_MAX_BYTES, GROK_TRANSCRIPT_INCREMENT_MAX_BYTES, agentHarness, } from "../agent-shared.js";
13
13
  const GROK_MODELS_MAX_BYTES = 1024 * 1024;
@@ -67,16 +67,16 @@ const GROK_AGENT_CATALOG_TIMEOUT_MS = 30_000;
67
67
  * `initialize` 応答にある `_meta.modelState` を読む。`session/new` は同じ候補を返すがsessionを保存するので使わない。
68
68
  * promptもsessionも作らず、推論は起きない。
69
69
  */
70
- export function grokModelChoices(bin, cwd) {
70
+ export async function grokModelChoices(bin, cwd) {
71
71
  const request = { jsonrpc: "2.0", id: 1, method: "initialize", params: { protocolVersion: 1, clientCapabilities: {} } };
72
- const result = spawnAgentControlCommand(bin, ["agent", "--no-leader", "stdio"], cwd, {
72
+ // 応答前に標準入力を閉じると、grok 1.0.41は応答せずに正常終了することがある。応答を読むまで開けておく。
73
+ const result = await runAgentProtocolCommand(bin, ["agent", "--no-leader", "stdio"], {
73
74
  cwd,
74
- encoding: "utf8",
75
75
  env: process.env,
76
76
  input: JSON.stringify(request) + "\n",
77
+ until: '"id":1',
77
78
  timeout: GROK_AGENT_CATALOG_TIMEOUT_MS,
78
79
  maxBuffer: GROK_MODELS_MAX_BYTES,
79
- windowsHide: true,
80
80
  });
81
81
  if (result.error || result.status !== 0) {
82
82
  throw catalogUnavailable("Grok", result.error?.message || result.stderr?.trim() || `exit=${result.status ?? "unknown"}`);
@@ -201,9 +201,9 @@ export function spawnInMacGuiWhenOutsideAqua(bin, args, options) {
201
201
  return null;
202
202
  const timeout = options.timeout ?? 30_000;
203
203
  const input = options.input === undefined ? undefined : typeof options.input === "string" ? options.input : Buffer.from(options.input.buffer, options.input.byteOffset, options.input.byteLength);
204
- return runInMacGui([bin, ...args], options.env ?? process.env, String(options.cwd ?? process.cwd()), timeout, `launchd経由の ${path.basename(bin)} ${args.join(" ")} が${timeout}ms以内に終わりませんでした`, input);
204
+ return runInMacGui([bin, ...args], options.env ?? process.env, String(options.cwd ?? process.cwd()), timeout, `launchd経由の ${path.basename(bin)} ${args.join(" ")} が${timeout}ms以内に終わりませんでした`, input, options.until);
205
205
  }
206
- function runInMacGui(argv, env, cwd, timeoutMs, timeoutMessage, input) {
206
+ function runInMacGui(argv, env, cwd, timeoutMs, timeoutMessage, input, until) {
207
207
  const uid = process.getuid?.();
208
208
  if (uid === undefined)
209
209
  return null;
@@ -219,10 +219,14 @@ function runInMacGui(argv, env, cwd, timeoutMs, timeoutMessage, input) {
219
219
  const plist = path.join(dir, "job.plist");
220
220
  try {
221
221
  // launchdの仕事は標準入力を持たない。入力を渡す時はファイルに書き、同じ仕事の中でつなぐ。
222
+ // untilを渡した時は、その文字列が出力に現れるまで標準入力を開けておく(閉じると応答前に終わるCLIがある)。
222
223
  if (input !== undefined) {
223
224
  const inputFile = path.join(dir, "in");
224
225
  fs.writeFileSync(inputFile, input, { mode: 0o600 });
225
- argv = ["/bin/sh", "-c", 'f=$1; shift; exec "$@" <"$f"', "aiterm-gui-input", inputFile, ...argv];
226
+ argv = until === undefined
227
+ ? ["/bin/sh", "-c", 'f=$1; shift; exec "$@" <"$f"', "aiterm-gui-input", inputFile, ...argv]
228
+ : ["/bin/sh", "-c", 'f=$1; m=$2; o=$3; shift 3; { cat "$f"; until grep -qF -- "$m" "$o" 2>/dev/null; do sleep 0.05; done; } | "$@"',
229
+ "aiterm-gui-input", inputFile, until, path.join(dir, "out"), ...argv];
226
230
  }
227
231
  fs.writeFileSync(plist, macGuiTmuxPlist(label, dir, argv, env, cwd));
228
232
  const boot = spawnSync("launchctl", ["bootstrap", `gui/${uid}`, plist], { encoding: "utf8", timeout: 10000 });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aiterm-mcp",
3
- "version": "0.44.0",
3
+ "version": "0.44.1",
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": [