Inject agent-control MCP via codex wrapper
This commit is contained in:
@@ -1,8 +1,13 @@
|
|||||||
import { describe, expect, test } from "vitest";
|
import { describe, expect, test } from "vitest";
|
||||||
|
import { createServer as createHTTPServer } from "http";
|
||||||
|
import { randomUUID } from "node:crypto";
|
||||||
import { mkdtempSync, rmSync, promises as fs } from "node:fs";
|
import { mkdtempSync, rmSync, promises as fs } from "node:fs";
|
||||||
import type { Dirent } from "node:fs";
|
import type { Dirent } from "node:fs";
|
||||||
import os from "node:os";
|
import os from "node:os";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
|
import express from "express";
|
||||||
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
||||||
|
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
|
||||||
|
|
||||||
import { CodexAgentClient, isSyntheticRolloutUserMessage } from "./codex-agent.js";
|
import { CodexAgentClient, isSyntheticRolloutUserMessage } from "./codex-agent.js";
|
||||||
import {
|
import {
|
||||||
@@ -23,6 +28,9 @@ import type {
|
|||||||
AgentPersistenceHandle,
|
AgentPersistenceHandle,
|
||||||
AgentTimelineItem,
|
AgentTimelineItem,
|
||||||
} from "../agent-sdk-types.js";
|
} from "../agent-sdk-types.js";
|
||||||
|
import { AgentManager } from "../agent-manager.js";
|
||||||
|
import { AgentRegistry } from "../agent-registry.js";
|
||||||
|
import { createAgentMcpServer } from "../mcp-server.js";
|
||||||
|
|
||||||
function tmpCwd(): string {
|
function tmpCwd(): string {
|
||||||
return mkdtempSync(path.join(os.tmpdir(), "codex-agent-e2e-"));
|
return mkdtempSync(path.join(os.tmpdir(), "codex-agent-e2e-"));
|
||||||
@@ -51,6 +59,130 @@ function log(message: string): void {
|
|||||||
|
|
||||||
type ToolCallItem = Extract<AgentTimelineItem, { type: "tool_call" }>;
|
type ToolCallItem = Extract<AgentTimelineItem, { type: "tool_call" }>;
|
||||||
|
|
||||||
|
type AgentMcpServerHandle = {
|
||||||
|
url: string;
|
||||||
|
close: () => Promise<void>;
|
||||||
|
};
|
||||||
|
|
||||||
|
async function startAgentMcpServer(): Promise<AgentMcpServerHandle> {
|
||||||
|
const app = express();
|
||||||
|
app.use(express.json());
|
||||||
|
const httpServer = createHTTPServer(app);
|
||||||
|
|
||||||
|
const registryDir = await fs.mkdtemp(path.join(os.tmpdir(), "agent-mcp-registry-"));
|
||||||
|
const registryPath = path.join(registryDir, "agents.json");
|
||||||
|
const agentRegistry = new AgentRegistry(registryPath);
|
||||||
|
const agentManager = new AgentManager({
|
||||||
|
clients: {},
|
||||||
|
registry: agentRegistry,
|
||||||
|
});
|
||||||
|
|
||||||
|
let allowedHosts: string[] | undefined;
|
||||||
|
const agentMcpTransports = new Map<string, StreamableHTTPServerTransport>();
|
||||||
|
|
||||||
|
const createAgentMcpTransport = async (callerAgentId?: string) => {
|
||||||
|
const agentMcpServer = await createAgentMcpServer({
|
||||||
|
agentManager,
|
||||||
|
agentRegistry,
|
||||||
|
callerAgentId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const transport = new StreamableHTTPServerTransport({
|
||||||
|
sessionIdGenerator: () => randomUUID(),
|
||||||
|
onsessioninitialized: (sessionId) => {
|
||||||
|
agentMcpTransports.set(sessionId, transport);
|
||||||
|
log(`Agent MCP session initialized: ${sessionId}`);
|
||||||
|
},
|
||||||
|
onsessionclosed: (sessionId) => {
|
||||||
|
agentMcpTransports.delete(sessionId);
|
||||||
|
log(`Agent MCP session closed: ${sessionId}`);
|
||||||
|
},
|
||||||
|
enableDnsRebindingProtection: true,
|
||||||
|
...(allowedHosts ? { allowedHosts } : {}),
|
||||||
|
});
|
||||||
|
|
||||||
|
transport.onclose = () => {
|
||||||
|
if (transport.sessionId) {
|
||||||
|
agentMcpTransports.delete(transport.sessionId);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
transport.onerror = (error) => {
|
||||||
|
console.error("[Agent MCP] Transport error:", error);
|
||||||
|
};
|
||||||
|
|
||||||
|
await agentMcpServer.connect(transport);
|
||||||
|
return transport;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAgentMcpRequest: express.RequestHandler = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const sessionId = req.header("mcp-session-id");
|
||||||
|
let transport = sessionId ? agentMcpTransports.get(sessionId) : undefined;
|
||||||
|
|
||||||
|
if (!transport) {
|
||||||
|
if (req.method !== "POST") {
|
||||||
|
res.status(400).json({
|
||||||
|
jsonrpc: "2.0",
|
||||||
|
error: { code: -32000, message: "Missing or invalid MCP session" },
|
||||||
|
id: null,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!isInitializeRequest(req.body)) {
|
||||||
|
res.status(400).json({
|
||||||
|
jsonrpc: "2.0",
|
||||||
|
error: { code: -32000, message: "Initialization request expected" },
|
||||||
|
id: null,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
log("Agent MCP initialize request received");
|
||||||
|
const callerAgentIdRaw = req.query.callerAgentId;
|
||||||
|
const callerAgentId =
|
||||||
|
typeof callerAgentIdRaw === "string"
|
||||||
|
? callerAgentIdRaw
|
||||||
|
: Array.isArray(callerAgentIdRaw)
|
||||||
|
? callerAgentIdRaw[0]
|
||||||
|
: undefined;
|
||||||
|
transport = await createAgentMcpTransport(callerAgentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
await transport.handleRequest(req as any, res as any, req.body);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("[Agent MCP] Failed to handle request:", error);
|
||||||
|
if (!res.headersSent) {
|
||||||
|
res.status(500).json({
|
||||||
|
jsonrpc: "2.0",
|
||||||
|
error: { code: -32603, message: "Internal MCP server error" },
|
||||||
|
id: null,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
app.post("/mcp/agents", handleAgentMcpRequest);
|
||||||
|
app.get("/mcp/agents", handleAgentMcpRequest);
|
||||||
|
app.delete("/mcp/agents", handleAgentMcpRequest);
|
||||||
|
|
||||||
|
const port = await new Promise<number>((resolve) => {
|
||||||
|
httpServer.listen(0, () => {
|
||||||
|
const address = httpServer.address();
|
||||||
|
resolve(typeof address === "object" && address ? address.port : 0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
allowedHosts = [`127.0.0.1:${port}`, `localhost:${port}`];
|
||||||
|
const url = `http://127.0.0.1:${port}/mcp/agents`;
|
||||||
|
|
||||||
|
return {
|
||||||
|
url,
|
||||||
|
close: async () => {
|
||||||
|
await new Promise<void>((resolve) => httpServer.close(() => resolve()));
|
||||||
|
rmSync(registryDir, { recursive: true, force: true });
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function commandTextFromInput(input: unknown): string | null {
|
function commandTextFromInput(input: unknown): string | null {
|
||||||
if (!input || typeof input !== "object") {
|
if (!input || typeof input !== "object") {
|
||||||
return null;
|
return null;
|
||||||
@@ -386,7 +518,7 @@ describe("CodexAgentClient (SDK integration)", () => {
|
|||||||
const cwd = tmpCwd();
|
const cwd = tmpCwd();
|
||||||
const restoreSessionDir = useTempCodexSessionDir();
|
const restoreSessionDir = useTempCodexSessionDir();
|
||||||
const client = new CodexAgentClient();
|
const client = new CodexAgentClient();
|
||||||
const config: AgentSessionConfig = { provider: "codex", cwd };
|
const config: AgentSessionConfig = { provider: "codex", cwd, modeId: "full-access" };
|
||||||
let session: Awaited<ReturnType<typeof client.createSession>> | null = null;
|
let session: Awaited<ReturnType<typeof client.createSession>> | null = null;
|
||||||
try {
|
try {
|
||||||
session = await client.createSession(config);
|
session = await client.createSession(config);
|
||||||
@@ -699,6 +831,74 @@ describe("CodexAgentClient (SDK integration)", () => {
|
|||||||
},
|
},
|
||||||
180_000
|
180_000
|
||||||
);
|
);
|
||||||
|
|
||||||
|
test(
|
||||||
|
"sees agent-control MCP tools (list_agents) via codex",
|
||||||
|
async () => {
|
||||||
|
const cwd = tmpCwd();
|
||||||
|
const restoreSessionDir = useTempCodexSessionDir();
|
||||||
|
const mcpServer = await startAgentMcpServer();
|
||||||
|
const client = new CodexAgentClient();
|
||||||
|
const config: AgentSessionConfig = {
|
||||||
|
provider: "codex",
|
||||||
|
cwd,
|
||||||
|
modeId: "full-access",
|
||||||
|
extra: { codex: { agentControlMcpUrl: mcpServer.url } },
|
||||||
|
};
|
||||||
|
let session: Awaited<ReturnType<typeof client.createSession>> | null = null;
|
||||||
|
try {
|
||||||
|
session = await client.createSession(config);
|
||||||
|
log(`Agent MCP URL: ${mcpServer.url}`);
|
||||||
|
|
||||||
|
const prompt = [
|
||||||
|
"Use the MCP tool agent_control.list_agents and report the agent IDs you receive.",
|
||||||
|
"If the tool is unavailable, say exactly: MCP tool list_agents unavailable.",
|
||||||
|
"Then stop.",
|
||||||
|
].join("\n");
|
||||||
|
|
||||||
|
const toolCalls: ToolCallItem[] = [];
|
||||||
|
let finalText = "";
|
||||||
|
const eventLog: string[] = [];
|
||||||
|
|
||||||
|
for await (const event of session.stream(prompt)) {
|
||||||
|
eventLog.push(event.type);
|
||||||
|
if (event.type === "timeline" && event.provider === "codex") {
|
||||||
|
if (event.item.type === "tool_call") {
|
||||||
|
toolCalls.push(event.item);
|
||||||
|
} else if (event.item.type === "assistant_message") {
|
||||||
|
finalText += `${event.item.text}\n`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.type === "turn_failed") {
|
||||||
|
console.info("[CodexAgentTest] Turn failed:", event.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.type === "turn_completed" || event.type === "turn_failed") {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const listAgentsCall = toolCalls.find((call) => call.tool === "list_agents");
|
||||||
|
if (!listAgentsCall) {
|
||||||
|
console.info(
|
||||||
|
"[CodexAgentTest] MCP tool calls:",
|
||||||
|
toolCalls.map((call) => `${call.server}.${call.tool}`)
|
||||||
|
);
|
||||||
|
console.info("[CodexAgentTest] Final text:", finalText.trim());
|
||||||
|
console.info("[CodexAgentTest] Event types:", eventLog);
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(listAgentsCall).toBeDefined();
|
||||||
|
} finally {
|
||||||
|
await session?.close();
|
||||||
|
await mcpServer.close();
|
||||||
|
rmSync(cwd, { recursive: true, force: true });
|
||||||
|
restoreSessionDir();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
240_000
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("isSyntheticRolloutUserMessage", () => {
|
describe("isSyntheticRolloutUserMessage", () => {
|
||||||
|
|||||||
@@ -42,6 +42,20 @@ import type {
|
|||||||
|
|
||||||
type CodexAgentConfig = AgentSessionConfig & { provider: "codex" };
|
type CodexAgentConfig = AgentSessionConfig & { provider: "codex" };
|
||||||
|
|
||||||
|
type CodexExtraConfig = {
|
||||||
|
agentControlMcpUrl?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type CodexWrapperInfo = {
|
||||||
|
path: string;
|
||||||
|
dir: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type CodexSessionBootstrap = {
|
||||||
|
codex: Codex;
|
||||||
|
wrapper?: CodexWrapperInfo;
|
||||||
|
};
|
||||||
|
|
||||||
const CODEX_CAPABILITIES: AgentCapabilityFlags = {
|
const CODEX_CAPABILITIES: AgentCapabilityFlags = {
|
||||||
supportsStreaming: true,
|
supportsStreaming: true,
|
||||||
supportsSessionPersistence: true,
|
supportsSessionPersistence: true,
|
||||||
@@ -252,10 +266,77 @@ function detectSystemCodexPath(): string | undefined {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resolveAgentControlMcpUrl(config: CodexAgentConfig): string | null {
|
||||||
|
const extras = config.extra?.codex as CodexExtraConfig | undefined;
|
||||||
|
if (!extras || typeof extras !== "object") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const baseUrl = extras.agentControlMcpUrl;
|
||||||
|
if (!baseUrl || typeof baseUrl !== "string") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return config.parentAgentId
|
||||||
|
? appendCallerAgentId(baseUrl, config.parentAgentId)
|
||||||
|
: baseUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createCodexWrapperScript(
|
||||||
|
realCodexPath: string,
|
||||||
|
mcpUrl: string
|
||||||
|
): Promise<CodexWrapperInfo> {
|
||||||
|
const templatePath = await resolveCodexWrapperTemplatePath();
|
||||||
|
const template = await fs.readFile(templatePath, "utf8");
|
||||||
|
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "codex-wrapper-"));
|
||||||
|
const wrapperPath = path.join(dir, "codex-wrapper");
|
||||||
|
const content = template
|
||||||
|
.replace("__REAL_CODEX__", escapeBashValue(realCodexPath))
|
||||||
|
.replace("__MCP_URL__", escapeBashValue(mcpUrl));
|
||||||
|
await fs.writeFile(wrapperPath, content, { mode: 0o700 });
|
||||||
|
await fs.chmod(wrapperPath, 0o700);
|
||||||
|
return { path: wrapperPath, dir };
|
||||||
|
}
|
||||||
|
|
||||||
|
async function resolveCodexWrapperTemplatePath(): Promise<string> {
|
||||||
|
let dir = process.cwd();
|
||||||
|
for (let i = 0; i < 8; i += 1) {
|
||||||
|
const candidate = path.join(dir, "scripts", "codex-mcp-wrapper-template.sh");
|
||||||
|
if (await fileExists(candidate)) {
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
const parent = path.dirname(dir);
|
||||||
|
if (parent === dir) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
dir = parent;
|
||||||
|
}
|
||||||
|
throw new Error(
|
||||||
|
"Unable to locate scripts/codex-mcp-wrapper-template.sh for Codex MCP wrapper"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function appendCallerAgentId(url: string, agentId: string): string {
|
||||||
|
try {
|
||||||
|
const parsed = new URL(url);
|
||||||
|
parsed.searchParams.set("callerAgentId", agentId);
|
||||||
|
return parsed.toString();
|
||||||
|
} catch {
|
||||||
|
const separator = url.includes("?") ? "&" : "?";
|
||||||
|
return `${url}${separator}callerAgentId=${encodeURIComponent(agentId)}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function escapeBashValue(value: string): string {
|
||||||
|
return value
|
||||||
|
.replace(/\\/g, "\\\\")
|
||||||
|
.replace(/"/g, '\\"')
|
||||||
|
.replace(/\$/g, "\\$");
|
||||||
|
}
|
||||||
|
|
||||||
export class CodexAgentClient implements AgentClient {
|
export class CodexAgentClient implements AgentClient {
|
||||||
readonly provider = "codex" as const;
|
readonly provider = "codex" as const;
|
||||||
readonly capabilities = CODEX_CAPABILITIES;
|
readonly capabilities = CODEX_CAPABILITIES;
|
||||||
private readonly codex: Codex;
|
private readonly baseOptions: CodexOptions;
|
||||||
|
private readonly baseCodexPath: string | null;
|
||||||
|
|
||||||
constructor(options?: CodexOptions) {
|
constructor(options?: CodexOptions) {
|
||||||
const codexOptions = { ...options };
|
const codexOptions = { ...options };
|
||||||
@@ -273,12 +354,19 @@ export class CodexAgentClient implements AgentClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.codex = new Codex(codexOptions);
|
this.baseOptions = codexOptions;
|
||||||
|
this.baseCodexPath = codexOptions.codexPathOverride ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async createSession(config: AgentSessionConfig): Promise<AgentSession> {
|
async createSession(config: AgentSessionConfig): Promise<AgentSession> {
|
||||||
const codexConfig = this.assertConfig(config);
|
const codexConfig = this.assertConfig(config);
|
||||||
return CodexAgentSession.create(this.codex, codexConfig);
|
const bootstrap = await this.createCodexForSession(codexConfig);
|
||||||
|
return CodexAgentSession.create(
|
||||||
|
bootstrap.codex,
|
||||||
|
codexConfig,
|
||||||
|
undefined,
|
||||||
|
bootstrap.wrapper
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async resumeSession(
|
async resumeSession(
|
||||||
@@ -294,7 +382,13 @@ export class CodexAgentClient implements AgentClient {
|
|||||||
}
|
}
|
||||||
const mergedConfig = { ...merged, provider: "codex" } as AgentSessionConfig;
|
const mergedConfig = { ...merged, provider: "codex" } as AgentSessionConfig;
|
||||||
const codexConfig = this.assertConfig(mergedConfig);
|
const codexConfig = this.assertConfig(mergedConfig);
|
||||||
return CodexAgentSession.create(this.codex, codexConfig, handle);
|
const bootstrap = await this.createCodexForSession(codexConfig);
|
||||||
|
return CodexAgentSession.create(
|
||||||
|
bootstrap.codex,
|
||||||
|
codexConfig,
|
||||||
|
handle,
|
||||||
|
bootstrap.wrapper
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async listPersistedAgents(
|
async listPersistedAgents(
|
||||||
@@ -334,15 +428,43 @@ export class CodexAgentClient implements AgentClient {
|
|||||||
}
|
}
|
||||||
return config as CodexAgentConfig;
|
return config as CodexAgentConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async createCodexForSession(
|
||||||
|
config: CodexAgentConfig
|
||||||
|
): Promise<CodexSessionBootstrap> {
|
||||||
|
const mcpUrl = resolveAgentControlMcpUrl(config);
|
||||||
|
if (!mcpUrl || !this.baseCodexPath) {
|
||||||
|
if (mcpUrl && !this.baseCodexPath) {
|
||||||
|
console.warn("[Codex] MCP wrapper skipped (no codex binary path available)");
|
||||||
|
}
|
||||||
|
return { codex: new Codex(this.baseOptions) };
|
||||||
|
}
|
||||||
|
|
||||||
|
const wrapper = await createCodexWrapperScript(this.baseCodexPath, mcpUrl);
|
||||||
|
const env = {
|
||||||
|
...process.env,
|
||||||
|
...(this.baseOptions.env ?? {}),
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
codex: new Codex({
|
||||||
|
...this.baseOptions,
|
||||||
|
codexPathOverride: wrapper.path,
|
||||||
|
env,
|
||||||
|
}),
|
||||||
|
wrapper,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class CodexAgentSession implements AgentSession {
|
class CodexAgentSession implements AgentSession {
|
||||||
static async create(
|
static async create(
|
||||||
codex: Codex,
|
codex: Codex,
|
||||||
config: CodexAgentConfig,
|
config: CodexAgentConfig,
|
||||||
handle?: AgentPersistenceHandle
|
handle?: AgentPersistenceHandle,
|
||||||
|
wrapper?: CodexWrapperInfo
|
||||||
): Promise<CodexAgentSession> {
|
): Promise<CodexAgentSession> {
|
||||||
const session = new CodexAgentSession(codex, config, handle);
|
const session = new CodexAgentSession(codex, config, handle, wrapper);
|
||||||
if (handle) {
|
if (handle) {
|
||||||
await session.loadReplayHistory(handle);
|
await session.loadReplayHistory(handle);
|
||||||
}
|
}
|
||||||
@@ -363,6 +485,7 @@ class CodexAgentSession implements AgentSession {
|
|||||||
private availableModes: AgentMode[] = CODEX_MODES;
|
private availableModes: AgentMode[] = CODEX_MODES;
|
||||||
private readonly codexSessionDir: string | null;
|
private readonly codexSessionDir: string | null;
|
||||||
private rolloutPath: string | null;
|
private rolloutPath: string | null;
|
||||||
|
private readonly wrapper: CodexWrapperInfo | null;
|
||||||
private historyEvents: AgentStreamEvent[] = [];
|
private historyEvents: AgentStreamEvent[] = [];
|
||||||
private pendingPermissions = new Map<string, AgentPermissionRequest>();
|
private pendingPermissions = new Map<string, AgentPermissionRequest>();
|
||||||
private cancelCurrentTurn: (() => void) | null = null;
|
private cancelCurrentTurn: (() => void) | null = null;
|
||||||
@@ -371,10 +494,12 @@ class CodexAgentSession implements AgentSession {
|
|||||||
constructor(
|
constructor(
|
||||||
codex: Codex,
|
codex: Codex,
|
||||||
config: CodexAgentConfig,
|
config: CodexAgentConfig,
|
||||||
handle?: AgentPersistenceHandle
|
handle?: AgentPersistenceHandle,
|
||||||
|
wrapper?: CodexWrapperInfo
|
||||||
) {
|
) {
|
||||||
this.codex = codex;
|
this.codex = codex;
|
||||||
this.config = { ...config };
|
this.config = { ...config };
|
||||||
|
this.wrapper = wrapper ?? null;
|
||||||
|
|
||||||
// Validate mode if provided
|
// Validate mode if provided
|
||||||
if (config.modeId && !VALID_CODEX_MODES.has(config.modeId)) {
|
if (config.modeId && !VALID_CODEX_MODES.has(config.modeId)) {
|
||||||
@@ -634,6 +759,14 @@ class CodexAgentSession implements AgentSession {
|
|||||||
async close(): Promise<void> {
|
async close(): Promise<void> {
|
||||||
this.thread = null;
|
this.thread = null;
|
||||||
this.pendingPermissions.clear();
|
this.pendingPermissions.clear();
|
||||||
|
if (this.wrapper) {
|
||||||
|
try {
|
||||||
|
await fs.rm(this.wrapper.path, { force: true });
|
||||||
|
await fs.rm(this.wrapper.dir, { recursive: true, force: true });
|
||||||
|
} catch {
|
||||||
|
// ignore cleanup failures
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
@@ -697,6 +830,14 @@ class CodexAgentSession implements AgentSession {
|
|||||||
Object.assign(options, extra);
|
Object.assign(options, extra);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Codex CLI currently disables MCP tool availability when approval_policy is set.
|
||||||
|
// When injecting MCP servers, omit approvalPolicy/webSearchEnabled/networkAccessEnabled to ensure MCP tools are discoverable.
|
||||||
|
if (resolveAgentControlMcpUrl(this.config)) {
|
||||||
|
delete (options as Partial<ThreadOptions>).approvalPolicy;
|
||||||
|
delete (options as Partial<ThreadOptions>).webSearchEnabled;
|
||||||
|
delete (options as Partial<ThreadOptions>).networkAccessEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user