aiterm-mcp 0.29.6 → 0.29.7
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.md +1 -1
- package/dist/core.js +43 -0
- package/dist/index.js +25 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -472,7 +472,7 @@ When an agent's answer is longer than the on-screen tail (pane height ≈ 24 lin
|
|
|
472
472
|
As of v0.16 a parent agent **never blocks** on aiterm — there is no wait parameter anywhere (v0.17 makes the waiter's exit codes mirror its outcome). The whole flow is dispatch + one universal waiter:
|
|
473
473
|
|
|
474
474
|
1. Launch the child with `agent_launch({ harness: ... })`; every launch shares the normal project/user environment and adds only completion correlation plus lineage. Send a turn with plain `pty_send` (or `claude_turn issue` for durable Claude operations). The call returns immediately with an `event_cursor` in its structured receipt.
|
|
475
|
-
2.
|
|
475
|
+
2. Pass the receipt's `wait_process.executable` and `wait_process.args` unchanged to a true argv process API. PowerShell 7's `Start-Process` is the exception because it joins `-ArgumentList` arrays; pass `windows_start_process_argument_list` as its one ready-made argument string instead. This invokes the bundled waiter through the exact Node runtime that is already running aiterm, including on native Windows where npm's human-facing bin is a PowerShell script shim and install paths may contain spaces. `wait_command` remains a compatibility display string for humans. The waiter observes the harness-owned completion source, plus Claude's additive launch hook, as a **pure reader** and exits with a one-line `aiterm.agent-wait-result.v1` receipt. **Exit ≠ done**: the receipt's `outcome` is authoritative (`0` = `done`, `3` = `timeout`, `4` = `closed`, `1` = error).
|
|
476
476
|
3. **The parent never runs the waiter in its own foreground.** Waiting is correct — but the waiter is a separate process, not the parent's turn. A harness that re-invokes its agent when a background task exits (Claude Code) runs the waiter **in the background** and gets woken with zero polling. So that this is not left to interpretation, aiterm reads `clientInfo.name` from the MCP `initialize` handshake and its receipts name the concrete invocation for the detected host — for Claude Code, literally `Bash(command: "aiterm-wait …", run_in_background: true)`. Unknown or undeclared hosts get the generic "start it as a process that does not block the parent's turn" wording; nothing else about the contract changes. Every receipt leads with the same rule: dispatch and let go, then go do something else or end the turn.
|
|
477
477
|
4. Collect the result exactly as before: `pty_read(agent_transcript: true)`, or `claude_turn recover` for durable Claude operations. The waiter carries the signal, never the payload.
|
|
478
478
|
|
package/dist/core.js
CHANGED
|
@@ -11,6 +11,7 @@ import { spawnSync } from "node:child_process";
|
|
|
11
11
|
import * as fs from "node:fs";
|
|
12
12
|
import * as path from "node:path";
|
|
13
13
|
import { createHash, randomBytes } from "node:crypto";
|
|
14
|
+
import { fileURLToPath } from "node:url";
|
|
14
15
|
import * as rtk from "./rtk.js";
|
|
15
16
|
import { AitermError, telemetryOwnedFailure, ownTelemetryFailure } from "./errors.js";
|
|
16
17
|
import { isWin, SOCKDIR, tmuxCommand, sendPsmuxPayload, loadPtyBufferChunk, pasteBufferBaseArgs, TMUX_EMPTY_CONFIG, attachCommand, normalizePaneCommand, appendMarkSentinel, settlePaneLog, paneCwdArgument, } from "./tmux-runtime.js";
|
|
@@ -2144,6 +2145,48 @@ function assertInitialPromptNotPendingForSend(name, force) {
|
|
|
2144
2145
|
}
|
|
2145
2146
|
// aiterm-wait の exit 契約(CLI と各所の案内文で共有する正)。exit≠完了: outcome が done の時だけ完了。
|
|
2146
2147
|
export const AITERM_WAIT_OUTCOME_NOTE = `exit 0=done / 3=timeout(既定${DEFAULT_AGENT_DONE_TIMEOUT}秒・未完了) / 4=closed。receiptのoutcomeが正で、done以外は未完了`;
|
|
2148
|
+
function quoteWindowsProcessArgument(value) {
|
|
2149
|
+
if (value !== "" && !/[\s"]/u.test(value))
|
|
2150
|
+
return value;
|
|
2151
|
+
let quoted = '"';
|
|
2152
|
+
let backslashes = 0;
|
|
2153
|
+
for (const char of value) {
|
|
2154
|
+
if (char === "\\") {
|
|
2155
|
+
backslashes += 1;
|
|
2156
|
+
}
|
|
2157
|
+
else if (char === '"') {
|
|
2158
|
+
quoted += "\\".repeat(backslashes * 2 + 1) + '"';
|
|
2159
|
+
backslashes = 0;
|
|
2160
|
+
}
|
|
2161
|
+
else {
|
|
2162
|
+
quoted += "\\".repeat(backslashes) + char;
|
|
2163
|
+
backslashes = 0;
|
|
2164
|
+
}
|
|
2165
|
+
}
|
|
2166
|
+
return quoted + "\\".repeat(backslashes * 2) + '"';
|
|
2167
|
+
}
|
|
2168
|
+
export function windowsStartProcessArgumentList(args) {
|
|
2169
|
+
return args.map(quoteWindowsProcessArgument).join(" ");
|
|
2170
|
+
}
|
|
2171
|
+
// npmのplatform別bin shimをcallerに解釈させず、現在稼働中のNodeと同梱CLIを直接起動する。
|
|
2172
|
+
// Windowsでもbackendはpsmux、対話shellはPowerShell 7のまま。これはwaiter processの入口だけを所有する。
|
|
2173
|
+
export function agentWaitProcess(session, cursor, runtime = {}) {
|
|
2174
|
+
const executable = runtime.executable ?? process.execPath;
|
|
2175
|
+
const args = [
|
|
2176
|
+
runtime.cliPath ?? fileURLToPath(new URL("./aiterm-wait-cli.js", import.meta.url)),
|
|
2177
|
+
"--session",
|
|
2178
|
+
session,
|
|
2179
|
+
"--cursor",
|
|
2180
|
+
String(cursor),
|
|
2181
|
+
];
|
|
2182
|
+
return {
|
|
2183
|
+
executable,
|
|
2184
|
+
args,
|
|
2185
|
+
windows_start_process_argument_list: (runtime.platform ?? process.platform) === "win32"
|
|
2186
|
+
? windowsStartProcessArgumentList(args)
|
|
2187
|
+
: null,
|
|
2188
|
+
};
|
|
2189
|
+
}
|
|
2147
2190
|
// 親ホストの識別(MCP initialize の clientInfo.name)。完了待ちコマンドを「親のターンを塞がない
|
|
2148
2191
|
// 起動形」で名指しするためだけに使う。分からない時は汎用文へ落ち、機能は一切変えない。
|
|
2149
2192
|
let parentClientName = null;
|
package/dist/index.js
CHANGED
|
@@ -27,9 +27,17 @@ const server = new McpServer({ name: "aiterm", version: pkg.version });
|
|
|
27
27
|
* ここでは「待つな」を断定形で先に置き、foreground 実行の禁止までを説明の側に含める。
|
|
28
28
|
*/
|
|
29
29
|
const NON_BLOCKING_RULE = "dispatch した子は投げっぱなしでよい=親はここで待たない。" +
|
|
30
|
-
"完了通知は `
|
|
30
|
+
"完了通知はreceiptの `wait_process.executable` と `wait_process.args` をそのまま親のターンを塞がない別プロセスAPIへ渡して受け、" +
|
|
31
|
+
"PowerShell 7のStart-Processだけは `windows_start_process_argument_list` を単一文字列として渡す。" +
|
|
31
32
|
`exit を完了通知として扱う(${core.AITERM_WAIT_OUTCOME_NOTE}。ポーリング不要)。` +
|
|
32
|
-
"
|
|
33
|
+
"`wait_command` は人間向け互換表示でありprocess境界へ使わない。foreground実行で親のターンを塞がない。";
|
|
34
|
+
const waitProcessOutputSchema = z
|
|
35
|
+
.object({
|
|
36
|
+
executable: z.string(),
|
|
37
|
+
args: z.array(z.string()),
|
|
38
|
+
windows_start_process_argument_list: z.string().nullable(),
|
|
39
|
+
})
|
|
40
|
+
.nullable();
|
|
33
41
|
function ok(s) {
|
|
34
42
|
return { content: [{ type: "text", text: s }] };
|
|
35
43
|
}
|
|
@@ -134,6 +142,7 @@ server.registerTool("pty_send", {
|
|
|
134
142
|
mode: z.enum(["sent", "agent_dispatch"]),
|
|
135
143
|
session_id: z.string(),
|
|
136
144
|
event_cursor: z.number().int().nullable(),
|
|
145
|
+
wait_process: waitProcessOutputSchema,
|
|
137
146
|
launch_id: z.string().nullable(),
|
|
138
147
|
vendor: z.enum(["claude", "codex", "grok", "composer", "cursor"]).nullable(),
|
|
139
148
|
harness: z.enum(["claude-code", "codex-cli", "grok-cli", "cursor-cli"]).nullable(),
|
|
@@ -151,6 +160,7 @@ server.registerTool("pty_send", {
|
|
|
151
160
|
if (rtk)
|
|
152
161
|
throw new Error("agent session への dispatch は rtk:true と併用できません");
|
|
153
162
|
const receipt = await core.dispatchAgentTurn(session_id, text, { raw });
|
|
163
|
+
const waitProcess = core.agentWaitProcess(receipt.session_id, receipt.event_cursor);
|
|
154
164
|
return {
|
|
155
165
|
content: [
|
|
156
166
|
{
|
|
@@ -165,6 +175,7 @@ server.registerTool("pty_send", {
|
|
|
165
175
|
mode: "agent_dispatch",
|
|
166
176
|
session_id: receipt.session_id,
|
|
167
177
|
event_cursor: receipt.event_cursor,
|
|
178
|
+
wait_process: waitProcess,
|
|
168
179
|
launch_id: receipt.launch_id,
|
|
169
180
|
vendor: receipt.vendor,
|
|
170
181
|
harness: receipt.harness,
|
|
@@ -180,6 +191,7 @@ server.registerTool("pty_send", {
|
|
|
180
191
|
mode: "sent",
|
|
181
192
|
session_id,
|
|
182
193
|
event_cursor: null,
|
|
194
|
+
wait_process: null,
|
|
183
195
|
launch_id: null,
|
|
184
196
|
vendor: null,
|
|
185
197
|
harness: null,
|
|
@@ -457,13 +469,14 @@ const agentEffortDesc = (kind) => kind === "claude"
|
|
|
457
469
|
: kind === "cursor"
|
|
458
470
|
? "Cursor catalogのeffort。指定時はmodel必須で、adapterが model-effort の正規IDへ変換してlive catalogに照合する。"
|
|
459
471
|
: "Grok Build reasoning effort。利用可能値はCLI/modelのlive catalogに従う。省略時はCLI/model既定。";
|
|
460
|
-
// 全launcher
|
|
461
|
-
//
|
|
472
|
+
// 全launcher共通の完了受信ガイド。machine callerはreceiptのwait_processをそのまま別process APIへ渡す。
|
|
473
|
+
// wait_commandは人間向け互換表示。文型は NON_BLOCKING_RULE と同じく「待たない」が先。
|
|
462
474
|
const agentCompletionDesc = `起動して投げたら投げっぱなしでよい=親はここで待たない。` +
|
|
463
|
-
|
|
464
|
-
|
|
475
|
+
`完了通知は起動応答またはpty_send dispatch receiptの wait_processを、親のターンを塞がない` +
|
|
476
|
+
`別プロセスAPIへexecutable/argsの境界を保ったまま渡して受ける` +
|
|
477
|
+
`(PowerShell 7のStart-Processはwindows_start_process_argument_listを使う)` +
|
|
465
478
|
`(${core.AITERM_WAIT_OUTCOME_NOTE}。ポーリング不要・foreground実行はしない)。` +
|
|
466
|
-
|
|
479
|
+
`wait_commandは人間向け互換表示。結果回収は pty_read(agent_transcript:true)。`;
|
|
467
480
|
const agentEnvironmentDesc = `通常CLIと同じHOME・cwd・project/user/local設定・MCP・plugin・skill・permission/trustを共有する。` +
|
|
468
481
|
`aitermは完了相関stateだけをlaunch単位で所有する。起動されたagentにはsub-agent自己認識、親session、` +
|
|
469
482
|
`delegation depth/lineage、delegation_allowed=trueを注入し、必要な追加委譲は許可する。`;
|
|
@@ -492,6 +505,7 @@ async function launchAgent(kind, args) {
|
|
|
492
505
|
session_id: sid,
|
|
493
506
|
managed_completion: true,
|
|
494
507
|
event_cursor: eventCursor,
|
|
508
|
+
wait_process: eventCursor === null ? null : core.agentWaitProcess(sid, eventCursor),
|
|
495
509
|
wait_command: eventCursor === null ? null : `aiterm-wait --session ${sid} --cursor ${eventCursor}`,
|
|
496
510
|
submit_residue: submitResidue,
|
|
497
511
|
...(supportsWriteScope && write_scope !== undefined
|
|
@@ -558,9 +572,10 @@ function registerAgentTool(toolName, kind, desc) {
|
|
|
558
572
|
harness: z.literal(core.agentHarness(kind)),
|
|
559
573
|
session_id: z.string().regex(/^[A-Za-z0-9_-]{1,64}$/),
|
|
560
574
|
managed_completion: z.boolean().describe("後方互換field。trueはaiterm完了相関が有効という意味で、project/user環境の隔離を意味しない"),
|
|
561
|
-
// 起動時
|
|
562
|
-
//
|
|
575
|
+
// 起動時promptでturnが走っている時だけ非null。wait_processがmachine向けprocess境界、
|
|
576
|
+
// wait_commandは人間向け互換表示。
|
|
563
577
|
event_cursor: z.number().int().nullable(),
|
|
578
|
+
wait_process: waitProcessOutputSchema,
|
|
564
579
|
wait_command: z.string().nullable(),
|
|
565
580
|
// 初回prompt dispatch後のsubmit座礁観測(additive)。true=composerに残存を確認(submit未成立の疑い)/
|
|
566
581
|
// false=残存を観測せず(submit成立の保証ではない)/ null=promptなし・判定不能。
|
|
@@ -593,6 +608,7 @@ server.registerTool("agent_launch", {
|
|
|
593
608
|
session_id: z.string().regex(/^[A-Za-z0-9_-]{1,64}$/),
|
|
594
609
|
managed_completion: z.boolean(),
|
|
595
610
|
event_cursor: z.number().int().nullable(),
|
|
611
|
+
wait_process: waitProcessOutputSchema,
|
|
596
612
|
wait_command: z.string().nullable(),
|
|
597
613
|
submit_residue: z.boolean().nullable(),
|
|
598
614
|
write_scope: z.string().optional(),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aiterm-mcp",
|
|
3
|
-
"version": "0.29.
|
|
3
|
+
"version": "0.29.7",
|
|
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": [
|