aiterm-mcp 0.12.0 → 0.12.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.
@@ -77,7 +77,11 @@ function appendEvent(file, event) {
77
77
  if (!st.isFile() || st.uid !== uid() || st.nlink !== 1 || (st.mode & 0o077) !== 0) {
78
78
  fail(`event file が安全ではありません: ${file}`);
79
79
  }
80
- fs.writeSync(fd, line, undefined, "utf8");
80
+ const written = fs.writeSync(fd, line, undefined, "utf8");
81
+ if (written < Buffer.byteLength(line, "utf8")) {
82
+ fs.ftruncateSync(fd, st.size);
83
+ fail(`event file への書込みが途中で終了しました: ${file}`);
84
+ }
81
85
  }
82
86
  finally {
83
87
  fs.closeSync(fd);
package/dist/core.js CHANGED
@@ -48,6 +48,8 @@ const AGENT_DONE_SCREEN_SETTLE_MAX_POLLS = 5;
48
48
  const AGENT_DONE_SCREEN_SETTLE_MIN_SAMPLES = 3;
49
49
  const AGENT_SUBMIT_DELAY_MS = 250;
50
50
  const AGENT_EVENT_MAX_BYTES = 1024 * 1024;
51
+ const AGENT_EVENT_TAIL_BYTES = 64 * 1024;
52
+ const AGENT_METADATA_NEGATIVE_CACHE_TTL_MS = 2_000;
51
53
  const AGENT_TUI_READY_TIMEOUT_MS = 30_000;
52
54
  const AGENT_TUI_READY_POLL_MS = 500;
53
55
  const AGENT_TUI_READY_LINES = 45;
@@ -318,6 +320,7 @@ function existingAgentsDir() {
318
320
  }
319
321
  function cleanupAgentState(name) {
320
322
  assertSessionName(name);
323
+ agentMetadataNegativeCache.delete(name);
321
324
  const dir = existingAgentsDir();
322
325
  if (!dir)
323
326
  return;
@@ -1053,10 +1056,12 @@ export function killAll() {
1053
1056
  /* agent state dir 不在等は無視 */
1054
1057
  }
1055
1058
  }
1059
+ agentMetadataNegativeCache.clear();
1056
1060
  return "killed all sessions on this socket";
1057
1061
  }
1058
1062
  const DEFAULT_AGENT_DONE_TIMEOUT = 600;
1059
1063
  const agentWaitLocks = new Set();
1064
+ const agentMetadataNegativeCache = new Map();
1060
1065
  function normalizeAgentLauncherWait(v) {
1061
1066
  if (v == null || v === "none")
1062
1067
  return "none";
@@ -1798,9 +1803,16 @@ function tryLoadAgentMetadata(name) {
1798
1803
  }
1799
1804
  function latestAgentDoneEvent(meta) {
1800
1805
  const size = safeStatSize(meta.event_file);
1801
- if (size === 0 || size > AGENT_EVENT_MAX_BYTES)
1806
+ if (size === 0)
1802
1807
  return null;
1803
- const text = readFileRange(meta.event_file, 0, size).toString("utf8");
1808
+ const isTailRead = size > AGENT_EVENT_TAIL_BYTES;
1809
+ let text = readFileRange(meta.event_file, isTailRead ? size - AGENT_EVENT_TAIL_BYTES : 0, size).toString("utf8");
1810
+ if (isTailRead) {
1811
+ const firstNewline = text.indexOf("\n");
1812
+ if (firstNewline === -1)
1813
+ return null;
1814
+ text = text.slice(firstNewline + 1);
1815
+ }
1804
1816
  let latest = null;
1805
1817
  for (const line of text.split("\n")) {
1806
1818
  if (!line.trim() || Buffer.byteLength(line, "utf8") > 64 * 1024)
@@ -1966,9 +1978,21 @@ function inferAgentFrontend(name, meta, screen) {
1966
1978
  return "unknown";
1967
1979
  }
1968
1980
  function agentReadMetadataSuffix(name, screen) {
1969
- const meta = tryLoadAgentMetadata(name);
1970
- if (!meta)
1981
+ const now = Date.now();
1982
+ const negativeCacheUntil = agentMetadataNegativeCache.get(name);
1983
+ if (negativeCacheUntil && negativeCacheUntil > now)
1971
1984
  return "";
1985
+ let meta;
1986
+ try {
1987
+ meta = loadAgentMetadata(name);
1988
+ }
1989
+ catch (e) {
1990
+ if (e instanceof AitermError && e.message.includes("agent_done 管理セッションではありません")) {
1991
+ agentMetadataNegativeCache.set(name, now + AGENT_METADATA_NEGATIVE_CACHE_TTL_MS);
1992
+ }
1993
+ return "";
1994
+ }
1995
+ agentMetadataNegativeCache.delete(name);
1972
1996
  const ev = latestAgentDoneEvent(meta);
1973
1997
  const bits = [
1974
1998
  "agent",
@@ -2445,6 +2469,8 @@ export function openAgent(kind, opts = {}) {
2445
2469
  ? createCodexAgentMetadata(sid, cwd, opts.prompt ? "pending" : "none", { model, effort })
2446
2470
  : createGrokAgentMetadata(kind, sid, cwd, opts.prompt ? "pending" : "none")
2447
2471
  : null;
2472
+ if (meta)
2473
+ agentMetadataNegativeCache.delete(sid);
2448
2474
  launchNote = buildAgentLaunchNote(kind, model, effort, meta);
2449
2475
  const cmd = buildAgentCmd(kind, binForCmd, model, effort, opts.prompt ?? null, meta);
2450
2476
  const envPrefix = agentEnvPrefix(meta, sid);
@@ -75,7 +75,11 @@ function appendEvent(file, event) {
75
75
  if (!st.isFile() || st.uid !== uid() || st.nlink !== 1 || (st.mode & 0o077) !== 0) {
76
76
  fail(`event file が安全ではありません: ${file}`);
77
77
  }
78
- fs.writeSync(fd, line, undefined, "utf8");
78
+ const written = fs.writeSync(fd, line, undefined, "utf8");
79
+ if (written < Buffer.byteLength(line, "utf8")) {
80
+ fs.ftruncateSync(fd, st.size);
81
+ fail(`event file への書込みが途中で終了しました: ${file}`);
82
+ }
79
83
  }
80
84
  finally {
81
85
  fs.closeSync(fd);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aiterm-mcp",
3
- "version": "0.12.0",
3
+ "version": "0.12.1",
4
4
  "mcpName": "io.github.kitepon-rgb/aiterm-mcp",
5
5
  "description": "AI-driven persistent terminal as a local stdio MCP server (tmux-backed). Holds one local PTY; SSH and containers are just commands you send into it. Also launches interactive Codex/Grok/Composer agent TUIs in a persistent terminal. Token-reducing reads.",
6
6
  "keywords": [