aiterm-mcp 0.21.3 → 0.22.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/dist/core.js CHANGED
@@ -11,7 +11,7 @@ import { spawnSync } from "node:child_process";
11
11
  import * as fs from "node:fs";
12
12
  import * as path from "node:path";
13
13
  import * as os from "node:os";
14
- import { createHash, randomBytes } from "node:crypto";
14
+ import { createHash, randomBytes, randomUUID } from "node:crypto";
15
15
  import { fileURLToPath } from "node:url";
16
16
  import * as rtk from "./rtk.js";
17
17
  import { recordRuntimeError } from "./runtime-error-store.js";
@@ -48,6 +48,8 @@ const NON_POSIX_MARK_SHELLS = new Set(["fish", "csh", "tcsh"]);
48
48
  const MARK_DONE_RE = /<<<AITERM_DONE rc=[0-9]+>>>/;
49
49
  const LAUNCH_ID_RE = /^[0-9a-f]{32}$/;
50
50
  const OPERATION_ID_RE = /^sha256:[0-9a-f]{64}$/;
51
+ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
52
+ const AGENT_LINEAGE_RE = /^[A-Za-z0-9_.:-]+(?:>[A-Za-z0-9_.:-]+)*$/;
51
53
  const AGENT_DONE_POLL_MS = 100;
52
54
  const AGENT_DONE_SETTLE_MIN_MS = 250;
53
55
  const AGENT_DONE_SCREEN_SETTLE_POLL_MS = 100;
@@ -57,6 +59,7 @@ const AGENT_SUBMIT_DELAY_MS = 250;
57
59
  const AGENT_EVENT_MAX_BYTES = 1024 * 1024;
58
60
  const AGENT_EVENT_TAIL_BYTES = 64 * 1024;
59
61
  const CODEX_TRANSCRIPT_INCREMENT_MAX_BYTES = 16 * 1024 * 1024;
62
+ const GROK_TRANSCRIPT_INCREMENT_MAX_BYTES = 16 * 1024 * 1024;
60
63
  const AGENT_METADATA_NEGATIVE_CACHE_TTL_MS = 2_000;
61
64
  const AGENT_TUI_READY_TIMEOUT_MS = 30_000;
62
65
  const AGENT_TUI_READY_POLL_MS = 500;
@@ -437,24 +440,6 @@ function agentClaudeDispatchReceiptPath(name, launchId, operationId) {
437
440
  const validated = validateOperationId(operationId);
438
441
  return path.join(agentsDir(), `${name}.${launchId}.${validated.slice("sha256:".length)}.claude-dispatch`);
439
442
  }
440
- function agentManagedCodexHomePath(name, launchId) {
441
- assertSessionName(name);
442
- if (!LAUNCH_ID_RE.test(launchId))
443
- throw new AitermError(`launch_id が不正です: ${launchId}`, 2);
444
- return path.join(agentsDir(), `${name}.${launchId}.codex-home`);
445
- }
446
- function agentManagedGrokHomePath(name, launchId) {
447
- assertSessionName(name);
448
- if (!LAUNCH_ID_RE.test(launchId))
449
- throw new AitermError(`launch_id が不正です: ${launchId}`, 2);
450
- return path.join(agentsDir(), `${name}.${launchId}.grok-home`);
451
- }
452
- function agentManagedGrokUserHomePath(name, launchId) {
453
- assertSessionName(name);
454
- if (!LAUNCH_ID_RE.test(launchId))
455
- throw new AitermError(`launch_id が不正です: ${launchId}`, 2);
456
- return path.join(agentsDir(), `${name}.${launchId}.home`);
457
- }
458
443
  function existingAgentsDir() {
459
444
  if (typeof process.getuid !== "function")
460
445
  return null;
@@ -494,6 +479,7 @@ function cleanupAgentState(name) {
494
479
  f.endsWith(".events.jsonl") ||
495
480
  f.endsWith(".wait.lock") ||
496
481
  f.endsWith(".claude-settings.json") ||
482
+ f.endsWith(".claude-mcp.json") ||
497
483
  f.endsWith(".claude-result.json") ||
498
484
  f.endsWith(".claude-operation.json") ||
499
485
  f.endsWith(".claude-approval.json") ||
@@ -953,7 +939,7 @@ function assertManagedClaudeCredentialCommandNotSent(name, text) {
953
939
  const normalized = text.replace(PASTE_MARKERS_RE, "").replace(ANSI_RE, "").replace(CTRL_RE, "").trim();
954
940
  if (!/^\/(?:login|logout)$/i.test(normalized))
955
941
  return;
956
- throw new AitermError("managed Claude sessionでは共有認証を変更する /login と /logout を送信できません。" +
942
+ throw new AitermError("aiterm相関付きClaude sessionでは共有認証を変更する /login と /logout を送信できません。" +
957
943
  "認証操作は通常端末で一度だけ行い、必要ならこのsessionをcloseして起動し直してください。", 2);
958
944
  }
959
945
  export function send(name, text, o = {}) {
@@ -979,7 +965,7 @@ export function send(name, text, o = {}) {
979
965
  // 拒否した呼び出しが古いmarkを消したり偽のmarkを残すと、後続readが存在しない
980
966
  // sentinelを待つため、公開上の拒否は副作用ゼロでなければならない。
981
967
  if (!o.preserveAgentOperation && managedClaudeOperation(name) !== undefined) {
982
- throw new AitermError("managed Claude agent sessionへの通常送信はturn境界を失うため拒否します。" +
968
+ throw new AitermError("aiterm相関付きClaude agent sessionへの通常送信はturn境界を失うため拒否します。" +
983
969
  "pty_send(forceなし=自動dispatch)を使うか、通常対話へ切り替えるならsessionをcloseしてpty_openから手動起動し直してください。", 2);
984
970
  }
985
971
  writeLastcmd(name, text); // read rtk の reducer 分類用(書換/mark 前の素のコマンド)
@@ -1064,7 +1050,7 @@ export function sendKey(name, key, o = {}) {
1064
1050
  throw new AitermError(`session '${name}' が無い`, 2);
1065
1051
  const k = KEYMAP[key.toLowerCase()] ?? key;
1066
1052
  if (!o.preserveAgentOperation && managedClaudeOperation(name) !== undefined && k !== "C-c") {
1067
- throw new AitermError("managed Claude agent sessionではturn相関を壊さないC-cだけをpty_keyで送れます。" +
1053
+ throw new AitermError("aiterm相関付きClaude agent sessionではturn相関を壊さないC-cだけをpty_keyで送れます。" +
1068
1054
  "他の対話操作はpty_send(自動dispatch)、終了はpty_closeを使ってください。", 2);
1069
1055
  }
1070
1056
  tmux("send-keys", "-t", name, k);
@@ -1354,6 +1340,7 @@ export function killAll() {
1354
1340
  f.endsWith(".events.jsonl") ||
1355
1341
  f.endsWith(".wait.lock") ||
1356
1342
  f.endsWith(".claude-settings.json") ||
1343
+ f.endsWith(".claude-mcp.json") ||
1357
1344
  f.endsWith(".claude-result.json") ||
1358
1345
  f.endsWith(".claude-operation.json") ||
1359
1346
  f.endsWith(".claude-dispatch") ||
@@ -1376,12 +1363,80 @@ export function killAll() {
1376
1363
  agentMetadataNegativeCache.clear();
1377
1364
  return "killed all sessions on this socket";
1378
1365
  }
1366
+ function readAgentLineageSeed() {
1367
+ const role = process.env.AITERM_AGENT_ROLE;
1368
+ const session = process.env.AITERM_AGENT_SESSION_ID;
1369
+ const depthRaw = process.env.AITERM_AGENT_DEPTH;
1370
+ const lineage = process.env.AITERM_AGENT_LINEAGE;
1371
+ const delegationAllowed = process.env.AITERM_AGENT_DELEGATION_ALLOWED;
1372
+ const nested = [role, session, depthRaw, lineage, delegationAllowed].some((value) => value !== undefined);
1373
+ if (!nested) {
1374
+ return { parentSessionId: "host-root", delegationDepth: 1, lineagePrefix: "host-root" };
1375
+ }
1376
+ if (role !== "subagent" ||
1377
+ delegationAllowed !== "true" ||
1378
+ !session ||
1379
+ !/^[A-Za-z0-9_-]{1,64}$/.test(session) ||
1380
+ !depthRaw ||
1381
+ !/^\d+$/.test(depthRaw) ||
1382
+ !lineage ||
1383
+ lineage.length > 4096 ||
1384
+ !AGENT_LINEAGE_RE.test(lineage)) {
1385
+ throw new AitermError("継承したAITERM sub-agent lineage環境が不正です", 2);
1386
+ }
1387
+ const parentDepth = Number(depthRaw);
1388
+ if (!Number.isSafeInteger(parentDepth) || parentDepth < 1 || parentDepth >= 1_000_000) {
1389
+ throw new AitermError("継承したAITERM_AGENT_DEPTHが不正です", 2);
1390
+ }
1391
+ return { parentSessionId: session, delegationDepth: parentDepth + 1, lineagePrefix: lineage };
1392
+ }
1393
+ function createAgentLineageContext(kind, sessionId, seed) {
1394
+ const lineage = `${seed.lineagePrefix}>${kind}:${sessionId}`;
1395
+ if (lineage.length > 4096 || !AGENT_LINEAGE_RE.test(lineage)) {
1396
+ throw new AitermError("AITERM sub-agent lineageが上限または形式に違反しました", 2);
1397
+ }
1398
+ return {
1399
+ agentRole: "subagent",
1400
+ parentSessionId: seed.parentSessionId,
1401
+ delegationDepth: seed.delegationDepth,
1402
+ lineage,
1403
+ delegationAllowed: true,
1404
+ };
1405
+ }
1406
+ function agentLineageFields(context) {
1407
+ return {
1408
+ agent_role: context.agentRole,
1409
+ parent_session_id: context.parentSessionId,
1410
+ delegation_depth: context.delegationDepth,
1411
+ lineage: context.lineage,
1412
+ delegation_allowed: context.delegationAllowed,
1413
+ };
1414
+ }
1415
+ function subagentInstruction(meta) {
1416
+ if (meta.agent_role !== "subagent" ||
1417
+ !meta.parent_session_id ||
1418
+ !Number.isSafeInteger(meta.delegation_depth) ||
1419
+ !meta.lineage ||
1420
+ meta.delegation_allowed !== true) {
1421
+ throw new AitermError("sub-agent instructionに必要なlineage metadataがありません", 2);
1422
+ }
1423
+ return [
1424
+ "<aiterm_subagent_context>",
1425
+ "あなたはaitermから起動されたsub-agentであり、root agentではありません。",
1426
+ `AITERM_AGENT_LAUNCH_ID=${meta.launch_id}`,
1427
+ `role=${meta.agent_role}`,
1428
+ `parent_session_id=${meta.parent_session_id}`,
1429
+ `delegation_depth=${meta.delegation_depth}`,
1430
+ `lineage=${meta.lineage}`,
1431
+ "delegation_allowed=true",
1432
+ "任務の所有権を保ち、結果を親へ返してください。必要なら追加のsub-agentへ委譲してよいです。",
1433
+ "ただし、同じ任務全体を同型agentへ反射的に丸投げして自己複製ループを作らないでください。",
1434
+ "</aiterm_subagent_context>",
1435
+ ].join("\n");
1436
+ }
1379
1437
  const DEFAULT_AGENT_DONE_TIMEOUT = 600;
1380
1438
  const agentMetadataNegativeCache = new Map();
1381
1439
  let agentTuiReadyStableSamplesTestOverride = null;
1382
- function grokHookScriptPath() {
1383
- return path.join(path.dirname(fileURLToPath(import.meta.url)), "grok-stop-hook.js");
1384
- }
1385
1440
  function claudeHookScriptPath() {
1386
1441
  return path.join(path.dirname(fileURLToPath(import.meta.url)), "claude-stop-hook.js");
1387
1442
  }
@@ -1464,36 +1519,6 @@ function createEmpty0600NoFollow(p) {
1464
1519
  function realCodexHome() {
1465
1520
  return process.env.CODEX_HOME || path.join(process.env.HOME ?? os.homedir(), ".codex");
1466
1521
  }
1467
- // 引数で model/effort を渡された時は managed config のピンを上書きする(端末 config の
1468
- // model/model_reasoning_effort ピン——例: effort=ultra は proactive 自動委譲 ON——が対話子へ
1469
- // 黙って波及するのを防ぐ)。TOML の top-level キーは最初のテーブルヘッダより前にしか置けないため、
1470
- // 既存行の除去は先頭領域に限定し、上書き行はファイル先頭へ置く。
1471
- function applyCodexConfigOverrides(body, overrides) {
1472
- const lines = [];
1473
- if (overrides.model)
1474
- lines.push(`model = ${JSON.stringify(overrides.model)}`);
1475
- if (overrides.effort)
1476
- lines.push(`model_reasoning_effort = ${JSON.stringify(overrides.effort)}`);
1477
- if (!lines.length)
1478
- return body;
1479
- if (body == null)
1480
- return lines.join("\n") + "\n";
1481
- const rows = body.split(/\r?\n/);
1482
- let firstTable = rows.findIndex((l) => /^\s*\[/.test(l));
1483
- if (firstTable === -1)
1484
- firstTable = rows.length;
1485
- const head = rows.slice(0, firstTable).filter((l) => {
1486
- if (overrides.model && /^\s*(?:"model"|'model'|model)\s*=/.test(l))
1487
- return false;
1488
- if (overrides.effort && /^\s*(?:"model_reasoning_effort"|'model_reasoning_effort'|model_reasoning_effort)\s*=/.test(l))
1489
- return false;
1490
- return true;
1491
- });
1492
- let out = [...lines, ...head, ...rows.slice(firstTable)].join("\n");
1493
- if (!out.endsWith("\n"))
1494
- out += "\n";
1495
- return out;
1496
- }
1497
1522
  function readCodexConfigPins(configPath) {
1498
1523
  let body;
1499
1524
  try {
@@ -1518,7 +1543,7 @@ function readCodexConfigPins(configPath) {
1518
1543
  };
1519
1544
  return { model: pick("model"), effort: pick("model_reasoning_effort") };
1520
1545
  }
1521
- function managedCodexConfigSummary(configPath) {
1546
+ function codexConfigSummary(configPath) {
1522
1547
  let body;
1523
1548
  try {
1524
1549
  body = fs.readFileSync(configPath, "utf8");
@@ -1547,93 +1572,9 @@ function managedCodexConfigSummary(configPath) {
1547
1572
  bits.push(`approval_policy=${approvalPolicy}`);
1548
1573
  if (sandboxMode)
1549
1574
  bits.push(`sandbox_mode=${sandboxMode}`);
1550
- return `managed config: ${bits.join(" / ")}`;
1551
- }
1552
- // Custom agent definitions are configuration, not mutable Codex state. Copy only direct
1553
- // agents/*.toml entries into the per-launch home so role discovery matches the source home while
1554
- // sessions/cache remain isolated. Source symlinks are resolved and their contents are snapshotted;
1555
- // the managed home never points back to the source definition.
1556
- function snapshotCodexAgentDefinitions(srcHome, managedHome) {
1557
- const srcDir = path.join(srcHome, "agents");
1558
- let srcDirSt;
1559
- try {
1560
- srcDirSt = fs.statSync(srcDir);
1561
- }
1562
- catch (e) {
1563
- if (e.code === "ENOENT")
1564
- return;
1565
- throw e;
1566
- }
1567
- if (!srcDirSt.isDirectory()) {
1568
- throw new AitermError(`Codex agents が directory ではありません: ${srcDir}`, 2);
1569
- }
1570
- const names = fs.readdirSync(srcDir).filter((name) => name.endsWith(".toml")).sort();
1571
- if (!names.length)
1572
- return;
1573
- const dstDir = path.join(managedHome, "agents");
1574
- fs.mkdirSync(dstDir, { mode: 0o700 });
1575
- fs.chmodSync(dstDir, 0o700);
1576
- for (const name of names) {
1577
- const src = path.join(srcDir, name);
1578
- const resolved = fs.realpathSync(src);
1579
- const st = fs.statSync(resolved);
1580
- if (!st.isFile()) {
1581
- throw new AitermError(`Codex agent definition が通常ファイルではありません: ${src}`, 2);
1582
- }
1583
- writeText0600(path.join(dstDir, name), fs.readFileSync(resolved, "utf8"));
1584
- }
1585
- }
1586
- function createManagedCodexHome(name, launchId, overrides = {}) {
1587
- const srcHome = realCodexHome();
1588
- let srcSt;
1589
- try {
1590
- srcSt = fs.statSync(srcHome);
1591
- }
1592
- catch {
1593
- throw new AitermError(`Codex home が見つかりません: ${srcHome}`, 2);
1594
- }
1595
- if (!srcSt.isDirectory())
1596
- throw new AitermError(`Codex home が directory ではありません: ${srcHome}`, 2);
1597
- const managedHome = agentManagedCodexHomePath(name, launchId);
1598
- fs.mkdirSync(managedHome, { recursive: false, mode: 0o700 });
1599
- fs.chmodSync(managedHome, 0o700);
1600
- const authSrc = path.join(srcHome, "auth.json");
1601
- try {
1602
- const st = fs.statSync(authSrc);
1603
- if (!st.isFile())
1604
- throw new AitermError(`Codex auth.json が通常ファイルではありません: ${authSrc}`, 2);
1605
- }
1606
- catch (e) {
1607
- if (e.code === "ENOENT") {
1608
- throw new AitermError(`Codex auth.json が見つかりません。先に codex login が必要です: ${srcHome}`, 2);
1609
- }
1610
- throw e;
1611
- }
1612
- fs.symlinkSync(authSrc, path.join(managedHome, "auth.json"));
1613
- const configSrc = path.join(srcHome, "config.toml");
1614
- let configBody = null;
1615
- try {
1616
- const st = fs.statSync(configSrc);
1617
- if (st.isFile())
1618
- configBody = fs.readFileSync(configSrc, "utf8");
1619
- }
1620
- catch (e) {
1621
- if (e.code !== "ENOENT")
1622
- throw e;
1623
- }
1624
- const configOut = applyCodexConfigOverrides(configBody, overrides);
1625
- if (configOut != null) {
1626
- const configDst = path.join(managedHome, "config.toml");
1627
- fs.writeFileSync(configDst, configOut, { mode: 0o600 });
1628
- fs.chmodSync(configDst, 0o600);
1629
- }
1630
- snapshotCodexAgentDefinitions(srcHome, managedHome);
1631
- // Codex 自身の rollout transcript に task_complete が永続化されるため、完了検出用の
1632
- // Stop hook は作らない。hook と transcript の二重正本、および hook failure の単一障害点を
1633
- // ここでなくす。
1634
- return managedHome;
1575
+ return `共有 config: ${bits.join(" / ")}`;
1635
1576
  }
1636
- function createManagedClaudeSettings(name, launchId) {
1577
+ function createClaudeCorrelationSettings(name, launchId) {
1637
1578
  const hookScript = claudeHookScriptPath();
1638
1579
  if (!fs.existsSync(hookScript)) {
1639
1580
  throw new AitermError(`Claude Stop hook wrapper が見つかりません。npm run build を実行してください: ${hookScript}`, 2);
@@ -1713,64 +1654,6 @@ function resolveAndValidateGrokAuth(srcHome) {
1713
1654
  fs.closeSync(fd);
1714
1655
  }
1715
1656
  }
1716
- function writeManagedGrokConfig(grokHome) {
1717
- fs.mkdirSync(path.join(grokHome, "hooks"), { recursive: false, mode: 0o700 });
1718
- writeText0600(path.join(grokHome, "config.toml"), [
1719
- "[cli]",
1720
- "auto_update = false",
1721
- "",
1722
- "[features]",
1723
- "remote_fetch = false",
1724
- "managed_config = false",
1725
- "",
1726
- "[compat.claude]",
1727
- "skills = false",
1728
- "rules = false",
1729
- "agents = false",
1730
- "mcps = false",
1731
- "hooks = false",
1732
- "",
1733
- "[compat.cursor]",
1734
- "skills = false",
1735
- "rules = false",
1736
- "agents = false",
1737
- "mcps = false",
1738
- "hooks = false",
1739
- "",
1740
- ].join("\n"));
1741
- }
1742
- function createManagedGrokHome(name, launchId, authPath) {
1743
- const grokHome = agentManagedGrokHomePath(name, launchId);
1744
- const fakeHome = agentManagedGrokUserHomePath(name, launchId);
1745
- fs.mkdirSync(grokHome, { recursive: false, mode: 0o700 });
1746
- fs.mkdirSync(fakeHome, { recursive: false, mode: 0o700 });
1747
- fs.chmodSync(grokHome, 0o700);
1748
- fs.chmodSync(fakeHome, 0o700);
1749
- fs.symlinkSync(grokHome, path.join(fakeHome, ".grok"));
1750
- const hookScript = grokHookScriptPath();
1751
- if (!fs.existsSync(hookScript)) {
1752
- throw new AitermError(`Grok Stop hook wrapper が見つかりません。npm run build を実行してください: ${hookScript}`, 2);
1753
- }
1754
- writeManagedGrokConfig(grokHome);
1755
- writeJson0600(path.join(grokHome, "hooks", "aiterm-stop.json"), {
1756
- hooks: {
1757
- Stop: [
1758
- {
1759
- hooks: [
1760
- {
1761
- type: "command",
1762
- command: nodeHookCommand(hookScript),
1763
- timeout: 10,
1764
- },
1765
- ],
1766
- },
1767
- ],
1768
- },
1769
- });
1770
- // Grok 0.2.87 は compat false でも ~/.claude/plugins の hook file を拾う。HOME を一時化して
1771
- // plugin/hook source を完全に 0 にする。実 HOME は必要なら hook/agent 側で参照できるよう env で渡す。
1772
- return { grokHome, home: fakeHome, authPath };
1773
- }
1774
1657
  function writeAgentMetadata(meta) {
1775
1658
  writeJson0600(agentMetadataPath(meta.aiterm_session, meta.launch_id), meta);
1776
1659
  }
@@ -1905,7 +1788,7 @@ function parseClaudeApprovalScreen(screen) {
1905
1788
  question = i;
1906
1789
  }
1907
1790
  if (question < 0) {
1908
- throw new AitermError("managed Claudeの承認UIを現在画面で確認できません(Do you want to proceed? がありません)", 2);
1791
+ throw new AitermError("aiterm相関付きClaudeの承認UIを現在画面で確認できません(Do you want to proceed? がありません)", 2);
1909
1792
  }
1910
1793
  const choices = [];
1911
1794
  const seen = new Set();
@@ -1924,13 +1807,13 @@ function parseClaudeApprovalScreen(screen) {
1924
1807
  if (!decision)
1925
1808
  continue;
1926
1809
  if (!Number.isSafeInteger(index) || index < 1 || seen.has(decision)) {
1927
- throw new AitermError("managed Claudeの承認UI選択肢が一意に解釈できません", 2);
1810
+ throw new AitermError("aiterm相関付きClaudeの承認UI選択肢が一意に解釈できません", 2);
1928
1811
  }
1929
1812
  seen.add(decision);
1930
1813
  choices.push({ decision, index, label });
1931
1814
  }
1932
1815
  if (!seen.has("approve_once") || !seen.has("deny")) {
1933
- throw new AitermError("managed Claudeの承認UIに安全な単発Yes/No選択肢を確認できません", 2);
1816
+ throw new AitermError("aiterm相関付きClaudeの承認UIに安全な単発Yes/No選択肢を確認できません", 2);
1934
1817
  }
1935
1818
  return {
1936
1819
  promptDigest: `sha256:${createHash("sha256").update(canonical, "utf8").digest("hex")}`,
@@ -1940,7 +1823,7 @@ function parseClaudeApprovalScreen(screen) {
1940
1823
  function assertExpectedClaudeOperation(meta, expectedOperationId) {
1941
1824
  const active = readClaudeOperationMarker(meta);
1942
1825
  if (!active)
1943
- throw new AitermError("managed Claudeに未解決のactive operationがありません", 2);
1826
+ throw new AitermError("aiterm相関付きClaudeに未解決のactive operationがありません", 2);
1944
1827
  if (active.operationId !== expectedOperationId) {
1945
1828
  const actual = active.operationId ?? "operation_idなし";
1946
1829
  const expected = expectedOperationId ?? "operation_idなし";
@@ -1954,7 +1837,7 @@ export function runClaudeApproval({ action, session_id: name, operation_id: oper
1954
1837
  throw new AitermError(`session '${name}' が無い`, 2);
1955
1838
  const meta = loadAgentMetadata(name);
1956
1839
  if (meta.kind !== "claude")
1957
- throw new AitermError("claude_approvalはmanaged Claude agent sessionだけで使用できます", 2);
1840
+ throw new AitermError("claude_approvalはaiterm相関付きClaude agent sessionだけで使用できます", 2);
1958
1841
  const operationId = operationIdInput == null ? null : validateOperationId(operationIdInput);
1959
1842
  if (action === "inspect") {
1960
1843
  if (approvalChoice != null || observedPromptDigest != null) {
@@ -2220,13 +2103,13 @@ function liveWaitLocks(name) {
2220
2103
  }
2221
2104
  return out;
2222
2105
  }
2223
- function createClaudeAgentMetadata(name, cwd, initialPrompt, launchOperationId, launchRequestDigest) {
2106
+ function createClaudeAgentMetadata(name, cwd, initialPrompt, launchOperationId, launchRequestDigest, lineageContext) {
2224
2107
  const launchId = randomBytes(16).toString("hex");
2225
2108
  const eventFile = agentEventPath(name, launchId);
2226
2109
  const resultFile = agentClaudeResultPath(name, launchId);
2227
2110
  createEmpty0600NoFollow(eventFile);
2228
2111
  createEmpty0600NoFollow(resultFile);
2229
- const claudeSettings = createManagedClaudeSettings(name, launchId);
2112
+ const claudeSettings = createClaudeCorrelationSettings(name, launchId);
2230
2113
  const meta = {
2231
2114
  kind: "claude",
2232
2115
  aiterm_session: name,
@@ -2234,11 +2117,12 @@ function createClaudeAgentMetadata(name, cwd, initialPrompt, launchOperationId,
2234
2117
  event_file: eventFile,
2235
2118
  created_at: new Date().toISOString(),
2236
2119
  cwd,
2237
- vendor_session_id: null,
2120
+ vendor_session_id: randomUUID(),
2238
2121
  initial_prompt: initialPrompt,
2239
2122
  launch_operation_id: launchOperationId,
2240
2123
  launch_request_digest: launchRequestDigest,
2241
- hook_route: "managed_claude_settings",
2124
+ hook_route: "shared_claude_settings",
2125
+ ...agentLineageFields(lineageContext),
2242
2126
  node_platform: process.platform,
2243
2127
  claude_settings: claudeSettings,
2244
2128
  result_file: resultFile,
@@ -2246,11 +2130,11 @@ function createClaudeAgentMetadata(name, cwd, initialPrompt, launchOperationId,
2246
2130
  writeAgentMetadata(meta);
2247
2131
  return meta;
2248
2132
  }
2249
- function createCodexAgentMetadata(name, cwd, initialPrompt, overrides = {}, writeScope) {
2133
+ function createCodexAgentMetadata(name, cwd, initialPrompt, overrides = {}, writeScope, lineageContext) {
2250
2134
  const launchId = randomBytes(16).toString("hex");
2251
2135
  const eventFile = agentEventPath(name, launchId);
2252
2136
  createEmpty0600NoFollow(eventFile);
2253
- const codexHome = createManagedCodexHome(name, launchId, overrides);
2137
+ const codexHome = realCodexHome();
2254
2138
  const meta = {
2255
2139
  kind: "codex",
2256
2140
  aiterm_session: name,
@@ -2261,19 +2145,20 @@ function createCodexAgentMetadata(name, cwd, initialPrompt, overrides = {}, writ
2261
2145
  ...(writeScope === undefined ? {} : { write_scope: writeScope }),
2262
2146
  vendor_session_id: null,
2263
2147
  initial_prompt: initialPrompt,
2264
- hook_route: "managed_codex_home",
2148
+ hook_route: "shared_codex_home",
2265
2149
  completion_route: "codex_transcript",
2150
+ ...(lineageContext ? agentLineageFields(lineageContext) : {}),
2266
2151
  node_platform: process.platform,
2267
2152
  codex_home: codexHome,
2268
2153
  };
2269
2154
  writeAgentMetadata(meta);
2270
2155
  return meta;
2271
2156
  }
2272
- function createGrokAgentMetadata(kind, name, cwd, initialPrompt, authPath, writeScope) {
2157
+ function createGrokAgentMetadata(kind, name, cwd, initialPrompt, authPath, writeScope, lineageContext) {
2273
2158
  const launchId = randomBytes(16).toString("hex");
2274
2159
  const eventFile = agentEventPath(name, launchId);
2275
2160
  createEmpty0600NoFollow(eventFile);
2276
- const managed = createManagedGrokHome(name, launchId, authPath);
2161
+ const grokHome = realGrokHome();
2277
2162
  const meta = {
2278
2163
  kind,
2279
2164
  aiterm_session: name,
@@ -2282,17 +2167,45 @@ function createGrokAgentMetadata(kind, name, cwd, initialPrompt, authPath, write
2282
2167
  created_at: new Date().toISOString(),
2283
2168
  cwd,
2284
2169
  ...(writeScope === undefined ? {} : { write_scope: writeScope }),
2285
- vendor_session_id: null,
2170
+ vendor_session_id: randomUUID(),
2286
2171
  initial_prompt: initialPrompt,
2287
- hook_route: "managed_grok_home",
2172
+ hook_route: "shared_grok_home",
2173
+ completion_route: "grok_transcript",
2174
+ ...(lineageContext ? agentLineageFields(lineageContext) : {}),
2288
2175
  node_platform: process.platform,
2289
- grok_home: managed.grokHome,
2290
- home: managed.home,
2291
- grok_auth_path: managed.authPath,
2176
+ grok_home: grokHome,
2177
+ grok_auth_path: authPath,
2292
2178
  };
2293
2179
  writeAgentMetadata(meta);
2294
2180
  return meta;
2295
2181
  }
2182
+ function loadAgentLineageFields(m, required) {
2183
+ const present = m.agent_role !== undefined ||
2184
+ m.parent_session_id !== undefined ||
2185
+ m.delegation_depth !== undefined ||
2186
+ m.lineage !== undefined ||
2187
+ m.delegation_allowed !== undefined;
2188
+ if (!present && !required)
2189
+ return {};
2190
+ if (m.agent_role !== "subagent" ||
2191
+ typeof m.parent_session_id !== "string" ||
2192
+ !/^[A-Za-z0-9_-]{1,64}$/.test(m.parent_session_id) ||
2193
+ !Number.isSafeInteger(m.delegation_depth) ||
2194
+ m.delegation_depth < 1 ||
2195
+ typeof m.lineage !== "string" ||
2196
+ m.lineage.length > 4096 ||
2197
+ !AGENT_LINEAGE_RE.test(m.lineage) ||
2198
+ m.delegation_allowed !== true) {
2199
+ throw new AitermError("agent metadata のsub-agent lineageが不正です", 2);
2200
+ }
2201
+ return agentLineageFields({
2202
+ agentRole: "subagent",
2203
+ parentSessionId: m.parent_session_id,
2204
+ delegationDepth: m.delegation_depth,
2205
+ lineage: m.lineage,
2206
+ delegationAllowed: true,
2207
+ });
2208
+ }
2296
2209
  function loadAgentMetadata(name) {
2297
2210
  assertSessionName(name);
2298
2211
  const dir = agentsDir();
@@ -2300,7 +2213,7 @@ function loadAgentMetadata(name) {
2300
2213
  .readdirSync(dir)
2301
2214
  .filter((f) => f.startsWith(`${name}.`) && f.endsWith(".agent.json"));
2302
2215
  if (files.length === 0) {
2303
- throw new AitermError(`session '${name}' は agent_done 管理セッションではありません。claude_agent/codex_agent 等の launcher で起動してください(launcher は常に managed)。`, 2);
2216
+ throw new AitermError(`session '${name}' は agent_done 管理セッションではありません。claude_agent/codex_agent 等の launcher で起動してください。`, 2);
2304
2217
  }
2305
2218
  if (files.length !== 1) {
2306
2219
  throw new AitermError(`session '${name}' の agent metadata が複数あります。閉じて起動し直してください。`, 2);
@@ -2328,8 +2241,10 @@ function loadAgentMetadata(name) {
2328
2241
  const expectedResult = agentClaudeResultPath(name, m.launch_id);
2329
2242
  const launchOperationId = m.launch_operation_id ?? null;
2330
2243
  const launchRequestDigest = m.launch_request_digest ?? null;
2331
- if (m.hook_route !== "managed_claude_settings" ||
2244
+ if (m.hook_route !== "shared_claude_settings" ||
2332
2245
  m.claude_settings !== expectedSettings ||
2246
+ typeof m.vendor_session_id !== "string" ||
2247
+ !UUID_RE.test(m.vendor_session_id) ||
2333
2248
  m.result_file !== expectedResult ||
2334
2249
  ((launchOperationId === null) !== (launchRequestDigest === null)) ||
2335
2250
  (launchOperationId !== null && !OPERATION_ID_RE.test(launchOperationId)) ||
@@ -2344,19 +2259,20 @@ function loadAgentMetadata(name) {
2344
2259
  created_at: typeof m.created_at === "string" ? m.created_at : "",
2345
2260
  cwd: typeof m.cwd === "string" ? m.cwd : null,
2346
2261
  ...(typeof m.write_scope === "string" ? { write_scope: m.write_scope } : {}),
2347
- vendor_session_id: typeof m.vendor_session_id === "string" ? m.vendor_session_id : null,
2262
+ vendor_session_id: m.vendor_session_id,
2348
2263
  initial_prompt: normalizeInitialPromptState(m.initial_prompt),
2349
2264
  launch_operation_id: launchOperationId,
2350
2265
  launch_request_digest: launchRequestDigest,
2351
- hook_route: "managed_claude_settings",
2266
+ hook_route: "shared_claude_settings",
2267
+ ...loadAgentLineageFields(m, true),
2352
2268
  node_platform: process.platform,
2353
2269
  claude_settings: expectedSettings,
2354
2270
  result_file: expectedResult,
2355
2271
  };
2356
2272
  }
2357
2273
  if (m.kind === "codex") {
2358
- const expectedHome = agentManagedCodexHomePath(name, m.launch_id);
2359
- if (m.hook_route !== "managed_codex_home" || m.codex_home !== expectedHome) {
2274
+ const expectedHome = realCodexHome();
2275
+ if (m.hook_route !== "shared_codex_home" || m.completion_route !== "codex_transcript" || m.codex_home !== expectedHome) {
2360
2276
  throw new AitermError("agent metadata の path が現在の secure state root と一致しません", 2);
2361
2277
  }
2362
2278
  return {
@@ -2369,15 +2285,19 @@ function loadAgentMetadata(name) {
2369
2285
  ...(typeof m.write_scope === "string" ? { write_scope: m.write_scope } : {}),
2370
2286
  vendor_session_id: typeof m.vendor_session_id === "string" ? m.vendor_session_id : null,
2371
2287
  initial_prompt: normalizeInitialPromptState(m.initial_prompt),
2372
- hook_route: "managed_codex_home",
2373
- ...(m.completion_route === "codex_transcript" ? { completion_route: "codex_transcript" } : {}),
2288
+ hook_route: "shared_codex_home",
2289
+ completion_route: "codex_transcript",
2290
+ ...loadAgentLineageFields(m, true),
2374
2291
  node_platform: process.platform,
2375
2292
  codex_home: expectedHome,
2376
2293
  };
2377
2294
  }
2378
- const expectedGrokHome = agentManagedGrokHomePath(name, m.launch_id);
2379
- const expectedHome = agentManagedGrokUserHomePath(name, m.launch_id);
2380
- if (m.hook_route !== "managed_grok_home" || m.grok_home !== expectedGrokHome || m.home !== expectedHome) {
2295
+ const expectedGrokHome = realGrokHome();
2296
+ if (m.hook_route !== "shared_grok_home" ||
2297
+ m.completion_route !== "grok_transcript" ||
2298
+ m.grok_home !== expectedGrokHome ||
2299
+ typeof m.vendor_session_id !== "string" ||
2300
+ !UUID_RE.test(m.vendor_session_id)) {
2381
2301
  throw new AitermError("agent metadata の path が現在の secure state root と一致しません", 2);
2382
2302
  }
2383
2303
  const expectedAuthPath = resolveAndValidateGrokAuth(realGrokHome());
@@ -2392,12 +2312,13 @@ function loadAgentMetadata(name) {
2392
2312
  created_at: typeof m.created_at === "string" ? m.created_at : "",
2393
2313
  cwd: typeof m.cwd === "string" ? m.cwd : null,
2394
2314
  ...(typeof m.write_scope === "string" ? { write_scope: m.write_scope } : {}),
2395
- vendor_session_id: typeof m.vendor_session_id === "string" ? m.vendor_session_id : null,
2315
+ vendor_session_id: m.vendor_session_id,
2396
2316
  initial_prompt: normalizeInitialPromptState(m.initial_prompt),
2397
- hook_route: "managed_grok_home",
2317
+ hook_route: "shared_grok_home",
2318
+ completion_route: "grok_transcript",
2319
+ ...loadAgentLineageFields(m, true),
2398
2320
  node_platform: process.platform,
2399
2321
  grok_home: expectedGrokHome,
2400
- home: expectedHome,
2401
2322
  grok_auth_path: expectedAuthPath,
2402
2323
  };
2403
2324
  }
@@ -2503,7 +2424,10 @@ function bindCompletedInitialPrompt(meta) {
2503
2424
  setInitialPromptState(meta, "done");
2504
2425
  return;
2505
2426
  }
2506
- if (meta.vendor_session_id) {
2427
+ if ((meta.kind === "grok" || meta.kind === "composer") && meta.completion_route === "grok_transcript") {
2428
+ if (!latestGrokCompletion(meta)) {
2429
+ throw new AitermError(`agent session '${meta.aiterm_session}' は起動時 prompt の完了待ちです。${agentWaitGuide(meta.aiterm_session)}`, 2);
2430
+ }
2507
2431
  setInitialPromptState(meta, "done");
2508
2432
  return;
2509
2433
  }
@@ -2537,6 +2461,9 @@ function tryLoadAgentMetadata(name) {
2537
2461
  function latestAgentDoneEvent(meta, expectedOperationId = null) {
2538
2462
  if (meta.kind === "codex")
2539
2463
  return latestCodexCompletion(meta);
2464
+ if ((meta.kind === "grok" || meta.kind === "composer") && meta.completion_route === "grok_transcript") {
2465
+ return latestGrokCompletion(meta);
2466
+ }
2540
2467
  const size = safeStatSize(meta.event_file);
2541
2468
  if (size === 0)
2542
2469
  return null;
@@ -2672,10 +2599,52 @@ function listCodexTranscripts(codexHome) {
2672
2599
  }
2673
2600
  };
2674
2601
  visit(sessionsDir);
2675
- // managed CODEX_HOME では root TUI の rollout が最初に作られる。後から同じ home に
2676
- // sub-agent rollout が増えても root を取り違えないよう、未bind時は最古を選べる順にする。
2677
2602
  return files.sort();
2678
2603
  }
2604
+ function codexTranscriptMatchesLaunch(file, meta) {
2605
+ const createdAt = Date.parse(meta.created_at);
2606
+ try {
2607
+ const st = fs.statSync(file);
2608
+ if (Number.isFinite(createdAt) && st.mtimeMs + 5_000 < createdAt)
2609
+ return false;
2610
+ }
2611
+ catch {
2612
+ return false;
2613
+ }
2614
+ const size = Math.min(safeStatSize(file), 1024 * 1024);
2615
+ if (size === 0)
2616
+ return false;
2617
+ const marker = `AITERM_AGENT_LAUNCH_ID=${meta.launch_id}`;
2618
+ let rootCli = false;
2619
+ let launchMarker = false;
2620
+ for (const line of readFileRange(file, 0, size).toString("utf8").split("\n")) {
2621
+ if (!line.trim())
2622
+ continue;
2623
+ let record;
2624
+ try {
2625
+ record = JSON.parse(line);
2626
+ }
2627
+ catch {
2628
+ continue;
2629
+ }
2630
+ if (record?.type === "session_meta") {
2631
+ rootCli = record?.payload?.originator === "codex-tui" && record?.payload?.source === "cli";
2632
+ if (!rootCli)
2633
+ return false;
2634
+ }
2635
+ if (record?.type === "response_item" &&
2636
+ record?.payload?.type === "message" &&
2637
+ record?.payload?.role === "developer" &&
2638
+ Array.isArray(record?.payload?.content)) {
2639
+ launchMarker ||= record.payload.content.some((item) => (item?.type === "input_text" || item?.type === "output_text") &&
2640
+ typeof item?.text === "string" &&
2641
+ item.text.includes(marker));
2642
+ }
2643
+ if (rootCli && launchMarker)
2644
+ return true;
2645
+ }
2646
+ return false;
2647
+ }
2679
2648
  function codexTranscriptSessionId(file) {
2680
2649
  const size = Math.min(safeStatSize(file), AGENT_EVENT_TAIL_BYTES);
2681
2650
  if (size === 0)
@@ -2701,6 +2670,13 @@ function codexRootTranscript(meta) {
2701
2670
  return null;
2702
2671
  if (meta.vendor_session_id)
2703
2672
  return findLatestCodexTranscript(meta.codex_home, meta.vendor_session_id);
2673
+ if (meta.hook_route === "shared_codex_home") {
2674
+ const matches = listCodexTranscripts(meta.codex_home).filter((file) => codexTranscriptMatchesLaunch(file, meta));
2675
+ if (matches.length > 1) {
2676
+ throw new AitermError("共有CODEX_HOMEに同じlaunch markerのroot rolloutが複数あります。sessionを閉じて起動し直してください。", 2);
2677
+ }
2678
+ return matches[0] ?? null;
2679
+ }
2704
2680
  return listCodexTranscripts(meta.codex_home)[0] ?? null;
2705
2681
  }
2706
2682
  function bindCodexTranscriptSession(meta) {
@@ -2752,7 +2728,84 @@ function latestCodexCompletion(meta) {
2752
2728
  }
2753
2729
  return latest;
2754
2730
  }
2731
+ function grokSessionDirectory(meta) {
2732
+ if ((meta.kind !== "grok" && meta.kind !== "composer") || !meta.grok_home || !meta.vendor_session_id)
2733
+ return null;
2734
+ const cwd = meta.cwd ?? process.cwd();
2735
+ return path.join(meta.grok_home, "sessions", encodeURIComponent(cwd), meta.vendor_session_id);
2736
+ }
2737
+ function grokEventsTranscript(meta) {
2738
+ const dir = grokSessionDirectory(meta);
2739
+ return dir ? path.join(dir, "events.jsonl") : null;
2740
+ }
2741
+ function grokCompletionEvent(meta, record) {
2742
+ if ((meta.kind !== "grok" && meta.kind !== "composer") ||
2743
+ record?.type !== "turn_ended" ||
2744
+ (record?.outcome !== "completed" && record?.outcome !== "cancelled"))
2745
+ return null;
2746
+ const turnId = typeof record?.ts === "string" || typeof record?.ts === "number" ? String(record.ts) : null;
2747
+ return {
2748
+ type: "agent_done",
2749
+ vendor: meta.kind,
2750
+ aiterm_session: meta.aiterm_session,
2751
+ launch_id: meta.launch_id,
2752
+ vendor_session_id: meta.vendor_session_id,
2753
+ turn_id: turnId,
2754
+ operation_id: null,
2755
+ reason: `Grok transcript turn_ended:${record.outcome}`,
2756
+ done_status: "turn_done",
2757
+ stop_hook_active: false,
2758
+ at: typeof record?.ts === "string" ? record.ts : new Date().toISOString(),
2759
+ };
2760
+ }
2761
+ function latestGrokCompletion(meta) {
2762
+ const transcript = grokEventsTranscript(meta);
2763
+ if (!transcript || !fs.existsSync(transcript))
2764
+ return null;
2765
+ let latest = null;
2766
+ for (const line of readTranscriptLines(transcript)) {
2767
+ if (!line.trim())
2768
+ continue;
2769
+ try {
2770
+ latest = grokCompletionEvent(meta, JSON.parse(line)) ?? latest;
2771
+ }
2772
+ catch {
2773
+ // 末尾書込み中のlineは次の観測で完結してから読む。
2774
+ }
2775
+ }
2776
+ return latest;
2777
+ }
2778
+ function grokInitializationComplete(meta) {
2779
+ const transcript = grokEventsTranscript(meta);
2780
+ if (!transcript || !fs.existsSync(transcript))
2781
+ return false;
2782
+ const size = safeStatSize(transcript);
2783
+ const from = Math.max(0, size - GROK_TRANSCRIPT_INCREMENT_MAX_BYTES);
2784
+ const lines = readFileRange(transcript, from, size).toString("utf8").split("\n");
2785
+ if (from > 0)
2786
+ lines.shift();
2787
+ for (let index = lines.length - 1; index >= 0; index -= 1) {
2788
+ const line = lines[index].trim();
2789
+ if (!line)
2790
+ continue;
2791
+ try {
2792
+ const record = JSON.parse(line);
2793
+ if (record?.type === "mcp_init_completed")
2794
+ return true;
2795
+ if (record?.type === "mcp_init_started")
2796
+ return false;
2797
+ }
2798
+ catch {
2799
+ // 末尾書込み中や無関係な破損lineはready判定を進めない。
2800
+ }
2801
+ }
2802
+ return false;
2803
+ }
2755
2804
  function agentCompletionCursor(meta) {
2805
+ if ((meta.kind === "grok" || meta.kind === "composer") && meta.completion_route === "grok_transcript") {
2806
+ const transcript = grokEventsTranscript(meta);
2807
+ return transcript ? safeStatSize(transcript) : 0;
2808
+ }
2756
2809
  if (meta.kind !== "codex")
2757
2810
  return safeStatSize(meta.event_file);
2758
2811
  const transcript = bindCodexTranscriptSession(meta);
@@ -3116,6 +3169,80 @@ async function observeCodexDone(meta, timeout, requestedCursor) {
3116
3169
  await sleep(AGENT_DONE_POLL_MS);
3117
3170
  }
3118
3171
  }
3172
+ async function observeGrokDone(meta, timeout, requestedCursor) {
3173
+ const metadataFile = agentMetadataPath(meta.aiterm_session, meta.launch_id);
3174
+ const transcript = grokEventsTranscript(meta);
3175
+ if (!transcript)
3176
+ throw new AitermError("Grok transcriptのsession相関情報がありません", 2);
3177
+ const startOffset = requestedCursor ?? safeStatSize(transcript);
3178
+ let cursor = startOffset;
3179
+ let carry = "";
3180
+ let malformedEvents = 0;
3181
+ let discardLeadingFragment = false;
3182
+ let initializedBoundary = false;
3183
+ const deadline = performance.now() + timeout * 1000;
3184
+ const observation = (outcome, ev = null) => ({
3185
+ schema: "aiterm.agent-wait-result.v1",
3186
+ session_id: meta.aiterm_session,
3187
+ launch_id: meta.launch_id,
3188
+ vendor: meta.kind,
3189
+ outcome,
3190
+ operation_id: null,
3191
+ vendor_session_id: meta.vendor_session_id,
3192
+ turn_id: ev?.turn_id ?? null,
3193
+ malformed_events: malformedEvents,
3194
+ at: ev?.at ?? null,
3195
+ });
3196
+ for (;;) {
3197
+ if (!fs.existsSync(metadataFile))
3198
+ return observation("closed");
3199
+ if (fs.existsSync(transcript)) {
3200
+ if (!initializedBoundary) {
3201
+ if (cursor > 0) {
3202
+ const previous = readFileRange(transcript, cursor - 1, cursor).toString("utf8");
3203
+ discardLeadingFragment = previous !== "\n";
3204
+ }
3205
+ initializedBoundary = true;
3206
+ }
3207
+ const size = safeStatSize(transcript);
3208
+ if (size < cursor) {
3209
+ throw new AitermError("Grok transcript が完了待機中に短くなりました。該当セッションを閉じて起動し直してください。", 2);
3210
+ }
3211
+ if (size - startOffset > GROK_TRANSCRIPT_INCREMENT_MAX_BYTES) {
3212
+ throw new AitermError("Grok transcript のturn増分が大きすぎます。該当セッションを閉じて起動し直してください。", 2);
3213
+ }
3214
+ if (size > cursor) {
3215
+ carry += readFileRange(transcript, cursor, size).toString("utf8");
3216
+ cursor = size;
3217
+ const parts = carry.split("\n");
3218
+ carry = parts.pop() ?? "";
3219
+ if (discardLeadingFragment && parts.length > 0) {
3220
+ parts.shift();
3221
+ discardLeadingFragment = false;
3222
+ }
3223
+ for (const line of parts) {
3224
+ if (!line.trim())
3225
+ continue;
3226
+ if (Buffer.byteLength(line, "utf8") > AGENT_EVENT_MAX_BYTES) {
3227
+ malformedEvents++;
3228
+ continue;
3229
+ }
3230
+ try {
3231
+ const done = grokCompletionEvent(meta, JSON.parse(line));
3232
+ if (done)
3233
+ return observation("done", done);
3234
+ }
3235
+ catch {
3236
+ malformedEvents++;
3237
+ }
3238
+ }
3239
+ }
3240
+ }
3241
+ if (performance.now() >= deadline)
3242
+ return observation(timeout === 0 ? "running" : "timeout");
3243
+ await sleep(AGENT_DONE_POLL_MS);
3244
+ }
3245
+ }
3119
3246
  // 外部waiterプロセス用の純リーダー観測。lock・PTY・metadata書込・dispatch状態には一切触れない。
3120
3247
  // Codexはrollout transcript、他vendorはevent fileを増分走査し、vendor_session_idのbind永続化を
3121
3248
  // 行わない(waiterは観測者であって所有者でない)。
@@ -3132,6 +3259,9 @@ export async function observeAgentDone(name, o = {}) {
3132
3259
  if (meta.kind === "codex" && meta.completion_route === "codex_transcript") {
3133
3260
  return observeCodexDone(meta, timeout, o.cursor);
3134
3261
  }
3262
+ if ((meta.kind === "grok" || meta.kind === "composer") && meta.completion_route === "grok_transcript") {
3263
+ return observeGrokDone(meta, timeout, o.cursor);
3264
+ }
3135
3265
  const metadataFile = agentMetadataPath(meta.aiterm_session, meta.launch_id);
3136
3266
  // 境界の優先順: dispatch receipt の event_cursor(起動順序に依存しない)→ operation相関
3137
3267
  // (operation_idの一意性で先頭から全走査できる)→ waiter起動時EOF(waiter先行起動が前提)。
@@ -3190,7 +3320,10 @@ function isAgentTuiReady(kind, screen) {
3190
3320
  if (kind === "codex") {
3191
3321
  return screen.includes("OpenAI Codex") && /(^|\n)\s*[›>]/.test(screen);
3192
3322
  }
3193
- return screen.includes("Grok Build") && /(^|\n|\s)❯/.test(screen);
3323
+ // Grok Build 0.2.117 は起動完了後に製品名を消し、model footerだけを残す。
3324
+ // Composerも同じfrontendでmodel名だけが異なるため、両方をvendor UIの根拠にする。
3325
+ const grokFrontend = screen.includes("Grok Build") || /\b(?:Grok|Composer)\s+[\w.()-]+/.test(screen);
3326
+ return grokFrontend && /(^|\n|\s)❯/.test(screen);
3194
3327
  }
3195
3328
  // Codex/Claude は実行中に「(esc to interrupt)」を表示する(実機採取)。startup 側の処理
3196
3329
  // (MCP initialize 等)が走ったまま composer だけ描画されている画面は入力受付とみなさない。
@@ -3229,7 +3362,11 @@ async function waitAgentTuiReadyImpl(kind, sample, sleepFn, opts = {}) {
3229
3362
  }
3230
3363
  }
3231
3364
  async function waitAgentTuiReady(name, meta, timeoutMs = AGENT_TUI_READY_TIMEOUT_MS) {
3232
- return waitAgentTuiReadyImpl(meta.kind, () => captureScreen(name, AGENT_TUI_READY_LINES), sleep, { timeoutMs });
3365
+ return waitAgentTuiReadyImpl(meta.kind, () => {
3366
+ if ((meta.kind === "grok" || meta.kind === "composer") && !grokInitializationComplete(meta))
3367
+ return "";
3368
+ return captureScreen(name, AGENT_TUI_READY_LINES);
3369
+ }, sleep, { timeoutMs });
3233
3370
  }
3234
3371
  async function waitAgentTuiReadyByKind(name, kind, timeoutMs = AGENT_TUI_READY_TIMEOUT_MS) {
3235
3372
  return waitAgentTuiReadyImpl(kind, () => captureScreen(name, AGENT_TUI_READY_LINES), sleep, { timeoutMs });
@@ -3433,9 +3570,10 @@ export async function dispatchAgentTurn(name, text, o = {}) {
3433
3570
  throw new AitermError("operation_id はClaude agent sessionだけで使用できます", 2);
3434
3571
  }
3435
3572
  bindCompletedInitialPrompt(meta);
3436
- // Codexはbind済みのfollow-upでも毎回idleを確認してからtranscript境界を切る。同じcursorへ
3437
- // 複数turnを帰属させる余地を作らない。他vendorの既存dispatch条件は変えない。
3438
- if (meta.kind === "codex" || !meta.vendor_session_id) {
3573
+ // Codex/Grok/Composerはbind済みのfollow-upでも毎回idleを確認してからtranscript境界を切る。
3574
+ // Grok/Composerはsession IDが起動前から既知でも、共有MCPの初期化完了前には送信しない。
3575
+ // 同じcursorへ複数turnを帰属させる余地や、初期化中TUIへの早送信を作らない。
3576
+ if (meta.kind !== "claude" || !meta.vendor_session_id) {
3439
3577
  const ready = await waitAgentTuiReady(name, meta, o.ready_timeout ?? AGENT_TUI_READY_TIMEOUT_MS);
3440
3578
  if (!ready.ready) {
3441
3579
  throw new AitermError(`agent session '${name}' の ${agentLabel(meta.kind)} TUI が入力受付状態になりません。文字列は送信していません。` +
@@ -3484,7 +3622,7 @@ export async function runClaudeOperation({ session_id: name, action, operation_i
3484
3622
  const operationId = validateOperationId(operationIdInput);
3485
3623
  const meta = loadAgentMetadata(name);
3486
3624
  if (meta.kind !== "claude")
3487
- throw new AitermError("claude_turnはmanaged Claude agent sessionだけで使用できます", 2);
3625
+ throw new AitermError("claude_turnはaiterm相関付きClaude agent sessionだけで使用できます", 2);
3488
3626
  let dispatchReceipt = null;
3489
3627
  if (action === "issue") {
3490
3628
  if (typeof text !== "string" || text.length === 0) {
@@ -3595,7 +3733,7 @@ function assertClaudeAuthenticationReady(bin) {
3595
3733
  status.loggedIn === false) {
3596
3734
  throw new AitermError("Claude Codeの認証を利用できません。sessionは作成していません。" +
3597
3735
  "通常端末で `claude doctor` を実行し、Keychain/credential storeを直してから一度だけ `claude auth login` を実行してください。" +
3598
- "managed Claude session内で /login を繰り返さないでください。", 2);
3736
+ "aiterm相関付きClaude session内で /login を繰り返さないでください。", 2);
3599
3737
  }
3600
3738
  const timedOut = result.error && result.error.code === "ETIMEDOUT";
3601
3739
  throw new AitermError(`Claude Codeの認証状態を起動前に確認できません${timedOut ? "(5秒でtimeout)" : ""}。sessionは作成していません。` +
@@ -3638,7 +3776,7 @@ function buildAgentCmd(kind, bin, model, effort, prompt, meta = null) {
3638
3776
  const parts = [shq(bin)];
3639
3777
  if (kind === "claude") {
3640
3778
  if (meta?.kind === "claude") {
3641
- parts.push("--setting-sources", shq(""), "--settings", shq(meta.claude_settings ?? ""));
3779
+ parts.push("--setting-sources", shq("user,project,local"), "--settings", shq(meta.claude_settings ?? ""), "--session-id", shq(meta.vendor_session_id ?? ""), "--append-system-prompt", shq(subagentInstruction(meta)));
3642
3780
  }
3643
3781
  if (model)
3644
3782
  parts.push("--model", shq(model));
@@ -3650,12 +3788,14 @@ function buildAgentCmd(kind, bin, model, effort, prompt, meta = null) {
3650
3788
  // launcher自身が実効能力壁を作る。パス説明はCodex CLIに同等のallowlist引数がないため宣言のまま残す。
3651
3789
  if (meta?.kind === "codex" && meta.write_scope === "read-only")
3652
3790
  parts.push("--sandbox", "read-only");
3653
- // model/effort は CLI 引数で明示(config 継承より優先)。agent_done 時は managed home 側
3654
- // config.toml も同値で上書き済み(applyCodexConfigOverrides)。
3791
+ // model/effort は共有configを書き換えず、CLI引数で明示して起動単位に優先する。
3655
3792
  if (model)
3656
3793
  parts.push("-m", shq(model));
3657
3794
  if (effort)
3658
3795
  parts.push("-c", `model_reasoning_effort=${shq(effort)}`);
3796
+ if (meta?.kind === "codex" && meta.hook_route === "shared_codex_home") {
3797
+ parts.push("-c", `developer_instructions=${shq(subagentInstruction(meta))}`);
3798
+ }
3659
3799
  }
3660
3800
  else {
3661
3801
  // grok / composer は同じ grok CLI をモデル違いで起動。--effort は headless(grok -p)専用で
@@ -3664,6 +3804,9 @@ function buildAgentCmd(kind, bin, model, effort, prompt, meta = null) {
3664
3804
  if (meta?.kind === "grok" || meta?.kind === "composer")
3665
3805
  parts.push("--no-alt-screen");
3666
3806
  parts.push("--model", shq(model ?? GROK_MODEL_DEFAULTS[kind]));
3807
+ if ((meta?.kind === "grok" || meta?.kind === "composer") && meta.hook_route === "shared_grok_home") {
3808
+ parts.push("--session-id", shq(meta.vendor_session_id ?? ""), "--rules", shq(subagentInstruction(meta)));
3809
+ }
3667
3810
  if ((meta?.kind === "grok" || meta?.kind === "composer") && prompt)
3668
3811
  parts.push("--verbatim");
3669
3812
  }
@@ -3679,29 +3822,21 @@ function agentEnvPrefix(meta, sid) {
3679
3822
  `AITERM_SESSION_ID=${shq(sid)}`,
3680
3823
  `AITERM_AGENT_SESSION_ID=${shq(sid)}`,
3681
3824
  `AITERM_AGENT_LAUNCH_ID=${shq(meta.launch_id)}`,
3825
+ `AITERM_AGENT_ROLE=${shq(meta.agent_role ?? "subagent")}`,
3826
+ `AITERM_AGENT_PARENT_SESSION_ID=${shq(meta.parent_session_id ?? "host-root")}`,
3827
+ `AITERM_AGENT_DEPTH=${shq(String(meta.delegation_depth ?? 1))}`,
3828
+ `AITERM_AGENT_LINEAGE=${shq(meta.lineage ?? `host-root>${meta.kind}:${sid}`)}`,
3829
+ `AITERM_AGENT_DELEGATION_ALLOWED=${shq(meta.delegation_allowed === true ? "true" : "false")}`,
3682
3830
  ];
3683
3831
  if (meta.kind === "claude") {
3684
3832
  return common.join(" ") + " ";
3685
3833
  }
3686
3834
  if (meta.kind === "codex") {
3687
- return [`CODEX_HOME=${shq(meta.codex_home ?? "")}`, ...common].join(" ") + " ";
3835
+ return common.join(" ") + " ";
3688
3836
  }
3689
3837
  return [
3690
- `HOME=${shq(meta.home ?? "")}`,
3691
- `GROK_HOME=${shq(meta.grok_home ?? "")}`,
3692
3838
  ...(meta.grok_auth_path ? [`GROK_AUTH_PATH=${shq(meta.grok_auth_path)}`] : []),
3693
3839
  "GROK_DISABLE_AUTOUPDATER=1",
3694
- "GROK_CLAUDE_HOOKS_ENABLED=false",
3695
- "GROK_CURSOR_HOOKS_ENABLED=false",
3696
- "GROK_CLAUDE_SKILLS_ENABLED=false",
3697
- "GROK_CURSOR_SKILLS_ENABLED=false",
3698
- "GROK_CLAUDE_RULES_ENABLED=false",
3699
- "GROK_CURSOR_RULES_ENABLED=false",
3700
- "GROK_CLAUDE_AGENTS_ENABLED=false",
3701
- "GROK_CURSOR_AGENTS_ENABLED=false",
3702
- "GROK_CLAUDE_MCPS_ENABLED=false",
3703
- "GROK_CURSOR_MCPS_ENABLED=false",
3704
- `AITERM_REAL_HOME=${shq(process.env.HOME ?? os.homedir())}`,
3705
3840
  ...common,
3706
3841
  ].join(" ") + " ";
3707
3842
  }
@@ -3746,7 +3881,7 @@ function buildAgentLaunchNote(kind, model, effort, meta) {
3746
3881
  (effectiveEffort === "ultra"
3747
3882
  ? "⚠ effort=ultra は max 推論+proactive 自動委譲 ON(子エージェント自動生成・使用量急増に注意)。"
3748
3883
  : "");
3749
- const summary = meta?.kind === "codex" && meta.codex_home ? managedCodexConfigSummary(configPath) : "";
3884
+ const summary = meta?.kind === "codex" && meta.codex_home ? codexConfigSummary(configPath) : "";
3750
3885
  return (summary ? `${launch}\n${summary}\n` : launch) + writeScopeNote;
3751
3886
  }
3752
3887
  function claudeLaunchRequestDigest({ sessionName, model, effort, cwd, agentDone, }) {
@@ -3824,6 +3959,8 @@ export function openAgent(kind, opts = {}) {
3824
3959
  throw new AitermError("launch_operation_id付きlaunchにpromptは指定できません", 2);
3825
3960
  }
3826
3961
  }
3962
+ // 継承lineageの破損はsession作成前にfail loudする。root callerにはhost-root/depth 1を割り当てる。
3963
+ const lineageSeed = readAgentLineageSeed();
3827
3964
  let bin;
3828
3965
  try {
3829
3966
  bin = resolveAgentBin(kind);
@@ -3894,12 +4031,13 @@ export function openAgent(kind, opts = {}) {
3894
4031
  }
3895
4032
  let launchNote = "";
3896
4033
  try {
4034
+ const lineageContext = createAgentLineageContext(kind, sid, lineageSeed);
3897
4035
  const meta = agentDone
3898
4036
  ? kind === "claude"
3899
- ? createClaudeAgentMetadata(sid, cwd, opts.prompt ? "pending" : "none", launchOperationId, launchRequestDigest)
4037
+ ? createClaudeAgentMetadata(sid, cwd, opts.prompt ? "pending" : "none", launchOperationId, launchRequestDigest, lineageContext)
3900
4038
  : kind === "codex"
3901
- ? createCodexAgentMetadata(sid, cwd, opts.prompt ? "pending" : "none", { model, effort }, writeScope)
3902
- : createGrokAgentMetadata(kind, sid, cwd, opts.prompt ? "pending" : "none", grokAuthPath, writeScope)
4039
+ ? createCodexAgentMetadata(sid, cwd, opts.prompt ? "pending" : "none", { model, effort }, writeScope, lineageContext)
4040
+ : createGrokAgentMetadata(kind, sid, cwd, opts.prompt ? "pending" : "none", grokAuthPath, writeScope, lineageContext)
3903
4041
  : null;
3904
4042
  if (meta)
3905
4043
  agentMetadataNegativeCache.delete(sid);
@@ -3939,7 +4077,7 @@ export function openAgent(kind, opts = {}) {
3939
4077
  return [
3940
4078
  sid,
3941
4079
  `${label} を session ${sid} で起動した。${launchNote}${agentDone ? "agent_done 待機が有効。" : ""}` +
3942
- `${agentDone && (kind === "grok" || kind === "composer") ? " hook 汚染防止のため Grok 実行中は一時 HOME を使う。" : ""}\n${hint}\n` +
4080
+ `${agentDone ? " 通常のproject/user環境を共有し、aiterm所有の完了相関とsub-agent lineageだけを加算。" : ""}\n${hint}\n` +
3943
4081
  driveHint +
3944
4082
  `起動直後に増分 pty_read すると空/半描画になり得るので screen:true を使う。`,
3945
4083
  ];
@@ -3947,7 +4085,7 @@ export function openAgent(kind, opts = {}) {
3947
4085
  export async function openAgentWithInitialPrompt(kind, opts = {}) {
3948
4086
  const prompt = opts.prompt ?? null;
3949
4087
  if (opts.launch_operation_id != null && prompt !== null) {
3950
- throw new AitermError("launch_operation_idはpromptなしのmanaged Claude launchだけで指定できます", 2);
4088
+ throw new AitermError("launch_operation_idはpromptなしのClaude相関launchだけで指定できます", 2);
3951
4089
  }
3952
4090
  // v0.16.0: launcher は常に managed(Stop hook つき)で立つ。手動運転したい場合は
3953
4091
  // pty_open で素の PTY を開き、vendor CLI を自分で send する。