@agentclientprotocol/codex-acp 2.1.1 → 2.1.2-preview.2
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 +1 -1
- package/dist/index.js +1275 -403
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -27132,7 +27132,7 @@ import { createRequire } from "node:module";
|
|
|
27132
27132
|
// src/StdUtils.ts
|
|
27133
27133
|
var import_node = __toESM(require_main(), 1);
|
|
27134
27134
|
import { StringDecoder } from "node:string_decoder";
|
|
27135
|
-
import {
|
|
27135
|
+
import { finished, Readable } from "node:stream";
|
|
27136
27136
|
|
|
27137
27137
|
// src/Logger.ts
|
|
27138
27138
|
import fs from "node:fs";
|
|
@@ -27264,9 +27264,37 @@ function createJSONRPCReader(readable) {
|
|
|
27264
27264
|
};
|
|
27265
27265
|
}
|
|
27266
27266
|
function createJsonStream(readable, writable) {
|
|
27267
|
-
const
|
|
27268
|
-
const
|
|
27269
|
-
|
|
27267
|
+
const writeLine = createLineWriter(writable);
|
|
27268
|
+
const errorReplies = new WritableStream({ write: (chunk) => writeLine(chunk) });
|
|
27269
|
+
const input2 = Readable.toWeb(readable);
|
|
27270
|
+
const stream = ndJsonStream2(errorReplies, input2);
|
|
27271
|
+
const messages = new WritableStream({
|
|
27272
|
+
write: (message) => writeLine(JSON.stringify(message) + "\n")
|
|
27273
|
+
});
|
|
27274
|
+
return { readable: stream.readable, writable: messages };
|
|
27275
|
+
}
|
|
27276
|
+
function createLineWriter(writable) {
|
|
27277
|
+
let failure3;
|
|
27278
|
+
const waiting = /* @__PURE__ */ new Set();
|
|
27279
|
+
const stopWatching = finished(writable, (error62) => {
|
|
27280
|
+
stopWatching();
|
|
27281
|
+
writable.on("error", () => {
|
|
27282
|
+
});
|
|
27283
|
+
writable.off("drain", onDrain);
|
|
27284
|
+
failure3 = error62 ?? new Error("The output stream ended");
|
|
27285
|
+
for (const waiter of waiting) waiter.reject(failure3);
|
|
27286
|
+
waiting.clear();
|
|
27287
|
+
});
|
|
27288
|
+
const onDrain = () => {
|
|
27289
|
+
for (const waiter of waiting) waiter.resolve();
|
|
27290
|
+
waiting.clear();
|
|
27291
|
+
};
|
|
27292
|
+
writable.on("drain", onDrain);
|
|
27293
|
+
return (line) => {
|
|
27294
|
+
if (failure3 !== void 0) return Promise.reject(failure3);
|
|
27295
|
+
if (writable.write(line)) return Promise.resolve();
|
|
27296
|
+
return new Promise((resolve, reject) => waiting.add({ resolve, reject }));
|
|
27297
|
+
};
|
|
27270
27298
|
}
|
|
27271
27299
|
var LineSplitter = class {
|
|
27272
27300
|
constructor(onLine) {
|
|
@@ -27298,6 +27326,17 @@ var LineSplitter = class {
|
|
|
27298
27326
|
}
|
|
27299
27327
|
}
|
|
27300
27328
|
};
|
|
27329
|
+
async function settledWithin(promise2, ms) {
|
|
27330
|
+
let timer;
|
|
27331
|
+
const timeout = new Promise((resolve) => {
|
|
27332
|
+
timer = setTimeout(() => resolve("pending"), Math.max(0, ms));
|
|
27333
|
+
});
|
|
27334
|
+
try {
|
|
27335
|
+
return await Promise.race([promise2, timeout]);
|
|
27336
|
+
} finally {
|
|
27337
|
+
clearTimeout(timer);
|
|
27338
|
+
}
|
|
27339
|
+
}
|
|
27301
27340
|
|
|
27302
27341
|
// src/CodexJsonRpcConnection.ts
|
|
27303
27342
|
function startCodexConnection(codexPath, env) {
|
|
@@ -27357,6 +27396,17 @@ var ACPSessionConnection = class {
|
|
|
27357
27396
|
}
|
|
27358
27397
|
};
|
|
27359
27398
|
|
|
27399
|
+
// src/SessionTitle.ts
|
|
27400
|
+
var MAX_SESSION_TITLE_LENGTH = 256;
|
|
27401
|
+
function normalizeSessionTitle(title) {
|
|
27402
|
+
const normalized = title?.replace(/\s+/g, " ").trim() ?? "";
|
|
27403
|
+
if (normalized.length === 0) return null;
|
|
27404
|
+
if (normalized.length <= MAX_SESSION_TITLE_LENGTH) return normalized;
|
|
27405
|
+
let cut = normalized.slice(0, MAX_SESSION_TITLE_LENGTH - 1);
|
|
27406
|
+
if (/[\uD800-\uDBFF]$/.test(cut)) cut = cut.slice(0, -1);
|
|
27407
|
+
return `${cut.trimEnd()}\u2026`;
|
|
27408
|
+
}
|
|
27409
|
+
|
|
27360
27410
|
// src/TokenCount.ts
|
|
27361
27411
|
function toTokenCount(usage) {
|
|
27362
27412
|
return {
|
|
@@ -27403,6 +27453,7 @@ var AIR_MESSAGE_PHASE_KEY = "phase";
|
|
|
27403
27453
|
var AIR_GOAL_KEY = "goal";
|
|
27404
27454
|
var AIR_KIND_KEY = "kind";
|
|
27405
27455
|
var AIR_COMMAND_ACTION_KEY = "commandAction";
|
|
27456
|
+
var AIR_SKILL_PATH_KEY = "skillPath";
|
|
27406
27457
|
var AIR_PERMISSION_KEY = "permission";
|
|
27407
27458
|
var AIR_CONTEXT_COMPACTION_KEY = "contextCompaction";
|
|
27408
27459
|
var AIR_RAW_INPUT_RENDERING_KEY = "rawInputRendering";
|
|
@@ -30424,12 +30475,12 @@ var CodexEventHandler = class {
|
|
|
30424
30475
|
case "thread/tokenUsage/updated":
|
|
30425
30476
|
return this.createUsageUpdate(notification.params);
|
|
30426
30477
|
case "thread/name/updated":
|
|
30427
|
-
this.sessionState.sessionTitle = notification.params.threadName
|
|
30478
|
+
this.sessionState.sessionTitle = normalizeSessionTitle(notification.params.threadName);
|
|
30428
30479
|
this.sessionState.sessionTitleSource = notification.params.threadName == null ? "unset" : "explicit";
|
|
30429
30480
|
this.sessionState.titleGen?.observeRename();
|
|
30430
30481
|
return {
|
|
30431
30482
|
sessionUpdate: "session_info_update",
|
|
30432
|
-
title:
|
|
30483
|
+
title: this.sessionState.sessionTitle
|
|
30433
30484
|
};
|
|
30434
30485
|
case "thread/status/changed":
|
|
30435
30486
|
return this.createCodexSessionInfoUpdate({
|
|
@@ -32318,6 +32369,255 @@ function isCodexAuthRequest(request) {
|
|
|
32318
32369
|
return request.methodId === "api-key" || request.methodId === "chat-gpt" || request.methodId === "chat-gpt-device-code" || request.methodId === "gateway";
|
|
32319
32370
|
}
|
|
32320
32371
|
|
|
32372
|
+
// src/mcp/McpServerSignIn.ts
|
|
32373
|
+
import { randomUUID } from "node:crypto";
|
|
32374
|
+
function createMcpServerSignIn(connection, codexAcpClient, clientCapabilities) {
|
|
32375
|
+
return async (sessionId, serverName, signal) => {
|
|
32376
|
+
if (!clientSupportsUrlElicitation(clientCapabilities())) {
|
|
32377
|
+
return "unsupported";
|
|
32378
|
+
}
|
|
32379
|
+
try {
|
|
32380
|
+
return await signInWithUrlElicitation(connection, codexAcpClient(), sessionId, serverName, signal) ? "signedIn" : "failed";
|
|
32381
|
+
} catch (err) {
|
|
32382
|
+
if (!signal?.aborted) {
|
|
32383
|
+
logger.error(`Failed to authenticate MCP server ${serverName}`, err);
|
|
32384
|
+
}
|
|
32385
|
+
return "failed";
|
|
32386
|
+
}
|
|
32387
|
+
};
|
|
32388
|
+
}
|
|
32389
|
+
async function signInWithUrlElicitation(connection, codexAcpClient, sessionId, serverName, signal) {
|
|
32390
|
+
signal?.throwIfAborted();
|
|
32391
|
+
const stop = new AbortController();
|
|
32392
|
+
const onAbort = () => stop.abort(signal?.reason);
|
|
32393
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
32394
|
+
const stopped = new Promise((_, reject) => {
|
|
32395
|
+
stop.signal.addEventListener("abort", () => reject(stop.signal.reason), { once: true });
|
|
32396
|
+
});
|
|
32397
|
+
void stopped.catch(() => {
|
|
32398
|
+
});
|
|
32399
|
+
const elicitationId = `mcp-oauth-${randomUUID()}`;
|
|
32400
|
+
let dialogOpen = false;
|
|
32401
|
+
try {
|
|
32402
|
+
const completed = codexAcpClient.awaitMcpServerOauthLoginCompleted(serverName, sessionId, stop.signal);
|
|
32403
|
+
void completed.catch(() => {
|
|
32404
|
+
});
|
|
32405
|
+
const login2 = await Promise.race([
|
|
32406
|
+
codexAcpClient.mcpServerOauthLogin({ name: serverName, threadId: sessionId }),
|
|
32407
|
+
stopped
|
|
32408
|
+
]);
|
|
32409
|
+
const elicitation = Promise.resolve(connection.request(
|
|
32410
|
+
methods.client.elicitation.create,
|
|
32411
|
+
{
|
|
32412
|
+
mode: "url",
|
|
32413
|
+
sessionId,
|
|
32414
|
+
message: `Authenticate with MCP server ${serverName}`,
|
|
32415
|
+
url: login2.authorizationUrl,
|
|
32416
|
+
elicitationId
|
|
32417
|
+
}
|
|
32418
|
+
));
|
|
32419
|
+
void elicitation.catch(() => {
|
|
32420
|
+
});
|
|
32421
|
+
dialogOpen = true;
|
|
32422
|
+
const first = await Promise.race([
|
|
32423
|
+
completed.then((result2) => ({ type: "completed", result: result2 })),
|
|
32424
|
+
elicitation.then((response) => ({ type: "elicitation", response })),
|
|
32425
|
+
stopped
|
|
32426
|
+
]);
|
|
32427
|
+
if (first.type === "elicitation" && !CreateElicitationResponse.isAccept(first.response)) {
|
|
32428
|
+
dialogOpen = false;
|
|
32429
|
+
return false;
|
|
32430
|
+
}
|
|
32431
|
+
const result = first.type === "completed" ? first.result : await Promise.race([completed, stopped]);
|
|
32432
|
+
return result.success;
|
|
32433
|
+
} finally {
|
|
32434
|
+
stop.abort();
|
|
32435
|
+
signal?.removeEventListener("abort", onAbort);
|
|
32436
|
+
if (dialogOpen) {
|
|
32437
|
+
await connection.notify(methods.client.elicitation.complete, { elicitationId });
|
|
32438
|
+
}
|
|
32439
|
+
}
|
|
32440
|
+
}
|
|
32441
|
+
|
|
32442
|
+
// src/McpServerName.ts
|
|
32443
|
+
var MCP_SERVER_NAME_WHITESPACE = /\p{White_Space}/gu;
|
|
32444
|
+
function sanitizeMcpServerName(name) {
|
|
32445
|
+
return name.replace(MCP_SERVER_NAME_WHITESPACE, "_");
|
|
32446
|
+
}
|
|
32447
|
+
|
|
32448
|
+
// src/tool-calls/reporters/McpStartupReporter.ts
|
|
32449
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
32450
|
+
var McpStartupReporter = class {
|
|
32451
|
+
static skipped(serverNames) {
|
|
32452
|
+
return serverNames.map((server) => failure2(
|
|
32453
|
+
server,
|
|
32454
|
+
`[codex-acp] MCP server \`${server}\` was not started, because the Codex config already defines an MCP server with this name. Codex uses its own entry, or no server if that entry is disabled.`
|
|
32455
|
+
));
|
|
32456
|
+
}
|
|
32457
|
+
static failures(event) {
|
|
32458
|
+
return [
|
|
32459
|
+
...event.failed.map((server) => failure2(
|
|
32460
|
+
server.server,
|
|
32461
|
+
`[codex-acp forwarded startup error] MCP server \`${server.server}\` failed to start: ${server.error}`
|
|
32462
|
+
)),
|
|
32463
|
+
...event.cancelled.map((server) => failure2(
|
|
32464
|
+
server,
|
|
32465
|
+
`[codex-acp forwarded startup error] MCP server \`${server}\` startup was cancelled.`
|
|
32466
|
+
))
|
|
32467
|
+
];
|
|
32468
|
+
}
|
|
32469
|
+
};
|
|
32470
|
+
function failure2(serverName, message) {
|
|
32471
|
+
return {
|
|
32472
|
+
// A unique id, so that a later report for the same server cannot replace this one.
|
|
32473
|
+
toolCallId: `mcp_startup.${encodeURIComponent(serverName)}.${randomUUID2()}`,
|
|
32474
|
+
report: "start",
|
|
32475
|
+
kind: "other",
|
|
32476
|
+
title: `mcp__${serverName}__startup`,
|
|
32477
|
+
status: "failed",
|
|
32478
|
+
result: [textContent(message)]
|
|
32479
|
+
};
|
|
32480
|
+
}
|
|
32481
|
+
|
|
32482
|
+
// src/mcp/McpSessionStartup.ts
|
|
32483
|
+
var McpSessionStartup = class {
|
|
32484
|
+
connection;
|
|
32485
|
+
codexAcpClient;
|
|
32486
|
+
runWithProcessCheck;
|
|
32487
|
+
signIn;
|
|
32488
|
+
capabilities;
|
|
32489
|
+
/** True when the session is open and does not close. */
|
|
32490
|
+
isSessionOpen;
|
|
32491
|
+
pendingSessions = /* @__PURE__ */ new Map();
|
|
32492
|
+
constructor(connection, codexAcpClient, runWithProcessCheck, signIn, capabilities, isSessionOpen) {
|
|
32493
|
+
this.connection = connection;
|
|
32494
|
+
this.codexAcpClient = codexAcpClient;
|
|
32495
|
+
this.runWithProcessCheck = runWithProcessCheck;
|
|
32496
|
+
this.signIn = signIn;
|
|
32497
|
+
this.capabilities = capabilities;
|
|
32498
|
+
this.isSessionOpen = isSessionOpen;
|
|
32499
|
+
}
|
|
32500
|
+
/**
|
|
32501
|
+
* Starts the startup wait of the session and publishes the report when the startup ends.
|
|
32502
|
+
* It returns a promise only when it waits for the startup, and returns null otherwise.
|
|
32503
|
+
* The promise rejects when the startup fails before `awaitTimeoutMs`. Then the session has no pending startup.
|
|
32504
|
+
*/
|
|
32505
|
+
begin(sessionId, mcpServers, afterVersion, options) {
|
|
32506
|
+
const pendingStartup = this.createPendingSession(sessionId, mcpServers, afterVersion, options.skippedServers ?? []);
|
|
32507
|
+
if (options.publish) {
|
|
32508
|
+
this.pendingSessions.set(sessionId, pendingStartup);
|
|
32509
|
+
}
|
|
32510
|
+
const awaitTimeoutMs = options.awaitTimeoutMs;
|
|
32511
|
+
if (awaitTimeoutMs !== void 0 && awaitTimeoutMs > 0) {
|
|
32512
|
+
const startupWait = settledWithin(pendingStartup.startup, awaitTimeoutMs).then(() => void 0);
|
|
32513
|
+
void startupWait.then(
|
|
32514
|
+
() => this.publishOrAbort(sessionId, pendingStartup, options.publish),
|
|
32515
|
+
() => this.forget(sessionId, pendingStartup)
|
|
32516
|
+
);
|
|
32517
|
+
return startupWait;
|
|
32518
|
+
}
|
|
32519
|
+
this.publishOrAbort(sessionId, pendingStartup, options.publish);
|
|
32520
|
+
return null;
|
|
32521
|
+
}
|
|
32522
|
+
/** Stops the startup wait and the sign-in of the session. */
|
|
32523
|
+
close(sessionId) {
|
|
32524
|
+
this.pendingSessions.get(sessionId)?.abort.abort();
|
|
32525
|
+
this.pendingSessions.delete(sessionId);
|
|
32526
|
+
}
|
|
32527
|
+
isPending(sessionId) {
|
|
32528
|
+
return this.pendingSessions.has(sessionId);
|
|
32529
|
+
}
|
|
32530
|
+
publishOrAbort(sessionId, pendingStartup, publish) {
|
|
32531
|
+
if (publish) {
|
|
32532
|
+
this.publishAsync(sessionId);
|
|
32533
|
+
} else {
|
|
32534
|
+
pendingStartup.abort.abort();
|
|
32535
|
+
}
|
|
32536
|
+
}
|
|
32537
|
+
forget(sessionId, pendingStartup) {
|
|
32538
|
+
if (this.pendingSessions.get(sessionId) === pendingStartup) {
|
|
32539
|
+
this.pendingSessions.delete(sessionId);
|
|
32540
|
+
}
|
|
32541
|
+
}
|
|
32542
|
+
publishAsync(sessionId) {
|
|
32543
|
+
void this.doPublish(sessionId);
|
|
32544
|
+
}
|
|
32545
|
+
createPendingSession(sessionId, mcpServers, afterVersion, skippedServers) {
|
|
32546
|
+
const requestedServers = new Set(getRequestedMcpServerNames(mcpServers).filter((server) => !skippedServers.includes(server)));
|
|
32547
|
+
const abort = new AbortController();
|
|
32548
|
+
const startup = this.runWithProcessCheck(() => this.codexAcpClient().awaitMcpServerStartup(
|
|
32549
|
+
Array.from(requestedServers),
|
|
32550
|
+
afterVersion,
|
|
32551
|
+
{ threadId: sessionId, signal: abort.signal }
|
|
32552
|
+
));
|
|
32553
|
+
void startup.catch(() => {
|
|
32554
|
+
});
|
|
32555
|
+
return { requestedServers, skippedServers, startup, abort };
|
|
32556
|
+
}
|
|
32557
|
+
async doPublish(sessionId) {
|
|
32558
|
+
const pendingStartup = this.pendingSessions.get(sessionId);
|
|
32559
|
+
if (!pendingStartup) {
|
|
32560
|
+
return;
|
|
32561
|
+
}
|
|
32562
|
+
try {
|
|
32563
|
+
const mcpStartup = await pendingStartup.startup;
|
|
32564
|
+
if (!this.isSessionOpen(sessionId) || this.pendingSessions.get(sessionId) !== pendingStartup) {
|
|
32565
|
+
return;
|
|
32566
|
+
}
|
|
32567
|
+
await this.publish(sessionId, mcpStartup, pendingStartup);
|
|
32568
|
+
} catch (err) {
|
|
32569
|
+
if (!pendingStartup.abort.signal.aborted) {
|
|
32570
|
+
logger.error(`Failed to publish MCP startup status for session ${sessionId}`, err);
|
|
32571
|
+
}
|
|
32572
|
+
} finally {
|
|
32573
|
+
this.forget(sessionId, pendingStartup);
|
|
32574
|
+
}
|
|
32575
|
+
}
|
|
32576
|
+
async publish(sessionId, mcpStartup, pendingStartup) {
|
|
32577
|
+
const { requestedServers, skippedServers, abort: { signal } } = pendingStartup;
|
|
32578
|
+
const filteredStartup = {
|
|
32579
|
+
ready: mcpStartup.ready.filter((server) => requestedServers.has(server)),
|
|
32580
|
+
failed: mcpStartup.failed.filter((server) => requestedServers.has(server.server)),
|
|
32581
|
+
cancelled: mcpStartup.cancelled.filter((server) => requestedServers.has(server))
|
|
32582
|
+
};
|
|
32583
|
+
const failuresAfterOauth = [];
|
|
32584
|
+
const readyAfterOauth = [...filteredStartup.ready];
|
|
32585
|
+
for (const failure3 of filteredStartup.failed) {
|
|
32586
|
+
const signIn = failure3.failureReason === "reauthenticationRequired" ? await this.signIn(sessionId, failure3.server, signal) : "unsupported";
|
|
32587
|
+
if (signIn === "signedIn") {
|
|
32588
|
+
readyAfterOauth.push(failure3.server);
|
|
32589
|
+
} else {
|
|
32590
|
+
failuresAfterOauth.push(failure3);
|
|
32591
|
+
}
|
|
32592
|
+
}
|
|
32593
|
+
if (signal.aborted) {
|
|
32594
|
+
return;
|
|
32595
|
+
}
|
|
32596
|
+
const renderer = new AcpToolCallRenderer(this.capabilities());
|
|
32597
|
+
for (const facts4 of [
|
|
32598
|
+
...McpStartupReporter.skipped(skippedServers),
|
|
32599
|
+
...McpStartupReporter.failures({
|
|
32600
|
+
...filteredStartup,
|
|
32601
|
+
ready: readyAfterOauth,
|
|
32602
|
+
failed: failuresAfterOauth
|
|
32603
|
+
})
|
|
32604
|
+
]) {
|
|
32605
|
+
await this.connection.notify(methods.client.session.update, {
|
|
32606
|
+
sessionId,
|
|
32607
|
+
update: renderer.render(facts4)
|
|
32608
|
+
});
|
|
32609
|
+
}
|
|
32610
|
+
}
|
|
32611
|
+
};
|
|
32612
|
+
function getRequestedMcpServerNames(mcpServers) {
|
|
32613
|
+
return Array.from(new Set(mcpServers.map((server) => sanitizeMcpServerName(server.name))));
|
|
32614
|
+
}
|
|
32615
|
+
var MCP_STARTUP_AWAIT_TIMEOUT_META_KEY = "mcpStartupAwaitTimeoutMs";
|
|
32616
|
+
function parseMcpStartupAwaitTimeoutMs(meta3) {
|
|
32617
|
+
const value = meta3?.[MCP_STARTUP_AWAIT_TIMEOUT_META_KEY];
|
|
32618
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
32619
|
+
}
|
|
32620
|
+
|
|
32321
32621
|
// node_modules/open/index.js
|
|
32322
32622
|
import process10 from "node:process";
|
|
32323
32623
|
import path5 from "node:path";
|
|
@@ -33161,10 +33461,70 @@ var AgentMode = class _AgentMode {
|
|
|
33161
33461
|
// src/CodexAcpClient.ts
|
|
33162
33462
|
import path7 from "node:path";
|
|
33163
33463
|
|
|
33164
|
-
// src/
|
|
33165
|
-
|
|
33166
|
-
|
|
33167
|
-
|
|
33464
|
+
// src/CodexThreadErrors.ts
|
|
33465
|
+
function errorText(err) {
|
|
33466
|
+
if (err instanceof Error) return err.message;
|
|
33467
|
+
if (typeof err === "string") return err;
|
|
33468
|
+
if (err !== null && typeof err === "object" && "message" in err) {
|
|
33469
|
+
return String(err.message);
|
|
33470
|
+
}
|
|
33471
|
+
return "";
|
|
33472
|
+
}
|
|
33473
|
+
function isMissingRolloutError(err) {
|
|
33474
|
+
return errorText(err).includes("no rollout found for thread id");
|
|
33475
|
+
}
|
|
33476
|
+
function isThreadNotLoadedError(err) {
|
|
33477
|
+
return errorText(err).includes("thread not loaded:");
|
|
33478
|
+
}
|
|
33479
|
+
function isThreadNotFoundError(err) {
|
|
33480
|
+
return errorText(err).includes("thread not found:");
|
|
33481
|
+
}
|
|
33482
|
+
function isInvalidThreadIdError(err) {
|
|
33483
|
+
const text = errorText(err);
|
|
33484
|
+
return text.includes("invalid thread id:") || text.includes("invalid session id:");
|
|
33485
|
+
}
|
|
33486
|
+
function isNoActiveTurnError(err) {
|
|
33487
|
+
return errorText(err).includes("no active turn to interrupt");
|
|
33488
|
+
}
|
|
33489
|
+
function isUnknownThreadError(err) {
|
|
33490
|
+
return isMissingRolloutError(err) || isThreadNotLoadedError(err) || isInvalidThreadIdError(err);
|
|
33491
|
+
}
|
|
33492
|
+
function isThreadActiveWriterError(err) {
|
|
33493
|
+
return /\bthread \S+ already has an active writer\b/.test(errorText(err));
|
|
33494
|
+
}
|
|
33495
|
+
function threadActiveWriterRequestError(threadId, err) {
|
|
33496
|
+
return RequestError.invalidRequest(
|
|
33497
|
+
{ reason: "thread_active_writer", threadId, details: errorText(err) },
|
|
33498
|
+
"This Codex session is in use by another Codex client (the Codex app, the CLI or an IDE extension). Close the session there or quit that client, then try again."
|
|
33499
|
+
);
|
|
33500
|
+
}
|
|
33501
|
+
var INTERNAL_ERROR_CODE = -32603;
|
|
33502
|
+
var ACCOUNT_READ_UNAVAILABLE_MESSAGES = /* @__PURE__ */ new Set([
|
|
33503
|
+
"workspace routing discovery failed",
|
|
33504
|
+
"workspace routing discovery timed out"
|
|
33505
|
+
]);
|
|
33506
|
+
var ACCOUNT_READ_AUTH_FAILURE_MESSAGES = /* @__PURE__ */ new Set([
|
|
33507
|
+
"workspace routing discovery unauthorized (401)",
|
|
33508
|
+
"selected workspace missing from routing discovery",
|
|
33509
|
+
"workspace routing requires a ChatGPT account id"
|
|
33510
|
+
]);
|
|
33511
|
+
var ACCOUNT_READ_ACCOUNT_CHANGED_MESSAGE = "account changed during workspace routing discovery";
|
|
33512
|
+
function internalErrorText(err) {
|
|
33513
|
+
if (err === null || typeof err !== "object" || err.code !== INTERNAL_ERROR_CODE) {
|
|
33514
|
+
return null;
|
|
33515
|
+
}
|
|
33516
|
+
return errorText(err);
|
|
33517
|
+
}
|
|
33518
|
+
function isAccountReadUnavailableError(err) {
|
|
33519
|
+
const text = internalErrorText(err);
|
|
33520
|
+
return text !== null && ACCOUNT_READ_UNAVAILABLE_MESSAGES.has(text);
|
|
33521
|
+
}
|
|
33522
|
+
function isAccountReadAuthFailureError(err) {
|
|
33523
|
+
const text = internalErrorText(err);
|
|
33524
|
+
return text !== null && ACCOUNT_READ_AUTH_FAILURE_MESSAGES.has(text);
|
|
33525
|
+
}
|
|
33526
|
+
function isAccountReadAccountChangedError(err) {
|
|
33527
|
+
return internalErrorText(err) === ACCOUNT_READ_ACCOUNT_CHANGED_MESSAGE;
|
|
33168
33528
|
}
|
|
33169
33529
|
|
|
33170
33530
|
// package.json
|
|
@@ -33173,7 +33533,7 @@ var package_default = {
|
|
|
33173
33533
|
publishConfig: {
|
|
33174
33534
|
access: "public"
|
|
33175
33535
|
},
|
|
33176
|
-
version: "2.1.
|
|
33536
|
+
version: "2.1.2-preview.2",
|
|
33177
33537
|
description: "",
|
|
33178
33538
|
main: "dist/index.js",
|
|
33179
33539
|
bin: {
|
|
@@ -33447,13 +33807,14 @@ import { createHash } from "node:crypto";
|
|
|
33447
33807
|
async function forkSession(request, additionalDirectories, dependencies) {
|
|
33448
33808
|
await dependencies.refreshSkills(request.cwd, additionalDirectories);
|
|
33449
33809
|
const lastTurnId = await resolveForkTurnId(request, dependencies.codexClient);
|
|
33810
|
+
const sessionConfig = await dependencies.createSessionConfig(
|
|
33811
|
+
request.cwd,
|
|
33812
|
+
additionalDirectories,
|
|
33813
|
+
request.mcpServers ?? []
|
|
33814
|
+
);
|
|
33450
33815
|
const response = await dependencies.codexClient.threadFork({
|
|
33451
33816
|
excludeTurns: true,
|
|
33452
|
-
config:
|
|
33453
|
-
request.cwd,
|
|
33454
|
-
additionalDirectories,
|
|
33455
|
-
request.mcpServers ?? []
|
|
33456
|
-
),
|
|
33817
|
+
config: sessionConfig.config,
|
|
33457
33818
|
cwd: request.cwd,
|
|
33458
33819
|
...lastTurnId !== void 0 && { lastTurnId },
|
|
33459
33820
|
modelProvider: await dependencies.getResumeModelProvider(),
|
|
@@ -33468,7 +33829,8 @@ async function forkSession(request, additionalDirectories, dependencies) {
|
|
|
33468
33829
|
collaborationMode: dependencies.getCollaborationMode(response.thread.id),
|
|
33469
33830
|
modelProvider: response.modelProvider,
|
|
33470
33831
|
currentServiceTier: response.serviceTier ?? null,
|
|
33471
|
-
additionalDirectories
|
|
33832
|
+
additionalDirectories,
|
|
33833
|
+
skippedMcpServers: sessionConfig.skippedMcpServers
|
|
33472
33834
|
};
|
|
33473
33835
|
}
|
|
33474
33836
|
async function resolveForkTurnId(request, codexClient) {
|
|
@@ -33525,41 +33887,6 @@ function isUnknownRecord(value) {
|
|
|
33525
33887
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
33526
33888
|
}
|
|
33527
33889
|
|
|
33528
|
-
// src/CodexThreadErrors.ts
|
|
33529
|
-
function errorText(err) {
|
|
33530
|
-
if (err instanceof Error) return err.message;
|
|
33531
|
-
if (typeof err === "string") return err;
|
|
33532
|
-
if (err !== null && typeof err === "object" && "message" in err) {
|
|
33533
|
-
return String(err.message);
|
|
33534
|
-
}
|
|
33535
|
-
return "";
|
|
33536
|
-
}
|
|
33537
|
-
function isMissingRolloutError(err) {
|
|
33538
|
-
return errorText(err).includes("no rollout found for thread id");
|
|
33539
|
-
}
|
|
33540
|
-
function isThreadNotLoadedError(err) {
|
|
33541
|
-
return errorText(err).includes("thread not loaded:");
|
|
33542
|
-
}
|
|
33543
|
-
function isInvalidThreadIdError(err) {
|
|
33544
|
-
const text = errorText(err);
|
|
33545
|
-
return text.includes("invalid thread id:") || text.includes("invalid session id:");
|
|
33546
|
-
}
|
|
33547
|
-
function isNoActiveTurnError(err) {
|
|
33548
|
-
return errorText(err).includes("no active turn to interrupt");
|
|
33549
|
-
}
|
|
33550
|
-
function isUnknownThreadError(err) {
|
|
33551
|
-
return isMissingRolloutError(err) || isThreadNotLoadedError(err) || isInvalidThreadIdError(err);
|
|
33552
|
-
}
|
|
33553
|
-
function isThreadActiveWriterError(err) {
|
|
33554
|
-
return /\bthread \S+ already has an active writer\b/.test(errorText(err));
|
|
33555
|
-
}
|
|
33556
|
-
function threadActiveWriterRequestError(threadId, err) {
|
|
33557
|
-
return RequestError.invalidRequest(
|
|
33558
|
-
{ reason: "thread_active_writer", threadId, details: errorText(err) },
|
|
33559
|
-
"This Codex session is in use by another Codex client (the Codex app, the CLI or an IDE extension). Close the session there or quit that client, then try again."
|
|
33560
|
-
);
|
|
33561
|
-
}
|
|
33562
|
-
|
|
33563
33890
|
// src/CodexAcpClient.ts
|
|
33564
33891
|
var CUSTOM_GATEWAY_PROVIDER_ID = "custom-gateway";
|
|
33565
33892
|
var OPENAI_PROVIDER_ID = "openai";
|
|
@@ -33645,8 +33972,7 @@ var CodexAcpClient = class {
|
|
|
33645
33972
|
return result.success;
|
|
33646
33973
|
}
|
|
33647
33974
|
async authenticateWithChatGpt() {
|
|
33648
|
-
|
|
33649
|
-
if (accountResponse.account?.type === "chatgpt") {
|
|
33975
|
+
if (await this.hasWorkingChatGptLogin()) {
|
|
33650
33976
|
return true;
|
|
33651
33977
|
}
|
|
33652
33978
|
const loginCompletedPromise = this.awaitNextLoginCompleted();
|
|
@@ -33657,9 +33983,32 @@ var CodexAcpClient = class {
|
|
|
33657
33983
|
const result = await loginCompletedPromise;
|
|
33658
33984
|
return result.success;
|
|
33659
33985
|
}
|
|
33986
|
+
/**
|
|
33987
|
+
* Reads with a token refresh whether the agent has a ChatGPT login that a new login does not need to replace.
|
|
33988
|
+
*
|
|
33989
|
+
* An unavailable read answers `true`: only a ChatGPT login runs the routing discovery that failed, see
|
|
33990
|
+
* {@link isAccountReadUnavailableError}, and a new login cannot run without the network either.
|
|
33991
|
+
* A read that failed because the login does not work answers `false`, so a new login replaces it, see
|
|
33992
|
+
* {@link isAccountReadAuthFailureError}. Another error rejects.
|
|
33993
|
+
*/
|
|
33994
|
+
async hasWorkingChatGptLogin() {
|
|
33995
|
+
try {
|
|
33996
|
+
const accountResponse = await this.codexClient.accountRead({ refreshToken: true });
|
|
33997
|
+
return accountResponse.account?.type === "chatgpt";
|
|
33998
|
+
} catch (error62) {
|
|
33999
|
+
if (isAccountReadUnavailableError(error62)) {
|
|
34000
|
+
logger.log("Account read unavailable, so the stored ChatGPT login stays", { error: String(error62) });
|
|
34001
|
+
return true;
|
|
34002
|
+
}
|
|
34003
|
+
if (isAccountReadAuthFailureError(error62)) {
|
|
34004
|
+
logger.log("The stored ChatGPT login does not work, so a new login starts", { error: String(error62) });
|
|
34005
|
+
return false;
|
|
34006
|
+
}
|
|
34007
|
+
throw error62;
|
|
34008
|
+
}
|
|
34009
|
+
}
|
|
33660
34010
|
async authenticateWithChatGptDeviceCode(urlElicitationRequester) {
|
|
33661
|
-
|
|
33662
|
-
if (accountResponse.account?.type === "chatgpt") {
|
|
34011
|
+
if (await this.hasWorkingChatGptLogin()) {
|
|
33663
34012
|
return true;
|
|
33664
34013
|
}
|
|
33665
34014
|
if (!urlElicitationRequester) {
|
|
@@ -33770,12 +34119,13 @@ var CodexAcpClient = class {
|
|
|
33770
34119
|
await this.codexClient.accountLogout();
|
|
33771
34120
|
await accountUpdatedPromise;
|
|
33772
34121
|
}
|
|
33773
|
-
|
|
34122
|
+
/** Reads whether the agent needs a login, with the account read that gave the answer. */
|
|
34123
|
+
async readAuthRequirement() {
|
|
33774
34124
|
if (this.gatewayConfig != null) {
|
|
33775
|
-
return false;
|
|
34125
|
+
return { required: false, account: null };
|
|
33776
34126
|
}
|
|
33777
34127
|
const response = await this.codexClient.accountRead({ refreshToken: false });
|
|
33778
|
-
return response.requiresOpenaiAuth && !response.account;
|
|
34128
|
+
return { required: response.requiresOpenaiAuth && !response.account, account: response };
|
|
33779
34129
|
}
|
|
33780
34130
|
/**
|
|
33781
34131
|
* Validates and stores custom gateway routing. Shared by the `gateway` auth
|
|
@@ -33986,9 +34336,10 @@ var CodexAcpClient = class {
|
|
|
33986
34336
|
async resumeSession(request, onSubscribed) {
|
|
33987
34337
|
const additionalDirectories = readAdditionalDirectories(request.cwd, request.additionalDirectories, request._meta);
|
|
33988
34338
|
await this.refreshSkills(request.cwd, additionalDirectories);
|
|
34339
|
+
const sessionConfig = await this.createSessionConfig(request.cwd, additionalDirectories, request.mcpServers ?? []);
|
|
33989
34340
|
const response = await this.resumeThread({
|
|
33990
34341
|
excludeTurns: true,
|
|
33991
|
-
config:
|
|
34342
|
+
config: sessionConfig.config,
|
|
33992
34343
|
cwd: request.cwd,
|
|
33993
34344
|
modelProvider: await this.getResumeModelProvider(),
|
|
33994
34345
|
threadId: request.sessionId
|
|
@@ -34004,7 +34355,8 @@ var CodexAcpClient = class {
|
|
|
34004
34355
|
collaborationMode: response.collaborationMode ?? this.getCollaborationMode(response.thread.id),
|
|
34005
34356
|
modelProvider: response.modelProvider,
|
|
34006
34357
|
currentServiceTier: response.serviceTier ?? null,
|
|
34007
|
-
additionalDirectories
|
|
34358
|
+
additionalDirectories,
|
|
34359
|
+
skippedMcpServers: sessionConfig.skippedMcpServers
|
|
34008
34360
|
};
|
|
34009
34361
|
}
|
|
34010
34362
|
async forkSession(request) {
|
|
@@ -34022,9 +34374,10 @@ var CodexAcpClient = class {
|
|
|
34022
34374
|
async loadSession(request, onSubscribed) {
|
|
34023
34375
|
const additionalDirectories = readAdditionalDirectories(request.cwd, request.additionalDirectories, request._meta);
|
|
34024
34376
|
await this.refreshSkills(request.cwd, additionalDirectories);
|
|
34377
|
+
const sessionConfig = await this.createSessionConfig(request.cwd, additionalDirectories, request.mcpServers ?? []);
|
|
34025
34378
|
const response = await this.resumeThread({
|
|
34026
34379
|
excludeTurns: true,
|
|
34027
|
-
config:
|
|
34380
|
+
config: sessionConfig.config,
|
|
34028
34381
|
cwd: request.cwd,
|
|
34029
34382
|
modelProvider: await this.getResumeModelProvider(),
|
|
34030
34383
|
threadId: request.sessionId
|
|
@@ -34053,7 +34406,8 @@ var CodexAcpClient = class {
|
|
|
34053
34406
|
currentServiceTier: response.serviceTier ?? null,
|
|
34054
34407
|
thread,
|
|
34055
34408
|
history,
|
|
34056
|
-
additionalDirectories
|
|
34409
|
+
additionalDirectories,
|
|
34410
|
+
skippedMcpServers: sessionConfig.skippedMcpServers
|
|
34057
34411
|
};
|
|
34058
34412
|
}
|
|
34059
34413
|
/**
|
|
@@ -34084,8 +34438,9 @@ var CodexAcpClient = class {
|
|
|
34084
34438
|
async newSession(request) {
|
|
34085
34439
|
const additionalDirectories = readAdditionalDirectories(request.cwd, request.additionalDirectories, request._meta);
|
|
34086
34440
|
await this.refreshSkills(request.cwd, additionalDirectories);
|
|
34441
|
+
const sessionConfig = await this.createSessionConfig(request.cwd, additionalDirectories, request.mcpServers);
|
|
34087
34442
|
const response = await this.codexClient.threadStart({
|
|
34088
|
-
config:
|
|
34443
|
+
config: sessionConfig.config,
|
|
34089
34444
|
modelProvider: this.getModelProvider(),
|
|
34090
34445
|
cwd: request.cwd
|
|
34091
34446
|
});
|
|
@@ -34101,7 +34456,8 @@ var CodexAcpClient = class {
|
|
|
34101
34456
|
collaborationMode: this.getCollaborationMode(response.thread.id),
|
|
34102
34457
|
modelProvider: response.modelProvider,
|
|
34103
34458
|
currentServiceTier: response.serviceTier ?? null,
|
|
34104
|
-
additionalDirectories
|
|
34459
|
+
additionalDirectories,
|
|
34460
|
+
skippedMcpServers: sessionConfig.skippedMcpServers
|
|
34105
34461
|
};
|
|
34106
34462
|
}
|
|
34107
34463
|
async closeSession(sessionId) {
|
|
@@ -34178,11 +34534,11 @@ var CodexAcpClient = class {
|
|
|
34178
34534
|
async clearGoal(sessionId) {
|
|
34179
34535
|
await this.codexClient.runGoalClear({ threadId: sessionId });
|
|
34180
34536
|
}
|
|
34181
|
-
async awaitMcpServerStartup(serverNames, afterVersion) {
|
|
34182
|
-
return await this.codexClient.
|
|
34537
|
+
async awaitMcpServerStartup(serverNames, afterVersion, options) {
|
|
34538
|
+
return await this.codexClient.mcpStartup.await(serverNames, afterVersion, options);
|
|
34183
34539
|
}
|
|
34184
34540
|
getMcpServerStartupVersion() {
|
|
34185
|
-
return this.codexClient.
|
|
34541
|
+
return this.codexClient.mcpStartup.version();
|
|
34186
34542
|
}
|
|
34187
34543
|
async createSessionConfig(projectPath, additionalDirectories, mcpServers) {
|
|
34188
34544
|
const sessionRoots = [projectPath, ...additionalDirectories];
|
|
@@ -34205,7 +34561,7 @@ var CodexAcpClient = class {
|
|
|
34205
34561
|
};
|
|
34206
34562
|
const configWithWorkspaceRoots = mergeSandboxWorkspaceWriteRoots(mergedConfig, additionalDirectories);
|
|
34207
34563
|
if (mcpServers.length === 0) {
|
|
34208
|
-
return configWithWorkspaceRoots;
|
|
34564
|
+
return { config: configWithWorkspaceRoots, skippedMcpServers: [] };
|
|
34209
34565
|
}
|
|
34210
34566
|
const requestedServers = mcpServers.map((mcp) => ({
|
|
34211
34567
|
name: sanitizeMcpServerName(mcp.name),
|
|
@@ -34216,12 +34572,22 @@ var CodexAcpClient = class {
|
|
|
34216
34572
|
const existingNames = await this.getConfigMcpServerNames(projectPath);
|
|
34217
34573
|
serversToConfigure = requestedServers.filter((mcp) => !existingNames.has(mcp.name));
|
|
34218
34574
|
}
|
|
34219
|
-
|
|
34220
|
-
|
|
34575
|
+
const skippedMcpServers = requestedServers.filter((mcp) => !serversToConfigure.includes(mcp)).map((mcp) => mcp.name);
|
|
34576
|
+
if (skippedMcpServers.length > 0) {
|
|
34577
|
+
logger.log("Skipping requested MCP servers that the Codex config already defines", {
|
|
34578
|
+
projectPath,
|
|
34579
|
+
skippedMcpServers
|
|
34580
|
+
});
|
|
34581
|
+
}
|
|
34582
|
+
if (serversToConfigure.length === 0) {
|
|
34583
|
+
return { config: configWithWorkspaceRoots, skippedMcpServers };
|
|
34221
34584
|
}
|
|
34222
34585
|
return {
|
|
34223
|
-
|
|
34224
|
-
|
|
34586
|
+
config: {
|
|
34587
|
+
...configWithWorkspaceRoots,
|
|
34588
|
+
"mcp_servers": Object.fromEntries(serversToConfigure.map((mcp) => [mcp.name, this.createMcpSeverConfig(mcp.server)]))
|
|
34589
|
+
},
|
|
34590
|
+
skippedMcpServers
|
|
34225
34591
|
};
|
|
34226
34592
|
}
|
|
34227
34593
|
async getConfigMcpServerNames(projectPath) {
|
|
@@ -34409,14 +34775,18 @@ var CodexAcpClient = class {
|
|
|
34409
34775
|
});
|
|
34410
34776
|
});
|
|
34411
34777
|
}
|
|
34412
|
-
async listMcpServers() {
|
|
34413
|
-
return this.codexClient.listMcpServerStatus(
|
|
34778
|
+
async listMcpServers(params) {
|
|
34779
|
+
return this.codexClient.listMcpServerStatus(params);
|
|
34780
|
+
}
|
|
34781
|
+
/** Reloads the MCP configuration. Codex reconnects the servers of every loaded thread that failed, closed, or changed. */
|
|
34782
|
+
async reloadMcpServers() {
|
|
34783
|
+
await this.codexClient.mcpServerReload();
|
|
34414
34784
|
}
|
|
34415
34785
|
async mcpServerOauthLogin(params) {
|
|
34416
34786
|
return await this.codexClient.mcpServerOauthLogin(params);
|
|
34417
34787
|
}
|
|
34418
|
-
async awaitMcpServerOauthLoginCompleted(name, threadId) {
|
|
34419
|
-
return await this.codexClient.
|
|
34788
|
+
async awaitMcpServerOauthLoginCompleted(name, threadId, signal) {
|
|
34789
|
+
return await this.codexClient.mcpOauthCompletions.await(name, threadId, signal);
|
|
34420
34790
|
}
|
|
34421
34791
|
async listSessions(request) {
|
|
34422
34792
|
const sourceKinds = [
|
|
@@ -34445,7 +34815,7 @@ var CodexAcpClient = class {
|
|
|
34445
34815
|
const mapThreadToSession = (thread) => ({
|
|
34446
34816
|
sessionId: thread.id,
|
|
34447
34817
|
cwd: thread.cwd,
|
|
34448
|
-
title: (thread.name ?? thread.preview)
|
|
34818
|
+
title: normalizeSessionTitle(thread.name ?? thread.preview),
|
|
34449
34819
|
updatedAt: new Date(thread.updatedAt * 1e3).toISOString()
|
|
34450
34820
|
});
|
|
34451
34821
|
let sessions = listResponse.data.map(mapThreadToSession);
|
|
@@ -34658,6 +35028,204 @@ async function* oneItemPage(items) {
|
|
|
34658
35028
|
|
|
34659
35029
|
// src/CodexAppServerClient.ts
|
|
34660
35030
|
var import_node2 = __toESM(require_main(), 1);
|
|
35031
|
+
|
|
35032
|
+
// src/mcp/McpOauthCompletions.ts
|
|
35033
|
+
var McpOauthCompletions = class {
|
|
35034
|
+
/** The waiters, by server name. */
|
|
35035
|
+
waiters = /* @__PURE__ */ new Map();
|
|
35036
|
+
closedError = null;
|
|
35037
|
+
/** Completes the waiters of the server. A notification without a thread completes the waiters of every thread. */
|
|
35038
|
+
complete(event) {
|
|
35039
|
+
for (const waiter of [...this.waiters.get(event.name) ?? []]) {
|
|
35040
|
+
if (event.threadId === null || event.threadId === waiter.threadId) {
|
|
35041
|
+
waiter.resolve(event);
|
|
35042
|
+
}
|
|
35043
|
+
}
|
|
35044
|
+
}
|
|
35045
|
+
/** Waits for the sign-in completion of the server. The wait rejects when `signal` aborts or when the Codex connection closes. */
|
|
35046
|
+
async await(name, threadId, signal) {
|
|
35047
|
+
signal?.throwIfAborted();
|
|
35048
|
+
if (this.closedError !== null) {
|
|
35049
|
+
throw this.closedError;
|
|
35050
|
+
}
|
|
35051
|
+
return await new Promise((resolve, reject) => {
|
|
35052
|
+
const onAbort = () => waiter.reject(signal?.reason);
|
|
35053
|
+
const release = () => {
|
|
35054
|
+
const waiters2 = this.waiters.get(name);
|
|
35055
|
+
waiters2?.delete(waiter);
|
|
35056
|
+
if (waiters2?.size === 0) {
|
|
35057
|
+
this.waiters.delete(name);
|
|
35058
|
+
}
|
|
35059
|
+
signal?.removeEventListener("abort", onAbort);
|
|
35060
|
+
};
|
|
35061
|
+
const waiter = {
|
|
35062
|
+
threadId,
|
|
35063
|
+
resolve: (event) => {
|
|
35064
|
+
release();
|
|
35065
|
+
resolve(event);
|
|
35066
|
+
},
|
|
35067
|
+
reject: (error62) => {
|
|
35068
|
+
release();
|
|
35069
|
+
reject(error62);
|
|
35070
|
+
}
|
|
35071
|
+
};
|
|
35072
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
35073
|
+
let waiters = this.waiters.get(name);
|
|
35074
|
+
if (waiters === void 0) {
|
|
35075
|
+
waiters = /* @__PURE__ */ new Set();
|
|
35076
|
+
this.waiters.set(name, waiters);
|
|
35077
|
+
}
|
|
35078
|
+
waiters.add(waiter);
|
|
35079
|
+
});
|
|
35080
|
+
}
|
|
35081
|
+
/** Rejects every wait, now and later, because the Codex connection closed. */
|
|
35082
|
+
dispose() {
|
|
35083
|
+
this.closedError ??= new Error("Codex connection closed during the MCP server sign-in.");
|
|
35084
|
+
for (const waiters of [...this.waiters.values()]) {
|
|
35085
|
+
for (const waiter of [...waiters]) {
|
|
35086
|
+
waiter.reject(this.closedError);
|
|
35087
|
+
}
|
|
35088
|
+
}
|
|
35089
|
+
}
|
|
35090
|
+
};
|
|
35091
|
+
|
|
35092
|
+
// src/mcp/McpStartupTracker.ts
|
|
35093
|
+
var McpStartupTracker = class {
|
|
35094
|
+
currentVersion = 0;
|
|
35095
|
+
/** The last startup state of each MCP server, by server name and then by thread id. A `null` thread id is global. */
|
|
35096
|
+
states = /* @__PURE__ */ new Map();
|
|
35097
|
+
waiters = [];
|
|
35098
|
+
closedError = null;
|
|
35099
|
+
/** Returns the version of the last startup event. */
|
|
35100
|
+
version() {
|
|
35101
|
+
return this.currentVersion;
|
|
35102
|
+
}
|
|
35103
|
+
record(params) {
|
|
35104
|
+
this.currentVersion += 1;
|
|
35105
|
+
let states = this.states.get(params.name);
|
|
35106
|
+
if (states === void 0) {
|
|
35107
|
+
states = /* @__PURE__ */ new Map();
|
|
35108
|
+
this.states.set(params.name, states);
|
|
35109
|
+
}
|
|
35110
|
+
states.set(params.threadId ?? null, {
|
|
35111
|
+
status: params.status,
|
|
35112
|
+
error: params.error,
|
|
35113
|
+
failureReason: params.failureReason ?? null,
|
|
35114
|
+
version: this.currentVersion
|
|
35115
|
+
});
|
|
35116
|
+
for (const waiter of [...this.waiters]) {
|
|
35117
|
+
const result = this.tryBuildResult(waiter.serverNames, waiter.afterVersion, waiter.threadId);
|
|
35118
|
+
if (result !== null) {
|
|
35119
|
+
waiter.resolve(result);
|
|
35120
|
+
}
|
|
35121
|
+
}
|
|
35122
|
+
}
|
|
35123
|
+
/**
|
|
35124
|
+
* Waits until each server reaches a terminal startup state after `afterVersion`.
|
|
35125
|
+
* Only the events of `threadId` and the global events count.
|
|
35126
|
+
* The wait rejects when `signal` aborts or when the Codex connection closes.
|
|
35127
|
+
*/
|
|
35128
|
+
async await(serverNames, afterVersion, options) {
|
|
35129
|
+
const uniqueServerNames = Array.from(new Set(serverNames.map((name) => name.trim()).filter((name) => name.length > 0)));
|
|
35130
|
+
if (uniqueServerNames.length === 0) {
|
|
35131
|
+
return { ready: [], failed: [], cancelled: [] };
|
|
35132
|
+
}
|
|
35133
|
+
const { threadId, signal } = options;
|
|
35134
|
+
const result = this.tryBuildResult(uniqueServerNames, afterVersion, threadId);
|
|
35135
|
+
if (result !== null) {
|
|
35136
|
+
return result;
|
|
35137
|
+
}
|
|
35138
|
+
signal?.throwIfAborted();
|
|
35139
|
+
if (this.closedError !== null) {
|
|
35140
|
+
throw this.closedError;
|
|
35141
|
+
}
|
|
35142
|
+
return await new Promise((resolve, reject) => {
|
|
35143
|
+
const onAbort = () => waiter.reject(signal?.reason);
|
|
35144
|
+
const release = () => {
|
|
35145
|
+
const index = this.waiters.indexOf(waiter);
|
|
35146
|
+
if (index >= 0) {
|
|
35147
|
+
this.waiters.splice(index, 1);
|
|
35148
|
+
}
|
|
35149
|
+
signal?.removeEventListener("abort", onAbort);
|
|
35150
|
+
};
|
|
35151
|
+
const waiter = {
|
|
35152
|
+
serverNames: uniqueServerNames,
|
|
35153
|
+
afterVersion,
|
|
35154
|
+
threadId,
|
|
35155
|
+
resolve: (startup) => {
|
|
35156
|
+
release();
|
|
35157
|
+
resolve(startup);
|
|
35158
|
+
},
|
|
35159
|
+
reject: (error62) => {
|
|
35160
|
+
release();
|
|
35161
|
+
reject(error62);
|
|
35162
|
+
}
|
|
35163
|
+
};
|
|
35164
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
35165
|
+
this.waiters.push(waiter);
|
|
35166
|
+
});
|
|
35167
|
+
}
|
|
35168
|
+
/** Drops the startup states of a thread. The global states stay. */
|
|
35169
|
+
forgetThread(threadId) {
|
|
35170
|
+
for (const [serverName, states] of this.states) {
|
|
35171
|
+
states.delete(threadId);
|
|
35172
|
+
if (states.size === 0) {
|
|
35173
|
+
this.states.delete(serverName);
|
|
35174
|
+
}
|
|
35175
|
+
}
|
|
35176
|
+
}
|
|
35177
|
+
/** Rejects every wait, now and later, because the Codex connection closed. */
|
|
35178
|
+
dispose() {
|
|
35179
|
+
this.closedError ??= new Error("Codex connection closed during the MCP server startup.");
|
|
35180
|
+
for (const waiter of [...this.waiters]) {
|
|
35181
|
+
waiter.reject(this.closedError);
|
|
35182
|
+
}
|
|
35183
|
+
}
|
|
35184
|
+
/** Returns the latest startup state of the server for the thread. A global state also counts. */
|
|
35185
|
+
latestState(serverName, threadId) {
|
|
35186
|
+
let latest;
|
|
35187
|
+
for (const [stateThreadId, state] of this.states.get(serverName) ?? []) {
|
|
35188
|
+
if (stateThreadId !== null && stateThreadId !== threadId) {
|
|
35189
|
+
continue;
|
|
35190
|
+
}
|
|
35191
|
+
if (latest === void 0 || state.version > latest.version) {
|
|
35192
|
+
latest = state;
|
|
35193
|
+
}
|
|
35194
|
+
}
|
|
35195
|
+
return latest;
|
|
35196
|
+
}
|
|
35197
|
+
tryBuildResult(serverNames, afterVersion, threadId) {
|
|
35198
|
+
const ready = [];
|
|
35199
|
+
const failed = [];
|
|
35200
|
+
const cancelled = [];
|
|
35201
|
+
for (const serverName of serverNames) {
|
|
35202
|
+
const state = this.latestState(serverName, threadId);
|
|
35203
|
+
if (!state || state.version <= afterVersion) {
|
|
35204
|
+
return null;
|
|
35205
|
+
}
|
|
35206
|
+
switch (state.status) {
|
|
35207
|
+
case "starting":
|
|
35208
|
+
return null;
|
|
35209
|
+
case "ready":
|
|
35210
|
+
ready.push(serverName);
|
|
35211
|
+
break;
|
|
35212
|
+
case "failed":
|
|
35213
|
+
failed.push({
|
|
35214
|
+
server: serverName,
|
|
35215
|
+
error: state.error ?? "unknown MCP startup error",
|
|
35216
|
+
...state.failureReason === null ? {} : { failureReason: state.failureReason }
|
|
35217
|
+
});
|
|
35218
|
+
break;
|
|
35219
|
+
case "cancelled":
|
|
35220
|
+
cancelled.push(serverName);
|
|
35221
|
+
break;
|
|
35222
|
+
}
|
|
35223
|
+
}
|
|
35224
|
+
return { ready, failed, cancelled };
|
|
35225
|
+
}
|
|
35226
|
+
};
|
|
35227
|
+
|
|
35228
|
+
// src/CodexAppServerClient.ts
|
|
34661
35229
|
var CommandExecutionApprovalRequest = new import_node2.RequestType("item/commandExecution/requestApproval");
|
|
34662
35230
|
var FileChangeApprovalRequest = new import_node2.RequestType("item/fileChange/requestApproval");
|
|
34663
35231
|
var PermissionsApprovalRequest = new import_node2.RequestType("item/permissions/requestApproval");
|
|
@@ -34671,9 +35239,8 @@ var CodexAppServerClient = class {
|
|
|
34671
35239
|
connection;
|
|
34672
35240
|
approvalHandlers = /* @__PURE__ */ new Map();
|
|
34673
35241
|
elicitationHandlers = /* @__PURE__ */ new Map();
|
|
34674
|
-
|
|
34675
|
-
|
|
34676
|
-
mcpServerStartupResolvers = [];
|
|
35242
|
+
mcpStartup = new McpStartupTracker();
|
|
35243
|
+
mcpOauthCompletions = new McpOauthCompletions();
|
|
34677
35244
|
pendingTurnCompletionResolvers = /* @__PURE__ */ new Map();
|
|
34678
35245
|
pendingCompactionCompletionResolvers = /* @__PURE__ */ new Map();
|
|
34679
35246
|
turnCompletionCaptures = /* @__PURE__ */ new Map();
|
|
@@ -34685,17 +35252,15 @@ var CodexAppServerClient = class {
|
|
|
34685
35252
|
staleTurnIds = /* @__PURE__ */ new Map();
|
|
34686
35253
|
constructor(connection) {
|
|
34687
35254
|
this.connection = connection;
|
|
35255
|
+
this.connection.onClose?.(() => this.disposeMcpWaits());
|
|
35256
|
+
this.connection.onDispose?.(() => this.disposeMcpWaits());
|
|
34688
35257
|
this.connection.onUnhandledNotification((data) => {
|
|
34689
35258
|
const serverNotification = data;
|
|
34690
35259
|
if (isMcpServerStatusUpdatedNotification(serverNotification)) {
|
|
34691
|
-
this.
|
|
34692
|
-
|
|
34693
|
-
|
|
34694
|
-
|
|
34695
|
-
failureReason: serverNotification.params.failureReason ?? null,
|
|
34696
|
-
version: this.mcpServerStartupVersion
|
|
34697
|
-
});
|
|
34698
|
-
this.resolveMcpServerStartupResolvers();
|
|
35260
|
+
this.mcpStartup.record(serverNotification.params);
|
|
35261
|
+
}
|
|
35262
|
+
if (serverNotification.method === "mcpServer/oauthLogin/completed") {
|
|
35263
|
+
this.mcpOauthCompletions.complete(serverNotification.params);
|
|
34699
35264
|
}
|
|
34700
35265
|
if (isTurnCompletedNotification(serverNotification)) {
|
|
34701
35266
|
this.recordTurnCompleted(serverNotification.params);
|
|
@@ -35014,7 +35579,9 @@ var CodexAppServerClient = class {
|
|
|
35014
35579
|
});
|
|
35015
35580
|
void completed.catch(() => {
|
|
35016
35581
|
});
|
|
35017
|
-
const
|
|
35582
|
+
const rejectClosed = () => rejectCompleted(new Error("Codex connection closed during compaction."));
|
|
35583
|
+
const closed = this.connection.onClose?.(rejectClosed);
|
|
35584
|
+
const disposed = this.connection.onDispose?.(rejectClosed);
|
|
35018
35585
|
const completeCompaction = (event) => {
|
|
35019
35586
|
if (compactTurnId !== null && event.params.turnId !== compactTurnId) return;
|
|
35020
35587
|
resolveCompleted(event);
|
|
@@ -35040,6 +35607,7 @@ var CodexAppServerClient = class {
|
|
|
35040
35607
|
releaseRoutingCapture();
|
|
35041
35608
|
releaseCompactionCapture();
|
|
35042
35609
|
closed?.dispose();
|
|
35610
|
+
disposed?.dispose();
|
|
35043
35611
|
}
|
|
35044
35612
|
}
|
|
35045
35613
|
async turnInterrupt(params) {
|
|
@@ -35119,7 +35687,7 @@ var CodexAppServerClient = class {
|
|
|
35119
35687
|
cursor,
|
|
35120
35688
|
limit: HISTORY_PAGE_ITEMS,
|
|
35121
35689
|
sortDirection: "asc"
|
|
35122
|
-
}));
|
|
35690
|
+
}), (page) => page.some((entry) => entry.item.id === lastItemId));
|
|
35123
35691
|
for await (const page of pages) {
|
|
35124
35692
|
const items = page.map((entry) => entry.item);
|
|
35125
35693
|
const lastIndex = items.findIndex((item) => item.id === lastItemId);
|
|
@@ -35193,24 +35761,12 @@ var CodexAppServerClient = class {
|
|
|
35193
35761
|
async listMcpServerStatus(params) {
|
|
35194
35762
|
return await this.sendRequest({ method: "mcpServerStatus/list", params });
|
|
35195
35763
|
}
|
|
35764
|
+
async mcpServerReload() {
|
|
35765
|
+
return await this.sendRequest({ method: "config/mcpServer/reload", params: void 0 });
|
|
35766
|
+
}
|
|
35196
35767
|
async mcpServerOauthLogin(params) {
|
|
35197
35768
|
return await this.sendRequest({ method: "mcpServer/oauth/login", params });
|
|
35198
35769
|
}
|
|
35199
|
-
async awaitMcpServerOauthLoginCompleted(name, threadId) {
|
|
35200
|
-
return await new Promise((resolve) => {
|
|
35201
|
-
let disposable;
|
|
35202
|
-
disposable = this.connection.onNotification(
|
|
35203
|
-
"mcpServer/oauthLogin/completed",
|
|
35204
|
-
(event) => {
|
|
35205
|
-
if (event.name !== name || event.threadId !== threadId) {
|
|
35206
|
-
return;
|
|
35207
|
-
}
|
|
35208
|
-
disposable?.dispose();
|
|
35209
|
-
resolve(event);
|
|
35210
|
-
}
|
|
35211
|
-
);
|
|
35212
|
-
});
|
|
35213
|
-
}
|
|
35214
35770
|
async accountLogin(params) {
|
|
35215
35771
|
return await this.sendRequest({ method: "account/login/start", params });
|
|
35216
35772
|
}
|
|
@@ -35223,26 +35779,6 @@ var CodexAppServerClient = class {
|
|
|
35223
35779
|
async configRead(params) {
|
|
35224
35780
|
return await this.sendRequest({ method: "config/read", params });
|
|
35225
35781
|
}
|
|
35226
|
-
getMcpServerStartupVersion() {
|
|
35227
|
-
return this.mcpServerStartupVersion;
|
|
35228
|
-
}
|
|
35229
|
-
async awaitMcpServerStartup(serverNames, afterVersion) {
|
|
35230
|
-
const uniqueServerNames = Array.from(new Set(serverNames.map((serverName) => serverName.trim()).filter((serverName) => serverName.length > 0)));
|
|
35231
|
-
if (uniqueServerNames.length === 0) {
|
|
35232
|
-
return { ready: [], failed: [], cancelled: [] };
|
|
35233
|
-
}
|
|
35234
|
-
const result = this.tryBuildMcpStartupResult(uniqueServerNames, afterVersion);
|
|
35235
|
-
if (result !== null) {
|
|
35236
|
-
return result;
|
|
35237
|
-
}
|
|
35238
|
-
return await new Promise((resolve) => {
|
|
35239
|
-
this.mcpServerStartupResolvers.push({
|
|
35240
|
-
serverNames: uniqueServerNames,
|
|
35241
|
-
afterVersion,
|
|
35242
|
-
resolve
|
|
35243
|
-
});
|
|
35244
|
-
});
|
|
35245
|
-
}
|
|
35246
35782
|
async accountRead(params) {
|
|
35247
35783
|
return await this.sendRequest({ method: "account/read", params });
|
|
35248
35784
|
}
|
|
@@ -35515,46 +36051,9 @@ var CodexAppServerClient = class {
|
|
|
35515
36051
|
}
|
|
35516
36052
|
};
|
|
35517
36053
|
}
|
|
35518
|
-
|
|
35519
|
-
|
|
35520
|
-
|
|
35521
|
-
const result = this.tryBuildMcpStartupResult(resolver.serverNames, resolver.afterVersion);
|
|
35522
|
-
if (result !== null) {
|
|
35523
|
-
resolver.resolve(result);
|
|
35524
|
-
} else {
|
|
35525
|
-
pendingResolvers.push(resolver);
|
|
35526
|
-
}
|
|
35527
|
-
}
|
|
35528
|
-
this.mcpServerStartupResolvers.splice(0, this.mcpServerStartupResolvers.length, ...pendingResolvers);
|
|
35529
|
-
}
|
|
35530
|
-
tryBuildMcpStartupResult(serverNames, afterVersion) {
|
|
35531
|
-
const ready = [];
|
|
35532
|
-
const failed = [];
|
|
35533
|
-
const cancelled = [];
|
|
35534
|
-
for (const serverName of serverNames) {
|
|
35535
|
-
const state = this.mcpServerStartupStates.get(serverName);
|
|
35536
|
-
if (!state || state.version <= afterVersion) {
|
|
35537
|
-
return null;
|
|
35538
|
-
}
|
|
35539
|
-
switch (state.status) {
|
|
35540
|
-
case "starting":
|
|
35541
|
-
return null;
|
|
35542
|
-
case "ready":
|
|
35543
|
-
ready.push(serverName);
|
|
35544
|
-
break;
|
|
35545
|
-
case "failed":
|
|
35546
|
-
failed.push({
|
|
35547
|
-
server: serverName,
|
|
35548
|
-
error: state.error ?? "unknown MCP startup error",
|
|
35549
|
-
...state.failureReason === null ? {} : { failureReason: state.failureReason }
|
|
35550
|
-
});
|
|
35551
|
-
break;
|
|
35552
|
-
case "cancelled":
|
|
35553
|
-
cancelled.push(serverName);
|
|
35554
|
-
break;
|
|
35555
|
-
}
|
|
35556
|
-
}
|
|
35557
|
-
return { ready, failed, cancelled };
|
|
36054
|
+
disposeMcpWaits() {
|
|
36055
|
+
this.mcpStartup.dispose();
|
|
36056
|
+
this.mcpOauthCompletions.dispose();
|
|
35558
36057
|
}
|
|
35559
36058
|
async sendRequest(request) {
|
|
35560
36059
|
for (const callback of this.codexEventHandlers) {
|
|
@@ -35614,18 +36113,28 @@ function extractTurnRouting(notification) {
|
|
|
35614
36113
|
}
|
|
35615
36114
|
return { threadId, turnId: null };
|
|
35616
36115
|
}
|
|
35617
|
-
async function* historyPages(threadId, method, requestPage) {
|
|
36116
|
+
async function* historyPages(threadId, method, requestPage, endsHistory) {
|
|
36117
|
+
const request = (cursor2) => {
|
|
36118
|
+
const page = withHistoryTimeout(requestPage(cursor2), method, threadId);
|
|
36119
|
+
page.catch(() => {
|
|
36120
|
+
});
|
|
36121
|
+
return page;
|
|
36122
|
+
};
|
|
35618
36123
|
const seenCursors = /* @__PURE__ */ new Set();
|
|
35619
36124
|
let cursor = null;
|
|
36125
|
+
let next = null;
|
|
35620
36126
|
do {
|
|
35621
|
-
const page = await
|
|
35622
|
-
|
|
36127
|
+
const page = await (next ?? request(cursor));
|
|
36128
|
+
next = null;
|
|
35623
36129
|
cursor = page.nextCursor;
|
|
35624
|
-
|
|
35625
|
-
|
|
35626
|
-
throw new Error("Codex returned a repeated thread history cursor");
|
|
35627
|
-
}
|
|
36130
|
+
const repeated = cursor !== null && seenCursors.has(cursor);
|
|
36131
|
+
if (cursor !== null && !repeated) {
|
|
35628
36132
|
seenCursors.add(cursor);
|
|
36133
|
+
if (endsHistory && !endsHistory(page.data)) next = request(cursor);
|
|
36134
|
+
}
|
|
36135
|
+
yield page.data;
|
|
36136
|
+
if (repeated) {
|
|
36137
|
+
throw new Error("Codex returned a repeated thread history cursor");
|
|
35629
36138
|
}
|
|
35630
36139
|
} while (cursor !== null);
|
|
35631
36140
|
}
|
|
@@ -35700,6 +36209,338 @@ function createReasoningEffortConfigOption(supportedReasoningEfforts, currentEff
|
|
|
35700
36209
|
};
|
|
35701
36210
|
}
|
|
35702
36211
|
|
|
36212
|
+
// src/mcp/McpServerStatusList.ts
|
|
36213
|
+
async function listMcpServerStatus(codexAcpClient, threadId) {
|
|
36214
|
+
const servers = [];
|
|
36215
|
+
const seenCursors = /* @__PURE__ */ new Set();
|
|
36216
|
+
let cursor = null;
|
|
36217
|
+
do {
|
|
36218
|
+
const page = await codexAcpClient.listMcpServers({
|
|
36219
|
+
cursor,
|
|
36220
|
+
detail: "toolsAndAuthOnly",
|
|
36221
|
+
...threadId === void 0 ? {} : { threadId }
|
|
36222
|
+
});
|
|
36223
|
+
servers.push(...page.data);
|
|
36224
|
+
cursor = page.nextCursor;
|
|
36225
|
+
if (cursor !== null) {
|
|
36226
|
+
if (seenCursors.has(cursor)) {
|
|
36227
|
+
logger.log(`MCP server status list repeated the cursor ${cursor}`);
|
|
36228
|
+
break;
|
|
36229
|
+
}
|
|
36230
|
+
seenCursors.add(cursor);
|
|
36231
|
+
}
|
|
36232
|
+
} while (cursor !== null);
|
|
36233
|
+
return servers;
|
|
36234
|
+
}
|
|
36235
|
+
|
|
36236
|
+
// src/mcp/McpStatusMarkdown.ts
|
|
36237
|
+
var NO_SERVERS_MESSAGE = "No MCP servers are configured.";
|
|
36238
|
+
var SUMMARY_ORDER = [
|
|
36239
|
+
"connected",
|
|
36240
|
+
"starting",
|
|
36241
|
+
"notStarted",
|
|
36242
|
+
"authenticationRequired",
|
|
36243
|
+
"failed",
|
|
36244
|
+
"cancelled",
|
|
36245
|
+
"disabled",
|
|
36246
|
+
null
|
|
36247
|
+
];
|
|
36248
|
+
var GROUP_ORDER = [
|
|
36249
|
+
"failed",
|
|
36250
|
+
"cancelled",
|
|
36251
|
+
"authenticationRequired",
|
|
36252
|
+
"notStarted",
|
|
36253
|
+
"starting",
|
|
36254
|
+
"connected",
|
|
36255
|
+
null
|
|
36256
|
+
];
|
|
36257
|
+
function statusLabel(status) {
|
|
36258
|
+
switch (status) {
|
|
36259
|
+
case "connected":
|
|
36260
|
+
return "Connected";
|
|
36261
|
+
case "starting":
|
|
36262
|
+
return "Connecting";
|
|
36263
|
+
case "notStarted":
|
|
36264
|
+
return "Not started";
|
|
36265
|
+
case "authenticationRequired":
|
|
36266
|
+
return "Needs authentication";
|
|
36267
|
+
case "failed":
|
|
36268
|
+
return "Failed";
|
|
36269
|
+
case "cancelled":
|
|
36270
|
+
return "Cancelled";
|
|
36271
|
+
case "disabled":
|
|
36272
|
+
return "Disabled";
|
|
36273
|
+
case null:
|
|
36274
|
+
return "Unknown";
|
|
36275
|
+
}
|
|
36276
|
+
}
|
|
36277
|
+
var KNOWN_STATUSES = /* @__PURE__ */ new Set([...GROUP_ORDER.filter((status) => status !== null), "disabled"]);
|
|
36278
|
+
function toEntry(server) {
|
|
36279
|
+
const status = server.runtimeStatus;
|
|
36280
|
+
return {
|
|
36281
|
+
name: server.name,
|
|
36282
|
+
status: status !== null && KNOWN_STATUSES.has(status) ? status : null,
|
|
36283
|
+
toolCount: Object.keys(server.tools ?? {}).length,
|
|
36284
|
+
toolsError: server.toolsError,
|
|
36285
|
+
authStatus: server.authStatus,
|
|
36286
|
+
listedByCodex: true
|
|
36287
|
+
};
|
|
36288
|
+
}
|
|
36289
|
+
function formatStatus(servers, errors) {
|
|
36290
|
+
if (servers.length === 0) {
|
|
36291
|
+
return NO_SERVERS_MESSAGE;
|
|
36292
|
+
}
|
|
36293
|
+
const counts = SUMMARY_ORDER.map((status) => ({ status, count: servers.filter((server) => server.status === status).length })).filter((entry) => entry.count > 0).map((entry) => `${entry.count} ${statusLabel(entry.status).toLowerCase()}`);
|
|
36294
|
+
const lines = [`**MCP servers:** ${servers.length} (${counts.join(", ")})`];
|
|
36295
|
+
for (const status of GROUP_ORDER) {
|
|
36296
|
+
const group = servers.filter((server) => server.status === status);
|
|
36297
|
+
if (group.length === 0) {
|
|
36298
|
+
continue;
|
|
36299
|
+
}
|
|
36300
|
+
lines.push("", `**${statusLabel(status)}**`);
|
|
36301
|
+
for (const server of group) {
|
|
36302
|
+
const detail = serverDetail(server, errors.get(server.name) ?? null);
|
|
36303
|
+
lines.push(detail === null ? `- ${inlineCode(server.name)}` : `- ${inlineCode(server.name)}: ${detail}`);
|
|
36304
|
+
}
|
|
36305
|
+
}
|
|
36306
|
+
const disabled = servers.filter((server) => server.status === "disabled");
|
|
36307
|
+
if (disabled.length > 0) {
|
|
36308
|
+
lines.push("", `**Disabled:** ${disabled.map((server) => inlineCode(server.name)).join(", ")}`);
|
|
36309
|
+
}
|
|
36310
|
+
if (servers.some((server) => server.status !== null && RECONNECT_HINT_STATUSES.has(server.status))) {
|
|
36311
|
+
lines.push("", RECONNECT_HINT);
|
|
36312
|
+
}
|
|
36313
|
+
return lines.join("\n");
|
|
36314
|
+
}
|
|
36315
|
+
var RECONNECT_HINT = "Run `/mcp reconnect` to restart the servers that are not connected.";
|
|
36316
|
+
var RECONNECT_HINT_STATUSES = /* @__PURE__ */ new Set(["failed", "cancelled", "authenticationRequired", "notStarted"]);
|
|
36317
|
+
function serverDetail(server, startupError) {
|
|
36318
|
+
if (server.status === "authenticationRequired") {
|
|
36319
|
+
return NOT_SIGNED_IN;
|
|
36320
|
+
}
|
|
36321
|
+
const parts = [];
|
|
36322
|
+
const toolCount = server.toolCount ?? 0;
|
|
36323
|
+
if (server.status === "connected" || toolCount > 0) {
|
|
36324
|
+
parts.push(`${toolCount} ${toolCount === 1 ? "tool" : "tools"}`);
|
|
36325
|
+
}
|
|
36326
|
+
if (server.authStatus === "notLoggedIn" && server.status !== "disabled") {
|
|
36327
|
+
parts.push(NOT_SIGNED_IN);
|
|
36328
|
+
}
|
|
36329
|
+
const error62 = startupError ?? server.toolsError;
|
|
36330
|
+
const cleanedError = error62 === null ? "" : formatNoteDetail(error62);
|
|
36331
|
+
if (cleanedError.length > 0) {
|
|
36332
|
+
parts.push(cleanedError);
|
|
36333
|
+
}
|
|
36334
|
+
return parts.length === 0 ? null : parts.join("; ");
|
|
36335
|
+
}
|
|
36336
|
+
var NOT_SIGNED_IN = "not signed in";
|
|
36337
|
+
var MAX_ERROR_LENGTH = 160;
|
|
36338
|
+
var MAX_ERROR_SENTENCES = 2;
|
|
36339
|
+
var MAX_INPUT_LENGTH = 2e3;
|
|
36340
|
+
var WRAPPER_SEGMENT = /^(?:tool discovery failed|mcp startup failed|handshaking with mcp server failed|json-rpc error|startup error|error|mcp client for .+ failed to start|-32\d{3})$/i;
|
|
36341
|
+
var TYPE_PATH = /\S*\w::\w+::\S*/g;
|
|
36342
|
+
function cleanErrorMessage(text) {
|
|
36343
|
+
const oneLine = text.slice(0, MAX_INPUT_LENGTH).replace(/\s+/g, " ").trim();
|
|
36344
|
+
const message = oneLine.split(": ").map((segment) => segment.replace(TYPE_PATH, "").replace(/\s+/g, " ").trim()).filter((segment) => segment.length > 0 && !WRAPPER_SEGMENT.test(segment)).join(": ");
|
|
36345
|
+
if (message.length === 0) {
|
|
36346
|
+
return shorten(oneLine);
|
|
36347
|
+
}
|
|
36348
|
+
return shorten(message.split(/(?<=[.!?])\s+/).slice(0, MAX_ERROR_SENTENCES).join(" "));
|
|
36349
|
+
}
|
|
36350
|
+
function shorten(text) {
|
|
36351
|
+
const codePoints = Array.from(text);
|
|
36352
|
+
return codePoints.length > MAX_ERROR_LENGTH ? `${codePoints.slice(0, MAX_ERROR_LENGTH).join("").trimEnd()}\u2026` : text;
|
|
36353
|
+
}
|
|
36354
|
+
function formatNoteDetail(text) {
|
|
36355
|
+
return escapeMarkdown(cleanErrorMessage(text));
|
|
36356
|
+
}
|
|
36357
|
+
function escapeMarkdown(text) {
|
|
36358
|
+
return text.replace(/[\\`*_[\]<>~]/g, (character) => `\\${character}`);
|
|
36359
|
+
}
|
|
36360
|
+
var UNNAMED_SERVER = "(unnamed)";
|
|
36361
|
+
function inlineCode(name) {
|
|
36362
|
+
const text = name.replace(/\s+/g, " ");
|
|
36363
|
+
if (text.length === 0) {
|
|
36364
|
+
return UNNAMED_SERVER;
|
|
36365
|
+
}
|
|
36366
|
+
const longestRun = Math.max(0, ...Array.from(text.matchAll(/`+/g), (match) => match[0].length));
|
|
36367
|
+
const fence = "`".repeat(longestRun + 1);
|
|
36368
|
+
const padding = /^[` ]|[` ]$/.test(text) ? " " : "";
|
|
36369
|
+
return `${fence}${padding}${text}${padding}${fence}`;
|
|
36370
|
+
}
|
|
36371
|
+
|
|
36372
|
+
// src/mcp/McpCommand.ts
|
|
36373
|
+
var MCP_RECONNECT_STARTUP_TIMEOUT_MS = 3e4;
|
|
36374
|
+
var MCP_COMMAND_INPUT_HINT = "[reconnect]";
|
|
36375
|
+
var McpCommand = class {
|
|
36376
|
+
codexAcpClient;
|
|
36377
|
+
runWithProcessCheck;
|
|
36378
|
+
signIn;
|
|
36379
|
+
constructor(codexAcpClient, runWithProcessCheck, signIn) {
|
|
36380
|
+
this.codexAcpClient = codexAcpClient;
|
|
36381
|
+
this.runWithProcessCheck = runWithProcessCheck;
|
|
36382
|
+
this.signIn = signIn;
|
|
36383
|
+
}
|
|
36384
|
+
/** Returns the answer, or `null` when `signal` aborted. After an abort, the caller must send nothing. */
|
|
36385
|
+
async run(rest, sessionState, signal) {
|
|
36386
|
+
const args = rest.split(/\s+/).filter((arg) => arg.length > 0);
|
|
36387
|
+
if (args.length === 0) {
|
|
36388
|
+
const list = await this.listServers(sessionState);
|
|
36389
|
+
return signal?.aborted ? null : formatStatus(list.servers, /* @__PURE__ */ new Map());
|
|
36390
|
+
}
|
|
36391
|
+
if (args.length === 1 && args[0].toLowerCase() === "reconnect") {
|
|
36392
|
+
return await this.reconnect(sessionState, signal);
|
|
36393
|
+
}
|
|
36394
|
+
return `Command "/mcp" accepts no arguments, or \`reconnect\`.`;
|
|
36395
|
+
}
|
|
36396
|
+
async reconnect(sessionState, signal) {
|
|
36397
|
+
const before = await this.listServers(sessionState);
|
|
36398
|
+
if (before.servers.length === 0) {
|
|
36399
|
+
return NO_SERVERS_MESSAGE;
|
|
36400
|
+
}
|
|
36401
|
+
const notes = [];
|
|
36402
|
+
let reload = await this.reloadAndWait(sessionState, signal);
|
|
36403
|
+
if (reload.error !== null) {
|
|
36404
|
+
notes.push(`Could not reload the MCP configuration: ${formatNoteDetail(reload.error)}`);
|
|
36405
|
+
} else {
|
|
36406
|
+
notes.push(RELOAD_NOTE);
|
|
36407
|
+
const signIn = await this.signInServers(sessionState, reload, signal);
|
|
36408
|
+
notes.push(...signIn.notes);
|
|
36409
|
+
if (signIn.signedIn && !signal?.aborted) {
|
|
36410
|
+
const secondReload = await this.reloadAndWait(sessionState, signal);
|
|
36411
|
+
if (secondReload.error !== null) {
|
|
36412
|
+
notes.push(`Could not reload the MCP configuration after the sign-in: ${formatNoteDetail(secondReload.error)}`);
|
|
36413
|
+
reload = { ...secondReload, startup: reload.startup };
|
|
36414
|
+
} else {
|
|
36415
|
+
reload = secondReload;
|
|
36416
|
+
}
|
|
36417
|
+
}
|
|
36418
|
+
}
|
|
36419
|
+
if (signal?.aborted) {
|
|
36420
|
+
return null;
|
|
36421
|
+
}
|
|
36422
|
+
if (reload.startup === null) {
|
|
36423
|
+
notes.push("Some MCP servers are still starting. Run `/mcp` again later to see their status.");
|
|
36424
|
+
}
|
|
36425
|
+
return `${formatNotes(notes)}
|
|
36426
|
+
|
|
36427
|
+
${formatStatus(reload.list.servers, startupErrors(reload.startup))}`;
|
|
36428
|
+
}
|
|
36429
|
+
/** Reloads the MCP configuration, and waits for the startup of the servers that the reloaded list contains. */
|
|
36430
|
+
async reloadAndWait(sessionState, signal) {
|
|
36431
|
+
const afterVersion = this.codexAcpClient.getMcpServerStartupVersion();
|
|
36432
|
+
try {
|
|
36433
|
+
await this.runWithProcessCheck(() => this.codexAcpClient.reloadMcpServers());
|
|
36434
|
+
} catch (err) {
|
|
36435
|
+
logger.error(`Failed to reload the MCP configuration for session ${sessionState.sessionId}`, err);
|
|
36436
|
+
return { error: errorMessage(err), startup: emptyStartupResult(), list: await this.listServers(sessionState) };
|
|
36437
|
+
}
|
|
36438
|
+
const reloaded = await this.listServers(sessionState);
|
|
36439
|
+
if (!reloaded.live) {
|
|
36440
|
+
return { error: null, startup: emptyStartupResult(), list: reloaded };
|
|
36441
|
+
}
|
|
36442
|
+
const serverNames = reloaded.servers.filter((server) => server.listedByCodex && server.status !== "disabled").map((server) => server.name);
|
|
36443
|
+
const startup = await this.waitForStartup(sessionState.sessionId, serverNames, afterVersion, signal);
|
|
36444
|
+
if (signal?.aborted) {
|
|
36445
|
+
return { error: null, startup, list: reloaded };
|
|
36446
|
+
}
|
|
36447
|
+
return { error: null, startup, list: await this.listServers(sessionState) };
|
|
36448
|
+
}
|
|
36449
|
+
/** Returns `null` at the timeout or when `signal` aborts. */
|
|
36450
|
+
async waitForStartup(threadId, serverNames, afterVersion, signal) {
|
|
36451
|
+
if (signal?.aborted) {
|
|
36452
|
+
return null;
|
|
36453
|
+
}
|
|
36454
|
+
const stop = new AbortController();
|
|
36455
|
+
const onAbort = () => stop.abort();
|
|
36456
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
36457
|
+
const timer = setTimeout(() => stop.abort(), MCP_RECONNECT_STARTUP_TIMEOUT_MS);
|
|
36458
|
+
try {
|
|
36459
|
+
return await this.runWithProcessCheck(() => this.codexAcpClient.awaitMcpServerStartup(
|
|
36460
|
+
serverNames,
|
|
36461
|
+
afterVersion,
|
|
36462
|
+
{ threadId, signal: stop.signal }
|
|
36463
|
+
));
|
|
36464
|
+
} catch (err) {
|
|
36465
|
+
if (stop.signal.aborted) {
|
|
36466
|
+
return null;
|
|
36467
|
+
}
|
|
36468
|
+
throw err;
|
|
36469
|
+
} finally {
|
|
36470
|
+
clearTimeout(timer);
|
|
36471
|
+
signal?.removeEventListener("abort", onAbort);
|
|
36472
|
+
}
|
|
36473
|
+
}
|
|
36474
|
+
async signInServers(sessionState, reload, signal) {
|
|
36475
|
+
const names = new Set(reload.list.servers.filter((server) => server.status === "authenticationRequired").map((server) => server.name));
|
|
36476
|
+
for (const failure3 of reload.startup?.failed ?? []) {
|
|
36477
|
+
if (failure3.failureReason === "reauthenticationRequired") {
|
|
36478
|
+
names.add(failure3.server);
|
|
36479
|
+
}
|
|
36480
|
+
}
|
|
36481
|
+
const notes = [];
|
|
36482
|
+
let signedIn = false;
|
|
36483
|
+
for (const name of names) {
|
|
36484
|
+
if (signal?.aborted) {
|
|
36485
|
+
break;
|
|
36486
|
+
}
|
|
36487
|
+
const label = inlineCode(name);
|
|
36488
|
+
switch (await this.signIn(sessionState.sessionId, name, signal)) {
|
|
36489
|
+
case "unsupported":
|
|
36490
|
+
notes.push(`MCP server ${label} needs authentication, but this client cannot open a sign-in page.`);
|
|
36491
|
+
break;
|
|
36492
|
+
case "signedIn":
|
|
36493
|
+
notes.push(`Signed in to MCP server ${label}.`);
|
|
36494
|
+
signedIn = true;
|
|
36495
|
+
break;
|
|
36496
|
+
case "failed":
|
|
36497
|
+
notes.push(`The sign-in to MCP server ${label} failed or was cancelled.`);
|
|
36498
|
+
break;
|
|
36499
|
+
}
|
|
36500
|
+
}
|
|
36501
|
+
return { notes, signedIn };
|
|
36502
|
+
}
|
|
36503
|
+
async listServers(sessionState) {
|
|
36504
|
+
const { servers, live } = await this.listServerStatus(sessionState.sessionId);
|
|
36505
|
+
const entries = servers.map(toEntry);
|
|
36506
|
+
for (const name of sessionState.sessionMcpServers ?? []) {
|
|
36507
|
+
if (!entries.some((entry) => entry.name === name)) {
|
|
36508
|
+
entries.push({ name, status: null, toolCount: null, toolsError: null, authStatus: null, listedByCodex: false });
|
|
36509
|
+
}
|
|
36510
|
+
}
|
|
36511
|
+
return { servers: entries, live };
|
|
36512
|
+
}
|
|
36513
|
+
async listServerStatus(threadId) {
|
|
36514
|
+
try {
|
|
36515
|
+
const servers = await this.runWithProcessCheck(() => listMcpServerStatus(this.codexAcpClient, threadId));
|
|
36516
|
+
return { servers, live: true };
|
|
36517
|
+
} catch (err) {
|
|
36518
|
+
if (!isThreadNotFoundError(err) && !isUnknownThreadError(err)) {
|
|
36519
|
+
throw err;
|
|
36520
|
+
}
|
|
36521
|
+
logger.log(`Codex does not know thread ${threadId}, so the MCP server list has no live status`);
|
|
36522
|
+
const servers = await this.runWithProcessCheck(() => listMcpServerStatus(this.codexAcpClient));
|
|
36523
|
+
return { servers, live: false };
|
|
36524
|
+
}
|
|
36525
|
+
}
|
|
36526
|
+
};
|
|
36527
|
+
var RELOAD_NOTE = "Reloaded the MCP configuration of all open sessions. Codex restarted the servers that failed, stopped, or changed. A connected server with unchanged settings keeps its connection.";
|
|
36528
|
+
function startupErrors(result) {
|
|
36529
|
+
return new Map((result?.failed ?? []).map((failure3) => [failure3.server, failure3.error]));
|
|
36530
|
+
}
|
|
36531
|
+
function emptyStartupResult() {
|
|
36532
|
+
return { ready: [], failed: [], cancelled: [] };
|
|
36533
|
+
}
|
|
36534
|
+
function errorMessage(err) {
|
|
36535
|
+
if (err instanceof Error) {
|
|
36536
|
+
return err.message;
|
|
36537
|
+
}
|
|
36538
|
+
return String(err);
|
|
36539
|
+
}
|
|
36540
|
+
function formatNotes(notes) {
|
|
36541
|
+
return ["**Reconnect:**", ...notes.map((note) => `- ${note}`)].join("\n");
|
|
36542
|
+
}
|
|
36543
|
+
|
|
35703
36544
|
// src/CodexCommands.ts
|
|
35704
36545
|
var GOAL_CONTINUATION_PROMPT = [{
|
|
35705
36546
|
type: "text",
|
|
@@ -35710,14 +36551,15 @@ var CodexCommands = class {
|
|
|
35710
36551
|
codexAcpClient;
|
|
35711
36552
|
runWithProcessCheck;
|
|
35712
36553
|
onLogout;
|
|
36554
|
+
mcpCommand;
|
|
35713
36555
|
/** The commands that each session got last, to skip a publish that changes nothing. */
|
|
35714
36556
|
published = /* @__PURE__ */ new WeakMap();
|
|
35715
|
-
constructor(connection, codexAcpClient, runWithProcessCheck, onLogout
|
|
35716
|
-
}) {
|
|
36557
|
+
constructor(connection, codexAcpClient, runWithProcessCheck, onLogout, mcpServerSignIn) {
|
|
35717
36558
|
this.connection = connection;
|
|
35718
36559
|
this.codexAcpClient = codexAcpClient;
|
|
35719
36560
|
this.runWithProcessCheck = runWithProcessCheck;
|
|
35720
36561
|
this.onLogout = onLogout;
|
|
36562
|
+
this.mcpCommand = new McpCommand(codexAcpClient, runWithProcessCheck, mcpServerSignIn);
|
|
35721
36563
|
}
|
|
35722
36564
|
/** Sends the available commands. With `onlyChanges`, it sends nothing when the commands did not change. */
|
|
35723
36565
|
async publish(sessionState, shouldPublish = () => true, onlyChanges = false) {
|
|
@@ -35764,10 +36606,12 @@ var CodexCommands = class {
|
|
|
35764
36606
|
const name = `$${skill.name}`;
|
|
35765
36607
|
if (commands.has(name)) continue;
|
|
35766
36608
|
const description = skill.shortDescription ?? skill.description ?? skill.name;
|
|
36609
|
+
const meta3 = airOnlyMeta(airClient, AIR_SKILL_PATH_KEY, skill.path);
|
|
35767
36610
|
commands.set(name, {
|
|
35768
36611
|
name,
|
|
35769
36612
|
description,
|
|
35770
|
-
input: null
|
|
36613
|
+
input: null,
|
|
36614
|
+
...meta3 ? { _meta: meta3 } : {}
|
|
35771
36615
|
});
|
|
35772
36616
|
}
|
|
35773
36617
|
}
|
|
@@ -35797,8 +36641,8 @@ var CodexCommands = class {
|
|
|
35797
36641
|
},
|
|
35798
36642
|
{
|
|
35799
36643
|
name: "mcp",
|
|
35800
|
-
description: "
|
|
35801
|
-
input:
|
|
36644
|
+
description: "Show the status of the MCP servers, or reconnect them.",
|
|
36645
|
+
input: { hint: MCP_COMMAND_INPUT_HINT }
|
|
35802
36646
|
},
|
|
35803
36647
|
{
|
|
35804
36648
|
name: "skills",
|
|
@@ -35955,15 +36799,10 @@ var CodexCommands = class {
|
|
|
35955
36799
|
return { handled: true };
|
|
35956
36800
|
}
|
|
35957
36801
|
case "mcp": {
|
|
35958
|
-
const
|
|
35959
|
-
|
|
35960
|
-
|
|
35961
|
-
|
|
35962
|
-
return `- ${server.name}: ${toolCount} tools, ${resourceCount} resources, auth=${server.authStatus}`;
|
|
35963
|
-
});
|
|
35964
|
-
const sessionServers = sessionState.sessionMcpServers ? sessionState.sessionMcpServers.map((serverName) => `- ${serverName}`) : [];
|
|
35965
|
-
const lines = [...configuredServers, ...sessionServers];
|
|
35966
|
-
const text = lines.length > 0 ? ["Configured MCP servers:", ...lines].join("\n") : "No MCP servers configured.";
|
|
36802
|
+
const text = await this.mcpCommand.run(command.rest, sessionState, options.signal);
|
|
36803
|
+
if (text === null) {
|
|
36804
|
+
return { handled: true };
|
|
36805
|
+
}
|
|
35967
36806
|
const session = new ACPSessionConnection(this.connection, sessionId);
|
|
35968
36807
|
await session.update(createAgentTextMessageChunk(text));
|
|
35969
36808
|
return { handled: true };
|
|
@@ -36302,17 +37141,19 @@ var ToolCallReports = class {
|
|
|
36302
37141
|
recordStart(key, update) {
|
|
36303
37142
|
this.finishedToolCalls.delete(key);
|
|
36304
37143
|
const fields = /* @__PURE__ */ new Map();
|
|
36305
|
-
|
|
37144
|
+
const terminal = update.status === "completed" || update.status === "failed";
|
|
37145
|
+
const only = terminal ? FINISHED_FIELDS : void 0;
|
|
37146
|
+
for (const [name, value] of reportedFields(update, this.compareMeta && !terminal, only)) {
|
|
36306
37147
|
fields.set(name, value);
|
|
36307
37148
|
}
|
|
36308
37149
|
this.openToolCalls.set(key, fields);
|
|
36309
37150
|
return update;
|
|
36310
37151
|
}
|
|
36311
37152
|
recordUpdate(key, update) {
|
|
36312
|
-
const
|
|
36313
|
-
if (
|
|
37153
|
+
const finished2 = this.finishedToolCalls.get(key);
|
|
37154
|
+
if (finished2 !== void 0) {
|
|
36314
37155
|
const current = this.withoutLateOutput(update);
|
|
36315
|
-
return current === null ? null : this.withoutUnchangedFields(current,
|
|
37156
|
+
return current === null ? null : this.withoutUnchangedFields(current, finished2, FINISHED_FIELDS);
|
|
36316
37157
|
}
|
|
36317
37158
|
const fields = this.openToolCalls.get(key) ?? /* @__PURE__ */ new Map();
|
|
36318
37159
|
this.openToolCalls.set(key, fields);
|
|
@@ -36322,7 +37163,7 @@ var ToolCallReports = class {
|
|
|
36322
37163
|
withoutUnchangedFields(update, fields, recorded) {
|
|
36323
37164
|
const prepared = { ...update };
|
|
36324
37165
|
const meta3 = isRecord2(update._meta) ? { ...update._meta } : void 0;
|
|
36325
|
-
for (const [name, value] of reportedFields(update, this.compareMeta)) {
|
|
37166
|
+
for (const [name, value] of reportedFields(update, this.compareMeta && recorded === void 0, recorded)) {
|
|
36326
37167
|
if (fields.get(name) === value) {
|
|
36327
37168
|
if (name.startsWith(META_FIELD_PREFIX)) {
|
|
36328
37169
|
delete meta3?.[name.slice(META_FIELD_PREFIX.length)];
|
|
@@ -36374,10 +37215,11 @@ var ToolCallReports = class {
|
|
|
36374
37215
|
}
|
|
36375
37216
|
}
|
|
36376
37217
|
};
|
|
36377
|
-
function reportedFields(update, withMeta) {
|
|
37218
|
+
function reportedFields(update, withMeta, only) {
|
|
36378
37219
|
const fields = [];
|
|
36379
37220
|
const record2 = update;
|
|
36380
37221
|
for (const name of COMPARED_FIELDS) {
|
|
37222
|
+
if (only !== void 0 && !only.has(name)) continue;
|
|
36381
37223
|
const value = record2[name];
|
|
36382
37224
|
if (value !== void 0) fields.push([name, JSON.stringify(value)]);
|
|
36383
37225
|
}
|
|
@@ -36573,6 +37415,26 @@ function fromAccountUpdated(notification, previous) {
|
|
|
36573
37415
|
return gatewayStatus();
|
|
36574
37416
|
}
|
|
36575
37417
|
}
|
|
37418
|
+
function accountFromUpdated(notification, previous) {
|
|
37419
|
+
switch (notification.authMode) {
|
|
37420
|
+
case null:
|
|
37421
|
+
return null;
|
|
37422
|
+
case "chatgpt":
|
|
37423
|
+
case "chatgptAuthTokens": {
|
|
37424
|
+
const previousChatGpt = previous?.type === "chatgpt" ? previous : null;
|
|
37425
|
+
return {
|
|
37426
|
+
type: "chatgpt",
|
|
37427
|
+
email: previousChatGpt?.email ?? null,
|
|
37428
|
+
planType: notification.planType ?? previousChatGpt?.planType ?? "unknown"
|
|
37429
|
+
};
|
|
37430
|
+
}
|
|
37431
|
+
case "apikey":
|
|
37432
|
+
case "personalAccessToken":
|
|
37433
|
+
return { type: "apiKey" };
|
|
37434
|
+
default:
|
|
37435
|
+
return previous;
|
|
37436
|
+
}
|
|
37437
|
+
}
|
|
36576
37438
|
function sameAuthStatus(previous, next) {
|
|
36577
37439
|
if (!previous) {
|
|
36578
37440
|
return false;
|
|
@@ -36641,34 +37503,6 @@ var ClientCapabilities = class _ClientCapabilities {
|
|
|
36641
37503
|
}
|
|
36642
37504
|
};
|
|
36643
37505
|
|
|
36644
|
-
// src/tool-calls/reporters/McpStartupReporter.ts
|
|
36645
|
-
import { randomUUID } from "node:crypto";
|
|
36646
|
-
var McpStartupReporter = class {
|
|
36647
|
-
static failures(event) {
|
|
36648
|
-
return [
|
|
36649
|
-
...event.failed.map((server) => failure2(
|
|
36650
|
-
server.server,
|
|
36651
|
-
`[codex-acp forwarded startup error] MCP server \`${server.server}\` failed to start: ${server.error}`
|
|
36652
|
-
)),
|
|
36653
|
-
...event.cancelled.map((server) => failure2(
|
|
36654
|
-
server,
|
|
36655
|
-
`[codex-acp forwarded startup error] MCP server \`${server}\` startup was cancelled.`
|
|
36656
|
-
))
|
|
36657
|
-
];
|
|
36658
|
-
}
|
|
36659
|
-
};
|
|
36660
|
-
function failure2(serverName, message) {
|
|
36661
|
-
return {
|
|
36662
|
-
// A unique id, so that a later report for the same server cannot replace this one.
|
|
36663
|
-
toolCallId: `mcp_startup.${encodeURIComponent(serverName)}.${randomUUID()}`,
|
|
36664
|
-
report: "start",
|
|
36665
|
-
kind: "other",
|
|
36666
|
-
title: `mcp__${serverName}__startup`,
|
|
36667
|
-
status: "failed",
|
|
36668
|
-
result: [textContent(message)]
|
|
36669
|
-
};
|
|
36670
|
-
}
|
|
36671
|
-
|
|
36672
37506
|
// src/tool-calls/reporters/PlanReviewReporter.ts
|
|
36673
37507
|
var IMPLEMENT_PLAN_OPTION_ID = "implement_plan";
|
|
36674
37508
|
var REVISE_PLAN_OPTION_ID = "revise_plan";
|
|
@@ -36778,7 +37612,7 @@ function clientSupportsSubagents(capabilities) {
|
|
|
36778
37612
|
}
|
|
36779
37613
|
|
|
36780
37614
|
// src/CodexAcpServer.ts
|
|
36781
|
-
import { randomUUID as
|
|
37615
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
36782
37616
|
|
|
36783
37617
|
// src/TitleGenerator.ts
|
|
36784
37618
|
var TITLE_MODEL = "gpt-5.6-luna";
|
|
@@ -37337,15 +38171,20 @@ var CodexSessionCompactions = class {
|
|
|
37337
38171
|
var CODEX_PROCESS_EXITED_ERROR_CODE = 1001;
|
|
37338
38172
|
var TITLE_GENERATION_SETTLE_TIMEOUT_MS = 1e4;
|
|
37339
38173
|
var NO_ACTIVE_TURN_RETRY_DELAYS_MS = [25, 50, 100, 200, 400];
|
|
38174
|
+
var ACCOUNT_READ_LOAD_GRACE_MS = 1e3;
|
|
37340
38175
|
function clientSupportsTypedSessionFailures(capabilities) {
|
|
37341
38176
|
return clientSupportsAirCapability(capabilities, AIR_SESSION_FAILURE_KEY);
|
|
37342
38177
|
}
|
|
37343
38178
|
function clientSupportsAgentFileChangeReports(capabilities) {
|
|
37344
38179
|
return clientSupportsAirCapability(capabilities, AIR_AGENT_FILE_CHANGE_REPORT_KEY);
|
|
37345
38180
|
}
|
|
38181
|
+
function isAuthRequiredError(error62) {
|
|
38182
|
+
return error62 instanceof RequestError && error62.code === RequestError.authRequired().code;
|
|
38183
|
+
}
|
|
37346
38184
|
var CodexAcpServer = class {
|
|
37347
38185
|
codexAcpClient;
|
|
37348
38186
|
connection;
|
|
38187
|
+
mcpServerSignIn;
|
|
37349
38188
|
reportingConnection;
|
|
37350
38189
|
defaultAuthRequest;
|
|
37351
38190
|
getExitCode;
|
|
@@ -37359,8 +38198,18 @@ var CodexAcpServer = class {
|
|
|
37359
38198
|
booleanConfigOptionsSupported;
|
|
37360
38199
|
/** Last `authStatus` pushed to the client; used to suppress duplicates. */
|
|
37361
38200
|
currentAuthStatus;
|
|
38201
|
+
/**
|
|
38202
|
+
* The last known account of the agent. Every successful account read and every `account/updated` sets it,
|
|
38203
|
+
* and a logout clears it. A session open takes it when the account read is unavailable.
|
|
38204
|
+
*/
|
|
38205
|
+
lastReadAccount = null;
|
|
38206
|
+
/**
|
|
38207
|
+
* Counts the calls of {@link setAuthStatus}, also the calls that push nothing. A late account read
|
|
38208
|
+
* compares it with the value at the start of the read: a change means a newer status.
|
|
38209
|
+
*/
|
|
38210
|
+
authStatusVersion = 0;
|
|
37362
38211
|
sessions;
|
|
37363
|
-
|
|
38212
|
+
mcpSessionStartup;
|
|
37364
38213
|
pendingTurnStarts;
|
|
37365
38214
|
activePrompts;
|
|
37366
38215
|
steeringQueues;
|
|
@@ -37375,7 +38224,6 @@ var CodexAcpServer = class {
|
|
|
37375
38224
|
providerUpdate = null;
|
|
37376
38225
|
constructor(connection, codexAcpClient, defaultAuthRequest, getExitCode, getRecentStderr, codexProcessState) {
|
|
37377
38226
|
this.sessions = /* @__PURE__ */ new Map();
|
|
37378
|
-
this.pendingMcpStartupSessions = /* @__PURE__ */ new Map();
|
|
37379
38227
|
this.pendingTurnStarts = /* @__PURE__ */ new Map();
|
|
37380
38228
|
this.activePrompts = /* @__PURE__ */ new Map();
|
|
37381
38229
|
this.steeringQueues = /* @__PURE__ */ new Map();
|
|
@@ -37387,12 +38235,25 @@ var CodexAcpServer = class {
|
|
|
37387
38235
|
this.reportingConnection = new ToolCallReportingConnection(connection);
|
|
37388
38236
|
this.connection = this.reportingConnection.asClientConnection();
|
|
37389
38237
|
this.codexAcpClient = codexAcpClient;
|
|
38238
|
+
this.mcpServerSignIn = createMcpServerSignIn(
|
|
38239
|
+
this.connection,
|
|
38240
|
+
() => this.codexAcpClient,
|
|
38241
|
+
() => this.clientCapabilities
|
|
38242
|
+
);
|
|
38243
|
+
this.mcpSessionStartup = new McpSessionStartup(
|
|
38244
|
+
this.connection,
|
|
38245
|
+
() => this.codexAcpClient,
|
|
38246
|
+
(operation) => this.runWithProcessCheck(operation),
|
|
38247
|
+
this.mcpServerSignIn,
|
|
38248
|
+
() => this.capabilities,
|
|
38249
|
+
(sessionId) => this.sessions.has(sessionId) && !this.sessionIsClosing(sessionId)
|
|
38250
|
+
);
|
|
37390
38251
|
this.defaultAuthRequest = defaultAuthRequest ?? null;
|
|
37391
38252
|
this.codexProcessState = codexProcessState ?? null;
|
|
37392
38253
|
this.captureStderr();
|
|
37393
38254
|
this.getExitCode = getExitCode ?? (() => this.codexProcessState?.connection.process.exitCode ?? null);
|
|
37394
38255
|
this.getRecentStderr = getRecentStderr ?? (() => this.codexProcessState?.stderr ?? "");
|
|
37395
|
-
this.sessionFailureEpoch =
|
|
38256
|
+
this.sessionFailureEpoch = randomUUID3();
|
|
37396
38257
|
this.clientInfo = null;
|
|
37397
38258
|
this.clientCapabilities = null;
|
|
37398
38259
|
this.capabilities = ClientCapabilities.DEFAULT;
|
|
@@ -37406,7 +38267,8 @@ var CodexAcpServer = class {
|
|
|
37406
38267
|
this.connection,
|
|
37407
38268
|
client,
|
|
37408
38269
|
(operation) => this.runWithProcessCheck(operation),
|
|
37409
|
-
() => this.refreshAuthState(null)
|
|
38270
|
+
() => this.refreshAuthState(null),
|
|
38271
|
+
this.mcpServerSignIn
|
|
37410
38272
|
);
|
|
37411
38273
|
}
|
|
37412
38274
|
async initialize(_params) {
|
|
@@ -37496,7 +38358,7 @@ var CodexAcpServer = class {
|
|
|
37496
38358
|
}
|
|
37497
38359
|
switch (methodRequest.method) {
|
|
37498
38360
|
case "authentication/status":
|
|
37499
|
-
return await this.
|
|
38361
|
+
return await this.readAuthenticationStatus();
|
|
37500
38362
|
case "authentication/logout": {
|
|
37501
38363
|
await this.logout({});
|
|
37502
38364
|
return {};
|
|
@@ -37579,10 +38441,42 @@ var CodexAcpServer = class {
|
|
|
37579
38441
|
}
|
|
37580
38442
|
}
|
|
37581
38443
|
}
|
|
38444
|
+
/**
|
|
38445
|
+
* Answers `authentication/status`.
|
|
38446
|
+
*
|
|
38447
|
+
* An unavailable account read still tells that the agent has a ChatGPT login, because only such a login
|
|
38448
|
+
* runs the routing discovery, see {@link isAccountReadUnavailableError}. The answer is then `chat-gpt` with
|
|
38449
|
+
* the email of the last known account, or an empty email. A read that failed because the login does not
|
|
38450
|
+
* work answers `unauthenticated`, see {@link isAccountReadAuthFailureError}. Another error rejects.
|
|
38451
|
+
*/
|
|
38452
|
+
async readAuthenticationStatus() {
|
|
38453
|
+
try {
|
|
38454
|
+
return await this.runWithProcessCheck(() => this.codexAcpClient.getAuthenticationStatus());
|
|
38455
|
+
} catch (error62) {
|
|
38456
|
+
if (isAccountReadUnavailableError(error62)) {
|
|
38457
|
+
logger.log("Account read unavailable, so the authentication status uses the last known account", { error: String(error62) });
|
|
38458
|
+
const account = this.lastReadAccount;
|
|
38459
|
+
return { type: "chat-gpt", email: account?.type === "chatgpt" ? account.email ?? "" : "" };
|
|
38460
|
+
}
|
|
38461
|
+
if (isAccountReadAuthFailureError(error62)) {
|
|
38462
|
+
logger.log("The account read found that the agent needs a login", { error: String(error62) });
|
|
38463
|
+
return { type: "unauthenticated" };
|
|
38464
|
+
}
|
|
38465
|
+
throw error62;
|
|
38466
|
+
}
|
|
38467
|
+
}
|
|
38468
|
+
/**
|
|
38469
|
+
* Logs in with the default auth request, or throws `auth_required`, when the agent needs a login.
|
|
38470
|
+
* Returns the account read that found no login needed, or `null` when there was no read or a login ran.
|
|
38471
|
+
* Returns `"unavailable"` when the read failed without an answer about the login, see
|
|
38472
|
+
* {@link isAccountReadUnavailableError}. The session open then continues: a missing login fails the
|
|
38473
|
+
* resume or the first turn with the real error.
|
|
38474
|
+
*/
|
|
37582
38475
|
async checkAuthorization() {
|
|
37583
|
-
const
|
|
37584
|
-
|
|
37585
|
-
|
|
38476
|
+
const requirement = await this.readAccountOrUnavailable(() => this.codexAcpClient.readAuthRequirement());
|
|
38477
|
+
if (requirement === "unavailable") return "unavailable";
|
|
38478
|
+
logger.log("Auth requirement checked", { authRequired: requirement.required });
|
|
38479
|
+
if (requirement.required) {
|
|
37586
38480
|
if (this.defaultAuthRequest) {
|
|
37587
38481
|
logger.log("Authenticating with default auth request...", {
|
|
37588
38482
|
authRequest: this.defaultAuthRequest
|
|
@@ -37593,7 +38487,9 @@ var CodexAcpServer = class {
|
|
|
37593
38487
|
logger.log("Authentication required but no default auth request provided, return to IDE");
|
|
37594
38488
|
throw RequestError.authRequired();
|
|
37595
38489
|
}
|
|
38490
|
+
return null;
|
|
37596
38491
|
}
|
|
38492
|
+
return requirement.account;
|
|
37597
38493
|
}
|
|
37598
38494
|
async getOrCreateSession(request) {
|
|
37599
38495
|
try {
|
|
@@ -37681,7 +38577,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
|
|
|
37681
38577
|
async tryCreateSession(request, operation = "sessionId" in request ? "resume" : "new") {
|
|
37682
38578
|
const existingSessionRequest = request;
|
|
37683
38579
|
const requestedSessionGeneration = operation === "resume" ? this.beginSessionOpen(existingSessionRequest.sessionId) : null;
|
|
37684
|
-
await this.checkAuthorization();
|
|
38580
|
+
const knownAccount = await this.checkAuthorization();
|
|
37685
38581
|
const requestedMcpServers = request.mcpServers ?? [];
|
|
37686
38582
|
const mcpServerStartupVersion = requestedMcpServers.length > 0 ? this.codexAcpClient.getMcpServerStartupVersion() : null;
|
|
37687
38583
|
let sessionMetadata;
|
|
@@ -37713,7 +38609,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
|
|
|
37713
38609
|
const authProvider = sessionMetadata.modelProvider ?? this.codexAcpClient.getModelProvider();
|
|
37714
38610
|
let authState;
|
|
37715
38611
|
try {
|
|
37716
|
-
authState = await this.getAuthStateForProvider(authProvider);
|
|
38612
|
+
authState = await this.getAuthStateForProvider(authProvider, knownAccount);
|
|
37717
38613
|
} catch (err) {
|
|
37718
38614
|
if (resumeSubscribed && requestedSessionGeneration !== null) {
|
|
37719
38615
|
await this.cleanupStaleSessionOpen(sessionId, requestedSessionGeneration);
|
|
@@ -37774,30 +38670,21 @@ Check ${configPath} and project .codex directories, especially their config.toml
|
|
|
37774
38670
|
resumeSubscribed = false;
|
|
37775
38671
|
const canPublishSessionUpdates = operation !== "fork";
|
|
37776
38672
|
if (requestedMcpServers.length > 0 && mcpServerStartupVersion !== null) {
|
|
37777
|
-
const
|
|
37778
|
-
|
|
37779
|
-
|
|
37780
|
-
|
|
37781
|
-
|
|
37782
|
-
|
|
37783
|
-
}
|
|
37784
|
-
const startupAwaitTimeoutMs = parseMcpStartupAwaitTimeoutMs(request._meta);
|
|
37785
|
-
if (startupAwaitTimeoutMs !== void 0 && startupAwaitTimeoutMs > 0) {
|
|
38673
|
+
const startupWait = this.mcpSessionStartup.begin(sessionId, requestedMcpServers, mcpServerStartupVersion, {
|
|
38674
|
+
publish: canPublishSessionUpdates,
|
|
38675
|
+
awaitTimeoutMs: parseMcpStartupAwaitTimeoutMs(request._meta),
|
|
38676
|
+
skippedServers: sessionMetadata.skippedMcpServers
|
|
38677
|
+
});
|
|
38678
|
+
if (startupWait !== null) {
|
|
37786
38679
|
try {
|
|
37787
|
-
await
|
|
38680
|
+
await startupWait;
|
|
37788
38681
|
} catch (err) {
|
|
37789
|
-
if (this.pendingMcpStartupSessions.get(sessionId) === pendingStartup) {
|
|
37790
|
-
this.pendingMcpStartupSessions.delete(sessionId);
|
|
37791
|
-
}
|
|
37792
38682
|
await this.closeSession({ sessionId }).catch((closeError) => {
|
|
37793
38683
|
logger.error(`Failed to close session ${sessionId} after a failed MCP startup wait`, closeError);
|
|
37794
38684
|
});
|
|
37795
38685
|
throw err;
|
|
37796
38686
|
}
|
|
37797
38687
|
}
|
|
37798
|
-
if (canPublishSessionUpdates) {
|
|
37799
|
-
this.publishMcpStartupStatusAsync(sessionId);
|
|
37800
|
-
}
|
|
37801
38688
|
}
|
|
37802
38689
|
if (canPublishSessionUpdates) {
|
|
37803
38690
|
this.publishAvailableCommandsAsync(sessionState, sessionGeneration);
|
|
@@ -37810,7 +38697,15 @@ Check ${configPath} and project .codex directories, especially their config.toml
|
|
|
37810
38697
|
const sessionModeState = sessionState.agentMode.toSessionModeState(sessionState.clientCapabilities.airClient);
|
|
37811
38698
|
return [sessionId, sessionModelState, sessionModeState];
|
|
37812
38699
|
}
|
|
37813
|
-
|
|
38700
|
+
/**
|
|
38701
|
+
* Reads the account of `authProvider` and pushes the auth status. `knownAccount` is an account read that
|
|
38702
|
+
* the caller already has.
|
|
38703
|
+
*
|
|
38704
|
+
* When the read is unavailable, the adapter pushes no status, so the client keeps the last status.
|
|
38705
|
+
* A `none` status would be false here: AIR shows a login request for it. The session keeps the
|
|
38706
|
+
* account of the last read.
|
|
38707
|
+
*/
|
|
38708
|
+
async getAuthStateForProvider(authProvider, knownAccount = null) {
|
|
37814
38709
|
if (!this.authProviderUsesOpenAiAccount(authProvider)) {
|
|
37815
38710
|
await this.publishAuthStatus(authProvider, null);
|
|
37816
38711
|
return {
|
|
@@ -37818,13 +38713,96 @@ Check ${configPath} and project .codex directories, especially their config.toml
|
|
|
37818
38713
|
authConfigured: true
|
|
37819
38714
|
};
|
|
37820
38715
|
}
|
|
37821
|
-
const accountResponse =
|
|
38716
|
+
const accountResponse = knownAccount === "unavailable" ? null : knownAccount ?? await this.readAccountIfAvailable();
|
|
38717
|
+
if (accountResponse === null) {
|
|
38718
|
+
logger.log("No account read, so the auth status stays as it was");
|
|
38719
|
+
return {
|
|
38720
|
+
account: this.lastReadAccount,
|
|
38721
|
+
authConfigured: true
|
|
38722
|
+
};
|
|
38723
|
+
}
|
|
38724
|
+
this.lastReadAccount = accountResponse.account;
|
|
37822
38725
|
await this.publishAuthStatus(authProvider, accountResponse.account);
|
|
37823
38726
|
return {
|
|
37824
38727
|
account: accountResponse.account,
|
|
37825
38728
|
authConfigured: accountResponse.account !== null || !accountResponse.requiresOpenaiAuth
|
|
37826
38729
|
};
|
|
37827
38730
|
}
|
|
38731
|
+
/** Reads the account, or returns `null` when the read failed without an answer about the login. */
|
|
38732
|
+
async readAccountIfAvailable() {
|
|
38733
|
+
const response = await this.readAccountOrUnavailable(() => this.codexAcpClient.getAccount());
|
|
38734
|
+
return response === "unavailable" ? null : response;
|
|
38735
|
+
}
|
|
38736
|
+
/**
|
|
38737
|
+
* Runs an account read, or returns `"unavailable"` when it failed without an answer about the login,
|
|
38738
|
+
* see {@link isAccountReadUnavailableError}. Another error rejects.
|
|
38739
|
+
*/
|
|
38740
|
+
async readAccountOrUnavailable(read) {
|
|
38741
|
+
try {
|
|
38742
|
+
return await this.runWithProcessCheck(read);
|
|
38743
|
+
} catch (error62) {
|
|
38744
|
+
if (!isAccountReadUnavailableError(error62)) throw error62;
|
|
38745
|
+
logger.log("Account read unavailable, the login state is unknown", { error: String(error62) });
|
|
38746
|
+
return "unavailable";
|
|
38747
|
+
}
|
|
38748
|
+
}
|
|
38749
|
+
/**
|
|
38750
|
+
* Applies the account read of a load that answered before the read ended.
|
|
38751
|
+
*
|
|
38752
|
+
* The answer applies only while the load is the current open of the session, and while no auth status
|
|
38753
|
+
* was set after the read started. A close or a new load of the session makes the answer old.
|
|
38754
|
+
* A newer status, from any source, also makes it old. An old answer changes nothing.
|
|
38755
|
+
*
|
|
38756
|
+
* - A read that found no login needed sets the account of the session and pushes the auth status.
|
|
38757
|
+
* - A read that found that the agent needs a login (`auth_required`), or that failed because the login
|
|
38758
|
+
* does not work ({@link isAccountReadAuthFailureError}, for example a revoked token), clears the account
|
|
38759
|
+
* and pushes the `none` status at once, with no second read. The client then shows a login request,
|
|
38760
|
+
* as it does for an `auth_required` error of the load.
|
|
38761
|
+
* - A read that failed because another client logged in or out reads the account again and pushes it.
|
|
38762
|
+
* - An unavailable read changes nothing.
|
|
38763
|
+
* - Any other failure is logged as an error and pushes nothing. It tells nothing sure about the login,
|
|
38764
|
+
* so the client keeps the last status.
|
|
38765
|
+
*/
|
|
38766
|
+
applyLateAccountRead(sessionState, authorization, authStatusVersionAtStart) {
|
|
38767
|
+
const sessionIsCurrent = () => this.sessions.get(sessionState.sessionId) === sessionState;
|
|
38768
|
+
const answerIsCurrent = () => {
|
|
38769
|
+
if (!sessionIsCurrent()) {
|
|
38770
|
+
logger.log("A late account read ended after the load closed, so it changes nothing", { sessionId: sessionState.sessionId });
|
|
38771
|
+
return false;
|
|
38772
|
+
}
|
|
38773
|
+
if (this.authStatusVersion !== authStatusVersionAtStart) {
|
|
38774
|
+
logger.log("A late account read ended after a newer auth status, so it changes nothing", { sessionId: sessionState.sessionId });
|
|
38775
|
+
return false;
|
|
38776
|
+
}
|
|
38777
|
+
return true;
|
|
38778
|
+
};
|
|
38779
|
+
void authorization.then(
|
|
38780
|
+
async (knownAccount) => {
|
|
38781
|
+
if (knownAccount === "unavailable" || !answerIsCurrent()) return;
|
|
38782
|
+
const authState = await this.getAuthStateForProvider(sessionState.authProvider, knownAccount);
|
|
38783
|
+
if (!sessionIsCurrent()) return;
|
|
38784
|
+
sessionState.account = authState.account;
|
|
38785
|
+
sessionState.authConfigured = authState.authConfigured;
|
|
38786
|
+
},
|
|
38787
|
+
async (error62) => {
|
|
38788
|
+
if (!answerIsCurrent()) return;
|
|
38789
|
+
if (isAuthRequiredError(error62) || isAccountReadAuthFailureError(error62)) {
|
|
38790
|
+
logger.log("A late account read found that the agent needs a login", { error: String(error62) });
|
|
38791
|
+
this.lastReadAccount = null;
|
|
38792
|
+
sessionState.account = null;
|
|
38793
|
+
sessionState.authConfigured = false;
|
|
38794
|
+
await this.publishAuthStatus(sessionState.authProvider, null);
|
|
38795
|
+
return;
|
|
38796
|
+
}
|
|
38797
|
+
if (isAccountReadAccountChangedError(error62)) {
|
|
38798
|
+
logger.log("The account changed while a late account read ran, so the adapter reads it again");
|
|
38799
|
+
await this.publishAuthStatusRead();
|
|
38800
|
+
return;
|
|
38801
|
+
}
|
|
38802
|
+
logger.error("A late account read failed, so the auth status stays as it was", error62);
|
|
38803
|
+
}
|
|
38804
|
+
).catch((error62) => logger.error("Failed to apply a late account read", error62));
|
|
38805
|
+
}
|
|
37828
38806
|
authProviderUsesOpenAiAccount(authProvider) {
|
|
37829
38807
|
return authProvider === null || authProvider === "openai";
|
|
37830
38808
|
}
|
|
@@ -37965,7 +38943,8 @@ Check ${configPath} and project .codex directories, especially their config.toml
|
|
|
37965
38943
|
} finally {
|
|
37966
38944
|
if (this.getSessionGeneration(params.sessionId) === closeGeneration) {
|
|
37967
38945
|
this.sessions.delete(params.sessionId);
|
|
37968
|
-
this.
|
|
38946
|
+
this.mcpSessionStartup.close(params.sessionId);
|
|
38947
|
+
this.codexAcpClient.appServerClient.mcpStartup.forgetThread(params.sessionId);
|
|
37969
38948
|
this.pendingTurnStarts.delete(params.sessionId);
|
|
37970
38949
|
this.activePrompts.delete(params.sessionId);
|
|
37971
38950
|
this.steeringQueues.delete(params.sessionId);
|
|
@@ -37994,7 +38973,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
|
|
|
37994
38973
|
return {};
|
|
37995
38974
|
}
|
|
37996
38975
|
hasLocalSession(sessionId) {
|
|
37997
|
-
return this.sessions.has(sessionId) || this.
|
|
38976
|
+
return this.sessions.has(sessionId) || this.mcpSessionStartup.isPending(sessionId) || this.pendingTurnStarts.has(sessionId) || this.activePrompts.has(sessionId) || this.hasPendingSessionOpen(sessionId) || this.sessionIsClosing(sessionId);
|
|
37998
38977
|
}
|
|
37999
38978
|
hasPendingSessionOpen(sessionId) {
|
|
38000
38979
|
return this.sessionOpenGenerations.get(sessionId) === this.getSessionGeneration(sessionId);
|
|
@@ -38223,12 +39202,22 @@ Check ${configPath} and project .codex directories, especially their config.toml
|
|
|
38223
39202
|
* Never rejects: an unreadable source means "nothing to report", not an
|
|
38224
39203
|
* error. The client then keeps showing the last pushed value, or "not
|
|
38225
39204
|
* reported" when there was none.
|
|
39205
|
+
*
|
|
39206
|
+
* A read that failed because the login does not work, for example a revoked
|
|
39207
|
+
* token, is an answer: it pushes the `none` status, see
|
|
39208
|
+
* {@link isAccountReadAuthFailureError}.
|
|
38226
39209
|
*/
|
|
38227
39210
|
async publishAuthStatusRead() {
|
|
38228
39211
|
let authStatus;
|
|
38229
39212
|
try {
|
|
38230
39213
|
authStatus = await this.readAgentAuthIdentity();
|
|
38231
39214
|
} catch (error62) {
|
|
39215
|
+
if (isAccountReadAuthFailureError(error62)) {
|
|
39216
|
+
logger.log("The account read found that the agent needs a login", { error: String(error62) });
|
|
39217
|
+
this.lastReadAccount = null;
|
|
39218
|
+
await this.setAuthStatus(fromAccount(null));
|
|
39219
|
+
return;
|
|
39220
|
+
}
|
|
38232
39221
|
logger.log("Cannot determine auth status", { error: String(error62) });
|
|
38233
39222
|
return;
|
|
38234
39223
|
}
|
|
@@ -38251,6 +39240,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
|
|
|
38251
39240
|
return gatewayStatus(modelProvider);
|
|
38252
39241
|
}
|
|
38253
39242
|
const accountResponse = await this.runWithProcessCheck(() => this.codexAcpClient.getAccount());
|
|
39243
|
+
this.lastReadAccount = accountResponse.account;
|
|
38254
39244
|
return fromAccount(accountResponse.account);
|
|
38255
39245
|
}
|
|
38256
39246
|
/**
|
|
@@ -38287,6 +39277,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
|
|
|
38287
39277
|
* invalidate; only a gateway logout or a provider change does.
|
|
38288
39278
|
*/
|
|
38289
39279
|
async applyAccountUpdated(notification) {
|
|
39280
|
+
this.lastReadAccount = accountFromUpdated(notification, this.lastReadAccount);
|
|
38290
39281
|
try {
|
|
38291
39282
|
if (this.codexAcpClient.getAuthGatewayProviderName() !== null) {
|
|
38292
39283
|
return;
|
|
@@ -38316,6 +39307,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
|
|
|
38316
39307
|
* so no payload can equal it.
|
|
38317
39308
|
*/
|
|
38318
39309
|
async setAuthStatus(next) {
|
|
39310
|
+
this.authStatusVersion += 1;
|
|
38319
39311
|
if (sameAuthStatus(this.currentAuthStatus, next)) {
|
|
38320
39312
|
return;
|
|
38321
39313
|
}
|
|
@@ -38783,18 +39775,30 @@ Check ${configPath} and project .codex directories, especially their config.toml
|
|
|
38783
39775
|
}
|
|
38784
39776
|
async getOrCreateSessionWithHistory(request) {
|
|
38785
39777
|
const requestedSessionGeneration = this.beginSessionOpen(request.sessionId);
|
|
38786
|
-
|
|
39778
|
+
const loadStartedAt = performance.now();
|
|
39779
|
+
const graceLeftMs = () => ACCOUNT_READ_LOAD_GRACE_MS - (performance.now() - loadStartedAt);
|
|
39780
|
+
const authStatusVersionAtStart = this.authStatusVersion;
|
|
39781
|
+
const authorization = this.defaultAuthRequest ? Promise.resolve(await this.checkAuthorization()) : this.checkAuthorization();
|
|
39782
|
+
authorization.catch(() => {
|
|
39783
|
+
});
|
|
38787
39784
|
const requestedMcpServers = request.mcpServers ?? [];
|
|
38788
39785
|
const mcpServerStartupVersion = requestedMcpServers.length > 0 ? this.codexAcpClient.getMcpServerStartupVersion() : null;
|
|
38789
39786
|
logger.log(`Load existing session: ${request.sessionId}...`);
|
|
38790
39787
|
let subscribed = false;
|
|
38791
39788
|
let sessionMetadata;
|
|
39789
|
+
let knownAccount;
|
|
38792
39790
|
try {
|
|
38793
|
-
|
|
38794
|
-
|
|
38795
|
-
|
|
38796
|
-
|
|
38797
|
-
|
|
39791
|
+
try {
|
|
39792
|
+
sessionMetadata = await this.runWithProcessCheck(
|
|
39793
|
+
() => this.codexAcpClient.loadSession(request, () => {
|
|
39794
|
+
subscribed = true;
|
|
39795
|
+
})
|
|
39796
|
+
);
|
|
39797
|
+
} catch (err) {
|
|
39798
|
+
await settledWithin(authorization, graceLeftMs());
|
|
39799
|
+
throw err;
|
|
39800
|
+
}
|
|
39801
|
+
knownAccount = await settledWithin(authorization, graceLeftMs());
|
|
38798
39802
|
} catch (err) {
|
|
38799
39803
|
if (subscribed) {
|
|
38800
39804
|
await this.cleanupStaleSessionOpen(request.sessionId, requestedSessionGeneration);
|
|
@@ -38805,7 +39809,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
|
|
|
38805
39809
|
const authProvider = sessionMetadata.modelProvider ?? this.codexAcpClient.getModelProvider();
|
|
38806
39810
|
let authState;
|
|
38807
39811
|
try {
|
|
38808
|
-
authState = await this.getAuthStateForProvider(authProvider);
|
|
39812
|
+
authState = await this.getAuthStateForProvider(authProvider, knownAccount === "pending" ? "unavailable" : knownAccount);
|
|
38809
39813
|
} catch (err) {
|
|
38810
39814
|
if (subscribed) {
|
|
38811
39815
|
await this.cleanupStaleSessionOpen(request.sessionId, requestedSessionGeneration);
|
|
@@ -38862,13 +39866,15 @@ Check ${configPath} and project .codex directories, especially their config.toml
|
|
|
38862
39866
|
() => sessionState.sessionTitleSource
|
|
38863
39867
|
);
|
|
38864
39868
|
this.installSessionState(sessionState);
|
|
39869
|
+
if (knownAccount === "pending") {
|
|
39870
|
+
this.applyLateAccountRead(sessionState, authorization, authStatusVersionAtStart);
|
|
39871
|
+
}
|
|
38865
39872
|
subscribed = false;
|
|
38866
39873
|
if (requestedMcpServers.length > 0 && mcpServerStartupVersion !== null) {
|
|
38867
|
-
this.
|
|
38868
|
-
|
|
38869
|
-
|
|
38870
|
-
);
|
|
38871
|
-
this.publishMcpStartupStatusAsync(sessionId);
|
|
39874
|
+
this.mcpSessionStartup.begin(sessionId, requestedMcpServers, mcpServerStartupVersion, {
|
|
39875
|
+
publish: true,
|
|
39876
|
+
skippedServers: sessionMetadata.skippedMcpServers
|
|
39877
|
+
});
|
|
38872
39878
|
}
|
|
38873
39879
|
await this.publishAvailableCommands(sessionState, requestedSessionGeneration);
|
|
38874
39880
|
await this.publishCurrentGoalBestEffort(sessionState, requestedSessionGeneration, true);
|
|
@@ -39017,7 +40023,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
|
|
|
39017
40023
|
}
|
|
39018
40024
|
}
|
|
39019
40025
|
async publishThreadHistoryTitle(session, sessionState, thread, firstItems) {
|
|
39020
|
-
const explicitTitle =
|
|
40026
|
+
const explicitTitle = normalizeSessionTitle(thread.name);
|
|
39021
40027
|
if (explicitTitle) {
|
|
39022
40028
|
sessionState.sessionTitle = explicitTitle;
|
|
39023
40029
|
sessionState.sessionTitleSource = "explicit";
|
|
@@ -39028,13 +40034,13 @@ Check ${configPath} and project .codex directories, especially their config.toml
|
|
|
39028
40034
|
});
|
|
39029
40035
|
return;
|
|
39030
40036
|
}
|
|
39031
|
-
const historyTitle = this.findFirstUserMessageTitle(firstItems) ??
|
|
40037
|
+
const historyTitle = this.findFirstUserMessageTitle(firstItems) ?? normalizeSessionTitle(thread.preview);
|
|
39032
40038
|
await this.publishFallbackSessionTitle(sessionState, historyTitle);
|
|
39033
40039
|
}
|
|
39034
40040
|
findFirstUserMessageTitle(items) {
|
|
39035
40041
|
for (const item of items) {
|
|
39036
40042
|
if (item.type !== "userMessage") continue;
|
|
39037
|
-
const title =
|
|
40043
|
+
const title = normalizeSessionTitle(item.content.filter((input2) => input2.type === "text").map((input2) => input2.text).join(" "));
|
|
39038
40044
|
if (title) return title;
|
|
39039
40045
|
}
|
|
39040
40046
|
return null;
|
|
@@ -39081,11 +40087,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
|
|
|
39081
40087
|
}
|
|
39082
40088
|
}
|
|
39083
40089
|
createPromptFallbackTitle(prompt) {
|
|
39084
|
-
return
|
|
39085
|
-
}
|
|
39086
|
-
normalizeSessionTitle(title) {
|
|
39087
|
-
const normalized = title?.replace(/\s+/g, " ").trim() ?? "";
|
|
39088
|
-
return normalized.length > 0 ? normalized : null;
|
|
40090
|
+
return normalizeSessionTitle(prompt.filter((block) => block.type === "text").map((block) => block.text).join(" "));
|
|
39089
40091
|
}
|
|
39090
40092
|
async createHistoryUpdates(item, sessionState) {
|
|
39091
40093
|
const renderer = new AcpToolCallRenderer(sessionState.clientCapabilities);
|
|
@@ -39240,102 +40242,6 @@ Check ${configPath} and project .codex directories, especially their config.toml
|
|
|
39240
40242
|
logger.log("Skipping MCP server recovery for load/resume without explicit mcpServers");
|
|
39241
40243
|
return [];
|
|
39242
40244
|
}
|
|
39243
|
-
publishMcpStartupStatusAsync(sessionId) {
|
|
39244
|
-
void this.doPublishMcpStartupStatus(sessionId);
|
|
39245
|
-
}
|
|
39246
|
-
createPendingMcpStartupSession(mcpServers, afterVersion) {
|
|
39247
|
-
const requestedServers = new Set(getRequestedMcpServerNames(mcpServers));
|
|
39248
|
-
return {
|
|
39249
|
-
requestedServers,
|
|
39250
|
-
startup: this.runWithProcessCheck(
|
|
39251
|
-
() => this.codexAcpClient.awaitMcpServerStartup(Array.from(requestedServers), afterVersion)
|
|
39252
|
-
)
|
|
39253
|
-
};
|
|
39254
|
-
}
|
|
39255
|
-
async doPublishMcpStartupStatus(sessionId) {
|
|
39256
|
-
const pendingStartup = this.pendingMcpStartupSessions.get(sessionId);
|
|
39257
|
-
if (!pendingStartup) {
|
|
39258
|
-
return;
|
|
39259
|
-
}
|
|
39260
|
-
try {
|
|
39261
|
-
const mcpStartup = await pendingStartup.startup;
|
|
39262
|
-
if (!this.sessions.has(sessionId) || this.sessionIsClosing(sessionId) || this.pendingMcpStartupSessions.get(sessionId) !== pendingStartup) {
|
|
39263
|
-
return;
|
|
39264
|
-
}
|
|
39265
|
-
await this.publishMcpStartupStatus(sessionId, mcpStartup, pendingStartup.requestedServers);
|
|
39266
|
-
} catch (err) {
|
|
39267
|
-
logger.error(`Failed to publish MCP startup status for session ${sessionId}`, err);
|
|
39268
|
-
} finally {
|
|
39269
|
-
if (this.pendingMcpStartupSessions.get(sessionId) === pendingStartup) {
|
|
39270
|
-
this.pendingMcpStartupSessions.delete(sessionId);
|
|
39271
|
-
}
|
|
39272
|
-
}
|
|
39273
|
-
}
|
|
39274
|
-
async publishMcpStartupStatus(sessionId, mcpStartup, requestedServers) {
|
|
39275
|
-
const filteredStartup = requestedServers ? {
|
|
39276
|
-
ready: mcpStartup.ready.filter((server) => requestedServers.has(server)),
|
|
39277
|
-
failed: mcpStartup.failed.filter((server) => requestedServers.has(server.server)),
|
|
39278
|
-
cancelled: mcpStartup.cancelled.filter((server) => requestedServers.has(server))
|
|
39279
|
-
} : mcpStartup;
|
|
39280
|
-
const failuresAfterOauth = [];
|
|
39281
|
-
const readyAfterOauth = [...filteredStartup.ready];
|
|
39282
|
-
for (const failure3 of filteredStartup.failed) {
|
|
39283
|
-
if (failure3.failureReason !== "reauthenticationRequired" || !clientSupportsUrlElicitation(this.clientCapabilities)) {
|
|
39284
|
-
failuresAfterOauth.push(failure3);
|
|
39285
|
-
continue;
|
|
39286
|
-
}
|
|
39287
|
-
try {
|
|
39288
|
-
const authenticated = await this.authenticateMcpServer(sessionId, failure3.server);
|
|
39289
|
-
if (authenticated) {
|
|
39290
|
-
readyAfterOauth.push(failure3.server);
|
|
39291
|
-
} else {
|
|
39292
|
-
failuresAfterOauth.push(failure3);
|
|
39293
|
-
}
|
|
39294
|
-
} catch (error62) {
|
|
39295
|
-
logger.error(`Failed to authenticate MCP server ${failure3.server}`, error62);
|
|
39296
|
-
failuresAfterOauth.push(failure3);
|
|
39297
|
-
}
|
|
39298
|
-
}
|
|
39299
|
-
const renderer = new AcpToolCallRenderer(this.capabilities);
|
|
39300
|
-
for (const facts4 of McpStartupReporter.failures({
|
|
39301
|
-
...filteredStartup,
|
|
39302
|
-
ready: readyAfterOauth,
|
|
39303
|
-
failed: failuresAfterOauth
|
|
39304
|
-
})) {
|
|
39305
|
-
await this.connection.notify(methods.client.session.update, {
|
|
39306
|
-
sessionId,
|
|
39307
|
-
update: renderer.render(facts4)
|
|
39308
|
-
});
|
|
39309
|
-
}
|
|
39310
|
-
}
|
|
39311
|
-
async authenticateMcpServer(sessionId, serverName) {
|
|
39312
|
-
const elicitationId = `mcp-oauth-${randomUUID2()}`;
|
|
39313
|
-
const completed = this.codexAcpClient.awaitMcpServerOauthLoginCompleted(serverName, sessionId);
|
|
39314
|
-
const login2 = await this.codexAcpClient.mcpServerOauthLogin({
|
|
39315
|
-
name: serverName,
|
|
39316
|
-
threadId: sessionId
|
|
39317
|
-
});
|
|
39318
|
-
const elicitation = Promise.resolve(this.connection.request(
|
|
39319
|
-
methods.client.elicitation.create,
|
|
39320
|
-
{
|
|
39321
|
-
mode: "url",
|
|
39322
|
-
sessionId,
|
|
39323
|
-
message: `Authenticate with MCP server ${serverName}`,
|
|
39324
|
-
url: login2.authorizationUrl,
|
|
39325
|
-
elicitationId
|
|
39326
|
-
}
|
|
39327
|
-
));
|
|
39328
|
-
const first = await Promise.race([
|
|
39329
|
-
completed.then((result2) => ({ type: "completed", result: result2 })),
|
|
39330
|
-
elicitation.then((response) => ({ type: "elicitation", response }))
|
|
39331
|
-
]);
|
|
39332
|
-
if (first.type === "elicitation" && !CreateElicitationResponse.isAccept(first.response)) {
|
|
39333
|
-
return false;
|
|
39334
|
-
}
|
|
39335
|
-
const result = first.type === "completed" ? first.result : await completed;
|
|
39336
|
-
await this.connection.notify(methods.client.elicitation.complete, { elicitationId });
|
|
39337
|
-
return result.success;
|
|
39338
|
-
}
|
|
39339
40245
|
trackActivePrompt(sessionId) {
|
|
39340
40246
|
let resolveCompletion = () => {
|
|
39341
40247
|
};
|
|
@@ -39622,6 +40528,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
|
|
|
39622
40528
|
return cancelledPromptResponse();
|
|
39623
40529
|
}
|
|
39624
40530
|
const commandPromise = this.availableCommands.tryHandleCommand(params.prompt, sessionState, {
|
|
40531
|
+
signal: activePrompt.signal,
|
|
39625
40532
|
onTurnStartPending: () => {
|
|
39626
40533
|
sessionState.lastTokenUsage = null;
|
|
39627
40534
|
ensurePendingTurnStart();
|
|
@@ -40058,41 +40965,6 @@ ${stderr}` : "";
|
|
|
40058
40965
|
await this.interruptSessionTurn(sessionState, "Cancel", false);
|
|
40059
40966
|
}
|
|
40060
40967
|
};
|
|
40061
|
-
function getRequestedMcpServerNames(mcpServers) {
|
|
40062
|
-
return Array.from(new Set(mcpServers.map((server) => sanitizeMcpServerName(server.name))));
|
|
40063
|
-
}
|
|
40064
|
-
var MCP_STARTUP_AWAIT_TIMEOUT_META_KEY = "mcpStartupAwaitTimeoutMs";
|
|
40065
|
-
function parseMcpStartupAwaitTimeoutMs(meta3) {
|
|
40066
|
-
const value = meta3?.[MCP_STARTUP_AWAIT_TIMEOUT_META_KEY];
|
|
40067
|
-
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
40068
|
-
}
|
|
40069
|
-
function raceMcpStartupTimeout(startup, timeoutMs) {
|
|
40070
|
-
return new Promise((resolve, reject) => {
|
|
40071
|
-
let settled = false;
|
|
40072
|
-
const timer = setTimeout(() => {
|
|
40073
|
-
if (!settled) {
|
|
40074
|
-
settled = true;
|
|
40075
|
-
resolve();
|
|
40076
|
-
}
|
|
40077
|
-
}, timeoutMs);
|
|
40078
|
-
startup.then(
|
|
40079
|
-
() => {
|
|
40080
|
-
if (!settled) {
|
|
40081
|
-
settled = true;
|
|
40082
|
-
clearTimeout(timer);
|
|
40083
|
-
resolve();
|
|
40084
|
-
}
|
|
40085
|
-
},
|
|
40086
|
-
(err) => {
|
|
40087
|
-
if (!settled) {
|
|
40088
|
-
settled = true;
|
|
40089
|
-
clearTimeout(timer);
|
|
40090
|
-
reject(err);
|
|
40091
|
-
}
|
|
40092
|
-
}
|
|
40093
|
-
);
|
|
40094
|
-
});
|
|
40095
|
-
}
|
|
40096
40968
|
var SessionClosedDuringLoadError = class extends Error {
|
|
40097
40969
|
constructor() {
|
|
40098
40970
|
super("The session closed during the history load");
|