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/README.ja.md +18 -13
- package/README.md +32 -28
- package/dist/core.js +402 -326
- package/dist/index.js +18 -10
- package/package.json +1 -1
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("
|
|
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("
|
|
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("
|
|
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
|
|
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
|
|
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
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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は
|
|
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 =
|
|
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:
|
|
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: "
|
|
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 =
|
|
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: "
|
|
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
|
|
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:
|
|
2170
|
+
vendor_session_id: randomUUID(),
|
|
2342
2171
|
initial_prompt: initialPrompt,
|
|
2343
|
-
hook_route: "
|
|
2172
|
+
hook_route: "shared_grok_home",
|
|
2173
|
+
completion_route: "grok_transcript",
|
|
2174
|
+
...(lineageContext ? agentLineageFields(lineageContext) : {}),
|
|
2344
2175
|
node_platform: process.platform,
|
|
2345
|
-
grok_home:
|
|
2346
|
-
|
|
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
|
|
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 !== "
|
|
2244
|
+
if (m.hook_route !== "shared_claude_settings" ||
|
|
2390
2245
|
m.claude_settings !== expectedSettings ||
|
|
2391
|
-
|
|
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:
|
|
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: "
|
|
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 =
|
|
2419
|
-
if (m.hook_route !== "
|
|
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: "
|
|
2433
|
-
|
|
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 =
|
|
2439
|
-
|
|
2440
|
-
|
|
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:
|
|
2315
|
+
vendor_session_id: m.vendor_session_id,
|
|
2456
2316
|
initial_prompt: normalizeInitialPromptState(m.initial_prompt),
|
|
2457
|
-
hook_route: "
|
|
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.
|
|
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
|
-
|
|
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, () =>
|
|
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
|
|
3497
|
-
//
|
|
3498
|
-
|
|
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は
|
|
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
|
-
"
|
|
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
|
|
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
|
|
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 ?
|
|
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
|
|
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なしの
|
|
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 する。
|