aiterm-mcp 0.50.0 → 0.51.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/CHANGELOG.md CHANGED
@@ -7,6 +7,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.51.0] - 2026-10-04
11
+
12
+ ### 追加
13
+
14
+ - `aiterm-delivery-wake --parent <codex|claude|cursor>`。MCPの本体を使う時だけ起こす中継(`mcp-lazy`)の後ろで、眠っている本体を
15
+ 親配送の引き取りのために起こすべきかを返す(ADR 0082)。終了した持ち主がその種類の親の配送を持ったままならexit 0、
16
+ 無ければexit 1。何も出力せず、子processを起こさない。同じ判定を回す席のうち1席だけが起こす。
17
+ 中継には先行起動の判定として渡す(`MCP_LAZY_WAKE_COMMAND`)。
18
+
19
+ ### 変更
20
+
21
+ - `pty_observe`の`activity.post_startup_process_count`で、中継`mcp-lazy`の直接の子を数えない(ADR 0082)。
22
+ 中継は道具を初めて呼ばれた時に本体を自分の下に起こすので、中継ごしの席は道具を一度使うと数が0へ戻らなかった。
23
+ 見分けるのは、親の起動時のargvの先頭のbasenameが`mcp-lazy`で始まること。その下で動くprocessは今までどおり数える。
24
+
25
+ ### 修正
26
+
27
+ - Cursor親の受け取りprocess(`cursor-parent-receive`)が、親のCursorが終わった後も残り、後から完了した子の回答を引き取っていた
28
+ (aiterm-steer-delivery 0.1.12)。Cursorの背景shellは親から切り離されて動くので、席を閉じても最長24時間残る。
29
+ 引き取られた回答は`submitted`になるが、読む相手は居ない。受け取りprocessは、出力の読み手が居なくなったら引き取らずに終わる。
30
+ その配送は`sending`のまま残り、24時間で`CURSOR_PARENT_DELIVERY_UNCLAIMED`になる。本文は今までどおり記録に残る。
31
+
10
32
  ## [0.50.0] - 2026-10-04
11
33
 
12
34
  ### 変更
@@ -2028,7 +2050,8 @@ prototype (preserved under `prototype/python/` as the porting source and referen
2028
2050
  `ubuntu-latest` for Node 18/20/22, publishing to npm on `v*` tags with
2029
2051
  provenance.
2030
2052
 
2031
- [Unreleased]: https://github.com/kitepon/aiterm-mcp/compare/v0.50.0...HEAD
2053
+ [Unreleased]: https://github.com/kitepon/aiterm-mcp/compare/v0.51.0...HEAD
2054
+ [0.51.0]: https://github.com/kitepon/aiterm-mcp/compare/v0.50.0...v0.51.0
2032
2055
  [0.50.0]: https://github.com/kitepon/aiterm-mcp/compare/v0.49.0...v0.50.0
2033
2056
  [0.49.0]: https://github.com/kitepon/aiterm-mcp/compare/v0.48.0...v0.49.0
2034
2057
  [0.48.0]: https://github.com/kitepon/aiterm-mcp/compare/v0.47.1...v0.48.0
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.50.0** · 動作対象は Linux · WSL2 · macOS · Windows ネイティブ · MIT · [変更履歴](CHANGELOG.md)。
212
+ **状態:** 開発継続中 · 現行公開版 **v0.51.0** · 動作対象は Linux · WSL2 · macOS · Windows ネイティブ · MIT · [変更履歴](CHANGELOG.md)。
213
213
 
214
214
  ### 更新と巻き戻し
215
215
 
@@ -299,7 +299,7 @@ $ aiterm-wait --session codex1 --cursor <event_cursor> # exit 0=done / 3=timeo
299
299
 
300
300
  Cursorの`model`は`gpt-5.6-luna`のようなbase model、`reasoning_effort`は`high`のように別指定する。adapterは現行`model-effort` IDを`cursor-agent models`へ照合し、起動中変更はCursor標準model pickerのparameter editorを使う。不在時は別modelへfallbackしない。
301
301
 
302
- 新しい端末の環境は、その端末を開いたMCP processの環境で決まる(v0.50.0から。ADR 0081)。tmuxのserverを
302
+ 新しい端末の環境は、その端末を開いたMCP processの環境で決まる(0.50.0から。ADR 0081)。tmuxのserverを
303
303
  先に起こした別の呼び出し元の値は入らない。Aitermが端末ごとに付け直す`AITERM_SESSION_ID`・`AITERM_AGENT_*`は
304
304
  継がない。harnessがMCP processへ渡す環境が少なければ、端末の環境もその分だけになる(Codexは既定で
305
305
  `HOME`・`PATH`・`SHELL`・`TERM`。要る変数は`[mcp_servers.aiterm]`の`env_vars`で渡す)。
@@ -517,11 +517,20 @@ sessionへ登録する名前の配列で、`pty_list({ env_keys: ["JOB_OWNER"] }
517
517
  `token_hint`は画面の直近token表示値またはnull。画面本文や生argvを解析する必要はない。
518
518
 
519
519
  agent sessionを閉じると失うものは、次の2項目で分かる。`activity.post_startup_process_count`は、`agent_launch`の起動準備が
520
- 完了した時点(初手を送る前)に居なかったprocessの数。起動直後の1分に始めた裏の作業も数える。Codex自身の補助process
521
- (`codex-code-mode-host`)は数えない。`pending_child_deliveries`は、そのsessionが親として待っていて、まだ届け終えていない
520
+ 完了した時点(初手を送る前)に居なかったprocessの数。起動直後の1分に始めた裏の作業も数える。数えないものは2つ。Codex自身の
521
+ 補助process(`codex-code-mode-host`)と、中継`mcp-lazy`の直接の子(初めて使う時に起こすMCPの本体と、先行起動の判定)。
522
+ その下で動くprocessは数える。`pending_child_deliveries`は、そのsessionが親として待っていて、まだ届け終えていない
522
523
  子の結果の数。呼び出した側が誰でも付く。どちらも、分からない時はnull(通常PTY、harnessのprocessを特定できない時。
523
524
  processの数は0.48.0以前が起動したagentも)。別端末が旧版の時は項目ごと無い。nullと項目なしは「分からない」で、0ではない。
524
525
 
526
+ MCPの本体を初めて使う時まで起こさない中継の後ろにAitermを登録すると、眠っている本体は、終了した持ち主の配送を引き取れない。
527
+ `aiterm-delivery-wake --parent <codex|claude|cursor>`は、起こす必要があるかを返す。その種類の親の配送を、終了した持ち主が
528
+ 持ったままならexit 0、そうでなければexit 1、引数の誤りはexit 2。何も出力せず、子processを起こさない。同じ判定を回す席のうち
529
+ 1席だけが起こすよう、配送に印を付ける(引き取られないまま30秒過ぎたら、次の判定が印を取り直す)。`mcp-lazy`には、本体と同じ環境で
530
+ 先行起動の判定として渡す。
531
+ `MCP_LAZY_WAKE_COMMAND='["/absolute/path/to/node","/absolute/path/to/aiterm-mcp/dist/delivery-wake-cli.js","--parent","claude"]'`
532
+ 持ち主の開始時刻はLinuxでは`/proc`で照合する。それ以外のOSでは、pidがあれば生きている持ち主として扱う。
533
+
525
534
  `agent_launch({ harness, cwd, trust_project: true })`はpromptなしでも既知のworkspace・project hooks・MCP初期同意を
526
535
  進め、入力受付とharness生存を確認して`startup.status="ready"`を返す。指定なしのpromptなし起動は`not_checked`。
527
536
  Claude Code初回起動の文字表示テーマ選択では、画面で選択済みの項目を確定して起動を続ける。
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.50.0** · 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).
226
+ **Status:** actively maintained · current public release **v0.51.0** · 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
 
@@ -321,7 +321,7 @@ The canonical harness choices are:
321
321
  | `grok-cli` | Grok Build CLI | Grok model selected with `model`; live catalog check |
322
322
  | `cursor-cli` | Cursor Agent CLI | GPT, Claude, Grok, or another Cursor catalog model; normal transcript completion |
323
323
 
324
- A new terminal gets the environment of the MCP process that opened it (since v0.50.0, ADR 0081).
324
+ A new terminal gets the environment of the MCP process that opened it (since 0.50.0, ADR 0081).
325
325
  Values from another caller that happened to start the tmux server first no longer appear in it.
326
326
  `AITERM_SESSION_ID` and `AITERM_AGENT_*` are not inherited; aiterm sets them per terminal. If a harness
327
327
  passes only a few variables to its MCP process, the terminal has only those (Codex passes `HOME`, `PATH`,
@@ -550,11 +550,22 @@ displayed token count or null. Callers do not need raw argv or pane-text parsing
550
550
 
551
551
  Two fields tell a caller what closing an agent session would lose. `activity.post_startup_process_count` is the number of
552
552
  processes in the session that did not exist when `agent_launch` finished startup (before the first prompt), so background work
553
- started in the first minute is counted too; Codex's own `codex-code-mode-host` helper is not counted. `pending_child_deliveries`
553
+ started in the first minute is counted too. Two kinds of process are not counted: Codex's own `codex-code-mode-host` helper, and
554
+ the direct children of an `mcp-lazy` relay (the MCP server it starts on first use and its wake predicate). What runs below them
555
+ is counted. `pending_child_deliveries`
554
556
  is the number of sub-agent results this session is still waiting for as a parent, whoever the caller is. Both are null when
555
557
  Aiterm cannot tell (ordinary terminals, agents launched by 0.48.0 or earlier for the process count, or an unresolved harness
556
558
  process), and may be absent when a remote host runs an older Aiterm. Treat null or absent as unknown, not as zero.
557
559
 
560
+ When Aiterm is registered behind a relay that starts the MCP server only on first use, a sleeping server cannot take over the
561
+ deliveries of an owner that has exited. `aiterm-delivery-wake --parent <codex|claude|cursor>` answers whether one needs to start:
562
+ exit 0 when an exited owner still holds a delivery for that parent kind, exit 1 otherwise, exit 2 for bad arguments. It prints
563
+ nothing, starts no child process, and marks the delivery so that only one of the seats running the same check starts its server
564
+ (the mark is retaken after 30 seconds if nobody took the delivery over). For `mcp-lazy`, pass it as the wake predicate with the
565
+ same environment as the server:
566
+ `MCP_LAZY_WAKE_COMMAND='["/absolute/path/to/node","/absolute/path/to/aiterm-mcp/dist/delivery-wake-cli.js","--parent","claude"]'`.
567
+ Owner start times are checked through `/proc` on Linux; elsewhere an existing PID is treated as a live owner.
568
+
558
569
  `agent_launch({ harness, cwd, trust_project: true })` completes known workspace, project-hook, and project-MCP startup
559
570
  consent even without a prompt, then verifies input readiness and harness liveness before returning `startup.status="ready"`.
560
571
  For Claude Code's first-run text-style menu, it confirms the item already selected on screen before continuing startup.
@@ -791,7 +802,7 @@ trust, memory, or history stores. Cleanup removes only aiterm-owned launch metad
791
802
  correlation files.
792
803
 
793
804
  The ordinary environment comes from the MCP process that opened the terminal, not from whichever caller
794
- started the persistent multiplexer server (since v0.50.0). Every harness still accepts
805
+ started the persistent multiplexer server (since 0.50.0). Every harness still accepts
795
806
  `env_vars: ["NAME", ...]`; those names are placed on the launch command and registered on the session.
796
807
 
797
808
  ## License
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 } from "./process-runtime.js";
19
+ import { readRuntimeProcesses, processSubtree, parentProcess, processIdentity, backgroundProcesses, lazyRelayProcess } 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_EVENT_MAX_BYTES, assertSessionName, agentsDir, agentEventPath, agentMetadataPath, writeAgentMetadata, AGENT_EVENT_TAIL_BYTES, agentLabel, agentHarness, subagentInstruction, agentLineageFields, } from "./agent-shared.js";
@@ -1077,6 +1077,18 @@ export function selectHarnessProcesses(meta, rows, subtree) {
1077
1077
  });
1078
1078
  return roots;
1079
1079
  }
1080
+ // 起動完了の控えに無いprocessの数。harnessと中継が自分のために立てるprocessは、利用者の作業ではないので数えない。
1081
+ // その下で動くprocessは数える。
1082
+ export function postStartupProcessCount(kind, activityRows, rows, startup) {
1083
+ const byPid = new Map(rows.map(row => [row.pid, row]));
1084
+ const relayChild = (row) => {
1085
+ const parent = parentProcess(row, byPid);
1086
+ return parent !== undefined && lazyRelayProcess(parent.command);
1087
+ };
1088
+ return new Set(activityRows
1089
+ .filter(row => !(kind === "codex" && codexHelperProcess(row.command)) && !relayChild(row))
1090
+ .map(row => `${row.pid}:${row.started_identity}`).filter(identity => !startup.has(identity))).size;
1091
+ }
1080
1092
  export function observeSession(name, cursor) {
1081
1093
  assertSessionName(name);
1082
1094
  const previous = cursor === undefined ? null : decodeActivityCursor(cursor, name);
@@ -1178,9 +1190,7 @@ export function observeSession(name, cursor) {
1178
1190
  background_cpu_seconds: Object.values(backgroundCpu).reduce((sum, cpu) => sum + cpu, 0),
1179
1191
  background_cpu_delta_seconds: comparable ? Object.entries(backgroundCpu).reduce((sum, [identity, cpu]) => sum + cpu - (previous.background_processes[identity] ?? 0), 0) : null,
1180
1192
  background_cpu_delta_complete: comparable ? Object.keys(previous.background_processes).every(identity => identity in backgroundCpu) : null,
1181
- post_startup_process_count: startup ? new Set(activityRows
1182
- .filter(row => !(meta?.kind === "codex" && codexHelperProcess(row.command)))
1183
- .map(row => `${row.pid}:${row.started_identity}`).filter(identity => !startup.has(identity))).size : null,
1193
+ post_startup_process_count: startup ? postStartupProcessCount(meta.kind, activityRows, rows, startup) : null,
1184
1194
  };
1185
1195
  return result;
1186
1196
  }
@@ -0,0 +1,142 @@
1
+ #!/usr/bin/env node
2
+ // aiterm-delivery-wake — 眠っているAitermのMCP本体を、親配送の引き取りのために起こすべきかを返す。
3
+ // MCPを使う時だけ起こす中継(mcp-lazy)の先行起動の判定に渡す。本体が眠っている間は、終了した持ち主の配送を
4
+ // 引き取る者が居ない(引き取りは起きている本体が5秒おきに行う)。
5
+ // exit 0=起こす / 1=眠ったままでよい / 2=引数の誤り。stdoutへは何も出さない。
6
+ // 中継の下で動くので、子processを起こさない(起こすと席のprocess数に入る)。node builtinとstate-rootだけに依存する。
7
+ import * as fs from "node:fs";
8
+ import * as path from "node:path";
9
+ import { fileURLToPath } from "node:url";
10
+ import { currentUid, runtimeStateBase } from "./state-root.js";
11
+ const DIRECTORY = {
12
+ codex: "parent-deliveries", claude: "claude-parent-deliveries", cursor: "cursor-parent-deliveries",
13
+ };
14
+ // 起こす役を引き受けた印の寿命。起こした本体は起動してすぐ引き取る。引き取られないまま過ぎたら、次の判定が引き受け直す。
15
+ const CLAIM_TTL_MS = 30_000;
16
+ const USAGE = "usage: aiterm-delivery-wake --parent <codex|claude|cursor>";
17
+ function processExists(pid) {
18
+ if (pid <= 0)
19
+ return false;
20
+ try {
21
+ process.kill(pid, 0);
22
+ return true;
23
+ }
24
+ catch (error) {
25
+ return error.code === "EPERM";
26
+ }
27
+ }
28
+ // psのlstartと同じ値を/procから作る(起動時刻+開始tick。LinuxのUSER_HZは100)。/procの無いOSはnull。
29
+ function linuxStartedAt(pid) {
30
+ try {
31
+ const stat = fs.readFileSync(`/proc/${pid}/stat`, "utf8");
32
+ const ticks = Number(stat.slice(stat.lastIndexOf(")") + 2).split(" ")[19]);
33
+ const boot = Number(/^btime (\d+)$/m.exec(fs.readFileSync("/proc/stat", "utf8"))?.[1]);
34
+ return Number.isFinite(ticks) && Number.isFinite(boot) ? (boot + Math.floor(ticks / 100)) * 1000 : null;
35
+ }
36
+ catch {
37
+ return null;
38
+ }
39
+ }
40
+ const DEFAULTS = { exists: processExists, started_at: linuxStartedAt, now: () => Date.now() };
41
+ function ownerGone(owner, deps) {
42
+ const value = owner;
43
+ if (!value || !Number.isSafeInteger(value.pid) || typeof value.started_identity !== "string" || typeof value.closed !== "boolean")
44
+ return false;
45
+ if (value.closed)
46
+ return true;
47
+ const pid = value.pid;
48
+ if (!deps.exists(pid))
49
+ return true;
50
+ // 同じpidが別のprocessへ再利用されていないかを、開始時刻で見る。照合できない時は生きている扱い(本体が起きた時に照合する)。
51
+ const recorded = Date.parse(value.started_identity);
52
+ const actual = deps.started_at(pid);
53
+ return Number.isFinite(recorded) && actual !== null && Math.abs(actual - recorded) > 2_000;
54
+ }
55
+ /**
56
+ * 終了した持ち主の下に、引き取られていない配送の記録があれば、起こす役を引き受けてtrueを返す。
57
+ * 同じ種類の席は全部が同じ判定を回す。印を先に作れた1席だけが起こす。
58
+ */
59
+ export function shouldWakeForDeliveries(stateRoot, parent, dependencies = {}) {
60
+ const deps = { ...DEFAULTS, ...dependencies };
61
+ let wake = false;
62
+ for (const prefix of ["", "remote-"]) {
63
+ const root = path.join(stateRoot, prefix + DIRECTORY[parent]);
64
+ const active = path.join(root, "active");
65
+ const claims = path.join(root, "wake-claims");
66
+ let owners;
67
+ try {
68
+ owners = fs.readdirSync(active, { withFileTypes: true });
69
+ }
70
+ catch {
71
+ continue;
72
+ }
73
+ const orphaned = new Set();
74
+ for (const directory of owners) {
75
+ if (!directory.isDirectory())
76
+ continue;
77
+ const ownerDir = path.join(active, directory.name);
78
+ let owner;
79
+ let records;
80
+ try {
81
+ owner = JSON.parse(fs.readFileSync(path.join(ownerDir, "owner.json"), "utf8"));
82
+ records = fs.readdirSync(ownerDir).filter(name => name !== "owner.json" && name.endsWith(".json"));
83
+ }
84
+ catch {
85
+ continue;
86
+ }
87
+ if (records.length > 0 && ownerGone(owner, deps))
88
+ orphaned.add(directory.name);
89
+ }
90
+ // 引き取りが済んだ持ち主の印を片付ける。
91
+ try {
92
+ for (const name of fs.readdirSync(claims))
93
+ if (!orphaned.has(name))
94
+ fs.rmSync(path.join(claims, name), { recursive: true, force: true });
95
+ }
96
+ catch { /* 印の置き場がまだ無い */ }
97
+ for (const name of orphaned) {
98
+ const claim = path.join(claims, name);
99
+ try {
100
+ fs.mkdirSync(claims, { recursive: true, mode: 0o700 });
101
+ fs.mkdirSync(claim, { mode: 0o700 });
102
+ wake = true;
103
+ }
104
+ catch (error) {
105
+ if (error.code !== "EEXIST")
106
+ throw error;
107
+ const now = deps.now();
108
+ if (now - fs.statSync(claim).mtimeMs < CLAIM_TTL_MS)
109
+ continue;
110
+ fs.utimesSync(claim, now / 1000, now / 1000);
111
+ wake = true;
112
+ }
113
+ }
114
+ }
115
+ return wake;
116
+ }
117
+ function main(argv) {
118
+ if (argv.length !== 2 || argv[0] !== "--parent" || !(argv[1] in DIRECTORY)) {
119
+ process.stderr.write(`${USAGE}\n`);
120
+ return 2;
121
+ }
122
+ return shouldWakeForDeliveries(path.join(runtimeStateBase(), `aiterm-mcp-${currentUid()}`), argv[1]) ? 0 : 1;
123
+ }
124
+ function invokedDirectly() {
125
+ if (!process.argv[1])
126
+ return false;
127
+ try {
128
+ return fs.realpathSync(process.argv[1]) === fs.realpathSync(fileURLToPath(import.meta.url));
129
+ }
130
+ catch {
131
+ return false;
132
+ }
133
+ }
134
+ if (invokedDirectly()) {
135
+ try {
136
+ process.exitCode = main(process.argv.slice(2));
137
+ }
138
+ catch (error) {
139
+ process.stderr.write(`aiterm-delivery-wake: ${error instanceof Error ? error.message : String(error)}\n`);
140
+ process.exitCode = 3;
141
+ }
142
+ }
@@ -1,6 +1,7 @@
1
1
  // OSのprocess表とnative PIDの所有者。argvは相関にだけ使い、公開identityにはdigestだけを載せる。
2
2
  import { spawnSync } from "node:child_process";
3
3
  import { createHash } from "node:crypto";
4
+ import path from "node:path";
4
5
  import { AitermError } from "./errors.js";
5
6
  import { isWin } from "./tmux-runtime.js";
6
7
  import { resolveWindowsPowerShell7 } from "./windows-powershell.js";
@@ -153,6 +154,12 @@ export function processSubtree(rows, rootPid) {
153
154
  }
154
155
  return rows.filter(row => selected.has(row.pid));
155
156
  }
157
+ // 中継(mcp-lazy)は、登録されたMCPの本体と先行起動の判定を、自分の直接の子として起こす。
158
+ // 見分けるのは起動した実行ファイルの名前(argvの先頭のbasenameが`mcp-lazy`で始まる)。取り決めはmcp-lazyのREADMEにある。
159
+ export function lazyRelayProcess(command) {
160
+ const first = /^(?:"([^"]+)"|(\S+))/.exec(command);
161
+ return path.posix.basename((first?.[1] ?? first?.[2] ?? "").replace(/\\/g, "/")).toLowerCase().startsWith("mcp-lazy");
162
+ }
156
163
  export function backgroundProcesses(rows, root) {
157
164
  // 起動時の足場processを除く既存契約。pane開始から60秒以上後に生成された子孫だけを集計する。
158
165
  const rootStart = Date.parse(root.started_identity);
package/docs/DESIGN.md CHANGED
@@ -54,6 +54,8 @@ PIDは開始識別子・argv digestと組にし、paneとharnessを同一視し
54
54
  POSIXの停止状態はOSのprocess表から取得し、SIGSTOP中は残画面より優先して`blocked/harness_stopped`を返す。
55
55
  sessionを閉じると失うものは数で返す(ADR 0080)。`activity.post_startup_process_count`は起動完了の時点に居なかったprocessの数、
56
56
  `pending_child_deliveries`はそのsessionが親として待つ未配送の数。数えられない時はnullで、0と区別する。
57
+ harnessと中継が自分のために立てるprocessは数えない(Codexの`codex-code-mode-host`、中継`mcp-lazy`の直接の子。ADR 0082)。その下は数える。
58
+ 中継の後ろで眠っている本体を配送の引き取りのために起こすかは、`aiterm-delivery-wake`が返す(ADR 0082)。
57
59
  Claude Codeの入力待ちは、起動時の見出しが取得範囲から流れ出た後も、入力欄の形(`❯`行の上下の罫線)で読む。
58
60
  画面本文とargv本文は返さず、活動cursorには画面digestとprocess別CPUだけを持たせる。
59
61
  初回とpane再作成後の差分はnull。区間中にprocessが消えた時は観測できたCPU増分だけを返し、
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aiterm-mcp",
3
- "version": "0.50.0",
3
+ "version": "0.51.0",
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": [
@@ -43,6 +43,7 @@
43
43
  "aiterm-setup": "dist/setup-cli.js",
44
44
  "aiterm-runtime-errors": "dist/runtime-errors-cli.js",
45
45
  "aiterm-wait": "dist/aiterm-wait-cli.js",
46
+ "aiterm-delivery-wake": "dist/delivery-wake-cli.js",
46
47
  "aiterm-update": "dist/update-cli.js"
47
48
  },
48
49
  "files": [
@@ -70,7 +71,7 @@
70
71
  },
71
72
  "dependencies": {
72
73
  "@modelcontextprotocol/sdk": "^1.29.0",
73
- "aiterm-steer-delivery": "^0.1.10",
74
+ "aiterm-steer-delivery": "^0.1.12",
74
75
  "ws": "8.21.3",
75
76
  "zod": "^4.4.3"
76
77
  },