aiterm-mcp 0.52.1 → 0.52.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/CHANGELOG.md +20 -1
- package/README.ja.md +1 -1
- package/README.md +1 -1
- package/dist/core.js +76 -14
- package/dist/process-runtime.js +39 -1
- package/docs/DESIGN.md +2 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.52.2] - 2026-10-05
|
|
11
|
+
|
|
12
|
+
### 修正
|
|
13
|
+
|
|
14
|
+
- 0.52.1で、socketの置き場(`claude-tmux-sockets`)ごと無くなったagent sessionへの送信が、
|
|
15
|
+
`ENOENT: … .agent-send.lock`という生のエラーを返していた。0.52.0までは
|
|
16
|
+
「入力受付状態になりません。文字列は送信していません。」の断りだった。連携元が「文字列は送信していません」で
|
|
17
|
+
未送信を見分けている時、0.52.1ではこの場合だけ見分けられなかった。送信の順番を守るlock(ADR 0088)を
|
|
18
|
+
置き場に作れない時は、lockを取らずに進め、今まで通りの断りを返す。置き場が無ければsessionにも届かず、
|
|
19
|
+
守る相手が無い。sessionだけが無くなった時(置き場は残る)の断りは、0.52.1でも変わっていない。
|
|
20
|
+
- 0.52.1で、送信の途中で終了したprocessのpidを別の生きたprocessが使い回すと、そのagent sessionへの送信が
|
|
21
|
+
`AGENT_SEND_BUSY`で断られ続けた。lockの持ち主をpidだけで見ていたため。`pty_close`してもlockが残り、
|
|
22
|
+
閉じて同じIDで起動し直しても同じ断りだった。生きて見える持ち主は、processが始まってからの経過と
|
|
23
|
+
lockを作ってからの時間を比べて本人かを確かめ、lockより後に始まった別のprocessなら片付ける。
|
|
24
|
+
片付けの途中で残った印は、5秒より古ければ、pidが生きて見えても残った物と読む(その時の断りは今までと同じ文面)。
|
|
25
|
+
`pty_close`は、使い回されたpidのlockと古い印も片付ける。OSへの照会は、送信が実際に重なった時と、
|
|
26
|
+
閉じる時にlockが残っていた時だけ行う。
|
|
27
|
+
|
|
10
28
|
## [0.52.1] - 2026-10-05
|
|
11
29
|
|
|
12
30
|
### 修正
|
|
@@ -2117,7 +2135,8 @@ prototype (preserved under `prototype/python/` as the porting source and referen
|
|
|
2117
2135
|
`ubuntu-latest` for Node 18/20/22, publishing to npm on `v*` tags with
|
|
2118
2136
|
provenance.
|
|
2119
2137
|
|
|
2120
|
-
[Unreleased]: https://github.com/kitepon/aiterm-mcp/compare/v0.52.
|
|
2138
|
+
[Unreleased]: https://github.com/kitepon/aiterm-mcp/compare/v0.52.2...HEAD
|
|
2139
|
+
[0.52.2]: https://github.com/kitepon/aiterm-mcp/compare/v0.52.1...v0.52.2
|
|
2121
2140
|
[0.52.1]: https://github.com/kitepon/aiterm-mcp/compare/v0.52.0...v0.52.1
|
|
2122
2141
|
[0.52.0]: https://github.com/kitepon/aiterm-mcp/compare/v0.51.3...v0.52.0
|
|
2123
2142
|
[0.51.3]: https://github.com/kitepon/aiterm-mcp/compare/v0.51.2...v0.51.3
|
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.52.
|
|
212
|
+
**状態:** 開発継続中 · 現行公開版 **v0.52.2** · 動作対象は Linux · WSL2 · macOS · Windows ネイティブ · MIT · [変更履歴](CHANGELOG.md)。
|
|
213
213
|
|
|
214
214
|
### 更新と巻き戻し
|
|
215
215
|
|
package/README.md
CHANGED
|
@@ -223,7 +223,7 @@ collection is off by default and performs no network I/O. It ships via
|
|
|
223
223
|
tag-triggered CI with npm provenance (OIDC Trusted Publishing); the GitHub
|
|
224
224
|
Release re-registers the Official MCP Registry entry.
|
|
225
225
|
|
|
226
|
-
**Status:** actively maintained · current public release **v0.52.
|
|
226
|
+
**Status:** actively maintained · current public release **v0.52.2** · 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).
|
|
227
227
|
|
|
228
228
|
### Update and rollback
|
|
229
229
|
|
package/dist/core.js
CHANGED
|
@@ -16,7 +16,7 @@ import * as rtk from "./rtk.js";
|
|
|
16
16
|
import { isCursorMcpClient } from "aiterm-steer-delivery";
|
|
17
17
|
import { paneTokenHint } from "./harnesses/pane-tokens.js";
|
|
18
18
|
import { unfinishedDeliveriesOwnedBy } from "./parent-delivery-owners.js";
|
|
19
|
-
import { readRuntimeProcesses, processSubtree, parentProcess, processIdentity, backgroundProcesses, lazyRelayProcess, aitermProcessProbe, windowsConsoleHost } from "./process-runtime.js";
|
|
19
|
+
import { readRuntimeProcesses, processSubtree, parentProcess, processIdentity, backgroundProcesses, lazyRelayProcess, aitermProcessProbe, windowsConsoleHost, processElapsedSeconds } from "./process-runtime.js";
|
|
20
20
|
import { AitermError, telemetryOwnedFailure, ownTelemetryFailure } from "./errors.js";
|
|
21
21
|
import { isWin, SOCKDIR, tmuxCommand, sendPsmuxPayload, loadPtyBufferChunk, pasteBufferBaseArgs, tmuxNewSession, registeredSessionEnvironment, attachCommand, normalizePaneCommand, atomicShellMultiline, appendMarkSentinel, markShellCommand, settlePaneLog, paneCwdArgument, sessionEnvironmentLaunch, } from "./tmux-runtime.js";
|
|
22
22
|
import { sleep, currentUid, runtimeStateBase, safeStatSize, readFileRange, writeJson0600, createEmpty0600, shq, LAUNCH_ID_RE, AGENT_DONE_POLL_MS, AGENT_RATE_LIMIT_POLL_MS, pollGate, AGENT_EVENT_MAX_BYTES, assertSessionName, agentsDir, agentEventPath, agentMetadataPath, writeAgentMetadata, AGENT_EVENT_TAIL_BYTES, agentLabel, agentHarness, subagentInstruction, agentLineageFields, } from "./agent-shared.js";
|
|
@@ -1860,6 +1860,7 @@ function isPidAlive(pid) {
|
|
|
1860
1860
|
function probeWaitLock(p) {
|
|
1861
1861
|
let pid = null;
|
|
1862
1862
|
let at = null;
|
|
1863
|
+
let token = null;
|
|
1863
1864
|
let ageMs = 0;
|
|
1864
1865
|
try {
|
|
1865
1866
|
const st = fs.lstatSync(p);
|
|
@@ -1871,15 +1872,17 @@ function probeWaitLock(p) {
|
|
|
1871
1872
|
pid = v.pid;
|
|
1872
1873
|
if (typeof v.at === "string")
|
|
1873
1874
|
at = v.at;
|
|
1875
|
+
if (typeof v.token === "string")
|
|
1876
|
+
token = v.token;
|
|
1874
1877
|
}
|
|
1875
1878
|
catch {
|
|
1876
1879
|
/* pid 不明のまま鮮度判定に落ちる */
|
|
1877
1880
|
}
|
|
1878
1881
|
if (pid == null)
|
|
1879
|
-
return { pid: null, at, live: ageMs < WAIT_LOCK_FRESH_MS };
|
|
1882
|
+
return { pid: null, at, token, ageMs, live: ageMs < WAIT_LOCK_FRESH_MS };
|
|
1880
1883
|
if (pid === process.pid)
|
|
1881
|
-
return { pid, at, live: false };
|
|
1882
|
-
return { pid, at, live: isPidAlive(pid) };
|
|
1884
|
+
return { pid, at, token, ageMs, live: false };
|
|
1885
|
+
return { pid, at, token, ageMs, live: isPidAlive(pid) };
|
|
1883
1886
|
}
|
|
1884
1887
|
function unlinkStaleWaitLock(p) {
|
|
1885
1888
|
try {
|
|
@@ -2034,16 +2037,42 @@ function probeAgentSendLock(name) {
|
|
|
2034
2037
|
const probe = probeWaitLock(agentSendLockPath(name));
|
|
2035
2038
|
return probe.pid === process.pid && agentSendHeld.has(name) ? { ...probe, live: true } : probe;
|
|
2036
2039
|
}
|
|
2040
|
+
// 持ち主のpidが生きて見えても、lockより後に始まったprocessなら、終了した持ち主のpidを使い回した別のprocessである。
|
|
2041
|
+
// 読みの粗さ(psの経過は1秒刻み)と小さな時計合わせで本人を別人と読まないよう、この秒数を越えて若い時だけ別人とする。
|
|
2042
|
+
const AGENT_SEND_LOCK_OWNER_SLACK_S = 5;
|
|
2043
|
+
function agentSendLockOwnerReplaced(probe) {
|
|
2044
|
+
if (!probe.live || probe.pid == null || probe.pid === process.pid)
|
|
2045
|
+
return false;
|
|
2046
|
+
const created = probe.at === null ? Number.NaN : Date.parse(probe.at);
|
|
2047
|
+
const lockAgeS = (Number.isFinite(created) ? Date.now() - created : probe.ageMs ?? 0) / 1000;
|
|
2048
|
+
let elapsed;
|
|
2049
|
+
try {
|
|
2050
|
+
elapsed = processElapsedSeconds(probe.pid);
|
|
2051
|
+
}
|
|
2052
|
+
catch {
|
|
2053
|
+
// 確かめられない時は、今まで通り生きていると読む(誤って片付けるより断る方に倒す)。
|
|
2054
|
+
return false;
|
|
2055
|
+
}
|
|
2056
|
+
return elapsed === null || elapsed + AGENT_SEND_LOCK_OWNER_SLACK_S < lockAgeS;
|
|
2057
|
+
}
|
|
2058
|
+
// 片付け役が印を持つのは一瞬。持ち主が終了しているか、鮮度猶予より古い印は、片付けの途中で残った物と読む。
|
|
2059
|
+
// 古さでも読むのは、終了した片付け役のpidを別のprocessが使い回すと、pidだけでは生きて見えるため。
|
|
2060
|
+
function agentSendReapMarkStuck(guard) {
|
|
2061
|
+
const probe = probeWaitLock(guard);
|
|
2062
|
+
return !probe.live || (probe.ageMs ?? 0) >= WAIT_LOCK_FRESH_MS;
|
|
2063
|
+
}
|
|
2037
2064
|
// 持ち主が終了したlockを片付ける。確かめてから消すまでの間に別のprocessが新しいlockを作ると生きたlockを消すので、
|
|
2038
2065
|
// 消す役を`.reap`の`O_EXCL`で1つに絞り、その中でもう一度確かめてから消す。lockが在る間は誰も新しいlockを作れない。
|
|
2039
|
-
//
|
|
2040
|
-
|
|
2066
|
+
// replacedTokenは、持ち主が別人と確かめたlockの印。pidが生きて見えるので、確かめた時と同じlockの時だけ消す。
|
|
2067
|
+
// done=片付けた(または片付ける物が無かった)/busy=別のprocessが片付け中/stuck=片付けの印が残っている
|
|
2068
|
+
function reapDeadAgentSendLock(name, replacedToken = null) {
|
|
2041
2069
|
const guard = agentSendReapPath(name);
|
|
2042
2070
|
const token = tryCreateOwnedLock(guard);
|
|
2043
2071
|
if (token === null)
|
|
2044
|
-
return
|
|
2072
|
+
return agentSendReapMarkStuck(guard) ? "stuck" : "busy";
|
|
2045
2073
|
try {
|
|
2046
|
-
|
|
2074
|
+
const probe = probeAgentSendLock(name);
|
|
2075
|
+
if (!probe.live || (replacedToken !== null && probe.token === replacedToken)) {
|
|
2047
2076
|
try {
|
|
2048
2077
|
fs.unlinkSync(agentSendLockPath(name));
|
|
2049
2078
|
}
|
|
@@ -2057,24 +2086,54 @@ function reapDeadAgentSendLock(name) {
|
|
|
2057
2086
|
}
|
|
2058
2087
|
return "done";
|
|
2059
2088
|
}
|
|
2089
|
+
// BellTeamのため: 先頭の「AGENT_SEND_BUSY: agent session '<名前>' は」と、文中の「文字列は送信していません。」で
|
|
2090
|
+
// 未送信の断りを見分けている(2026-10-05)。この2か所を変える時は、変える前に知らせる。
|
|
2060
2091
|
function agentSendBusyError(name, pid, waitMs) {
|
|
2061
2092
|
return new AitermError(`AGENT_SEND_BUSY: agent session '${name}' は${pid != null ? `別プロセス(pid ${pid})` : "先に受け付けた送信"}の処理中です。` +
|
|
2062
2093
|
`${waitMs}ms待っても順番が来ませんでした。文字列は送信していません。少し後で再度 pty_send してください。`, 2);
|
|
2063
2094
|
}
|
|
2064
2095
|
async function acquireAgentSendFileLock(name, deadline, waitMs) {
|
|
2065
2096
|
const p = agentSendLockPath(name);
|
|
2097
|
+
let checkedOwner = null;
|
|
2098
|
+
let checkedOwnerReplaced = false;
|
|
2066
2099
|
for (;;) {
|
|
2067
|
-
|
|
2100
|
+
let token;
|
|
2101
|
+
try {
|
|
2102
|
+
token = tryCreateOwnedLock(p);
|
|
2103
|
+
}
|
|
2104
|
+
catch (e) {
|
|
2105
|
+
// socketの置き場が無ければ、守る相手のsessionにも届かない。lockを取らずに進め、送信側の今まで通りの断りを返す。
|
|
2106
|
+
if (e.code === "ENOENT")
|
|
2107
|
+
return () => { };
|
|
2108
|
+
throw e;
|
|
2109
|
+
}
|
|
2068
2110
|
if (token !== null)
|
|
2069
2111
|
return () => releaseOwnedLock(p, token);
|
|
2070
2112
|
const probe = probeAgentSendLock(name);
|
|
2071
|
-
|
|
2113
|
+
let dead = !probe.live;
|
|
2114
|
+
let replacedToken = null;
|
|
2115
|
+
// 生きて見える持ち主は、初めて見た時に1回、本人かを確かめる。結果は覚え、待つ間にOSへ照会し直さない。
|
|
2116
|
+
if (!dead && probe.pid != null) {
|
|
2117
|
+
const owner = `${probe.pid}@${probe.at}`;
|
|
2118
|
+
if (owner !== checkedOwner) {
|
|
2119
|
+
checkedOwner = owner;
|
|
2120
|
+
checkedOwnerReplaced = agentSendLockOwnerReplaced(probe);
|
|
2121
|
+
}
|
|
2122
|
+
if (checkedOwnerReplaced) {
|
|
2123
|
+
dead = true;
|
|
2124
|
+
replacedToken = probe.token ?? null;
|
|
2125
|
+
}
|
|
2126
|
+
}
|
|
2127
|
+
if (dead && reapDeadAgentSendLock(name, replacedToken) === "stuck") {
|
|
2128
|
+
// BellTeamのため: 先頭の「agent session '<名前>' に、終了した送信のlockを片付ける途中で残った印があります。」と、
|
|
2129
|
+
// 文中の「文字列は送信していません。」で見分け、pty_close→起動し直しへ回している(2026-10-05)。
|
|
2130
|
+
// この文面と、pty_closeがこの印を片付ける事を変える時は、変える前に知らせる。
|
|
2072
2131
|
throw new AitermError(`agent session '${name}' に、終了した送信のlockを片付ける途中で残った印があります。` +
|
|
2073
2132
|
`自動回収は並行送信の混線を招くため行いません。文字列は送信していません。` +
|
|
2074
2133
|
`pty_listで対象を確認し、pty_closeでsessionを閉じてから同じIDで再作成してください`, 2);
|
|
2075
2134
|
}
|
|
2076
2135
|
if (performance.now() >= deadline)
|
|
2077
|
-
throw agentSendBusyError(name,
|
|
2136
|
+
throw agentSendBusyError(name, dead ? null : probe.pid, waitMs);
|
|
2078
2137
|
await sleep(AGENT_SEND_LOCK_POLL_MS);
|
|
2079
2138
|
}
|
|
2080
2139
|
}
|
|
@@ -2120,13 +2179,16 @@ async function withAgentSendLock(name, run) {
|
|
|
2120
2179
|
}
|
|
2121
2180
|
}
|
|
2122
2181
|
// pty_closeの掃除。生きた持ち主のlockは残す(その送信はsessionが無くなった所で失敗し、自分のlockを外す)。
|
|
2182
|
+
// 持ち主が終了したlock、pidを使い回されて生きて見えるlock、残った片付けの印は片付ける。
|
|
2123
2183
|
function cleanupAgentSendLock(name) {
|
|
2124
2184
|
const guard = agentSendReapPath(name);
|
|
2125
2185
|
try {
|
|
2126
|
-
if (fs.existsSync(guard) &&
|
|
2186
|
+
if (fs.existsSync(guard) && agentSendReapMarkStuck(guard))
|
|
2127
2187
|
fs.unlinkSync(guard);
|
|
2128
|
-
if (fs.existsSync(agentSendLockPath(name)))
|
|
2129
|
-
|
|
2188
|
+
if (!fs.existsSync(agentSendLockPath(name)))
|
|
2189
|
+
return;
|
|
2190
|
+
const probe = probeAgentSendLock(name);
|
|
2191
|
+
reapDeadAgentSendLock(name, agentSendLockOwnerReplaced(probe) ? probe.token ?? null : null);
|
|
2130
2192
|
}
|
|
2131
2193
|
catch {
|
|
2132
2194
|
/* 掃除の失敗で閉じる処理を止めない */
|
package/dist/process-runtime.js
CHANGED
|
@@ -36,6 +36,7 @@ export function parsePosixProcessTable(text) {
|
|
|
36
36
|
const POSIX_PS = "/bin/ps";
|
|
37
37
|
const POSIX_TABLE_ARGS = ["-axww", "-o", "pid=,ppid=,pgid=,stat=,lstart=,time=,command="];
|
|
38
38
|
const POSIX_IDENTITY_ARGS = ["-o", "pid=,lstart=", "-p"];
|
|
39
|
+
const POSIX_ELAPSED_ARGS = ["-o", "etime=", "-p"];
|
|
39
40
|
const WINDOWS_PROBE_HEADER = ["$ErrorActionPreference='Stop'", "[Console]::OutputEncoding=[Text.UTF8Encoding]::new($false)"];
|
|
40
41
|
const WINDOWS_PROBE_ARGS = ["-NoLogo", "-NoProfile", "-NonInteractive", "-EncodedCommand"];
|
|
41
42
|
// 先頭2行だけのbase64。3 byteの区切りで切り、後ろの行に左右されない前方一致にする。
|
|
@@ -47,7 +48,8 @@ const WINDOWS_PROBE_PREFIX = (() => {
|
|
|
47
48
|
export function aitermProcessProbe(command) {
|
|
48
49
|
if (command.includes(` ${WINDOWS_PROBE_ARGS.join(" ")} ${WINDOWS_PROBE_PREFIX}`))
|
|
49
50
|
return true;
|
|
50
|
-
return command === `${POSIX_PS} ${POSIX_TABLE_ARGS.join(" ")}` || command.startsWith(`${POSIX_PS} ${POSIX_IDENTITY_ARGS.join(" ")} `)
|
|
51
|
+
return command === `${POSIX_PS} ${POSIX_TABLE_ARGS.join(" ")}` || command.startsWith(`${POSIX_PS} ${POSIX_IDENTITY_ARGS.join(" ")} `)
|
|
52
|
+
|| command.startsWith(`${POSIX_PS} ${POSIX_ELAPSED_ARGS.join(" ")} `);
|
|
51
53
|
}
|
|
52
54
|
/** Windowsのconsole host。console processの起動に付いて立ち、最後のconsole processが終わると消える。 */
|
|
53
55
|
export function windowsConsoleHost(row) {
|
|
@@ -157,6 +159,42 @@ export function readProcessIdentities(pids) {
|
|
|
157
159
|
return { pid: row.pid, started_identity: new Date(row.started_identity).toISOString() };
|
|
158
160
|
});
|
|
159
161
|
}
|
|
162
|
+
/** `ps -o etime=`の「[[日-]時:]分:秒」を秒にする。 */
|
|
163
|
+
export function parsePosixElapsed(value) {
|
|
164
|
+
const match = /^(?:(?:(\d+)-)?(\d+):)?(\d+):(\d+)$/.exec(value.trim());
|
|
165
|
+
if (!match)
|
|
166
|
+
return null;
|
|
167
|
+
return Number(match[1] ?? 0) * 86400 + Number(match[2] ?? 0) * 3600 + Number(match[3]) * 60 + Number(match[4]);
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* processが始まってからの秒数。process表に無ければnull。OSへ照会できなかった時は例外にする。
|
|
171
|
+
* 開始時刻でなく経過で返す。POSIXのpsが出す開始時刻は、time zoneの設定と時計合わせで読みが変わる。
|
|
172
|
+
*/
|
|
173
|
+
export function processElapsedSeconds(pid) {
|
|
174
|
+
if (!Number.isSafeInteger(pid) || pid <= 0)
|
|
175
|
+
throw new AitermError("process照会のpidが不正です", 2);
|
|
176
|
+
if (isWin) {
|
|
177
|
+
const row = readProcessIdentities([pid])[0];
|
|
178
|
+
if (!row)
|
|
179
|
+
return null;
|
|
180
|
+
if (row.started_identity === null)
|
|
181
|
+
throw new AitermError("processの開始時刻を取得できません", 2);
|
|
182
|
+
return Math.max(0, (Date.now() - Date.parse(row.started_identity)) / 1000);
|
|
183
|
+
}
|
|
184
|
+
const result = spawnSync(POSIX_PS, [...POSIX_ELAPSED_ARGS, String(pid)], {
|
|
185
|
+
encoding: "utf8", env: { ...process.env, LC_ALL: "C" }, timeout: 10000,
|
|
186
|
+
});
|
|
187
|
+
const text = (result.stdout ?? "").trim();
|
|
188
|
+
// 該当するprocessが無い時、psは何も出さずstatus 1で終わる。
|
|
189
|
+
if (result.error || (result.status !== 0 && !(result.status === 1 && !text)))
|
|
190
|
+
throw new AitermError("OSのprocess一覧を取得できません", 2);
|
|
191
|
+
if (!text)
|
|
192
|
+
return null;
|
|
193
|
+
const elapsed = parsePosixElapsed(text);
|
|
194
|
+
if (elapsed === null)
|
|
195
|
+
throw new AitermError("processの経過時間の形式を認識できません", 2);
|
|
196
|
+
return elapsed;
|
|
197
|
+
}
|
|
160
198
|
// Windowsの親PIDは親の終了後も残り、別processへ再利用される。子より後に始まったprocessは
|
|
161
199
|
// 本当の親ではないので、親子関係として辿らない(辿ると循環や無関係なprocessの混入が起きる)。
|
|
162
200
|
export function parentProcess(row, byPid) {
|
package/docs/DESIGN.md
CHANGED
|
@@ -314,6 +314,8 @@ stale send lockは並行processとのABAを避けるため自動削除せず、
|
|
|
314
314
|
同じIDで再作成する。全session一括停止は公開しない。
|
|
315
315
|
agent sessionへの送信1本を守る`<name>.agent-send.lock`は持つ時間が長いので、持ち主が終了した残骸を次の送信が片付ける。消す役を`<name>.agent-send.lock.reap`の排他作成で1つに絞り、
|
|
316
316
|
その中でもう一度確かめてから消すので、確かめた後で別のlockに入れ替わらない。消す役の印そのものが残った時だけは自動で消さず、`pty_close`を案内する。
|
|
317
|
+
終了した持ち主のpidを別のprocessが使い回すと、pidだけでは生きて見える。生きて見える持ち主は、processが始まってからの経過とlockを作ってからの時間を比べ、
|
|
318
|
+
lockより後に始まったprocessなら別人と読んで片付ける。消す役の印は、5秒より古ければ残った物と読む。`pty_close`は、どちらも片付ける。
|
|
317
319
|
|
|
318
320
|
Grokのread-only sandbox起動拒否は、`src/harnesses/grok.ts`の
|
|
319
321
|
`assertGrokSandboxNotRejected`がCLIのエラー表示から検出する。`src/core.ts`の共通入力受付待機は
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aiterm-mcp",
|
|
3
|
-
"version": "0.52.
|
|
3
|
+
"version": "0.52.2",
|
|
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": [
|