@leo000001/codex-mcp 2.1.6 → 2.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +56 -10
- package/dist/index.js +495 -52
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -340,6 +340,7 @@ var SESSION_ACTIONS = [
|
|
|
340
340
|
"cancel",
|
|
341
341
|
"interrupt",
|
|
342
342
|
"fork",
|
|
343
|
+
"clean",
|
|
343
344
|
"clean_background_terminals"
|
|
344
345
|
];
|
|
345
346
|
var CHECK_ACTIONS = ["poll", "respond_permission", "respond_user_input"];
|
|
@@ -393,7 +394,7 @@ var DEFAULT_TERMINAL_CLEANUP_MS = 5 * 60 * 1e3;
|
|
|
393
394
|
var CLEANUP_INTERVAL_MS = 6e4;
|
|
394
395
|
|
|
395
396
|
// src/app-server/client.ts
|
|
396
|
-
var CLIENT_VERSION = true ? "2.1.
|
|
397
|
+
var CLIENT_VERSION = true ? "2.1.7" : "0.0.0-dev";
|
|
397
398
|
var DEFAULT_REQUEST_TIMEOUT = 3e4;
|
|
398
399
|
var STARTUP_REQUEST_TIMEOUT = 9e4;
|
|
399
400
|
var MAX_WRITE_QUEUE_BYTES = 5 * 1024 * 1024;
|
|
@@ -844,6 +845,31 @@ function resolveAndValidateFilePath(inputPath, baseDir, label = "path") {
|
|
|
844
845
|
return resolved;
|
|
845
846
|
}
|
|
846
847
|
|
|
848
|
+
// src/utils/turn-compat.ts
|
|
849
|
+
function classifyTurnCompatibilityError(err) {
|
|
850
|
+
const message = (err instanceof Error ? err.message : String(err)).toLowerCase();
|
|
851
|
+
const mentionsMinimal = message.includes("minimal");
|
|
852
|
+
const mentionsWebSearch = message.includes("web_search") || message.includes("web search");
|
|
853
|
+
const mentionsEffort = message.includes("effort") || message.includes("reasoning_effort") || message.includes("reasoning effort");
|
|
854
|
+
return mentionsMinimal && mentionsWebSearch && mentionsEffort ? "minimal_web_search" : void 0;
|
|
855
|
+
}
|
|
856
|
+
function compatibilityErrorMessage(kind) {
|
|
857
|
+
switch (kind) {
|
|
858
|
+
case "minimal_web_search":
|
|
859
|
+
return `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: effort=minimal is incompatible with the Codex web_search tool in this CLI build. Use effort=low or higher, or let codex-mcp auto-upgrade it.`;
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
function toFriendlyTurnCompatibilityError(err) {
|
|
863
|
+
const kind = classifyTurnCompatibilityError(err);
|
|
864
|
+
if (kind) {
|
|
865
|
+
return new Error(compatibilityErrorMessage(kind));
|
|
866
|
+
}
|
|
867
|
+
return err instanceof Error ? err : new Error(String(err));
|
|
868
|
+
}
|
|
869
|
+
function buildEffortFallbackWarning(from, to) {
|
|
870
|
+
return `effort=${from} is incompatible with the Codex web_search tool in this CLI build; automatically retried with effort=${to}.`;
|
|
871
|
+
}
|
|
872
|
+
|
|
847
873
|
// src/session/manager.ts
|
|
848
874
|
var COALESCED_PROGRESS_DELTA_METHODS = /* @__PURE__ */ new Set([
|
|
849
875
|
Methods.COMMAND_OUTPUT_DELTA,
|
|
@@ -851,6 +877,14 @@ var COALESCED_PROGRESS_DELTA_METHODS = /* @__PURE__ */ new Set([
|
|
|
851
877
|
Methods.REASONING_TEXT_DELTA,
|
|
852
878
|
Methods.REASONING_SUMMARY_DELTA
|
|
853
879
|
]);
|
|
880
|
+
var SKIPPABLE_DELTA_METHODS = /* @__PURE__ */ new Set([
|
|
881
|
+
Methods.AGENT_MESSAGE_DELTA,
|
|
882
|
+
Methods.COMMAND_OUTPUT_DELTA,
|
|
883
|
+
Methods.FILE_CHANGE_OUTPUT_DELTA,
|
|
884
|
+
Methods.REASONING_TEXT_DELTA,
|
|
885
|
+
Methods.REASONING_SUMMARY_DELTA,
|
|
886
|
+
Methods.PLAN_DELTA
|
|
887
|
+
]);
|
|
854
888
|
var MAX_COALESCED_DELTA_CHARS = 16384;
|
|
855
889
|
var AUTH_REFRESH_UNSUPPORTED_CODE = -32e3;
|
|
856
890
|
var AUTH_REFRESH_UNSUPPORTED_MESSAGE = "account/chatgptAuthTokens/refresh unsupported: codex-mcp does not manage external ChatGPT auth tokens";
|
|
@@ -883,6 +917,22 @@ function stripShellNoise(delta) {
|
|
|
883
917
|
}
|
|
884
918
|
var MAX_WAITERS_PER_SESSION = 4;
|
|
885
919
|
var MAX_WAIT_MS = 12e4;
|
|
920
|
+
var EFFORT_FALLBACK_LEVEL = "low";
|
|
921
|
+
var CLEANABLE_SESSION_STATUSES = ["idle", "error", "cancelled"];
|
|
922
|
+
var REASONING_PROGRESS_METHODS = /* @__PURE__ */ new Set([
|
|
923
|
+
Methods.REASONING_TEXT_DELTA,
|
|
924
|
+
Methods.REASONING_SUMMARY_DELTA,
|
|
925
|
+
Methods.REASONING_SUMMARY_PART_ADDED,
|
|
926
|
+
Methods.PLAN_DELTA
|
|
927
|
+
]);
|
|
928
|
+
var ACTING_PROGRESS_METHODS = /* @__PURE__ */ new Set([
|
|
929
|
+
Methods.COMMAND_OUTPUT_DELTA,
|
|
930
|
+
Methods.COMMAND_TERMINAL_INTERACTION,
|
|
931
|
+
Methods.FILE_CHANGE_OUTPUT_DELTA,
|
|
932
|
+
Methods.MCP_TOOL_PROGRESS,
|
|
933
|
+
Methods.TURN_DIFF_UPDATED,
|
|
934
|
+
Methods.TURN_PLAN_UPDATED
|
|
935
|
+
]);
|
|
886
936
|
var SessionManager = class {
|
|
887
937
|
sessions = /* @__PURE__ */ new Map();
|
|
888
938
|
clients = /* @__PURE__ */ new Map();
|
|
@@ -937,7 +987,12 @@ var SessionManager = class {
|
|
|
937
987
|
sandbox: rec.meta.sandbox,
|
|
938
988
|
eventBuffer: createEventBuffer(),
|
|
939
989
|
pendingRequests: /* @__PURE__ */ new Map(),
|
|
940
|
-
lastResult: rec.result
|
|
990
|
+
lastResult: rec.result,
|
|
991
|
+
lastAgentMessageText: typeof rec.result?.text === "string" ? rec.result.text : typeof rec.result?.output === "string" ? rec.result.output : void 0,
|
|
992
|
+
progressState: {
|
|
993
|
+
lastEventAt: rec.meta.lastActiveAt ?? now,
|
|
994
|
+
tokens: extractTokens(rec.result?.turn)
|
|
995
|
+
}
|
|
941
996
|
};
|
|
942
997
|
this.sessions.set(rec.sessionId, session);
|
|
943
998
|
if (rec.lastSeq >= 0) {
|
|
@@ -972,6 +1027,26 @@ var SessionManager = class {
|
|
|
972
1027
|
} catch {
|
|
973
1028
|
}
|
|
974
1029
|
}
|
|
1030
|
+
async startTurnWithCompatibilityFallback(client, turnParams) {
|
|
1031
|
+
try {
|
|
1032
|
+
return { turnStartResult: await client.turnStart(turnParams) };
|
|
1033
|
+
} catch (err) {
|
|
1034
|
+
if (turnParams.effort === "minimal" && classifyTurnCompatibilityError(err) === "minimal_web_search") {
|
|
1035
|
+
try {
|
|
1036
|
+
return {
|
|
1037
|
+
turnStartResult: await client.turnStart({
|
|
1038
|
+
...turnParams,
|
|
1039
|
+
effort: EFFORT_FALLBACK_LEVEL
|
|
1040
|
+
}),
|
|
1041
|
+
compatWarnings: [buildEffortFallbackWarning("minimal", EFFORT_FALLBACK_LEVEL)]
|
|
1042
|
+
};
|
|
1043
|
+
} catch (retryErr) {
|
|
1044
|
+
throw toFriendlyTurnCompatibilityError(retryErr);
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
throw toFriendlyTurnCompatibilityError(err);
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
975
1050
|
// ── Session Creation ─────────────────────────────────────────────
|
|
976
1051
|
async createSession(prompt, cwd, spawnOpts, effort, advanced) {
|
|
977
1052
|
const sessionId = `sess_${randomUUID().slice(0, 12)}`;
|
|
@@ -993,7 +1068,9 @@ var SessionManager = class {
|
|
|
993
1068
|
sandbox: spawnOpts.sandbox,
|
|
994
1069
|
config: spawnOpts.config,
|
|
995
1070
|
eventBuffer: createEventBuffer(),
|
|
996
|
-
pendingRequests: /* @__PURE__ */ new Map()
|
|
1071
|
+
pendingRequests: /* @__PURE__ */ new Map(),
|
|
1072
|
+
lastAgentMessageText: void 0,
|
|
1073
|
+
progressState: { lastEventAt: now }
|
|
997
1074
|
};
|
|
998
1075
|
this.sessions.set(sessionId, session);
|
|
999
1076
|
this.clients.set(sessionId, client);
|
|
@@ -1029,20 +1106,23 @@ var SessionManager = class {
|
|
|
1029
1106
|
input.push({ type: "localImage", path: imagePath });
|
|
1030
1107
|
}
|
|
1031
1108
|
}
|
|
1032
|
-
const
|
|
1109
|
+
const turnStart = await this.startTurnWithCompatibilityFallback(client, {
|
|
1033
1110
|
threadId,
|
|
1034
1111
|
input,
|
|
1035
1112
|
effort,
|
|
1036
1113
|
summary: advanced?.summary,
|
|
1037
1114
|
outputSchema: advanced?.outputSchema
|
|
1038
1115
|
});
|
|
1116
|
+
const turnStartResult = turnStart.turnStartResult;
|
|
1039
1117
|
const startedTurnId = extractTurnId(turnStartResult);
|
|
1040
1118
|
if (startedTurnId) session.activeTurnId = startedTurnId;
|
|
1041
1119
|
return {
|
|
1042
1120
|
sessionId,
|
|
1043
1121
|
threadId,
|
|
1044
1122
|
status: "running",
|
|
1045
|
-
pollInterval: DEFAULT_POLL_INTERVAL
|
|
1123
|
+
pollInterval: DEFAULT_POLL_INTERVAL,
|
|
1124
|
+
compatWarnings: turnStart.compatWarnings,
|
|
1125
|
+
progress: this.getProgress(sessionId)
|
|
1046
1126
|
};
|
|
1047
1127
|
} catch (err) {
|
|
1048
1128
|
session.status = "error";
|
|
@@ -1075,6 +1155,7 @@ var SessionManager = class {
|
|
|
1075
1155
|
);
|
|
1076
1156
|
}
|
|
1077
1157
|
clearTerminalEvents(session.eventBuffer);
|
|
1158
|
+
session.lastAgentMessageText = void 0;
|
|
1078
1159
|
session.status = "running";
|
|
1079
1160
|
session.lastActiveAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1080
1161
|
this.persistSessionIfChanged(session);
|
|
@@ -1094,8 +1175,11 @@ var SessionManager = class {
|
|
|
1094
1175
|
if (overrides?.sandbox) {
|
|
1095
1176
|
turnParams.sandboxPolicy = toSandboxPolicy(overrides.sandbox);
|
|
1096
1177
|
}
|
|
1178
|
+
let compatWarnings;
|
|
1097
1179
|
try {
|
|
1098
|
-
const
|
|
1180
|
+
const turnStart = await this.startTurnWithCompatibilityFallback(client, turnParams);
|
|
1181
|
+
compatWarnings = turnStart.compatWarnings;
|
|
1182
|
+
const turnStartResult = turnStart.turnStartResult;
|
|
1099
1183
|
const startedTurnId = extractTurnId(turnStartResult);
|
|
1100
1184
|
if (startedTurnId) session.activeTurnId = startedTurnId;
|
|
1101
1185
|
const canOverride = client.supportsTurnOverrides;
|
|
@@ -1120,7 +1204,9 @@ var SessionManager = class {
|
|
|
1120
1204
|
sessionId,
|
|
1121
1205
|
threadId: session.threadId,
|
|
1122
1206
|
status: "running",
|
|
1123
|
-
pollInterval: DEFAULT_POLL_INTERVAL
|
|
1207
|
+
pollInterval: DEFAULT_POLL_INTERVAL,
|
|
1208
|
+
compatWarnings,
|
|
1209
|
+
progress: this.getProgress(sessionId)
|
|
1124
1210
|
};
|
|
1125
1211
|
}
|
|
1126
1212
|
// ── Session Management ───────────────────────────────────────────
|
|
@@ -1166,6 +1252,9 @@ var SessionManager = class {
|
|
|
1166
1252
|
getLastResult(sessionId) {
|
|
1167
1253
|
return this.getSessionOrThrow(sessionId).lastResult;
|
|
1168
1254
|
}
|
|
1255
|
+
getProgress(sessionId) {
|
|
1256
|
+
return buildProgressInfo(this.getSessionOrThrow(sessionId));
|
|
1257
|
+
}
|
|
1169
1258
|
getPendingActionTypes(sessionId) {
|
|
1170
1259
|
const session = this.getSessionOrThrow(sessionId);
|
|
1171
1260
|
const actionTypes = /* @__PURE__ */ new Set();
|
|
@@ -1285,6 +1374,50 @@ var SessionManager = class {
|
|
|
1285
1374
|
true
|
|
1286
1375
|
);
|
|
1287
1376
|
}
|
|
1377
|
+
async cleanSessions(options) {
|
|
1378
|
+
const statuses = new Set(options?.statuses ?? CLEANABLE_SESSION_STATUSES);
|
|
1379
|
+
const olderThanMs = options?.olderThanMs;
|
|
1380
|
+
const dryRun = options?.dryRun ?? false;
|
|
1381
|
+
const includeDisk = options?.includeDisk ?? true;
|
|
1382
|
+
const now = Date.now();
|
|
1383
|
+
const matchedSessionIds = [];
|
|
1384
|
+
for (const [sessionId, session] of Array.from(this.sessions.entries())) {
|
|
1385
|
+
if (!statuses.has(session.status)) continue;
|
|
1386
|
+
if (typeof olderThanMs === "number" && olderThanMs > 0) {
|
|
1387
|
+
const lastActive = new Date(session.lastActiveAt).getTime();
|
|
1388
|
+
if (!Number.isFinite(lastActive)) continue;
|
|
1389
|
+
if (now - lastActive < olderThanMs) continue;
|
|
1390
|
+
}
|
|
1391
|
+
matchedSessionIds.push(sessionId);
|
|
1392
|
+
}
|
|
1393
|
+
if (dryRun) {
|
|
1394
|
+
return {
|
|
1395
|
+
matchedSessionIds,
|
|
1396
|
+
removedSessionIds: [],
|
|
1397
|
+
removedCount: 0,
|
|
1398
|
+
diskSessionsRemoved: 0,
|
|
1399
|
+
dryRun: true
|
|
1400
|
+
};
|
|
1401
|
+
}
|
|
1402
|
+
let diskSessionsRemoved = 0;
|
|
1403
|
+
const removedSessionIds = [];
|
|
1404
|
+
for (const sessionId of matchedSessionIds) {
|
|
1405
|
+
const evicted = this.evictSession(sessionId, includeDisk);
|
|
1406
|
+
if (evicted.deleted) {
|
|
1407
|
+
removedSessionIds.push(sessionId);
|
|
1408
|
+
}
|
|
1409
|
+
if (evicted.diskRemoved) {
|
|
1410
|
+
diskSessionsRemoved++;
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
return {
|
|
1414
|
+
matchedSessionIds,
|
|
1415
|
+
removedSessionIds,
|
|
1416
|
+
removedCount: removedSessionIds.length,
|
|
1417
|
+
diskSessionsRemoved,
|
|
1418
|
+
dryRun: false
|
|
1419
|
+
};
|
|
1420
|
+
}
|
|
1288
1421
|
async forkSession(sessionId) {
|
|
1289
1422
|
const session = this.getSessionOrThrow(sessionId);
|
|
1290
1423
|
const originalClient = this.getClientOrThrow(sessionId);
|
|
@@ -1408,26 +1541,48 @@ var SessionManager = class {
|
|
|
1408
1541
|
const buf = session.eventBuffer;
|
|
1409
1542
|
const responseMode = options.responseMode ?? "full";
|
|
1410
1543
|
const pollOptions = options.pollOptions;
|
|
1411
|
-
const
|
|
1544
|
+
const finalOnly = pollOptions?.finalOnly ?? false;
|
|
1545
|
+
const skipDeltas = pollOptions?.skipDeltas ?? false;
|
|
1546
|
+
const includeEvents = finalOnly ? false : pollOptions?.includeEvents ?? true;
|
|
1412
1547
|
const includeActions = pollOptions?.includeActions ?? true;
|
|
1413
|
-
const includeResult = pollOptions?.includeResult ?? true;
|
|
1548
|
+
const includeResult = finalOnly ? true : pollOptions?.includeResult ?? true;
|
|
1414
1549
|
const maxBytes = pollOptions?.maxBytes;
|
|
1415
1550
|
const effectiveCursor = cursor ?? session.lastEventCursor;
|
|
1416
|
-
|
|
1551
|
+
const unseenEvents = buf.events.filter((e) => e.id >= effectiveCursor);
|
|
1552
|
+
let events = includeEvents ? unseenEvents : [];
|
|
1417
1553
|
let cursorResetTo;
|
|
1418
|
-
if (
|
|
1554
|
+
if (buf.events.length > 0) {
|
|
1419
1555
|
const earliest = buf.events[0].id;
|
|
1420
1556
|
if (earliest > effectiveCursor) {
|
|
1421
1557
|
cursorResetTo = earliest;
|
|
1422
|
-
|
|
1558
|
+
if (includeEvents) {
|
|
1559
|
+
events = buf.events;
|
|
1560
|
+
}
|
|
1423
1561
|
}
|
|
1424
1562
|
}
|
|
1425
1563
|
const cursorFloor = cursorResetTo ?? effectiveCursor;
|
|
1426
|
-
|
|
1427
|
-
|
|
1564
|
+
let highestConsumedEventId;
|
|
1565
|
+
if (includeEvents) {
|
|
1566
|
+
if (skipDeltas) {
|
|
1567
|
+
const filtered = [];
|
|
1568
|
+
for (const event of events) {
|
|
1569
|
+
highestConsumedEventId = event.id;
|
|
1570
|
+
if (isSkippableDeltaEvent(event)) continue;
|
|
1571
|
+
filtered.push(event);
|
|
1572
|
+
if (filtered.length >= maxEvents) break;
|
|
1573
|
+
}
|
|
1574
|
+
events = filtered;
|
|
1575
|
+
} else if (events.length > maxEvents) {
|
|
1576
|
+
events = events.slice(0, maxEvents);
|
|
1577
|
+
highestConsumedEventId = events.length > 0 ? events[events.length - 1]?.id : void 0;
|
|
1578
|
+
} else if (events.length > 0) {
|
|
1579
|
+
highestConsumedEventId = events[events.length - 1]?.id;
|
|
1580
|
+
}
|
|
1581
|
+
} else if (finalOnly && unseenEvents.length > 0) {
|
|
1582
|
+
highestConsumedEventId = unseenEvents[unseenEvents.length - 1]?.id;
|
|
1428
1583
|
}
|
|
1429
1584
|
let nextCursor = clampCursorToLatest(
|
|
1430
|
-
|
|
1585
|
+
typeof highestConsumedEventId === "number" ? highestConsumedEventId + 1 : cursorFloor,
|
|
1431
1586
|
buf.nextId
|
|
1432
1587
|
);
|
|
1433
1588
|
const actions = [];
|
|
@@ -1457,6 +1612,7 @@ var SessionManager = class {
|
|
|
1457
1612
|
sessionId,
|
|
1458
1613
|
status: session.status,
|
|
1459
1614
|
pollInterval: pollIntervalForStatus(session.status),
|
|
1615
|
+
progress: buildProgressInfo(session),
|
|
1460
1616
|
events: events.map((event) => serializeEventForMode(event, responseMode)),
|
|
1461
1617
|
nextCursor,
|
|
1462
1618
|
cursorResetTo,
|
|
@@ -1483,6 +1639,10 @@ var SessionManager = class {
|
|
|
1483
1639
|
result.result = void 0;
|
|
1484
1640
|
truncatedFields.push("result");
|
|
1485
1641
|
}
|
|
1642
|
+
if (typeof result.progress !== "undefined" && payloadByteSize(result) > normalizedMaxBytes) {
|
|
1643
|
+
result.progress = void 0;
|
|
1644
|
+
truncatedFields.push("progress");
|
|
1645
|
+
}
|
|
1486
1646
|
if (typeof result.actions !== "undefined" && payloadByteSize(result) > normalizedMaxBytes) {
|
|
1487
1647
|
if (session.status === "waiting_approval") {
|
|
1488
1648
|
result.actions = compactActionsForBudget(result.actions);
|
|
@@ -1510,7 +1670,7 @@ var SessionManager = class {
|
|
|
1510
1670
|
}
|
|
1511
1671
|
}
|
|
1512
1672
|
}
|
|
1513
|
-
if (includeEvents) {
|
|
1673
|
+
if (includeEvents || finalOnly) {
|
|
1514
1674
|
session.lastEventCursor = persistMonotonicCursor(
|
|
1515
1675
|
session.lastEventCursor,
|
|
1516
1676
|
result.nextCursor,
|
|
@@ -1739,6 +1899,7 @@ var SessionManager = class {
|
|
|
1739
1899
|
client.onNotification((method, params) => {
|
|
1740
1900
|
session.lastActiveAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1741
1901
|
const p = params;
|
|
1902
|
+
recordProgressObservation(session, method, p);
|
|
1742
1903
|
switch (method) {
|
|
1743
1904
|
case Methods.THREAD_STARTED: {
|
|
1744
1905
|
const thread = isRecord(p.thread) ? p.thread : void 0;
|
|
@@ -1782,17 +1943,21 @@ var SessionManager = class {
|
|
|
1782
1943
|
if (session.status === "cancelled") break;
|
|
1783
1944
|
const turnObj = p.turn;
|
|
1784
1945
|
const completedTurnId = turnObj?.id ?? session.activeTurnId ?? "";
|
|
1946
|
+
const rawTurnOutput = normalizeOptionalString(turnObj?.output);
|
|
1947
|
+
const finalText = normalizeOptionalString(turnObj?.output) ?? session.lastAgentMessageText;
|
|
1785
1948
|
session.status = "idle";
|
|
1786
1949
|
session.activeTurnId = void 0;
|
|
1787
1950
|
session.lastResult = {
|
|
1788
1951
|
turnId: completedTurnId,
|
|
1789
|
-
|
|
1952
|
+
text: finalText,
|
|
1953
|
+
output: rawTurnOutput,
|
|
1790
1954
|
structuredOutput: turnObj?.structuredOutput,
|
|
1791
1955
|
turn: p.turn,
|
|
1792
1956
|
status: turnObj?.status,
|
|
1793
1957
|
turnError: turnObj?.error,
|
|
1794
1958
|
completedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1795
1959
|
};
|
|
1960
|
+
mergeProgressTokens(session, extractTokens(turnObj?.usage));
|
|
1796
1961
|
pushEvent(
|
|
1797
1962
|
session.eventBuffer,
|
|
1798
1963
|
"result",
|
|
@@ -1847,6 +2012,10 @@ var SessionManager = class {
|
|
|
1847
2012
|
const item = p.item;
|
|
1848
2013
|
const itemType = item && typeof item.type === "string" ? item.type : void 0;
|
|
1849
2014
|
const status = normalizeOptionalString(item?.status);
|
|
2015
|
+
const completedItem = method === Methods.ITEM_COMPLETED || method === Methods.RAW_RESPONSE_ITEM_COMPLETED;
|
|
2016
|
+
if (itemType === "agentMessage" && completedItem && status === "completed" && typeof item?.text === "string") {
|
|
2017
|
+
session.lastAgentMessageText = item.text;
|
|
2018
|
+
}
|
|
1850
2019
|
const eventType = itemType === "agentMessage" || itemType === "userMessage" ? "output" : "progress";
|
|
1851
2020
|
pushEvent(session.eventBuffer, eventType, {
|
|
1852
2021
|
method,
|
|
@@ -1890,6 +2059,7 @@ var SessionManager = class {
|
|
|
1890
2059
|
}
|
|
1891
2060
|
session.lastActiveAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1892
2061
|
const p = params;
|
|
2062
|
+
recordProgressObservation(session, method, p);
|
|
1893
2063
|
switch (method) {
|
|
1894
2064
|
case Methods.COMMAND_APPROVAL: {
|
|
1895
2065
|
const requestId = `req_${randomUUID().slice(0, 8)}`;
|
|
@@ -2176,16 +2346,7 @@ var SessionManager = class {
|
|
|
2176
2346
|
this.ttlWarningEmitted.delete(id);
|
|
2177
2347
|
this.requestCancellation(id, "Running timeout");
|
|
2178
2348
|
} else if ((session.status === "cancelled" || session.status === "error") && age > DEFAULT_TERMINAL_CLEANUP_MS) {
|
|
2179
|
-
this.
|
|
2180
|
-
this.clients.get(id)?.destroy().catch((err) => {
|
|
2181
|
-
console.error(
|
|
2182
|
-
`[codex-mcp] Failed to destroy app-server client during cleanup: session=${id} error=${err instanceof Error ? err.message : String(err)}`
|
|
2183
|
-
);
|
|
2184
|
-
});
|
|
2185
|
-
this.clients.delete(id);
|
|
2186
|
-
this.sessions.delete(id);
|
|
2187
|
-
this.lastPersistedStatus.delete(id);
|
|
2188
|
-
this.ttlWarningEmitted.delete(id);
|
|
2349
|
+
this.evictSession(id, true);
|
|
2189
2350
|
} else {
|
|
2190
2351
|
let ttlMs;
|
|
2191
2352
|
if (session.status === "idle") {
|
|
@@ -2216,6 +2377,34 @@ var SessionManager = class {
|
|
|
2216
2377
|
);
|
|
2217
2378
|
});
|
|
2218
2379
|
}
|
|
2380
|
+
evictSession(sessionId, removeDisk) {
|
|
2381
|
+
const session = this.sessions.get(sessionId);
|
|
2382
|
+
if (!session) return { deleted: false, diskRemoved: false };
|
|
2383
|
+
clearSessionPendingRequests(session);
|
|
2384
|
+
this.notifyWaiters(sessionId);
|
|
2385
|
+
this.clients.get(sessionId)?.destroy().catch((err) => {
|
|
2386
|
+
console.error(
|
|
2387
|
+
`[codex-mcp] Failed to destroy app-server client during cleanup: session=${sessionId} error=${err instanceof Error ? err.message : String(err)}`
|
|
2388
|
+
);
|
|
2389
|
+
});
|
|
2390
|
+
this.clients.delete(sessionId);
|
|
2391
|
+
const deleted = this.sessions.delete(sessionId);
|
|
2392
|
+
this.lastPersistedStatus.delete(sessionId);
|
|
2393
|
+
this.ttlWarningEmitted.delete(sessionId);
|
|
2394
|
+
this.sessionNotifiers.delete(sessionId);
|
|
2395
|
+
this.cancellationInFlight.delete(sessionId);
|
|
2396
|
+
let diskRemoved = false;
|
|
2397
|
+
if (removeDisk) {
|
|
2398
|
+
try {
|
|
2399
|
+
if (this.persistence) {
|
|
2400
|
+
this.persistence.removeSession(sessionId);
|
|
2401
|
+
diskRemoved = true;
|
|
2402
|
+
}
|
|
2403
|
+
} catch {
|
|
2404
|
+
}
|
|
2405
|
+
}
|
|
2406
|
+
return { deleted, diskRemoved };
|
|
2407
|
+
}
|
|
2219
2408
|
};
|
|
2220
2409
|
function pollIntervalForStatus(status) {
|
|
2221
2410
|
if (status === "waiting_approval") return WAITING_APPROVAL_POLL_INTERVAL;
|
|
@@ -2230,6 +2419,114 @@ function createEventBuffer() {
|
|
|
2230
2419
|
nextId: 0
|
|
2231
2420
|
};
|
|
2232
2421
|
}
|
|
2422
|
+
function buildProgressInfo(session) {
|
|
2423
|
+
const storedTokens = session.progressState?.tokens;
|
|
2424
|
+
const resultTokens = extractTokens(session.lastResult?.turn);
|
|
2425
|
+
return {
|
|
2426
|
+
phase: deriveProgressPhase(session),
|
|
2427
|
+
lastEventAt: session.progressState?.lastEventAt ?? session.lastActiveAt,
|
|
2428
|
+
activeTurnId: session.activeTurnId,
|
|
2429
|
+
pendingActionCount: countPendingRequests(session),
|
|
2430
|
+
lastMethod: session.progressState?.lastMethod,
|
|
2431
|
+
percent: session.progressState?.percent,
|
|
2432
|
+
tokens: mergeTokens(storedTokens, resultTokens)
|
|
2433
|
+
};
|
|
2434
|
+
}
|
|
2435
|
+
function countPendingRequests(session) {
|
|
2436
|
+
let count = 0;
|
|
2437
|
+
for (const req of session.pendingRequests.values()) {
|
|
2438
|
+
if (!req.resolved) count++;
|
|
2439
|
+
}
|
|
2440
|
+
return count;
|
|
2441
|
+
}
|
|
2442
|
+
function deriveProgressPhase(session) {
|
|
2443
|
+
if (session.status === "waiting_approval") return "waiting_approval";
|
|
2444
|
+
if (session.status === "cancelled") return "cancelled";
|
|
2445
|
+
if (session.status === "error") return "error";
|
|
2446
|
+
if (session.status === "idle") return "finished";
|
|
2447
|
+
if (!session.activeTurnId) return "starting";
|
|
2448
|
+
const lastMethod = session.progressState?.lastMethod;
|
|
2449
|
+
if (typeof lastMethod === "string") {
|
|
2450
|
+
if (REASONING_PROGRESS_METHODS.has(lastMethod)) return "reasoning";
|
|
2451
|
+
if (ACTING_PROGRESS_METHODS.has(lastMethod)) return "acting";
|
|
2452
|
+
}
|
|
2453
|
+
return "running";
|
|
2454
|
+
}
|
|
2455
|
+
function recordProgressObservation(session, method, params) {
|
|
2456
|
+
const next = session.progressState ?? { lastEventAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
2457
|
+
next.lastEventAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2458
|
+
if (method !== Methods.THREAD_TOKEN_USAGE_UPDATED) {
|
|
2459
|
+
next.lastMethod = method;
|
|
2460
|
+
}
|
|
2461
|
+
const percent = extractPercent(params);
|
|
2462
|
+
if (typeof percent === "number") next.percent = percent;
|
|
2463
|
+
mergeProgressTokens(session, extractTokens(params));
|
|
2464
|
+
session.progressState = next;
|
|
2465
|
+
}
|
|
2466
|
+
function mergeProgressTokens(session, tokens) {
|
|
2467
|
+
if (!tokens) return;
|
|
2468
|
+
const next = session.progressState ?? { lastEventAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
2469
|
+
next.tokens = mergeTokens(next.tokens, tokens);
|
|
2470
|
+
session.progressState = next;
|
|
2471
|
+
}
|
|
2472
|
+
function mergeTokens(base, extra) {
|
|
2473
|
+
if (!base && !extra) return void 0;
|
|
2474
|
+
return {
|
|
2475
|
+
input: extra?.input ?? base?.input,
|
|
2476
|
+
output: extra?.output ?? base?.output,
|
|
2477
|
+
total: extra?.total ?? base?.total
|
|
2478
|
+
};
|
|
2479
|
+
}
|
|
2480
|
+
function extractPercent(value) {
|
|
2481
|
+
if (!isRecord(value)) return void 0;
|
|
2482
|
+
const candidates = [value.percent, value.percentage, value.progress, value.fractionComplete];
|
|
2483
|
+
for (const candidate of candidates) {
|
|
2484
|
+
if (typeof candidate !== "number" || !Number.isFinite(candidate)) continue;
|
|
2485
|
+
if (candidate >= 0 && candidate <= 1) return Math.round(candidate * 100);
|
|
2486
|
+
if (candidate >= 0 && candidate <= 100) return candidate;
|
|
2487
|
+
}
|
|
2488
|
+
return void 0;
|
|
2489
|
+
}
|
|
2490
|
+
function extractTokens(value) {
|
|
2491
|
+
if (!isRecord(value)) return void 0;
|
|
2492
|
+
const usage = isRecord(value.usage) ? value.usage : void 0;
|
|
2493
|
+
const source = usage ?? value;
|
|
2494
|
+
const input = pickNumber(source, [
|
|
2495
|
+
"inputTokens",
|
|
2496
|
+
"input_tokens",
|
|
2497
|
+
"promptTokens",
|
|
2498
|
+
"prompt_tokens"
|
|
2499
|
+
]);
|
|
2500
|
+
const output = pickNumber(source, [
|
|
2501
|
+
"outputTokens",
|
|
2502
|
+
"output_tokens",
|
|
2503
|
+
"completionTokens",
|
|
2504
|
+
"completion_tokens"
|
|
2505
|
+
]);
|
|
2506
|
+
const total = pickNumber(source, [
|
|
2507
|
+
"totalTokens",
|
|
2508
|
+
"total_tokens",
|
|
2509
|
+
"tokenCount",
|
|
2510
|
+
"token_count",
|
|
2511
|
+
"total"
|
|
2512
|
+
]);
|
|
2513
|
+
if (typeof input !== "number" && typeof output !== "number" && typeof total !== "number") {
|
|
2514
|
+
return void 0;
|
|
2515
|
+
}
|
|
2516
|
+
return { input, output, total };
|
|
2517
|
+
}
|
|
2518
|
+
function pickNumber(source, keys) {
|
|
2519
|
+
for (const key of keys) {
|
|
2520
|
+
const value = source[key];
|
|
2521
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
2522
|
+
}
|
|
2523
|
+
return void 0;
|
|
2524
|
+
}
|
|
2525
|
+
function isSkippableDeltaEvent(event) {
|
|
2526
|
+
if (!isRecord(event.data)) return false;
|
|
2527
|
+
const method = event.data.method;
|
|
2528
|
+
return typeof method === "string" && SKIPPABLE_DELTA_METHODS.has(method);
|
|
2529
|
+
}
|
|
2233
2530
|
function clearTerminalEvents(buf) {
|
|
2234
2531
|
buf.events = buf.events.filter((e) => e.type !== "result" && e.type !== "error");
|
|
2235
2532
|
}
|
|
@@ -2719,6 +3016,20 @@ function buildExecutionInfo(waitForResultMs, status, fallbackReason) {
|
|
|
2719
3016
|
fallbackReason: effective === "background" ? fallbackReason : void 0
|
|
2720
3017
|
};
|
|
2721
3018
|
}
|
|
3019
|
+
function coerceProgressForStatus(status, progress, options) {
|
|
3020
|
+
if (!progress) return void 0;
|
|
3021
|
+
let phase = progress.phase;
|
|
3022
|
+
if (status === "idle") phase = "finished";
|
|
3023
|
+
else if (status === "error") phase = "error";
|
|
3024
|
+
else if (status === "cancelled") phase = "cancelled";
|
|
3025
|
+
else if (status === "waiting_approval") phase = "waiting_approval";
|
|
3026
|
+
return {
|
|
3027
|
+
...progress,
|
|
3028
|
+
phase,
|
|
3029
|
+
lastEventAt: options?.completedAt ?? progress.lastEventAt,
|
|
3030
|
+
pendingActionCount: status === "waiting_approval" ? Math.max(progress.pendingActionCount, options?.pendingActionCount ?? 0) : status === "running" ? progress.pendingActionCount : 0
|
|
3031
|
+
};
|
|
3032
|
+
}
|
|
2722
3033
|
async function waitForCodexSessionForegroundResult(sessionManager, sessionId, waitForResultMs, signal) {
|
|
2723
3034
|
const deadline = Date.now() + Math.min(waitForResultMs, 3e5);
|
|
2724
3035
|
while (Date.now() < deadline) {
|
|
@@ -2761,6 +3072,9 @@ async function waitForCodexSessionForegroundResult(sessionManager, sessionId, wa
|
|
|
2761
3072
|
}
|
|
2762
3073
|
|
|
2763
3074
|
// src/tools/codex.ts
|
|
3075
|
+
function safeGetProgress(sessionManager, sessionId) {
|
|
3076
|
+
return typeof sessionManager.getProgress === "function" ? sessionManager.getProgress(sessionId) : void 0;
|
|
3077
|
+
}
|
|
2764
3078
|
async function executeCodex(args, sessionManager, serverCwd, requestSignal) {
|
|
2765
3079
|
const cwd = resolveAndValidateCwd(args.cwd, serverCwd);
|
|
2766
3080
|
const spawnOpts = extractSpawnOptions(args);
|
|
@@ -2775,6 +3089,10 @@ async function executeCodex(args, sessionManager, serverCwd, requestSignal) {
|
|
|
2775
3089
|
const waitMs = args.advanced?.waitForResult;
|
|
2776
3090
|
const baseResult = {
|
|
2777
3091
|
...startResult,
|
|
3092
|
+
progress: coerceProgressForStatus(
|
|
3093
|
+
"running",
|
|
3094
|
+
safeGetProgress(sessionManager, startResult.sessionId) ?? startResult.progress
|
|
3095
|
+
),
|
|
2778
3096
|
execution: buildExecutionInfo(waitMs, "running"),
|
|
2779
3097
|
interactionState: interactionStateForStatus("running"),
|
|
2780
3098
|
recommendedNextAction: recommendedNextActionForStatus("running")
|
|
@@ -2792,6 +3110,15 @@ async function executeCodex(args, sessionManager, serverCwd, requestSignal) {
|
|
|
2792
3110
|
result: foreground.result,
|
|
2793
3111
|
status: foreground.status,
|
|
2794
3112
|
completedAt: foreground.completedAt,
|
|
3113
|
+
compatWarnings: startResult.compatWarnings,
|
|
3114
|
+
progress: coerceProgressForStatus(
|
|
3115
|
+
foreground.status,
|
|
3116
|
+
safeGetProgress(sessionManager, startResult.sessionId) ?? startResult.progress,
|
|
3117
|
+
{
|
|
3118
|
+
completedAt: foreground.completedAt,
|
|
3119
|
+
pendingActionCount: foreground.pendingActionTypes?.length ?? 0
|
|
3120
|
+
}
|
|
3121
|
+
),
|
|
2795
3122
|
pollInterval: foreground.status === "waiting_approval" ? WAITING_APPROVAL_POLL_INTERVAL : foreground.status === "running" ? startResult.pollInterval : void 0,
|
|
2796
3123
|
execution: buildExecutionInfo(waitMs, foreground.status, foreground.fallbackReason),
|
|
2797
3124
|
interactionState: interactionStateForStatus(foreground.status),
|
|
@@ -2803,6 +3130,9 @@ async function executeCodex(args, sessionManager, serverCwd, requestSignal) {
|
|
|
2803
3130
|
}
|
|
2804
3131
|
|
|
2805
3132
|
// src/tools/codex-reply.ts
|
|
3133
|
+
function safeGetProgress2(sessionManager, sessionId) {
|
|
3134
|
+
return typeof sessionManager.getProgress === "function" ? sessionManager.getProgress(sessionId) : void 0;
|
|
3135
|
+
}
|
|
2806
3136
|
async function executeCodexReply(args, sessionManager, requestSignal) {
|
|
2807
3137
|
const startResult = await sessionManager.replyToSession(args.sessionId, args.prompt, {
|
|
2808
3138
|
model: args.model,
|
|
@@ -2817,6 +3147,10 @@ async function executeCodexReply(args, sessionManager, requestSignal) {
|
|
|
2817
3147
|
const waitMs = args.waitForResult;
|
|
2818
3148
|
const baseResult = {
|
|
2819
3149
|
...startResult,
|
|
3150
|
+
progress: coerceProgressForStatus(
|
|
3151
|
+
"running",
|
|
3152
|
+
safeGetProgress2(sessionManager, startResult.sessionId) ?? startResult.progress
|
|
3153
|
+
),
|
|
2820
3154
|
execution: buildExecutionInfo(waitMs, "running"),
|
|
2821
3155
|
interactionState: interactionStateForStatus("running"),
|
|
2822
3156
|
recommendedNextAction: recommendedNextActionForStatus("running")
|
|
@@ -2834,6 +3168,15 @@ async function executeCodexReply(args, sessionManager, requestSignal) {
|
|
|
2834
3168
|
sessionId: startResult.sessionId,
|
|
2835
3169
|
threadId: startResult.threadId,
|
|
2836
3170
|
status: foreground.status,
|
|
3171
|
+
compatWarnings: startResult.compatWarnings,
|
|
3172
|
+
progress: coerceProgressForStatus(
|
|
3173
|
+
foreground.status,
|
|
3174
|
+
safeGetProgress2(sessionManager, startResult.sessionId) ?? startResult.progress,
|
|
3175
|
+
{
|
|
3176
|
+
completedAt: foreground.completedAt,
|
|
3177
|
+
pendingActionCount: foreground.pendingActionTypes?.length ?? 0
|
|
3178
|
+
}
|
|
3179
|
+
),
|
|
2837
3180
|
pollInterval: foreground.status === "waiting_approval" ? WAITING_APPROVAL_POLL_INTERVAL : foreground.status === "running" ? startResult.pollInterval : void 0,
|
|
2838
3181
|
result: foreground.result,
|
|
2839
3182
|
completedAt: foreground.completedAt,
|
|
@@ -2885,6 +3228,13 @@ async function executeCodexSession(args, sessionManager) {
|
|
|
2885
3228
|
};
|
|
2886
3229
|
}
|
|
2887
3230
|
return await sessionManager.forkSession(args.sessionId);
|
|
3231
|
+
case "clean":
|
|
3232
|
+
return await sessionManager.cleanSessions({
|
|
3233
|
+
statuses: args.statuses,
|
|
3234
|
+
olderThanMs: args.olderThanMs,
|
|
3235
|
+
dryRun: args.dryRun,
|
|
3236
|
+
includeDisk: args.includeDisk
|
|
3237
|
+
});
|
|
2888
3238
|
case "clean_background_terminals":
|
|
2889
3239
|
if (!args.sessionId) {
|
|
2890
3240
|
return {
|
|
@@ -2920,24 +3270,15 @@ function executeCodexCheck(args, sessionManager, requestSignal) {
|
|
|
2920
3270
|
const maxEvents = typeof args.maxEvents === "number" ? Math.max(POLL_MIN_MAX_EVENTS, Math.floor(args.maxEvents)) : POLL_DEFAULT_MAX_EVENTS;
|
|
2921
3271
|
const waitMs = pollOptions?.waitMs;
|
|
2922
3272
|
if (typeof waitMs === "number" && waitMs > 0) {
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
sessionManager,
|
|
2933
|
-
sessionManager.pollEvents(args.sessionId, args.cursor, maxEvents, {
|
|
2934
|
-
responseMode,
|
|
2935
|
-
pollOptions
|
|
2936
|
-
})
|
|
2937
|
-
)
|
|
2938
|
-
);
|
|
2939
|
-
}
|
|
2940
|
-
return enrichCheckResult(sessionManager, firstCheck);
|
|
3273
|
+
return pollWithWait(
|
|
3274
|
+
sessionManager,
|
|
3275
|
+
args.sessionId,
|
|
3276
|
+
args.cursor,
|
|
3277
|
+
maxEvents,
|
|
3278
|
+
{ responseMode, pollOptions },
|
|
3279
|
+
Math.min(waitMs, 12e4),
|
|
3280
|
+
requestSignal
|
|
3281
|
+
).then((result) => enrichCheckResult(sessionManager, result));
|
|
2941
3282
|
}
|
|
2942
3283
|
return enrichCheckResult(
|
|
2943
3284
|
sessionManager,
|
|
@@ -3058,6 +3399,31 @@ function executeCodexCheck(args, sessionManager, requestSignal) {
|
|
|
3058
3399
|
};
|
|
3059
3400
|
}
|
|
3060
3401
|
}
|
|
3402
|
+
function hasVisibleData(result) {
|
|
3403
|
+
return result.events.length > 0 || result.actions !== void 0 && result.actions.length > 0 || result.result !== void 0;
|
|
3404
|
+
}
|
|
3405
|
+
async function pollWithWait(sessionManager, sessionId, cursor, maxEvents, options, waitMs, signal) {
|
|
3406
|
+
const deadline = Date.now() + waitMs;
|
|
3407
|
+
let currentCursor = cursor;
|
|
3408
|
+
while (true) {
|
|
3409
|
+
const result = sessionManager.pollEvents(sessionId, currentCursor, maxEvents, options);
|
|
3410
|
+
if (hasVisibleData(result)) {
|
|
3411
|
+
return result;
|
|
3412
|
+
}
|
|
3413
|
+
if (signal?.aborted) {
|
|
3414
|
+
return result;
|
|
3415
|
+
}
|
|
3416
|
+
const remainingMs = deadline - Date.now();
|
|
3417
|
+
if (remainingMs <= 0) {
|
|
3418
|
+
return result;
|
|
3419
|
+
}
|
|
3420
|
+
currentCursor = result.nextCursor;
|
|
3421
|
+
await sessionManager.waitForChange(sessionId, remainingMs, signal).catch(() => void 0);
|
|
3422
|
+
if (signal?.aborted) {
|
|
3423
|
+
return sessionManager.pollEvents(sessionId, currentCursor, maxEvents, options);
|
|
3424
|
+
}
|
|
3425
|
+
}
|
|
3426
|
+
}
|
|
3061
3427
|
function enrichCheckResult(sessionManager, result) {
|
|
3062
3428
|
const actionTypes = result.status === "waiting_approval" ? sessionManager.getPendingActionTypes(result.sessionId) : [];
|
|
3063
3429
|
return {
|
|
@@ -3541,8 +3907,11 @@ function buildConfigGuideText() {
|
|
|
3541
3907
|
"- `codex_check.pollOptions.includeEvents`: default `true`.",
|
|
3542
3908
|
"- `codex_check.pollOptions.includeActions`: default `true`.",
|
|
3543
3909
|
"- `codex_check.pollOptions.includeResult`: default `true`.",
|
|
3910
|
+
"- `codex_check.pollOptions.skipDeltas`: default `false`.",
|
|
3911
|
+
"- `codex_check.pollOptions.finalOnly`: default `false`.",
|
|
3544
3912
|
"- `codex_check.pollOptions.maxBytes`: default unlimited.",
|
|
3545
3913
|
"- `codex_check.cursor`: default is session last consumed cursor when omitted.",
|
|
3914
|
+
"- `progress` is included on `codex`, `codex_reply`, and `codex_check` responses.",
|
|
3546
3915
|
""
|
|
3547
3916
|
].join("\n");
|
|
3548
3917
|
}
|
|
@@ -3556,6 +3925,8 @@ function buildGotchasText() {
|
|
|
3556
3925
|
`- Poll enforces minimum \`maxEvents=${POLL_MIN_MAX_EVENTS}\`; sending \`0\` is normalized to \`${POLL_MIN_MAX_EVENTS}\`.`,
|
|
3557
3926
|
`- \`respond_permission\` and \`respond_user_input\` default to compact ACK with \`maxEvents=${RESPOND_DEFAULT_MAX_EVENTS}\`.`,
|
|
3558
3927
|
"- Default response mode is `minimal`; use `full` if you need full raw event payloads.",
|
|
3928
|
+
"- Use `pollOptions.skipDeltas=true` to suppress delta-heavy stream chunks while still advancing the cursor.",
|
|
3929
|
+
"- Use `pollOptions.finalOnly=true` when you only care about actions + terminal result; it also advances the cursor past hidden events.",
|
|
3559
3930
|
"- respond_* uses monotonic cursor handling: `max(cursor, sessionLastCursor)`.",
|
|
3560
3931
|
"- If `cursorResetTo` is present, your cursor is stale (old events were evicted); restart from that value.",
|
|
3561
3932
|
"- **Poll frequency guidance**: Adapt poll interval to task complexity and previous poll results. For `running` sessions, start at 2 minutes and increase for long tasks. Only poll frequently (~1s) when `waiting_approval`. Do NOT high-frequency poll \u2014 it wastes tokens and provides no benefit.",
|
|
@@ -3570,6 +3941,8 @@ function buildGotchasText() {
|
|
|
3570
3941
|
"## Event model",
|
|
3571
3942
|
"",
|
|
3572
3943
|
"- Top-level `events[].type` is one of: `output`, `progress`, `approval_request`, `approval_result`, `result`, `error`.",
|
|
3944
|
+
"- Terminal `result.text` provides a stable final assistant message, even when the backend omits `turn.output`; `result.output` remains the raw backend field.",
|
|
3945
|
+
"- `progress` normalizes the current phase, pending action count, last observed method, and token totals when available.",
|
|
3573
3946
|
"- Fine-grained stream semantics are in `events[].data.method` (for example command output delta, reasoning delta, turn updates).",
|
|
3574
3947
|
'- Retryable interruptions surface as `progress` with `method="codex-mcp/reconnect"` and include retry fields.',
|
|
3575
3948
|
"- During reconnect/retry, continue polling normally; if retries stop (`willRetry=false`), session transitions to error path.",
|
|
@@ -3586,7 +3959,8 @@ function buildGotchasText() {
|
|
|
3586
3959
|
`- Idle sessions are auto-cleaned after ${msToMinutes(DEFAULT_IDLE_CLEANUP_MS)} minutes.`,
|
|
3587
3960
|
`- Running/waiting sessions are auto-cleaned after ${msToMinutes(DEFAULT_RUNNING_CLEANUP_MS)} minutes.`,
|
|
3588
3961
|
`- Error/cancelled sessions are retained for about ${msToMinutes(DEFAULT_TERMINAL_CLEANUP_MS)} minutes, then removed.`,
|
|
3589
|
-
|
|
3962
|
+
'- Use `codex_session(action="clean")` to batch-remove idle/error/cancelled sessions on demand.',
|
|
3963
|
+
"- Session metadata/results are persisted for recovery; manual clean can also delete those disk artifacts.",
|
|
3590
3964
|
"",
|
|
3591
3965
|
"## Capacity",
|
|
3592
3966
|
"",
|
|
@@ -3673,6 +4047,7 @@ function buildQuickstartText() {
|
|
|
3673
4047
|
"```",
|
|
3674
4048
|
"",
|
|
3675
4049
|
"5. Continue polling until terminal status (`idle`, `error`, or `cancelled`), respecting the >=2 minute interval while `running`.",
|
|
4050
|
+
"6. Read `progress.phase` / `progress.tokens` for a coarse execution snapshot without parsing raw delta events.",
|
|
3676
4051
|
"",
|
|
3677
4052
|
"## Cursor notes",
|
|
3678
4053
|
"",
|
|
@@ -3681,6 +4056,13 @@ function buildQuickstartText() {
|
|
|
3681
4056
|
"- Omit `responseMode`: default is `minimal`.",
|
|
3682
4057
|
"- Use returned `nextCursor` for the next call.",
|
|
3683
4058
|
"- If `cursorResetTo` appears, reset to that value and continue.",
|
|
4059
|
+
"- If you need schema-constrained results, pass `advanced.outputSchema` (or top-level `outputSchema` in `codex_reply`) and read terminal `result.structuredOutput`.",
|
|
4060
|
+
"",
|
|
4061
|
+
"## Read next",
|
|
4062
|
+
"",
|
|
4063
|
+
"- `codex-mcp:///config`: parameter-by-parameter guide, including `advanced.*` mapping and reply overrides.",
|
|
4064
|
+
"- `codex-mcp:///delegation-guide`: task presets for approvalPolicy/sandbox selection.",
|
|
4065
|
+
"- `codex-mcp:///gotchas`: polling, approval timeout, cursor, and exec-mode failure modes.",
|
|
3684
4066
|
""
|
|
3685
4067
|
].join("\n");
|
|
3686
4068
|
}
|
|
@@ -3729,6 +4111,13 @@ function buildDelegationGuideText() {
|
|
|
3729
4111
|
"",
|
|
3730
4112
|
"**Key rule:** `read-only` sandbox already prevents writes, so `approvalPolicy: 'never'` is safe with it. Avoid `untrusted` + `read-only` \u2014 every read command triggers approval for no safety gain.",
|
|
3731
4113
|
"",
|
|
4114
|
+
"## Approval policy quick guide",
|
|
4115
|
+
"- `never`: no interactive prompts. Best for read-only review or tightly scoped trusted tasks.",
|
|
4116
|
+
"- `on-failure`: pragmatic default for implementation work when you still want some safety rails.",
|
|
4117
|
+
"- `on-request`: use when a human or outer agent will actively poll and answer approvals.",
|
|
4118
|
+
"- `untrusted`: strictest interactive mode; expect frequent prompts and higher timeout sensitivity.",
|
|
4119
|
+
`- Default approval timeout is ${DEFAULT_APPROVAL_TIMEOUT_MS}ms. If interactive approvals are possible, raise \`advanced.approvalTimeoutMs\` to at least 300000 so requests do not expire between normal running-session polls.`,
|
|
4120
|
+
"",
|
|
3732
4121
|
"## Quick mode: `waitForResult`",
|
|
3733
4122
|
"For short tasks (< 2 min), set `advanced.waitForResult` to get the final result in a single tool call:",
|
|
3734
4123
|
"```json",
|
|
@@ -3756,6 +4145,11 @@ function buildDelegationGuideText() {
|
|
|
3756
4145
|
"",
|
|
3757
4146
|
"**Approval timeout:** Default is 60s; infrequent polling causes silent auto-decline. See `codex-mcp:///gotchas`.",
|
|
3758
4147
|
"",
|
|
4148
|
+
"## Read next",
|
|
4149
|
+
"- `codex-mcp:///quickstart` for the exact start -> poll -> respond loop",
|
|
4150
|
+
"- `codex-mcp:///config` for parameter mapping and override persistence",
|
|
4151
|
+
"- `codex-mcp:///gotchas` for timeout, cursor, and exec-mode caveats",
|
|
4152
|
+
"",
|
|
3759
4153
|
"## Security notes",
|
|
3760
4154
|
"- `sandbox: 'read-only'` is the strongest isolation \u2014 blocks all writes regardless of approval policy",
|
|
3761
4155
|
"- `approvalPolicy: 'never'` + `sandbox: 'workspace-write'` gives the agent full write access with no human oversight \u2014 use only for well-defined, low-risk tasks",
|
|
@@ -3943,9 +4337,13 @@ function registerResources(server, deps) {
|
|
|
3943
4337
|
}
|
|
3944
4338
|
|
|
3945
4339
|
// src/server.ts
|
|
3946
|
-
var SERVER_VERSION = true ? "2.1.
|
|
4340
|
+
var SERVER_VERSION = true ? "2.1.7" : "0.0.0-dev";
|
|
3947
4341
|
function formatErrorMessage(err) {
|
|
3948
4342
|
const message = err instanceof Error ? err.message : String(err);
|
|
4343
|
+
const compatibilityKind = classifyTurnCompatibilityError(err);
|
|
4344
|
+
if (compatibilityKind) {
|
|
4345
|
+
return compatibilityErrorMessage(compatibilityKind);
|
|
4346
|
+
}
|
|
3949
4347
|
const m = /^Error \[([A-Z_]+)\]:\s*(.*)$/.exec(message);
|
|
3950
4348
|
if (m) {
|
|
3951
4349
|
const [, code, rest] = m;
|
|
@@ -3997,6 +4395,28 @@ function createServer(serverCwd, options) {
|
|
|
3997
4395
|
});
|
|
3998
4396
|
const interactionStateSchema = z.enum(["working", "waiting_input", "finished"]);
|
|
3999
4397
|
const nextActionSchema = z.enum(["poll", "respond_permission", "respond_user_input", "none"]);
|
|
4398
|
+
const progressSchema = z.object({
|
|
4399
|
+
phase: z.enum([
|
|
4400
|
+
"starting",
|
|
4401
|
+
"running",
|
|
4402
|
+
"reasoning",
|
|
4403
|
+
"acting",
|
|
4404
|
+
"waiting_approval",
|
|
4405
|
+
"finished",
|
|
4406
|
+
"error",
|
|
4407
|
+
"cancelled"
|
|
4408
|
+
]),
|
|
4409
|
+
lastEventAt: z.string(),
|
|
4410
|
+
activeTurnId: z.string().optional(),
|
|
4411
|
+
pendingActionCount: z.number().int(),
|
|
4412
|
+
lastMethod: z.string().optional(),
|
|
4413
|
+
percent: z.number().optional(),
|
|
4414
|
+
tokens: z.object({
|
|
4415
|
+
input: z.number().optional(),
|
|
4416
|
+
output: z.number().optional(),
|
|
4417
|
+
total: z.number().optional()
|
|
4418
|
+
}).optional()
|
|
4419
|
+
});
|
|
4000
4420
|
const setupResultShape = {
|
|
4001
4421
|
ready: z.boolean(),
|
|
4002
4422
|
cwd: z.string(),
|
|
@@ -4033,6 +4453,8 @@ function createServer(serverCwd, options) {
|
|
|
4033
4453
|
),
|
|
4034
4454
|
result: z.unknown().optional().describe("Final result when waitForResult is set and session completed."),
|
|
4035
4455
|
completedAt: z.string().optional().describe("ISO timestamp when the session completed (only when waitForResult succeeded)."),
|
|
4456
|
+
compatWarnings: z.array(z.string()).optional(),
|
|
4457
|
+
progress: progressSchema.optional(),
|
|
4036
4458
|
execution: executionInfoSchema.optional(),
|
|
4037
4459
|
interactionState: interactionStateSchema.optional(),
|
|
4038
4460
|
recommendedNextAction: nextActionSchema.optional(),
|
|
@@ -4042,6 +4464,10 @@ function createServer(serverCwd, options) {
|
|
|
4042
4464
|
includeEvents: z.boolean().optional().describe("Default: true. Include events[] in response."),
|
|
4043
4465
|
includeActions: z.boolean().optional().describe("Default: true. Include actions[] in response."),
|
|
4044
4466
|
includeResult: z.boolean().optional().describe("Default: true. Include result in response."),
|
|
4467
|
+
skipDeltas: z.boolean().optional().describe(
|
|
4468
|
+
"Default: false. Drop delta-heavy streaming events while still advancing the cursor."
|
|
4469
|
+
),
|
|
4470
|
+
finalOnly: z.boolean().optional().describe("Default: false. Omit events and focus on actions + terminal result."),
|
|
4045
4471
|
maxBytes: z.number().int().positive().optional().describe("Default: unlimited. Best-effort response payload cap in bytes."),
|
|
4046
4472
|
waitMs: z.number().int().nonnegative().optional().describe(
|
|
4047
4473
|
"Long-poll: block up to this many ms for new events (max 120000). Omit or 0 for immediate return."
|
|
@@ -4187,7 +4613,7 @@ function createServer(serverCwd, options) {
|
|
|
4187
4613
|
"codex",
|
|
4188
4614
|
{
|
|
4189
4615
|
title: "Start Codex Session",
|
|
4190
|
-
description: "Start session asynchronously and return `{ sessionId, threadId, status, pollInterval }`.
|
|
4616
|
+
description: "Start a Codex session asynchronously and return `{ sessionId, threadId, status, pollInterval }`. Poll with `codex_check(action='poll')` until terminal status, and treat `pollInterval` as a minimum hint: `running` >=120000ms, `waiting_approval` ~=1000ms. See `codex-mcp:///quickstart` for the main loop, `codex-mcp:///config` for parameter guidance, and `codex-mcp:///delegation-guide` for approval/sandbox presets.",
|
|
4191
4617
|
inputSchema: {
|
|
4192
4618
|
prompt: z.string().describe("Task or question"),
|
|
4193
4619
|
approvalPolicy: z.enum(APPROVAL_POLICIES).describe("Required enum: untrusted/on-failure/on-request/never."),
|
|
@@ -4315,18 +4741,23 @@ function createServer(serverCwd, options) {
|
|
|
4315
4741
|
"codex_session",
|
|
4316
4742
|
{
|
|
4317
4743
|
title: "Manage Sessions",
|
|
4318
|
-
description: `Session actions: list, get, cancel, interrupt, fork, clean_background_terminals.
|
|
4744
|
+
description: `Session actions: list, get, cancel, interrupt, fork, clean, clean_background_terminals.
|
|
4319
4745
|
|
|
4320
4746
|
- list: sessions in memory.
|
|
4321
4747
|
- get: details. includeSensitive defaults to false; true adds threadId/cwd/profile/config.
|
|
4322
4748
|
- cancel: terminal.
|
|
4323
4749
|
- interrupt: stop current turn.
|
|
4324
4750
|
- fork: clone current thread into a new session; source remains unchanged.
|
|
4751
|
+
- clean: batch-remove idle/error/cancelled sessions, optionally from disk too.
|
|
4325
4752
|
- clean_background_terminals: ask app-server to clean stale background terminals for this thread.`,
|
|
4326
4753
|
inputSchema: {
|
|
4327
4754
|
action: z.enum(SESSION_ACTIONS),
|
|
4328
4755
|
sessionId: z.string().optional().describe("Required for get/cancel/interrupt/fork/clean_background_terminals"),
|
|
4329
|
-
includeSensitive: z.boolean().default(false).optional().describe("Include cwd/config/threadId/profile in get (default: false)")
|
|
4756
|
+
includeSensitive: z.boolean().default(false).optional().describe("Include cwd/config/threadId/profile in get (default: false)"),
|
|
4757
|
+
statuses: z.array(z.enum(["idle", "error", "cancelled"])).optional().describe("For clean only. Default: idle/error/cancelled."),
|
|
4758
|
+
olderThanMs: z.number().int().nonnegative().optional().describe("For clean only. Remove sessions idle for at least this many ms."),
|
|
4759
|
+
dryRun: z.boolean().optional().describe("For clean only. Preview matched sessions."),
|
|
4760
|
+
includeDisk: z.boolean().optional().describe("For clean only. Default: true. Also remove persisted session state.")
|
|
4330
4761
|
},
|
|
4331
4762
|
outputSchema: {
|
|
4332
4763
|
sessions: z.array(publicSessionInfoSchema).optional(),
|
|
@@ -4347,6 +4778,11 @@ function createServer(serverCwd, options) {
|
|
|
4347
4778
|
pollInterval: z.number().int().optional().describe(
|
|
4348
4779
|
"Recommended minimum delay before next poll (ms): running >=120000, waiting_approval ~=1000."
|
|
4349
4780
|
),
|
|
4781
|
+
matchedSessionIds: z.array(z.string()).optional(),
|
|
4782
|
+
removedSessionIds: z.array(z.string()).optional(),
|
|
4783
|
+
removedCount: z.number().int().optional(),
|
|
4784
|
+
diskSessionsRemoved: z.number().int().optional(),
|
|
4785
|
+
dryRun: z.boolean().optional(),
|
|
4350
4786
|
success: z.boolean().optional(),
|
|
4351
4787
|
message: z.string().optional(),
|
|
4352
4788
|
...errorOutputShape
|
|
@@ -4382,7 +4818,7 @@ function createServer(serverCwd, options) {
|
|
|
4382
4818
|
"codex_check",
|
|
4383
4819
|
{
|
|
4384
4820
|
title: "Poll & Respond",
|
|
4385
|
-
description: `Poll session for events or respond to approval/input requests. Use pollInterval as a minimum hint; stop polling on terminal status (idle/error/cancelled). See codex-mcp:///
|
|
4821
|
+
description: `Poll session for events or respond to approval/input requests. Use pollInterval as a minimum hint; stop polling on terminal status (idle/error/cancelled). WARNING: running sessions usually poll at >=120000ms, but approvalTimeoutMs defaults to ${DEFAULT_APPROVAL_TIMEOUT_MS}ms, so approvals can expire between polls unless you raise the timeout or use non-interactive policies. See codex-mcp:///quickstart and codex-mcp:///gotchas.
|
|
4386
4822
|
|
|
4387
4823
|
poll: events since cursor. Default maxEvents=${POLL_DEFAULT_MAX_EVENTS}.
|
|
4388
4824
|
respond_permission: approval decision. Default maxEvents=${RESPOND_DEFAULT_MAX_EVENTS} (compact ACK).
|
|
@@ -4394,6 +4830,7 @@ respond_user_input: user-input answers. Default maxEvents=${RESPOND_DEFAULT_MAX_
|
|
|
4394
4830
|
pollInterval: z.number().int().optional().describe(
|
|
4395
4831
|
"Recommended minimum delay before next poll (ms): running >=120000, waiting_approval ~=1000."
|
|
4396
4832
|
),
|
|
4833
|
+
progress: progressSchema.optional(),
|
|
4397
4834
|
interactionState: interactionStateSchema.optional(),
|
|
4398
4835
|
recommendedNextAction: nextActionSchema.optional(),
|
|
4399
4836
|
events: z.array(
|
|
@@ -4429,6 +4866,7 @@ respond_user_input: user-input answers. Default maxEvents=${RESPOND_DEFAULT_MAX_
|
|
|
4429
4866
|
).optional(),
|
|
4430
4867
|
result: z.object({
|
|
4431
4868
|
turnId: z.string(),
|
|
4869
|
+
text: z.string().optional(),
|
|
4432
4870
|
output: z.string().optional(),
|
|
4433
4871
|
structuredOutput: z.unknown().optional(),
|
|
4434
4872
|
turn: z.unknown().optional(),
|
|
@@ -5027,7 +5465,7 @@ var ExecClient = class extends EventEmitter2 {
|
|
|
5027
5465
|
|
|
5028
5466
|
// src/session/persistence.ts
|
|
5029
5467
|
import { join as join5 } from "path";
|
|
5030
|
-
import { mkdirSync as mkdirSync4, existsSync as existsSync7 } from "fs";
|
|
5468
|
+
import { mkdirSync as mkdirSync4, existsSync as existsSync7, rmSync as rmSync3 } from "fs";
|
|
5031
5469
|
import { homedir as homedir2 } from "os";
|
|
5032
5470
|
|
|
5033
5471
|
// src/persistence/atomic-writer.ts
|
|
@@ -5474,6 +5912,11 @@ var SessionPersistence = class {
|
|
|
5474
5912
|
this.eventLogs.delete(sessionId);
|
|
5475
5913
|
}
|
|
5476
5914
|
}
|
|
5915
|
+
/** Remove a persisted session directory from disk. */
|
|
5916
|
+
removeSession(sessionId) {
|
|
5917
|
+
this.destroySessionLog(sessionId);
|
|
5918
|
+
rmSync3(join5(this.sessionsDir, sessionId), { recursive: true, force: true });
|
|
5919
|
+
}
|
|
5477
5920
|
/** Clean up: flush all logs, release lock. */
|
|
5478
5921
|
destroy() {
|
|
5479
5922
|
for (const log of this.eventLogs.values()) {
|