aiterm-mcp 0.21.4 → 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;
@@ -412,12 +415,6 @@ function agentManagedClaudeSettingsPath(name, launchId) {
412
415
  throw new AitermError(`launch_id が不正です: ${launchId}`, 2);
413
416
  return path.join(agentsDir(), `${name}.${launchId}.claude-settings.json`);
414
417
  }
415
- function agentManagedClaudeMcpConfigPath(name, launchId) {
416
- assertSessionName(name);
417
- if (!LAUNCH_ID_RE.test(launchId))
418
- throw new AitermError(`launch_id が不正です: ${launchId}`, 2);
419
- return path.join(agentsDir(), `${name}.${launchId}.claude-mcp.json`);
420
- }
421
418
  function agentClaudeResultPath(name, launchId) {
422
419
  assertSessionName(name);
423
420
  if (!LAUNCH_ID_RE.test(launchId))
@@ -443,24 +440,6 @@ function agentClaudeDispatchReceiptPath(name, launchId, operationId) {
443
440
  const validated = validateOperationId(operationId);
444
441
  return path.join(agentsDir(), `${name}.${launchId}.${validated.slice("sha256:".length)}.claude-dispatch`);
445
442
  }
446
- function agentManagedCodexHomePath(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}.codex-home`);
451
- }
452
- function agentManagedGrokHomePath(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}.grok-home`);
457
- }
458
- function agentManagedGrokUserHomePath(name, launchId) {
459
- assertSessionName(name);
460
- if (!LAUNCH_ID_RE.test(launchId))
461
- throw new AitermError(`launch_id が不正です: ${launchId}`, 2);
462
- return path.join(agentsDir(), `${name}.${launchId}.home`);
463
- }
464
443
  function existingAgentsDir() {
465
444
  if (typeof process.getuid !== "function")
466
445
  return null;
@@ -960,7 +939,7 @@ function assertManagedClaudeCredentialCommandNotSent(name, text) {
960
939
  const normalized = text.replace(PASTE_MARKERS_RE, "").replace(ANSI_RE, "").replace(CTRL_RE, "").trim();
961
940
  if (!/^\/(?:login|logout)$/i.test(normalized))
962
941
  return;
963
- throw new AitermError("managed Claude sessionでは共有認証を変更する /login と /logout を送信できません。" +
942
+ throw new AitermError("aiterm相関付きClaude sessionでは共有認証を変更する /login と /logout を送信できません。" +
964
943
  "認証操作は通常端末で一度だけ行い、必要ならこのsessionをcloseして起動し直してください。", 2);
965
944
  }
966
945
  export function send(name, text, o = {}) {
@@ -986,7 +965,7 @@ export function send(name, text, o = {}) {
986
965
  // 拒否した呼び出しが古いmarkを消したり偽のmarkを残すと、後続readが存在しない
987
966
  // sentinelを待つため、公開上の拒否は副作用ゼロでなければならない。
988
967
  if (!o.preserveAgentOperation && managedClaudeOperation(name) !== undefined) {
989
- throw new AitermError("managed Claude agent sessionへの通常送信はturn境界を失うため拒否します。" +
968
+ throw new AitermError("aiterm相関付きClaude agent sessionへの通常送信はturn境界を失うため拒否します。" +
990
969
  "pty_send(forceなし=自動dispatch)を使うか、通常対話へ切り替えるならsessionをcloseしてpty_openから手動起動し直してください。", 2);
991
970
  }
992
971
  writeLastcmd(name, text); // read rtk の reducer 分類用(書換/mark 前の素のコマンド)
@@ -1071,7 +1050,7 @@ export function sendKey(name, key, o = {}) {
1071
1050
  throw new AitermError(`session '${name}' が無い`, 2);
1072
1051
  const k = KEYMAP[key.toLowerCase()] ?? key;
1073
1052
  if (!o.preserveAgentOperation && managedClaudeOperation(name) !== undefined && k !== "C-c") {
1074
- 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で送れます。" +
1075
1054
  "他の対話操作はpty_send(自動dispatch)、終了はpty_closeを使ってください。", 2);
1076
1055
  }
1077
1056
  tmux("send-keys", "-t", name, k);
@@ -1384,12 +1363,80 @@ export function killAll() {
1384
1363
  agentMetadataNegativeCache.clear();
1385
1364
  return "killed all sessions on this socket";
1386
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
+ }
1387
1437
  const DEFAULT_AGENT_DONE_TIMEOUT = 600;
1388
1438
  const agentMetadataNegativeCache = new Map();
1389
1439
  let agentTuiReadyStableSamplesTestOverride = null;
1390
- function grokHookScriptPath() {
1391
- return path.join(path.dirname(fileURLToPath(import.meta.url)), "grok-stop-hook.js");
1392
- }
1393
1440
  function claudeHookScriptPath() {
1394
1441
  return path.join(path.dirname(fileURLToPath(import.meta.url)), "claude-stop-hook.js");
1395
1442
  }
@@ -1472,36 +1519,6 @@ function createEmpty0600NoFollow(p) {
1472
1519
  function realCodexHome() {
1473
1520
  return process.env.CODEX_HOME || path.join(process.env.HOME ?? os.homedir(), ".codex");
1474
1521
  }
1475
- // 引数で model/effort を渡された時は managed config のピンを上書きする(端末 config の
1476
- // model/model_reasoning_effort ピン——例: effort=ultra は proactive 自動委譲 ON——が対話子へ
1477
- // 黙って波及するのを防ぐ)。TOML の top-level キーは最初のテーブルヘッダより前にしか置けないため、
1478
- // 既存行の除去は先頭領域に限定し、上書き行はファイル先頭へ置く。
1479
- function applyCodexConfigOverrides(body, overrides) {
1480
- const lines = [];
1481
- if (overrides.model)
1482
- lines.push(`model = ${JSON.stringify(overrides.model)}`);
1483
- if (overrides.effort)
1484
- lines.push(`model_reasoning_effort = ${JSON.stringify(overrides.effort)}`);
1485
- if (!lines.length)
1486
- return body;
1487
- if (body == null)
1488
- return lines.join("\n") + "\n";
1489
- const rows = body.split(/\r?\n/);
1490
- let firstTable = rows.findIndex((l) => /^\s*\[/.test(l));
1491
- if (firstTable === -1)
1492
- firstTable = rows.length;
1493
- const head = rows.slice(0, firstTable).filter((l) => {
1494
- if (overrides.model && /^\s*(?:"model"|'model'|model)\s*=/.test(l))
1495
- return false;
1496
- if (overrides.effort && /^\s*(?:"model_reasoning_effort"|'model_reasoning_effort'|model_reasoning_effort)\s*=/.test(l))
1497
- return false;
1498
- return true;
1499
- });
1500
- let out = [...lines, ...head, ...rows.slice(firstTable)].join("\n");
1501
- if (!out.endsWith("\n"))
1502
- out += "\n";
1503
- return out;
1504
- }
1505
1522
  function readCodexConfigPins(configPath) {
1506
1523
  let body;
1507
1524
  try {
@@ -1526,7 +1543,7 @@ function readCodexConfigPins(configPath) {
1526
1543
  };
1527
1544
  return { model: pick("model"), effort: pick("model_reasoning_effort") };
1528
1545
  }
1529
- function managedCodexConfigSummary(configPath) {
1546
+ function codexConfigSummary(configPath) {
1530
1547
  let body;
1531
1548
  try {
1532
1549
  body = fs.readFileSync(configPath, "utf8");
@@ -1555,93 +1572,9 @@ function managedCodexConfigSummary(configPath) {
1555
1572
  bits.push(`approval_policy=${approvalPolicy}`);
1556
1573
  if (sandboxMode)
1557
1574
  bits.push(`sandbox_mode=${sandboxMode}`);
1558
- return `managed config: ${bits.join(" / ")}`;
1559
- }
1560
- // Custom agent definitions are configuration, not mutable Codex state. Copy only direct
1561
- // agents/*.toml entries into the per-launch home so role discovery matches the source home while
1562
- // sessions/cache remain isolated. Source symlinks are resolved and their contents are snapshotted;
1563
- // the managed home never points back to the source definition.
1564
- function snapshotCodexAgentDefinitions(srcHome, managedHome) {
1565
- const srcDir = path.join(srcHome, "agents");
1566
- let srcDirSt;
1567
- try {
1568
- srcDirSt = fs.statSync(srcDir);
1569
- }
1570
- catch (e) {
1571
- if (e.code === "ENOENT")
1572
- return;
1573
- throw e;
1574
- }
1575
- if (!srcDirSt.isDirectory()) {
1576
- throw new AitermError(`Codex agents が directory ではありません: ${srcDir}`, 2);
1577
- }
1578
- const names = fs.readdirSync(srcDir).filter((name) => name.endsWith(".toml")).sort();
1579
- if (!names.length)
1580
- return;
1581
- const dstDir = path.join(managedHome, "agents");
1582
- fs.mkdirSync(dstDir, { mode: 0o700 });
1583
- fs.chmodSync(dstDir, 0o700);
1584
- for (const name of names) {
1585
- const src = path.join(srcDir, name);
1586
- const resolved = fs.realpathSync(src);
1587
- const st = fs.statSync(resolved);
1588
- if (!st.isFile()) {
1589
- throw new AitermError(`Codex agent definition が通常ファイルではありません: ${src}`, 2);
1590
- }
1591
- writeText0600(path.join(dstDir, name), fs.readFileSync(resolved, "utf8"));
1592
- }
1593
- }
1594
- function createManagedCodexHome(name, launchId, overrides = {}) {
1595
- const srcHome = realCodexHome();
1596
- let srcSt;
1597
- try {
1598
- srcSt = fs.statSync(srcHome);
1599
- }
1600
- catch {
1601
- throw new AitermError(`Codex home が見つかりません: ${srcHome}`, 2);
1602
- }
1603
- if (!srcSt.isDirectory())
1604
- throw new AitermError(`Codex home が directory ではありません: ${srcHome}`, 2);
1605
- const managedHome = agentManagedCodexHomePath(name, launchId);
1606
- fs.mkdirSync(managedHome, { recursive: false, mode: 0o700 });
1607
- fs.chmodSync(managedHome, 0o700);
1608
- const authSrc = path.join(srcHome, "auth.json");
1609
- try {
1610
- const st = fs.statSync(authSrc);
1611
- if (!st.isFile())
1612
- throw new AitermError(`Codex auth.json が通常ファイルではありません: ${authSrc}`, 2);
1613
- }
1614
- catch (e) {
1615
- if (e.code === "ENOENT") {
1616
- throw new AitermError(`Codex auth.json が見つかりません。先に codex login が必要です: ${srcHome}`, 2);
1617
- }
1618
- throw e;
1619
- }
1620
- fs.symlinkSync(authSrc, path.join(managedHome, "auth.json"));
1621
- const configSrc = path.join(srcHome, "config.toml");
1622
- let configBody = null;
1623
- try {
1624
- const st = fs.statSync(configSrc);
1625
- if (st.isFile())
1626
- configBody = fs.readFileSync(configSrc, "utf8");
1627
- }
1628
- catch (e) {
1629
- if (e.code !== "ENOENT")
1630
- throw e;
1631
- }
1632
- const configOut = applyCodexConfigOverrides(configBody, overrides);
1633
- if (configOut != null) {
1634
- const configDst = path.join(managedHome, "config.toml");
1635
- fs.writeFileSync(configDst, configOut, { mode: 0o600 });
1636
- fs.chmodSync(configDst, 0o600);
1637
- }
1638
- snapshotCodexAgentDefinitions(srcHome, managedHome);
1639
- // Codex 自身の rollout transcript に task_complete が永続化されるため、完了検出用の
1640
- // Stop hook は作らない。hook と transcript の二重正本、および hook failure の単一障害点を
1641
- // ここでなくす。
1642
- return managedHome;
1575
+ return `共有 config: ${bits.join(" / ")}`;
1643
1576
  }
1644
- function createManagedClaudeSettings(name, launchId) {
1577
+ function createClaudeCorrelationSettings(name, launchId) {
1645
1578
  const hookScript = claudeHookScriptPath();
1646
1579
  if (!fs.existsSync(hookScript)) {
1647
1580
  throw new AitermError(`Claude Stop hook wrapper が見つかりません。npm run build を実行してください: ${hookScript}`, 2);
@@ -1664,52 +1597,6 @@ function createManagedClaudeSettings(name, launchId) {
1664
1597
  });
1665
1598
  return settings;
1666
1599
  }
1667
- function createManagedClaudeMcpConfig(name, launchId) {
1668
- const home = process.env.HOME?.trim() || os.homedir();
1669
- const source = path.join(home, ".claude.json");
1670
- let canonical;
1671
- try {
1672
- canonical = fs.realpathSync(source);
1673
- }
1674
- catch (error) {
1675
- if (error.code === "ENOENT")
1676
- return null;
1677
- throw new AitermError(`Claude user MCP設定を読めません: ${error.message}`, 2);
1678
- }
1679
- let fd;
1680
- let parsed;
1681
- try {
1682
- fd = fs.openSync(canonical, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW | fs.constants.O_NONBLOCK);
1683
- const st = fs.fstatSync(fd);
1684
- if (!st.isFile() || st.uid !== currentUid() || st.size > 16 * 1024 * 1024) {
1685
- throw new AitermError("Claude user MCP設定の安全検証に失敗しました", 2);
1686
- }
1687
- parsed = JSON.parse(fs.readFileSync(fd, "utf8"));
1688
- }
1689
- catch (error) {
1690
- if (error instanceof AitermError)
1691
- throw error;
1692
- throw new AitermError(`Claude user MCP設定を読めません: ${error.message}`, 2);
1693
- }
1694
- finally {
1695
- if (fd !== undefined)
1696
- fs.closeSync(fd);
1697
- }
1698
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
1699
- throw new AitermError("Claude user MCP設定のtop-level JSONはobjectである必要があります", 2);
1700
- }
1701
- const servers = parsed.mcpServers;
1702
- if (servers === undefined)
1703
- return null;
1704
- if (!servers || typeof servers !== "object" || Array.isArray(servers)) {
1705
- throw new AitermError("Claude user MCP設定のmcpServersはobjectである必要があります", 2);
1706
- }
1707
- if (Object.keys(servers).length === 0)
1708
- return null;
1709
- const destination = agentManagedClaudeMcpConfigPath(name, launchId);
1710
- writeJson0600(destination, { mcpServers: servers });
1711
- return destination;
1712
- }
1713
1600
  function realGrokHome() {
1714
1601
  return path.resolve(process.env.GROK_HOME || path.join(process.env.HOME ?? os.homedir(), ".grok"));
1715
1602
  }
@@ -1767,64 +1654,6 @@ function resolveAndValidateGrokAuth(srcHome) {
1767
1654
  fs.closeSync(fd);
1768
1655
  }
1769
1656
  }
1770
- function writeManagedGrokConfig(grokHome) {
1771
- fs.mkdirSync(path.join(grokHome, "hooks"), { recursive: false, mode: 0o700 });
1772
- writeText0600(path.join(grokHome, "config.toml"), [
1773
- "[cli]",
1774
- "auto_update = false",
1775
- "",
1776
- "[features]",
1777
- "remote_fetch = false",
1778
- "managed_config = false",
1779
- "",
1780
- "[compat.claude]",
1781
- "skills = false",
1782
- "rules = false",
1783
- "agents = false",
1784
- "mcps = false",
1785
- "hooks = false",
1786
- "",
1787
- "[compat.cursor]",
1788
- "skills = false",
1789
- "rules = false",
1790
- "agents = false",
1791
- "mcps = false",
1792
- "hooks = false",
1793
- "",
1794
- ].join("\n"));
1795
- }
1796
- function createManagedGrokHome(name, launchId, authPath) {
1797
- const grokHome = agentManagedGrokHomePath(name, launchId);
1798
- const fakeHome = agentManagedGrokUserHomePath(name, launchId);
1799
- fs.mkdirSync(grokHome, { recursive: false, mode: 0o700 });
1800
- fs.mkdirSync(fakeHome, { recursive: false, mode: 0o700 });
1801
- fs.chmodSync(grokHome, 0o700);
1802
- fs.chmodSync(fakeHome, 0o700);
1803
- fs.symlinkSync(grokHome, path.join(fakeHome, ".grok"));
1804
- const hookScript = grokHookScriptPath();
1805
- if (!fs.existsSync(hookScript)) {
1806
- throw new AitermError(`Grok Stop hook wrapper が見つかりません。npm run build を実行してください: ${hookScript}`, 2);
1807
- }
1808
- writeManagedGrokConfig(grokHome);
1809
- writeJson0600(path.join(grokHome, "hooks", "aiterm-stop.json"), {
1810
- hooks: {
1811
- Stop: [
1812
- {
1813
- hooks: [
1814
- {
1815
- type: "command",
1816
- command: nodeHookCommand(hookScript),
1817
- timeout: 10,
1818
- },
1819
- ],
1820
- },
1821
- ],
1822
- },
1823
- });
1824
- // Grok 0.2.87 は compat false でも ~/.claude/plugins の hook file を拾う。HOME を一時化して
1825
- // plugin/hook source を完全に 0 にする。実 HOME は必要なら hook/agent 側で参照できるよう env で渡す。
1826
- return { grokHome, home: fakeHome, authPath };
1827
- }
1828
1657
  function writeAgentMetadata(meta) {
1829
1658
  writeJson0600(agentMetadataPath(meta.aiterm_session, meta.launch_id), meta);
1830
1659
  }
@@ -1959,7 +1788,7 @@ function parseClaudeApprovalScreen(screen) {
1959
1788
  question = i;
1960
1789
  }
1961
1790
  if (question < 0) {
1962
- 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);
1963
1792
  }
1964
1793
  const choices = [];
1965
1794
  const seen = new Set();
@@ -1978,13 +1807,13 @@ function parseClaudeApprovalScreen(screen) {
1978
1807
  if (!decision)
1979
1808
  continue;
1980
1809
  if (!Number.isSafeInteger(index) || index < 1 || seen.has(decision)) {
1981
- throw new AitermError("managed Claudeの承認UI選択肢が一意に解釈できません", 2);
1810
+ throw new AitermError("aiterm相関付きClaudeの承認UI選択肢が一意に解釈できません", 2);
1982
1811
  }
1983
1812
  seen.add(decision);
1984
1813
  choices.push({ decision, index, label });
1985
1814
  }
1986
1815
  if (!seen.has("approve_once") || !seen.has("deny")) {
1987
- throw new AitermError("managed Claudeの承認UIに安全な単発Yes/No選択肢を確認できません", 2);
1816
+ throw new AitermError("aiterm相関付きClaudeの承認UIに安全な単発Yes/No選択肢を確認できません", 2);
1988
1817
  }
1989
1818
  return {
1990
1819
  promptDigest: `sha256:${createHash("sha256").update(canonical, "utf8").digest("hex")}`,
@@ -1994,7 +1823,7 @@ function parseClaudeApprovalScreen(screen) {
1994
1823
  function assertExpectedClaudeOperation(meta, expectedOperationId) {
1995
1824
  const active = readClaudeOperationMarker(meta);
1996
1825
  if (!active)
1997
- throw new AitermError("managed Claudeに未解決のactive operationがありません", 2);
1826
+ throw new AitermError("aiterm相関付きClaudeに未解決のactive operationがありません", 2);
1998
1827
  if (active.operationId !== expectedOperationId) {
1999
1828
  const actual = active.operationId ?? "operation_idなし";
2000
1829
  const expected = expectedOperationId ?? "operation_idなし";
@@ -2008,7 +1837,7 @@ export function runClaudeApproval({ action, session_id: name, operation_id: oper
2008
1837
  throw new AitermError(`session '${name}' が無い`, 2);
2009
1838
  const meta = loadAgentMetadata(name);
2010
1839
  if (meta.kind !== "claude")
2011
- throw new AitermError("claude_approvalはmanaged Claude agent sessionだけで使用できます", 2);
1840
+ throw new AitermError("claude_approvalはaiterm相関付きClaude agent sessionだけで使用できます", 2);
2012
1841
  const operationId = operationIdInput == null ? null : validateOperationId(operationIdInput);
2013
1842
  if (action === "inspect") {
2014
1843
  if (approvalChoice != null || observedPromptDigest != null) {
@@ -2274,14 +2103,13 @@ function liveWaitLocks(name) {
2274
2103
  }
2275
2104
  return out;
2276
2105
  }
2277
- function createClaudeAgentMetadata(name, cwd, initialPrompt, launchOperationId, launchRequestDigest) {
2106
+ function createClaudeAgentMetadata(name, cwd, initialPrompt, launchOperationId, launchRequestDigest, lineageContext) {
2278
2107
  const launchId = randomBytes(16).toString("hex");
2279
2108
  const eventFile = agentEventPath(name, launchId);
2280
2109
  const resultFile = agentClaudeResultPath(name, launchId);
2281
2110
  createEmpty0600NoFollow(eventFile);
2282
2111
  createEmpty0600NoFollow(resultFile);
2283
- const claudeSettings = createManagedClaudeSettings(name, launchId);
2284
- const claudeMcpConfig = createManagedClaudeMcpConfig(name, launchId);
2112
+ const claudeSettings = createClaudeCorrelationSettings(name, launchId);
2285
2113
  const meta = {
2286
2114
  kind: "claude",
2287
2115
  aiterm_session: name,
@@ -2289,24 +2117,24 @@ function createClaudeAgentMetadata(name, cwd, initialPrompt, launchOperationId,
2289
2117
  event_file: eventFile,
2290
2118
  created_at: new Date().toISOString(),
2291
2119
  cwd,
2292
- vendor_session_id: null,
2120
+ vendor_session_id: randomUUID(),
2293
2121
  initial_prompt: initialPrompt,
2294
2122
  launch_operation_id: launchOperationId,
2295
2123
  launch_request_digest: launchRequestDigest,
2296
- hook_route: "managed_claude_settings",
2124
+ hook_route: "shared_claude_settings",
2125
+ ...agentLineageFields(lineageContext),
2297
2126
  node_platform: process.platform,
2298
2127
  claude_settings: claudeSettings,
2299
- claude_mcp_config: claudeMcpConfig,
2300
2128
  result_file: resultFile,
2301
2129
  };
2302
2130
  writeAgentMetadata(meta);
2303
2131
  return meta;
2304
2132
  }
2305
- function createCodexAgentMetadata(name, cwd, initialPrompt, overrides = {}, writeScope) {
2133
+ function createCodexAgentMetadata(name, cwd, initialPrompt, overrides = {}, writeScope, lineageContext) {
2306
2134
  const launchId = randomBytes(16).toString("hex");
2307
2135
  const eventFile = agentEventPath(name, launchId);
2308
2136
  createEmpty0600NoFollow(eventFile);
2309
- const codexHome = createManagedCodexHome(name, launchId, overrides);
2137
+ const codexHome = realCodexHome();
2310
2138
  const meta = {
2311
2139
  kind: "codex",
2312
2140
  aiterm_session: name,
@@ -2317,19 +2145,20 @@ function createCodexAgentMetadata(name, cwd, initialPrompt, overrides = {}, writ
2317
2145
  ...(writeScope === undefined ? {} : { write_scope: writeScope }),
2318
2146
  vendor_session_id: null,
2319
2147
  initial_prompt: initialPrompt,
2320
- hook_route: "managed_codex_home",
2148
+ hook_route: "shared_codex_home",
2321
2149
  completion_route: "codex_transcript",
2150
+ ...(lineageContext ? agentLineageFields(lineageContext) : {}),
2322
2151
  node_platform: process.platform,
2323
2152
  codex_home: codexHome,
2324
2153
  };
2325
2154
  writeAgentMetadata(meta);
2326
2155
  return meta;
2327
2156
  }
2328
- function createGrokAgentMetadata(kind, name, cwd, initialPrompt, authPath, writeScope) {
2157
+ function createGrokAgentMetadata(kind, name, cwd, initialPrompt, authPath, writeScope, lineageContext) {
2329
2158
  const launchId = randomBytes(16).toString("hex");
2330
2159
  const eventFile = agentEventPath(name, launchId);
2331
2160
  createEmpty0600NoFollow(eventFile);
2332
- const managed = createManagedGrokHome(name, launchId, authPath);
2161
+ const grokHome = realGrokHome();
2333
2162
  const meta = {
2334
2163
  kind,
2335
2164
  aiterm_session: name,
@@ -2338,17 +2167,45 @@ function createGrokAgentMetadata(kind, name, cwd, initialPrompt, authPath, write
2338
2167
  created_at: new Date().toISOString(),
2339
2168
  cwd,
2340
2169
  ...(writeScope === undefined ? {} : { write_scope: writeScope }),
2341
- vendor_session_id: null,
2170
+ vendor_session_id: randomUUID(),
2342
2171
  initial_prompt: initialPrompt,
2343
- hook_route: "managed_grok_home",
2172
+ hook_route: "shared_grok_home",
2173
+ completion_route: "grok_transcript",
2174
+ ...(lineageContext ? agentLineageFields(lineageContext) : {}),
2344
2175
  node_platform: process.platform,
2345
- grok_home: managed.grokHome,
2346
- home: managed.home,
2347
- grok_auth_path: managed.authPath,
2176
+ grok_home: grokHome,
2177
+ grok_auth_path: authPath,
2348
2178
  };
2349
2179
  writeAgentMetadata(meta);
2350
2180
  return meta;
2351
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
+ }
2352
2209
  function loadAgentMetadata(name) {
2353
2210
  assertSessionName(name);
2354
2211
  const dir = agentsDir();
@@ -2356,7 +2213,7 @@ function loadAgentMetadata(name) {
2356
2213
  .readdirSync(dir)
2357
2214
  .filter((f) => f.startsWith(`${name}.`) && f.endsWith(".agent.json"));
2358
2215
  if (files.length === 0) {
2359
- 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);
2360
2217
  }
2361
2218
  if (files.length !== 1) {
2362
2219
  throw new AitermError(`session '${name}' の agent metadata が複数あります。閉じて起動し直してください。`, 2);
@@ -2381,14 +2238,13 @@ function loadAgentMetadata(name) {
2381
2238
  }
2382
2239
  if (m.kind === "claude") {
2383
2240
  const expectedSettings = agentManagedClaudeSettingsPath(name, m.launch_id);
2384
- const expectedMcpConfig = agentManagedClaudeMcpConfigPath(name, m.launch_id);
2385
2241
  const expectedResult = agentClaudeResultPath(name, m.launch_id);
2386
- const claudeMcpConfig = m.claude_mcp_config ?? null;
2387
2242
  const launchOperationId = m.launch_operation_id ?? null;
2388
2243
  const launchRequestDigest = m.launch_request_digest ?? null;
2389
- if (m.hook_route !== "managed_claude_settings" ||
2244
+ if (m.hook_route !== "shared_claude_settings" ||
2390
2245
  m.claude_settings !== expectedSettings ||
2391
- (claudeMcpConfig !== null && claudeMcpConfig !== expectedMcpConfig) ||
2246
+ typeof m.vendor_session_id !== "string" ||
2247
+ !UUID_RE.test(m.vendor_session_id) ||
2392
2248
  m.result_file !== expectedResult ||
2393
2249
  ((launchOperationId === null) !== (launchRequestDigest === null)) ||
2394
2250
  (launchOperationId !== null && !OPERATION_ID_RE.test(launchOperationId)) ||
@@ -2403,20 +2259,20 @@ function loadAgentMetadata(name) {
2403
2259
  created_at: typeof m.created_at === "string" ? m.created_at : "",
2404
2260
  cwd: typeof m.cwd === "string" ? m.cwd : null,
2405
2261
  ...(typeof m.write_scope === "string" ? { write_scope: m.write_scope } : {}),
2406
- vendor_session_id: typeof m.vendor_session_id === "string" ? m.vendor_session_id : null,
2262
+ vendor_session_id: m.vendor_session_id,
2407
2263
  initial_prompt: normalizeInitialPromptState(m.initial_prompt),
2408
2264
  launch_operation_id: launchOperationId,
2409
2265
  launch_request_digest: launchRequestDigest,
2410
- hook_route: "managed_claude_settings",
2266
+ hook_route: "shared_claude_settings",
2267
+ ...loadAgentLineageFields(m, true),
2411
2268
  node_platform: process.platform,
2412
2269
  claude_settings: expectedSettings,
2413
- claude_mcp_config: claudeMcpConfig === expectedMcpConfig ? expectedMcpConfig : null,
2414
2270
  result_file: expectedResult,
2415
2271
  };
2416
2272
  }
2417
2273
  if (m.kind === "codex") {
2418
- const expectedHome = agentManagedCodexHomePath(name, m.launch_id);
2419
- 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) {
2420
2276
  throw new AitermError("agent metadata の path が現在の secure state root と一致しません", 2);
2421
2277
  }
2422
2278
  return {
@@ -2429,15 +2285,19 @@ function loadAgentMetadata(name) {
2429
2285
  ...(typeof m.write_scope === "string" ? { write_scope: m.write_scope } : {}),
2430
2286
  vendor_session_id: typeof m.vendor_session_id === "string" ? m.vendor_session_id : null,
2431
2287
  initial_prompt: normalizeInitialPromptState(m.initial_prompt),
2432
- hook_route: "managed_codex_home",
2433
- ...(m.completion_route === "codex_transcript" ? { completion_route: "codex_transcript" } : {}),
2288
+ hook_route: "shared_codex_home",
2289
+ completion_route: "codex_transcript",
2290
+ ...loadAgentLineageFields(m, true),
2434
2291
  node_platform: process.platform,
2435
2292
  codex_home: expectedHome,
2436
2293
  };
2437
2294
  }
2438
- const expectedGrokHome = agentManagedGrokHomePath(name, m.launch_id);
2439
- const expectedHome = agentManagedGrokUserHomePath(name, m.launch_id);
2440
- 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)) {
2441
2301
  throw new AitermError("agent metadata の path が現在の secure state root と一致しません", 2);
2442
2302
  }
2443
2303
  const expectedAuthPath = resolveAndValidateGrokAuth(realGrokHome());
@@ -2452,12 +2312,13 @@ function loadAgentMetadata(name) {
2452
2312
  created_at: typeof m.created_at === "string" ? m.created_at : "",
2453
2313
  cwd: typeof m.cwd === "string" ? m.cwd : null,
2454
2314
  ...(typeof m.write_scope === "string" ? { write_scope: m.write_scope } : {}),
2455
- vendor_session_id: typeof m.vendor_session_id === "string" ? m.vendor_session_id : null,
2315
+ vendor_session_id: m.vendor_session_id,
2456
2316
  initial_prompt: normalizeInitialPromptState(m.initial_prompt),
2457
- hook_route: "managed_grok_home",
2317
+ hook_route: "shared_grok_home",
2318
+ completion_route: "grok_transcript",
2319
+ ...loadAgentLineageFields(m, true),
2458
2320
  node_platform: process.platform,
2459
2321
  grok_home: expectedGrokHome,
2460
- home: expectedHome,
2461
2322
  grok_auth_path: expectedAuthPath,
2462
2323
  };
2463
2324
  }
@@ -2563,7 +2424,10 @@ function bindCompletedInitialPrompt(meta) {
2563
2424
  setInitialPromptState(meta, "done");
2564
2425
  return;
2565
2426
  }
2566
- 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
+ }
2567
2431
  setInitialPromptState(meta, "done");
2568
2432
  return;
2569
2433
  }
@@ -2597,6 +2461,9 @@ function tryLoadAgentMetadata(name) {
2597
2461
  function latestAgentDoneEvent(meta, expectedOperationId = null) {
2598
2462
  if (meta.kind === "codex")
2599
2463
  return latestCodexCompletion(meta);
2464
+ if ((meta.kind === "grok" || meta.kind === "composer") && meta.completion_route === "grok_transcript") {
2465
+ return latestGrokCompletion(meta);
2466
+ }
2600
2467
  const size = safeStatSize(meta.event_file);
2601
2468
  if (size === 0)
2602
2469
  return null;
@@ -2732,10 +2599,52 @@ function listCodexTranscripts(codexHome) {
2732
2599
  }
2733
2600
  };
2734
2601
  visit(sessionsDir);
2735
- // managed CODEX_HOME では root TUI の rollout が最初に作られる。後から同じ home に
2736
- // sub-agent rollout が増えても root を取り違えないよう、未bind時は最古を選べる順にする。
2737
2602
  return files.sort();
2738
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
+ }
2739
2648
  function codexTranscriptSessionId(file) {
2740
2649
  const size = Math.min(safeStatSize(file), AGENT_EVENT_TAIL_BYTES);
2741
2650
  if (size === 0)
@@ -2761,6 +2670,13 @@ function codexRootTranscript(meta) {
2761
2670
  return null;
2762
2671
  if (meta.vendor_session_id)
2763
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
+ }
2764
2680
  return listCodexTranscripts(meta.codex_home)[0] ?? null;
2765
2681
  }
2766
2682
  function bindCodexTranscriptSession(meta) {
@@ -2812,7 +2728,84 @@ function latestCodexCompletion(meta) {
2812
2728
  }
2813
2729
  return latest;
2814
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
+ }
2815
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
+ }
2816
2809
  if (meta.kind !== "codex")
2817
2810
  return safeStatSize(meta.event_file);
2818
2811
  const transcript = bindCodexTranscriptSession(meta);
@@ -3176,6 +3169,80 @@ async function observeCodexDone(meta, timeout, requestedCursor) {
3176
3169
  await sleep(AGENT_DONE_POLL_MS);
3177
3170
  }
3178
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
+ }
3179
3246
  // 外部waiterプロセス用の純リーダー観測。lock・PTY・metadata書込・dispatch状態には一切触れない。
3180
3247
  // Codexはrollout transcript、他vendorはevent fileを増分走査し、vendor_session_idのbind永続化を
3181
3248
  // 行わない(waiterは観測者であって所有者でない)。
@@ -3192,6 +3259,9 @@ export async function observeAgentDone(name, o = {}) {
3192
3259
  if (meta.kind === "codex" && meta.completion_route === "codex_transcript") {
3193
3260
  return observeCodexDone(meta, timeout, o.cursor);
3194
3261
  }
3262
+ if ((meta.kind === "grok" || meta.kind === "composer") && meta.completion_route === "grok_transcript") {
3263
+ return observeGrokDone(meta, timeout, o.cursor);
3264
+ }
3195
3265
  const metadataFile = agentMetadataPath(meta.aiterm_session, meta.launch_id);
3196
3266
  // 境界の優先順: dispatch receipt の event_cursor(起動順序に依存しない)→ operation相関
3197
3267
  // (operation_idの一意性で先頭から全走査できる)→ waiter起動時EOF(waiter先行起動が前提)。
@@ -3250,7 +3320,10 @@ function isAgentTuiReady(kind, screen) {
3250
3320
  if (kind === "codex") {
3251
3321
  return screen.includes("OpenAI Codex") && /(^|\n)\s*[›>]/.test(screen);
3252
3322
  }
3253
- 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);
3254
3327
  }
3255
3328
  // Codex/Claude は実行中に「(esc to interrupt)」を表示する(実機採取)。startup 側の処理
3256
3329
  // (MCP initialize 等)が走ったまま composer だけ描画されている画面は入力受付とみなさない。
@@ -3289,7 +3362,11 @@ async function waitAgentTuiReadyImpl(kind, sample, sleepFn, opts = {}) {
3289
3362
  }
3290
3363
  }
3291
3364
  async function waitAgentTuiReady(name, meta, timeoutMs = AGENT_TUI_READY_TIMEOUT_MS) {
3292
- 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 });
3293
3370
  }
3294
3371
  async function waitAgentTuiReadyByKind(name, kind, timeoutMs = AGENT_TUI_READY_TIMEOUT_MS) {
3295
3372
  return waitAgentTuiReadyImpl(kind, () => captureScreen(name, AGENT_TUI_READY_LINES), sleep, { timeoutMs });
@@ -3493,9 +3570,10 @@ export async function dispatchAgentTurn(name, text, o = {}) {
3493
3570
  throw new AitermError("operation_id はClaude agent sessionだけで使用できます", 2);
3494
3571
  }
3495
3572
  bindCompletedInitialPrompt(meta);
3496
- // Codexはbind済みのfollow-upでも毎回idleを確認してからtranscript境界を切る。同じcursorへ
3497
- // 複数turnを帰属させる余地を作らない。他vendorの既存dispatch条件は変えない。
3498
- 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) {
3499
3577
  const ready = await waitAgentTuiReady(name, meta, o.ready_timeout ?? AGENT_TUI_READY_TIMEOUT_MS);
3500
3578
  if (!ready.ready) {
3501
3579
  throw new AitermError(`agent session '${name}' の ${agentLabel(meta.kind)} TUI が入力受付状態になりません。文字列は送信していません。` +
@@ -3544,7 +3622,7 @@ export async function runClaudeOperation({ session_id: name, action, operation_i
3544
3622
  const operationId = validateOperationId(operationIdInput);
3545
3623
  const meta = loadAgentMetadata(name);
3546
3624
  if (meta.kind !== "claude")
3547
- throw new AitermError("claude_turnはmanaged Claude agent sessionだけで使用できます", 2);
3625
+ throw new AitermError("claude_turnはaiterm相関付きClaude agent sessionだけで使用できます", 2);
3548
3626
  let dispatchReceipt = null;
3549
3627
  if (action === "issue") {
3550
3628
  if (typeof text !== "string" || text.length === 0) {
@@ -3655,7 +3733,7 @@ function assertClaudeAuthenticationReady(bin) {
3655
3733
  status.loggedIn === false) {
3656
3734
  throw new AitermError("Claude Codeの認証を利用できません。sessionは作成していません。" +
3657
3735
  "通常端末で `claude doctor` を実行し、Keychain/credential storeを直してから一度だけ `claude auth login` を実行してください。" +
3658
- "managed Claude session内で /login を繰り返さないでください。", 2);
3736
+ "aiterm相関付きClaude session内で /login を繰り返さないでください。", 2);
3659
3737
  }
3660
3738
  const timedOut = result.error && result.error.code === "ETIMEDOUT";
3661
3739
  throw new AitermError(`Claude Codeの認証状態を起動前に確認できません${timedOut ? "(5秒でtimeout)" : ""}。sessionは作成していません。` +
@@ -3698,9 +3776,7 @@ function buildAgentCmd(kind, bin, model, effort, prompt, meta = null) {
3698
3776
  const parts = [shq(bin)];
3699
3777
  if (kind === "claude") {
3700
3778
  if (meta?.kind === "claude") {
3701
- parts.push("--setting-sources", shq(""), "--settings", shq(meta.claude_settings ?? ""));
3702
- if (meta.claude_mcp_config)
3703
- parts.push("--mcp-config", shq(meta.claude_mcp_config));
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)));
3704
3780
  }
3705
3781
  if (model)
3706
3782
  parts.push("--model", shq(model));
@@ -3712,12 +3788,14 @@ function buildAgentCmd(kind, bin, model, effort, prompt, meta = null) {
3712
3788
  // launcher自身が実効能力壁を作る。パス説明はCodex CLIに同等のallowlist引数がないため宣言のまま残す。
3713
3789
  if (meta?.kind === "codex" && meta.write_scope === "read-only")
3714
3790
  parts.push("--sandbox", "read-only");
3715
- // model/effort は CLI 引数で明示(config 継承より優先)。agent_done 時は managed home 側
3716
- // config.toml も同値で上書き済み(applyCodexConfigOverrides)。
3791
+ // model/effort は共有configを書き換えず、CLI引数で明示して起動単位に優先する。
3717
3792
  if (model)
3718
3793
  parts.push("-m", shq(model));
3719
3794
  if (effort)
3720
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
+ }
3721
3799
  }
3722
3800
  else {
3723
3801
  // grok / composer は同じ grok CLI をモデル違いで起動。--effort は headless(grok -p)専用で
@@ -3726,6 +3804,9 @@ function buildAgentCmd(kind, bin, model, effort, prompt, meta = null) {
3726
3804
  if (meta?.kind === "grok" || meta?.kind === "composer")
3727
3805
  parts.push("--no-alt-screen");
3728
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
+ }
3729
3810
  if ((meta?.kind === "grok" || meta?.kind === "composer") && prompt)
3730
3811
  parts.push("--verbatim");
3731
3812
  }
@@ -3741,29 +3822,21 @@ function agentEnvPrefix(meta, sid) {
3741
3822
  `AITERM_SESSION_ID=${shq(sid)}`,
3742
3823
  `AITERM_AGENT_SESSION_ID=${shq(sid)}`,
3743
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")}`,
3744
3830
  ];
3745
3831
  if (meta.kind === "claude") {
3746
3832
  return common.join(" ") + " ";
3747
3833
  }
3748
3834
  if (meta.kind === "codex") {
3749
- return [`CODEX_HOME=${shq(meta.codex_home ?? "")}`, ...common].join(" ") + " ";
3835
+ return common.join(" ") + " ";
3750
3836
  }
3751
3837
  return [
3752
- `HOME=${shq(meta.home ?? "")}`,
3753
- `GROK_HOME=${shq(meta.grok_home ?? "")}`,
3754
3838
  ...(meta.grok_auth_path ? [`GROK_AUTH_PATH=${shq(meta.grok_auth_path)}`] : []),
3755
3839
  "GROK_DISABLE_AUTOUPDATER=1",
3756
- "GROK_CLAUDE_HOOKS_ENABLED=false",
3757
- "GROK_CURSOR_HOOKS_ENABLED=false",
3758
- "GROK_CLAUDE_SKILLS_ENABLED=false",
3759
- "GROK_CURSOR_SKILLS_ENABLED=false",
3760
- "GROK_CLAUDE_RULES_ENABLED=false",
3761
- "GROK_CURSOR_RULES_ENABLED=false",
3762
- "GROK_CLAUDE_AGENTS_ENABLED=false",
3763
- "GROK_CURSOR_AGENTS_ENABLED=false",
3764
- "GROK_CLAUDE_MCPS_ENABLED=false",
3765
- "GROK_CURSOR_MCPS_ENABLED=false",
3766
- `AITERM_REAL_HOME=${shq(process.env.HOME ?? os.homedir())}`,
3767
3840
  ...common,
3768
3841
  ].join(" ") + " ";
3769
3842
  }
@@ -3808,7 +3881,7 @@ function buildAgentLaunchNote(kind, model, effort, meta) {
3808
3881
  (effectiveEffort === "ultra"
3809
3882
  ? "⚠ effort=ultra は max 推論+proactive 自動委譲 ON(子エージェント自動生成・使用量急増に注意)。"
3810
3883
  : "");
3811
- const summary = meta?.kind === "codex" && meta.codex_home ? managedCodexConfigSummary(configPath) : "";
3884
+ const summary = meta?.kind === "codex" && meta.codex_home ? codexConfigSummary(configPath) : "";
3812
3885
  return (summary ? `${launch}\n${summary}\n` : launch) + writeScopeNote;
3813
3886
  }
3814
3887
  function claudeLaunchRequestDigest({ sessionName, model, effort, cwd, agentDone, }) {
@@ -3886,6 +3959,8 @@ export function openAgent(kind, opts = {}) {
3886
3959
  throw new AitermError("launch_operation_id付きlaunchにpromptは指定できません", 2);
3887
3960
  }
3888
3961
  }
3962
+ // 継承lineageの破損はsession作成前にfail loudする。root callerにはhost-root/depth 1を割り当てる。
3963
+ const lineageSeed = readAgentLineageSeed();
3889
3964
  let bin;
3890
3965
  try {
3891
3966
  bin = resolveAgentBin(kind);
@@ -3956,12 +4031,13 @@ export function openAgent(kind, opts = {}) {
3956
4031
  }
3957
4032
  let launchNote = "";
3958
4033
  try {
4034
+ const lineageContext = createAgentLineageContext(kind, sid, lineageSeed);
3959
4035
  const meta = agentDone
3960
4036
  ? kind === "claude"
3961
- ? createClaudeAgentMetadata(sid, cwd, opts.prompt ? "pending" : "none", launchOperationId, launchRequestDigest)
4037
+ ? createClaudeAgentMetadata(sid, cwd, opts.prompt ? "pending" : "none", launchOperationId, launchRequestDigest, lineageContext)
3962
4038
  : kind === "codex"
3963
- ? createCodexAgentMetadata(sid, cwd, opts.prompt ? "pending" : "none", { model, effort }, writeScope)
3964
- : 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)
3965
4041
  : null;
3966
4042
  if (meta)
3967
4043
  agentMetadataNegativeCache.delete(sid);
@@ -4001,7 +4077,7 @@ export function openAgent(kind, opts = {}) {
4001
4077
  return [
4002
4078
  sid,
4003
4079
  `${label} を session ${sid} で起動した。${launchNote}${agentDone ? "agent_done 待機が有効。" : ""}` +
4004
- `${agentDone && (kind === "grok" || kind === "composer") ? " hook 汚染防止のため Grok 実行中は一時 HOME を使う。" : ""}\n${hint}\n` +
4080
+ `${agentDone ? " 通常のproject/user環境を共有し、aiterm所有の完了相関とsub-agent lineageだけを加算。" : ""}\n${hint}\n` +
4005
4081
  driveHint +
4006
4082
  `起動直後に増分 pty_read すると空/半描画になり得るので screen:true を使う。`,
4007
4083
  ];
@@ -4009,7 +4085,7 @@ export function openAgent(kind, opts = {}) {
4009
4085
  export async function openAgentWithInitialPrompt(kind, opts = {}) {
4010
4086
  const prompt = opts.prompt ?? null;
4011
4087
  if (opts.launch_operation_id != null && prompt !== null) {
4012
- throw new AitermError("launch_operation_idはpromptなしのmanaged Claude launchだけで指定できます", 2);
4088
+ throw new AitermError("launch_operation_idはpromptなしのClaude相関launchだけで指定できます", 2);
4013
4089
  }
4014
4090
  // v0.16.0: launcher は常に managed(Stop hook つき)で立つ。手動運転したい場合は
4015
4091
  // pty_open で素の PTY を開き、vendor CLI を自分で send する。