@ycodium-ai/agent-codex 0.2.3086 → 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 +809 -773
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -1381,6 +1381,43 @@ 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/codexArtifacts.ts
1385
+ function imageGenerationArtifacts(input) {
1386
+ if (input.itemType !== "imageGeneration" || input.status !== "completed") return [];
1387
+ if (!isRecord3(input.params)) return [];
1388
+ const item = isRecord3(input.params["item"]) ? input.params["item"] : input.params;
1389
+ const savedPath = item["savedPath"];
1390
+ if (typeof savedPath !== "string" || savedPath.trim().length === 0) return [];
1391
+ return [{ path: savedPath, ...input.itemId ? { toolCallId: input.itemId } : {} }];
1392
+ }
1393
+
1394
+ // src/codexItemTypes.ts
1395
+ function normalizeItemType(value) {
1396
+ if (!value) return "item";
1397
+ return value.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[._/-]/g, " ").replace(/\s+/g, " ").trim().toLowerCase();
1398
+ }
1399
+ function toCanonicalItemType(raw) {
1400
+ const type = normalizeItemType(raw);
1401
+ if (type.includes("user")) return "user_message";
1402
+ if (type.includes("agent message") || type.includes("assistant")) return "assistant_message";
1403
+ if (type.includes("reasoning") || type.includes("thought")) return "reasoning";
1404
+ if (type.includes("plan") || type.includes("todo")) return "plan";
1405
+ if (type.includes("command")) return "command_execution";
1406
+ if (type.includes("file change") || type.includes("patch") || type.includes("edit")) {
1407
+ return "file_change";
1408
+ }
1409
+ if (type.includes("mcp")) return "mcp_tool_call";
1410
+ if (type.includes("dynamic tool")) return "dynamic_tool_call";
1411
+ if (type.includes("collab")) return "collab_agent_tool_call";
1412
+ if (type.includes("web search")) return "web_search";
1413
+ if (type.includes("image")) return "image_view";
1414
+ if (type.includes("review entered")) return "review_entered";
1415
+ if (type.includes("review exited")) return "review_exited";
1416
+ if (type.includes("compact")) return "context_compaction";
1417
+ if (type.includes("error")) return "error";
1418
+ return "unknown";
1419
+ }
1420
+
1384
1421
  // src/events/base.ts
1385
1422
  import * as NodeCrypto from "node:crypto";
1386
1423
  var CODEX_PROVIDER = "codex";
@@ -1657,351 +1694,119 @@ function mapCollabAgentEvent(input) {
1657
1694
  return [];
1658
1695
  }
1659
1696
 
1660
- // src/approvalMapping.ts
1661
- function fileChangeEntries(params) {
1662
- if (!isRecord3(params)) return [];
1663
- const item = isRecord3(params["item"]) ? params["item"] : params;
1664
- const changes = item["changes"] ?? item["fileChanges"] ?? params["fileChanges"];
1665
- if (!isRecord3(changes) && !Array.isArray(changes)) return [];
1666
- const entries = Array.isArray(changes) ? changes : Object.entries(changes).map(([path, change]) => ({
1667
- path,
1668
- ...isRecord3(change) ? change : {}
1669
- }));
1670
- const out = [];
1671
- for (const entry of entries) {
1672
- if (!isRecord3(entry)) continue;
1673
- const path = asString(entry["path"]) ?? asString(entry["file"]);
1674
- if (!path) continue;
1675
- out.push({
1676
- path,
1677
- kind: (asString(entry["type"]) ?? asString(entry["kind"]) ?? "update").toLowerCase(),
1678
- movePath: asString(entry["move_path"]) ?? asString(entry["movePath"]),
1679
- diff: asString(entry["unified_diff"]) ?? asString(entry["unifiedDiff"]),
1680
- content: asString(entry["content"])
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
1681
1726
  });
1682
1727
  }
1683
- return out;
1728
+ return mapped.length > 0 ? mapped : void 0;
1684
1729
  }
1685
- var MAX_DESCRIBED_FILE_CHANGES = 20;
1686
- function describeFileChanges(params) {
1687
- const entries = fileChangeEntries(params).toSorted(
1688
- (left, right) => left.path.localeCompare(right.path)
1689
- );
1690
- if (entries.length === 0) return void 0;
1691
- const lines = entries.slice(0, MAX_DESCRIBED_FILE_CHANGES).map(
1692
- (entry) => entry.movePath ? `${entry.kind} ${entry.path} -> ${entry.movePath}` : `${entry.kind} ${entry.path}`
1693
- );
1694
- const remaining = entries.length - lines.length;
1695
- return remaining > 0 ? `${lines.join("\n")}
1696
- +${remaining} more` : lines.join("\n");
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;
1697
1751
  }
1698
- function toApplyPatchFileChanges(params) {
1699
- const out = [];
1700
- for (const { path, kind, movePath, diff, content } of fileChangeEntries(params)) {
1701
- if (kind === "add") {
1702
- out.push({
1703
- path,
1704
- changeKind: "add",
1705
- ...content !== void 0 ? { content: { kind: "replace", newText: content } } : {}
1706
- });
1707
- } else if (kind === "delete") {
1708
- out.push({
1709
- path,
1710
- changeKind: "delete",
1711
- ...content !== void 0 ? { content: { kind: "replace", oldText: content, newText: "" } } : {}
1712
- });
1713
- } else if (movePath) {
1714
- out.push({
1715
- path,
1716
- changeKind: "rename",
1717
- ...diff ? { content: { kind: "unified", diff } } : {}
1718
- });
1719
- } else {
1720
- out.push({
1721
- path,
1722
- changeKind: "modify",
1723
- ...diff ? { content: { kind: "unified", diff } } : {}
1724
- });
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;
1725
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;
1726
1769
  }
1727
1770
  return out;
1728
1771
  }
1729
- function describeCommandApproval(params) {
1730
- const record = isRecord3(params) ? params : {};
1731
- const commandValue = record["command"];
1732
- const command = Array.isArray(commandValue) ? commandValue.filter((entry) => typeof entry === "string").join(" ") : asString(commandValue) ?? "";
1733
- const cwd = asString(record["cwd"]);
1734
- const reason = trimText(record["reason"]);
1735
- const callId = asString(record["callId"]);
1736
- return {
1737
- command,
1738
- ...cwd ? { cwd } : {},
1739
- ...reason ? { reason } : {},
1740
- ...callId ? { callId } : {}
1741
- };
1742
- }
1743
- function describeFileChangeApproval(params) {
1744
- const record = isRecord3(params) ? params : {};
1745
- const reason = trimText(record["reason"]);
1746
- const grantRoot = asString(record["grantRoot"]);
1747
- const callId = asString(record["callId"]);
1748
- const detail = reason ?? describeFileChanges(record) ?? trimText(grantRoot);
1749
- return {
1750
- ...reason ? { reason } : {},
1751
- ...detail ? { detail } : {},
1752
- ...grantRoot ? { grantRoot } : {},
1753
- ...callId ? { callId } : {},
1754
- fileChanges: toApplyPatchFileChanges(record)
1755
- };
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
+ );
1756
1779
  }
1757
- function describePermissionsApproval(params) {
1758
- const record = isRecord3(params) ? params : {};
1759
- const permissions = isRecord3(record["permissions"]) ? record["permissions"] : {};
1760
- const fileSystem = isRecord3(permissions["fileSystem"]) ? permissions["fileSystem"] : {};
1761
- const network = isRecord3(permissions["network"]) ? permissions["network"] : {};
1762
- const pathsOf = (value) => Array.isArray(value) ? value.flatMap((entry) => typeof entry === "string" && entry.length > 0 ? [entry] : []) : [];
1763
- const read = pathsOf(fileSystem["read"]);
1764
- const write = pathsOf(fileSystem["write"]);
1765
- const entries = Array.isArray(fileSystem["entries"]) ? fileSystem["entries"] : [];
1766
- for (const entry of entries) {
1767
- if (!isRecord3(entry) || !isRecord3(entry["path"])) continue;
1768
- const target = entry["path"];
1769
- const path = target["type"] === "path" ? asString(target["path"]) : target["type"] === "glob_pattern" ? asString(target["pattern"]) : void 0;
1770
- if (!path) continue;
1771
- if (entry["access"] === "read") read.push(path);
1772
- if (entry["access"] === "write") write.push(path);
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" } };
1773
1802
  }
1774
- const reason = trimText(record["reason"]);
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" } };
1775
1807
  return {
1776
- ...reason ? { reason } : {},
1777
- read: [...new Set(read)],
1778
- write: [...new Set(write)],
1779
- network: network["enabled"] === true
1780
- };
1781
- }
1782
- function toPermissionsResponse(decision, params, persistent) {
1783
- if (decision !== "allow") return { permissions: {} };
1784
- const requested = isRecord3(params) && isRecord3(params["permissions"]) ? params["permissions"] : {};
1785
- return {
1786
- permissions: {
1787
- ...requested["fileSystem"] !== void 0 ? { fileSystem: requested["fileSystem"] } : {},
1788
- ...requested["network"] !== void 0 ? { network: requested["network"] } : {}
1789
- },
1790
- scope: persistent ? "session" : "turn"
1791
- };
1792
- }
1793
- function describeMcpElicitation(params) {
1794
- const record = isRecord3(params) ? params : {};
1795
- const appName = asString(record["appName"]) ?? asString(record["serverName"]) ?? void 0;
1796
- const serverName = asString(record["serverName"]);
1797
- const message = trimText(record["message"]);
1798
- const url = asString(record["url"]) ?? asString(record["authorizationUrl"]);
1799
- const identity = appName ?? url ?? "unknown";
1800
- return {
1801
- ...appName ? { appName } : {},
1802
- ...serverName ? { serverName } : {},
1803
- ...message ? { message } : {},
1804
- ...url ? { url } : {},
1805
- identity,
1806
- mode: url ? "url" : "form",
1807
- ...record["requestedSchema"] !== void 0 ? { schema: record["requestedSchema"] } : {}
1808
- };
1809
- }
1810
- function persistFromMeta(meta) {
1811
- if (!isRecord3(meta)) return void 0;
1812
- const persist = asString(meta["persist"]) ?? asString(meta["persistence"]);
1813
- if (persist === "session" || persist === "acceptForSession") return "session";
1814
- if (persist === "always" || persist === "permanent" || persist === "forever" || persist === "persistent" || persist === "acceptAlways") {
1815
- return "always";
1816
- }
1817
- return void 0;
1818
- }
1819
- function toMcpElicitationResponse(decision, params) {
1820
- const described = describeMcpElicitation(params);
1821
- if (decision !== "allow" || described.mode === "url") {
1822
- return {
1823
- action: decision === "allow" ? "decline" : decision === "cancel" ? "cancel" : "decline"
1824
- };
1825
- }
1826
- const schema = isRecord3(described.schema) ? described.schema : void 0;
1827
- const properties = schema && isRecord3(schema["properties"]) ? schema["properties"] : {};
1828
- const required = schema && Array.isArray(schema["required"]) ? schema["required"] : [];
1829
- const content = {};
1830
- for (const [key, spec] of Object.entries(properties)) {
1831
- if (!isRecord3(spec)) continue;
1832
- if (Array.isArray(spec["oneOf"]) || Array.isArray(spec["enum"])) {
1833
- const options = Array.isArray(spec["oneOf"]) ? spec["oneOf"] : Array.isArray(spec["enum"]) ? spec["enum"] : [];
1834
- const first = options[0];
1835
- content[key] = isRecord3(first) ? first["const"] ?? first["value"] ?? first : first;
1836
- } else if (spec["type"] === "boolean") {
1837
- content[key] = true;
1838
- } else if (typeof spec["default"] !== "undefined") {
1839
- content[key] = spec["default"];
1840
- }
1841
- }
1842
- for (const key of required) {
1843
- if (typeof key === "string" && !(key in content)) {
1844
- return { action: "decline" };
1845
- }
1846
- }
1847
- const persist = persistFromMeta(isRecord3(params) ? params["_meta"] : void 0);
1848
- return {
1849
- action: "accept",
1850
- content,
1851
- ...persist ? { _meta: { persist } } : {}
1852
- };
1853
- }
1854
-
1855
- // src/codexArtifacts.ts
1856
- function imageGenerationArtifacts(input) {
1857
- if (input.itemType !== "imageGeneration" || input.status !== "completed") return [];
1858
- if (!isRecord3(input.params)) return [];
1859
- const item = isRecord3(input.params["item"]) ? input.params["item"] : input.params;
1860
- const savedPath = item["savedPath"];
1861
- if (typeof savedPath !== "string" || savedPath.trim().length === 0) return [];
1862
- return [{ path: savedPath, ...input.itemId ? { toolCallId: input.itemId } : {} }];
1863
- }
1864
-
1865
- // src/codexItemTypes.ts
1866
- function normalizeItemType(value) {
1867
- if (!value) return "item";
1868
- return value.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[._/-]/g, " ").replace(/\s+/g, " ").trim().toLowerCase();
1869
- }
1870
- function toCanonicalItemType(raw) {
1871
- const type = normalizeItemType(raw);
1872
- if (type.includes("user")) return "user_message";
1873
- if (type.includes("agent message") || type.includes("assistant")) return "assistant_message";
1874
- if (type.includes("reasoning") || type.includes("thought")) return "reasoning";
1875
- if (type.includes("plan") || type.includes("todo")) return "plan";
1876
- if (type.includes("command")) return "command_execution";
1877
- if (type.includes("file change") || type.includes("patch") || type.includes("edit")) {
1878
- return "file_change";
1879
- }
1880
- if (type.includes("mcp")) return "mcp_tool_call";
1881
- if (type.includes("dynamic tool")) return "dynamic_tool_call";
1882
- if (type.includes("collab")) return "collab_agent_tool_call";
1883
- if (type.includes("web search")) return "web_search";
1884
- if (type.includes("image")) return "image_view";
1885
- if (type.includes("review entered")) return "review_entered";
1886
- if (type.includes("review exited")) return "review_exited";
1887
- if (type.includes("compact")) return "context_compaction";
1888
- if (type.includes("error")) return "error";
1889
- return "unknown";
1890
- }
1891
-
1892
- // src/events/userInput.ts
1893
- function toUserInputQuestions(params) {
1894
- if (!isRecord3(params)) return void 0;
1895
- const questions = Array.isArray(params["questions"]) ? params["questions"] : [];
1896
- const mapped = [];
1897
- for (const question of questions) {
1898
- if (!isRecord3(question)) return void 0;
1899
- const id = asString(question["id"]);
1900
- const header = asString(question["header"]);
1901
- const text = asString(question["question"]);
1902
- const rawOptions = Array.isArray(question["options"]) ? question["options"] : [];
1903
- const options = [];
1904
- for (const option of rawOptions) {
1905
- if (!isRecord3(option)) continue;
1906
- const label = trimText(option["label"]);
1907
- if (!label) continue;
1908
- options.push({
1909
- ...asString(option["id"]) ? { id: asString(option["id"]) } : {},
1910
- label,
1911
- description: trimText(option["description"]) ?? ""
1912
- });
1913
- }
1914
- if (!id || !header || !text || options.length === 0) return void 0;
1915
- mapped.push({
1916
- id,
1917
- header,
1918
- question: text,
1919
- options,
1920
- multiSelect: false
1921
- });
1922
- }
1923
- return mapped.length > 0 ? mapped : void 0;
1924
- }
1925
- function toAsyncQuestions(params) {
1926
- if (!isRecord3(params)) return void 0;
1927
- const item = isRecord3(params["item"]) ? params["item"] : params;
1928
- if (item["delivery"] !== "async" || !Array.isArray(item["questions"])) return void 0;
1929
- const mapped = [];
1930
- item["questions"].forEach((question, index) => {
1931
- if (!isRecord3(question)) return;
1932
- const title = trimText(question["title"]);
1933
- if (!title) return;
1934
- const labels = Array.isArray(question["options"]) ? question["options"] : [];
1935
- const options = labels.map((label) => trimText(label)).filter((label) => label !== void 0).map((label) => ({ label, description: "" }));
1936
- mapped.push({
1937
- id: `q${index}`,
1938
- header: title,
1939
- question: title,
1940
- options,
1941
- allowFreeform: true,
1942
- multiSelect: false
1943
- });
1944
- });
1945
- return mapped.length > 0 ? mapped : void 0;
1946
- }
1947
- function toCodexUserInputAnswers(answers) {
1948
- if (!isRecord3(answers)) return void 0;
1949
- const out = {};
1950
- for (const [questionId, value] of Object.entries(answers)) {
1951
- if (typeof value === "string") {
1952
- out[questionId] = { answers: [value] };
1953
- continue;
1954
- }
1955
- if (Array.isArray(value) && value.every((entry) => typeof entry === "string")) {
1956
- out[questionId] = { answers: [...value] };
1957
- continue;
1958
- }
1959
- if (isRecord3(value) && Array.isArray(value["answers"]) && value["answers"].every((entry) => typeof entry === "string")) {
1960
- out[questionId] = { answers: [...value["answers"]] };
1961
- continue;
1962
- }
1963
- return void 0;
1964
- }
1965
- return out;
1966
- }
1967
- function toCanonicalUserInputAnswers(answers) {
1968
- return Object.fromEntries(
1969
- Object.entries(answers).map(([questionId, value]) => [
1970
- questionId,
1971
- value.answers.length === 1 ? value.answers[0] : [...value.answers]
1972
- ])
1973
- );
1974
- }
1975
-
1976
- // src/usage/codexQuota.ts
1977
- var asRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
1978
- var asFiniteNumber = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
1979
- var asNonEmptyString = (value) => typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
1980
- var clampPercent = (value) => Math.min(100, Math.max(0, value));
1981
- var epochSecondsToIso = (value) => {
1982
- const seconds = asFiniteNumber(value);
1983
- if (seconds === null) return null;
1984
- const date = new Date(seconds * 1e3);
1985
- return Number.isNaN(date.getTime()) ? null : date.toISOString();
1986
- };
1987
- var SESSION_WINDOW_MINUTES = 5 * 60;
1988
- var WEEKLY_WINDOW_MINUTES = 7 * 24 * 60;
1989
- var formatWindowDuration = (minutes) => {
1990
- if (minutes % (24 * 60) === 0) return `${String(minutes / (24 * 60))}d`;
1991
- if (minutes % 60 === 0) return `${String(minutes / 60)}h`;
1992
- return `${String(minutes)}m`;
1993
- };
1994
- var classifyWindow = (minutes, slot) => {
1995
- if (minutes === SESSION_WINDOW_MINUTES) {
1996
- return { id: "session", label: { key: "usage.window.session" } };
1997
- }
1998
- if (minutes === WEEKLY_WINDOW_MINUTES) {
1999
- return { id: "weekly", label: { key: "usage.window.weekly" } };
2000
- }
2001
- if (minutes === null) return { id: slot, label: { key: "usage.window.limit" } };
2002
- return {
2003
- id: `window-${String(minutes)}m`,
2004
- label: { key: "usage.window.duration", values: { duration: formatWindowDuration(minutes) } }
1808
+ id: `window-${String(minutes)}m`,
1809
+ label: { key: "usage.window.duration", values: { duration: formatWindowDuration(minutes) } }
2005
1810
  };
2006
1811
  };
2007
1812
  var mapWindow = (raw, slot, describe) => {
@@ -2858,23 +2663,218 @@ function mapNotificationToEvents(input) {
2858
2663
  }
2859
2664
  }
2860
2665
 
2861
- // src/events/serverRequests.ts
2862
- function toRequestTypeFromKind(kind) {
2863
- switch (kind) {
2864
- case "command":
2865
- return "command_execution_approval";
2866
- case "file-read":
2867
- return "file_read_approval";
2868
- case "file-change":
2869
- return "file_change_approval";
2870
- case "apply-patch":
2871
- return "apply_patch_approval";
2872
- case "mcp-elicitation":
2873
- return "mcp_elicitation_approval";
2874
- // 契约没有单独的权限审批类型;宿主把 unknown 当 tool 类审批卡片渲染。
2875
- case "permission":
2876
- default:
2877
- return "unknown";
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
2862
+ function toRequestTypeFromKind(kind) {
2863
+ switch (kind) {
2864
+ case "command":
2865
+ return "command_execution_approval";
2866
+ case "file-read":
2867
+ return "file_read_approval";
2868
+ case "file-change":
2869
+ return "file_change_approval";
2870
+ case "apply-patch":
2871
+ return "apply_patch_approval";
2872
+ case "mcp-elicitation":
2873
+ return "mcp_elicitation_approval";
2874
+ // 契约没有单独的权限审批类型;宿主把 unknown 当 tool 类审批卡片渲染。
2875
+ case "permission":
2876
+ default:
2877
+ return "unknown";
2878
2878
  }
2879
2879
  }
2880
2880
  function toRequestTypeFromMethod(method) {
@@ -3064,19 +3064,12 @@ function openCommandsOf(booked) {
3064
3064
  };
3065
3065
  }
3066
3066
 
3067
- // src/sessionRuntime.ts
3067
+ // src/sessionCollab.ts
3068
3068
  var COLLAB_META_LOOKUP_TIMEOUT_MS = 5e3;
3069
3069
  function metaValue(value) {
3070
3070
  const text = asString(value)?.trim();
3071
3071
  return text === void 0 || text.length === 0 ? void 0 : text;
3072
3072
  }
3073
- function cancelWireFor(pending) {
3074
- if (pending.kind === "permission") return toPermissionsResponse("cancel", pending.params, false);
3075
- return pending.kind === "mcp-elicitation" ? toMcpElicitationResponse("cancel", pending.params) : { decision: "cancel" };
3076
- }
3077
- var sleep = (ms) => new Promise((resolve2) => {
3078
- setTimeout(resolve2, ms);
3079
- });
3080
3073
  function isRootChild(agentPath, agentThreadId, rootThreadId) {
3081
3074
  if (agentThreadId === rootThreadId) return true;
3082
3075
  return agentPath === "/root" || agentPath === "/";
@@ -3090,224 +3083,10 @@ function readSubAgentActivity(method, params) {
3090
3083
  agentPath: asString(item["agentPath"]) ?? void 0
3091
3084
  };
3092
3085
  }
3093
- function pathLeaf2(value) {
3094
- if (!value) return void 0;
3095
- const parts = value.split("/").filter((part) => part.length > 0);
3096
- return parts.at(-1);
3097
- }
3098
- function unconfirmedAnswerError(requestId, delivery) {
3099
- return new Error(
3100
- 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`
3101
- );
3102
- }
3103
- var CodexSessionRuntime = class {
3104
- threadId;
3105
- cwd;
3106
- session;
3107
- nativeThreadId;
3108
- activeTurnId;
3109
- model;
3110
- runtimeMode;
3111
- /** 宿主解好的这一轮的档(座位画的就是它)。缺席 = 宿主没给,按 `runtimeMode` 查表。 */
3112
- nativeModeId;
3113
- interactionMode;
3114
- closed = false;
3115
- closeCompleted = false;
3116
- turns = [];
3117
- pendingApprovals = /* @__PURE__ */ new Map();
3118
- pendingUserInputs = /* @__PURE__ */ new Map();
3119
- requestKinds = /* @__PURE__ */ new Map();
3120
- writeAcks = /* @__PURE__ */ new Map();
3121
- grants;
3122
- declineRelay;
3123
- collabChildren = /* @__PURE__ */ new Map();
3124
- collabChildMeta = /* @__PURE__ */ new Map();
3125
- liveForeignTurns = /* @__PURE__ */ new Map();
3126
- openCommands = /* @__PURE__ */ new Map();
3127
- collabReceiverTurns = /* @__PURE__ */ new Map();
3128
- memoryThreadIds = /* @__PURE__ */ new Set();
3129
- /** 这个会话见过的主额度快照(按字段合并),额度用尽停轮时点名窗口用。 */
3130
- rateLimits;
3131
- appServerArgs;
3132
- hasConfiguredMcpServer;
3133
- host;
3134
- attachments;
3135
- rpc;
3136
- emit;
3137
- /**
3138
- * 对外 requestId 的前缀,每个会话(即每个 app-server 进程)一份。JSON-RPC id 每个进程
3139
- * 都从 0 数起,宿主按 requestId 记「已接受」,不加前缀的话重启后的新请求会撞上旧请求。
3140
- */
3141
- #requestIdPrefix = `codex-${NodeCrypto2.randomUUID()}:`;
3142
- /** JSON-RPC id → 对外 requestId。`serverRequest/resolved` 里的 id 可能是数字也可能是字符串。 */
3143
- requestIdFor(jsonRpcId) {
3144
- return `${this.#requestIdPrefix}${String(jsonRpcId)}`;
3145
- }
3146
- /** 这一轮该用哪套三字段组合:只由这一轮的档决定(见 `codexNativeModeFor`)。 */
3147
- #threadConfig() {
3148
- return codexThreadConfigForNativeMode(codexNativeModeFor(this.runtimeMode, this.nativeModeId));
3149
- }
3150
- /** 同上,沙箱策略那一位。 */
3151
- #turnSandboxPolicy() {
3152
- return codexTurnSandboxPolicyForNativeMode(
3153
- codexNativeModeFor(this.runtimeMode, this.nativeModeId)
3154
- );
3155
- }
3156
- /** 给 CLI 的应答写入失败:带上线程与方法名记一行 warn。 */
3157
- #warnAnswerWriteFailed(method, jsonRpcId, error) {
3158
- this.host.log.warn("writing an answer to codex failed", {
3159
- threadId: this.threadId,
3160
- method,
3161
- jsonRpcId,
3162
- error: error instanceof Error ? error.message : String(error)
3163
- });
3164
- }
3165
- constructor(host, rpc, emit, input, cwd, appServerArgs, grants, declineRelay, attachments) {
3166
- this.attachments = attachments;
3167
- this.host = host;
3168
- this.rpc = rpc;
3169
- this.emit = emit;
3170
- this.threadId = input.threadId;
3171
- this.cwd = cwd;
3172
- this.runtimeMode = input.runtimeMode;
3173
- this.nativeModeId = input.nativeModeId;
3174
- this.appServerArgs = appServerArgs;
3175
- this.hasConfiguredMcpServer = (input.mcpServers?.length ?? 0) > 0;
3176
- this.grants = grants ?? createApprovalGrantStore();
3177
- this.declineRelay = declineRelay ?? createDeclineReasonRelay();
3178
- const createdAt = nowIso();
3179
- this.session = {
3180
- provider: CODEX_DRIVER_KIND,
3181
- providerInstanceId: host.instanceId,
3182
- status: "idle",
3183
- runtimeMode: input.runtimeMode,
3184
- nativeModeId: codexNativeModeFor(input.runtimeMode, input.nativeModeId),
3185
- cwd,
3186
- threadId: input.threadId,
3187
- createdAt,
3188
- updatedAt: createdAt,
3189
- ...input.bindingGeneration === void 0 ? {} : { bindingGeneration: input.bindingGeneration }
3190
- };
3191
- }
3192
- registerWriteAck(jsonRpcId) {
3193
- return new Promise((resolve2) => {
3194
- this.writeAcks.set(jsonRpcId, { resolve: resolve2 });
3195
- });
3196
- }
3197
- settleNativeWrite(requestId, status) {
3198
- const ack = this.writeAcks.get(requestId);
3199
- if (!ack) return;
3200
- this.writeAcks.delete(requestId);
3201
- ack.resolve(status);
3202
- }
3203
- failPendingNativeWrites(reason) {
3204
- for (const [, ack] of this.writeAcks) ack.resolve("closed");
3205
- this.writeAcks.clear();
3206
- void reason;
3207
- }
3208
- async confirmAfterNativeWrite(jsonRpcId) {
3209
- const existing = this.writeAcks.get(jsonRpcId);
3210
- if (existing) {
3211
- return await new Promise((resolve2) => {
3212
- const previous = existing.resolve;
3213
- existing.resolve = (status) => {
3214
- previous(status);
3215
- resolve2(status);
3216
- };
3217
- });
3218
- }
3219
- return await this.registerWriteAck(jsonRpcId);
3220
- }
3221
- handleStderrLine(line) {
3222
- const kind = classifyStderrLine(line);
3223
- if (kind === "drop") return;
3224
- if (isFatalStderr(line)) {
3225
- this.emit({
3226
- eventId: nextEventId(),
3227
- provider: CODEX_DRIVER_KIND,
3228
- threadId: this.threadId,
3229
- createdAt: nowIso(),
3230
- type: "runtime.error",
3231
- payload: { message: line }
3232
- });
3233
- return;
3234
- }
3235
- if (kind === "error") {
3236
- this.host.log.error(line, { threadId: this.threadId });
3237
- return;
3238
- }
3239
- this.host.log.warn(line, { threadId: this.threadId });
3240
- }
3241
- handleNotification(method, params) {
3242
- if (this.closed) return;
3243
- const conversationId = conversationIdFromNotification(params);
3244
- if (conversationId && conversationId !== this.nativeThreadId && conversationId !== this.threadId) {
3245
- this.interceptCollab(method, params, conversationId);
3246
- return;
3247
- }
3248
- if (conversationId && this.memoryThreadIds.has(conversationId)) {
3249
- if (method === "serverRequest" || method === "serverRequest/resolved") {
3250
- this.dispatchMapped(method, params);
3251
- }
3252
- return;
3253
- }
3254
- if (method === "thread/started") {
3255
- const name = isRecord3(params) ? asString(params["name"]) ?? (isRecord3(params["thread"]) ? asString(params["thread"]["name"]) : null) : null;
3256
- if (isMemoryConsolidationThread(name ?? void 0) && conversationId) {
3257
- this.memoryThreadIds.add(conversationId);
3258
- return;
3259
- }
3260
- }
3261
- const activity = readSubAgentActivity(method, params);
3262
- if (activity?.agentThreadId !== void 0 && !isRootChild(activity.agentPath, activity.agentThreadId, this.nativeThreadId ?? this.threadId)) {
3263
- this.upsertCollabChild(activity.agentThreadId, { agentPath: activity.agentPath });
3264
- }
3265
- this.dispatchMapped(method, params);
3266
- }
3267
- dispatchMapped(method, params) {
3268
- if (method === "serverRequest/resolved") {
3269
- const rawId = isRecord3(params) ? params["requestId"] : void 0;
3270
- if (typeof rawId !== "number" && typeof rawId !== "string") return;
3271
- const resolvedId = this.requestIdFor(rawId);
3272
- const pending = this.pendingApprovals.get(resolvedId);
3273
- if (pending === void 0) return;
3274
- pending.release();
3275
- this.emit({
3276
- eventId: nextEventId(),
3277
- provider: CODEX_DRIVER_KIND,
3278
- threadId: this.threadId,
3279
- createdAt: nowIso(),
3280
- type: "request.resolved",
3281
- requestId: resolvedId,
3282
- payload: {
3283
- requestType: toRequestTypeFromKind(this.requestKinds.get(resolvedId)),
3284
- resolution: params
3285
- }
3286
- });
3287
- return;
3288
- }
3289
- trackOpenCommand(this.openCommands, method, params);
3290
- if (method === "account/rateLimits/updated" && isRecord3(params)) {
3291
- this.rateLimits = mergeCodexRateLimits(this.rateLimits, params["rateLimits"]);
3292
- }
3293
- for (const event of mapNotificationToEvents({
3294
- method,
3295
- params,
3296
- threadId: this.threadId,
3297
- providerInstanceId: this.host.instanceId,
3298
- rateLimits: this.rateLimits
3299
- })) {
3300
- this.emit(event);
3301
- }
3302
- if (method === "turn/started") {
3303
- const turnId = turnIdOf(params);
3304
- if (turnId) this.activeTurnId = turnId;
3305
- }
3306
- if (method === "turn/completed") {
3307
- this.activeTurnId = void 0;
3308
- const { activeTurnId: _completedTurn, ...idleSession } = this.session;
3309
- this.session = { ...idleSession, status: "idle", updatedAt: nowIso() };
3310
- }
3086
+ var CodexSessionCollab = class {
3087
+ runtime;
3088
+ constructor(runtime) {
3089
+ this.runtime = runtime;
3311
3090
  }
3312
3091
  interceptCollab(method, params, conversationId) {
3313
3092
  const thread = isRecord3(params) && isRecord3(params["thread"]) ? params["thread"] : void 0;
@@ -3316,8 +3095,9 @@ var CodexSessionRuntime = class {
3316
3095
  const spawn = isRecord3(subAgent) ? subAgent["thread_spawn"] : void 0;
3317
3096
  if (method === "thread/started" && thread !== void 0 && isRecord3(spawn)) {
3318
3097
  const agentPath = asString(spawn["agent_path"]) ?? void 0;
3319
- const knownPath = agentPath ?? this.collabChildren.get(conversationId)?.agentPath;
3320
- 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;
3321
3101
  this.upsertCollabChild(conversationId, {
3322
3102
  agentPath,
3323
3103
  role: asString(spawn["agent_role"]) ?? asString(thread["agentRole"]) ?? void 0,
@@ -3329,13 +3109,18 @@ var CodexSessionRuntime = class {
3329
3109
  if ((method === "item/started" || method === "item/completed") && itemType === "collabAgentToolCall") {
3330
3110
  const item = isRecord3(params) ? isRecord3(params["item"]) ? params["item"] : params : {};
3331
3111
  const receiver = asString(item["receiverThreadId"]) ?? asString(item["agentThreadId"]);
3332
- 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);
3333
3114
  return;
3334
3115
  }
3335
3116
  const activity = readSubAgentActivity(method, params);
3336
3117
  if (activity !== void 0) {
3337
3118
  const agentThreadId = activity.agentThreadId ?? conversationId;
3338
- 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
+ )) {
3339
3124
  return;
3340
3125
  }
3341
3126
  this.upsertCollabChild(agentThreadId, { agentPath: activity.agentPath });
@@ -3350,16 +3135,16 @@ var CodexSessionRuntime = class {
3350
3135
  this.updateCollabChildMeta(conversationId, { model, effort }, true);
3351
3136
  return;
3352
3137
  }
3353
- const child = this.collabChildren.get(conversationId);
3138
+ const child = this.runtime.collabChildren.get(conversationId);
3354
3139
  if (method === "turn/started") {
3355
3140
  this.reopenCollabChildMeta(conversationId);
3356
3141
  const turnId = turnIdOf(params);
3357
- if (turnId) this.liveForeignTurns.set(conversationId, turnId);
3142
+ if (turnId) this.runtime.liveForeignTurns.set(conversationId, turnId);
3358
3143
  }
3359
3144
  if (method === "turn/completed" || method === "thread/closed") {
3360
- this.liveForeignTurns.delete(conversationId);
3145
+ this.runtime.liveForeignTurns.delete(conversationId);
3361
3146
  }
3362
- if (method === "thread/closed" && this.collabChildren.has(conversationId)) {
3147
+ if (method === "thread/closed" && this.runtime.collabChildren.has(conversationId)) {
3363
3148
  this.metaOf(conversationId).closed = true;
3364
3149
  }
3365
3150
  if (!child) {
@@ -3406,12 +3191,12 @@ var CodexSessionRuntime = class {
3406
3191
  */
3407
3192
  upsertCollabChild(agentThreadId, identity) {
3408
3193
  const fill = (known, next) => known ?? (next !== void 0 && next.length > 0 ? next : void 0);
3409
- const existing = this.collabChildren.get(agentThreadId);
3194
+ const existing = this.runtime.collabChildren.get(agentThreadId);
3410
3195
  const agentPath = fill(existing?.agentPath, identity.agentPath);
3411
3196
  const role = fill(existing?.role, identity.role);
3412
3197
  const nickname = fill(existing?.nickname, identity.nickname);
3413
3198
  if (existing === void 0) {
3414
- const spawnTurnId = this.activeTurnId;
3199
+ const spawnTurnId = this.runtime.activeTurnId;
3415
3200
  const child = {
3416
3201
  agentThreadId,
3417
3202
  ...agentPath ? { agentPath } : {},
@@ -3420,7 +3205,7 @@ var CodexSessionRuntime = class {
3420
3205
  ...spawnTurnId ? { spawnTurnId } : {},
3421
3206
  status: "running"
3422
3207
  };
3423
- this.collabChildren.set(agentThreadId, child);
3208
+ this.runtime.collabChildren.set(agentThreadId, child);
3424
3209
  this.emitCollab("collabAgent/started", child);
3425
3210
  this.startCollabChildMetaLookup(agentThreadId);
3426
3211
  return;
@@ -3434,19 +3219,19 @@ var CodexSessionRuntime = class {
3434
3219
  ...role ? { role } : {},
3435
3220
  ...nickname ? { nickname } : {}
3436
3221
  };
3437
- this.collabChildren.set(agentThreadId, merged);
3222
+ this.runtime.collabChildren.set(agentThreadId, merged);
3438
3223
  this.emitCollab("collabAgent/metadata", merged, { status: merged.status });
3439
3224
  }
3440
3225
  metaOf(agentThreadId) {
3441
- let meta = this.collabChildMeta.get(agentThreadId);
3226
+ let meta = this.runtime.collabChildMeta.get(agentThreadId);
3442
3227
  if (meta === void 0) {
3443
3228
  meta = { lookupStarted: false, closed: false };
3444
- this.collabChildMeta.set(agentThreadId, meta);
3229
+ this.runtime.collabChildMeta.set(agentThreadId, meta);
3445
3230
  }
3446
3231
  return meta;
3447
3232
  }
3448
3233
  reopenCollabChildMeta(agentThreadId) {
3449
- const meta = this.collabChildMeta.get(agentThreadId);
3234
+ const meta = this.runtime.collabChildMeta.get(agentThreadId);
3450
3235
  if (meta !== void 0) meta.closed = false;
3451
3236
  }
3452
3237
  /**
@@ -3460,7 +3245,7 @@ var CodexSessionRuntime = class {
3460
3245
  if (model === meta.model && effort === meta.effort) return;
3461
3246
  if (model !== void 0) meta.model = model;
3462
3247
  if (effort !== void 0) meta.effort = effort;
3463
- const child = this.collabChildren.get(agentThreadId);
3248
+ const child = this.runtime.collabChildren.get(agentThreadId);
3464
3249
  if (child !== void 0 && !meta.closed) this.emitCollab("collabAgent/metadataUpdated", child);
3465
3250
  }
3466
3251
  /**
@@ -3471,12 +3256,12 @@ var CodexSessionRuntime = class {
3471
3256
  const meta = this.metaOf(agentThreadId);
3472
3257
  if (meta.lookupStarted || meta.closed) return;
3473
3258
  meta.lookupStarted = true;
3474
- void this.rpc.request(
3259
+ void this.runtime.rpc.request(
3475
3260
  "thread/resume",
3476
3261
  { threadId: agentThreadId, excludeTurns: true },
3477
3262
  COLLAB_META_LOOKUP_TIMEOUT_MS
3478
3263
  ).then((response) => {
3479
- if (this.closed || meta.closed || !isRecord3(response)) return;
3264
+ if (this.runtime.closed || meta.closed || !isRecord3(response)) return;
3480
3265
  const thread = response["thread"];
3481
3266
  if (!isRecord3(thread) || thread["id"] !== agentThreadId) return;
3482
3267
  this.updateCollabChildMeta(
@@ -3487,180 +3272,463 @@ var CodexSessionRuntime = class {
3487
3272
  }).catch(() => void 0);
3488
3273
  }
3489
3274
  emitCollab(method, child, extra) {
3490
- const meta = this.collabChildMeta.get(child.agentThreadId);
3275
+ const meta = this.runtime.collabChildMeta.get(child.agentThreadId);
3491
3276
  const params = {
3492
3277
  agentThreadId: child.agentThreadId,
3493
3278
  agentPath: child.agentPath,
3494
- agentRole: child.role ?? pathLeaf2(child.agentPath) ?? "general-purpose",
3279
+ agentRole: child.role ?? pathLeaf(child.agentPath) ?? "general-purpose",
3495
3280
  agentNickname: child.nickname,
3496
3281
  ...meta?.model ? { model: meta.model } : {},
3497
3282
  ...meta?.effort ? { effort: meta.effort } : {},
3498
3283
  ...isRecord3(extra) ? extra : {}
3499
3284
  };
3500
- for (const event of mapNotificationToEvents({
3501
- method,
3502
- params,
3503
- threadId: this.threadId,
3504
- providerInstanceId: this.host.instanceId
3505
- })) {
3506
- this.emit(event);
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 });
3389
+ return;
3390
+ }
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);
3427
+ }
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
+ }
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);
3454
+ }
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}`);
3461
+ }
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
+ });
3490
+ }
3491
+ }
3492
+ /** 会话收口时把还挂着的审批答成「取消」:应答形状按审批种类,权限审批不认 decision。 */
3493
+ settlePendingApprovals() {
3494
+ for (const [requestId, pending] of this.runtime.pendingApprovals) {
3495
+ pending.settle(cancelWireFor(pending));
3496
+ this.runtime.pendingApprovals.delete(requestId);
3497
+ }
3498
+ }
3499
+ settlePendingUserInputs(result) {
3500
+ for (const [requestId, pending] of this.runtime.pendingUserInputs) {
3501
+ pending.settle(result);
3502
+ this.runtime.pendingUserInputs.delete(requestId);
3503
+ }
3504
+ }
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;
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);
3558
+ /**
3559
+ * 对外 requestId 的前缀,每个会话(即每个 app-server 进程)一份。JSON-RPC id 每个进程
3560
+ * 都从 0 数起,宿主按 requestId 记「已接受」,不加前缀的话重启后的新请求会撞上旧请求。
3561
+ */
3562
+ #requestIdPrefix = `codex-${NodeCrypto2.randomUUID()}:`;
3563
+ /** JSON-RPC id → 对外 requestId。`serverRequest/resolved` 里的 id 可能是数字也可能是字符串。 */
3564
+ requestIdFor(jsonRpcId) {
3565
+ return `${this.#requestIdPrefix}${String(jsonRpcId)}`;
3566
+ }
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 }
3602
+ };
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
+ });
3507
3630
  }
3631
+ return await this.registerWriteAck(jsonRpcId);
3508
3632
  }
3509
- async handleServerRequest(id, method, params) {
3510
- if (method === "item/commandExecution/requestApproval") {
3511
- await this.handleApproval(
3512
- id,
3513
- method,
3514
- params,
3515
- "command",
3516
- describeCommandApproval(params).command
3517
- );
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
+ });
3518
3645
  return;
3519
3646
  }
3520
- if (method === "item/fileChange/requestApproval") {
3521
- const described = describeFileChangeApproval(params);
3522
- await this.handleApproval(
3523
- id,
3524
- method,
3525
- params,
3526
- "file-change",
3527
- described.callId ?? "file-change"
3528
- );
3647
+ if (kind === "error") {
3648
+ this.host.log.error(line, { threadId: this.threadId });
3529
3649
  return;
3530
3650
  }
3531
- if (method === "mcpServer/elicitation/request") {
3532
- const described = describeMcpElicitation(params);
3533
- 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);
3534
3658
  return;
3535
3659
  }
3536
- if (method === "item/permissions/requestApproval") {
3537
- await this.handleApproval(
3538
- id,
3539
- method,
3540
- params,
3541
- "permission",
3542
- isRecord3(params) ? asString(params["itemId"]) ?? "permission" : "permission"
3543
- );
3660
+ if (conversationId && this.memoryThreadIds.has(conversationId)) {
3661
+ if (method === "serverRequest" || method === "serverRequest/resolved") {
3662
+ this.dispatchMapped(method, params);
3663
+ }
3544
3664
  return;
3545
3665
  }
3546
- if (method === "item/tool/requestUserInput") {
3547
- const requestId = this.requestIdFor(id);
3548
- const events = mapServerRequestToEvents({
3549
- requestId,
3550
- method,
3551
- params,
3552
- threadId: this.threadId,
3553
- providerInstanceId: this.host.instanceId
3554
- });
3555
- if (events.length === 0) {
3556
- 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);
3557
3670
  return;
3558
3671
  }
3559
- await new Promise((resolve2) => {
3560
- this.pendingUserInputs.set(requestId, {
3561
- jsonRpcId: id,
3562
- settle: (result) => {
3563
- this.pendingUserInputs.delete(requestId);
3564
- void this.rpc.respond(id, result).catch((error) => this.#warnAnswerWriteFailed(method, id, error)).finally(resolve2);
3565
- }
3566
- });
3567
- for (const event of events) this.emit(event);
3568
- });
3569
- return;
3570
3672
  }
3571
- 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);
3572
3678
  }
3573
- async handleApproval(id, method, params, kind, identity) {
3574
- const grant = kind === "permission" ? void 0 : this.grants.lookup(codexApprovalGrantKey(kind, identity));
3575
- if (grant) {
3576
- const decision = wireDecisionFromPersist(kind, grant);
3577
- 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
+ });
3578
3699
  return;
3579
3700
  }
3580
- const requestId = this.requestIdFor(id);
3581
- const events = mapServerRequestToEvents({
3582
- 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({
3583
3706
  method,
3584
3707
  params,
3585
3708
  threadId: this.threadId,
3586
- providerInstanceId: this.host.instanceId
3587
- });
3588
- this.requestKinds.set(requestId, kind);
3589
- await new Promise((resolve2) => {
3590
- this.pendingApprovals.set(requestId, {
3591
- jsonRpcId: id,
3592
- method,
3593
- params,
3594
- kind,
3595
- identity,
3596
- settle: (decision) => {
3597
- this.pendingApprovals.delete(requestId);
3598
- void this.rpc.respond(id, decision).catch((error) => this.#warnAnswerWriteFailed(method, id, error)).finally(resolve2);
3599
- },
3600
- release: () => {
3601
- this.pendingApprovals.delete(requestId);
3602
- resolve2();
3603
- }
3604
- });
3605
- for (const event of events) this.emit(event);
3606
- });
3607
- }
3608
- async respondToRequest(requestId, decision, reason, persist) {
3609
- const pending = this.pendingApprovals.get(requestId);
3610
- if (!pending) throw new Error(`Unknown pending Codex approval request: ${requestId}`);
3611
- if (decision === "deny" && reason) this.declineRelay.queue(this.threadId, reason);
3612
- let wire = { decision: decision === "allow" ? "accept" : "decline" };
3613
- if (decision === "allow" && pending.kind === "mcp-elicitation") {
3614
- wire = toMcpElicitationResponse("allow", pending.params);
3709
+ providerInstanceId: this.host.instanceId,
3710
+ rateLimits: this.rateLimits
3711
+ })) {
3712
+ this.emit(event);
3615
3713
  }
3616
- if (pending.kind === "permission") {
3617
- wire = toPermissionsResponse(decision, pending.params, persist !== void 0);
3618
- } else if (decision === "allow" && persist) {
3619
- this.grants.remember(codexApprovalGrantKey(pending.kind, pending.identity), persist);
3620
- if (pending.kind !== "mcp-elicitation") {
3621
- wire = { decision: wireDecisionFromPersist(pending.kind, persist) };
3622
- }
3714
+ if (method === "turn/started") {
3715
+ const turnId = turnIdOf(params);
3716
+ if (turnId) this.activeTurnId = turnId;
3623
3717
  }
3624
- const ack = this.registerWriteAck(pending.jsonRpcId);
3625
- pending.settle(wire);
3626
- const delivery = await ack.catch(() => "closed");
3627
- this.emit({
3628
- eventId: nextEventId(),
3629
- provider: CODEX_DRIVER_KIND,
3630
- threadId: this.threadId,
3631
- createdAt: nowIso(),
3632
- type: "request.resolved",
3633
- requestId,
3634
- // requestType 是契约必填;缺了它宿主整条解不动,卡片停在「等待决议」。
3635
- payload: {
3636
- requestType: toRequestTypeFromKind(pending.kind),
3637
- decision,
3638
- ...reason ? { reason } : {}
3639
- }
3640
- });
3641
- 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);
3642
3729
  }
3643
3730
  async respondToUserInput(requestId, answers) {
3644
- const pending = this.pendingUserInputs.get(requestId);
3645
- if (!pending) throw new Error(`Unknown pending Codex user input request: ${requestId}`);
3646
- const mapped = toCodexUserInputAnswers(answers);
3647
- if (mapped === void 0) {
3648
- throw new Error(`invalid user input answers for request ${requestId}`);
3649
- }
3650
- const ack = this.registerWriteAck(pending.jsonRpcId);
3651
- pending.settle({ answers: mapped });
3652
- const delivery = await ack.catch(() => "closed");
3653
- this.emit({
3654
- eventId: nextEventId(),
3655
- provider: CODEX_DRIVER_KIND,
3656
- threadId: this.threadId,
3657
- createdAt: nowIso(),
3658
- type: "user-input.resolved",
3659
- requestId,
3660
- // 契约的 answers 是 questionId → 答案 的 record,不是数组。
3661
- payload: { answers: toCanonicalUserInputAnswers(mapped) }
3662
- });
3663
- if (delivery !== "written") throw unconfirmedAnswerError(requestId, delivery);
3731
+ return this.#requests.respondToUserInput(requestId, answers);
3664
3732
  }
3665
3733
  async withActiveWriterRecovery(run, allowUnsubscribe) {
3666
3734
  let lastError;
@@ -3854,22 +3922,7 @@ var CodexSessionRuntime = class {
3854
3922
  if (target) {
3855
3923
  await this.rpc.request("turn/interrupt", { threadId: this.nativeThreadId, turnId: target }, 0).catch(() => void 0);
3856
3924
  }
3857
- this.cancelPendingApprovals();
3858
- }
3859
- /** 把还挂着的审批答成「取消」,逐条发 request.resolved。 */
3860
- cancelPendingApprovals() {
3861
- for (const [requestId, pending] of this.pendingApprovals) {
3862
- pending.settle(cancelWireFor(pending));
3863
- this.emit({
3864
- eventId: nextEventId(),
3865
- provider: CODEX_DRIVER_KIND,
3866
- threadId: this.threadId,
3867
- createdAt: nowIso(),
3868
- type: "request.resolved",
3869
- requestId,
3870
- payload: { requestType: toRequestTypeFromKind(pending.kind), decision: "cancel" }
3871
- });
3872
- }
3925
+ this.#requests.cancelPendingApprovals();
3873
3926
  }
3874
3927
  async compactContext() {
3875
3928
  if (!this.nativeThreadId) return;
@@ -3903,16 +3956,10 @@ var CodexSessionRuntime = class {
3903
3956
  }
3904
3957
  /** 会话收口时把还挂着的审批答成「取消」:应答形状按审批种类,权限审批不认 decision。 */
3905
3958
  settlePendingApprovals() {
3906
- for (const [requestId, pending] of this.pendingApprovals) {
3907
- pending.settle(cancelWireFor(pending));
3908
- this.pendingApprovals.delete(requestId);
3909
- }
3959
+ return this.#requests.settlePendingApprovals();
3910
3960
  }
3911
3961
  settlePendingUserInputs(result) {
3912
- for (const [requestId, pending] of this.pendingUserInputs) {
3913
- pending.settle(result);
3914
- this.pendingUserInputs.delete(requestId);
3915
- }
3962
+ return this.#requests.settlePendingUserInputs(result);
3916
3963
  }
3917
3964
  async close() {
3918
3965
  if (this.closeCompleted) return;
@@ -3938,17 +3985,6 @@ var CodexSessionRuntime = class {
3938
3985
  });
3939
3986
  }
3940
3987
  };
3941
- async function markProviderRejection(run) {
3942
- try {
3943
- return await run();
3944
- } catch (error) {
3945
- if (!(error instanceof CodexRpcResponseError)) throw error;
3946
- const rejected = Object.assign(new Error(error.message), {
3947
- code: "provider-rejected"
3948
- });
3949
- throw rejected;
3950
- }
3951
- }
3952
3988
 
3953
3989
  // src/sessions.ts
3954
3990
  import * as NodeFS3 from "node:fs";