aiterm-mcp 0.3.1 → 0.4.0

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.ja.md CHANGED
@@ -179,7 +179,7 @@ claude mcp add --scope user --transport stdio aiterm -- aiterm-mcp
179
179
 
180
180
  ## 既知の制約(バグではなく仕様)
181
181
 
182
- - **ネスト中(ssh / docker / REPL)は quiescence が原理的に効かない。** 前面コマンドがシェル集合(bash/sh/zsh/fish/dash)の外になるため。完了検出は `until`(プロンプト等の正規表現)か `mark: true`(終了コード付き sentinel)を使う。
182
+ - **ネスト中(ssh / docker / REPL)は quiescence が原理的に効かない。** 前面コマンドがシェル集合(bash/sh/zsh/fish/dash)の外になるため。ネスト中で `until` 未指定のときは、待っても完了を確定できる信号が無いので、`pty_read({ wait: true })` はフル `timeout` を空費せず出力静止時点で `is_complete=False via nested` と早期に返し、`until`(プロンプト等の正規表現)か `mark: true`(終了コード付き sentinel)の指定を促す。
183
183
  - **`is_complete=False` は失敗ではない。** 「timeout 内に完了を観測できなかった」という意味。長時間コマンドでは `timeout` を伸ばすか `until`/`mark` を使う。
184
184
  - **破壊ゲートはサンドボックスではなく tripwire。** よくある破壊形だけを弾く。相対パスの `rm`、`$VAR` 展開後に危険化するもの、ssh 先で実行されるコマンドは捕捉しない。
185
185
  - **`pty_send({ rtk: true })` は単行コマンドのみ+外部 `rtk` バイナリが必要**(無ければ素通し)。一方 `pty_read({ rtk: true })` の reducer は自前実装で rtk 非依存。
package/README.md CHANGED
@@ -179,7 +179,7 @@ Before sending, `pty_send` blocks destructive commands (`rm -rf /`, `mkfs`, `dd
179
179
 
180
180
  ## Known constraints (by design, not bugs)
181
181
 
182
- - **While nested (ssh / docker / REPL), quiescence cannot fire by design**, because the foreground command is no longer in the shell set (bash/sh/zsh/fish/dash). Use `until` (a regex for the prompt etc.) or `mark: true` (an exit-code sentinel) for completion.
182
+ - **While nested (ssh / docker / REPL), quiescence cannot fire by design**, because the foreground command is no longer in the shell set (bash/sh/zsh/fish/dash). When nested with no `until`, `pty_read({ wait: true })` returns early as `is_complete=False via nested` (rather than burning the full `timeout`, since no signal can confirm completion there) with a note to pass `until` (a regex for the prompt) or `mark: true` (an exit-code sentinel) for a confirmed completion.
183
183
  - **`is_complete=False` is not a failure.** It means "completion was not observed within `timeout`." For long commands, raise `timeout` or use `until`/`mark`.
184
184
  - **The destructive gate is a tripwire, not a sandbox.** It blocks common destructive forms only. It does **not** catch relative-path `rm`, things that become dangerous after `$VAR` expansion, or commands run on the far side of an SSH session.
185
185
  - **`pty_send({ rtk: true })` is single-line only and needs the external `rtk` binary** (passthrough without it). The `pty_read({ rtk: true })` reducer, by contrast, is self-contained and rtk-independent.
package/dist/core.js CHANGED
@@ -332,7 +332,7 @@ async function settleWinLog(name) {
332
332
  await sleep(POLL * 1000);
333
333
  }
334
334
  }
335
- /** 完了境界の4層: dead / until 一致 / (出力静止 ∧ シェルに戻った) / timeout。 */
335
+ /** 完了境界: dead / until 一致 / (出力静止 ∧ シェル復帰)=quiescent / (ネスト中+until無しで出力静止)=nested(未確定・早期返却) / timeout。 */
336
336
  async function waitCompletion(name, untilRe, timeout) {
337
337
  // 締切は単調時計で測る。Date.now() は NTP 補正やサスペンドで巻き戻り、長時間待ちで誤判定する(Python は time.monotonic)。
338
338
  const deadline = performance.now() + timeout * 1000;
@@ -362,10 +362,22 @@ async function waitCompletion(name, untilRe, timeout) {
362
362
  }
363
363
  if (size === lastSize) {
364
364
  stable++;
365
- if (stable >= STABLE_POLLS && SHELLS.has(paneCurrentCommand(name))) {
366
- if (isWin)
367
- await settleWinLog(name);
368
- return [true, "quiescent"];
365
+ if (stable >= STABLE_POLLS) {
366
+ const fg = paneCurrentCommand(name);
367
+ if (SHELLS.has(fg)) {
368
+ if (isWin)
369
+ await settleWinLog(name);
370
+ return [true, "quiescent"]; // 出力静止 ∧ シェル復帰 = 確証つき完了
371
+ }
372
+ // ネスト中(前面が ssh/docker/REPL 等でシェル集合外)は quiescence の「シェル復帰」条件を
373
+ // 原理的に満たせない。until 未指定ならこれ以上待っても確証は増えない(until/dead/quiescent の
374
+ // いずれも発火し得ない)ので、出力が静止した時点で「未確定」のまま早期返却し until/mark を促す。
375
+ // fg==="" は前面コマンド取得失敗=ネスト断定不可なので早期返却せず従来どおり timeout まで待つ。
376
+ if (!until && fg !== "") {
377
+ if (isWin)
378
+ await settleWinLog(name);
379
+ return [false, "nested"];
380
+ }
369
381
  }
370
382
  }
371
383
  else {
@@ -377,6 +389,17 @@ async function waitCompletion(name, untilRe, timeout) {
377
389
  await sleep(POLL * 1000);
378
390
  }
379
391
  }
392
+ // 完了ステータス → is_complete 表記。確証のある層のみ True(until/dead/quiescent)。
393
+ // timeout と nested(ネスト中・出力静止だが確証なし)は False。nested は until/mark を促す注記を添える。
394
+ function completionSuffix(status) {
395
+ const complete = status === "until" || status === "dead" || status === "quiescent";
396
+ let s = ` [is_complete=${complete ? "True" : "False"} via ${status}]`;
397
+ if (status === "nested")
398
+ s +=
399
+ " ネスト中(前面が ssh/docker/REPL 等)は出力静止だけでは完了を確定できません。" +
400
+ "until(リモートのプロンプト等の正規表現)か mark:true で完了を指定してください。";
401
+ return s;
402
+ }
380
403
  function rtkRewrite(text) {
381
404
  if (text.trim().includes("\n"))
382
405
  return text;
@@ -506,20 +529,16 @@ export async function readOutput(name, o = {}) {
506
529
  const [reduced, rname] = rtk.reduce(cmd, framed);
507
530
  if (reduced !== null) {
508
531
  const meta = `[aiterm ${name}: rtk:${rname} 適用 / ~${estimateTokens(reduced)} tok (raw ~${estimateTokens(text)} tok)]`;
509
- if (status) {
510
- const complete = status !== "timeout";
511
- return reduced + "\n" + meta + ` [is_complete=${complete ? "True" : "False"} via ${status}]`;
512
- }
532
+ if (status)
533
+ return reduced + "\n" + meta + completionSuffix(status);
513
534
  return reduced + "\n" + meta;
514
535
  }
515
536
  }
516
537
  // reducer 非該当 → 汎用削減へフォールバック
517
538
  }
518
539
  const [body, meta] = reduceOutput(text, name, !o.range);
519
- if (status) {
520
- const complete = status !== "timeout";
521
- return body + "\n" + meta + ` [is_complete=${complete ? "True" : "False"} via ${status}]`;
522
- }
540
+ if (status)
541
+ return body + "\n" + meta + completionSuffix(status);
523
542
  return body + "\n" + meta;
524
543
  }
525
544
  export function listSessions() {
package/dist/index.js CHANGED
@@ -12,7 +12,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
12
12
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
13
13
  import { z } from "zod";
14
14
  import * as core from "./core.js";
15
- const server = new McpServer({ name: "aiterm", version: "0.3.1" });
15
+ const server = new McpServer({ name: "aiterm", version: "0.4.0" });
16
16
  function ok(s) {
17
17
  return { content: [{ type: "text", text: s }] };
18
18
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aiterm-mcp",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "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. Token-reducing reads.",
5
5
  "keywords": [
6
6
  "mcp",