@ycodium-ai/agent-codex 0.2.3082 → 0.2.3088

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.
Files changed (2) hide show
  1. package/dist/index.js +1395 -1351
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -48,7 +48,7 @@ var CODEX_CARD = {
48
48
  // 三字段组合在 openThread 时定,切档要重开会话(与 runtimeModeSwitch 同为 next-turn)。
49
49
  nativeModeSwitch: true,
50
50
  contextCompaction: "native",
51
- // 子代理经 collab 通知报成 task.*(events.ts 的 mapCollabAgentEvent)。
51
+ // 子代理经 collab 通知报成 task.*(events/collabAgents.ts 的 mapCollabAgentEvent)。
52
52
  reportsSpawnedAgents: true,
53
53
  // 出处不是 provider 文档:这是产品此前对所有 provider 统一施加的单图上限(10 MiB),
54
54
  // codex 在这个上限下一直把图片以 data URL 内联。没有查到 Codex / OpenAI 对单图的明文
@@ -1381,9 +1381,6 @@ function buildCodexDeveloperInstructions(interactionMode, runtime, browserToolsA
1381
1381
  <runtime_info>In case you're asked: you are running in Ycodium through the Codex harness, as ${toSingleLine(runtime.model)} with ${toSingleLine(runtime.reasoningEffort)} reasoning effort. No need to mention this otherwise.</runtime_info>`;
1382
1382
  }
1383
1383
 
1384
- // src/events.ts
1385
- import * as NodeCrypto from "node:crypto";
1386
-
1387
1384
  // src/codexArtifacts.ts
1388
1385
  function imageGenerationArtifacts(input) {
1389
1386
  if (input.itemType !== "imageGeneration" || input.status !== "completed") return [];
@@ -1421,407 +1418,572 @@ function toCanonicalItemType(raw) {
1421
1418
  return "unknown";
1422
1419
  }
1423
1420
 
1424
- // src/approvalMapping.ts
1425
- function fileChangeEntries(params) {
1426
- if (!isRecord3(params)) return [];
1427
- const item = isRecord3(params["item"]) ? params["item"] : params;
1428
- const changes = item["changes"] ?? item["fileChanges"] ?? params["fileChanges"];
1429
- if (!isRecord3(changes) && !Array.isArray(changes)) return [];
1430
- const entries = Array.isArray(changes) ? changes : Object.entries(changes).map(([path, change]) => ({
1431
- path,
1432
- ...isRecord3(change) ? change : {}
1433
- }));
1434
- const out = [];
1435
- for (const entry of entries) {
1436
- if (!isRecord3(entry)) continue;
1437
- const path = asString(entry["path"]) ?? asString(entry["file"]);
1438
- if (!path) continue;
1439
- out.push({
1440
- path,
1441
- kind: (asString(entry["type"]) ?? asString(entry["kind"]) ?? "update").toLowerCase(),
1442
- movePath: asString(entry["move_path"]) ?? asString(entry["movePath"]),
1443
- diff: asString(entry["unified_diff"]) ?? asString(entry["unifiedDiff"]),
1444
- content: asString(entry["content"])
1445
- });
1446
- }
1447
- return out;
1421
+ // src/events/base.ts
1422
+ import * as NodeCrypto from "node:crypto";
1423
+ var CODEX_PROVIDER = "codex";
1424
+ var EVENT_ID_PREFIX = `codex-evt-${NodeCrypto.randomUUID()}`;
1425
+ var eventSeq = 0;
1426
+ function nextEventId() {
1427
+ eventSeq += 1;
1428
+ return `${EVENT_ID_PREFIX}-${eventSeq}`;
1448
1429
  }
1449
- var MAX_DESCRIBED_FILE_CHANGES = 20;
1450
- function describeFileChanges(params) {
1451
- const entries = fileChangeEntries(params).toSorted(
1452
- (left, right) => left.path.localeCompare(right.path)
1453
- );
1454
- if (entries.length === 0) return void 0;
1455
- const lines = entries.slice(0, MAX_DESCRIBED_FILE_CHANGES).map(
1456
- (entry) => entry.movePath ? `${entry.kind} ${entry.path} -> ${entry.movePath}` : `${entry.kind} ${entry.path}`
1457
- );
1458
- const remaining = entries.length - lines.length;
1459
- return remaining > 0 ? `${lines.join("\n")}
1460
- +${remaining} more` : lines.join("\n");
1430
+ function nowIso2() {
1431
+ return (/* @__PURE__ */ new Date()).toISOString();
1461
1432
  }
1462
- function toApplyPatchFileChanges(params) {
1463
- const out = [];
1464
- for (const { path, kind, movePath, diff, content } of fileChangeEntries(params)) {
1465
- if (kind === "add") {
1466
- out.push({
1467
- path,
1468
- changeKind: "add",
1469
- ...content !== void 0 ? { content: { kind: "replace", newText: content } } : {}
1470
- });
1471
- } else if (kind === "delete") {
1472
- out.push({
1473
- path,
1474
- changeKind: "delete",
1475
- ...content !== void 0 ? { content: { kind: "replace", oldText: content, newText: "" } } : {}
1476
- });
1477
- } else if (movePath) {
1478
- out.push({
1479
- path,
1480
- changeKind: "rename",
1481
- ...diff ? { content: { kind: "unified", diff } } : {}
1482
- });
1483
- } else {
1484
- out.push({
1485
- path,
1486
- changeKind: "modify",
1487
- ...diff ? { content: { kind: "unified", diff } } : {}
1488
- });
1489
- }
1490
- }
1491
- return out;
1433
+ function eventRaw(source, method, payload) {
1434
+ return { source, method, payload };
1492
1435
  }
1493
- function describeCommandApproval(params) {
1494
- const record = isRecord3(params) ? params : {};
1495
- const commandValue = record["command"];
1496
- const command = Array.isArray(commandValue) ? commandValue.filter((entry) => typeof entry === "string").join(" ") : asString(commandValue) ?? "";
1497
- const cwd = asString(record["cwd"]);
1498
- const reason = trimText(record["reason"]);
1499
- const callId = asString(record["callId"]);
1436
+ function baseEvent(input) {
1500
1437
  return {
1501
- command,
1502
- ...cwd ? { cwd } : {},
1503
- ...reason ? { reason } : {},
1504
- ...callId ? { callId } : {}
1438
+ eventId: nextEventId(),
1439
+ provider: CODEX_PROVIDER,
1440
+ threadId: input.threadId,
1441
+ createdAt: nowIso2(),
1442
+ type: input.type,
1443
+ payload: input.payload,
1444
+ ...input.providerInstanceId ? { providerInstanceId: input.providerInstanceId } : {},
1445
+ ...input.turnId ? { turnId: input.turnId } : {},
1446
+ ...input.itemId ? { itemId: input.itemId } : {},
1447
+ ...input.requestId ? { requestId: input.requestId } : {},
1448
+ ...input.method ? {
1449
+ raw: eventRaw(
1450
+ input.source ?? "codex.app-server.notification",
1451
+ input.method,
1452
+ input.payload
1453
+ )
1454
+ } : {}
1505
1455
  };
1506
1456
  }
1507
- function describeFileChangeApproval(params) {
1508
- const record = isRecord3(params) ? params : {};
1509
- const reason = trimText(record["reason"]);
1510
- const grantRoot = asString(record["grantRoot"]);
1511
- const callId = asString(record["callId"]);
1512
- const detail = reason ?? describeFileChanges(record) ?? trimText(grantRoot);
1513
- return {
1514
- ...reason ? { reason } : {},
1515
- ...detail ? { detail } : {},
1516
- ...grantRoot ? { grantRoot } : {},
1517
- ...callId ? { callId } : {},
1518
- fileChanges: toApplyPatchFileChanges(record)
1519
- };
1457
+
1458
+ // src/events/fields.ts
1459
+ function itemIdOf(params) {
1460
+ if (!isRecord3(params)) return void 0;
1461
+ const item = isRecord3(params["item"]) ? params["item"] : params;
1462
+ return asString(item["id"]) ?? asString(params["itemId"]) ?? void 0;
1520
1463
  }
1521
- function describePermissionsApproval(params) {
1522
- const record = isRecord3(params) ? params : {};
1523
- const permissions = isRecord3(record["permissions"]) ? record["permissions"] : {};
1524
- const fileSystem = isRecord3(permissions["fileSystem"]) ? permissions["fileSystem"] : {};
1525
- const network = isRecord3(permissions["network"]) ? permissions["network"] : {};
1526
- const pathsOf = (value) => Array.isArray(value) ? value.flatMap((entry) => typeof entry === "string" && entry.length > 0 ? [entry] : []) : [];
1527
- const read = pathsOf(fileSystem["read"]);
1528
- const write = pathsOf(fileSystem["write"]);
1529
- const entries = Array.isArray(fileSystem["entries"]) ? fileSystem["entries"] : [];
1530
- for (const entry of entries) {
1531
- if (!isRecord3(entry) || !isRecord3(entry["path"])) continue;
1532
- const target = entry["path"];
1533
- const path = target["type"] === "path" ? asString(target["path"]) : target["type"] === "glob_pattern" ? asString(target["pattern"]) : void 0;
1534
- if (!path) continue;
1535
- if (entry["access"] === "read") read.push(path);
1536
- if (entry["access"] === "write") write.push(path);
1537
- }
1538
- const reason = trimText(record["reason"]);
1539
- return {
1540
- ...reason ? { reason } : {},
1541
- read: [...new Set(read)],
1542
- write: [...new Set(write)],
1543
- network: network["enabled"] === true
1544
- };
1464
+ function itemStatusOf(params) {
1465
+ if (!isRecord3(params)) return void 0;
1466
+ const item = isRecord3(params["item"]) ? params["item"] : params;
1467
+ return asString(item["status"]) ?? void 0;
1545
1468
  }
1546
- function toPermissionsResponse(decision, params, persistent) {
1547
- if (decision !== "allow") return { permissions: {} };
1548
- const requested = isRecord3(params) && isRecord3(params["permissions"]) ? params["permissions"] : {};
1549
- return {
1550
- permissions: {
1551
- ...requested["fileSystem"] !== void 0 ? { fileSystem: requested["fileSystem"] } : {},
1552
- ...requested["network"] !== void 0 ? { network: requested["network"] } : {}
1553
- },
1554
- scope: persistent ? "session" : "turn"
1555
- };
1469
+ function turnIdOf(params) {
1470
+ if (!isRecord3(params)) return void 0;
1471
+ return asString(params["turnId"]) ?? (isRecord3(params["turn"]) ? asString(params["turn"]["id"]) : null) ?? void 0;
1556
1472
  }
1557
- function describeMcpElicitation(params) {
1558
- const record = isRecord3(params) ? params : {};
1559
- const appName = asString(record["appName"]) ?? asString(record["serverName"]) ?? void 0;
1560
- const serverName = asString(record["serverName"]);
1561
- const message = trimText(record["message"]);
1562
- const url = asString(record["url"]) ?? asString(record["authorizationUrl"]);
1563
- const identity = appName ?? url ?? "unknown";
1564
- return {
1565
- ...appName ? { appName } : {},
1566
- ...serverName ? { serverName } : {},
1567
- ...message ? { message } : {},
1568
- ...url ? { url } : {},
1569
- identity,
1570
- mode: url ? "url" : "form",
1571
- ...record["requestedSchema"] !== void 0 ? { schema: record["requestedSchema"] } : {}
1572
- };
1473
+ function itemTitle(params) {
1474
+ if (!isRecord3(params)) return void 0;
1475
+ const item = isRecord3(params["item"]) ? params["item"] : params;
1476
+ return trimText(item["title"]) ?? trimText(item["name"]) ?? trimText(item["command"]) ?? trimText(item["summary"]) ?? void 0;
1573
1477
  }
1574
- function persistFromMeta(meta) {
1575
- if (!isRecord3(meta)) return void 0;
1576
- const persist = asString(meta["persist"]) ?? asString(meta["persistence"]);
1577
- if (persist === "session" || persist === "acceptForSession") return "session";
1578
- if (persist === "always" || persist === "permanent" || persist === "forever" || persist === "persistent" || persist === "acceptAlways") {
1579
- return "always";
1478
+ function itemDetail(params) {
1479
+ if (!isRecord3(params)) return void 0;
1480
+ const item = isRecord3(params["item"]) ? params["item"] : params;
1481
+ const query = isRecord3(item["action"]) ? item["action"]["queries"] : item["query"];
1482
+ if (Array.isArray(query)) {
1483
+ const joined = query.filter((entry) => typeof entry === "string").join(", ");
1484
+ if (joined.length > 0) return joined;
1580
1485
  }
1581
- return void 0;
1486
+ return trimText(item["text"]) ?? trimText(item["summary"]) ?? trimText(item["path"]) ?? trimText(item["savedPath"]) ?? trimText(item["prompt"]) ?? (typeof query === "string" ? query : void 0);
1582
1487
  }
1583
- function toMcpElicitationResponse(decision, params) {
1584
- const described = describeMcpElicitation(params);
1585
- if (decision !== "allow" || described.mode === "url") {
1586
- return {
1587
- action: decision === "allow" ? "decline" : decision === "cancel" ? "cancel" : "decline"
1588
- };
1589
- }
1590
- const schema = isRecord3(described.schema) ? described.schema : void 0;
1591
- const properties = schema && isRecord3(schema["properties"]) ? schema["properties"] : {};
1592
- const required = schema && Array.isArray(schema["required"]) ? schema["required"] : [];
1593
- const content = {};
1594
- for (const [key, spec] of Object.entries(properties)) {
1595
- if (!isRecord3(spec)) continue;
1596
- if (Array.isArray(spec["oneOf"]) || Array.isArray(spec["enum"])) {
1597
- const options = Array.isArray(spec["oneOf"]) ? spec["oneOf"] : Array.isArray(spec["enum"]) ? spec["enum"] : [];
1598
- const first = options[0];
1599
- content[key] = isRecord3(first) ? first["const"] ?? first["value"] ?? first : first;
1600
- } else if (spec["type"] === "boolean") {
1601
- content[key] = true;
1602
- } else if (typeof spec["default"] !== "undefined") {
1603
- content[key] = spec["default"];
1604
- }
1605
- }
1606
- for (const key of required) {
1607
- if (typeof key === "string" && !(key in content)) {
1608
- return { action: "decline" };
1609
- }
1488
+ function toTurnStatus(status) {
1489
+ switch (status) {
1490
+ case "failed":
1491
+ return "failed";
1492
+ case "cancelled":
1493
+ case "canceled":
1494
+ return "cancelled";
1495
+ case "interrupted":
1496
+ return "interrupted";
1497
+ default:
1498
+ return "completed";
1610
1499
  }
1611
- const persist = persistFromMeta(isRecord3(params) ? params["_meta"] : void 0);
1612
- return {
1613
- action: "accept",
1614
- content,
1615
- ...persist ? { _meta: { persist } } : {}
1616
- };
1617
1500
  }
1618
-
1619
- // src/usage/codexQuota.ts
1620
- var asRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
1621
- var asFiniteNumber = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
1622
- var asNonEmptyString = (value) => typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
1623
- var clampPercent = (value) => Math.min(100, Math.max(0, value));
1624
- var epochSecondsToIso = (value) => {
1625
- const seconds = asFiniteNumber(value);
1626
- if (seconds === null) return null;
1627
- const date = new Date(seconds * 1e3);
1628
- return Number.isNaN(date.getTime()) ? null : date.toISOString();
1629
- };
1630
- var SESSION_WINDOW_MINUTES = 5 * 60;
1631
- var WEEKLY_WINDOW_MINUTES = 7 * 24 * 60;
1632
- var formatWindowDuration = (minutes) => {
1633
- if (minutes % (24 * 60) === 0) return `${String(minutes / (24 * 60))}d`;
1634
- if (minutes % 60 === 0) return `${String(minutes / 60)}h`;
1635
- return `${String(minutes)}m`;
1636
- };
1637
- var classifyWindow = (minutes, slot) => {
1638
- if (minutes === SESSION_WINDOW_MINUTES) {
1639
- return { id: "session", label: { key: "usage.window.session" } };
1640
- }
1641
- if (minutes === WEEKLY_WINDOW_MINUTES) {
1642
- return { id: "weekly", label: { key: "usage.window.weekly" } };
1643
- }
1644
- if (minutes === null) return { id: slot, label: { key: "usage.window.limit" } };
1645
- return {
1646
- id: `window-${String(minutes)}m`,
1647
- label: { key: "usage.window.duration", values: { duration: formatWindowDuration(minutes) } }
1648
- };
1649
- };
1650
- var mapWindow = (raw, slot, describe) => {
1651
- const window = asRecord(raw);
1652
- const usedPercent = asFiniteNumber(window?.["usedPercent"]);
1653
- if (window === null || usedPercent === null) return null;
1654
- const rawMinutes = asFiniteNumber(window["windowDurationMins"]);
1655
- const minutes = rawMinutes !== null && rawMinutes > 0 ? Math.trunc(rawMinutes) : null;
1656
- const { id, label } = describe(minutes, slot);
1501
+ function threadStateFromStatus(status) {
1502
+ const type = isRecord3(status) ? asString(status["type"]) : asString(status);
1503
+ if (type === "idle") return "idle";
1504
+ if (type === "systemError" || type === "error") return "error";
1505
+ return "active";
1506
+ }
1507
+ function codexCompactionId(threadId, turnId) {
1508
+ return `codex:${threadId}:${turnId ?? "session"}`;
1509
+ }
1510
+ function normalizeCodexTokenUsage(tokenUsage) {
1511
+ if (!isRecord3(tokenUsage)) return void 0;
1512
+ const last = isRecord3(tokenUsage["last"]) ? tokenUsage["last"] : void 0;
1513
+ const total = isRecord3(tokenUsage["total"]) ? tokenUsage["total"] : void 0;
1514
+ const count = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
1515
+ const usedTokens = count(last?.["totalTokens"]);
1516
+ if (usedTokens === void 0 || usedTokens <= 0) return void 0;
1517
+ const totalProcessedTokens = count(total?.["totalTokens"]);
1518
+ const maxTokens = count(tokenUsage["modelContextWindow"]);
1519
+ const inputTokens = count(last?.["inputTokens"]);
1520
+ const cachedInputTokens = count(last?.["cachedInputTokens"]);
1521
+ const outputTokens = count(last?.["outputTokens"]);
1522
+ const reasoningOutputTokens = count(last?.["reasoningOutputTokens"]);
1657
1523
  return {
1658
- id,
1659
- label,
1660
- usedPercent: clampPercent(usedPercent),
1661
- resetsAt: epochSecondsToIso(window["resetsAt"]),
1662
- ...minutes === null ? {} : { windowMs: minutes * 6e4 }
1524
+ usedTokens,
1525
+ lastUsedTokens: usedTokens,
1526
+ ...totalProcessedTokens !== void 0 && totalProcessedTokens > usedTokens ? { totalProcessedTokens } : {},
1527
+ ...maxTokens !== void 0 && maxTokens > 0 ? { maxTokens } : {},
1528
+ ...inputTokens !== void 0 ? { inputTokens, lastInputTokens: inputTokens } : {},
1529
+ ...cachedInputTokens !== void 0 ? { cachedInputTokens, lastCachedInputTokens: cachedInputTokens } : {},
1530
+ ...outputTokens !== void 0 ? { outputTokens, lastOutputTokens: outputTokens } : {},
1531
+ ...reasoningOutputTokens !== void 0 ? { reasoningOutputTokens, lastReasoningOutputTokens: reasoningOutputTokens } : {},
1532
+ compactsAutomatically: true
1663
1533
  };
1664
- };
1665
- var PLAN_LABELS = {
1666
- free: "Free",
1667
- go: "Go",
1668
- plus: "Plus",
1669
- pro: "Pro",
1670
- prolite: "Pro Lite",
1671
- team: "Team",
1672
- business: "Business",
1673
- enterprise: "Enterprise",
1674
- edu: "Edu"
1675
- };
1676
- var planLabel = (planType) => {
1677
- const id = asNonEmptyString(planType);
1678
- if (id === null || id === "unknown") return void 0;
1679
- return PLAN_LABELS[id] ?? id.split("_").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
1680
- };
1681
- var creditBalance = (value) => {
1682
- if (typeof value === "number") return Number.isFinite(value) ? value : null;
1683
- if (typeof value !== "string" || value.trim().length === 0) return null;
1684
- const numeric = Number(value.trim());
1685
- return Number.isFinite(numeric) ? numeric : null;
1686
- };
1687
- var MAIN_LIMIT_ID = "codex";
1688
- var CREDITS_BALANCE_ID = "credits";
1689
- var isMainCodexLimit = (snapshot) => {
1690
- const limitId = asNonEmptyString(asRecord(snapshot)?.["limitId"]);
1691
- return limitId === null || limitId === MAIN_LIMIT_ID;
1692
- };
1693
- var mapCodexSnapshotWindows = (snapshot) => {
1694
- const bucket = asRecord(snapshot);
1695
- if (bucket === null || !isMainCodexLimit(bucket)) return [];
1696
- const windows = [];
1697
- const takenIds = /* @__PURE__ */ new Set();
1698
- for (const slot of ["primary", "secondary"]) {
1699
- const window = mapWindow(bucket[slot], slot, classifyWindow);
1700
- if (window === null) continue;
1701
- const id = takenIds.has(window.id) ? `${window.id}:${slot}` : window.id;
1702
- takenIds.add(id);
1703
- windows.push({ ...window, id, group: MAIN_LIMIT_ID });
1704
- }
1705
- return windows;
1706
- };
1707
- var mapCodexRateLimits = (response, accountKey) => {
1708
- const root = asRecord(response);
1709
- const legacy = asRecord(root?.["rateLimits"]);
1710
- const byLimitId = asRecord(root?.["rateLimitsByLimitId"]);
1711
- const codexBucket = asRecord(byLimitId?.[MAIN_LIMIT_ID]);
1712
- const main = codexBucket ?? (legacy !== null && isMainCodexLimit(legacy) ? legacy : null);
1713
- if (root === null || legacy === null && codexBucket === null) return null;
1714
- const windows = mapCodexSnapshotWindows(main);
1715
- const takenIds = new Set(windows.map((window) => window.id));
1716
- const push = (window, slot) => {
1717
- if (window === null) return;
1718
- const id = takenIds.has(window.id) ? `${window.id}:${slot}` : window.id;
1719
- takenIds.add(id);
1720
- windows.push({ ...window, id });
1534
+ }
1535
+ function turnCompletedControlFlow(params) {
1536
+ const turn = isRecord3(params) ? isRecord3(params["turn"]) ? params["turn"] : params : {};
1537
+ const state = toTurnStatus(asString(turn["status"]) ?? void 0);
1538
+ const error = isRecord3(turn["error"]) ? trimText(turn["error"]["message"]) : void 0;
1539
+ return error ? { state, errorMessage: error } : { state };
1540
+ }
1541
+
1542
+ // src/events/collabAgents.ts
1543
+ var COLLAB_CHILD_STATUSES = /* @__PURE__ */ new Set([
1544
+ "running",
1545
+ "idle",
1546
+ "failed",
1547
+ "interrupted",
1548
+ "cancelled"
1549
+ ]);
1550
+ function asCollabChildStatus(value) {
1551
+ return typeof value === "string" && COLLAB_CHILD_STATUSES.has(value) ? value : void 0;
1552
+ }
1553
+ function collabTurnEndStatus(turnStatus) {
1554
+ const state = toTurnStatus(turnStatus);
1555
+ return state === "completed" ? "idle" : state;
1556
+ }
1557
+ function pathLeaf(value) {
1558
+ if (!value) return void 0;
1559
+ const parts = value.split("/").filter((part) => part.length > 0);
1560
+ return parts.at(-1);
1561
+ }
1562
+ function mapCollabAgentEvent(input) {
1563
+ if (!isRecord3(input.params)) return [];
1564
+ const agentThreadId = asString(input.params["agentThreadId"]) ?? asString(input.params["threadId"]);
1565
+ if (!agentThreadId) return [];
1566
+ const agentPath = asString(input.params["agentPath"]) ?? asString(input.params["path"]) ?? void 0;
1567
+ const nickname = asString(input.params["agentNickname"]) ?? asString(input.params["nickname"]);
1568
+ const role = asString(input.params["agentRole"]) ?? asString(input.params["role"]) ?? pathLeaf(agentPath) ?? "general-purpose";
1569
+ const knownName = nickname ?? pathLeaf(agentPath);
1570
+ const title = knownName ?? agentThreadId;
1571
+ const model = trimText(input.params["model"]);
1572
+ const effort = trimText(input.params["effort"]);
1573
+ const linkage = {
1574
+ role,
1575
+ ...knownName ? { title: knownName } : {},
1576
+ ...model ? { model } : {},
1577
+ ...effort ? { effort } : {},
1578
+ ...agentPath ? { agentPath } : {},
1579
+ timelineBypass: true,
1580
+ rosterKind: "subagent"
1721
1581
  };
1722
- const others = /* @__PURE__ */ new Map();
1723
- for (const [limitId, rawBucket] of Object.entries(byLimitId ?? {})) {
1724
- const bucket = asRecord(rawBucket);
1725
- if (limitId !== MAIN_LIMIT_ID && bucket !== null) others.set(limitId, bucket);
1582
+ const threadId = input.rootThreadId;
1583
+ const method = input.method;
1584
+ if (method === "collabAgent/started" || method === "task.started") {
1585
+ return [
1586
+ baseEvent({
1587
+ type: "task.started",
1588
+ threadId,
1589
+ payload: { taskId: agentThreadId, ...linkage },
1590
+ method,
1591
+ providerInstanceId: input.providerInstanceId
1592
+ })
1593
+ ];
1726
1594
  }
1727
- const legacyLimitId = asNonEmptyString(legacy?.["limitId"]);
1728
- if (legacy !== null && legacyLimitId !== null && !isMainCodexLimit(legacy)) {
1729
- if (!others.has(legacyLimitId)) others.set(legacyLimitId, legacy);
1595
+ if (method === "collabAgent/progress" || method === "task.progress") {
1596
+ const summary = trimText(input.params["detail"]) ?? trimText(input.params["text"]);
1597
+ return [
1598
+ baseEvent({
1599
+ type: "task.progress",
1600
+ threadId,
1601
+ // 契约 TaskProgressPayload 的 description 是必填;子代理行靠它显示名字。
1602
+ payload: {
1603
+ taskId: agentThreadId,
1604
+ description: title,
1605
+ ...summary ? { summary } : {},
1606
+ ...linkage
1607
+ },
1608
+ method,
1609
+ providerInstanceId: input.providerInstanceId
1610
+ })
1611
+ ];
1730
1612
  }
1731
- for (const [limitId, bucket] of others) {
1732
- const name = asNonEmptyString(bucket["limitName"]) ?? limitId;
1733
- for (const slot of ["primary", "secondary"]) {
1734
- const window = mapWindow(bucket[slot], slot, (minutes) => ({
1735
- id: `${limitId}:${slot}`,
1736
- label: minutes === null ? { text: name } : {
1737
- key: "usage.window.named",
1738
- values: { name, duration: formatWindowDuration(minutes) }
1739
- }
1740
- }));
1741
- push(window === null ? null : { ...window, group: limitId }, slot);
1742
- }
1613
+ if (method === "collabAgent/completed" || method === "task.completed") {
1614
+ const status = collabTurnEndStatus(asString(input.params["turnStatus"]) ?? void 0);
1615
+ return [
1616
+ baseEvent({
1617
+ type: "task.updated",
1618
+ threadId,
1619
+ payload: { taskId: agentThreadId, status, ...linkage },
1620
+ method,
1621
+ providerInstanceId: input.providerInstanceId
1622
+ })
1623
+ ];
1743
1624
  }
1744
- const balances = [];
1745
- const credits = asRecord(main?.["credits"]);
1746
- const balance = creditBalance(credits?.["balance"]);
1747
- if (credits !== null && credits["unlimited"] !== true && balance !== null) {
1748
- if (credits["hasCredits"] === true || balance > 0) {
1749
- balances.push({
1750
- id: CREDITS_BALANCE_ID,
1751
- label: { key: "usage.balance.credits" },
1752
- remaining: balance,
1753
- used: null,
1754
- limit: null,
1755
- unit: { kind: "credits" },
1756
- resetsAt: null
1757
- });
1758
- }
1625
+ if (method === "collabAgent/statusChanged") {
1626
+ const raw = asString(input.params["status"]);
1627
+ const status = raw === "systemError" || raw === "error" ? "failed" : raw === "idle" ? "idle" : "running";
1628
+ return [
1629
+ baseEvent({
1630
+ type: "task.updated",
1631
+ threadId,
1632
+ payload: { taskId: agentThreadId, status, ...linkage },
1633
+ method,
1634
+ providerInstanceId: input.providerInstanceId
1635
+ })
1636
+ ];
1759
1637
  }
1760
- const plan = planLabel((main ?? legacy)?.["planType"]);
1761
- const resetSummary = asRecord(root["rateLimitResetCredits"]);
1762
- const availableCount = asFiniteNumber(resetSummary?.["availableCount"]);
1638
+ if (method === "collabAgent/metadataUpdated") {
1639
+ return [
1640
+ baseEvent({
1641
+ type: "task.updated",
1642
+ threadId,
1643
+ payload: { taskId: agentThreadId, ...linkage },
1644
+ method,
1645
+ providerInstanceId: input.providerInstanceId
1646
+ })
1647
+ ];
1648
+ }
1649
+ if (method === "collabAgent/metadata") {
1650
+ const status = asCollabChildStatus(input.params["status"]);
1651
+ if (status === void 0) return [];
1652
+ return [
1653
+ baseEvent({
1654
+ type: "task.updated",
1655
+ threadId,
1656
+ payload: { taskId: agentThreadId, status, ...linkage },
1657
+ method,
1658
+ providerInstanceId: input.providerInstanceId
1659
+ })
1660
+ ];
1661
+ }
1662
+ if (method === "collabAgent/tokenUsage") {
1663
+ const tokenUsage = isRecord3(input.params["tokenUsage"]) ? input.params["tokenUsage"] : void 0;
1664
+ const total = tokenUsage && isRecord3(tokenUsage["total"]) ? tokenUsage["total"] : void 0;
1665
+ const count = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
1666
+ const totalTokens = total ? count(total["totalTokens"]) : void 0;
1667
+ if (total === void 0 || totalTokens === void 0) return [];
1668
+ const inputTokens = count(total["inputTokens"]);
1669
+ const cachedInputTokens = count(total["cachedInputTokens"]);
1670
+ const outputTokens = count(total["outputTokens"]);
1671
+ const reasoningOutputTokens = count(total["reasoningOutputTokens"]);
1672
+ const typedUsage = {
1673
+ totalTokens,
1674
+ ...inputTokens !== void 0 ? { inputTokens } : {},
1675
+ ...cachedInputTokens !== void 0 ? { cachedInputTokens } : {},
1676
+ ...outputTokens !== void 0 ? { outputTokens } : {},
1677
+ ...reasoningOutputTokens !== void 0 ? { reasoningOutputTokens } : {}
1678
+ };
1679
+ return [
1680
+ baseEvent({
1681
+ type: "task.progress",
1682
+ threadId,
1683
+ payload: {
1684
+ taskId: agentThreadId,
1685
+ description: title,
1686
+ typedUsage,
1687
+ ...linkage
1688
+ },
1689
+ method,
1690
+ providerInstanceId: input.providerInstanceId
1691
+ })
1692
+ ];
1693
+ }
1694
+ return [];
1695
+ }
1696
+
1697
+ // src/events/userInput.ts
1698
+ function toUserInputQuestions(params) {
1699
+ if (!isRecord3(params)) return void 0;
1700
+ const questions = Array.isArray(params["questions"]) ? params["questions"] : [];
1701
+ const mapped = [];
1702
+ for (const question of questions) {
1703
+ if (!isRecord3(question)) return void 0;
1704
+ const id = asString(question["id"]);
1705
+ const header = asString(question["header"]);
1706
+ const text = asString(question["question"]);
1707
+ const rawOptions = Array.isArray(question["options"]) ? question["options"] : [];
1708
+ const options = [];
1709
+ for (const option of rawOptions) {
1710
+ if (!isRecord3(option)) continue;
1711
+ const label = trimText(option["label"]);
1712
+ if (!label) continue;
1713
+ options.push({
1714
+ ...asString(option["id"]) ? { id: asString(option["id"]) } : {},
1715
+ label,
1716
+ description: trimText(option["description"]) ?? ""
1717
+ });
1718
+ }
1719
+ if (!id || !header || !text || options.length === 0) return void 0;
1720
+ mapped.push({
1721
+ id,
1722
+ header,
1723
+ question: text,
1724
+ options,
1725
+ multiSelect: false
1726
+ });
1727
+ }
1728
+ return mapped.length > 0 ? mapped : void 0;
1729
+ }
1730
+ function toAsyncQuestions(params) {
1731
+ if (!isRecord3(params)) return void 0;
1732
+ const item = isRecord3(params["item"]) ? params["item"] : params;
1733
+ if (item["delivery"] !== "async" || !Array.isArray(item["questions"])) return void 0;
1734
+ const mapped = [];
1735
+ item["questions"].forEach((question, index) => {
1736
+ if (!isRecord3(question)) return;
1737
+ const title = trimText(question["title"]);
1738
+ if (!title) return;
1739
+ const labels = Array.isArray(question["options"]) ? question["options"] : [];
1740
+ const options = labels.map((label) => trimText(label)).filter((label) => label !== void 0).map((label) => ({ label, description: "" }));
1741
+ mapped.push({
1742
+ id: `q${index}`,
1743
+ header: title,
1744
+ question: title,
1745
+ options,
1746
+ allowFreeform: true,
1747
+ multiSelect: false
1748
+ });
1749
+ });
1750
+ return mapped.length > 0 ? mapped : void 0;
1751
+ }
1752
+ function toCodexUserInputAnswers(answers) {
1753
+ if (!isRecord3(answers)) return void 0;
1754
+ const out = {};
1755
+ for (const [questionId, value] of Object.entries(answers)) {
1756
+ if (typeof value === "string") {
1757
+ out[questionId] = { answers: [value] };
1758
+ continue;
1759
+ }
1760
+ if (Array.isArray(value) && value.every((entry) => typeof entry === "string")) {
1761
+ out[questionId] = { answers: [...value] };
1762
+ continue;
1763
+ }
1764
+ if (isRecord3(value) && Array.isArray(value["answers"]) && value["answers"].every((entry) => typeof entry === "string")) {
1765
+ out[questionId] = { answers: [...value["answers"]] };
1766
+ continue;
1767
+ }
1768
+ return void 0;
1769
+ }
1770
+ return out;
1771
+ }
1772
+ function toCanonicalUserInputAnswers(answers) {
1773
+ return Object.fromEntries(
1774
+ Object.entries(answers).map(([questionId, value]) => [
1775
+ questionId,
1776
+ value.answers.length === 1 ? value.answers[0] : [...value.answers]
1777
+ ])
1778
+ );
1779
+ }
1780
+
1781
+ // src/usage/codexQuota.ts
1782
+ var asRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
1783
+ var asFiniteNumber = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
1784
+ var asNonEmptyString = (value) => typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
1785
+ var clampPercent = (value) => Math.min(100, Math.max(0, value));
1786
+ var epochSecondsToIso = (value) => {
1787
+ const seconds = asFiniteNumber(value);
1788
+ if (seconds === null) return null;
1789
+ const date = new Date(seconds * 1e3);
1790
+ return Number.isNaN(date.getTime()) ? null : date.toISOString();
1791
+ };
1792
+ var SESSION_WINDOW_MINUTES = 5 * 60;
1793
+ var WEEKLY_WINDOW_MINUTES = 7 * 24 * 60;
1794
+ var formatWindowDuration = (minutes) => {
1795
+ if (minutes % (24 * 60) === 0) return `${String(minutes / (24 * 60))}d`;
1796
+ if (minutes % 60 === 0) return `${String(minutes / 60)}h`;
1797
+ return `${String(minutes)}m`;
1798
+ };
1799
+ var classifyWindow = (minutes, slot) => {
1800
+ if (minutes === SESSION_WINDOW_MINUTES) {
1801
+ return { id: "session", label: { key: "usage.window.session" } };
1802
+ }
1803
+ if (minutes === WEEKLY_WINDOW_MINUTES) {
1804
+ return { id: "weekly", label: { key: "usage.window.weekly" } };
1805
+ }
1806
+ if (minutes === null) return { id: slot, label: { key: "usage.window.limit" } };
1763
1807
  return {
1764
- status: "available",
1765
- ...plan === void 0 ? {} : { plan },
1766
- windows,
1767
- balances,
1768
- accountKey,
1769
- // 回包没带摘要(老版本 CLI)就是不知道,不填;填 0 会让界面说「没有可用的重置额度」。
1770
- ...availableCount === null || availableCount < 0 ? {} : { resetCredits: { availableCount: Math.trunc(availableCount) } }
1808
+ id: `window-${String(minutes)}m`,
1809
+ label: { key: "usage.window.duration", values: { duration: formatWindowDuration(minutes) } }
1771
1810
  };
1772
1811
  };
1773
- var codexQuotaFromRateLimits = (snapshot) => {
1774
- const bucket = asRecord(snapshot);
1775
- if (bucket === null) return null;
1776
- const reading = mapCodexRateLimits({ rateLimits: snapshot }, "");
1777
- if (reading === null || reading.status !== "available") return null;
1778
- const main = isMainCodexLimit(bucket);
1779
- const group = main ? MAIN_LIMIT_ID : asNonEmptyString(bucket["limitId"]);
1780
- const replaces = {
1781
- windowGroups: group === null ? [] : [group],
1782
- balanceIds: main ? [CREDITS_BALANCE_ID] : []
1783
- };
1812
+ var mapWindow = (raw, slot, describe) => {
1813
+ const window = asRecord(raw);
1814
+ const usedPercent = asFiniteNumber(window?.["usedPercent"]);
1815
+ if (window === null || usedPercent === null) return null;
1816
+ const rawMinutes = asFiniteNumber(window["windowDurationMins"]);
1817
+ const minutes = rawMinutes !== null && rawMinutes > 0 ? Math.trunc(rawMinutes) : null;
1818
+ const { id, label } = describe(minutes, slot);
1784
1819
  return {
1785
- windows: reading.windows,
1786
- balances: reading.balances,
1787
- ...reading.plan === void 0 ? {} : { plan: reading.plan },
1788
- replaces
1820
+ id,
1821
+ label,
1822
+ usedPercent: clampPercent(usedPercent),
1823
+ resetsAt: epochSecondsToIso(window["resetsAt"]),
1824
+ ...minutes === null ? {} : { windowMs: minutes * 6e4 }
1789
1825
  };
1790
1826
  };
1791
- var classifyRateLimitsError = (message) => {
1792
- const text = message.toLowerCase();
1793
- if (text.includes("chatgpt authentication required")) return "unsupportedLogin";
1794
- if (text.includes("authentication required")) return "missingCredentials";
1795
- if (/\b(401|403)\b|unauthorized|forbidden/.test(text)) return "authRejected";
1796
- if (/\b429\b|too many requests/.test(text)) return "rateLimited";
1797
- return "unreachable";
1827
+ var PLAN_LABELS = {
1828
+ free: "Free",
1829
+ go: "Go",
1830
+ plus: "Plus",
1831
+ pro: "Pro",
1832
+ prolite: "Pro Lite",
1833
+ team: "Team",
1834
+ business: "Business",
1835
+ enterprise: "Enterprise",
1836
+ edu: "Edu"
1798
1837
  };
1799
- var CODEX_OFFICIAL_HOSTS = /* @__PURE__ */ new Set(["chatgpt.com"]);
1800
- var CODEX_DEFAULT_MODEL_PROVIDER = "openai";
1801
- var isOfficialUrl = (value) => {
1802
- const parsed = URL.parse(value.trim());
1803
- return parsed === null ? false : CODEX_OFFICIAL_HOSTS.has(parsed.hostname.toLowerCase());
1838
+ var planLabel = (planType) => {
1839
+ const id = asNonEmptyString(planType);
1840
+ if (id === null || id === "unknown") return void 0;
1841
+ return PLAN_LABELS[id] ?? id.split("_").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
1804
1842
  };
1805
- var readTomlScalar = (raw) => {
1806
- const quoted = /^(["'])(.*?)\1/.exec(raw.trim());
1807
- if (quoted?.[2] !== void 0) return quoted[2];
1808
- const [beforeComment = ""] = raw.split("#");
1809
- return beforeComment.trim();
1843
+ var creditBalance = (value) => {
1844
+ if (typeof value === "number") return Number.isFinite(value) ? value : null;
1845
+ if (typeof value !== "string" || value.trim().length === 0) return null;
1846
+ const numeric = Number(value.trim());
1847
+ return Number.isFinite(numeric) ? numeric : null;
1810
1848
  };
1811
- var TABLE_HEADER = /^\[\s*([^\][]+?)\s*\]\s*(?:#.*)?$/;
1812
- var KEY_VALUE = /^([A-Za-z0-9_.\-"']+)\s*=\s*(.*)$/;
1813
- var BASE_URL_KEYS = /* @__PURE__ */ new Set([
1814
- "chatgpt_base_url",
1815
- "openai_base_url",
1816
- "base_url"
1817
- ]);
1818
- var codexConfigSignal = (contents, options) => {
1819
- let table = null;
1820
- let selectedProvider = CODEX_DEFAULT_MODEL_PROVIDER;
1821
- const providerBaseUrls = /* @__PURE__ */ new Map();
1822
- for (const rawLine of contents.split("\n")) {
1823
- const line = rawLine.trim();
1824
- if (line.length === 0 || line.startsWith("#")) continue;
1849
+ var MAIN_LIMIT_ID = "codex";
1850
+ var CREDITS_BALANCE_ID = "credits";
1851
+ var isMainCodexLimit = (snapshot) => {
1852
+ const limitId = asNonEmptyString(asRecord(snapshot)?.["limitId"]);
1853
+ return limitId === null || limitId === MAIN_LIMIT_ID;
1854
+ };
1855
+ var mapCodexSnapshotWindows = (snapshot) => {
1856
+ const bucket = asRecord(snapshot);
1857
+ if (bucket === null || !isMainCodexLimit(bucket)) return [];
1858
+ const windows = [];
1859
+ const takenIds = /* @__PURE__ */ new Set();
1860
+ for (const slot of ["primary", "secondary"]) {
1861
+ const window = mapWindow(bucket[slot], slot, classifyWindow);
1862
+ if (window === null) continue;
1863
+ const id = takenIds.has(window.id) ? `${window.id}:${slot}` : window.id;
1864
+ takenIds.add(id);
1865
+ windows.push({ ...window, id, group: MAIN_LIMIT_ID });
1866
+ }
1867
+ return windows;
1868
+ };
1869
+ var mapCodexRateLimits = (response, accountKey) => {
1870
+ const root = asRecord(response);
1871
+ const legacy = asRecord(root?.["rateLimits"]);
1872
+ const byLimitId = asRecord(root?.["rateLimitsByLimitId"]);
1873
+ const codexBucket = asRecord(byLimitId?.[MAIN_LIMIT_ID]);
1874
+ const main = codexBucket ?? (legacy !== null && isMainCodexLimit(legacy) ? legacy : null);
1875
+ if (root === null || legacy === null && codexBucket === null) return null;
1876
+ const windows = mapCodexSnapshotWindows(main);
1877
+ const takenIds = new Set(windows.map((window) => window.id));
1878
+ const push = (window, slot) => {
1879
+ if (window === null) return;
1880
+ const id = takenIds.has(window.id) ? `${window.id}:${slot}` : window.id;
1881
+ takenIds.add(id);
1882
+ windows.push({ ...window, id });
1883
+ };
1884
+ const others = /* @__PURE__ */ new Map();
1885
+ for (const [limitId, rawBucket] of Object.entries(byLimitId ?? {})) {
1886
+ const bucket = asRecord(rawBucket);
1887
+ if (limitId !== MAIN_LIMIT_ID && bucket !== null) others.set(limitId, bucket);
1888
+ }
1889
+ const legacyLimitId = asNonEmptyString(legacy?.["limitId"]);
1890
+ if (legacy !== null && legacyLimitId !== null && !isMainCodexLimit(legacy)) {
1891
+ if (!others.has(legacyLimitId)) others.set(legacyLimitId, legacy);
1892
+ }
1893
+ for (const [limitId, bucket] of others) {
1894
+ const name = asNonEmptyString(bucket["limitName"]) ?? limitId;
1895
+ for (const slot of ["primary", "secondary"]) {
1896
+ const window = mapWindow(bucket[slot], slot, (minutes) => ({
1897
+ id: `${limitId}:${slot}`,
1898
+ label: minutes === null ? { text: name } : {
1899
+ key: "usage.window.named",
1900
+ values: { name, duration: formatWindowDuration(minutes) }
1901
+ }
1902
+ }));
1903
+ push(window === null ? null : { ...window, group: limitId }, slot);
1904
+ }
1905
+ }
1906
+ const balances = [];
1907
+ const credits = asRecord(main?.["credits"]);
1908
+ const balance = creditBalance(credits?.["balance"]);
1909
+ if (credits !== null && credits["unlimited"] !== true && balance !== null) {
1910
+ if (credits["hasCredits"] === true || balance > 0) {
1911
+ balances.push({
1912
+ id: CREDITS_BALANCE_ID,
1913
+ label: { key: "usage.balance.credits" },
1914
+ remaining: balance,
1915
+ used: null,
1916
+ limit: null,
1917
+ unit: { kind: "credits" },
1918
+ resetsAt: null
1919
+ });
1920
+ }
1921
+ }
1922
+ const plan = planLabel((main ?? legacy)?.["planType"]);
1923
+ const resetSummary = asRecord(root["rateLimitResetCredits"]);
1924
+ const availableCount = asFiniteNumber(resetSummary?.["availableCount"]);
1925
+ return {
1926
+ status: "available",
1927
+ ...plan === void 0 ? {} : { plan },
1928
+ windows,
1929
+ balances,
1930
+ accountKey,
1931
+ // 回包没带摘要(老版本 CLI)就是不知道,不填;填 0 会让界面说「没有可用的重置额度」。
1932
+ ...availableCount === null || availableCount < 0 ? {} : { resetCredits: { availableCount: Math.trunc(availableCount) } }
1933
+ };
1934
+ };
1935
+ var codexQuotaFromRateLimits = (snapshot) => {
1936
+ const bucket = asRecord(snapshot);
1937
+ if (bucket === null) return null;
1938
+ const reading = mapCodexRateLimits({ rateLimits: snapshot }, "");
1939
+ if (reading === null || reading.status !== "available") return null;
1940
+ const main = isMainCodexLimit(bucket);
1941
+ const group = main ? MAIN_LIMIT_ID : asNonEmptyString(bucket["limitId"]);
1942
+ const replaces = {
1943
+ windowGroups: group === null ? [] : [group],
1944
+ balanceIds: main ? [CREDITS_BALANCE_ID] : []
1945
+ };
1946
+ return {
1947
+ windows: reading.windows,
1948
+ balances: reading.balances,
1949
+ ...reading.plan === void 0 ? {} : { plan: reading.plan },
1950
+ replaces
1951
+ };
1952
+ };
1953
+ var classifyRateLimitsError = (message) => {
1954
+ const text = message.toLowerCase();
1955
+ if (text.includes("chatgpt authentication required")) return "unsupportedLogin";
1956
+ if (text.includes("authentication required")) return "missingCredentials";
1957
+ if (/\b(401|403)\b|unauthorized|forbidden/.test(text)) return "authRejected";
1958
+ if (/\b429\b|too many requests/.test(text)) return "rateLimited";
1959
+ return "unreachable";
1960
+ };
1961
+ var CODEX_OFFICIAL_HOSTS = /* @__PURE__ */ new Set(["chatgpt.com"]);
1962
+ var CODEX_DEFAULT_MODEL_PROVIDER = "openai";
1963
+ var isOfficialUrl = (value) => {
1964
+ const parsed = URL.parse(value.trim());
1965
+ return parsed === null ? false : CODEX_OFFICIAL_HOSTS.has(parsed.hostname.toLowerCase());
1966
+ };
1967
+ var readTomlScalar = (raw) => {
1968
+ const quoted = /^(["'])(.*?)\1/.exec(raw.trim());
1969
+ if (quoted?.[2] !== void 0) return quoted[2];
1970
+ const [beforeComment = ""] = raw.split("#");
1971
+ return beforeComment.trim();
1972
+ };
1973
+ var TABLE_HEADER = /^\[\s*([^\][]+?)\s*\]\s*(?:#.*)?$/;
1974
+ var KEY_VALUE = /^([A-Za-z0-9_.\-"']+)\s*=\s*(.*)$/;
1975
+ var BASE_URL_KEYS = /* @__PURE__ */ new Set([
1976
+ "chatgpt_base_url",
1977
+ "openai_base_url",
1978
+ "base_url"
1979
+ ]);
1980
+ var codexConfigSignal = (contents, options) => {
1981
+ let table = null;
1982
+ let selectedProvider = CODEX_DEFAULT_MODEL_PROVIDER;
1983
+ const providerBaseUrls = /* @__PURE__ */ new Map();
1984
+ for (const rawLine of contents.split("\n")) {
1985
+ const line = rawLine.trim();
1986
+ if (line.length === 0 || line.startsWith("#")) continue;
1825
1987
  const header = TABLE_HEADER.exec(line);
1826
1988
  if (header !== null) {
1827
1989
  table = header[1] ?? "";
@@ -1931,402 +2093,51 @@ var codexUsageLimitMessage = (snapshot, atIso, providerMessage) => {
1931
2093
  var isUsageLimitExceeded = (error) => asRecord2(error)?.["codexErrorInfo"] === "usageLimitExceeded";
1932
2094
 
1933
2095
  // src/events.ts
1934
- var CODEX_PROVIDER = "codex";
1935
- var EVENT_ID_PREFIX = `codex-evt-${NodeCrypto.randomUUID()}`;
1936
- var eventSeq = 0;
1937
- function nextEventId() {
1938
- eventSeq += 1;
1939
- return `${EVENT_ID_PREFIX}-${eventSeq}`;
1940
- }
1941
- function nowIso2() {
1942
- return (/* @__PURE__ */ new Date()).toISOString();
1943
- }
1944
- function itemIdOf(params) {
1945
- if (!isRecord3(params)) return void 0;
1946
- const item = isRecord3(params["item"]) ? params["item"] : params;
1947
- return asString(item["id"]) ?? asString(params["itemId"]) ?? void 0;
1948
- }
1949
- function itemStatusOf(params) {
1950
- if (!isRecord3(params)) return void 0;
1951
- const item = isRecord3(params["item"]) ? params["item"] : params;
1952
- return asString(item["status"]) ?? void 0;
1953
- }
1954
- function turnIdOf(params) {
1955
- if (!isRecord3(params)) return void 0;
1956
- return asString(params["turnId"]) ?? (isRecord3(params["turn"]) ? asString(params["turn"]["id"]) : null) ?? void 0;
1957
- }
1958
- function itemTitle(params) {
1959
- if (!isRecord3(params)) return void 0;
1960
- const item = isRecord3(params["item"]) ? params["item"] : params;
1961
- return trimText(item["title"]) ?? trimText(item["name"]) ?? trimText(item["command"]) ?? trimText(item["summary"]) ?? void 0;
1962
- }
1963
- function itemDetail(params) {
1964
- if (!isRecord3(params)) return void 0;
1965
- const item = isRecord3(params["item"]) ? params["item"] : params;
1966
- const query = isRecord3(item["action"]) ? item["action"]["queries"] : item["query"];
1967
- if (Array.isArray(query)) {
1968
- const joined = query.filter((entry) => typeof entry === "string").join(", ");
1969
- if (joined.length > 0) return joined;
2096
+ function mapNotificationToEvents(input) {
2097
+ const { method, params, threadId, providerInstanceId, rateLimits } = input;
2098
+ if (method.startsWith("collabAgent/")) {
2099
+ return mapCollabAgentEvent({ method, params, rootThreadId: threadId, providerInstanceId });
1970
2100
  }
1971
- return trimText(item["text"]) ?? trimText(item["summary"]) ?? trimText(item["path"]) ?? trimText(item["savedPath"]) ?? trimText(item["prompt"]) ?? (typeof query === "string" ? query : void 0);
1972
- }
1973
- function toTurnStatus(status) {
1974
- switch (status) {
1975
- case "failed":
1976
- return "failed";
1977
- case "cancelled":
1978
- case "canceled":
1979
- return "cancelled";
1980
- case "interrupted":
1981
- return "interrupted";
1982
- default:
1983
- return "completed";
2101
+ if (method === "error") {
2102
+ if (isUsageLimitExceeded(isRecord3(params) ? params["error"] : void 0)) return [];
2103
+ const willRetry = isRecord3(params) && params["willRetry"] === true;
2104
+ const nested = isRecord3(params) && isRecord3(params["error"]) ? params["error"] : void 0;
2105
+ const message = (nested ? trimText(nested["message"]) : void 0) ?? (isRecord3(params) ? trimText(params["message"]) : void 0) ?? "Codex error";
2106
+ const payload = { message };
2107
+ if (!willRetry) payload["class"] = "provider_error";
2108
+ if (params !== void 0) payload["detail"] = params;
2109
+ return [
2110
+ baseEvent({
2111
+ type: willRetry ? "runtime.warning" : "runtime.error",
2112
+ threadId,
2113
+ payload,
2114
+ turnId: turnIdOf(params),
2115
+ method,
2116
+ providerInstanceId
2117
+ })
2118
+ ];
1984
2119
  }
1985
- }
1986
- var COLLAB_CHILD_STATUSES = /* @__PURE__ */ new Set([
1987
- "running",
1988
- "idle",
1989
- "failed",
1990
- "interrupted",
1991
- "cancelled"
1992
- ]);
1993
- function asCollabChildStatus(value) {
1994
- return typeof value === "string" && COLLAB_CHILD_STATUSES.has(value) ? value : void 0;
1995
- }
1996
- function collabTurnEndStatus(turnStatus) {
1997
- const state = toTurnStatus(turnStatus);
1998
- return state === "completed" ? "idle" : state;
1999
- }
2000
- function threadStateFromStatus(status) {
2001
- const type = isRecord3(status) ? asString(status["type"]) : asString(status);
2002
- if (type === "idle") return "idle";
2003
- if (type === "systemError" || type === "error") return "error";
2004
- return "active";
2005
- }
2006
- function codexCompactionId(threadId, turnId) {
2007
- return `codex:${threadId}:${turnId ?? "session"}`;
2008
- }
2009
- function normalizeCodexTokenUsage(tokenUsage) {
2010
- if (!isRecord3(tokenUsage)) return void 0;
2011
- const last = isRecord3(tokenUsage["last"]) ? tokenUsage["last"] : void 0;
2012
- const total = isRecord3(tokenUsage["total"]) ? tokenUsage["total"] : void 0;
2013
- const count = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
2014
- const usedTokens = count(last?.["totalTokens"]);
2015
- if (usedTokens === void 0 || usedTokens <= 0) return void 0;
2016
- const totalProcessedTokens = count(total?.["totalTokens"]);
2017
- const maxTokens = count(tokenUsage["modelContextWindow"]);
2018
- const inputTokens = count(last?.["inputTokens"]);
2019
- const cachedInputTokens = count(last?.["cachedInputTokens"]);
2020
- const outputTokens = count(last?.["outputTokens"]);
2021
- const reasoningOutputTokens = count(last?.["reasoningOutputTokens"]);
2022
- return {
2023
- usedTokens,
2024
- lastUsedTokens: usedTokens,
2025
- ...totalProcessedTokens !== void 0 && totalProcessedTokens > usedTokens ? { totalProcessedTokens } : {},
2026
- ...maxTokens !== void 0 && maxTokens > 0 ? { maxTokens } : {},
2027
- ...inputTokens !== void 0 ? { inputTokens, lastInputTokens: inputTokens } : {},
2028
- ...cachedInputTokens !== void 0 ? { cachedInputTokens, lastCachedInputTokens: cachedInputTokens } : {},
2029
- ...outputTokens !== void 0 ? { outputTokens, lastOutputTokens: outputTokens } : {},
2030
- ...reasoningOutputTokens !== void 0 ? { reasoningOutputTokens, lastReasoningOutputTokens: reasoningOutputTokens } : {},
2031
- compactsAutomatically: true
2032
- };
2033
- }
2034
- function turnCompletedControlFlow(params) {
2035
- const turn = isRecord3(params) ? isRecord3(params["turn"]) ? params["turn"] : params : {};
2036
- const state = toTurnStatus(asString(turn["status"]) ?? void 0);
2037
- const error = isRecord3(turn["error"]) ? trimText(turn["error"]["message"]) : void 0;
2038
- return error ? { state, errorMessage: error } : { state };
2039
- }
2040
- function toUserInputQuestions(params) {
2041
- if (!isRecord3(params)) return void 0;
2042
- const questions = Array.isArray(params["questions"]) ? params["questions"] : [];
2043
- const mapped = [];
2044
- for (const question of questions) {
2045
- if (!isRecord3(question)) return void 0;
2046
- const id = asString(question["id"]);
2047
- const header = asString(question["header"]);
2048
- const text = asString(question["question"]);
2049
- const rawOptions = Array.isArray(question["options"]) ? question["options"] : [];
2050
- const options = [];
2051
- for (const option of rawOptions) {
2052
- if (!isRecord3(option)) continue;
2053
- const label = trimText(option["label"]);
2054
- if (!label) continue;
2055
- options.push({
2056
- ...asString(option["id"]) ? { id: asString(option["id"]) } : {},
2057
- label,
2058
- description: trimText(option["description"]) ?? ""
2059
- });
2060
- }
2061
- if (!id || !header || !text || options.length === 0) return void 0;
2062
- mapped.push({
2063
- id,
2064
- header,
2065
- question: text,
2066
- options,
2067
- multiSelect: false
2068
- });
2069
- }
2070
- return mapped.length > 0 ? mapped : void 0;
2071
- }
2072
- function toAsyncQuestions(params) {
2073
- if (!isRecord3(params)) return void 0;
2074
- const item = isRecord3(params["item"]) ? params["item"] : params;
2075
- if (item["delivery"] !== "async" || !Array.isArray(item["questions"])) return void 0;
2076
- const mapped = [];
2077
- item["questions"].forEach((question, index) => {
2078
- if (!isRecord3(question)) return;
2079
- const title = trimText(question["title"]);
2080
- if (!title) return;
2081
- const labels = Array.isArray(question["options"]) ? question["options"] : [];
2082
- const options = labels.map((label) => trimText(label)).filter((label) => label !== void 0).map((label) => ({ label, description: "" }));
2083
- mapped.push({
2084
- id: `q${index}`,
2085
- header: title,
2086
- question: title,
2087
- options,
2088
- allowFreeform: true,
2089
- multiSelect: false
2090
- });
2091
- });
2092
- return mapped.length > 0 ? mapped : void 0;
2093
- }
2094
- function toCodexUserInputAnswers(answers) {
2095
- if (!isRecord3(answers)) return void 0;
2096
- const out = {};
2097
- for (const [questionId, value] of Object.entries(answers)) {
2098
- if (typeof value === "string") {
2099
- out[questionId] = { answers: [value] };
2100
- continue;
2101
- }
2102
- if (Array.isArray(value) && value.every((entry) => typeof entry === "string")) {
2103
- out[questionId] = { answers: [...value] };
2104
- continue;
2105
- }
2106
- if (isRecord3(value) && Array.isArray(value["answers"]) && value["answers"].every((entry) => typeof entry === "string")) {
2107
- out[questionId] = { answers: [...value["answers"]] };
2108
- continue;
2120
+ if (method === "process/stderr") {
2121
+ const text = isRecord3(params) ? trimText(params["text"]) ?? "" : "";
2122
+ if (!text) return [];
2123
+ if (isFatalStderr(text)) {
2124
+ return [
2125
+ baseEvent({
2126
+ type: "runtime.error",
2127
+ threadId,
2128
+ payload: { message: text },
2129
+ method,
2130
+ providerInstanceId
2131
+ })
2132
+ ];
2109
2133
  }
2110
- return void 0;
2111
- }
2112
- return out;
2113
- }
2114
- function toCanonicalUserInputAnswers(answers) {
2115
- return Object.fromEntries(
2116
- Object.entries(answers).map(([questionId, value]) => [
2117
- questionId,
2118
- value.answers.length === 1 ? value.answers[0] : [...value.answers]
2119
- ])
2120
- );
2121
- }
2122
- function pathLeaf(value) {
2123
- if (!value) return void 0;
2124
- const parts = value.split("/").filter((part) => part.length > 0);
2125
- return parts.at(-1);
2126
- }
2127
- function eventRaw(source, method, payload) {
2128
- return { source, method, payload };
2129
- }
2130
- function baseEvent(input) {
2131
- return {
2132
- eventId: nextEventId(),
2133
- provider: CODEX_PROVIDER,
2134
- threadId: input.threadId,
2135
- createdAt: nowIso2(),
2136
- type: input.type,
2137
- payload: input.payload,
2138
- ...input.providerInstanceId ? { providerInstanceId: input.providerInstanceId } : {},
2139
- ...input.turnId ? { turnId: input.turnId } : {},
2140
- ...input.itemId ? { itemId: input.itemId } : {},
2141
- ...input.requestId ? { requestId: input.requestId } : {},
2142
- ...input.method ? {
2143
- raw: eventRaw(
2144
- input.source ?? "codex.app-server.notification",
2145
- input.method,
2146
- input.payload
2147
- )
2148
- } : {}
2149
- };
2150
- }
2151
- function mapCollabAgentEvent(input) {
2152
- if (!isRecord3(input.params)) return [];
2153
- const agentThreadId = asString(input.params["agentThreadId"]) ?? asString(input.params["threadId"]);
2154
- if (!agentThreadId) return [];
2155
- const agentPath = asString(input.params["agentPath"]) ?? asString(input.params["path"]) ?? void 0;
2156
- const nickname = asString(input.params["agentNickname"]) ?? asString(input.params["nickname"]);
2157
- const role = asString(input.params["agentRole"]) ?? asString(input.params["role"]) ?? pathLeaf(agentPath) ?? "general-purpose";
2158
- const knownName = nickname ?? pathLeaf(agentPath);
2159
- const title = knownName ?? agentThreadId;
2160
- const model = trimText(input.params["model"]);
2161
- const effort = trimText(input.params["effort"]);
2162
- const linkage = {
2163
- role,
2164
- ...knownName ? { title: knownName } : {},
2165
- ...model ? { model } : {},
2166
- ...effort ? { effort } : {},
2167
- ...agentPath ? { agentPath } : {},
2168
- timelineBypass: true,
2169
- rosterKind: "subagent"
2170
- };
2171
- const threadId = input.rootThreadId;
2172
- const method = input.method;
2173
- if (method === "collabAgent/started" || method === "task.started") {
2174
- return [
2175
- baseEvent({
2176
- type: "task.started",
2177
- threadId,
2178
- payload: { taskId: agentThreadId, ...linkage },
2179
- method,
2180
- providerInstanceId: input.providerInstanceId
2181
- })
2182
- ];
2183
- }
2184
- if (method === "collabAgent/progress" || method === "task.progress") {
2185
- const summary = trimText(input.params["detail"]) ?? trimText(input.params["text"]);
2186
- return [
2187
- baseEvent({
2188
- type: "task.progress",
2189
- threadId,
2190
- // 契约 TaskProgressPayload 的 description 是必填;子代理行靠它显示名字。
2191
- payload: {
2192
- taskId: agentThreadId,
2193
- description: title,
2194
- ...summary ? { summary } : {},
2195
- ...linkage
2196
- },
2197
- method,
2198
- providerInstanceId: input.providerInstanceId
2199
- })
2200
- ];
2201
- }
2202
- if (method === "collabAgent/completed" || method === "task.completed") {
2203
- const status = collabTurnEndStatus(asString(input.params["turnStatus"]) ?? void 0);
2204
- return [
2205
- baseEvent({
2206
- type: "task.updated",
2207
- threadId,
2208
- payload: { taskId: agentThreadId, status, ...linkage },
2209
- method,
2210
- providerInstanceId: input.providerInstanceId
2211
- })
2212
- ];
2213
- }
2214
- if (method === "collabAgent/statusChanged") {
2215
- const raw = asString(input.params["status"]);
2216
- const status = raw === "systemError" || raw === "error" ? "failed" : raw === "idle" ? "idle" : "running";
2217
- return [
2218
- baseEvent({
2219
- type: "task.updated",
2220
- threadId,
2221
- payload: { taskId: agentThreadId, status, ...linkage },
2222
- method,
2223
- providerInstanceId: input.providerInstanceId
2224
- })
2225
- ];
2226
- }
2227
- if (method === "collabAgent/metadataUpdated") {
2228
2134
  return [
2229
2135
  baseEvent({
2230
- type: "task.updated",
2231
- threadId,
2232
- payload: { taskId: agentThreadId, ...linkage },
2233
- method,
2234
- providerInstanceId: input.providerInstanceId
2235
- })
2236
- ];
2237
- }
2238
- if (method === "collabAgent/metadata") {
2239
- const status = asCollabChildStatus(input.params["status"]);
2240
- if (status === void 0) return [];
2241
- return [
2242
- baseEvent({
2243
- type: "task.updated",
2136
+ type: "runtime.warning",
2244
2137
  threadId,
2245
- payload: { taskId: agentThreadId, status, ...linkage },
2138
+ payload: { message: text },
2246
2139
  method,
2247
- providerInstanceId: input.providerInstanceId
2248
- })
2249
- ];
2250
- }
2251
- if (method === "collabAgent/tokenUsage") {
2252
- const tokenUsage = isRecord3(input.params["tokenUsage"]) ? input.params["tokenUsage"] : void 0;
2253
- const total = tokenUsage && isRecord3(tokenUsage["total"]) ? tokenUsage["total"] : void 0;
2254
- const count = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
2255
- const totalTokens = total ? count(total["totalTokens"]) : void 0;
2256
- if (total === void 0 || totalTokens === void 0) return [];
2257
- const inputTokens = count(total["inputTokens"]);
2258
- const cachedInputTokens = count(total["cachedInputTokens"]);
2259
- const outputTokens = count(total["outputTokens"]);
2260
- const reasoningOutputTokens = count(total["reasoningOutputTokens"]);
2261
- const typedUsage = {
2262
- totalTokens,
2263
- ...inputTokens !== void 0 ? { inputTokens } : {},
2264
- ...cachedInputTokens !== void 0 ? { cachedInputTokens } : {},
2265
- ...outputTokens !== void 0 ? { outputTokens } : {},
2266
- ...reasoningOutputTokens !== void 0 ? { reasoningOutputTokens } : {}
2267
- };
2268
- return [
2269
- baseEvent({
2270
- type: "task.progress",
2271
- threadId,
2272
- payload: {
2273
- taskId: agentThreadId,
2274
- description: title,
2275
- typedUsage,
2276
- ...linkage
2277
- },
2278
- method,
2279
- providerInstanceId: input.providerInstanceId
2280
- })
2281
- ];
2282
- }
2283
- return [];
2284
- }
2285
- function mapNotificationToEvents(input) {
2286
- const { method, params, threadId, providerInstanceId, rateLimits } = input;
2287
- if (method.startsWith("collabAgent/")) {
2288
- return mapCollabAgentEvent({ method, params, rootThreadId: threadId, providerInstanceId });
2289
- }
2290
- if (method === "error") {
2291
- if (isUsageLimitExceeded(isRecord3(params) ? params["error"] : void 0)) return [];
2292
- const willRetry = isRecord3(params) && params["willRetry"] === true;
2293
- const nested = isRecord3(params) && isRecord3(params["error"]) ? params["error"] : void 0;
2294
- const message = (nested ? trimText(nested["message"]) : void 0) ?? (isRecord3(params) ? trimText(params["message"]) : void 0) ?? "Codex error";
2295
- const payload = { message };
2296
- if (!willRetry) payload["class"] = "provider_error";
2297
- if (params !== void 0) payload["detail"] = params;
2298
- return [
2299
- baseEvent({
2300
- type: willRetry ? "runtime.warning" : "runtime.error",
2301
- threadId,
2302
- payload,
2303
- turnId: turnIdOf(params),
2304
- method,
2305
- providerInstanceId
2306
- })
2307
- ];
2308
- }
2309
- if (method === "process/stderr") {
2310
- const text = isRecord3(params) ? trimText(params["text"]) ?? "" : "";
2311
- if (!text) return [];
2312
- if (isFatalStderr(text)) {
2313
- return [
2314
- baseEvent({
2315
- type: "runtime.error",
2316
- threadId,
2317
- payload: { message: text },
2318
- method,
2319
- providerInstanceId
2320
- })
2321
- ];
2322
- }
2323
- return [
2324
- baseEvent({
2325
- type: "runtime.warning",
2326
- threadId,
2327
- payload: { message: text },
2328
- method,
2329
- providerInstanceId
2140
+ providerInstanceId
2330
2141
  })
2331
2142
  ];
2332
2143
  }
@@ -2851,6 +2662,203 @@ function mapNotificationToEvents(input) {
2851
2662
  return [];
2852
2663
  }
2853
2664
  }
2665
+
2666
+ // src/approvalMapping.ts
2667
+ function fileChangeEntries(params) {
2668
+ if (!isRecord3(params)) return [];
2669
+ const item = isRecord3(params["item"]) ? params["item"] : params;
2670
+ const changes = item["changes"] ?? item["fileChanges"] ?? params["fileChanges"];
2671
+ if (!isRecord3(changes) && !Array.isArray(changes)) return [];
2672
+ const entries = Array.isArray(changes) ? changes : Object.entries(changes).map(([path, change]) => ({
2673
+ path,
2674
+ ...isRecord3(change) ? change : {}
2675
+ }));
2676
+ const out = [];
2677
+ for (const entry of entries) {
2678
+ if (!isRecord3(entry)) continue;
2679
+ const path = asString(entry["path"]) ?? asString(entry["file"]);
2680
+ if (!path) continue;
2681
+ out.push({
2682
+ path,
2683
+ kind: (asString(entry["type"]) ?? asString(entry["kind"]) ?? "update").toLowerCase(),
2684
+ movePath: asString(entry["move_path"]) ?? asString(entry["movePath"]),
2685
+ diff: asString(entry["unified_diff"]) ?? asString(entry["unifiedDiff"]),
2686
+ content: asString(entry["content"])
2687
+ });
2688
+ }
2689
+ return out;
2690
+ }
2691
+ var MAX_DESCRIBED_FILE_CHANGES = 20;
2692
+ function describeFileChanges(params) {
2693
+ const entries = fileChangeEntries(params).toSorted(
2694
+ (left, right) => left.path.localeCompare(right.path)
2695
+ );
2696
+ if (entries.length === 0) return void 0;
2697
+ const lines = entries.slice(0, MAX_DESCRIBED_FILE_CHANGES).map(
2698
+ (entry) => entry.movePath ? `${entry.kind} ${entry.path} -> ${entry.movePath}` : `${entry.kind} ${entry.path}`
2699
+ );
2700
+ const remaining = entries.length - lines.length;
2701
+ return remaining > 0 ? `${lines.join("\n")}
2702
+ +${remaining} more` : lines.join("\n");
2703
+ }
2704
+ function toApplyPatchFileChanges(params) {
2705
+ const out = [];
2706
+ for (const { path, kind, movePath, diff, content } of fileChangeEntries(params)) {
2707
+ if (kind === "add") {
2708
+ out.push({
2709
+ path,
2710
+ changeKind: "add",
2711
+ ...content !== void 0 ? { content: { kind: "replace", newText: content } } : {}
2712
+ });
2713
+ } else if (kind === "delete") {
2714
+ out.push({
2715
+ path,
2716
+ changeKind: "delete",
2717
+ ...content !== void 0 ? { content: { kind: "replace", oldText: content, newText: "" } } : {}
2718
+ });
2719
+ } else if (movePath) {
2720
+ out.push({
2721
+ path,
2722
+ changeKind: "rename",
2723
+ ...diff ? { content: { kind: "unified", diff } } : {}
2724
+ });
2725
+ } else {
2726
+ out.push({
2727
+ path,
2728
+ changeKind: "modify",
2729
+ ...diff ? { content: { kind: "unified", diff } } : {}
2730
+ });
2731
+ }
2732
+ }
2733
+ return out;
2734
+ }
2735
+ function describeCommandApproval(params) {
2736
+ const record = isRecord3(params) ? params : {};
2737
+ const commandValue = record["command"];
2738
+ const command = Array.isArray(commandValue) ? commandValue.filter((entry) => typeof entry === "string").join(" ") : asString(commandValue) ?? "";
2739
+ const cwd = asString(record["cwd"]);
2740
+ const reason = trimText(record["reason"]);
2741
+ const callId = asString(record["callId"]);
2742
+ return {
2743
+ command,
2744
+ ...cwd ? { cwd } : {},
2745
+ ...reason ? { reason } : {},
2746
+ ...callId ? { callId } : {}
2747
+ };
2748
+ }
2749
+ function describeFileChangeApproval(params) {
2750
+ const record = isRecord3(params) ? params : {};
2751
+ const reason = trimText(record["reason"]);
2752
+ const grantRoot = asString(record["grantRoot"]);
2753
+ const callId = asString(record["callId"]);
2754
+ const detail = reason ?? describeFileChanges(record) ?? trimText(grantRoot);
2755
+ return {
2756
+ ...reason ? { reason } : {},
2757
+ ...detail ? { detail } : {},
2758
+ ...grantRoot ? { grantRoot } : {},
2759
+ ...callId ? { callId } : {},
2760
+ fileChanges: toApplyPatchFileChanges(record)
2761
+ };
2762
+ }
2763
+ function describePermissionsApproval(params) {
2764
+ const record = isRecord3(params) ? params : {};
2765
+ const permissions = isRecord3(record["permissions"]) ? record["permissions"] : {};
2766
+ const fileSystem = isRecord3(permissions["fileSystem"]) ? permissions["fileSystem"] : {};
2767
+ const network = isRecord3(permissions["network"]) ? permissions["network"] : {};
2768
+ const pathsOf = (value) => Array.isArray(value) ? value.flatMap((entry) => typeof entry === "string" && entry.length > 0 ? [entry] : []) : [];
2769
+ const read = pathsOf(fileSystem["read"]);
2770
+ const write = pathsOf(fileSystem["write"]);
2771
+ const entries = Array.isArray(fileSystem["entries"]) ? fileSystem["entries"] : [];
2772
+ for (const entry of entries) {
2773
+ if (!isRecord3(entry) || !isRecord3(entry["path"])) continue;
2774
+ const target = entry["path"];
2775
+ const path = target["type"] === "path" ? asString(target["path"]) : target["type"] === "glob_pattern" ? asString(target["pattern"]) : void 0;
2776
+ if (!path) continue;
2777
+ if (entry["access"] === "read") read.push(path);
2778
+ if (entry["access"] === "write") write.push(path);
2779
+ }
2780
+ const reason = trimText(record["reason"]);
2781
+ return {
2782
+ ...reason ? { reason } : {},
2783
+ read: [...new Set(read)],
2784
+ write: [...new Set(write)],
2785
+ network: network["enabled"] === true
2786
+ };
2787
+ }
2788
+ function toPermissionsResponse(decision, params, persistent) {
2789
+ if (decision !== "allow") return { permissions: {} };
2790
+ const requested = isRecord3(params) && isRecord3(params["permissions"]) ? params["permissions"] : {};
2791
+ return {
2792
+ permissions: {
2793
+ ...requested["fileSystem"] !== void 0 ? { fileSystem: requested["fileSystem"] } : {},
2794
+ ...requested["network"] !== void 0 ? { network: requested["network"] } : {}
2795
+ },
2796
+ scope: persistent ? "session" : "turn"
2797
+ };
2798
+ }
2799
+ function describeMcpElicitation(params) {
2800
+ const record = isRecord3(params) ? params : {};
2801
+ const appName = asString(record["appName"]) ?? asString(record["serverName"]) ?? void 0;
2802
+ const serverName = asString(record["serverName"]);
2803
+ const message = trimText(record["message"]);
2804
+ const url = asString(record["url"]) ?? asString(record["authorizationUrl"]);
2805
+ const identity = appName ?? url ?? "unknown";
2806
+ return {
2807
+ ...appName ? { appName } : {},
2808
+ ...serverName ? { serverName } : {},
2809
+ ...message ? { message } : {},
2810
+ ...url ? { url } : {},
2811
+ identity,
2812
+ mode: url ? "url" : "form",
2813
+ ...record["requestedSchema"] !== void 0 ? { schema: record["requestedSchema"] } : {}
2814
+ };
2815
+ }
2816
+ function persistFromMeta(meta) {
2817
+ if (!isRecord3(meta)) return void 0;
2818
+ const persist = asString(meta["persist"]) ?? asString(meta["persistence"]);
2819
+ if (persist === "session" || persist === "acceptForSession") return "session";
2820
+ if (persist === "always" || persist === "permanent" || persist === "forever" || persist === "persistent" || persist === "acceptAlways") {
2821
+ return "always";
2822
+ }
2823
+ return void 0;
2824
+ }
2825
+ function toMcpElicitationResponse(decision, params) {
2826
+ const described = describeMcpElicitation(params);
2827
+ if (decision !== "allow" || described.mode === "url") {
2828
+ return {
2829
+ action: decision === "allow" ? "decline" : decision === "cancel" ? "cancel" : "decline"
2830
+ };
2831
+ }
2832
+ const schema = isRecord3(described.schema) ? described.schema : void 0;
2833
+ const properties = schema && isRecord3(schema["properties"]) ? schema["properties"] : {};
2834
+ const required = schema && Array.isArray(schema["required"]) ? schema["required"] : [];
2835
+ const content = {};
2836
+ for (const [key, spec] of Object.entries(properties)) {
2837
+ if (!isRecord3(spec)) continue;
2838
+ if (Array.isArray(spec["oneOf"]) || Array.isArray(spec["enum"])) {
2839
+ const options = Array.isArray(spec["oneOf"]) ? spec["oneOf"] : Array.isArray(spec["enum"]) ? spec["enum"] : [];
2840
+ const first = options[0];
2841
+ content[key] = isRecord3(first) ? first["const"] ?? first["value"] ?? first : first;
2842
+ } else if (spec["type"] === "boolean") {
2843
+ content[key] = true;
2844
+ } else if (typeof spec["default"] !== "undefined") {
2845
+ content[key] = spec["default"];
2846
+ }
2847
+ }
2848
+ for (const key of required) {
2849
+ if (typeof key === "string" && !(key in content)) {
2850
+ return { action: "decline" };
2851
+ }
2852
+ }
2853
+ const persist = persistFromMeta(isRecord3(params) ? params["_meta"] : void 0);
2854
+ return {
2855
+ action: "accept",
2856
+ content,
2857
+ ...persist ? { _meta: { persist } } : {}
2858
+ };
2859
+ }
2860
+
2861
+ // src/events/serverRequests.ts
2854
2862
  function toRequestTypeFromKind(kind) {
2855
2863
  switch (kind) {
2856
2864
  case "command":
@@ -2995,311 +3003,90 @@ var windowsPath = (path) => path.replaceAll("/", "\\").toLowerCase();
2995
3003
  function basename(path) {
2996
3004
  const parts = path.split(/[\\/]/);
2997
3005
  return (parts[parts.length - 1] ?? "").toLowerCase();
2998
- }
2999
- function codexCommandMatcher(display) {
3000
- const expected = splitPosixCommand(display);
3001
- if (expected === null || expected.length === 0) return void 0;
3002
- return (argv, platform2) => {
3003
- if (platform2 !== "win32") return posixCommandMatches(expected, argv);
3004
- if (argv.length !== expected.length) return false;
3005
- const [actualExe, ...actualRest] = argv;
3006
- const [expectedExe, ...expectedRest] = expected;
3007
- if (actualExe === void 0 || expectedExe === void 0) return false;
3008
- if (windowsPath(actualExe) !== windowsPath(expectedExe)) return false;
3009
- const lastIndex = expectedRest.length - 1;
3010
- return expectedRest.every((arg, index) => {
3011
- const actual = actualRest[index];
3012
- if (actual === arg) return true;
3013
- return index === lastIndex && index >= 1 && expectedRest[index - 1]?.toLowerCase() === "-command" && isPowerShellExecutableName(basename(expectedExe)) && actual === WINDOWS_POWERSHELL_PREFIX + arg;
3014
- });
3015
- };
3016
- }
3017
- function trackOpenCommand(open, method, params) {
3018
- if (!isRecord3(params)) return;
3019
- const turnId = turnIdOf(params);
3020
- if (method === "turn/completed") {
3021
- for (const [id2, entry] of open) {
3022
- if (turnId === void 0 || entry.turnId === turnId) open.delete(id2);
3023
- }
3024
- return;
3025
- }
3026
- const item = params["item"];
3027
- if (!isRecord3(item) || item["type"] !== "commandExecution") return;
3028
- const id = item["id"];
3029
- if (typeof id !== "string") return;
3030
- if (method === "item/completed") {
3031
- open.delete(id);
3032
- return;
3033
- }
3034
- if (method !== "item/started") return;
3035
- const startedAtMs = params["startedAtMs"];
3036
- const command = item["command"];
3037
- if (typeof startedAtMs !== "number" || typeof command !== "string") return;
3038
- open.set(id, { turnId, startedAtMs, command });
3039
- }
3040
- function codexSessionProcessMatcher(appServerArgs) {
3041
- return (argv) => argv.length === appServerArgs.length + 1 && sameArgv(argv.slice(1), appServerArgs);
3042
- }
3043
- function openCommandsOf(booked) {
3044
- if (booked === void 0) return void 0;
3045
- const activeTurn = booked.runtime.activeTurnId;
3046
- const commands = [...booked.runtime.openCommands].flatMap(([toolCallId, entry]) => {
3047
- if (activeTurn !== void 0 && entry.turnId !== activeTurn) return [];
3048
- const matches = codexCommandMatcher(entry.command);
3049
- return matches === void 0 ? [] : [{ toolCallId, startedAtMs: entry.startedAtMs, matches }];
3050
- });
3051
- if (commands.length === 0) return void 0;
3052
- return {
3053
- rootPid: booked.handle.pid,
3054
- sessionProcess: codexSessionProcessMatcher(booked.runtime.appServerArgs),
3055
- commands
3056
- };
3057
- }
3058
-
3059
- // src/sessionRuntime.ts
3060
- var COLLAB_META_LOOKUP_TIMEOUT_MS = 5e3;
3061
- function metaValue(value) {
3062
- const text = asString(value)?.trim();
3063
- return text === void 0 || text.length === 0 ? void 0 : text;
3064
- }
3065
- function cancelWireFor(pending) {
3066
- if (pending.kind === "permission") return toPermissionsResponse("cancel", pending.params, false);
3067
- return pending.kind === "mcp-elicitation" ? toMcpElicitationResponse("cancel", pending.params) : { decision: "cancel" };
3068
- }
3069
- var sleep = (ms) => new Promise((resolve2) => {
3070
- setTimeout(resolve2, ms);
3071
- });
3072
- function isRootChild(agentPath, agentThreadId, rootThreadId) {
3073
- if (agentThreadId === rootThreadId) return true;
3074
- return agentPath === "/root" || agentPath === "/";
3075
- }
3076
- function readSubAgentActivity(method, params) {
3077
- if (method !== "item/started" && method !== "item/completed") return void 0;
3078
- if (itemTypeOf(params) !== "subAgentActivity") return void 0;
3079
- const item = isRecord3(params) ? isRecord3(params["item"]) ? params["item"] : params : {};
3080
- return {
3081
- agentThreadId: asString(item["agentThreadId"]) ?? void 0,
3082
- agentPath: asString(item["agentPath"]) ?? void 0
3083
- };
3084
- }
3085
- function pathLeaf2(value) {
3086
- if (!value) return void 0;
3087
- const parts = value.split("/").filter((part) => part.length > 0);
3088
- return parts.at(-1);
3089
- }
3090
- function unconfirmedAnswerError(requestId, delivery) {
3091
- return new Error(
3092
- delivery === "closed" ? `could not confirm that codex received the answer to request ${requestId}: the session closed before the write was confirmed` : `could not confirm that codex received the answer to request ${requestId}: writing it to the CLI failed`
3093
- );
3094
- }
3095
- var CodexSessionRuntime = class {
3096
- threadId;
3097
- cwd;
3098
- session;
3099
- nativeThreadId;
3100
- activeTurnId;
3101
- model;
3102
- runtimeMode;
3103
- /** 宿主解好的这一轮的档(座位画的就是它)。缺席 = 宿主没给,按 `runtimeMode` 查表。 */
3104
- nativeModeId;
3105
- interactionMode;
3106
- closed = false;
3107
- closeCompleted = false;
3108
- turns = [];
3109
- pendingApprovals = /* @__PURE__ */ new Map();
3110
- pendingUserInputs = /* @__PURE__ */ new Map();
3111
- requestKinds = /* @__PURE__ */ new Map();
3112
- writeAcks = /* @__PURE__ */ new Map();
3113
- grants;
3114
- declineRelay;
3115
- collabChildren = /* @__PURE__ */ new Map();
3116
- collabChildMeta = /* @__PURE__ */ new Map();
3117
- liveForeignTurns = /* @__PURE__ */ new Map();
3118
- openCommands = /* @__PURE__ */ new Map();
3119
- collabReceiverTurns = /* @__PURE__ */ new Map();
3120
- memoryThreadIds = /* @__PURE__ */ new Set();
3121
- /** 这个会话见过的主额度快照(按字段合并),额度用尽停轮时点名窗口用。 */
3122
- rateLimits;
3123
- appServerArgs;
3124
- hasConfiguredMcpServer;
3125
- host;
3126
- attachments;
3127
- rpc;
3128
- emit;
3129
- /**
3130
- * 对外 requestId 的前缀,每个会话(即每个 app-server 进程)一份。JSON-RPC id 每个进程
3131
- * 都从 0 数起,宿主按 requestId 记「已接受」,不加前缀的话重启后的新请求会撞上旧请求。
3132
- */
3133
- #requestIdPrefix = `codex-${NodeCrypto2.randomUUID()}:`;
3134
- /** JSON-RPC id → 对外 requestId。`serverRequest/resolved` 里的 id 可能是数字也可能是字符串。 */
3135
- requestIdFor(jsonRpcId) {
3136
- return `${this.#requestIdPrefix}${String(jsonRpcId)}`;
3137
- }
3138
- /** 这一轮该用哪套三字段组合:只由这一轮的档决定(见 `codexNativeModeFor`)。 */
3139
- #threadConfig() {
3140
- return codexThreadConfigForNativeMode(codexNativeModeFor(this.runtimeMode, this.nativeModeId));
3141
- }
3142
- /** 同上,沙箱策略那一位。 */
3143
- #turnSandboxPolicy() {
3144
- return codexTurnSandboxPolicyForNativeMode(
3145
- codexNativeModeFor(this.runtimeMode, this.nativeModeId)
3146
- );
3147
- }
3148
- /** 给 CLI 的应答写入失败:带上线程与方法名记一行 warn。 */
3149
- #warnAnswerWriteFailed(method, jsonRpcId, error) {
3150
- this.host.log.warn("writing an answer to codex failed", {
3151
- threadId: this.threadId,
3152
- method,
3153
- jsonRpcId,
3154
- error: error instanceof Error ? error.message : String(error)
3155
- });
3156
- }
3157
- constructor(host, rpc, emit, input, cwd, appServerArgs, grants, declineRelay, attachments) {
3158
- this.attachments = attachments;
3159
- this.host = host;
3160
- this.rpc = rpc;
3161
- this.emit = emit;
3162
- this.threadId = input.threadId;
3163
- this.cwd = cwd;
3164
- this.runtimeMode = input.runtimeMode;
3165
- this.nativeModeId = input.nativeModeId;
3166
- this.appServerArgs = appServerArgs;
3167
- this.hasConfiguredMcpServer = (input.mcpServers?.length ?? 0) > 0;
3168
- this.grants = grants ?? createApprovalGrantStore();
3169
- this.declineRelay = declineRelay ?? createDeclineReasonRelay();
3170
- const createdAt = nowIso();
3171
- this.session = {
3172
- provider: CODEX_DRIVER_KIND,
3173
- providerInstanceId: host.instanceId,
3174
- status: "idle",
3175
- runtimeMode: input.runtimeMode,
3176
- nativeModeId: codexNativeModeFor(input.runtimeMode, input.nativeModeId),
3177
- cwd,
3178
- threadId: input.threadId,
3179
- createdAt,
3180
- updatedAt: createdAt,
3181
- ...input.bindingGeneration === void 0 ? {} : { bindingGeneration: input.bindingGeneration }
3182
- };
3183
- }
3184
- registerWriteAck(jsonRpcId) {
3185
- return new Promise((resolve2) => {
3186
- this.writeAcks.set(jsonRpcId, { resolve: resolve2 });
3187
- });
3188
- }
3189
- settleNativeWrite(requestId, status) {
3190
- const ack = this.writeAcks.get(requestId);
3191
- if (!ack) return;
3192
- this.writeAcks.delete(requestId);
3193
- ack.resolve(status);
3194
- }
3195
- failPendingNativeWrites(reason) {
3196
- for (const [, ack] of this.writeAcks) ack.resolve("closed");
3197
- this.writeAcks.clear();
3198
- void reason;
3199
- }
3200
- async confirmAfterNativeWrite(jsonRpcId) {
3201
- const existing = this.writeAcks.get(jsonRpcId);
3202
- if (existing) {
3203
- return await new Promise((resolve2) => {
3204
- const previous = existing.resolve;
3205
- existing.resolve = (status) => {
3206
- previous(status);
3207
- resolve2(status);
3208
- };
3209
- });
3210
- }
3211
- return await this.registerWriteAck(jsonRpcId);
3212
- }
3213
- handleStderrLine(line) {
3214
- const kind = classifyStderrLine(line);
3215
- if (kind === "drop") return;
3216
- if (isFatalStderr(line)) {
3217
- this.emit({
3218
- eventId: nextEventId(),
3219
- provider: CODEX_DRIVER_KIND,
3220
- threadId: this.threadId,
3221
- createdAt: nowIso(),
3222
- type: "runtime.error",
3223
- payload: { message: line }
3224
- });
3225
- return;
3226
- }
3227
- if (kind === "error") {
3228
- this.host.log.error(line, { threadId: this.threadId });
3229
- return;
3230
- }
3231
- this.host.log.warn(line, { threadId: this.threadId });
3232
- }
3233
- handleNotification(method, params) {
3234
- if (this.closed) return;
3235
- const conversationId = conversationIdFromNotification(params);
3236
- if (conversationId && conversationId !== this.nativeThreadId && conversationId !== this.threadId) {
3237
- this.interceptCollab(method, params, conversationId);
3238
- return;
3239
- }
3240
- if (conversationId && this.memoryThreadIds.has(conversationId)) {
3241
- if (method === "serverRequest" || method === "serverRequest/resolved") {
3242
- this.dispatchMapped(method, params);
3243
- }
3244
- return;
3245
- }
3246
- if (method === "thread/started") {
3247
- const name = isRecord3(params) ? asString(params["name"]) ?? (isRecord3(params["thread"]) ? asString(params["thread"]["name"]) : null) : null;
3248
- if (isMemoryConsolidationThread(name ?? void 0) && conversationId) {
3249
- this.memoryThreadIds.add(conversationId);
3250
- return;
3251
- }
3252
- }
3253
- const activity = readSubAgentActivity(method, params);
3254
- if (activity?.agentThreadId !== void 0 && !isRootChild(activity.agentPath, activity.agentThreadId, this.nativeThreadId ?? this.threadId)) {
3255
- this.upsertCollabChild(activity.agentThreadId, { agentPath: activity.agentPath });
3256
- }
3257
- this.dispatchMapped(method, params);
3258
- }
3259
- dispatchMapped(method, params) {
3260
- if (method === "serverRequest/resolved") {
3261
- const rawId = isRecord3(params) ? params["requestId"] : void 0;
3262
- if (typeof rawId !== "number" && typeof rawId !== "string") return;
3263
- const resolvedId = this.requestIdFor(rawId);
3264
- const pending = this.pendingApprovals.get(resolvedId);
3265
- if (pending === void 0) return;
3266
- pending.release();
3267
- this.emit({
3268
- eventId: nextEventId(),
3269
- provider: CODEX_DRIVER_KIND,
3270
- threadId: this.threadId,
3271
- createdAt: nowIso(),
3272
- type: "request.resolved",
3273
- requestId: resolvedId,
3274
- payload: {
3275
- requestType: toRequestTypeFromKind(this.requestKinds.get(resolvedId)),
3276
- resolution: params
3277
- }
3278
- });
3279
- return;
3280
- }
3281
- trackOpenCommand(this.openCommands, method, params);
3282
- if (method === "account/rateLimits/updated" && isRecord3(params)) {
3283
- this.rateLimits = mergeCodexRateLimits(this.rateLimits, params["rateLimits"]);
3284
- }
3285
- for (const event of mapNotificationToEvents({
3286
- method,
3287
- params,
3288
- threadId: this.threadId,
3289
- providerInstanceId: this.host.instanceId,
3290
- rateLimits: this.rateLimits
3291
- })) {
3292
- this.emit(event);
3293
- }
3294
- if (method === "turn/started") {
3295
- const turnId = turnIdOf(params);
3296
- if (turnId) this.activeTurnId = turnId;
3297
- }
3298
- if (method === "turn/completed") {
3299
- this.activeTurnId = void 0;
3300
- const { activeTurnId: _completedTurn, ...idleSession } = this.session;
3301
- this.session = { ...idleSession, status: "idle", updatedAt: nowIso() };
3006
+ }
3007
+ function codexCommandMatcher(display) {
3008
+ const expected = splitPosixCommand(display);
3009
+ if (expected === null || expected.length === 0) return void 0;
3010
+ return (argv, platform2) => {
3011
+ if (platform2 !== "win32") return posixCommandMatches(expected, argv);
3012
+ if (argv.length !== expected.length) return false;
3013
+ const [actualExe, ...actualRest] = argv;
3014
+ const [expectedExe, ...expectedRest] = expected;
3015
+ if (actualExe === void 0 || expectedExe === void 0) return false;
3016
+ if (windowsPath(actualExe) !== windowsPath(expectedExe)) return false;
3017
+ const lastIndex = expectedRest.length - 1;
3018
+ return expectedRest.every((arg, index) => {
3019
+ const actual = actualRest[index];
3020
+ if (actual === arg) return true;
3021
+ return index === lastIndex && index >= 1 && expectedRest[index - 1]?.toLowerCase() === "-command" && isPowerShellExecutableName(basename(expectedExe)) && actual === WINDOWS_POWERSHELL_PREFIX + arg;
3022
+ });
3023
+ };
3024
+ }
3025
+ function trackOpenCommand(open, method, params) {
3026
+ if (!isRecord3(params)) return;
3027
+ const turnId = turnIdOf(params);
3028
+ if (method === "turn/completed") {
3029
+ for (const [id2, entry] of open) {
3030
+ if (turnId === void 0 || entry.turnId === turnId) open.delete(id2);
3302
3031
  }
3032
+ return;
3033
+ }
3034
+ const item = params["item"];
3035
+ if (!isRecord3(item) || item["type"] !== "commandExecution") return;
3036
+ const id = item["id"];
3037
+ if (typeof id !== "string") return;
3038
+ if (method === "item/completed") {
3039
+ open.delete(id);
3040
+ return;
3041
+ }
3042
+ if (method !== "item/started") return;
3043
+ const startedAtMs = params["startedAtMs"];
3044
+ const command = item["command"];
3045
+ if (typeof startedAtMs !== "number" || typeof command !== "string") return;
3046
+ open.set(id, { turnId, startedAtMs, command });
3047
+ }
3048
+ function codexSessionProcessMatcher(appServerArgs) {
3049
+ return (argv) => argv.length === appServerArgs.length + 1 && sameArgv(argv.slice(1), appServerArgs);
3050
+ }
3051
+ function openCommandsOf(booked) {
3052
+ if (booked === void 0) return void 0;
3053
+ const activeTurn = booked.runtime.activeTurnId;
3054
+ const commands = [...booked.runtime.openCommands].flatMap(([toolCallId, entry]) => {
3055
+ if (activeTurn !== void 0 && entry.turnId !== activeTurn) return [];
3056
+ const matches = codexCommandMatcher(entry.command);
3057
+ return matches === void 0 ? [] : [{ toolCallId, startedAtMs: entry.startedAtMs, matches }];
3058
+ });
3059
+ if (commands.length === 0) return void 0;
3060
+ return {
3061
+ rootPid: booked.handle.pid,
3062
+ sessionProcess: codexSessionProcessMatcher(booked.runtime.appServerArgs),
3063
+ commands
3064
+ };
3065
+ }
3066
+
3067
+ // src/sessionCollab.ts
3068
+ var COLLAB_META_LOOKUP_TIMEOUT_MS = 5e3;
3069
+ function metaValue(value) {
3070
+ const text = asString(value)?.trim();
3071
+ return text === void 0 || text.length === 0 ? void 0 : text;
3072
+ }
3073
+ function isRootChild(agentPath, agentThreadId, rootThreadId) {
3074
+ if (agentThreadId === rootThreadId) return true;
3075
+ return agentPath === "/root" || agentPath === "/";
3076
+ }
3077
+ function readSubAgentActivity(method, params) {
3078
+ if (method !== "item/started" && method !== "item/completed") return void 0;
3079
+ if (itemTypeOf(params) !== "subAgentActivity") return void 0;
3080
+ const item = isRecord3(params) ? isRecord3(params["item"]) ? params["item"] : params : {};
3081
+ return {
3082
+ agentThreadId: asString(item["agentThreadId"]) ?? void 0,
3083
+ agentPath: asString(item["agentPath"]) ?? void 0
3084
+ };
3085
+ }
3086
+ var CodexSessionCollab = class {
3087
+ runtime;
3088
+ constructor(runtime) {
3089
+ this.runtime = runtime;
3303
3090
  }
3304
3091
  interceptCollab(method, params, conversationId) {
3305
3092
  const thread = isRecord3(params) && isRecord3(params["thread"]) ? params["thread"] : void 0;
@@ -3308,8 +3095,9 @@ var CodexSessionRuntime = class {
3308
3095
  const spawn = isRecord3(subAgent) ? subAgent["thread_spawn"] : void 0;
3309
3096
  if (method === "thread/started" && thread !== void 0 && isRecord3(spawn)) {
3310
3097
  const agentPath = asString(spawn["agent_path"]) ?? void 0;
3311
- const knownPath = agentPath ?? this.collabChildren.get(conversationId)?.agentPath;
3312
- if (isRootChild(knownPath, conversationId, this.nativeThreadId ?? this.threadId)) return;
3098
+ const knownPath = agentPath ?? this.runtime.collabChildren.get(conversationId)?.agentPath;
3099
+ if (isRootChild(knownPath, conversationId, this.runtime.nativeThreadId ?? this.runtime.threadId))
3100
+ return;
3313
3101
  this.upsertCollabChild(conversationId, {
3314
3102
  agentPath,
3315
3103
  role: asString(spawn["agent_role"]) ?? asString(thread["agentRole"]) ?? void 0,
@@ -3321,13 +3109,18 @@ var CodexSessionRuntime = class {
3321
3109
  if ((method === "item/started" || method === "item/completed") && itemType === "collabAgentToolCall") {
3322
3110
  const item = isRecord3(params) ? isRecord3(params["item"]) ? params["item"] : params : {};
3323
3111
  const receiver = asString(item["receiverThreadId"]) ?? asString(item["agentThreadId"]);
3324
- if (receiver && this.activeTurnId) this.collabReceiverTurns.set(receiver, this.activeTurnId);
3112
+ if (receiver && this.runtime.activeTurnId)
3113
+ this.runtime.collabReceiverTurns.set(receiver, this.runtime.activeTurnId);
3325
3114
  return;
3326
3115
  }
3327
3116
  const activity = readSubAgentActivity(method, params);
3328
3117
  if (activity !== void 0) {
3329
3118
  const agentThreadId = activity.agentThreadId ?? conversationId;
3330
- if (isRootChild(activity.agentPath, agentThreadId, this.nativeThreadId ?? this.threadId)) {
3119
+ if (isRootChild(
3120
+ activity.agentPath,
3121
+ agentThreadId,
3122
+ this.runtime.nativeThreadId ?? this.runtime.threadId
3123
+ )) {
3331
3124
  return;
3332
3125
  }
3333
3126
  this.upsertCollabChild(agentThreadId, { agentPath: activity.agentPath });
@@ -3342,16 +3135,16 @@ var CodexSessionRuntime = class {
3342
3135
  this.updateCollabChildMeta(conversationId, { model, effort }, true);
3343
3136
  return;
3344
3137
  }
3345
- const child = this.collabChildren.get(conversationId);
3138
+ const child = this.runtime.collabChildren.get(conversationId);
3346
3139
  if (method === "turn/started") {
3347
3140
  this.reopenCollabChildMeta(conversationId);
3348
3141
  const turnId = turnIdOf(params);
3349
- if (turnId) this.liveForeignTurns.set(conversationId, turnId);
3142
+ if (turnId) this.runtime.liveForeignTurns.set(conversationId, turnId);
3350
3143
  }
3351
3144
  if (method === "turn/completed" || method === "thread/closed") {
3352
- this.liveForeignTurns.delete(conversationId);
3145
+ this.runtime.liveForeignTurns.delete(conversationId);
3353
3146
  }
3354
- if (method === "thread/closed" && this.collabChildren.has(conversationId)) {
3147
+ if (method === "thread/closed" && this.runtime.collabChildren.has(conversationId)) {
3355
3148
  this.metaOf(conversationId).closed = true;
3356
3149
  }
3357
3150
  if (!child) {
@@ -3374,285 +3167,568 @@ var CodexSessionRuntime = class {
3374
3167
  );
3375
3168
  return;
3376
3169
  }
3377
- if (method === "error") {
3378
- const willRetry = isRecord3(params) ? params["willRetry"] === true : false;
3379
- if (willRetry) return;
3380
- child.status = "failed";
3381
- this.emitCollab("collabAgent/statusChanged", child, { status: "systemError" });
3170
+ if (method === "error") {
3171
+ const willRetry = isRecord3(params) ? params["willRetry"] === true : false;
3172
+ if (willRetry) return;
3173
+ child.status = "failed";
3174
+ this.emitCollab("collabAgent/statusChanged", child, { status: "systemError" });
3175
+ return;
3176
+ }
3177
+ if (method === "thread/tokenUsage/updated") {
3178
+ this.emitCollab("collabAgent/tokenUsage", child, params);
3179
+ return;
3180
+ }
3181
+ if (CHILD_CHATTER_METHODS.has(method)) return;
3182
+ if (CHILD_AGENT_EVENT_METHODS.has(method)) {
3183
+ this.emitCollab("collabAgent/progress", child, params);
3184
+ }
3185
+ }
3186
+ /**
3187
+ * 两条登记路(根线程的 subAgentActivity、子线程的 thread/started)都走这里。
3188
+ * 第一次见到就登记并宣布;已登记的只合并身份:已有的字段保留,缺的补上,补到了就发
3189
+ * 一条带当前状态的元数据更新。不能再宣布一次:一轮结束之后才到的宣布,会让宿主把
3190
+ * idle 当成新一轮开始。
3191
+ */
3192
+ upsertCollabChild(agentThreadId, identity) {
3193
+ const fill = (known, next) => known ?? (next !== void 0 && next.length > 0 ? next : void 0);
3194
+ const existing = this.runtime.collabChildren.get(agentThreadId);
3195
+ const agentPath = fill(existing?.agentPath, identity.agentPath);
3196
+ const role = fill(existing?.role, identity.role);
3197
+ const nickname = fill(existing?.nickname, identity.nickname);
3198
+ if (existing === void 0) {
3199
+ const spawnTurnId = this.runtime.activeTurnId;
3200
+ const child = {
3201
+ agentThreadId,
3202
+ ...agentPath ? { agentPath } : {},
3203
+ ...role ? { role } : {},
3204
+ ...nickname ? { nickname } : {},
3205
+ ...spawnTurnId ? { spawnTurnId } : {},
3206
+ status: "running"
3207
+ };
3208
+ this.runtime.collabChildren.set(agentThreadId, child);
3209
+ this.emitCollab("collabAgent/started", child);
3210
+ this.startCollabChildMetaLookup(agentThreadId);
3211
+ return;
3212
+ }
3213
+ if (agentPath === existing.agentPath && role === existing.role && nickname === existing.nickname) {
3214
+ return;
3215
+ }
3216
+ const merged = {
3217
+ ...existing,
3218
+ ...agentPath ? { agentPath } : {},
3219
+ ...role ? { role } : {},
3220
+ ...nickname ? { nickname } : {}
3221
+ };
3222
+ this.runtime.collabChildren.set(agentThreadId, merged);
3223
+ this.emitCollab("collabAgent/metadata", merged, { status: merged.status });
3224
+ }
3225
+ metaOf(agentThreadId) {
3226
+ let meta = this.runtime.collabChildMeta.get(agentThreadId);
3227
+ if (meta === void 0) {
3228
+ meta = { lookupStarted: false, closed: false };
3229
+ this.runtime.collabChildMeta.set(agentThreadId, meta);
3230
+ }
3231
+ return meta;
3232
+ }
3233
+ reopenCollabChildMeta(agentThreadId) {
3234
+ const meta = this.runtime.collabChildMeta.get(agentThreadId);
3235
+ if (meta !== void 0) meta.closed = false;
3236
+ }
3237
+ /**
3238
+ * 合并子线程的型号与推理档;变了且子代理已登记就补发一条不带状态的更新。
3239
+ * 通知报的是当前值(overwrite),回读只补缺:回读的回包可能比通知晚到,不能盖掉更新的值。
3240
+ */
3241
+ updateCollabChildMeta(agentThreadId, update, overwrite) {
3242
+ const meta = this.metaOf(agentThreadId);
3243
+ const model = update.model !== void 0 && (overwrite || !meta.model) ? update.model : meta.model;
3244
+ const effort = update.effort !== void 0 && (overwrite || !meta.effort) ? update.effort : meta.effort;
3245
+ if (model === meta.model && effort === meta.effort) return;
3246
+ if (model !== void 0) meta.model = model;
3247
+ if (effort !== void 0) meta.effort = effort;
3248
+ const child = this.runtime.collabChildren.get(agentThreadId);
3249
+ if (child !== void 0 && !meta.closed) this.emitCollab("collabAgent/metadataUpdated", child);
3250
+ }
3251
+ /**
3252
+ * 子线程已经在 CLI 里加载着:thread/resume 只是重新接上它,不开新一轮;
3253
+ * excludeTurns 避免把它的历史再读一遍。回包带当前的型号与推理档。
3254
+ */
3255
+ startCollabChildMetaLookup(agentThreadId) {
3256
+ const meta = this.metaOf(agentThreadId);
3257
+ if (meta.lookupStarted || meta.closed) return;
3258
+ meta.lookupStarted = true;
3259
+ void this.runtime.rpc.request(
3260
+ "thread/resume",
3261
+ { threadId: agentThreadId, excludeTurns: true },
3262
+ COLLAB_META_LOOKUP_TIMEOUT_MS
3263
+ ).then((response) => {
3264
+ if (this.runtime.closed || meta.closed || !isRecord3(response)) return;
3265
+ const thread = response["thread"];
3266
+ if (!isRecord3(thread) || thread["id"] !== agentThreadId) return;
3267
+ this.updateCollabChildMeta(
3268
+ agentThreadId,
3269
+ { model: metaValue(response["model"]), effort: metaValue(response["reasoningEffort"]) },
3270
+ false
3271
+ );
3272
+ }).catch(() => void 0);
3273
+ }
3274
+ emitCollab(method, child, extra) {
3275
+ const meta = this.runtime.collabChildMeta.get(child.agentThreadId);
3276
+ const params = {
3277
+ agentThreadId: child.agentThreadId,
3278
+ agentPath: child.agentPath,
3279
+ agentRole: child.role ?? pathLeaf(child.agentPath) ?? "general-purpose",
3280
+ agentNickname: child.nickname,
3281
+ ...meta?.model ? { model: meta.model } : {},
3282
+ ...meta?.effort ? { effort: meta.effort } : {},
3283
+ ...isRecord3(extra) ? extra : {}
3284
+ };
3285
+ for (const event of mapNotificationToEvents({
3286
+ method,
3287
+ params,
3288
+ threadId: this.runtime.threadId,
3289
+ providerInstanceId: this.runtime.host.instanceId
3290
+ })) {
3291
+ this.runtime.emit(event);
3292
+ }
3293
+ }
3294
+ };
3295
+
3296
+ // src/sessionRequests.ts
3297
+ function cancelWireFor(pending) {
3298
+ if (pending.kind === "permission") return toPermissionsResponse("cancel", pending.params, false);
3299
+ return pending.kind === "mcp-elicitation" ? toMcpElicitationResponse("cancel", pending.params) : { decision: "cancel" };
3300
+ }
3301
+ function unconfirmedAnswerError(requestId, delivery) {
3302
+ return new Error(
3303
+ delivery === "closed" ? `could not confirm that codex received the answer to request ${requestId}: the session closed before the write was confirmed` : `could not confirm that codex received the answer to request ${requestId}: writing it to the CLI failed`
3304
+ );
3305
+ }
3306
+ var CodexSessionRequests = class {
3307
+ runtime;
3308
+ constructor(runtime) {
3309
+ this.runtime = runtime;
3310
+ }
3311
+ /** 给 CLI 的应答写入失败:带上线程与方法名记一行 warn。 */
3312
+ #warnAnswerWriteFailed(method, jsonRpcId, error) {
3313
+ this.runtime.host.log.warn("writing an answer to codex failed", {
3314
+ threadId: this.runtime.threadId,
3315
+ method,
3316
+ jsonRpcId,
3317
+ error: error instanceof Error ? error.message : String(error)
3318
+ });
3319
+ }
3320
+ async handleServerRequest(id, method, params) {
3321
+ if (method === "item/commandExecution/requestApproval") {
3322
+ await this.handleApproval(
3323
+ id,
3324
+ method,
3325
+ params,
3326
+ "command",
3327
+ describeCommandApproval(params).command
3328
+ );
3329
+ return;
3330
+ }
3331
+ if (method === "item/fileChange/requestApproval") {
3332
+ const described = describeFileChangeApproval(params);
3333
+ await this.handleApproval(
3334
+ id,
3335
+ method,
3336
+ params,
3337
+ "file-change",
3338
+ described.callId ?? "file-change"
3339
+ );
3340
+ return;
3341
+ }
3342
+ if (method === "mcpServer/elicitation/request") {
3343
+ const described = describeMcpElicitation(params);
3344
+ await this.handleApproval(id, method, params, "mcp-elicitation", described.identity);
3345
+ return;
3346
+ }
3347
+ if (method === "item/permissions/requestApproval") {
3348
+ await this.handleApproval(
3349
+ id,
3350
+ method,
3351
+ params,
3352
+ "permission",
3353
+ isRecord3(params) ? asString(params["itemId"]) ?? "permission" : "permission"
3354
+ );
3355
+ return;
3356
+ }
3357
+ if (method === "item/tool/requestUserInput") {
3358
+ const requestId = this.runtime.requestIdFor(id);
3359
+ const events = mapServerRequestToEvents({
3360
+ requestId,
3361
+ method,
3362
+ params,
3363
+ threadId: this.runtime.threadId,
3364
+ providerInstanceId: this.runtime.host.instanceId
3365
+ });
3366
+ if (events.length === 0) {
3367
+ await this.runtime.rpc.respond(id, {});
3368
+ return;
3369
+ }
3370
+ await new Promise((resolve2) => {
3371
+ this.runtime.pendingUserInputs.set(requestId, {
3372
+ jsonRpcId: id,
3373
+ settle: (result) => {
3374
+ this.runtime.pendingUserInputs.delete(requestId);
3375
+ void this.runtime.rpc.respond(id, result).catch((error) => this.#warnAnswerWriteFailed(method, id, error)).finally(resolve2);
3376
+ }
3377
+ });
3378
+ for (const event of events) this.runtime.emit(event);
3379
+ });
3380
+ return;
3381
+ }
3382
+ await this.runtime.rpc.respondError(id, -32601, `Method not found: ${method}`);
3383
+ }
3384
+ async handleApproval(id, method, params, kind, identity) {
3385
+ const grant = kind === "permission" ? void 0 : this.runtime.grants.lookup(codexApprovalGrantKey(kind, identity));
3386
+ if (grant) {
3387
+ const decision = wireDecisionFromPersist(kind, grant);
3388
+ await this.runtime.rpc.respond(id, { decision });
3382
3389
  return;
3383
3390
  }
3384
- if (method === "thread/tokenUsage/updated") {
3385
- this.emitCollab("collabAgent/tokenUsage", child, params);
3386
- return;
3391
+ const requestId = this.runtime.requestIdFor(id);
3392
+ const events = mapServerRequestToEvents({
3393
+ requestId,
3394
+ method,
3395
+ params,
3396
+ threadId: this.runtime.threadId,
3397
+ providerInstanceId: this.runtime.host.instanceId
3398
+ });
3399
+ this.runtime.requestKinds.set(requestId, kind);
3400
+ await new Promise((resolve2) => {
3401
+ this.runtime.pendingApprovals.set(requestId, {
3402
+ jsonRpcId: id,
3403
+ method,
3404
+ params,
3405
+ kind,
3406
+ identity,
3407
+ settle: (decision) => {
3408
+ this.runtime.pendingApprovals.delete(requestId);
3409
+ void this.runtime.rpc.respond(id, decision).catch((error) => this.#warnAnswerWriteFailed(method, id, error)).finally(resolve2);
3410
+ },
3411
+ release: () => {
3412
+ this.runtime.pendingApprovals.delete(requestId);
3413
+ resolve2();
3414
+ }
3415
+ });
3416
+ for (const event of events) this.runtime.emit(event);
3417
+ });
3418
+ }
3419
+ async respondToRequest(requestId, decision, reason, persist) {
3420
+ const pending = this.runtime.pendingApprovals.get(requestId);
3421
+ if (!pending) throw new Error(`Unknown pending Codex approval request: ${requestId}`);
3422
+ if (decision === "deny" && reason)
3423
+ this.runtime.declineRelay.queue(this.runtime.threadId, reason);
3424
+ let wire = { decision: decision === "allow" ? "accept" : "decline" };
3425
+ if (decision === "allow" && pending.kind === "mcp-elicitation") {
3426
+ wire = toMcpElicitationResponse("allow", pending.params);
3387
3427
  }
3388
- if (CHILD_CHATTER_METHODS.has(method)) return;
3389
- if (CHILD_AGENT_EVENT_METHODS.has(method)) {
3390
- this.emitCollab("collabAgent/progress", child, params);
3428
+ if (pending.kind === "permission") {
3429
+ wire = toPermissionsResponse(decision, pending.params, persist !== void 0);
3430
+ } else if (decision === "allow" && persist) {
3431
+ this.runtime.grants.remember(codexApprovalGrantKey(pending.kind, pending.identity), persist);
3432
+ if (pending.kind !== "mcp-elicitation") {
3433
+ wire = { decision: wireDecisionFromPersist(pending.kind, persist) };
3434
+ }
3391
3435
  }
3436
+ const ack = this.runtime.registerWriteAck(pending.jsonRpcId);
3437
+ pending.settle(wire);
3438
+ const delivery = await ack.catch(() => "closed");
3439
+ this.runtime.emit({
3440
+ eventId: nextEventId(),
3441
+ provider: CODEX_DRIVER_KIND,
3442
+ threadId: this.runtime.threadId,
3443
+ createdAt: nowIso(),
3444
+ type: "request.resolved",
3445
+ requestId,
3446
+ // requestType 是契约必填;缺了它宿主整条解不动,卡片停在「等待决议」。
3447
+ payload: {
3448
+ requestType: toRequestTypeFromKind(pending.kind),
3449
+ decision,
3450
+ ...reason ? { reason } : {}
3451
+ }
3452
+ });
3453
+ if (delivery !== "written") throw unconfirmedAnswerError(requestId, delivery);
3392
3454
  }
3393
- /**
3394
- * 两条登记路(根线程的 subAgentActivity、子线程的 thread/started)都走这里。
3395
- * 第一次见到就登记并宣布;已登记的只合并身份:已有的字段保留,缺的补上,补到了就发
3396
- * 一条带当前状态的元数据更新。不能再宣布一次:一轮结束之后才到的宣布,会让宿主把
3397
- * idle 当成新一轮开始。
3398
- */
3399
- upsertCollabChild(agentThreadId, identity) {
3400
- const fill = (known, next) => known ?? (next !== void 0 && next.length > 0 ? next : void 0);
3401
- const existing = this.collabChildren.get(agentThreadId);
3402
- const agentPath = fill(existing?.agentPath, identity.agentPath);
3403
- const role = fill(existing?.role, identity.role);
3404
- const nickname = fill(existing?.nickname, identity.nickname);
3405
- if (existing === void 0) {
3406
- const spawnTurnId = this.activeTurnId;
3407
- const child = {
3408
- agentThreadId,
3409
- ...agentPath ? { agentPath } : {},
3410
- ...role ? { role } : {},
3411
- ...nickname ? { nickname } : {},
3412
- ...spawnTurnId ? { spawnTurnId } : {},
3413
- status: "running"
3414
- };
3415
- this.collabChildren.set(agentThreadId, child);
3416
- this.emitCollab("collabAgent/started", child);
3417
- this.startCollabChildMetaLookup(agentThreadId);
3418
- return;
3455
+ async respondToUserInput(requestId, answers) {
3456
+ const pending = this.runtime.pendingUserInputs.get(requestId);
3457
+ if (!pending) throw new Error(`Unknown pending Codex user input request: ${requestId}`);
3458
+ const mapped = toCodexUserInputAnswers(answers);
3459
+ if (mapped === void 0) {
3460
+ throw new Error(`invalid user input answers for request ${requestId}`);
3419
3461
  }
3420
- if (agentPath === existing.agentPath && role === existing.role && nickname === existing.nickname) {
3421
- return;
3462
+ const ack = this.runtime.registerWriteAck(pending.jsonRpcId);
3463
+ pending.settle({ answers: mapped });
3464
+ const delivery = await ack.catch(() => "closed");
3465
+ this.runtime.emit({
3466
+ eventId: nextEventId(),
3467
+ provider: CODEX_DRIVER_KIND,
3468
+ threadId: this.runtime.threadId,
3469
+ createdAt: nowIso(),
3470
+ type: "user-input.resolved",
3471
+ requestId,
3472
+ // 契约的 answers 是 questionId → 答案 的 record,不是数组。
3473
+ payload: { answers: toCanonicalUserInputAnswers(mapped) }
3474
+ });
3475
+ if (delivery !== "written") throw unconfirmedAnswerError(requestId, delivery);
3476
+ }
3477
+ /** 把还挂着的审批答成「取消」,逐条发 request.resolved。 */
3478
+ cancelPendingApprovals() {
3479
+ for (const [requestId, pending] of this.runtime.pendingApprovals) {
3480
+ pending.settle(cancelWireFor(pending));
3481
+ this.runtime.emit({
3482
+ eventId: nextEventId(),
3483
+ provider: CODEX_DRIVER_KIND,
3484
+ threadId: this.runtime.threadId,
3485
+ createdAt: nowIso(),
3486
+ type: "request.resolved",
3487
+ requestId,
3488
+ payload: { requestType: toRequestTypeFromKind(pending.kind), decision: "cancel" }
3489
+ });
3422
3490
  }
3423
- const merged = {
3424
- ...existing,
3425
- ...agentPath ? { agentPath } : {},
3426
- ...role ? { role } : {},
3427
- ...nickname ? { nickname } : {}
3428
- };
3429
- this.collabChildren.set(agentThreadId, merged);
3430
- this.emitCollab("collabAgent/metadata", merged, { status: merged.status });
3431
3491
  }
3432
- metaOf(agentThreadId) {
3433
- let meta = this.collabChildMeta.get(agentThreadId);
3434
- if (meta === void 0) {
3435
- meta = { lookupStarted: false, closed: false };
3436
- this.collabChildMeta.set(agentThreadId, meta);
3492
+ /** 会话收口时把还挂着的审批答成「取消」:应答形状按审批种类,权限审批不认 decision。 */
3493
+ settlePendingApprovals() {
3494
+ for (const [requestId, pending] of this.runtime.pendingApprovals) {
3495
+ pending.settle(cancelWireFor(pending));
3496
+ this.runtime.pendingApprovals.delete(requestId);
3437
3497
  }
3438
- return meta;
3439
3498
  }
3440
- reopenCollabChildMeta(agentThreadId) {
3441
- const meta = this.collabChildMeta.get(agentThreadId);
3442
- if (meta !== void 0) meta.closed = false;
3499
+ settlePendingUserInputs(result) {
3500
+ for (const [requestId, pending] of this.runtime.pendingUserInputs) {
3501
+ pending.settle(result);
3502
+ this.runtime.pendingUserInputs.delete(requestId);
3503
+ }
3443
3504
  }
3444
- /**
3445
- * 合并子线程的型号与推理档;变了且子代理已登记就补发一条不带状态的更新。
3446
- * 通知报的是当前值(overwrite),回读只补缺:回读的回包可能比通知晚到,不能盖掉更新的值。
3447
- */
3448
- updateCollabChildMeta(agentThreadId, update, overwrite) {
3449
- const meta = this.metaOf(agentThreadId);
3450
- const model = update.model !== void 0 && (overwrite || !meta.model) ? update.model : meta.model;
3451
- const effort = update.effort !== void 0 && (overwrite || !meta.effort) ? update.effort : meta.effort;
3452
- if (model === meta.model && effort === meta.effort) return;
3453
- if (model !== void 0) meta.model = model;
3454
- if (effort !== void 0) meta.effort = effort;
3455
- const child = this.collabChildren.get(agentThreadId);
3456
- if (child !== void 0 && !meta.closed) this.emitCollab("collabAgent/metadataUpdated", child);
3505
+ };
3506
+ async function markProviderRejection(run) {
3507
+ try {
3508
+ return await run();
3509
+ } catch (error) {
3510
+ if (!(error instanceof CodexRpcResponseError)) throw error;
3511
+ const rejected = Object.assign(new Error(error.message), {
3512
+ code: "provider-rejected"
3513
+ });
3514
+ throw rejected;
3457
3515
  }
3516
+ }
3517
+
3518
+ // src/sessionRuntime.ts
3519
+ var sleep = (ms) => new Promise((resolve2) => {
3520
+ setTimeout(resolve2, ms);
3521
+ });
3522
+ var CodexSessionRuntime = class {
3523
+ threadId;
3524
+ cwd;
3525
+ session;
3526
+ nativeThreadId;
3527
+ activeTurnId;
3528
+ model;
3529
+ runtimeMode;
3530
+ /** 宿主解好的这一轮的档(座位画的就是它)。缺席 = 宿主没给,按 `runtimeMode` 查表。 */
3531
+ nativeModeId;
3532
+ interactionMode;
3533
+ closed = false;
3534
+ closeCompleted = false;
3535
+ turns = [];
3536
+ pendingApprovals = /* @__PURE__ */ new Map();
3537
+ pendingUserInputs = /* @__PURE__ */ new Map();
3538
+ requestKinds = /* @__PURE__ */ new Map();
3539
+ writeAcks = /* @__PURE__ */ new Map();
3540
+ grants;
3541
+ declineRelay;
3542
+ collabChildren = /* @__PURE__ */ new Map();
3543
+ collabChildMeta = /* @__PURE__ */ new Map();
3544
+ liveForeignTurns = /* @__PURE__ */ new Map();
3545
+ openCommands = /* @__PURE__ */ new Map();
3546
+ collabReceiverTurns = /* @__PURE__ */ new Map();
3547
+ memoryThreadIds = /* @__PURE__ */ new Set();
3548
+ /** 这个会话见过的主额度快照(按字段合并),额度用尽停轮时点名窗口用。 */
3549
+ rateLimits;
3550
+ appServerArgs;
3551
+ hasConfiguredMcpServer;
3552
+ host;
3553
+ attachments;
3554
+ rpc;
3555
+ emit;
3556
+ #collab = new CodexSessionCollab(this);
3557
+ #requests = new CodexSessionRequests(this);
3458
3558
  /**
3459
- * 子线程已经在 CLI 里加载着:thread/resume 只是重新接上它,不开新一轮;
3460
- * excludeTurns 避免把它的历史再读一遍。回包带当前的型号与推理档。
3559
+ * 对外 requestId 的前缀,每个会话(即每个 app-server 进程)一份。JSON-RPC id 每个进程
3560
+ * 都从 0 数起,宿主按 requestId 记「已接受」,不加前缀的话重启后的新请求会撞上旧请求。
3461
3561
  */
3462
- startCollabChildMetaLookup(agentThreadId) {
3463
- const meta = this.metaOf(agentThreadId);
3464
- if (meta.lookupStarted || meta.closed) return;
3465
- meta.lookupStarted = true;
3466
- void this.rpc.request(
3467
- "thread/resume",
3468
- { threadId: agentThreadId, excludeTurns: true },
3469
- COLLAB_META_LOOKUP_TIMEOUT_MS
3470
- ).then((response) => {
3471
- if (this.closed || meta.closed || !isRecord3(response)) return;
3472
- const thread = response["thread"];
3473
- if (!isRecord3(thread) || thread["id"] !== agentThreadId) return;
3474
- this.updateCollabChildMeta(
3475
- agentThreadId,
3476
- { model: metaValue(response["model"]), effort: metaValue(response["reasoningEffort"]) },
3477
- false
3478
- );
3479
- }).catch(() => void 0);
3562
+ #requestIdPrefix = `codex-${NodeCrypto2.randomUUID()}:`;
3563
+ /** JSON-RPC id → 对外 requestId。`serverRequest/resolved` 里的 id 可能是数字也可能是字符串。 */
3564
+ requestIdFor(jsonRpcId) {
3565
+ return `${this.#requestIdPrefix}${String(jsonRpcId)}`;
3480
3566
  }
3481
- emitCollab(method, child, extra) {
3482
- const meta = this.collabChildMeta.get(child.agentThreadId);
3483
- const params = {
3484
- agentThreadId: child.agentThreadId,
3485
- agentPath: child.agentPath,
3486
- agentRole: child.role ?? pathLeaf2(child.agentPath) ?? "general-purpose",
3487
- agentNickname: child.nickname,
3488
- ...meta?.model ? { model: meta.model } : {},
3489
- ...meta?.effort ? { effort: meta.effort } : {},
3490
- ...isRecord3(extra) ? extra : {}
3567
+ /** 这一轮该用哪套三字段组合:只由这一轮的档决定(见 `codexNativeModeFor`)。 */
3568
+ #threadConfig() {
3569
+ return codexThreadConfigForNativeMode(codexNativeModeFor(this.runtimeMode, this.nativeModeId));
3570
+ }
3571
+ /** 同上,沙箱策略那一位。 */
3572
+ #turnSandboxPolicy() {
3573
+ return codexTurnSandboxPolicyForNativeMode(
3574
+ codexNativeModeFor(this.runtimeMode, this.nativeModeId)
3575
+ );
3576
+ }
3577
+ constructor(host, rpc, emit, input, cwd, appServerArgs, grants, declineRelay, attachments) {
3578
+ this.attachments = attachments;
3579
+ this.host = host;
3580
+ this.rpc = rpc;
3581
+ this.emit = emit;
3582
+ this.threadId = input.threadId;
3583
+ this.cwd = cwd;
3584
+ this.runtimeMode = input.runtimeMode;
3585
+ this.nativeModeId = input.nativeModeId;
3586
+ this.appServerArgs = appServerArgs;
3587
+ this.hasConfiguredMcpServer = (input.mcpServers?.length ?? 0) > 0;
3588
+ this.grants = grants ?? createApprovalGrantStore();
3589
+ this.declineRelay = declineRelay ?? createDeclineReasonRelay();
3590
+ const createdAt = nowIso();
3591
+ this.session = {
3592
+ provider: CODEX_DRIVER_KIND,
3593
+ providerInstanceId: host.instanceId,
3594
+ status: "idle",
3595
+ runtimeMode: input.runtimeMode,
3596
+ nativeModeId: codexNativeModeFor(input.runtimeMode, input.nativeModeId),
3597
+ cwd,
3598
+ threadId: input.threadId,
3599
+ createdAt,
3600
+ updatedAt: createdAt,
3601
+ ...input.bindingGeneration === void 0 ? {} : { bindingGeneration: input.bindingGeneration }
3491
3602
  };
3492
- for (const event of mapNotificationToEvents({
3493
- method,
3494
- params,
3495
- threadId: this.threadId,
3496
- providerInstanceId: this.host.instanceId
3497
- })) {
3498
- this.emit(event);
3603
+ }
3604
+ registerWriteAck(jsonRpcId) {
3605
+ return new Promise((resolve2) => {
3606
+ this.writeAcks.set(jsonRpcId, { resolve: resolve2 });
3607
+ });
3608
+ }
3609
+ settleNativeWrite(requestId, status) {
3610
+ const ack = this.writeAcks.get(requestId);
3611
+ if (!ack) return;
3612
+ this.writeAcks.delete(requestId);
3613
+ ack.resolve(status);
3614
+ }
3615
+ failPendingNativeWrites(reason) {
3616
+ for (const [, ack] of this.writeAcks) ack.resolve("closed");
3617
+ this.writeAcks.clear();
3618
+ void reason;
3619
+ }
3620
+ async confirmAfterNativeWrite(jsonRpcId) {
3621
+ const existing = this.writeAcks.get(jsonRpcId);
3622
+ if (existing) {
3623
+ return await new Promise((resolve2) => {
3624
+ const previous = existing.resolve;
3625
+ existing.resolve = (status) => {
3626
+ previous(status);
3627
+ resolve2(status);
3628
+ };
3629
+ });
3499
3630
  }
3631
+ return await this.registerWriteAck(jsonRpcId);
3500
3632
  }
3501
- async handleServerRequest(id, method, params) {
3502
- if (method === "item/commandExecution/requestApproval") {
3503
- await this.handleApproval(
3504
- id,
3505
- method,
3506
- params,
3507
- "command",
3508
- describeCommandApproval(params).command
3509
- );
3633
+ handleStderrLine(line) {
3634
+ const kind = classifyStderrLine(line);
3635
+ if (kind === "drop") return;
3636
+ if (isFatalStderr(line)) {
3637
+ this.emit({
3638
+ eventId: nextEventId(),
3639
+ provider: CODEX_DRIVER_KIND,
3640
+ threadId: this.threadId,
3641
+ createdAt: nowIso(),
3642
+ type: "runtime.error",
3643
+ payload: { message: line }
3644
+ });
3510
3645
  return;
3511
3646
  }
3512
- if (method === "item/fileChange/requestApproval") {
3513
- const described = describeFileChangeApproval(params);
3514
- await this.handleApproval(
3515
- id,
3516
- method,
3517
- params,
3518
- "file-change",
3519
- described.callId ?? "file-change"
3520
- );
3647
+ if (kind === "error") {
3648
+ this.host.log.error(line, { threadId: this.threadId });
3521
3649
  return;
3522
3650
  }
3523
- if (method === "mcpServer/elicitation/request") {
3524
- const described = describeMcpElicitation(params);
3525
- await this.handleApproval(id, method, params, "mcp-elicitation", described.identity);
3651
+ this.host.log.warn(line, { threadId: this.threadId });
3652
+ }
3653
+ handleNotification(method, params) {
3654
+ if (this.closed) return;
3655
+ const conversationId = conversationIdFromNotification(params);
3656
+ if (conversationId && conversationId !== this.nativeThreadId && conversationId !== this.threadId) {
3657
+ this.#collab.interceptCollab(method, params, conversationId);
3526
3658
  return;
3527
3659
  }
3528
- if (method === "item/permissions/requestApproval") {
3529
- await this.handleApproval(
3530
- id,
3531
- method,
3532
- params,
3533
- "permission",
3534
- isRecord3(params) ? asString(params["itemId"]) ?? "permission" : "permission"
3535
- );
3660
+ if (conversationId && this.memoryThreadIds.has(conversationId)) {
3661
+ if (method === "serverRequest" || method === "serverRequest/resolved") {
3662
+ this.dispatchMapped(method, params);
3663
+ }
3536
3664
  return;
3537
3665
  }
3538
- if (method === "item/tool/requestUserInput") {
3539
- const requestId = this.requestIdFor(id);
3540
- const events = mapServerRequestToEvents({
3541
- requestId,
3542
- method,
3543
- params,
3544
- threadId: this.threadId,
3545
- providerInstanceId: this.host.instanceId
3546
- });
3547
- if (events.length === 0) {
3548
- await this.rpc.respond(id, {});
3666
+ if (method === "thread/started") {
3667
+ const name = isRecord3(params) ? asString(params["name"]) ?? (isRecord3(params["thread"]) ? asString(params["thread"]["name"]) : null) : null;
3668
+ if (isMemoryConsolidationThread(name ?? void 0) && conversationId) {
3669
+ this.memoryThreadIds.add(conversationId);
3549
3670
  return;
3550
3671
  }
3551
- await new Promise((resolve2) => {
3552
- this.pendingUserInputs.set(requestId, {
3553
- jsonRpcId: id,
3554
- settle: (result) => {
3555
- this.pendingUserInputs.delete(requestId);
3556
- void this.rpc.respond(id, result).catch((error) => this.#warnAnswerWriteFailed(method, id, error)).finally(resolve2);
3557
- }
3558
- });
3559
- for (const event of events) this.emit(event);
3560
- });
3561
- return;
3562
3672
  }
3563
- await this.rpc.respondError(id, -32601, `Method not found: ${method}`);
3673
+ const activity = readSubAgentActivity(method, params);
3674
+ if (activity?.agentThreadId !== void 0 && !isRootChild(activity.agentPath, activity.agentThreadId, this.nativeThreadId ?? this.threadId)) {
3675
+ this.#collab.upsertCollabChild(activity.agentThreadId, { agentPath: activity.agentPath });
3676
+ }
3677
+ this.dispatchMapped(method, params);
3564
3678
  }
3565
- async handleApproval(id, method, params, kind, identity) {
3566
- const grant = kind === "permission" ? void 0 : this.grants.lookup(codexApprovalGrantKey(kind, identity));
3567
- if (grant) {
3568
- const decision = wireDecisionFromPersist(kind, grant);
3569
- await this.rpc.respond(id, { decision });
3679
+ dispatchMapped(method, params) {
3680
+ if (method === "serverRequest/resolved") {
3681
+ const rawId = isRecord3(params) ? params["requestId"] : void 0;
3682
+ if (typeof rawId !== "number" && typeof rawId !== "string") return;
3683
+ const resolvedId = this.requestIdFor(rawId);
3684
+ const pending = this.pendingApprovals.get(resolvedId);
3685
+ if (pending === void 0) return;
3686
+ pending.release();
3687
+ this.emit({
3688
+ eventId: nextEventId(),
3689
+ provider: CODEX_DRIVER_KIND,
3690
+ threadId: this.threadId,
3691
+ createdAt: nowIso(),
3692
+ type: "request.resolved",
3693
+ requestId: resolvedId,
3694
+ payload: {
3695
+ requestType: toRequestTypeFromKind(this.requestKinds.get(resolvedId)),
3696
+ resolution: params
3697
+ }
3698
+ });
3570
3699
  return;
3571
3700
  }
3572
- const requestId = this.requestIdFor(id);
3573
- const events = mapServerRequestToEvents({
3574
- requestId,
3701
+ trackOpenCommand(this.openCommands, method, params);
3702
+ if (method === "account/rateLimits/updated" && isRecord3(params)) {
3703
+ this.rateLimits = mergeCodexRateLimits(this.rateLimits, params["rateLimits"]);
3704
+ }
3705
+ for (const event of mapNotificationToEvents({
3575
3706
  method,
3576
3707
  params,
3577
3708
  threadId: this.threadId,
3578
- providerInstanceId: this.host.instanceId
3579
- });
3580
- this.requestKinds.set(requestId, kind);
3581
- await new Promise((resolve2) => {
3582
- this.pendingApprovals.set(requestId, {
3583
- jsonRpcId: id,
3584
- method,
3585
- params,
3586
- kind,
3587
- identity,
3588
- settle: (decision) => {
3589
- this.pendingApprovals.delete(requestId);
3590
- void this.rpc.respond(id, decision).catch((error) => this.#warnAnswerWriteFailed(method, id, error)).finally(resolve2);
3591
- },
3592
- release: () => {
3593
- this.pendingApprovals.delete(requestId);
3594
- resolve2();
3595
- }
3596
- });
3597
- for (const event of events) this.emit(event);
3598
- });
3599
- }
3600
- async respondToRequest(requestId, decision, reason, persist) {
3601
- const pending = this.pendingApprovals.get(requestId);
3602
- if (!pending) throw new Error(`Unknown pending Codex approval request: ${requestId}`);
3603
- if (decision === "deny" && reason) this.declineRelay.queue(this.threadId, reason);
3604
- let wire = { decision: decision === "allow" ? "accept" : "decline" };
3605
- if (decision === "allow" && pending.kind === "mcp-elicitation") {
3606
- wire = toMcpElicitationResponse("allow", pending.params);
3709
+ providerInstanceId: this.host.instanceId,
3710
+ rateLimits: this.rateLimits
3711
+ })) {
3712
+ this.emit(event);
3607
3713
  }
3608
- if (pending.kind === "permission") {
3609
- wire = toPermissionsResponse(decision, pending.params, persist !== void 0);
3610
- } else if (decision === "allow" && persist) {
3611
- this.grants.remember(codexApprovalGrantKey(pending.kind, pending.identity), persist);
3612
- if (pending.kind !== "mcp-elicitation") {
3613
- wire = { decision: wireDecisionFromPersist(pending.kind, persist) };
3614
- }
3714
+ if (method === "turn/started") {
3715
+ const turnId = turnIdOf(params);
3716
+ if (turnId) this.activeTurnId = turnId;
3615
3717
  }
3616
- const ack = this.registerWriteAck(pending.jsonRpcId);
3617
- pending.settle(wire);
3618
- const delivery = await ack.catch(() => "closed");
3619
- this.emit({
3620
- eventId: nextEventId(),
3621
- provider: CODEX_DRIVER_KIND,
3622
- threadId: this.threadId,
3623
- createdAt: nowIso(),
3624
- type: "request.resolved",
3625
- requestId,
3626
- // requestType 是契约必填;缺了它宿主整条解不动,卡片停在「等待决议」。
3627
- payload: {
3628
- requestType: toRequestTypeFromKind(pending.kind),
3629
- decision,
3630
- ...reason ? { reason } : {}
3631
- }
3632
- });
3633
- if (delivery !== "written") throw unconfirmedAnswerError(requestId, delivery);
3718
+ if (method === "turn/completed") {
3719
+ this.activeTurnId = void 0;
3720
+ const { activeTurnId: _completedTurn, ...idleSession } = this.session;
3721
+ this.session = { ...idleSession, status: "idle", updatedAt: nowIso() };
3722
+ }
3723
+ }
3724
+ async handleServerRequest(id, method, params) {
3725
+ return this.#requests.handleServerRequest(id, method, params);
3726
+ }
3727
+ async respondToRequest(requestId, decision, reason, persist) {
3728
+ return this.#requests.respondToRequest(requestId, decision, reason, persist);
3634
3729
  }
3635
3730
  async respondToUserInput(requestId, answers) {
3636
- const pending = this.pendingUserInputs.get(requestId);
3637
- if (!pending) throw new Error(`Unknown pending Codex user input request: ${requestId}`);
3638
- const mapped = toCodexUserInputAnswers(answers);
3639
- if (mapped === void 0) {
3640
- throw new Error(`invalid user input answers for request ${requestId}`);
3641
- }
3642
- const ack = this.registerWriteAck(pending.jsonRpcId);
3643
- pending.settle({ answers: mapped });
3644
- const delivery = await ack.catch(() => "closed");
3645
- this.emit({
3646
- eventId: nextEventId(),
3647
- provider: CODEX_DRIVER_KIND,
3648
- threadId: this.threadId,
3649
- createdAt: nowIso(),
3650
- type: "user-input.resolved",
3651
- requestId,
3652
- // 契约的 answers 是 questionId → 答案 的 record,不是数组。
3653
- payload: { answers: toCanonicalUserInputAnswers(mapped) }
3654
- });
3655
- if (delivery !== "written") throw unconfirmedAnswerError(requestId, delivery);
3731
+ return this.#requests.respondToUserInput(requestId, answers);
3656
3732
  }
3657
3733
  async withActiveWriterRecovery(run, allowUnsubscribe) {
3658
3734
  let lastError;
@@ -3846,22 +3922,7 @@ var CodexSessionRuntime = class {
3846
3922
  if (target) {
3847
3923
  await this.rpc.request("turn/interrupt", { threadId: this.nativeThreadId, turnId: target }, 0).catch(() => void 0);
3848
3924
  }
3849
- this.cancelPendingApprovals();
3850
- }
3851
- /** 把还挂着的审批答成「取消」,逐条发 request.resolved。 */
3852
- cancelPendingApprovals() {
3853
- for (const [requestId, pending] of this.pendingApprovals) {
3854
- pending.settle(cancelWireFor(pending));
3855
- this.emit({
3856
- eventId: nextEventId(),
3857
- provider: CODEX_DRIVER_KIND,
3858
- threadId: this.threadId,
3859
- createdAt: nowIso(),
3860
- type: "request.resolved",
3861
- requestId,
3862
- payload: { requestType: toRequestTypeFromKind(pending.kind), decision: "cancel" }
3863
- });
3864
- }
3925
+ this.#requests.cancelPendingApprovals();
3865
3926
  }
3866
3927
  async compactContext() {
3867
3928
  if (!this.nativeThreadId) return;
@@ -3895,16 +3956,10 @@ var CodexSessionRuntime = class {
3895
3956
  }
3896
3957
  /** 会话收口时把还挂着的审批答成「取消」:应答形状按审批种类,权限审批不认 decision。 */
3897
3958
  settlePendingApprovals() {
3898
- for (const [requestId, pending] of this.pendingApprovals) {
3899
- pending.settle(cancelWireFor(pending));
3900
- this.pendingApprovals.delete(requestId);
3901
- }
3959
+ return this.#requests.settlePendingApprovals();
3902
3960
  }
3903
3961
  settlePendingUserInputs(result) {
3904
- for (const [requestId, pending] of this.pendingUserInputs) {
3905
- pending.settle(result);
3906
- this.pendingUserInputs.delete(requestId);
3907
- }
3962
+ return this.#requests.settlePendingUserInputs(result);
3908
3963
  }
3909
3964
  async close() {
3910
3965
  if (this.closeCompleted) return;
@@ -3930,17 +3985,6 @@ var CodexSessionRuntime = class {
3930
3985
  });
3931
3986
  }
3932
3987
  };
3933
- async function markProviderRejection(run) {
3934
- try {
3935
- return await run();
3936
- } catch (error) {
3937
- if (!(error instanceof CodexRpcResponseError)) throw error;
3938
- const rejected = Object.assign(new Error(error.message), {
3939
- code: "provider-rejected"
3940
- });
3941
- throw rejected;
3942
- }
3943
- }
3944
3988
 
3945
3989
  // src/sessions.ts
3946
3990
  import * as NodeFS3 from "node:fs";