// 重构前 index.ts 只读参考：commit 6a04144d3f5103014f75b637d7e6bc2f45bf007f，blob 56e590c1a6b5ca7ad128bf2c992f60e46c355a58；可用 `git show 6a04144d3f5103014f75b637d7e6bc2f45bf007f:src/components/microservices/code-queue/src/index.ts` 查看。

import { spawn, type ChildProcessWithoutNullStreams } from "node:child_process";
import { existsSync, readFileSync } from "node:fs";
import * as readline from "node:readline";
import type { AppServerExit, CodexEventSummary, CodexRunResult, JsonValue, QueueTask, RuntimeConfig, SessionCommandOutput, TerminalStatus } from "../types";
import type { ActiveRun, CodeAgentClient } from "./common";
import { extractRecord, extractString, terminalStatus, textInput, withCodeAgentGitConfigEnv } from "./common";
import { classifyRunnerError, runnerErrorClassificationJson } from "../runner-error-classifier";

export interface CodexPortContext {
  config: Pick<RuntimeConfig, "approvalPolicy" | "codexHome" | "defaultWorkdir" | "resolvedRunnerSkillsPath" | "sandbox" | "sourceCodexConfig" | "turnNoActivityTimeoutMs">;
  activeRuns: Map<string, ActiveRun>;
  appendOutput: (task: QueueTask, channel: "system" | "assistant" | "reasoning" | "command" | "diff" | "tool" | "error", text: string, method?: string, itemId?: string, append?: boolean) => unknown;
  addEvent: (task: QueueTask, event: CodexEventSummary) => void;
  ensureTaskExecutionContainer: (task: QueueTask) => Promise<void>;
  formatCommandOutput: (output: SessionCommandOutput | null | undefined) => string;
  logger: (level: "debug" | "info" | "warn" | "error", message: string, data?: JsonValue) => void;
  persistTaskState: (task: QueueTask) => void;
  providerIsMain: (providerId: string) => boolean;
  queueIdOf: (task: QueueTask) => string;
  recordNumberField: (record: Record<string, unknown> | null, keys: string[]) => number | null;
  recordStringField: (record: Record<string, unknown> | null, keys: string[]) => string;
  refreshSchedulerHeartbeat: (task: QueueTask, options?: { observedAgentEvent?: boolean; persistedTrace?: boolean }) => void;
  remoteAppServerCommand: (task: QueueTask) => string;
  windowsNativeAppServerCommand: (task: QueueTask) => string;
  resolveReasoningEffort: (model: string, explicit?: string | null) => string | null;
  safePreview: (value: string, max?: number) => string;
  nowIso: () => string;
}

let context: CodexPortContext | null = null;

export function configureCodexPort(runtimeContext: CodexPortContext): void {
  context = runtimeContext;
}

function ctx(): CodexPortContext {
  if (context === null) throw new Error("codex port is not configured");
  return context;
}

function windowsPathFromWslMount(path: string): string {
  const match = path.match(/^\/mnt\/([a-zA-Z])(?:\/(.*))?$/u);
  if (match === null) return path;
  const drive = String(match[1]).toUpperCase();
  const rest = String(match[2] ?? "").replace(/\//gu, "\\");
  return rest.length > 0 ? `${drive}:\\${rest}` : `${drive}:\\`;
}

function appServerCwdForTask(task: QueueTask): string {
  return task.executionMode === "windows-native" ? windowsPathFromWslMount(task.cwd) : task.cwd;
}

const codexProxyEnvKeys = ["HTTP_PROXY", "HTTPS_PROXY", "ALL_PROXY", "http_proxy", "https_proxy", "all_proxy"];
const defaultCodexDirectHosts = ["hyueapi.com", ".hyueapi.com", "api.minimaxi.com", ".minimaxi.com"];

function splitEnvList(value: string | undefined, fallback: string[]): string[] {
  const raw = value === undefined || value.trim().length === 0 ? fallback : value.split(",");
  return Array.from(new Set(raw.map((item) => item.trim().toLowerCase()).filter(Boolean)));
}

function directHostNeedle(host: string): string {
  return host.replace(/^\./u, "").toLowerCase();
}

function readTextIfPresent(path: string): string {
  try {
    return existsSync(path) ? readFileSync(path, "utf8") : "";
  } catch {
    return "";
  }
}

function codexNetworkConfigText(): string {
  return [
    "OPENAI_BASE_URL",
    "OPENAI_API_BASE",
    "OPENAI_RESPONSES_URL",
    "CODEX_OPENAI_BASE_URL",
    "CODEX_BASE_URL",
  ].map((key) => process.env[key] ?? "").concat([
    readTextIfPresent(`${ctx().config.codexHome.replace(/\/+$/u, "")}/config.toml`),
    readTextIfPresent(ctx().config.sourceCodexConfig),
  ]).join("\n").toLowerCase();
}

function shouldRunCodexDirect(): boolean {
  const configText = codexNetworkConfigText();
  if (configText.length === 0) return false;
  return splitEnvList(process.env.CODE_QUEUE_CODEX_DIRECT_HOSTS, defaultCodexDirectHosts)
    .some((host) => configText.includes(directHostNeedle(host)));
}

function codexAppServerEnv(task: QueueTask): NodeJS.ProcessEnv {
  const env: NodeJS.ProcessEnv = {
    ...process.env,
    CODEX_HOME: ctx().config.codexHome,
    CODEX_INTERNAL_ORIGINATOR_OVERRIDE: "unidesk_code_queue",
    UNIDESK_SKILLS_PATH: ctx().config.resolvedRunnerSkillsPath(),
  };
  if (!shouldRunCodexDirect()) return withCodeAgentGitConfigEnv(env);
  for (const key of codexProxyEnvKeys) delete env[key];
  const directHosts = splitEnvList(process.env.CODE_QUEUE_CODEX_DIRECT_HOSTS, defaultCodexDirectHosts).join(",");
  env.NO_PROXY = [env.NO_PROXY, directHosts].filter((part) => part !== undefined && part.length > 0).join(",");
  env.no_proxy = [env.no_proxy, directHosts].filter((part) => part !== undefined && part.length > 0).join(",");
  ctx().appendOutput(task, "system", `codex app-server direct network enabled for ${directHosts}\n`, "codex/network");
  ctx().logger("info", "codex_app_server_direct_network", { taskId: task.id, directHosts });
  return withCodeAgentGitConfigEnv(env);
}

class AppServerClient {
  private child: ChildProcessWithoutNullStreams;
  private nextId = 1;
  private pending = new Map<number, { resolve: (value: unknown) => void; reject: (error: Error) => void }>();
  private stderrChunks: Buffer[] = [];
  private closed = false;
  private exitInfo: AppServerExit | null = null;
  private closeResolve!: (value: AppServerExit) => void;
  readonly closedPromise: Promise<AppServerExit>;

  constructor(private readonly task: QueueTask, private readonly onNotification: (message: Record<string, unknown>) => void) {
    this.closedPromise = new Promise((resolveClosed) => { this.closeResolve = resolveClosed; });
    const remoteCommand = task.executionMode === "windows-native"
      ? ctx().windowsNativeAppServerCommand(task)
      : ctx().remoteAppServerCommand(task);
    this.child = ctx().providerIsMain(task.providerId)
      ? spawn("codex", ["app-server", "--listen", "stdio://"], {
        cwd: task.cwd,
        env: codexAppServerEnv(task),
        stdio: "pipe",
      })
      : spawn("bun", ["scripts/cli.ts", "ssh", task.providerId, remoteCommand], {
        cwd: ctx().config.defaultWorkdir,
        env: process.env,
        stdio: "pipe",
      });
    this.child.stderr.on("data", (chunk: Buffer) => {
      this.stderrChunks.push(chunk);
      while (Buffer.concat(this.stderrChunks).length > 96_000) this.stderrChunks.shift();
    });
    const rl = readline.createInterface({ input: this.child.stdout, crlfDelay: Infinity });
    void this.readLines(rl);
    this.child.on("close", (code, signal) => this.handleClose(code, signal));
    this.child.on("error", (error) => this.handleClose(127, error.message));
  }

  async initialize(): Promise<void> {
    await this.request("initialize", {
      clientInfo: { name: "unidesk_code_queue", title: "UniDesk Code Queue", version: "0.1.0" },
      capabilities: { experimentalApi: true },
    });
    this.notify("initialized", {});
  }

  async startOrResumeThread(): Promise<string> {
    if (this.task.codexThreadId !== null) {
      ctx().appendOutput(this.task, "system", `thread resume requested ${this.task.codexThreadId}\n`, "thread/resume");
      const response = await this.request("thread/resume", {
        threadId: this.task.codexThreadId,
        model: this.task.model,
        cwd: appServerCwdForTask(this.task),
        approvalPolicy: ctx().config.approvalPolicy,
        sandbox: ctx().config.sandbox,
      });
      const threadId = extractString(extractRecord(response)?.thread, "id");
      if (threadId === null) throw new Error("thread/resume response did not include thread.id");
      ctx().appendOutput(this.task, "system", `thread resumed ${threadId}\n`, "thread/resume");
      return threadId;
    }
    const response = await this.request("thread/start", {
      model: this.task.model,
      cwd: appServerCwdForTask(this.task),
      approvalPolicy: ctx().config.approvalPolicy,
      sandbox: ctx().config.sandbox,
      serviceName: "unidesk-code-queue",
    });
    const threadId = extractString(extractRecord(response)?.thread, "id");
    if (threadId === null) throw new Error("thread/start response did not include thread.id");
    return threadId;
  }

  async startTurn(threadId: string, prompt: string): Promise<string> {
    const params: Record<string, JsonValue> = {
      threadId,
      input: textInput(prompt),
      cwd: appServerCwdForTask(this.task),
      approvalPolicy: ctx().config.approvalPolicy,
      model: this.task.model,
    };
    const effort = ctx().resolveReasoningEffort(this.task.model, this.task.reasoningEffort);
    if (effort !== null) params.effort = effort;
    const response = await this.request("turn/start", params);
    const turnId = extractString(extractRecord(response)?.turn, "id");
    if (turnId === null) throw new Error("turn/start response did not include turn.id");
    return turnId;
  }

  async steer(threadId: string, turnId: string, prompt: string): Promise<void> {
    await this.request("turn/steer", { threadId, expectedTurnId: turnId, input: textInput(prompt) });
  }

  async interrupt(threadId: string, turnId: string): Promise<void> {
    await this.request("turn/interrupt", { threadId, turnId });
  }

  stop(): void {
    if (this.closed) return;
    this.child.kill("SIGTERM");
    setTimeout(() => {
      if (!this.closed) this.child.kill("SIGKILL");
    }, 1500).unref?.();
  }

  private request(method: string, params: unknown): Promise<unknown> {
    if (this.closed) return Promise.reject(new Error("app-server is already closed"));
    const id = this.nextId++;
    const message = { method, id, params };
    return new Promise((resolve, reject) => {
      this.pending.set(id, { resolve, reject });
      this.write(message);
    });
  }

  private notify(method: string, params: unknown): void {
    this.write({ method, params });
  }

  private write(message: unknown): void {
    this.child.stdin.write(`${JSON.stringify(message)}\n`);
  }

  private async readLines(rl: readline.Interface): Promise<void> {
    try {
      for await (const line of rl) {
        const trimmed = String(line).trim();
        if (trimmed.length === 0) continue;
        const message = JSON.parse(trimmed) as Record<string, unknown>;
        this.handleMessage(message);
      }
    } catch (error) {
      ctx().appendOutput(this.task, "error", `app-server stream error: ${error instanceof Error ? error.message : String(error)}\n`, "app-server");
    }
  }

  private handleMessage(message: Record<string, unknown>): void {
    const id = typeof message.id === "number" ? message.id : null;
    const method = typeof message.method === "string" ? message.method : null;
    if (id !== null && method === null) {
      const pending = this.pending.get(id);
      if (pending === undefined) return;
      this.pending.delete(id);
      if ("error" in message) {
        pending.reject(new Error(JSON.stringify(message.error)));
      } else {
        pending.resolve(message.result);
      }
      return;
    }
    if (id !== null && method !== null) {
      this.handleServerRequest(id, method);
      return;
    }
    if (method !== null) this.onNotification(message);
  }

  private handleServerRequest(id: number, method: string): void {
    if (method === "item/commandExecution/requestApproval") {
      this.write({ id, result: { decision: "decline" } });
      return;
    }
    if (method === "item/fileChange/requestApproval") {
      this.write({ id, result: { decision: "decline" } });
      return;
    }
    this.write({ id, error: { code: -32601, message: `Unsupported client-side request: ${method}` } });
  }

  private handleClose(code: number | null, signal: string | null): void {
    if (this.closed) return;
    this.closed = true;
    this.exitInfo = { code, signal, stderrTail: Buffer.concat(this.stderrChunks).toString("utf8").slice(-8000) };
    for (const pending of this.pending.values()) pending.reject(new Error(`app-server closed with code=${code} signal=${signal}`));
    this.pending.clear();
    this.closeResolve(this.exitInfo);
  }
}

function eventSummary(message: Record<string, unknown>): CodexEventSummary {
  const params = extractRecord(message.params);
  const item = extractRecord(params?.item);
  const turn = extractRecord(params?.turn);
  const error = extractRecord(turn?.error) ?? extractRecord(params?.error);
  return {
    at: ctx().nowIso(),
    method: typeof message.method === "string" ? message.method : "unknown",
    itemType: typeof item?.type === "string" ? item.type : undefined,
    status: typeof item?.status === "string" ? item.status : typeof turn?.status === "string" ? turn.status : undefined,
    message: typeof error?.message === "string" ? ctx().safePreview(error.message, 600) : undefined,
    textPreview: typeof item?.text === "string" ? ctx().safePreview(item.text, 800) : undefined,
  };
}

function commandCompletionStreams(item: Record<string, unknown> | null): { stdout: string; stderr: string; output: string; exitCode: number | null } {
  if (item === null) return { stdout: "", stderr: "", output: "", exitCode: null };
  const result = extractRecord(item.result);
  const stdout = ctx().recordStringField(item, ["stdout", "stdoutText"]) || ctx().recordStringField(result, ["stdout", "stdoutText"]);
  const stderr = ctx().recordStringField(item, ["stderr", "stderrText"]) || ctx().recordStringField(result, ["stderr", "stderrText"]);
  const output = ctx().recordStringField(item, ["output", "outputText", "text"]) || ctx().recordStringField(result, ["output", "outputText", "text"]);
  const exitCode = ctx().recordNumberField(item, ["exitCode", "code"]) ?? ctx().recordNumberField(result, ["exitCode", "code"]);
  return { stdout: stdout || (stderr.length > 0 ? "" : output), stderr, output: output || [stdout, stderr].filter(Boolean).join("\n"), exitCode };
}

function hasRecentCommandOutputDelta(task: QueueTask, itemId: string | undefined): boolean {
  if (itemId === undefined) return false;
  for (let index = task.output.length - 1; index >= 0 && index >= task.output.length - 80; index -= 1) {
    const output = task.output[index];
    if (output?.itemId !== itemId) continue;
    if (output.method === "item/commandExecution/outputDelta") return true;
    if (output.method === "item/started") return false;
  }
  return false;
}

function handleNotification(task: QueueTask, message: Record<string, unknown>, terminal: (status: TerminalStatus, error: string | null) => void): void {
  const method = typeof message.method === "string" ? message.method : "unknown";
  const params = extractRecord(message.params);
  ctx().refreshSchedulerHeartbeat(task, { observedAgentEvent: true });
  ctx().addEvent(task, eventSummary(message));
  if (method === "thread/started") {
    const threadId = extractString(extractRecord(params?.thread), "id");
    if (threadId !== null) task.codexThreadId = threadId;
    ctx().refreshSchedulerHeartbeat(task, { observedAgentEvent: true });
    ctx().appendOutput(task, "system", `thread started ${threadId ?? "unknown"}\n`, method);
    return;
  }
  if (method === "turn/started") {
    const turnId = extractString(extractRecord(params?.turn), "id");
    task.activeTurnId = turnId;
    ctx().refreshSchedulerHeartbeat(task, { observedAgentEvent: true });
    ctx().appendOutput(task, "system", `turn started ${turnId ?? "unknown"}\n`, method);
    return;
  }
  if (method === "item/agentMessage/delta") {
    ctx().appendOutput(task, "assistant", String(params?.delta ?? ""), method, extractString(params, "itemId") ?? undefined, true);
    return;
  }
  if (method === "item/reasoning/summaryTextDelta" || method === "item/reasoning/textDelta") {
    ctx().appendOutput(task, "reasoning", String(params?.delta ?? ""), method, extractString(params, "itemId") ?? undefined, true);
    return;
  }
  if (method === "item/commandExecution/outputDelta") {
    ctx().appendOutput(task, "command", String(params?.delta ?? ""), method, extractString(params, "itemId") ?? undefined, true);
    return;
  }
  if (method === "item/fileChange/outputDelta") {
    ctx().appendOutput(task, "diff", String(params?.delta ?? ""), method, extractString(params, "itemId") ?? undefined, true);
    return;
  }
  if (method === "item/started" || method === "item/completed") {
    const item = extractRecord(params?.item);
    const type = String(item?.type ?? "item");
    if (type === "agentMessage" && typeof item?.text === "string") task.finalResponse = item.text;
    if (type === "commandExecution") {
      const itemId = extractString(item, "id") ?? undefined;
      if (method === "item/completed") {
        const streams = commandCompletionStreams(item);
        const hasDelta = hasRecentCommandOutputDelta(task, itemId);
        const completedOutput = hasDelta && streams.stderr.trim().length > 0
          ? `\n[stderr]\n${streams.stderr.trimEnd()}`
          : hasDelta
            ? ""
            : ctx().formatCommandOutput({ callId: itemId ?? "", at: ctx().nowIso(), stdout: streams.stdout, stderr: streams.stderr, output: streams.output, exitCode: streams.exitCode });
        if (completedOutput.trim().length > 0) ctx().appendOutput(task, "command", `${completedOutput.trimEnd()}\n`, "item/commandExecution/outputDelta", itemId, true);
      }
      ctx().appendOutput(task, "command", `${method}: ${String(item?.command ?? "command")} status=${String(item?.status ?? "unknown")}\n`, method, itemId);
    }
    if (type === "fileChange") ctx().appendOutput(task, "diff", `${method}: file changes status=${String(item?.status ?? "unknown")}\n`, method, extractString(item, "id") ?? undefined);
    if (type === "mcpToolCall" || type === "webSearch" || type === "dynamicToolCall") ctx().appendOutput(task, "tool", `${method}: ${type}\n`, method, extractString(item, "id") ?? undefined);
    return;
  }
  if (method === "error") {
    const error = extractRecord(params?.error);
    ctx().appendOutput(task, "error", `${String(error?.message ?? "Codex error")}\n`, method);
    return;
  }
  if (method === "turn/completed") {
    const turn = extractRecord(params?.turn);
    const status = terminalStatus(String(turn?.status ?? "failed"));
    const error = extractRecord(turn?.error);
    task.activeTurnId = null;
    ctx().refreshSchedulerHeartbeat(task, { observedAgentEvent: true });
    ctx().appendOutput(task, status === "completed" ? "system" : "error", `turn completed status=${status ?? "unknown"}\n`, method);
    terminal(status, typeof error?.message === "string" ? error.message : null);
  }
}

function classifyCodexRunnerFailure(task: QueueTask, result: Pick<CodexRunResult, "terminalStatus" | "terminalError" | "transportClosedBeforeTerminal" | "appServerExit">): JsonValue | null {
  if (result.terminalStatus === "completed" && !result.transportClosedBeforeTerminal) return null;
  const message = [
    result.terminalError ?? "",
    result.transportClosedBeforeTerminal ? "transport closed before terminal event" : "",
    result.appServerExit.stderrTail,
  ].filter((part) => part.trim().length > 0).join("\n");
  if (message.trim().length === 0) return null;
  return runnerErrorClassificationJson(classifyRunnerError(message, task.providerId));
}

export async function runCodexTurn(task: QueueTask, prompt: string): Promise<CodexRunResult> {
  const queueId = ctx().queueIdOf(task);
  const events: CodexEventSummary[] = [];
  let terminalSeen = false;
  let lastAppActivityAt = Date.now();
  let terminalResult: { status: TerminalStatus; error: string | null } = { status: null, error: null };
  let terminalResolve!: () => void;
  const terminalPromise = new Promise<void>((resolveTerminal) => { terminalResolve = resolveTerminal; });
  await ctx().ensureTaskExecutionContainer(task);
  if (task.executionMode === "windows-native") {
    ctx().appendOutput(task, "system", "windows-native execution: container stdio relay will connect to Codex on the provider Windows host\n", "execution/mode");
  }
  const app = new AppServerClient(task, (message) => {
    const method = typeof message.method === "string" ? message.method : "unknown";
    if (method !== "thread/started" && method !== "turn/started") lastAppActivityAt = Date.now();
    const before = task.events.length;
    handleNotification(task, message, (status, error) => {
      terminalSeen = true;
      terminalResult = { status, error };
      terminalResolve();
    });
    events.push(...task.events.slice(before));
  });

  try {
    await app.initialize();
    const threadId = await app.startOrResumeThread();
    task.codexThreadId = threadId;
    const runStartedAt = ctx().nowIso();
    ctx().activeRuns.set(queueId, { taskId: task.id, queueId, app, port: "codex", threadId, turnId: null, startedAt: runStartedAt, lastLocalHeartbeatAt: runStartedAt, lastObservedAgentEventAt: null, lastPersistedTraceAt: null });
    ctx().refreshSchedulerHeartbeat(task);
    const turnId = await app.startTurn(threadId, prompt);
    task.activeTurnId = turnId;
    const run = ctx().activeRuns.get(queueId);
    if (run?.app === app) {
      run.turnId = turnId;
      run.lastLocalHeartbeatAt = ctx().nowIso();
    }
    ctx().refreshSchedulerHeartbeat(task);
    ctx().persistTaskState(task);
    const activityWatchdog = setInterval(() => {
      if (terminalSeen) return;
      const idleMs = Date.now() - lastAppActivityAt;
      if (idleMs < ctx().config.turnNoActivityTimeoutMs) return;
      const message = `No Codex app activity for ${Math.round(idleMs / 1000)}s; stopping app-server so the existing thread can retry.`;
      ctx().appendOutput(task, "error", `${message}\n`, "turn/no-activity-watchdog");
      ctx().logger("warn", "turn_no_activity_watchdog", { taskId: task.id, turnId, idleMs, timeoutMs: ctx().config.turnNoActivityTimeoutMs });
      app.stop();
    }, 15_000);
    const race = await Promise.race([terminalPromise.then(() => "terminal" as const), app.closedPromise.then(() => "closed" as const)]);
    clearInterval(activityWatchdog);
    const closedBeforeTerminal = race === "closed" && !terminalSeen;
    if (terminalSeen) app.stop();
    const exit = await app.closedPromise;
    const result: CodexRunResult = {
      threadId,
      turnId,
      finalResponse: task.finalResponse,
      terminalStatus: terminalResult.status,
      terminalError: terminalResult.error,
      transportClosedBeforeTerminal: closedBeforeTerminal,
      appServerExit: exit,
      events,
    };
    result.runnerErrorClassification = classifyCodexRunnerFailure(task, result);
    return result;
  } catch (error) {
    const message = error instanceof Error ? error.message : String(error);
    ctx().appendOutput(task, "error", `${message}\n`, "app-server");
    app.stop();
    const exit = await app.closedPromise;
    const result: CodexRunResult = {
      threadId: task.codexThreadId,
      turnId: task.activeTurnId,
      finalResponse: task.finalResponse,
      terminalStatus: "failed",
      terminalError: message,
      transportClosedBeforeTerminal: !terminalSeen,
      appServerExit: exit,
      events,
    };
    result.runnerErrorClassification = classifyCodexRunnerFailure(task, result);
    return result;
  } finally {
    if (ctx().activeRuns.get(queueId)?.app === app) ctx().activeRuns.delete(queueId);
    app.stop();
  }
}
