aiterm-mcp 0.29.2 → 0.29.5
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/dist/core.js +65 -51
- package/dist/index.js +1 -1
- package/dist/psmux-send-worker.js +99 -0
- package/dist/tmux-runtime.js +23 -0
- package/package.json +1 -1
package/dist/core.js
CHANGED
|
@@ -13,9 +13,9 @@ import * as path from "node:path";
|
|
|
13
13
|
import { createHash, randomBytes } from "node:crypto";
|
|
14
14
|
import * as rtk from "./rtk.js";
|
|
15
15
|
import { AitermError, telemetryOwnedFailure, ownTelemetryFailure } from "./errors.js";
|
|
16
|
-
import { isWin, SOCKDIR, tmuxCommand, loadPtyBufferChunk, pasteBufferBaseArgs, TMUX_EMPTY_CONFIG, attachCommand, normalizePaneCommand, appendMarkSentinel, settlePaneLog, paneCwdArgument, } from "./tmux-runtime.js";
|
|
16
|
+
import { isWin, SOCKDIR, tmuxCommand, sendPsmuxPayload, loadPtyBufferChunk, pasteBufferBaseArgs, TMUX_EMPTY_CONFIG, attachCommand, normalizePaneCommand, appendMarkSentinel, settlePaneLog, paneCwdArgument, } from "./tmux-runtime.js";
|
|
17
17
|
import { sleep, currentUid, runtimeStateBase, safeStatSize, readFileRange, writeJson0600, createEmpty0600, shq, LAUNCH_ID_RE, AGENT_DONE_POLL_MS, AGENT_EVENT_MAX_BYTES, assertSessionName, agentsDir, agentEventPath, agentMetadataPath, writeAgentMetadata, AGENT_EVENT_TAIL_BYTES, agentLabel, agentHarness, subagentInstruction, agentLineageFields, } from "./agent-shared.js";
|
|
18
|
-
import { GROK_MODEL_DEFAULTS, realGrokHome, resolveAndValidateGrokAuth, assertGrokModelAvailable, grokEventsTranscript, latestGrokCompletion,
|
|
18
|
+
import { GROK_MODEL_DEFAULTS, realGrokHome, resolveAndValidateGrokAuth, assertGrokModelAvailable, grokEventsTranscript, latestGrokCompletion, observeGrokDone, buildGrokAgentCmd, grokLaunchNote, grokEnvTokens, grokTuiReady, GROK_COMPOSER_MARKER_RE, grokFooterHasConfiguration, grokTranscriptText, createGrokAgentMetadata, } from "./harnesses/grok.js";
|
|
19
19
|
import { bindCodexTranscriptSession, latestCodexCompletion, observeCodexDone, buildCodexAgentCmd, codexLaunchNote, codexTuiReady, codexLaunchBlockingDialog, CODEX_COMPOSER_MARKER_RE, codexModelChoice, codexEffortChoice, codexMoreReasoningChoice, codexTranscriptText, createCodexAgentMetadata, } from "./harnesses/codex.js";
|
|
20
20
|
import { OPERATION_ID_RE, CLAUDE_RESULT_MAX_BYTES, CLAUDE_EFFORTS, agentManagedClaudeSettingsPath, agentClaudeResultPath, agentClaudeOperationPath, agentClaudeApprovalReceiptPath, agentClaudeDispatchReceiptPath, validateOperationId, readClaudeResultText, assertClaudeAuthenticationReady, buildClaudeAgentCmd, claudeLaunchNote, claudeTuiReady, CLAUDE_COMPOSER_MARKER_RE, createClaudeAgentMetadata, } from "./harnesses/claude.js";
|
|
21
21
|
import { bindCursorTranscriptSession, cursorTurnBoundary, latestCursorCompletion, observeCursorDone, cursorTranscriptText, assertCursorAuthenticationReady, assertCursorModelAvailable, buildCursorAgentCmd, createCursorAgentMetadata, cursorLaunchNote, cursorEffortNavigation, cursorTuiReady, CURSOR_COMPOSER_MARKER_RE, validateCursorModelEffort, } from "./harnesses/cursor.js";
|
|
@@ -75,7 +75,7 @@ const MAX_SEND_BYTES = 64 * 1024;
|
|
|
75
75
|
const PTY_PASTE_CHUNK_BYTES = 256;
|
|
76
76
|
const PTY_PASTE_CHUNK_PAUSE_MS = 10;
|
|
77
77
|
const PTY_PASTE_PAUSE_BUFFER = new Int32Array(new SharedArrayBuffer(4));
|
|
78
|
-
const SESSION_SEND_LOCK_WAIT_MS = 10_000;
|
|
78
|
+
const SESSION_SEND_LOCK_WAIT_MS = isWin ? 130_000 : 10_000;
|
|
79
79
|
const SESSION_SEND_LOCK_POLL_MS = 25;
|
|
80
80
|
// 安全: send 前に弾く破壊的コマンド(外部システム境界の防御)
|
|
81
81
|
const DESTRUCTIVE = [
|
|
@@ -121,6 +121,15 @@ function paneCurrentCommand(name) {
|
|
|
121
121
|
return "";
|
|
122
122
|
return normalizePaneCommand(r.stdout.trim());
|
|
123
123
|
}
|
|
124
|
+
function paneCurrentCommandForMark(name) {
|
|
125
|
+
const foreground = paneCurrentCommand(name);
|
|
126
|
+
if (!isWin || foreground === "powershell" || foreground === "pwsh")
|
|
127
|
+
return foreground;
|
|
128
|
+
// psmuxはnew-session直後だけpane_current_commandへ起動元shellを返すことがある。
|
|
129
|
+
// 現在の末尾画面にPowerShell promptが描画済みなら、mark構文はその実効shellへ合わせる。
|
|
130
|
+
const screen = captureScreen(name, 4).replace(/\x1b\[[0-9;?]*[a-zA-Z]/g, "");
|
|
131
|
+
return /(?:^|\n)PS [^\r\n]*>\s*$/m.test(screen) ? "pwsh" : foreground;
|
|
132
|
+
}
|
|
124
133
|
function pasteBufferSupportsNoSanitizeFlag() {
|
|
125
134
|
const listed = tmux("list-commands");
|
|
126
135
|
if (listed.code !== 0) {
|
|
@@ -658,7 +667,7 @@ export function send(name, text, o = {}) {
|
|
|
658
667
|
if (o.mark) {
|
|
659
668
|
// mark の sentinel は POSIX シェル構文。前面が fish/csh/tcsh 等の非 POSIX 対話シェルだと "$?" が
|
|
660
669
|
// 壊れて sentinel が成立しない。黙って壊れた完了検出を作らず、明示エラーで until を促す(B8)。
|
|
661
|
-
fg =
|
|
670
|
+
fg = paneCurrentCommandForMark(name);
|
|
662
671
|
if (NON_POSIX_MARK_SHELLS.has(fg)) {
|
|
663
672
|
throw new AitermError(`mark は POSIX シェル(bash/sh/zsh/dash)前提です。前面が ${fg} のため sentinel の "$?" が` +
|
|
664
673
|
`正しく展開されません。until で完了パターンを指定してください。`, 2);
|
|
@@ -702,42 +711,44 @@ export function send(name, text, o = {}) {
|
|
|
702
711
|
/* noop */
|
|
703
712
|
}
|
|
704
713
|
}
|
|
705
|
-
//
|
|
706
|
-
//
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
for (let i = 0; i < chunks.length; i += 1) {
|
|
712
|
-
const bufferName = `${bufferBase}-${i}`;
|
|
713
|
-
const partial = i > 0
|
|
714
|
-
? " 先行chunkはPTYに入力済みでEnterは未送信です。再送前に入力を確認・消去してください。"
|
|
715
|
-
: "";
|
|
716
|
-
const loaded = loadPtyBufferChunk(true, bufferName, chunks[i]);
|
|
717
|
-
if (loaded.code !== 0) {
|
|
718
|
-
tmuxCleanup("delete-buffer", "-b", bufferName);
|
|
719
|
-
throw new AitermError(`tmux bufferへの送信準備に失敗しました` +
|
|
720
|
-
`(chunk ${i + 1}/${chunks.length}): ${loaded.stderr.trim() || `code=${loaded.code}`}.${partial}`, 2);
|
|
721
|
-
}
|
|
722
|
-
// -Sは対応新版だけでvis(3)制御文字変換を無効化する。
|
|
723
|
-
// send 自身の raw/sanitize 契約だけを真実とし、tmux 側で黙って再変換させない。
|
|
724
|
-
const pasteArgs = pasteBufferBaseArgs();
|
|
725
|
-
if (o.bracketedPaste)
|
|
726
|
-
pasteArgs.push("-p");
|
|
727
|
-
if (pasteSupportsNoSanitize)
|
|
728
|
-
pasteArgs.push("-S");
|
|
729
|
-
pasteArgs.push("-b", bufferName, "-t", name);
|
|
730
|
-
const pasted = tmux(...pasteArgs);
|
|
731
|
-
if (pasted.code !== 0) {
|
|
732
|
-
// paste 失敗時は -d で消えないbufferを明示的に掃除する。元エラーを優先する。
|
|
733
|
-
tmuxCleanup("delete-buffer", "-b", bufferName);
|
|
734
|
-
throw new AitermError(`tmux bufferのPTY送信に失敗しました` +
|
|
735
|
-
`(chunk ${i + 1}/${chunks.length}): ${pasted.stderr.trim() || `code=${pasted.code}`}.${partial}`, 2);
|
|
714
|
+
// POSIX tmuxはUTF-8安全な256byte pasteへ分割する。Windows psmuxは認証済み
|
|
715
|
+
// server commandでpayload全体を単一SendBytesへ変換し、別processとの交差を構造的に防ぐ。
|
|
716
|
+
if (isWin) {
|
|
717
|
+
const sent = sendPsmuxPayload(true, name, text, !!o.bracketedPaste);
|
|
718
|
+
if (sent.code !== 0) {
|
|
719
|
+
throw new AitermError(`psmuxのPTY送信に失敗しました: ${sent.stderr.trim() || `code=${sent.code}`}`, 2);
|
|
736
720
|
}
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
721
|
+
}
|
|
722
|
+
else {
|
|
723
|
+
const chunks = splitPtyText(text);
|
|
724
|
+
const pasteSupportsNoSanitize = pasteBufferSupportsNoSanitizeFlag();
|
|
725
|
+
const bufferBase = `aiterm-${process.pid}-${randomBytes(8).toString("hex")}`;
|
|
726
|
+
for (let i = 0; i < chunks.length; i += 1) {
|
|
727
|
+
const bufferName = `${bufferBase}-${i}`;
|
|
728
|
+
const partial = i > 0
|
|
729
|
+
? " 先行chunkはPTYに入力済みでEnterは未送信です。再送前に入力を確認・消去してください。"
|
|
730
|
+
: "";
|
|
731
|
+
const loaded = loadPtyBufferChunk(true, bufferName, chunks[i]);
|
|
732
|
+
if (loaded.code !== 0) {
|
|
733
|
+
tmuxCleanup("delete-buffer", "-b", bufferName);
|
|
734
|
+
throw new AitermError(`tmux bufferへの送信準備に失敗しました` +
|
|
735
|
+
`(chunk ${i + 1}/${chunks.length}): ${loaded.stderr.trim() || `code=${loaded.code}`}.${partial}`, 2);
|
|
736
|
+
}
|
|
737
|
+
const pasteArgs = pasteBufferBaseArgs();
|
|
738
|
+
if (o.bracketedPaste)
|
|
739
|
+
pasteArgs.push("-p");
|
|
740
|
+
if (pasteSupportsNoSanitize)
|
|
741
|
+
pasteArgs.push("-S");
|
|
742
|
+
pasteArgs.push("-b", bufferName, "-t", name);
|
|
743
|
+
const pasted = tmux(...pasteArgs);
|
|
744
|
+
if (pasted.code !== 0) {
|
|
745
|
+
tmuxCleanup("delete-buffer", "-b", bufferName);
|
|
746
|
+
throw new AitermError(`tmux bufferのPTY送信に失敗しました` +
|
|
747
|
+
`(chunk ${i + 1}/${chunks.length}): ${pasted.stderr.trim() || `code=${pasted.code}`}.${partial}`, 2);
|
|
748
|
+
}
|
|
749
|
+
if (i + 1 < chunks.length) {
|
|
750
|
+
Atomics.wait(PTY_PASTE_PAUSE_BUFFER, 0, 0, PTY_PASTE_CHUNK_PAUSE_MS);
|
|
751
|
+
}
|
|
741
752
|
}
|
|
742
753
|
}
|
|
743
754
|
if (enter) {
|
|
@@ -2294,12 +2305,19 @@ function isAgentTuiReady(kind, screen) {
|
|
|
2294
2305
|
// Codex/Claude は実行中に「(esc to interrupt)」、Cursor は「Working」+
|
|
2295
2306
|
// 「ctrl+c to stop」を表示する(いずれも実機採取)。startup 側の処理(MCP initialize 等)が
|
|
2296
2307
|
// 走ったまま古い composer がscrollbackに残る画面は入力受付とみなさない。
|
|
2297
|
-
// Grok/Composer
|
|
2308
|
+
// Grok/Composer は実機で `Waiting for response` / `Responding…` / `[stop]` を表示する。
|
|
2298
2309
|
function isAgentTuiBusy(kind, screen) {
|
|
2299
2310
|
if (kind === "cursor")
|
|
2300
2311
|
return /ctrl\+c to stop/i.test(screen);
|
|
2301
2312
|
if (kind === "codex" || kind === "claude")
|
|
2302
2313
|
return /esc to interrupt/i.test(screen);
|
|
2314
|
+
if (kind === "grok" || kind === "composer") {
|
|
2315
|
+
return screen.includes("Waiting for response")
|
|
2316
|
+
|| screen.includes("Responding…")
|
|
2317
|
+
|| screen.includes("Responding...")
|
|
2318
|
+
|| screen.includes("[stop]")
|
|
2319
|
+
|| /\[hooks:\s*\d+\/\d+\]/u.test(screen);
|
|
2320
|
+
}
|
|
2303
2321
|
return false;
|
|
2304
2322
|
}
|
|
2305
2323
|
// ready gate 用: 入力欄マーカーがあっても busy 表示中は ready と数えない。
|
|
@@ -2353,11 +2371,7 @@ async function waitAgentTuiReadyImpl(kind, sample, sleepFn, opts = {}) {
|
|
|
2353
2371
|
}
|
|
2354
2372
|
}
|
|
2355
2373
|
async function waitAgentTuiReady(name, meta, timeoutMs = AGENT_TUI_READY_TIMEOUT_MS) {
|
|
2356
|
-
return waitAgentTuiReadyImpl(meta.kind, () => {
|
|
2357
|
-
if ((meta.kind === "grok" || meta.kind === "composer") && !grokInitializationComplete(meta))
|
|
2358
|
-
return "";
|
|
2359
|
-
return captureScreen(name, AGENT_TUI_READY_LINES);
|
|
2360
|
-
}, sleep, { timeoutMs });
|
|
2374
|
+
return waitAgentTuiReadyImpl(meta.kind, () => captureScreen(name, AGENT_TUI_READY_LINES), sleep, { timeoutMs });
|
|
2361
2375
|
}
|
|
2362
2376
|
async function waitAgentTuiReadyByKind(name, kind, timeoutMs = AGENT_TUI_READY_TIMEOUT_MS) {
|
|
2363
2377
|
return waitAgentTuiReadyImpl(kind, () => captureScreen(name, AGENT_TUI_READY_LINES), sleep, { timeoutMs });
|
|
@@ -3207,22 +3221,22 @@ export async function openAgentWithInitialPrompt(kind, opts = {}) {
|
|
|
3207
3221
|
}
|
|
3208
3222
|
// v0.16.0: launcher は常に managed(Stop hook つき)で立つ。手動運転したい場合は
|
|
3209
3223
|
// pty_open で素の PTY を開き、harness CLI を自分で send する。
|
|
3210
|
-
//
|
|
3211
|
-
//
|
|
3212
|
-
|
|
3224
|
+
// prompt無しはTUIを起動するだけ。prompt有りはharnessを問わず、TUI ready確認後の
|
|
3225
|
+
// sendInitialAgentPromptへ一本化する。Grokの--verbatim argvはCLIが本文をqueueへ置いただけで
|
|
3226
|
+
// turnを開始しない版があり、event_cursor=0を「実行中」と返すと利用側へ嘘をつく。
|
|
3227
|
+
if (!prompt) {
|
|
3213
3228
|
const [sid, hint] = openAgent(kind, {
|
|
3214
3229
|
session_name: opts.session_name ?? null,
|
|
3215
3230
|
model: opts.model ?? null,
|
|
3216
3231
|
reasoning_effort: opts.reasoning_effort ?? null,
|
|
3217
3232
|
cwd: opts.cwd ?? null,
|
|
3218
|
-
prompt,
|
|
3233
|
+
prompt: null,
|
|
3219
3234
|
agent_done: true,
|
|
3220
3235
|
launch_operation_id: opts.launch_operation_id ?? null,
|
|
3221
3236
|
write_scope: opts.write_scope,
|
|
3222
3237
|
env_vars: opts.env_vars,
|
|
3223
3238
|
});
|
|
3224
|
-
|
|
3225
|
-
return [sid, hint, prompt ? 0 : null, null];
|
|
3239
|
+
return [sid, hint, null, null];
|
|
3226
3240
|
}
|
|
3227
3241
|
const [sid, hint] = openAgent(kind, {
|
|
3228
3242
|
session_name: opts.session_name ?? null,
|
package/dist/index.js
CHANGED
|
@@ -563,7 +563,7 @@ function registerAgentTool(toolName, kind, desc) {
|
|
|
563
563
|
event_cursor: z.number().int().nullable(),
|
|
564
564
|
wait_command: z.string().nullable(),
|
|
565
565
|
// 初回prompt dispatch後のsubmit座礁観測(additive)。true=composerに残存を確認(submit未成立の疑い)/
|
|
566
|
-
// false=残存を観測せず(submit成立の保証ではない)/ null=prompt
|
|
566
|
+
// false=残存を観測せず(submit成立の保証ではない)/ null=promptなし・判定不能。
|
|
567
567
|
submit_residue: z.boolean().nullable(),
|
|
568
568
|
...writeScopeOutputSchema,
|
|
569
569
|
},
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as net from "node:net";
|
|
3
|
+
import * as os from "node:os";
|
|
4
|
+
import * as path from "node:path";
|
|
5
|
+
const MAX_PAYLOAD_BYTES = 64 * 1024 + 12;
|
|
6
|
+
const PTY_CHUNK_BYTES = 256;
|
|
7
|
+
const MAX_FINAL_DRAIN_MS = 3_000;
|
|
8
|
+
const SESSION_BASE_RE = /^[A-Za-z0-9_-]{1,160}$/;
|
|
9
|
+
const KEY_RE = /^[0-9a-f]{16}$/i;
|
|
10
|
+
async function readPayload() {
|
|
11
|
+
const chunks = [];
|
|
12
|
+
let total = 0;
|
|
13
|
+
for await (const chunk of process.stdin) {
|
|
14
|
+
const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk));
|
|
15
|
+
total += bytes.length;
|
|
16
|
+
if (total > MAX_PAYLOAD_BYTES)
|
|
17
|
+
throw new Error(`payload exceeds ${MAX_PAYLOAD_BYTES} bytes`);
|
|
18
|
+
chunks.push(bytes);
|
|
19
|
+
}
|
|
20
|
+
return Buffer.concat(chunks);
|
|
21
|
+
}
|
|
22
|
+
function psmuxDataDir() {
|
|
23
|
+
const override = process.env.PSMUX_DATA_DIR;
|
|
24
|
+
if (override) {
|
|
25
|
+
if (!path.isAbsolute(override))
|
|
26
|
+
throw new Error("PSMUX_DATA_DIR must be absolute");
|
|
27
|
+
return override;
|
|
28
|
+
}
|
|
29
|
+
return path.join(process.env.USERPROFILE || os.homedir(), ".psmux");
|
|
30
|
+
}
|
|
31
|
+
async function sendCommand(port, key, command, expectedResponse) {
|
|
32
|
+
await new Promise((resolve, reject) => {
|
|
33
|
+
const socket = net.createConnection({ host: "127.0.0.1", port });
|
|
34
|
+
let response = "";
|
|
35
|
+
let settled = false;
|
|
36
|
+
const finish = (error) => {
|
|
37
|
+
if (settled)
|
|
38
|
+
return;
|
|
39
|
+
settled = true;
|
|
40
|
+
socket.destroy();
|
|
41
|
+
if (error)
|
|
42
|
+
reject(error);
|
|
43
|
+
else
|
|
44
|
+
resolve();
|
|
45
|
+
};
|
|
46
|
+
socket.setTimeout(2_000, () => finish(new Error("psmux command timed out")));
|
|
47
|
+
socket.on("connect", () => socket.end(`AUTH ${key}\n${command}\n`));
|
|
48
|
+
socket.on("data", (chunk) => {
|
|
49
|
+
response += chunk.toString("utf8");
|
|
50
|
+
});
|
|
51
|
+
socket.on("end", () => {
|
|
52
|
+
if (response.split(/\r?\n/).includes("OK") &&
|
|
53
|
+
(expectedResponse == null || response.includes(expectedResponse)))
|
|
54
|
+
finish();
|
|
55
|
+
else
|
|
56
|
+
finish(new Error(`psmux authentication failed: ${response.trim() || "empty response"}`));
|
|
57
|
+
});
|
|
58
|
+
socket.on("error", (error) => finish(error));
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
function delay(ms) {
|
|
62
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
63
|
+
}
|
|
64
|
+
async function send() {
|
|
65
|
+
const sessionBase = process.argv[2] ?? "";
|
|
66
|
+
if (!SESSION_BASE_RE.test(sessionBase))
|
|
67
|
+
throw new Error("invalid psmux session base");
|
|
68
|
+
const payload = await readPayload();
|
|
69
|
+
const dir = psmuxDataDir();
|
|
70
|
+
const portText = fs.readFileSync(path.join(dir, `${sessionBase}.port`), "utf8").trim();
|
|
71
|
+
const key = fs.readFileSync(path.join(dir, `${sessionBase}.key`), "utf8").trim();
|
|
72
|
+
const port = Number(portText);
|
|
73
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535)
|
|
74
|
+
throw new Error("invalid psmux port");
|
|
75
|
+
if (!KEY_RE.test(key))
|
|
76
|
+
throw new Error("invalid psmux auth key");
|
|
77
|
+
const subcommands = [];
|
|
78
|
+
for (let offset = 0; offset < payload.length; offset += PTY_CHUNK_BYTES) {
|
|
79
|
+
const chunk = payload.subarray(offset, offset + PTY_CHUNK_BYTES);
|
|
80
|
+
const hex = Array.from(chunk, (byte) => byte.toString(16).padStart(2, "0")).join(" ");
|
|
81
|
+
subcommands.push(`send -H ${hex}`);
|
|
82
|
+
}
|
|
83
|
+
const chunkDrainMs = payload.length > 1024 ? 300 : 100;
|
|
84
|
+
for (const command of subcommands) {
|
|
85
|
+
await sendCommand(port, key, command);
|
|
86
|
+
await delay(chunkDrainMs);
|
|
87
|
+
}
|
|
88
|
+
const completionMarker = `AITERM_PSMUX_SEND_DONE_${process.pid}`;
|
|
89
|
+
await sendCommand(port, key, `display-message -p ${completionMarker}`, completionMarker);
|
|
90
|
+
const finalDrainMs = Math.min(MAX_FINAL_DRAIN_MS, Math.max(200, Math.ceil(payload.length / 1024) * 500));
|
|
91
|
+
await delay(finalDrainMs);
|
|
92
|
+
if (process.env.AITERM_PSMUX_DEBUG === "1") {
|
|
93
|
+
process.stderr.write(`psmux-send-worker debug: chunks=${subcommands.length} sent=${subcommands.length}\n`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
send().catch((error) => {
|
|
97
|
+
process.stderr.write(`psmux-send-worker: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
98
|
+
process.exitCode = 1;
|
|
99
|
+
});
|
package/dist/tmux-runtime.js
CHANGED
|
@@ -6,6 +6,7 @@ import * as fs from "node:fs";
|
|
|
6
6
|
import * as path from "node:path";
|
|
7
7
|
import * as os from "node:os";
|
|
8
8
|
import { createHash } from "node:crypto";
|
|
9
|
+
import { fileURLToPath } from "node:url";
|
|
9
10
|
import { AitermError, ptyDependencyError } from "./errors.js";
|
|
10
11
|
// Windows ネイティブには tmux が無いため、tmux CLI 互換の native psmux を叩く
|
|
11
12
|
// (POSIX = Linux/WSL2/macOS は従来どおり tmux を直接叩く。WSL 橋は e3f5fc8 で全廃)。
|
|
@@ -129,6 +130,28 @@ export function tmuxCommandWithInput(observe, input, ...args) {
|
|
|
129
130
|
export function tmuxCommand(observe, ...args) {
|
|
130
131
|
return tmuxCommandWithInput(observe, undefined, ...args);
|
|
131
132
|
}
|
|
133
|
+
export function sendPsmuxPayload(observe, sessionName, text, bracketedPaste) {
|
|
134
|
+
if (!isWin)
|
|
135
|
+
return { code: 1, stdout: "", stderr: "psmux direct send is Windows-only" };
|
|
136
|
+
ensureWinPsmux(observe);
|
|
137
|
+
const worker = fileURLToPath(new URL("./psmux-send-worker.js", import.meta.url));
|
|
138
|
+
const body = bracketedPaste ? `\x1b[200~${text}\x1b[201~` : text;
|
|
139
|
+
const result = spawnSync(process.execPath, [worker, `${WIN_NS}__${sessionName}`], {
|
|
140
|
+
encoding: "utf8",
|
|
141
|
+
maxBuffer: 1024 * 1024,
|
|
142
|
+
input: Buffer.from(body, "utf8"),
|
|
143
|
+
env: tmuxSpawnEnv(),
|
|
144
|
+
timeout: 120_000,
|
|
145
|
+
});
|
|
146
|
+
if (result.error) {
|
|
147
|
+
return {
|
|
148
|
+
code: 1,
|
|
149
|
+
stdout: result.stdout ?? "",
|
|
150
|
+
stderr: `${result.error.code ?? "worker error"}: ${result.error.message}`,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
return { code: result.status ?? 1, stdout: result.stdout ?? "", stderr: result.stderr ?? "" };
|
|
154
|
+
}
|
|
132
155
|
// psmux の load-buffer は stdin (`-`) 非対応で path 位置引数だけを取る。
|
|
133
156
|
// Windows は owner-only の SOCKDIR 内へ一時ファイル経由で渡し、POSIX は stdin で渡す。
|
|
134
157
|
export function loadPtyBufferChunk(observe, bufferName, chunk) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aiterm-mcp",
|
|
3
|
-
"version": "0.29.
|
|
3
|
+
"version": "0.29.5",
|
|
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": [
|