feat(agent): enhance Codex initialization with MCP preheating and structured status updates

This commit is contained in:
HouYunFei
2026-08-03 10:54:15 +08:00
parent ea0414e88c
commit 8efd714221
11 changed files with 136 additions and 21 deletions
+12
View File
@@ -258,6 +258,18 @@ export class CodexAppClient {
}
return;
}
if (method === "mcpServer/startupStatus/updated") {
const value = params as unknown as CodexNotificationParams<"mcpServer/startupStatus/updated">;
this.emit("agent_bootstrap", {
type: "mcp.startup",
threadId: value.threadId || this.currentThreadId,
name: value.name,
status: value.status,
error: value.error,
failureReason: value.failureReason,
});
return;
}
const turnEvent = method.startsWith("turn/") || method.startsWith("item/") || method === "thread/tokenUsage/updated" || method === "error";
if (turnEvent) {
const threadId = String(field(params, "threadId") || this.currentThreadId);
+2
View File
@@ -6,6 +6,7 @@ export type CodexTurnError = JsonRecord & { message: string };
export type CodexItem = JsonRecord & { id: string; type: string; text?: string };
export type CodexPlanStep = { step: string; status: "pending" | "inProgress" | "completed" };
export type CodexPlanUpdate = { threadId: string; turnId: string; explanation?: string | null; plan: CodexPlanStep[]; turnStatus?: string };
export type CodexMcpStartupStatus = { threadId: string | null; name: string; status: "starting" | "ready" | "failed" | "cancelled"; error: string | null; failureReason: "reauthenticationRequired" | null };
export type CodexReasoningEffort = "minimal" | "low" | "medium" | "high" | "xhigh" | "max" | "ultra";
export type CodexModel = JsonRecord & {
id: string;
@@ -99,6 +100,7 @@ type CodexNotificationSpec = {
"item/reasoning/summaryTextDelta": { threadId: string; turnId: string; itemId: string; delta: string; summaryIndex: number };
"item/commandExecution/outputDelta": { threadId: string; turnId: string; itemId: string; delta: string };
"thread/tokenUsage/updated": { threadId: string; turnId: string; tokenUsage: { last: TokenUsageBreakdown } };
"mcpServer/startupStatus/updated": CodexMcpStartupStatus;
error: { threadId: string; turnId: string; error: CodexTurnError; willRetry: boolean };
};
+23 -2
View File
@@ -43,6 +43,24 @@ export function startHttpServer() {
session.emitThread("workspace_changed", activeThreadId, { ...payload, activeThreadId });
return workspace;
};
let draftThreadStart: ReturnType<typeof startCodexThread> | null = null;
const prepareDraftThread = (clientId: string, permission: AgentPermissionMode) => {
if (draftThreadStart) return draftThreadStart;
const workspace = ensureSiteWorkspace(config);
emit("agent_bootstrap", { type: "codex.preparing", sourceClientId: clientId });
const start = startCodexThread(emit, workspace.workspacePath, permission);
draftThreadStart = start;
void start.then((thread) => {
if (draftThreadStart !== start) return;
draftThreadStart = null;
const threadId = String((thread as Record<string, unknown>).id || "");
if (threadId && !ensureSiteWorkspace(config).activeThreadId) setActiveThread(threadId, { emptyThread: true, draftThread: true, sourceClientId: clientId });
}).catch((error) => {
if (draftThreadStart === start) draftThreadStart = null;
emit("agent_bootstrap", { type: "codex.prepare_failed", sourceClientId: clientId, error: error instanceof Error ? error.message : String(error) });
});
return start;
};
const app = express();
app.disable("x-powered-by");
app.use(express.json({ limit: "30mb" }));
@@ -124,7 +142,10 @@ export function startHttpServer() {
res.json({ ok: true, workspace: nextWorkspace, thread: summarizeCodexThread(thread), messages: [] });
}));
app.post("/agent/codex/threads/reset", codexMutation((req, res) => {
res.json({ ok: true, workspace: setActiveThread("", { emptyThread: true, draftThread: true, sourceClientId: String(req.body?.clientId || "") }) });
const clientId = String(req.body?.clientId || "");
const workspace = setActiveThread("", { emptyThread: true, draftThread: true, sourceClientId: clientId });
void prepareDraftThread(clientId, permissionMode(req.body?.permissionMode));
res.json({ ok: true, workspace });
}));
app.get("/agent/codex/threads/:threadId", route(async (req, res) => {
const workspace = ensureSiteWorkspace(config);
@@ -172,7 +193,7 @@ export function startHttpServer() {
try {
let turnId = "";
if (!threadId) {
const thread = await startCodexThread(emit, workspace.workspacePath, permissionMode(req.body?.permissionMode));
const thread = await prepareDraftThread(clientId, permissionMode(req.body?.permissionMode));
threadId = String((thread as Record<string, unknown>).id || "");
setActiveThread(threadId, { emptyThread: true, sourceClientId: clientId });
}