aiterm-mcp 0.28.1 → 0.28.3

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
@@ -18,7 +18,7 @@
18
18
 
19
19
  > **あなたの AI に、ほかの AI を操らせる。** `agent_launch`の1回の呼び出しで、実行基盤harnessとmodelを別々に選び、永続sessionを受け取る。CursorでGPT/Claude/Grokを選んでも、session・hook・transcriptはCursorが所有する。
20
20
  >
21
- > **これは何か:** AI が握る 1 本の永続 MCP 端末——その中に他のコーディングエージェントも起動できる。`ssh`・`docker exec`・REPL・別エージェントの TUI は、すべてその 1 本の端末の中へ「送るだけのテキスト」として入れ子になる。仕組みはあえて素朴——MCP クライアントが相手エージェントの端末を 1 ターンずつ操作するだけ。隠れたプロトコルも・aiterm独自の共有メモリ層も・自律的な交渉も無い。起動したagentは、直接CLIと同じproject/vendorの通常memory・設定を読む。
21
+ > **これは何か:** AI が握る 1 本の永続 MCP 端末——その中に他のコーディングエージェントも起動できる。`ssh`・`docker exec`・REPL・別エージェントの TUI は、すべてその 1 本の端末の中へ「送るだけのテキスト」として入れ子になる。仕組みはあえて素朴——MCP クライアントが相手エージェントの端末を 1 ターンずつ操作するだけ。隠れたプロトコルも・aiterm独自の共有メモリ層も・自律的な交渉も無い。起動したagentは、直接CLIと同じproject/harnessの通常memory・設定を読む。
22
22
  >
23
23
  > **人が tmux に張り付く必要はない。** aiterm は MCP 越しにプログラムから駆動されるので、「AI が別のエージェントを起動して操作する」のに端末の前に誰も座らなくていい——オーケストレーションのループ・CI ステップ・cron から動かせる。
24
24
  >
@@ -118,17 +118,17 @@ serverがMCP processより先に起動していても、古いserver環境に席
118
118
  常駐するmodel/effort footerと入力欄でCodexを識別する。idle sessionをそのまま変更でき、
119
119
  caller側の画面再描画、再試行、agent再起動は不要。
120
120
 
121
- **v0.24.0では起動中agentの設定変更を追加。** `agent_configure`はvendor標準操作を使って、
122
- 起動中のCodex/Claudeのmodelとreasoning effortを変更する。PTY、vendor session、会話contextは維持する。
121
+ **v0.24.0では起動中agentの設定変更を追加。** `agent_configure`はharness標準操作を使って、
122
+ 起動中のCodex/Claudeのmodelとreasoning effortを変更する。PTY、harness session、会話contextは維持する。
123
123
 
124
- **v0.23.0では、ローカル完結の別vendor向けportable forkを追加した。** どのlauncherでも
124
+ **v0.23.0では、ローカル完結の別harness向けportable forkを追加した。** どのlauncherでも
125
125
  `throughline_source_session`と新しいミッションを`prompt`へ渡すと、PTY作成前にローカルの
126
126
  Throughlineから対象sessionの読み取り専用handoff contextを取得する。返された記憶はそのまま
127
127
  ミッションの前へ置かれ、元sessionのDB所属は移動もcopyもされない。Throughlineが無い、または
128
128
  結果が不正/空ならclean launchへfallbackせず明示失敗する。引数を省略した通常起動は従来どおり。
129
129
 
130
130
  **v0.22.0では4 launcherを完全なプロジェクト共同作業員へ戻した。** 直接CLIを起動した時と同じ
131
- `HOME`、作業tree、vendor home、project/user/local設定、MCP、plugin、skill、permission、trust、memory、
131
+ `HOME`、作業tree、harness home、project/user/local設定、MCP、plugin、skill、permission、trust、memory、
132
132
  session historyをそのまま使う。aitermがlaunchごとに分離するのは完了相関stateだけ。子には
133
133
  `role=subagent`、親session、delegation depth、lineage、`delegation_allowed=true`を注入する。
134
134
  孫以降への再委譲は禁止せず、孫はdepth 2と伸びたlineageを受け取る。既存receiptの
@@ -488,7 +488,7 @@ self-hostedのmacOS native・Linux native・Windows native・WSL2で同じ`npm t
488
488
  OS別の縮小suiteで代用しません。tag起点のnpm公開は4環境greenとtagged commitの`origin/main`
489
489
  祖先確認を通過した後だけ実行します。
490
490
 
491
- 共通進行は`src/core.ts`、harness固有は`src/vendors/`、OS差は`src/tmux-runtime.ts`/`src/agent-resolver.ts`、reducerは`src/rtk.ts`、公開面は`src/index.ts`が所有する。設計の出発点と reducer の移植元(pytest reducer は本家 rtk 0.42.0 と一致するよう移植・ただし上記の `FAILED` 行の差異は意図的・回帰テストで固定)は `prototype/python/` を参照。
491
+ 共通進行は`src/core.ts`、harness固有は`src/harnesses/`、OS差は`src/tmux-runtime.ts`/`src/agent-resolver.ts`、reducerは`src/rtk.ts`、公開面は`src/index.ts`が所有する。設計の出発点と reducer の移植元(pytest reducer は本家 rtk 0.42.0 と一致するよう移植・ただし上記の `FAILED` 行の差異は意図的・回帰テストで固定)は `prototype/python/` を参照。
492
492
 
493
493
  ## 試す
494
494
 
package/README.md CHANGED
@@ -18,7 +18,7 @@
18
18
 
19
19
  > **Let your AI orchestrate other AIs.** One `agent_launch` call selects the execution harness separately from its model and hands you a persistent session to drive. Cursor can run GPT, Claude, or Grok while Cursor still owns the session, hooks, and transcript.
20
20
  >
21
- > **What it is:** one persistent MCP terminal your AI drives — and can launch other coding agents into. `ssh`, `docker exec`, a REPL, or another agent's TUI all nest inside that one terminal as just text you send in. The mechanism is deliberately plain — your MCP client drives the other agent's terminal turn by turn: no hidden protocol, no separate aiterm-owned shared-memory layer, no autonomous negotiation. Launched agents still read the normal project and vendor memory/configuration that a direct CLI launch would use.
21
+ > **What it is:** one persistent MCP terminal your AI drives — and can launch other coding agents into. `ssh`, `docker exec`, a REPL, or another agent's TUI all nest inside that one terminal as just text you send in. The mechanism is deliberately plain — your MCP client drives the other agent's terminal turn by turn: no hidden protocol, no separate aiterm-owned shared-memory layer, no autonomous negotiation. Launched agents still read the normal project and harness memory/configuration that a direct CLI launch would use.
22
22
  >
23
23
  > **No human at a tmux required.** aiterm is driven programmatically over MCP, so an AI can launch and drive another agent with no one sitting in the terminal — from an orchestration loop, a CI step, or a cron job.
24
24
  >
@@ -96,7 +96,7 @@ toolchain behind kitepon.dev's products.
96
96
 
97
97
  Fifteen tools: six **PTY tools** — `pty_open` / `pty_send` / `pty_read` / `pty_key` / `pty_close` / `pty_list` — to open, drive, and read one persistent terminal; one canonical **agent launcher**, `agent_launch`, which selects `claude-code`, `codex-cli`, `grok-cli`, or `cursor-cli` as the execution harness; four deprecated launcher aliases kept for migration; `agent_configure`; `claude_turn`; `claude_approval`; and `diagnostics`. The backend is **tmux**, so sessions survive even if the MCP server or the AI client restarts.
98
98
 
99
- **v0.28.0 separates the execution harness from the model.** The harness owns the agent loop, authentication, hooks, session, and transcript; `model` is what that harness runs. Cursor Agent CLI can therefore select GPT, Claude, or Grok without changing the completion contract from Cursor hooks to another vendor's. Grok Composer is a Grok CLI model preset, not another harness: use `harness: "grok-cli", model: "grok-composer-2.5-fast"`. The old four launcher tools are thin compatibility aliases over the same implementation.
99
+ **v0.28.0 separates the execution harness from the model.** The harness owns the agent loop, authentication, hooks, session, and transcript; `model` is what that harness runs. Cursor Agent CLI can therefore select GPT, Claude, or Grok without changing the completion contract from Cursor hooks to another harness's. Grok Composer is a Grok CLI model preset, not another harness: use `harness: "grok-cli", model: "grok-composer-2.5-fast"`. The old four launcher tools are thin compatibility aliases over the same implementation.
100
100
 
101
101
  **v0.25.2 stabilizes repeated in-place configuration changes, including Grok 4.6.** If Grok Build
102
102
  1.0.3 redraws before its `/model` success notice can be observed, aiterm confirms the requested model/effort
@@ -107,7 +107,7 @@ round a failure into success; explicit `grok-4.6` launch and configuration still
107
107
  `reasoning_effort`, enforce `write_scope: "read-only"` with `--sandbox read-only`, and support
108
108
  in-place model/effort changes through `agent_configure`. Before creating a PTY, aiterm checks an
109
109
  explicit Grok/Composer model—and Composer's default model—against the live `grok models` catalog.
110
- An unavailable model fails visibly instead of letting the vendor CLI fall back to another model.
110
+ An unavailable model fails visibly instead of letting the harness CLI fall back to another model.
111
111
 
112
112
  **v0.24.3 forwards explicitly selected launcher environment variables from the current MCP process.**
113
113
  Pass variable names in `env_vars`; aiterm reads their current values at launch and injects only the
@@ -121,11 +121,11 @@ startup header has scrolled out of the captured pane, aiterm recognizes Codex by
121
121
  model/effort footer together with the input prompt. An idle session is therefore configured
122
122
  directly; callers do not need to redraw the TUI, retry, or restart the agent.
123
123
 
124
- **v0.24.0 adds in-place agent configuration.** `agent_configure` uses each vendor's
124
+ **v0.24.0 adds in-place agent configuration.** `agent_configure` uses each harness's
125
125
  native controls to change the model and/or reasoning effort of a running Codex or Claude
126
- session while preserving its PTY, vendor session, and conversation context.
126
+ session while preserving its PTY, harness session, and conversation context.
127
127
 
128
- **v0.23.0 adds a local, cross-vendor portable fork.** Pass `throughline_source_session`
128
+ **v0.23.0 adds a local, cross-harness portable fork.** Pass `throughline_source_session`
129
129
  with a mission in `prompt` to any launcher, and aiterm asks the locally installed Throughline
130
130
  for that session's read-only handoff context before creating the PTY. The exact returned memory
131
131
  is prepended to the mission without moving or copying the source session's database ownership.
@@ -133,7 +133,7 @@ If Throughline is missing or returns an invalid/empty result, launch fails visib
133
133
  fallback. Omitting the field preserves the ordinary clean launch.
134
134
 
135
135
  **v0.22.0 makes launched agents full project collaborators.** All four launchers now use the
136
- same normal `HOME`, working tree, vendor home, project/user/local configuration, MCP servers,
136
+ same normal `HOME`, working tree, harness home, project/user/local configuration, MCP servers,
137
137
  plugins, skills, permissions, trust, memory, and session history as a direct CLI launch. Aiterm
138
138
  isolates only its own per-launch completion correlation state. Every child is told that it is a
139
139
  sub-agent and receives its parent session, delegation depth, lineage, and
@@ -150,7 +150,7 @@ structured launch receipts so a supplied scope and its enforcement status are re
150
150
  v0.20.3 prevents concurrent
151
151
  correlated Claude/Fable sessions from turning one broken login into many competing login
152
152
  flows. Every new Claude launch verifies the
153
- vendor-owned shared credential store before creating a PTY, while healthy credentials
153
+ harness-owned shared credential store before creating a PTY, while healthy credentials
154
154
  remain reusable across concurrent and repeated sessions. The v0.20 line also distinguishes
155
155
  a non-blocking `aiterm-wait --timeout 0` observation (`running`, exit 5) from a real timed-out
156
156
  wait. The v0.19 line added the correlated Claude approval relay,
@@ -228,14 +228,14 @@ The canonical harness choices are:
228
228
 
229
229
  `env_vars` is an allowlist of environment-variable **names**, not a name/value map. At launch,
230
230
  aiterm reads each valid name from its current MCP process, shell-quotes present values, and places
231
- them on that one vendor launch command. Missing names are omitted; invalid shell variable names
231
+ them on that one harness launch command. Missing names are omitted; invalid shell variable names
232
232
  fail before session creation. There is no implicit whole-environment copy, tmux-server restart,
233
233
  retry, or fallback. Values do not enter the MCP tool arguments, but they are delivered through the
234
- PTY launch command and retained in aiterm's per-session `.lastcmd`; the launched vendor and other
234
+ PTY launch command and retained in aiterm's per-session `.lastcmd`; the launched harness and other
235
235
  processes with access to the same OS user may read them. Use this for non-secret seat identity and
236
236
  workflow variables, not as a secret transport.
237
237
 
238
- The selected harness CLI must be installed and authenticated. Aiterm resolves `CLAUDE_BIN` / `CODEX_BIN` / `GROK_BIN` / `CURSOR_AGENT_BIN`, then the documented default binary, then `PATH`. Cursor resolution deliberately uses `cursor-agent`, never the ambiguous `agent` name. Claude and Cursor authentication are checked before a PTY exists, so a failed preflight leaves no session. All harnesses use their normal vendor-owned credential and configuration stores in place.
238
+ The selected harness CLI must be installed and authenticated. Aiterm resolves `CLAUDE_BIN` / `CODEX_BIN` / `GROK_BIN` / `CURSOR_AGENT_BIN`, then the documented default binary, then `PATH`. Cursor resolution deliberately uses `cursor-agent`, never the ambiguous `agent` name. Claude and Cursor authentication are checked before a PTY exists, so a failed preflight leaves no session. All harnesses use their normal harness-owned credential and configuration stores in place.
239
239
 
240
240
  Portable fork is optional. When `throughline_source_session` is present, `prompt` is the required
241
241
  new mission and `launch_operation_id` cannot be combined with it. aiterm resolves Throughline via
@@ -531,7 +531,7 @@ runners; it does not replace any OS with a reduced suite. Tag-triggered npm publ
531
531
  after all four environments pass and the tagged commit is confirmed on `origin/main`. The native
532
532
  Windows runner needs psmux ≥ 3.3.8 and Git for Windows on its PATH, and must run as an
533
533
  interactive Windows user; `NETWORK SERVICE` lacks the per-user environment the pane shell and
534
- vendor CLIs rely on and is not a valid runner identity (the separate WSL2 runner still owns the
534
+ harness CLIs rely on and is not a valid runner identity (the separate WSL2 runner still owns the
535
535
  initialized WSL distro).
536
536
 
537
537
  Logic lives in `src/core.ts` (tmux control, reduction, completion detection, safety, agent launch) and `src/rtk.ts` (per-command reducers); `src/index.ts` is the MCP surface. The design origin and the reducer's porting source (the pytest reducer is ported to match upstream rtk 0.42.0, except the deliberate `FAILED`-line difference noted above, and is locked by regression tests) are in `prototype/python/`.
@@ -552,7 +552,7 @@ If aiterm let your AI hand a task to another agent — or saved you a round-trip
552
552
  ## Shared agent environment
553
553
 
554
554
  All harnesses use the caller's normal project and user environment. Aiterm does not copy,
555
- symlink, filter, or replace vendor configuration, authentication, MCP, plugin, skill, permission,
555
+ symlink, filter, or replace harness configuration, authentication, MCP, plugin, skill, permission,
556
556
  trust, memory, or history stores. Cleanup removes only aiterm-owned launch metadata and completion
557
557
  correlation files.
558
558
 
@@ -28,7 +28,7 @@ export function isWindowsNativeExecutable(candidate) {
28
28
  // Windows の bin 受入: native 実行ファイル(.exe/.cmd/.bat)に加え、pane shell
29
29
  // (Git Bash)が shebang で実行できる script も実在すれば受け入れる。旧 WSL 側
30
30
  // バイナリ検査への黙ったフォールバックは廃止(別 HOME・別 auth の subagent を
31
- // 作るため)。native 実行ファイルの強制が要る vendor(grok/composer の実効
31
+ // 作るため)。native 実行ファイルの強制が要る harness(grok/composer の実効
32
32
  // sandbox 等)は openAgent 側の専用ゲートが明示エラーで担う。
33
33
  export function isUsableAgentExecutableFile(candidate) {
34
34
  if (!isWin)
@@ -1,35 +1,12 @@
1
- // vendor中立の共有プリミティブ。core と vendors/ の両方が依存する最下層で、
2
- // tmux-runtime / errors 以外の内部moduleへ依存しない(依存方向: core → vendors → agent-shared)。
1
+ // harness中立の共有プリミティブ。core と harnesses/ の両方が依存する最下層で、
2
+ // tmux-runtime / errors 以外の内部moduleへ依存しない(依存方向: core → harnesses → agent-shared)。
3
3
  import * as fs from "node:fs";
4
- import * as os from "node:os";
5
4
  import * as path from "node:path";
6
5
  import { randomBytes } from "node:crypto";
7
6
  import { AitermError } from "./errors.js";
8
7
  export const sleep = (ms) => new Promise((res) => setTimeout(res, ms));
9
- export function currentUid() {
10
- // Windows(native)は process.getuid を持たない。以前はここで throw していたが、
11
- // agent metadata の存在確認経由で素の pty_send まで巻き込んで全 send を殺していた。
12
- // Windows の fs.Stats.uid は常に 0 なので、ここも 0 を返せば owner 比較
13
- // (st.uid !== currentUid()) は自然に通過する。Windows は POSIX owner 検証を
14
- // 持たない(NTFS ACL は別体系)という既知の制約の明示的受容であり、POSIX 側は
15
- // getuid をそのまま返すため挙動不変。
16
- if (typeof process.getuid !== "function")
17
- return 0;
18
- return process.getuid();
19
- }
20
- export function runtimeStateBase() {
21
- const xdg = process.env.XDG_RUNTIME_DIR;
22
- if (xdg) {
23
- try {
24
- if (fs.statSync(xdg).isDirectory())
25
- return xdg;
26
- }
27
- catch {
28
- /* XDG_RUNTIME_DIR が壊れている CI/非 login 環境では os.tmpdir() に戻す */
29
- }
30
- }
31
- return os.tmpdir();
32
- }
8
+ import { currentUid, runtimeStateBase } from "./state-root.js";
9
+ export { currentUid, runtimeStateBase };
33
10
  export function safeStatSize(p) {
34
11
  try {
35
12
  return fs.statSync(p).size;
@@ -200,7 +177,7 @@ export function subagentInstruction(meta) {
200
177
  ].join("\n");
201
178
  }
202
179
  // launch noteのwrite_scope説明。文言はkindに依存する分岐まで含めて単一実装で持つ
203
- // (vendor別noteへ複製すると文言が発散する)。
180
+ // (harness別noteへ複製すると文言が発散する)。
204
181
  export function writeScopeLaunchNote(kind, writeScope) {
205
182
  return writeScope === undefined
206
183
  ? ""
@@ -2,7 +2,7 @@
2
2
  // aiterm-wait — agent turn 完了eventの純リーダー観測CLI。
3
3
  // 完了/timeout/close を1行のJSON receiptで返してexitする。lock・PTY・dispatch状態には一切触れない。
4
4
  // 親AIホストのバックグラウンドタスクとして起動し、exitを「観測終了の通知」として使う。
5
- // exit≠完了: exit code は outcome を映す(0=done / 5=running=未完了 / 3=timeout=未完了 / 4=closed / 6=rate_limited=vendor利用上限 / 1=エラー)。
5
+ // exit≠完了: exit code は outcome を映す(0=done / 5=running=未完了 / 3=timeout=未完了 / 4=closed / 6=rate_limited=harness利用上限 / 1=エラー)。
6
6
  // --timeout 0 は待たずに一度だけ観測する照会で、未完了は running(timeout と混同させない)。
7
7
  // receipt の outcome が正で、done 以外は未完了。timeout の既定は core の DEFAULT_AGENT_DONE_TIMEOUT(600秒)。
8
8
  import { fileURLToPath } from "node:url";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import * as fs from "node:fs";
3
- import * as os from "node:os";
4
3
  import * as path from "node:path";
4
+ import { currentUid as uid, runtimeStateBase } from "./state-root.js";
5
5
  import { createHash, randomBytes } from "node:crypto";
6
6
  const LAUNCH_ID_RE = /^[0-9a-f]{32}$/;
7
7
  const SESSION_RE = /^[A-Za-z0-9_-]{1,64}$/;
@@ -21,26 +21,6 @@ function hasAitermEnv() {
21
21
  process.env.AITERM_AGENT_SESSION_ID ||
22
22
  process.env.AITERM_AGENT_LAUNCH_ID);
23
23
  }
24
- function uid() {
25
- // state root のパス構成要素 `aiterm-mcp-<uid>` にだけ使う。Windows(native) は
26
- // process.getuid を持たないため core の currentUid() と同じ受容で 0 を返す。
27
- if (typeof process.getuid !== "function")
28
- return 0;
29
- return process.getuid();
30
- }
31
- function runtimeStateBase() {
32
- const xdg = process.env.XDG_RUNTIME_DIR;
33
- if (xdg) {
34
- try {
35
- if (fs.statSync(xdg).isDirectory())
36
- return xdg;
37
- }
38
- catch {
39
- /* 壊れたXDG_RUNTIME_DIRはTMPDIRへ戻す */
40
- }
41
- }
42
- return os.tmpdir();
43
- }
44
24
  function agentsDir() {
45
25
  // state root は OS が与える per-user runtime dir(XDG_RUNTIME_DIR / os.tmpdir())の下にある。
46
26
  // 以前はここで symlink・owner・mode を検査していたが、共有 /tmp に敵対的な同居主体がいる
package/dist/core.js CHANGED
@@ -15,10 +15,10 @@ import * as rtk from "./rtk.js";
15
15
  import { AitermError, telemetryOwnedFailure, ownTelemetryFailure } from "./errors.js";
16
16
  import { isWin, SOCKDIR, tmuxCommand, 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, grokInitializationComplete, observeGrokDone, buildGrokAgentCmd, grokLaunchNote, grokEnvTokens, grokTuiReady, GROK_COMPOSER_MARKER_RE, grokFooterHasConfiguration, grokTranscriptText, createGrokAgentMetadata, } from "./vendors/grok.js";
19
- import { bindCodexTranscriptSession, latestCodexCompletion, observeCodexDone, buildCodexAgentCmd, codexLaunchNote, codexTuiReady, CODEX_COMPOSER_MARKER_RE, codexModelChoice, codexEffortChoice, codexMoreReasoningChoice, codexTranscriptText, createCodexAgentMetadata, } from "./vendors/codex.js";
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 "./vendors/claude.js";
21
- import { bindCursorTranscriptSession, cursorTurnBoundary, latestCursorCompletion, observeCursorDone, cursorTranscriptText, assertCursorAuthenticationReady, assertCursorModelAvailable, buildCursorAgentCmd, createCursorAgentMetadata, cursorLaunchNote, cursorEffortNavigation, cursorTuiReady, CURSOR_COMPOSER_MARKER_RE, validateCursorModelEffort, } from "./vendors/cursor.js";
18
+ import { GROK_MODEL_DEFAULTS, realGrokHome, resolveAndValidateGrokAuth, assertGrokModelAvailable, grokEventsTranscript, latestGrokCompletion, grokInitializationComplete, observeGrokDone, buildGrokAgentCmd, grokLaunchNote, grokEnvTokens, grokTuiReady, GROK_COMPOSER_MARKER_RE, grokFooterHasConfiguration, grokTranscriptText, createGrokAgentMetadata, } from "./harnesses/grok.js";
19
+ import { bindCodexTranscriptSession, latestCodexCompletion, observeCodexDone, buildCodexAgentCmd, codexLaunchNote, codexTuiReady, CODEX_COMPOSER_MARKER_RE, codexModelChoice, codexEffortChoice, codexMoreReasoningChoice, codexTranscriptText, createCodexAgentMetadata, } from "./harnesses/codex.js";
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
+ import { bindCursorTranscriptSession, cursorTurnBoundary, latestCursorCompletion, observeCursorDone, cursorTranscriptText, assertCursorAuthenticationReady, assertCursorModelAvailable, buildCursorAgentCmd, createCursorAgentMetadata, cursorLaunchNote, cursorEffortNavigation, cursorTuiReady, CURSOR_COMPOSER_MARKER_RE, validateCursorModelEffort, } from "./harnesses/cursor.js";
22
22
  import { resolveAgentBin, resolveThroughlineBin, runThroughlineHandoffContext, isWindowsNativeExecutable, agentBinForPaneShell, resolveWinPaneShell } from "./agent-resolver.js";
23
23
  export { AitermError } from "./errors.js";
24
24
  export { tmuxSpawnEnv } from "./tmux-runtime.js";
@@ -1789,18 +1789,18 @@ function scanAgentDoneLines(lines, meta, expectedOperationId = null) {
1789
1789
  if (expectedOperationId && ev.operation_id !== expectedOperationId)
1790
1790
  continue;
1791
1791
  if (meta.vendor_session_id)
1792
- return { event: ev, malformedEvents, ambiguousVendorSession: false };
1792
+ return { event: ev, malformedEvents, ambiguousHarnessSession: false };
1793
1793
  if (candidate?.vendor_session_id &&
1794
1794
  ev.vendor_session_id &&
1795
1795
  candidate.vendor_session_id !== ev.vendor_session_id) {
1796
- return { event: null, malformedEvents, ambiguousVendorSession: true };
1796
+ return { event: null, malformedEvents, ambiguousHarnessSession: true };
1797
1797
  }
1798
1798
  if (!candidate)
1799
1799
  candidate = ev;
1800
1800
  }
1801
- return { event: candidate, malformedEvents, ambiguousVendorSession: false };
1801
+ return { event: candidate, malformedEvents, ambiguousHarnessSession: false };
1802
1802
  }
1803
- function bindAgentVendorSession(meta, ev) {
1803
+ function bindAgentHarnessSession(meta, ev) {
1804
1804
  if (!meta.vendor_session_id && ev.vendor_session_id) {
1805
1805
  meta.vendor_session_id = ev.vendor_session_id;
1806
1806
  }
@@ -1816,7 +1816,7 @@ function bindCompletedInitialPrompt(meta) {
1816
1816
  if (!done) {
1817
1817
  throw new AitermError(`agent session '${meta.aiterm_session}' は起動時 prompt の完了待ちです。${agentWaitGuide(meta.aiterm_session)}`, 2);
1818
1818
  }
1819
- bindAgentVendorSession(meta, done);
1819
+ bindAgentHarnessSession(meta, done);
1820
1820
  setInitialPromptState(meta, "done");
1821
1821
  return;
1822
1822
  }
@@ -1825,7 +1825,7 @@ function bindCompletedInitialPrompt(meta) {
1825
1825
  if (!done) {
1826
1826
  throw new AitermError(`agent session '${meta.aiterm_session}' は起動時 prompt の完了待ちです。${agentWaitGuide(meta.aiterm_session)}`, 2);
1827
1827
  }
1828
- bindAgentVendorSession(meta, done);
1828
+ bindAgentHarnessSession(meta, done);
1829
1829
  setInitialPromptState(meta, "done");
1830
1830
  return;
1831
1831
  }
@@ -1844,7 +1844,7 @@ function bindCompletedInitialPrompt(meta) {
1844
1844
  const lines = text.split("\n");
1845
1845
  const tail = lines.pop() ?? "";
1846
1846
  const scanned = scanAgentDoneLines(lines, meta);
1847
- if (scanned.ambiguousVendorSession) {
1847
+ if (scanned.ambiguousHarnessSession) {
1848
1848
  throw new AitermError("agent event file に複数の vendor_session_id が混在しています。該当セッションを閉じて起動し直してください。", 2);
1849
1849
  }
1850
1850
  if (!scanned.event) {
@@ -1852,7 +1852,7 @@ function bindCompletedInitialPrompt(meta) {
1852
1852
  const partial = tail.trim() ? " partial_event=true" : "";
1853
1853
  throw new AitermError(`agent session '${meta.aiterm_session}' は起動時 prompt の完了 event をまだ確認できません。${agentWaitGuide(meta.aiterm_session)}${malformed}${partial}`, 2);
1854
1854
  }
1855
- bindAgentVendorSession(meta, scanned.event);
1855
+ bindAgentHarnessSession(meta, scanned.event);
1856
1856
  setInitialPromptState(meta, "done");
1857
1857
  }
1858
1858
  function tryLoadAgentMetadata(name) {
@@ -1923,7 +1923,7 @@ function completedClaudeOperationEvent(meta, operationId) {
1923
1923
  }
1924
1924
  return match;
1925
1925
  }
1926
- function recoverAgentVendorSession(meta) {
1926
+ function recoverAgentHarnessSession(meta) {
1927
1927
  if (meta.vendor_session_id)
1928
1928
  return;
1929
1929
  if (meta.kind === "codex") {
@@ -1952,12 +1952,12 @@ function recoverAgentVendorSession(meta) {
1952
1952
  const lines = text.split("\n");
1953
1953
  lines.pop(); // hook は newline 完結eventだけを確定済みとして扱う。
1954
1954
  const scanned = scanAgentDoneLines(lines, meta);
1955
- if (scanned.ambiguousVendorSession) {
1955
+ if (scanned.ambiguousHarnessSession) {
1956
1956
  throw new AitermError("agent event file に複数の vendor_session_id が混在しています。該当セッションを閉じて起動し直してください。", 2);
1957
1957
  }
1958
1958
  if (!scanned.event?.vendor_session_id)
1959
1959
  return;
1960
- bindAgentVendorSession(meta, scanned.event);
1960
+ bindAgentHarnessSession(meta, scanned.event);
1961
1961
  writeAgentMetadata(meta);
1962
1962
  }
1963
1963
  function agentCompletionCursor(meta) {
@@ -2021,7 +2021,7 @@ async function settlePublishedClaudeCompletionMarker(meta, marker) {
2021
2021
  }
2022
2022
  return active;
2023
2023
  }
2024
- /** agent vendor の構造化 transcript から直近完了ターンの最終回答を読む。 */
2024
+ /** agent harness の構造化 transcript から直近完了ターンの最終回答を読む。 */
2025
2025
  export async function readAgentTranscript(name, o = {}) {
2026
2026
  const meta = loadAgentMetadata(name);
2027
2027
  const operationId = o.operation_id == null ? null : validateOperationId(o.operation_id);
@@ -2038,8 +2038,8 @@ export async function readAgentTranscript(name, o = {}) {
2038
2038
  }
2039
2039
  }
2040
2040
  // wait timeout は「失敗」ではなく状態不明。後着した同一launchの完了eventから
2041
- // vendor session をbindし、promptを再送せず結果だけ回収できるようにする。
2042
- recoverAgentVendorSession(meta);
2041
+ // harness session をbindし、promptを再送せず結果だけ回収できるようにする。
2042
+ recoverAgentHarnessSession(meta);
2043
2043
  if (!meta.vendor_session_id) {
2044
2044
  throw new AitermError(`agent session '${name}' はまだターンが完了していません。agent_done 完了後に再取得してください。${agentWaitGuide(name)}`, 2);
2045
2045
  }
@@ -2160,7 +2160,7 @@ export function agentWaitGuide(session) {
2160
2160
  const cmd = `aiterm-wait --session ${session ?? "<session_id>"} --cursor 0`;
2161
2161
  return `完了通知は ${agentWaitLaunchForm(cmd)} で受ける(親はここで待たない・polling 不要)。receipt の outcome=done を確認してから再取得する。`;
2162
2162
  }
2163
- // vendor 別の利用上限バナー。検知は「報告」専用で、完了判定や自動復旧には使わない。
2163
+ // harness 別の利用上限バナー。検知は「報告」専用で、完了判定や自動復旧には使わない。
2164
2164
  // 出典(2026-08-22): grok は live 実バナーで検証、codex/claude はインストール済み実バイナリの
2165
2165
  // 埋込文字列から抽出(codex: "You've hit your usage limit for" / claude: "Usage limit reached ·
2166
2166
  // continuing automatically when it resets"。Claude Code はリセット時に自動継続する設計なので、
@@ -2172,7 +2172,7 @@ const AGENT_RATE_LIMIT_PATTERNS = {
2172
2172
  claude: [/Usage limit reached/i],
2173
2173
  };
2174
2174
  const AGENT_RATE_LIMIT_SCAN_BYTES = 16 * 1024;
2175
- // pane log の末尾から上限バナーを探す。読めない・無い・対象 vendor でないは全て null(誤検知より取りこぼし側へ倒す)。
2175
+ // pane log の末尾から上限バナーを探す。読めない・無い・対象 harness でないは全て null(誤検知より取りこぼし側へ倒す)。
2176
2176
  export function detectAgentRateLimit(kind, aitermSession) {
2177
2177
  const patterns = AGENT_RATE_LIMIT_PATTERNS[kind];
2178
2178
  if (!patterns)
@@ -2264,7 +2264,7 @@ export async function observeAgentDone(name, o = {}) {
2264
2264
  carry = parts.pop() ?? "";
2265
2265
  const scanned = scanAgentDoneLines(parts, meta, operationId);
2266
2266
  malformedEvents += scanned.malformedEvents;
2267
- if (scanned.ambiguousVendorSession) {
2267
+ if (scanned.ambiguousHarnessSession) {
2268
2268
  throw new AitermError("agent event file に複数の vendor_session_id が混在しています。該当セッションを閉じて起動し直してください。", 2);
2269
2269
  }
2270
2270
  if (scanned.event)
@@ -2578,7 +2578,7 @@ async function waitForGrokConfigurationResult(name, before, model, effort, timeo
2578
2578
  if (added.some((line) => /^(?:Switched to |✓?\s*Default model:)/.test(line)))
2579
2579
  return;
2580
2580
  // Grok Build 1.0.3では成功通知が次の再描画で消えることがある。変更前には無かった
2581
- // target model/effortが常駐footerへ現れた場合も、vendor自身の最終状態として受理する。
2581
+ // target model/effortが常駐footerへ現れた場合も、harness自身の最終状態として受理する。
2582
2582
  if (!footerAlreadyMatched && grokFooterHasConfiguration(screen, model, effort))
2583
2583
  return;
2584
2584
  await sleep(100);
@@ -2591,7 +2591,7 @@ function sendMenuChoice(name, choice) {
2591
2591
  throw new AitermError(`agent設定の選択を送れませんでした: ${sent.stderr.trim() || `code=${sent.code}`}`, 2);
2592
2592
  }
2593
2593
  }
2594
- /** 同じ対話sessionを保ったまま、vendor標準の操作でmodel/effortを変更する。 */
2594
+ /** 同じ対話sessionを保ったまま、harness標準の操作でmodel/effortを変更する。 */
2595
2595
  export async function configureAgent(name, opts) {
2596
2596
  assertSessionName(name);
2597
2597
  const model = opts.model?.trim() || null;
@@ -2738,7 +2738,7 @@ export function __testCodexConfigureChoices(screen, model, effort) {
2738
2738
  };
2739
2739
  }
2740
2740
  // v0.16.0: 親をブロックする wait 経路は廃止した。send は ready gate と submit 分離を内蔵した
2741
- // dispatch として即返り、event_cursor(送信直前のvendor完了正本境界)を receipt で返す。
2741
+ // dispatch として即返り、event_cursor(送信直前のharness完了正本境界)を receipt で返す。
2742
2742
  // 完了通知は aiterm-wait(--cursor で境界を渡す)、回収は pty_read / claude_turn recover が担う。
2743
2743
  export async function dispatchAgentTurn(name, text, o = {}) {
2744
2744
  assertSessionName(name);
@@ -2850,7 +2850,7 @@ function inspectClaudeOperation(meta, operationId, action) {
2850
2850
  if (!done)
2851
2851
  return { ...base, status: "unknown", raw_output: null, reason: "result_unknown" };
2852
2852
  if (!meta.vendor_session_id && done.vendor_session_id) {
2853
- bindAgentVendorSession(meta, done);
2853
+ bindAgentHarnessSession(meta, done);
2854
2854
  writeAgentMetadata(meta);
2855
2855
  }
2856
2856
  const rawOutput = readClaudeResultText(meta, done, operationId, transcriptUnavailable);
@@ -2893,8 +2893,8 @@ function portableForkPrompt(sourceSessionId, mission) {
2893
2893
  }
2894
2894
  return composePortableForkPrompt(value.context, mission);
2895
2895
  }
2896
- /** vendor CLI の存在だけを安全に要約する。認証状態・実行出力・解決先 path は返さない。 */
2897
- export function vendorLauncherDiagnostic(kind) {
2896
+ /** harness CLI の存在だけを安全に要約する。認証状態・実行出力・解決先 path は返さない。 */
2897
+ export function harnessLauncherDiagnostic(kind) {
2898
2898
  try {
2899
2899
  return resolveAgentBin(kind) ? "ready" : "not_applicable";
2900
2900
  }
@@ -3082,7 +3082,7 @@ export function openAgent(kind, opts = {}) {
3082
3082
  // grok.exe で実測済み(2026-08-15)。
3083
3083
  // Windows の grok/composer は Windows native の grok.exe だけを起動する(オーナー裁定 2026-08-15:
3084
3084
  // WindowsネイティブはWindowsネイティブで完結させ、WSL2へ持ち込まない)。WSL 側 grok を起動すると
3085
- // vendor 実体が WSL process になり、auth・session 記録(events/chat_history)が WSL home 側へ分裂して
3085
+ // harness 実体が WSL process になり、auth・session 記録(events/chat_history)が WSL home 側へ分裂して
3086
3086
  // transcript/completion を回収できない(実被弾: 2026-08-15 olc-plan-review-grok2)。
3087
3087
  if (isWin && (kind === "grok" || kind === "composer") && !isWindowsNativeExecutable(bin)) {
3088
3088
  ownTelemetryFailure("AITERM.VENDOR_LAUNCHER_FAILED", new AitermError(`Windows の ${label} launcher は Windows native の grok.exe だけを起動できます(現在の解決先: ${bin})。` +
@@ -3185,7 +3185,7 @@ export async function openAgentWithInitialPrompt(kind, opts = {}) {
3185
3185
  throw new AitermError("launch_operation_idはpromptなしのClaude相関launchだけで指定できます", 2);
3186
3186
  }
3187
3187
  // v0.16.0: launcher は常に managed(Stop hook つき)で立つ。手動運転したい場合は
3188
- // pty_open で素の PTY を開き、vendor CLI を自分で send する。
3188
+ // pty_open で素の PTY を開き、harness CLI を自分で send する。
3189
3189
  // 第3要素は「起動時点でturnが走っているか」の event_cursor: Grok/Composer/Cursor の argv prompt は
3190
3190
  // event file 新規作成直後の起動=境界0、prompt なしの起動は turn なし=null。
3191
3191
  if (!prompt || (kind !== "codex" && kind !== "claude")) {
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import * as fs from "node:fs";
3
- import * as os from "node:os";
4
3
  import * as path from "node:path";
4
+ import { currentUid as uid, runtimeStateBase } from "./state-root.js";
5
5
  const LAUNCH_ID_RE = /^[0-9a-f]{32}$/;
6
6
  const SESSION_RE = /^[A-Za-z0-9_-]{1,64}$/;
7
7
  const MAX_STDIN_BYTES = 1024 * 1024;
@@ -18,29 +18,6 @@ function hasAitermEnv() {
18
18
  process.env.AITERM_AGENT_SESSION_ID ||
19
19
  process.env.AITERM_AGENT_LAUNCH_ID);
20
20
  }
21
- function uid() {
22
- // Windows(native) は process.getuid を持たない。core の currentUid()・claude-stop-hook と
23
- // 同じ受容として 0 を返す(Windows の fs.Stats.uid は常に 0 のため owner 比較は自然に
24
- // 通過する。NTFS ACL は別体系)。POSIX は getuid のまま=挙動不変。
25
- // 以前はここで fail していたため、Windows では Grok/Composer の完了 event が
26
- // 一度も書かれず aiterm-wait が timeout まで返らなかった。
27
- if (typeof process.getuid !== "function")
28
- return 0;
29
- return process.getuid();
30
- }
31
- function runtimeStateBase() {
32
- const xdg = process.env.XDG_RUNTIME_DIR;
33
- if (xdg) {
34
- try {
35
- if (fs.statSync(xdg).isDirectory())
36
- return xdg;
37
- }
38
- catch {
39
- /* XDG_RUNTIME_DIR が壊れている CI/非 login 環境では os.tmpdir() に戻す */
40
- }
41
- }
42
- return os.tmpdir();
43
- }
44
21
  function agentsDir() {
45
22
  const root = path.join(runtimeStateBase(), `aiterm-mcp-${uid()}`);
46
23
  const agents = path.join(root, "agents");
@@ -1,6 +1,6 @@
1
1
  // Claude Code 固有の制御。完了正本は launch 固有 Stop hook が書く event/result(ADR 0025)。
2
2
  // core 所有のサービス(transcript 不在エラー)は引数で注入し、
3
- // 依存方向を core → vendors → agent-shared の一方向に保つ。
3
+ // 依存方向を core → harnesses → agent-shared の一方向に保つ。
4
4
  import * as fs from "node:fs";
5
5
  import * as path from "node:path";
6
6
  import { createHash, randomBytes, randomUUID } from "node:crypto";
@@ -44,7 +44,7 @@ export function agentClaudeDispatchReceiptPath(name, launchId, operationId) {
44
44
  return path.join(agentsDir(), `${name}.${launchId}.${validated.slice("sha256:".length)}.claude-dispatch`);
45
45
  }
46
46
  function claudeHookScriptPath() {
47
- // この module は dist/vendors/ に置かれるが、stop hook 実体は dist/ 直下に build される。
47
+ // この module は dist/harnesses/ に置かれるが、stop hook 実体は dist/ 直下に build される。
48
48
  return path.join(path.dirname(fileURLToPath(import.meta.url)), "..", "claude-stop-hook.js");
49
49
  }
50
50
  // process.execPath は Homebrew 等では Cellar の版付き実体を指す。長寿命 MCP server の起動後に
@@ -1,6 +1,6 @@
1
1
  // Codex 固有の制御。完了正本は root rollout transcript の task_complete(ADR 0022)。
2
2
  // core 所有のサービス(transcript 行読取・rate limit 検知)は引数で注入し、
3
- // 依存方向を core → vendors → agent-shared の一方向に保つ。
3
+ // 依存方向を core → harnesses → agent-shared の一方向に保つ。
4
4
  import * as fs from "node:fs";
5
5
  import * as os from "node:os";
6
6
  import * as path from "node:path";
@@ -65,7 +65,7 @@ export function codexConfigSummary(configPath) {
65
65
  bits.push(`sandbox_mode=${sandboxMode}`);
66
66
  return `共有 config: ${bits.join(" / ")}`;
67
67
  }
68
- export function findLatestCodexTranscript(codexHome, vendorSessionId) {
68
+ export function findLatestCodexTranscript(codexHome, harnessSessionId) {
69
69
  const sessionsDir = path.join(codexHome, "sessions");
70
70
  let latestFile = null;
71
71
  let latestMtime = -Infinity;
@@ -83,7 +83,7 @@ export function findLatestCodexTranscript(codexHome, vendorSessionId) {
83
83
  visit(file);
84
84
  continue;
85
85
  }
86
- if (!entry.isFile() || !entry.name.startsWith("rollout-") || !entry.name.endsWith(".jsonl") || !entry.name.includes(vendorSessionId))
86
+ if (!entry.isFile() || !entry.name.startsWith("rollout-") || !entry.name.endsWith(".jsonl") || !entry.name.includes(harnessSessionId))
87
87
  continue;
88
88
  try {
89
89
  const mtimeMs = fs.statSync(file).mtimeMs;
@@ -204,14 +204,14 @@ export function bindCodexTranscriptSession(meta) {
204
204
  const transcript = codexRootTranscript(meta);
205
205
  if (!transcript)
206
206
  return null;
207
- const vendorSessionId = codexTranscriptSessionId(transcript);
208
- if (vendorSessionId && !meta.vendor_session_id) {
209
- meta.vendor_session_id = vendorSessionId;
207
+ const harnessSessionId = codexTranscriptSessionId(transcript);
208
+ if (harnessSessionId && !meta.vendor_session_id) {
209
+ meta.vendor_session_id = harnessSessionId;
210
210
  writeAgentMetadata(meta);
211
211
  }
212
212
  return transcript;
213
213
  }
214
- export function codexCompletionEvent(meta, vendorSessionId, record) {
214
+ export function codexCompletionEvent(meta, harnessSessionId, record) {
215
215
  if (record?.type !== "event_msg" ||
216
216
  record?.payload?.type !== "task_complete" ||
217
217
  typeof record?.payload?.turn_id !== "string" ||
@@ -222,7 +222,7 @@ export function codexCompletionEvent(meta, vendorSessionId, record) {
222
222
  vendor: "codex",
223
223
  aiterm_session: meta.aiterm_session,
224
224
  launch_id: meta.launch_id,
225
- vendor_session_id: vendorSessionId,
225
+ vendor_session_id: harnessSessionId,
226
226
  turn_id: record.payload.turn_id,
227
227
  operation_id: null,
228
228
  reason: "Codex transcript task_complete",
@@ -235,13 +235,13 @@ export function latestCodexCompletion(meta, readTranscriptLines) {
235
235
  const transcript = codexRootTranscript(meta);
236
236
  if (!transcript)
237
237
  return null;
238
- const vendorSessionId = meta.vendor_session_id ?? codexTranscriptSessionId(transcript);
238
+ const harnessSessionId = meta.vendor_session_id ?? codexTranscriptSessionId(transcript);
239
239
  let latest = null;
240
240
  for (const line of readTranscriptLines(transcript)) {
241
241
  if (!line.trim())
242
242
  continue;
243
243
  try {
244
- latest = codexCompletionEvent(meta, vendorSessionId, JSON.parse(line)) ?? latest;
244
+ latest = codexCompletionEvent(meta, harnessSessionId, JSON.parse(line)) ?? latest;
245
245
  }
246
246
  catch {
247
247
  // Codexが末尾を書込み中なら、その行は次の観測で完結してから読む。
@@ -301,7 +301,7 @@ export async function observeCodexDone(meta, timeout, requestedCursor, detectRat
301
301
  parts.shift();
302
302
  discardLeadingFragment = false;
303
303
  }
304
- const vendorSessionId = meta.vendor_session_id ?? codexTranscriptSessionId(transcript);
304
+ const harnessSessionId = meta.vendor_session_id ?? codexTranscriptSessionId(transcript);
305
305
  for (const line of parts) {
306
306
  if (!line.trim())
307
307
  continue;
@@ -310,7 +310,7 @@ export async function observeCodexDone(meta, timeout, requestedCursor, detectRat
310
310
  continue;
311
311
  }
312
312
  try {
313
- const done = codexCompletionEvent(meta, vendorSessionId, JSON.parse(line));
313
+ const done = codexCompletionEvent(meta, harnessSessionId, JSON.parse(line));
314
314
  if (done)
315
315
  return observation("done", done);
316
316
  }
@@ -21,11 +21,11 @@ export function cursorTranscriptRoot(meta) {
21
21
  return null;
22
22
  return path.join(meta.cursor_home, "projects", cursorWorkspaceId(meta.cwd ?? process.cwd()), "agent-transcripts");
23
23
  }
24
- export function cursorTranscriptForSession(meta, vendorSessionId) {
24
+ export function cursorTranscriptForSession(meta, harnessSessionId) {
25
25
  const root = cursorTranscriptRoot(meta);
26
- if (!root || !UUID_RE.test(vendorSessionId))
26
+ if (!root || !UUID_RE.test(harnessSessionId))
27
27
  return null;
28
- return path.join(root, vendorSessionId, `${vendorSessionId}.jsonl`);
28
+ return path.join(root, harnessSessionId, `${harnessSessionId}.jsonl`);
29
29
  }
30
30
  export function listCursorTranscripts(meta) {
31
31
  const root = cursorTranscriptRoot(meta);
@@ -102,14 +102,14 @@ export function bindCursorTranscriptSession(meta) {
102
102
  const transcript = cursorTranscript(meta);
103
103
  if (!transcript)
104
104
  return null;
105
- const vendorSessionId = cursorTranscriptSessionId(transcript);
106
- if (vendorSessionId && !meta.vendor_session_id) {
107
- meta.vendor_session_id = vendorSessionId;
105
+ const harnessSessionId = cursorTranscriptSessionId(transcript);
106
+ if (harnessSessionId && !meta.vendor_session_id) {
107
+ meta.vendor_session_id = harnessSessionId;
108
108
  writeAgentMetadata(meta);
109
109
  }
110
110
  return transcript;
111
111
  }
112
- export function cursorCompletionEvent(meta, vendorSessionId, record, turnId) {
112
+ export function cursorCompletionEvent(meta, harnessSessionId, record, turnId) {
113
113
  if (meta.kind !== "cursor" || record?.type !== "turn_ended" || typeof record?.status !== "string")
114
114
  return null;
115
115
  return {
@@ -117,7 +117,7 @@ export function cursorCompletionEvent(meta, vendorSessionId, record, turnId) {
117
117
  vendor: "cursor",
118
118
  aiterm_session: meta.aiterm_session,
119
119
  launch_id: meta.launch_id,
120
- vendor_session_id: vendorSessionId,
120
+ vendor_session_id: harnessSessionId,
121
121
  turn_id: turnId,
122
122
  operation_id: null,
123
123
  reason: `Cursor transcript turn_ended:${record.status}`,
@@ -172,12 +172,12 @@ export function latestCursorCompletion(meta, readTranscriptLines) {
172
172
  const transcript = cursorTranscript(meta);
173
173
  if (!transcript)
174
174
  return null;
175
- const vendorSessionId = meta.vendor_session_id ?? cursorTranscriptSessionId(transcript);
175
+ const harnessSessionId = meta.vendor_session_id ?? cursorTranscriptSessionId(transcript);
176
176
  // callerとの共通signatureを保つ。Cursorのturn境界はbyte列でなくuser turn数を使う。
177
177
  void readTranscriptLines;
178
178
  const state = cursorTranscriptState(transcript);
179
179
  return state.terminalRecord
180
- ? cursorCompletionEvent(meta, vendorSessionId, state.terminalRecord, `cursor:${state.userTurns}`)
180
+ ? cursorCompletionEvent(meta, harnessSessionId, state.terminalRecord, `cursor:${state.userTurns}`)
181
181
  : null;
182
182
  }
183
183
  export async function observeCursorDone(meta, timeout, requestedCursor, detectRateLimit) {
@@ -208,8 +208,8 @@ export async function observeCursorDone(meta, timeout, requestedCursor, detectRa
208
208
  const state = cursorTranscriptState(transcript);
209
209
  malformedEvents = state.malformedEvents;
210
210
  if (state.userTurns > startBoundary && state.terminalRecord) {
211
- const vendorSessionId = meta.vendor_session_id ?? cursorTranscriptSessionId(transcript);
212
- const done = cursorCompletionEvent(meta, vendorSessionId, state.terminalRecord, `cursor:${state.userTurns}`);
211
+ const harnessSessionId = meta.vendor_session_id ?? cursorTranscriptSessionId(transcript);
212
+ const done = cursorCompletionEvent(meta, harnessSessionId, state.terminalRecord, `cursor:${state.userTurns}`);
213
213
  if (done)
214
214
  return observation("done", done);
215
215
  }
@@ -1,7 +1,7 @@
1
1
  // Grok / Composer 固有の制御。Composer は grok CLI の別モデル起動プリセットであり、
2
2
  // モデル既定値と表示名以外は Grok と完全共通(実測 2026-08-23 棚卸し・docs/32)。
3
3
  // core 所有のサービス(transcript 行読取・rate limit 検知)は引数で注入し、
4
- // 依存方向を core → vendors → agent-shared の一方向に保つ。
4
+ // 依存方向を core → harnesses → agent-shared の一方向に保つ。
5
5
  import * as fs from "node:fs";
6
6
  import * as os from "node:os";
7
7
  import * as path from "node:path";
@@ -43,8 +43,8 @@ export function resolveAndValidateGrokAuth(srcHome) {
43
43
  if (!value || typeof value !== "object" || Array.isArray(value))
44
44
  throw new AitermError("Grok 認証正本のJSONが不正です", 2);
45
45
  // auth file 自体は O_NOFOLLOW で開いているが、中間 directory の symlink は辿り得る。
46
- // vendor に渡す正本を path swap の入口にしないため、字句正規化した絶対 path と realpath を
47
- // 一致させ、canonical な祖先も root まで検証する。same-UID race の排他は vendor lock の責務。
46
+ // harness に渡す正本を path swap の入口にしないため、字句正規化した絶対 path と realpath を
47
+ // 一致させ、canonical な祖先も root まで検証する。same-UID race の排他は harness lock の責務。
48
48
  const lexicalPath = path.resolve(authPath);
49
49
  const canonicalPath = fs.realpathSync(authPath);
50
50
  if (lexicalPath !== canonicalPath)
@@ -299,7 +299,7 @@ export function grokLaunchNote(kind, model, effort, meta) {
299
299
  `effort=${effort ?? "CLI/model既定"}。` + writeScopeNote);
300
300
  }
301
301
  // grok/composer: 検証済み auth 正本をそのままの path 形で渡す。Windows では native 強制により
302
- // vendor は Windows process なので、Windows ドライブパスが正しい形(WSL 形への変換はしない)。
302
+ // harness は Windows process なので、Windows ドライブパスが正しい形(WSL 形への変換はしない)。
303
303
  export function grokEnvTokens(meta) {
304
304
  return [
305
305
  ...(meta.grok_auth_path ? [`GROK_AUTH_PATH=${shq(meta.grok_auth_path)}`] : []),
@@ -308,7 +308,7 @@ export function grokEnvTokens(meta) {
308
308
  }
309
309
  export function grokTuiReady(screen) {
310
310
  // Grok Build 0.2.117 は起動完了後に製品名を消し、model footerだけを残す。
311
- // Composerも同じfrontendでmodel名だけが異なるため、両方をvendor UIの根拠にする。
311
+ // Composerも同じfrontendでmodel名だけが異なるため、両方をharness UIの根拠にする。
312
312
  // Windows native grok.exe(1.0.4 実測)は入力欄markerを `❯` でなく `>` で描画するため両方を受ける。
313
313
  const grokFrontend = screen.includes("Grok Build") || /\b(?:Grok|Composer)\s+[\w.()-]+/.test(screen);
314
314
  return grokFrontend && /(^|\n|\s)[❯>]/.test(screen);
package/dist/index.js CHANGED
@@ -43,10 +43,10 @@ function fail(e) {
43
43
  */
44
44
  async function factoryDiagnostics() {
45
45
  const ptyList = core.readOnlyPtyListDiagnostic();
46
- const claude = core.vendorLauncherDiagnostic("claude");
47
- const codex = core.vendorLauncherDiagnostic("codex");
48
- const grok = core.vendorLauncherDiagnostic("grok");
49
- const cursor = core.vendorLauncherDiagnostic("cursor");
46
+ const claude = core.harnessLauncherDiagnostic("claude");
47
+ const codex = core.harnessLauncherDiagnostic("codex");
48
+ const grok = core.harnessLauncherDiagnostic("grok");
49
+ const cursor = core.harnessLauncherDiagnostic("cursor");
50
50
  const runtimeErrors = await runtimeErrorStoreDiagnostic();
51
51
  const overall = ptyList.status === "unverified" || runtimeErrors.status === "unverified" ? "unverified" : "ready";
52
52
  return JSON.stringify({
@@ -193,7 +193,7 @@ server.registerTool("pty_send", {
193
193
  server.registerTool("pty_read", {
194
194
  description: "セッションの出力をトークン削減して読む(既定は前回読取位置からの増分)。" +
195
195
  "削減: 制御文字除去 / 反復圧縮 / head+tail 折りたたみ+復元ヒント+メタ併記。" +
196
- "agent_transcript:true は agent session の直近完了ターンの最終 assistant メッセージを公開されたvendor記録から平文で返す。" +
196
+ "agent_transcript:true は agent session の直近完了ターンの最終 assistant メッセージを公開されたharness記録から平文で返す。" +
197
197
  "長い回答が screen tail で切れた時の回収用。",
198
198
  inputSchema: {
199
199
  session_id: z.string(),
@@ -0,0 +1,30 @@
1
+ // state root(`aiterm-mcp-<uid>`)の位置決めの唯一の置き場。node builtin 以外へ依存しない最下層で、
2
+ // 単体実行される stop hook 2本と agent-shared がここだけを共有する(campaign 32 の maintenance queue
3
+ // に積んだ uid()/runtimeStateBase() の三重実装を解消。stop hook の「内部 module へ依存しない」設計は、
4
+ // 本 module が builtin 依存だけであることで保たれる)。
5
+ import * as fs from "node:fs";
6
+ import * as os from "node:os";
7
+ export function currentUid() {
8
+ // Windows(native)は process.getuid を持たない。以前はここで throw していたが、
9
+ // agent metadata の存在確認経由で素の pty_send まで巻き込んで全 send を殺していた。
10
+ // Windows の fs.Stats.uid は常に 0 なので、ここも 0 を返せば owner 比較
11
+ // (st.uid !== currentUid()) は自然に通過する。Windows は POSIX owner 検証を
12
+ // 持たない(NTFS ACL は別体系)という既知の制約の明示的受容であり、POSIX 側は
13
+ // getuid をそのまま返すため挙動不変。
14
+ if (typeof process.getuid !== "function")
15
+ return 0;
16
+ return process.getuid();
17
+ }
18
+ export function runtimeStateBase() {
19
+ const xdg = process.env.XDG_RUNTIME_DIR;
20
+ if (xdg) {
21
+ try {
22
+ if (fs.statSync(xdg).isDirectory())
23
+ return xdg;
24
+ }
25
+ catch {
26
+ /* XDG_RUNTIME_DIR が壊れている CI/非 login 環境では os.tmpdir() に戻す */
27
+ }
28
+ }
29
+ return os.tmpdir();
30
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aiterm-mcp",
3
- "version": "0.28.1",
3
+ "version": "0.28.3",
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": [
@@ -26,7 +26,7 @@
26
26
  ],
27
27
  "license": "MIT",
28
28
  "author": {
29
- "name": "Quo / \u30af\u30aa at kitepon.dev",
29
+ "name": "Quo / クオ at kitepon.dev",
30
30
  "url": "https://kitepon.dev/"
31
31
  },
32
32
  "repository": {
@@ -45,7 +45,7 @@
45
45
  },
46
46
  "files": [
47
47
  "dist/*.js",
48
- "dist/vendors/*.js",
48
+ "dist/harnesses/*.js",
49
49
  "README.md",
50
50
  "LICENSE"
51
51
  ],