
import { spawnSync } from "node:child_process";
import { randomUUID } from "node:crypto";
import { accessSync, constants as fsConstants, existsSync, realpathSync, statSync } from "node:fs";
import { mkdir, readdir, readFile, rm, rmdir, stat, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { fileURLToPath } from "node:url";

import {
  codeAgentSessionLifecycleSummary,
  decorateCodeAgentSession
} from "./code-agent-session-lifecycle.ts";
import { runnerTraceFromSnapshot } from "./code-agent-trace-store.ts";
import {
  CODEX_APP_SERVER_PROTOCOL,
  CODEX_APP_SERVER_REQUIRED_METHODS,
  CODEX_APP_SERVER_WIRE_API,
  CODEX_CHILD_NO_PROXY_REQUIRED,
  CODEX_CHILD_STRIPPED_ENV_KEYS,
  CODEX_STDIO_BACKEND,
  CODEX_STDIO_BOUNDARY_INSTRUCTIONS,
  CODEX_STDIO_CAPABILITY_LEVEL,
  CODEX_STDIO_COMMAND_PROBE_TIMEOUT_MS,
  CODEX_STDIO_IMPLEMENTATION_TYPE,
  CODEX_STDIO_PROVIDER,
  CODEX_STDIO_RUNNER_KIND,
  CODEX_STDIO_SANDBOX,
  CODEX_STDIO_SESSION_MODE,
  CODEX_STDIO_SKILL_LIMIT,
  CODEX_STDIO_SKILL_REPLY_LIMIT,
  CODEX_STDIO_SKILL_SUMMARY_LIMIT,
  CODEX_STDIO_TOOL_OUTPUT_LIMIT,
  codexAppServerProviderBaseUrl,
  DEFAULT_CODEX_STDIO_COMMAND,
  DEFAULT_CODEX_STDIO_IDLE_TIMEOUT_MS,
  DEFAULT_CODEX_STDIO_MAX_SESSIONS,
  DEFAULT_CODEX_STDIO_PROBE_TTL_MS,
  DEFAULT_CODEX_STDIO_TURN_ACTIVITY_TIMEOUT_MS,
  DEFAULT_CODEX_STDIO_TURN_HARD_TIMEOUT_MS
} from "./codex-stdio-session.ts";

const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");

export function appServerActivityTimeoutError({ timeoutMs, idleMs, lastActivityAt, waitingFor, threadId, turnId, partialAssistant = false, diagnosis = null }) {
  const diagnosisSuffix = diagnosis?.code ? ` diagnosis=${diagnosis.code}.` : "";
  const error = new Error(partialAssistant
    ? `Codex app-server idle timeout after partial assistant output: no turn/completed for ${idleMs}ms, threshold ${timeoutMs}ms; waitingFor=${waitingFor ?? "unknown"}.${diagnosisSuffix}`
    : `Codex app-server idle timeout: no new events for ${idleMs}ms, threshold ${timeoutMs}ms; waitingFor=${waitingFor ?? "unknown"}.${diagnosisSuffix}`);
  Object.assign(error, {
    code: "codex_stdio_idle_timeout",
    timeoutMs,
    idleMs,
    lastActivityAt,
    waitingFor,
    threadId,
    turnId,
    partialAssistant,
    diagnosis,
    diagnosisCode: diagnosis?.code ?? null,
    stage: "app-server-turn"
  });
  return error;
}

export function appServerHardTimeoutError({ hardTimeoutMs, timeoutMs, idleMs, lastActivityAt, waitingFor, threadId, turnId, diagnosis = null }) {
  const diagnosisSuffix = diagnosis?.code ? ` diagnosis=${diagnosis.code}.` : "";
  const error = new Error(`Codex app-server hard timeout reached after ${hardTimeoutMs}ms; last activity was ${idleMs}ms ago; waitingFor=${waitingFor ?? "unknown"}.${diagnosisSuffix}`);
  Object.assign(error, {
    code: "codex_stdio_hard_timeout",
    timeoutMs,
    hardTimeoutMs,
    idleMs,
    lastActivityAt,
    waitingFor,
    threadId,
    turnId,
    diagnosis,
    diagnosisCode: diagnosis?.code ?? null,
    stage: "app-server-turn"
  });
  return error;
}

export function normalizeAppServerTurnResult(rawResult, state, fallback) {
  const raw = extractAppServerRecord(rawResult) ?? {};
  return {
    threadId: optionalId(raw.threadId) ?? state.threadId ?? fallback.threadId ?? null,
    turnId: optionalId(raw.turnId) ?? state.turnId ?? fallback.turnId ?? null,
    assistantText: redactText(firstNonEmpty(raw.assistantText, state.assistantText)),
    finalResponse: redactText(firstNonEmpty(raw.finalResponse, raw.content, raw.text, state.finalResponse, state.assistantText)),
    terminalStatus: appServerTerminalStatus(raw.terminalStatus ?? raw.status ?? state.terminalStatus),
    terminalError: raw.terminalError ? redactText(raw.terminalError) : state.terminalError ?? null,
    lastActivityAt: raw.lastActivityAt ?? state.lastActivityAt ?? null,
    idleMs: raw.idleMs ?? state.idleMs ?? null,
    waitingFor: raw.waitingFor ?? state.waitingFor ?? null,
    appServerExit: raw.appServerExit ?? null,
    diagnosis: raw.diagnosis ?? state.diagnosis ?? null
  };
}

export function summarizeAppServerTurn(turnResult) {
  return [
    turnResult?.threadId ? `threadId=${turnResult.threadId}` : "threadId=not_observed",
    turnResult?.turnId ? `turnId=${turnResult.turnId}` : null,
    `terminalStatus=${turnResult?.terminalStatus ?? "unknown"}`,
    firstNonEmpty(turnResult?.finalResponse, turnResult?.assistantText) ? `assistantChars=${String(firstNonEmpty(turnResult.finalResponse, turnResult.assistantText)).length}` : null
  ].filter(Boolean).join(" ");
}

export function codexStdioProviderTrace({ availability, toolName, turnResult, session } = {}) {
  return {
    transport: "stdio",
    protocol: CODEX_APP_SERVER_PROTOCOL,
    wireApi: CODEX_APP_SERVER_WIRE_API,
    runnerKind: CODEX_STDIO_RUNNER_KIND,
    implementationType: CODEX_STDIO_IMPLEMENTATION_TYPE,
    command: `${availability?.command || "/app/node_modules/.bin/codex"} app-server --listen stdio://`,
    toolName,
    threadId: turnResult?.threadId ?? session?.threadId ?? null,
    turnId: turnResult?.turnId ?? null,
    terminalStatus: turnResult?.terminalStatus ?? null,
    appServerExit: turnResult?.appServerExit ?? null,
    diagnosis: turnResult?.diagnosis ?? null,
    valuesPrinted: false
  };
}

export function codexAppServerTextInput(text) {
  return [{ type: "text", text: String(text ?? ""), text_elements: [] }];
}

export function extractAppServerRecord(value) {
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
}

export function extractAppServerString(record, key) {
  return typeof record?.[key] === "string" && record[key].trim() ? record[key].trim() : null;
}

export function appServerTerminalStatus(value) {
  const raw = String(value ?? "").trim().toLowerCase();
  if (raw === "completed" || raw === "complete" || raw === "success" || raw === "succeeded") return "completed";
  if (raw === "canceled" || raw === "cancelled") return "canceled";
  if (raw === "interrupted") return "interrupted";
  if (raw === "timeout" || raw === "timed_out") return "timeout";
  if (raw === "failed" || raw === "error") return "failed";
  return raw ? "failed" : null;
}

export function appServerCommandStatus(item) {
  const raw = String(item?.status ?? "").trim().toLowerCase();
  if (raw === "completed" || raw === "complete" || raw === "success" || raw === "succeeded") return "completed";
  if (raw === "failed" || raw === "error") return "failed";
  if (Number.isInteger(item?.exitCode)) return item.exitCode === 0 ? "completed" : "failed";
  return raw || "completed";
}

export function commandExecutionCommandText(item) {
  return firstCommandExecutionText(
    item?.command,
    item?.cmd,
    item?.argv,
    item?.input,
    item?.metadata?.command
  );
}

export function commandExecutionOutputText(item) {
  return firstCommandExecutionText(
    item?.aggregatedOutput,
    item?.output,
    item?.stdout,
    item?.result,
    item?.metadata?.output
  );
}

export function commandExecutionErrorText(item) {
  const error = item?.error;
  const message = typeof error === "string" ? error : typeof error?.message === "string" ? error.message : "";
  const text = firstCommandExecutionText(item?.stderr, message, item?.metadata?.stderr);
  return text ? tailText(redactText(text), 600) : undefined;
}

export function firstCommandExecutionText(...values) {
  for (const value of values) {
    const text = commandExecutionTextValue(value);
    if (text) return text;
  }
  return "";
}

export function commandExecutionTextValue(value) {
  if (typeof value === "string") return value.trim();
  if (Array.isArray(value)) return value.map((item) => commandExecutionTextValue(item)).filter(Boolean).join(" ").trim();
  if (value && typeof value === "object") {
    return firstCommandExecutionText(value.text, value.content, value.message, value.stdout, value.stderr, value.command, value.cmd);
  }
  return "";
}

export function longLivedSessionGate({
  provider,
  runnerKind,
  session,
  sessionMode,
  implementationType,
  codexStdioFeasibility
} = {}) {
  const normalizedProvider = String(provider ?? "").trim();
  const normalizedRunnerKind = String(runnerKind ?? "").trim();
  const normalizedSessionMode = String(sessionMode ?? "").trim();
  const normalizedImplementation = String(implementationType ?? "").trim();
  const resumableSessionStatuses = ["idle", "ready", "busy", "failed", "interrupted", "timeout", "error", "canceled", "cancelled"];
  const blockers = [];
  if (normalizedProvider === "openai-responses" || normalizedRunnerKind === "openai-responses-fallback") {
    blockers.push({
      code: "openai_responses_fallback_not_session",
      sourceIssue: "pikasTech/HWLAB#317",
      summary: "OpenAI Responses fallback is text-chat-only and cannot pass the long-lived Codex session gate."
    });
  }
  if (normalizedRunnerKind === "codex-cli-one-shot-ephemeral" || normalizedSessionMode === "ephemeral-one-shot") {
    blockers.push({
      code: "one_shot_runner_not_long_lived",
      sourceIssue: "pikasTech/HWLAB#317",
      summary: "codex exec --ephemeral / one-shot runner output is not a reusable long-lived runner session."
    });
  }
  if (normalizedImplementation === "controlled-readonly-session-registry") {
    blockers.push({
      code: "codex_stdio_blocked_readonly_session_available",
      sourceIssue: "pikasTech/HWLAB#317",
      summary: "This response is backed by a reusable controlled read-only session registry, not Codex stdio or an equivalent full Code Agent protocol adapter."
    });
  }
  if (session && !resumableSessionStatuses.includes(session.status)) {
    blockers.push({
      code: `session_${session.status || "inactive"}`,
      sourceIssue: "pikasTech/HWLAB#317",
      summary: `Long-lived Codex stdio session is not active: status=${session.status || "unknown"}.`
    });
  }
  for (const blocker of codexStdioFeasibility?.blockers ?? []) {
    if (!blocker?.code || blockers.some((item) => item.code === blocker.code)) continue;
    blockers.push({
      code: blocker.code,
      sourceIssue: blocker.sourceIssue ?? "pikasTech/HWLAB#317",
      summary: blocker.summary ?? `Codex stdio feasibility blocker: ${blocker.code}.`
    });
  }

  const feasible = codexStdioFeasibility?.canStartLongLivedCodexStdio === true || codexStdioFeasibility?.ready === true;
  const sessionPass =
    normalizedProvider === CODEX_STDIO_PROVIDER &&
    normalizedRunnerKind === CODEX_STDIO_RUNNER_KIND &&
    normalizedSessionMode === CODEX_STDIO_SESSION_MODE &&
    normalizedImplementation === CODEX_STDIO_IMPLEMENTATION_TYPE &&
    session?.longLivedSession === true &&
    session?.codexStdio === true &&
    session?.writeCapable === true &&
    session?.durable === true &&
    resumableSessionStatuses.includes(session?.status) &&
    feasible &&
    blockers.length === 0;
  const feasiblePass =
    normalizedProvider === CODEX_STDIO_PROVIDER &&
    normalizedRunnerKind === CODEX_STDIO_RUNNER_KIND &&
    normalizedSessionMode === CODEX_STDIO_SESSION_MODE &&
    normalizedImplementation === CODEX_STDIO_IMPLEMENTATION_TYPE &&
    !session &&
    feasible &&
    blockers.length === 0;
  const pass = sessionPass || feasiblePass;

  return {
    status: pass ? "pass" : "blocked",
    pass,
    requiredCapability: "long-lived-codex-stdio-session",
    currentCapability: normalizedImplementation || normalizedSessionMode || normalizedRunnerKind || "unknown",
    sessionId: session?.sessionId ?? null,
    sessionStatus: session?.status ?? null,
    runnerKind: normalizedRunnerKind || null,
    provider: normalizedProvider || null,
    sessionMode: normalizedSessionMode || null,
    implementationType: normalizedImplementation || null,
    blockers,
    sourceIssue: "pikasTech/HWLAB#317",
    summary: pass
      ? "Long-lived Codex stdio/session gate passed."
      : "Long-lived Codex stdio/session gate remains blocked; current response must not be reported as full Code Agent completion."
  };
}

export function publicSession(session, { conversationId = null, reused = null } = {}) {
  const conversationIds = [...session.conversationIds];
  return decorateCodeAgentSession({
    sessionId: session.sessionId,
    conversationId: conversationId ?? conversationIds[0] ?? null,
    conversationIds,
    status: session.status,
    workspace: session.workspace,
    sandbox: session.sandbox,
    runnerKind: session.runnerKind,
    sessionMode: session.sessionMode,
    capabilityLevel: session.capabilityLevel,
    implementationType: session.implementationType,
    createdAt: session.createdAt,
    updatedAt: session.updatedAt,
    idleTimeoutMs: session.idleTimeoutMs,
    expiresAt: session.expiresAt,
    lastTraceId: session.lastTraceId,
    currentTraceId: session.currentTraceId,
    turn: session.turn,
    threadId: session.threadId ?? null,
    reused: reused === null ? undefined : Boolean(reused),
    durable: session.durable === true,
    longLivedSession: session.longLivedSession === true,
    codexStdio: session.codexStdio === true,
    writeCapable: session.writeCapable === true,
    secretMaterialStored: false,
    valuesRedacted: true,
    ...(session.statusReason ? { statusReason: session.statusReason } : {})
  }, { reused });
}

export function blockedAcquire({ code, summary, session, timestamp, traceId, currentTrace = null, diagnosis = null }) {
  const publicEvidence = session
    ? publicSession(session, { timestamp, reused: true })
    : {
        status: "failed",
        updatedAt: timestamp,
        lastTraceId: optionalId(traceId),
        secretMaterialStored: false,
        valuesRedacted: true
      };
  return {
    ok: false,
    code,
    message: summary,
    session: publicEvidence,
    blocker: {
      code,
      sourceIssue: "pikasTech/HWLAB#317",
      summary,
      currentTraceId: currentTrace?.traceId ?? session?.currentTraceId ?? null,
      currentTraceStatus: currentTrace?.status ?? null,
      currentTraceLastEvent: currentTrace?.lastEvent ?? null,
      currentTraceUpdatedAt: currentTrace?.updatedAt ?? null,
      diagnosis
    }
  };
}

export function runnerDescriptor({ workspace, sandbox, session }) {
  return {
    kind: CODEX_STDIO_RUNNER_KIND,
    provider: CODEX_STDIO_PROVIDER,
    backend: CODEX_STDIO_BACKEND,
    workspace,
    sandbox,
    session: CODEX_STDIO_SESSION_MODE,
    sessionMode: CODEX_STDIO_SESSION_MODE,
    sessionId: session?.sessionId ?? null,
    turn: session?.turn ?? null,
    sessionReused: session?.reused ?? false,
    implementationType: CODEX_STDIO_IMPLEMENTATION_TYPE,
    codexStdio: true,
    longLivedSession: true,
    durableSession: true,
    writeCapable: true,
    readOnly: false,
    capabilityLevel: CODEX_STDIO_CAPABILITY_LEVEL,
    toolPolicy: {
      allowed: ["codex-app-server.thread/start", "codex-app-server.thread/resume", "codex-app-server.turn/start", "workspace-read", "workspace-write"],
      blocked: ["secret-read", "kubeconfig-read", "gateway-direct-call", "box-simu-direct-call", "patch-panel-direct-call", "M3/M4/M5-acceptance-claim-without-evidence"]
    },
    runnerLimitations: ["secret-values-redacted"],
    safety: codexStdioSafety()
  };
}

export function runnerTrace({ traceRecorder, traceId, workspace, sandbox, session, startedAt, outputTruncated }) {
  const sessionLifecycleStatus = codeAgentSessionLifecycleSummary({ session }).status;
  const snapshot = traceRecorder?.snapshot({
    sessionId: session?.sessionId,
    sessionStatus: session?.status,
    sessionLifecycleStatus,
    turn: session?.turn,
    workspace,
    sandbox,
    runnerKind: CODEX_STDIO_RUNNER_KIND,
    sessionMode: CODEX_STDIO_SESSION_MODE,
    implementationType: CODEX_STDIO_IMPLEMENTATION_TYPE
  });
  return runnerTraceFromSnapshot(snapshot ?? {
    traceId,
    events: [],
    eventLabels: [],
    updatedAt: timestampFor(),
    startedAt
  }, {
    runnerKind: CODEX_STDIO_RUNNER_KIND,
    workspace,
    sandbox,
    sessionMode: CODEX_STDIO_SESSION_MODE,
    sessionId: session?.sessionId ?? null,
    sessionStatus: session?.status ?? null,
    sessionLifecycleStatus,
    idleTimeoutMs: session?.idleTimeoutMs ?? null,
    lastTraceId: session?.lastTraceId ?? traceId,
    turn: session?.turn ?? null,
    sessionReused: session?.reused ?? false,
    implementationType: CODEX_STDIO_IMPLEMENTATION_TYPE,
    limitations: ["secret-values-redacted"],
    startedAt,
    outputTruncated: Boolean(outputTruncated),
    note: "Repo-owned Codex app-server stdio supervisor manages create/reuse/cancel/reap/idle timeout and real-time trace capture; hardware control remains routed through cloud-api/HWLAB API/skill CLI boundaries."
  });
}

export function sessionReuseEvidence(session) {
  return {
    conversationId: session.conversationId,
    sessionId: session.sessionId,
    threadId: session.threadId,
    mapped: true,
    reused: session.reused,
    turn: session.turn,
    previousTurns: Math.max(0, session.turn - 1),
    workspace: session.workspace,
    createdAt: session.createdAt,
    updatedAt: session.updatedAt,
    idleTimeoutMs: session.idleTimeoutMs,
    expiresAt: session.expiresAt,
    lastTraceId: session.lastTraceId,
    status: session.status,
    lifecycleStatus: session.lifecycleStatus ?? codeAgentSessionLifecycleSummary({ session }).status
  };
}

export function extractCodexToolOutput(toolResult) {
  const direct = toolResult?.structuredContent ?? toolResult?.output ?? toolResult;
  const directThreadId = optionalId(direct?.threadId ?? direct?.conversationId);
  const directContent = firstNonEmpty(direct?.content, direct?.message, direct?.text);
  if (directThreadId || directContent) {
    return {
      threadId: directThreadId,
      content: redactText(directContent)
    };
  }

  for (const item of toolResult?.content ?? []) {
    if (typeof item?.text !== "string") continue;
    const parsed = parseJsonOrNull(item.text);
    if (parsed) {
      const threadId = optionalId(parsed.threadId ?? parsed.conversationId);
      const content = firstNonEmpty(parsed.content, parsed.message, parsed.text);
      if (threadId || content) {
        return {
          threadId,
          content: redactText(content)
        };
      }
    }
    if (item.text.trim()) {
      return {
        threadId: null,
        content: redactText(item.text.trim())
      };
    }
  }

  return {
    threadId: null,
    content: ""
  };
}

export function summarizePrompt(message) {
  const value = String(message ?? "").replace(/\s+/gu, " ").trim();
  if (!value) return "empty prompt";
  return value.length > 160 ? `${value.slice(0, 157)}...` : value;
}

export function summarizeToolResult(toolResult) {
  const content = extractCodexToolOutput(toolResult);
  if (content.threadId || content.content) {
    return [
      content.threadId ? `threadId=${content.threadId}` : "threadId=not_observed",
      content.content ? `assistantChars=${content.content.length}` : null
    ].filter(Boolean).join(" ");
  }
  if (Array.isArray(toolResult?.content)) return `contentItems=${toolResult.content.length}`;
  return "tool result captured";
}

export async function collectWorkspaceSidecarEvidence({ message, workspace, traceId, env, now, skillsDirs, skillsDirsExact } = {}) {
  const intent = detectWorkspaceSidecarIntent(message);
  const toolCalls = [];
  const events = [];
  let skills = notRequestedSkills();
  let outputTruncated = false;

  if (intent.pwd) {
    const toolCall = await pwdToolCall({ workspace, traceId });
    toolCalls.push(toolCall);
    events.push(toolTraceEvent(toolCall, { labelPrefix: "sidecar" }));
  }

  if (intent.ls) {
    const toolCall = await lsToolCall({ workspace, target: intent.target, traceId });
    toolCalls.push(toolCall);
    events.push(toolTraceEvent(toolCall, { labelPrefix: "sidecar" }));
    outputTruncated = outputTruncated || toolCall.outputTruncated;
  }

  if (intent.skills) {
    skills = await discoverSkillsForStdio({ env, traceId, skillsDirs, skillsDirsExact });
    toolCalls.push({
      id: `tool_${randomUUID()}`,
      type: "file-read",
      name: "skills.discover",
      status: skills.status === "ready" ? "completed" : "blocked",
      cwd: workspace,
      exitCode: skills.status === "ready" ? 0 : 1,
      stdout: skills.status === "ready" ? `skills=${skills.count}` : "",
      stderrSummary: skills.status === "ready" ? "" : "skills_unavailable",
      outputTruncated: Boolean(skills.truncated),
      traceId
    });
    events.push({
      type: "tool_call",
      status: skills.status === "ready" ? "completed" : "blocked",
      label: `sidecar:skills.discover:${skills.status === "ready" ? "completed" : "blocked"}`,
      toolName: "skills.discover",
      outputSummary: skills.status === "ready" ? `skills=${skills.count}` : "skills_unavailable",
      outputTruncated: Boolean(skills.truncated)
    });
    outputTruncated = outputTruncated || Boolean(skills.truncated);
  }

  if (intent.smoke) {
    const smokeCalls = await workspaceSmokeToolCalls({ workspace, traceId, now });
    toolCalls.push(...smokeCalls);
    for (const call of smokeCalls) events.push(toolTraceEvent(call, { labelPrefix: "sidecar" }));
    outputTruncated = outputTruncated || smokeCalls.some((call) => call.outputTruncated);
  }

  return {
    intent,
    toolCalls,
    skills,
    events,
    outputTruncated
  };
}

export function toolTraceEvent(toolCall, { labelPrefix = "tool" } = {}) {
  return {
    type: "tool_call",
    status: toolCall.status,
    label: `${labelPrefix}:${toolCall.name}:${toolCall.status}`,
    toolName: toolCall.name,
    outputSummary: firstNonEmpty(toolCall.stderrSummary, toolCall.stdout ? `${toolCall.name} output captured` : null),
    outputTruncated: toolCall.outputTruncated === true
  };
}

export function detectWorkspaceSidecarIntent(message) {
  const text = String(message ?? "");
  const lower = text.toLowerCase();
  const wantsPwd = /\bpwd\b/u.test(lower) || /(?:当前|打印|显示|查看).{0,12}(?:工作目录|目录|路径|workspace)/iu.test(text);
  const explicitLs = /\bls\b/u.test(lower) || /(?:列出|查看).{0,12}(?:目录|文件)/u.test(text);
  return {
    pwd: wantsPwd,
    ls: explicitLs && (!wantsPwd || /\bls\b/u.test(lower) || /(?:文件|目录列表|所有文件)/u.test(text)),
    skills: /(?:可用|能使用|加载|列出|所有).{0,16}(?:skills?|skill|技能)|(?:skills?|skill|技能).{0,16}(?:可用|能使用|加载|列出|所有)/iu.test(text),
    smoke: /(?:write|写入|读取|清理|cleanup|smoke|冒烟).{0,24}(?:workspace|工作区|临时|tmp)|(?:workspace|工作区).{0,24}(?:write|写入|读取|清理|smoke|冒烟)/iu.test(text),
    target: extractWorkspaceTarget(text)
  };
}

export function extractWorkspaceTarget(text) {
  const value = String(text ?? "");
  const quoted = value.match(/[`"']([^`"']{1,240})[`"']/u)?.[1];
  if (quoted) return quoted.trim();
  const inlinePath = value.match(/((?:\.{1,2}\/|\/)[A-Za-z0-9._@:/+=-]+|[A-Za-z0-9._@+=-]+\.[A-Za-z0-9._-]+)/u)?.[1];
  return inlinePath || ".";
}

export async function pwdToolCall({ workspace, traceId }) {
  try {
    const workspaceStat = await stat(workspace);
    if (!workspaceStat.isDirectory()) {
      return blockedToolCall({
        name: "pwd",
        type: "workspace-read",
        workspace,
        traceId,
        reason: "configured workspace is not a directory"
      });
    }
  } catch (error) {
    return blockedToolCall({
      name: "pwd",
      type: "workspace-read",
      workspace,
      traceId,
      reason: error.message || "configured workspace is not readable"
    });
  }
  return {
    id: `tool_${randomUUID()}`,
    type: "workspace-read",
    name: "pwd",
    status: "completed",
    cwd: workspace,
    command: "pwd",
    exitCode: 0,
    stdout: redactText(workspace),
    stderrSummary: "",
    outputTruncated: false,
    traceId
  };
}

export async function lsToolCall({ workspace, target = ".", traceId }) {
  const targetInfo = resolveWorkspaceTarget(workspace, target, { mustExist: true });
  const blockedReason = targetInfo.blocked ? targetInfo.reason : await targetInfo.check();
  if (blockedReason) {
    return blockedToolCall({ name: "ls", type: "workspace-read", workspace, traceId, reason: blockedReason });
  }
  try {
    const info = await stat(targetInfo.path);
    const entries = info.isDirectory()
      ? await readdir(targetInfo.path, { withFileTypes: true })
      : [{ name: path.basename(targetInfo.path), isDirectory: () => false, isSymbolicLink: () => false }];
    const lines = entries
      .sort((a, b) => a.name.localeCompare(b.name, "en"))
      .slice(0, 200)
      .map((entry) => `${entry.isDirectory() ? "dir " : entry.isSymbolicLink() ? "link" : "file"} ${entry.name}`);
    const bounded = boundToolOutput(redactText(lines.join("\n")));
    return {
      id: `tool_${randomUUID()}`,
      type: "workspace-read",
      name: "ls",
      status: "completed",
      cwd: workspace,
      command: `ls ${safeDisplayPath(targetInfo.relative || ".")}`,
      exitCode: 0,
      stdout: bounded.text,
      stderrSummary: entries.length > 200 ? "entry limit 200 reached" : "",
      outputTruncated: bounded.truncated || entries.length > 200,
      traceId
    };
  } catch (error) {
    return blockedToolCall({ name: "ls", type: "workspace-read", workspace, traceId, reason: error.message });
  }
}

export async function workspaceSmokeToolCalls({ workspace, traceId, now }) {
  const parentDir = path.join(workspace, ".hwlab-code-agent-smoke");
  const dir = path.join(parentDir, `run-${randomUUID()}`);
  const filename = "stdio-smoke.txt";
  const filePath = path.join(dir, filename);
  const content = `traceId=${traceId}\ncreatedAt=${timestampFor(now)}\n`;
  const calls = [];
  try {
    await mkdir(dir, { recursive: true });
    await writeFile(filePath, content, { encoding: "utf8", flag: "wx" });
    calls.push({
      id: `tool_${randomUUID()}`,
      type: "workspace-write",
      name: "workspace.smoke.write",
      status: "completed",
      cwd: workspace,
      command: `write ${safeDisplayPath(path.relative(workspace, filePath))}`,
      exitCode: 0,
      stdout: "written",
      stderrSummary: "",
      outputTruncated: false,
      traceId
    });
    const readBack = await readFile(filePath, "utf8");
    const bounded = boundToolOutput(redactText(readBack), 300);
    calls.push({
      id: `tool_${randomUUID()}`,
      type: "workspace-read",
      name: "workspace.smoke.read",
      status: "completed",
      cwd: workspace,
      command: `read ${safeDisplayPath(path.relative(workspace, filePath))}`,
      exitCode: 0,
      stdout: bounded.text,
      stderrSummary: "",
      outputTruncated: bounded.truncated,
      traceId
    });
    await rm(filePath, { force: true });
    await rmdir(dir);
    await rmdir(parentDir).catch(() => {});
    calls.push({
      id: `tool_${randomUUID()}`,
      type: "workspace-write",
      name: "workspace.smoke.cleanup",
      status: "completed",
      cwd: workspace,
      command: `rm ${safeDisplayPath(path.relative(workspace, filePath))}`,
      exitCode: 0,
      stdout: "removed",
      stderrSummary: "",
      outputTruncated: false,
      traceId
    });
  } catch (error) {
    calls.push(blockedToolCall({
      name: "workspace.smoke",
      type: "workspace-write",
      workspace,
      traceId,
      reason: error.message
    }));
    try {
      await rm(dir, { recursive: true, force: true });
      await rmdir(parentDir).catch(() => {});
    } catch {
      // Best-effort cleanup; blocked tool evidence keeps readiness precise.
    }
  }
  return calls;
}

export async function discoverSkillsForStdio({ env = process.env, traceId, skillsDirs, skillsDirsExact } = {}) {
  const checkedDirs = resolveSkillDirs(env, { skillsDirs, skillsDirsExact });
  const sources = [];
  const items = [];
  for (const dir of checkedDirs) {
    if (!pathReadableSync(dir)) {
      sources.push({ path: dir, status: "missing_or_unreadable" });
      continue;
    }
    sources.push({ path: dir, status: "readable" });
    const manifests = await skillManifestPaths(dir);
    for (const manifestPath of manifests) {
      const manifest = await readSkillManifest(manifestPath);
      if (!manifest) continue;
      items.push({
        name: manifest.name,
        summary: boundSkillText(manifest.description ?? firstMarkdownSummary(manifest.body) ?? "No description provided."),
        source: manifestPath,
        manifestPath,
        manifest: {
          type: "SKILL.md",
          path: manifestPath,
          root: dir,
          relativePath: path.relative(dir, manifestPath) || "SKILL.md"
        },
        sourceRoot: dir,
        version: manifest.version ?? null,
        commitId: manifest.commitId ?? null,
        traceId
      });
    }
  }
  const unique = dedupeSkills(items).sort((a, b) => a.name.localeCompare(b.name, "en"));
  const returned = unique.slice(0, CODEX_STDIO_SKILL_LIMIT);
  if (returned.length === 0) {
    return {
      status: "blocked",
      code: "skills_unavailable",
      items: [],
      count: 0,
      totalCount: 0,
      checkedDirs,
      sources,
      sourceIssues: ["pikasTech/HWLAB#136", "pikasTech/HWLAB#237"],
      blockers: [
        {
          code: "skills_unavailable",
          sourceIssue: "pikasTech/HWLAB#136",
          summary: "No readable SKILL.md files were found in the configured Codex stdio skills directories."
        },
        {
          code: "skills_manifest_missing",
          sourceIssue: "pikasTech/HWLAB#237",
          summary: "Codex stdio skills discovery requires mounted skill manifests; generic text fallback must not invent skills."
        }
      ],
      valuesPrinted: false
    };
  }
  return {
    status: "ready",
    code: "skills_ready",
    items: returned,
    count: returned.length,
    totalCount: unique.length,
    truncated: unique.length > returned.length,
    checkedDirs,
    sources,
    sourceIssues: [],
    blockers: [],
    valuesPrinted: false
  };
}

export function resolveSkillDirs(env = process.env, options = {}) {
  const configured = Array.isArray(options.skillsDirs)
    ? options.skillsDirs
    : String(firstNonEmpty(env.HWLAB_CODE_AGENT_SKILLS_DIRS, env.UNIDESK_SKILLS_PATH, ""))
        .split(/[,;]/u)
        .flatMap((part) => part.split(path.delimiter));
  const normalized = configured
    .filter((dir) => typeof dir === "string" && dir.trim())
    .map((dir) => path.resolve(dir.trim()));
  if (options.skillsDirsExact === true || env.HWLAB_CODE_AGENT_SKILLS_STRICT === "1") {
    return [...new Set(normalized)];
  }
  const defaults = [env.HWLAB_PREINSTALLED_SKILLS_DIR || "/app/skills", env.HWLAB_USER_SKILLS_DIR]
    .filter((dir) => typeof dir === "string" && dir.trim())
    .map((dir) => path.resolve(dir.trim()));
  return [...new Set(normalized.length > 0 ? normalized : defaults)];
}

export async function skillManifestPaths(skillsDir) {
  const direct = path.join(skillsDir, "SKILL.md");
  const manifests = [];
  if (pathReadableSync(direct)) manifests.push(direct);
  let entries = [];
  try {
    entries = await readdir(skillsDir, { withFileTypes: true });
  } catch {
    return manifests;
  }
  for (const entry of entries) {
    if (!entry.isDirectory()) continue;
    const manifestPath = path.join(skillsDir, entry.name, "SKILL.md");
    if (pathReadableSync(manifestPath)) manifests.push(manifestPath);
  }
  return manifests;
}

export async function readSkillManifest(manifestPath) {
  try {
    const text = await readFile(manifestPath, "utf8");
    const frontmatter = parseFrontmatter(text);
    const body = text.replace(/^---\s*\n[\s\S]*?\n---\s*\n?/u, "");
    const name = frontmatter.name ?? path.basename(path.dirname(manifestPath));
    return name ? {
      name,
      description: frontmatter.description,
      version: firstManifestField(frontmatter, ["version", "skillVersion"]),
      commitId: firstManifestField(frontmatter, ["commitId", "commit", "gitCommit", "revision"]),
      body
    } : null;
  } catch {
    return null;
  }
}

export function parseFrontmatter(text) {
  const match = String(text ?? "").match(/^---\s*\n([\s\S]*?)\n---\s*(?:\n|$)/u);
  if (!match) return {};
  const data = {};
  for (const line of match[1].split(/\r?\n/u)) {
    const field = line.match(/^([A-Za-z0-9_-]+):\s*(.*)\s*$/u);
    if (!field) continue;
    data[field[1]] = field[2].replace(/^["']|["']$/gu, "").trim();
  }
  return data;
}

export function firstMarkdownSummary(body) {
  return String(body ?? "")
    .split(/\r?\n/u)
    .map((line) => line.trim())
    .find((line) => line && !line.startsWith("#") && !line.startsWith("-")) ?? null;
}

export function firstManifestField(frontmatter, keys) {
  for (const key of keys) {
    const value = String(frontmatter?.[key] ?? "").trim();
    if (value) return boundSkillText(value, 120);
  }
  return null;
}

export function boundSkillText(value, limit = CODEX_STDIO_SKILL_SUMMARY_LIMIT) {
  const text = redactText(String(value ?? "").replace(/\s+/gu, " ").trim());
  if (text.length <= limit) return text;
  return `${text.slice(0, Math.max(0, limit - 3))}...`;
}

export function dedupeSkills(items) {
  const seen = new Set();
  const deduped = [];
  for (const item of items) {
    const key = [
      String(item.name ?? "").toLowerCase(),
      item.sourceRoot ? path.resolve(item.sourceRoot) : "",
      item.manifest?.relativePath ?? (item.manifestPath ? path.basename(item.manifestPath) : "")
    ].join("\0");
    if (seen.has(key)) continue;
    seen.add(key);
    deduped.push(item);
  }
  return deduped;
}

export function notRequestedSkills() {
  return {
    status: "not_requested",
    items: [],
    count: 0,
    blockers: []
  };
}

export function codexReplyWithSidecar(content, sidecar = {}) {
  if (sidecar.intent?.skills && sidecar.skills?.status === "ready") {
    return skillsDiscoveryReply(sidecar.skills);
  }
  return String(content ?? "").trim();
}

export function skillsDiscoveryReply(skills) {
  const items = Array.isArray(skills?.items) ? skills.items : [];
  const listed = items.slice(0, CODEX_STDIO_SKILL_REPLY_LIMIT);
  const sourcePaths = Array.isArray(skills?.sources)
    ? skills.sources.map((source) => `${source.status}:${source.path}`).slice(0, 8)
    : [];
  const lines = [
    "这是当前 Codex stdio runner 读取到的真实 skill manifest 清单；没有使用文本 fallback。",
    `数量：本次返回 ${skills.count ?? listed.length} 个；已发现总数 ${skills.totalCount ?? items.length} 个${skills.truncated ? `；列表已截断到 ${skills.count ?? listed.length} 个` : ""}。`,
    sourcePaths.length ? `来源目录：${sourcePaths.join("；")}` : null,
    ""
  ].filter((line) => line !== null);
  for (const [index, item] of listed.entries()) {
    const metadata = [
      item.version ? `version=${item.version}` : null,
      item.commitId ? `commitId=${item.commitId}` : null,
      item.manifestPath ? `manifest=${item.manifestPath}` : item.source ? `source=${item.source}` : null
    ].filter(Boolean).join("；");
    lines.push(`${index + 1}. ${item.name}：${item.summary}${metadata ? `（${metadata}）` : ""}`);
  }
  if (items.length > listed.length) {
    lines.push(`其余 ${items.length - listed.length} 个 skill 未在正文展开，可在 payload.skills.items 查看。`);
  }
  return lines.join("\n").trim();
}

export function resolveWorkspaceTarget(workspace, target, { mustExist = false } = {}) {
  const rawTarget = String(target || ".").trim();
  if (!rawTarget) return { blocked: true, reason: "missing target path" };
  if (isForbiddenPath(rawTarget)) {
    return { blocked: true, reason: "security_blocked: target path may expose secrets or forbidden runtime material" };
  }
  const resolved = path.resolve(workspace, rawTarget);
  let realWorkspace = workspace;
  let realResolved = resolved;
  try {
    realWorkspace = realpathSync(workspace);
    realResolved = existsSync(resolved) ? realpathSync(resolved) : path.resolve(realWorkspace, path.relative(workspace, resolved));
  } catch {
    realWorkspace = path.resolve(workspace);
    realResolved = path.resolve(resolved);
  }
  if (!isPathInside(realResolved, realWorkspace)) {
    return { blocked: true, reason: "security_blocked: target path is outside the runner workspace" };
  }
  const relative = path.relative(realWorkspace, realResolved) || ".";
  if (isForbiddenPath(relative)) {
    return { blocked: true, reason: "security_blocked: target path is not allowed" };
  }
  return {
    path: realResolved,
    relative,
    async check() {
      if (!mustExist) return null;
      try {
        await stat(realResolved);
        return null;
      } catch {
        return "target path is not readable";
      }
    }
  };
}

export function isPathInside(child, parent) {
  const relative = path.relative(parent, child);
  return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
}

export function isForbiddenPath(value) {
  const normalized = String(value ?? "").replaceAll("\\", "/").toLowerCase();
  return /(^|\/)(?:\.env(?:\.|$)|\.npmrc$|\.pypirc$|id_rsa$|id_ed25519$|kubeconfig$|k3s\.yaml$|credentials?$|secrets?$|token(?:s)?$|database-url$)/u.test(normalized) ||
    /(?:secret|token|password|passwd|private[_-]?key|openai_api_key|database_url|kubeconfig)/u.test(normalized);
}

export function blockedToolCall({ name, type, workspace, traceId, reason }) {
  return {
    id: `tool_${randomUUID()}`,
    type,
    name,
    status: "blocked",
    cwd: workspace,
    exitCode: 1,
    stdout: "",
    stderrSummary: `security_blocked: ${redactText(reason)}`,
    outputTruncated: false,
    traceId
  };
}

export function safeDisplayPath(value) {
  return redactText(String(value ?? ".")).replace(/\s+/gu, " ");
}

export function buildCodexUserPrompt(message, { conversationId, traceId, conversationFacts, sidecar, gatewayShellTimeoutMs }) {
  return [
    `conversationId: ${conversationId}`,
    `traceId: ${traceId}`,
    `gatewayShellTimeoutMs: ${gatewayShellTimeoutMs}`,
    "",
    CODEX_STDIO_BOUNDARY_INSTRUCTIONS.replace(/<gatewayShellTimeoutMs>/gu, String(gatewayShellTimeoutMs)),
    codexConversationFactsPrompt(conversationFacts),
    codexSidecarSkillsPrompt(sidecar),
    "",
    "User message:",
    String(message ?? "").trim()
  ].filter((line) => line !== null && line !== undefined).join("\n");
}

export function normalizeGatewayShellTimeoutMs(value, env = process.env) {
  const parsed = Number.parseInt(value ?? env.HWLAB_GATEWAY_SHELL_TIMEOUT_MS ?? "", 10);
  if (!Number.isInteger(parsed) || parsed <= 0) return 120000;
  return Math.min(Math.max(parsed, 30000), 600000);
}

export function codexSidecarSkillsPrompt(sidecar) {
  if (!sidecar?.intent?.skills) return null;
  const skills = sidecar.skills;
  if (!skills || typeof skills !== "object") return null;
  if (skills.status !== "ready") {
    const blockers = Array.isArray(skills.blockers)
      ? skills.blockers.map((blocker) => safePromptFact(`${blocker?.code ?? "blocked"}:${blocker?.sourceIssue ?? ""}`)).slice(0, 4).join(",")
      : "skills_unavailable";
    return [
      "Current skills discovery facts (authoritative, bounded, redacted):",
      `- status=${safePromptFact(skills.status)}; count=0; blockers=${blockers}.`,
      "- Do not invent skill names. The API will return a structured skills_unavailable blocker."
    ].join("\n");
  }
  const items = Array.isArray(skills.items) ? skills.items.slice(0, 12) : [];
  return [
    "Current skills discovery facts (authoritative, bounded, redacted):",
    `- status=ready; returned=${safePromptFact(skills.count)}; total=${safePromptFact(skills.totalCount)}; truncated=${skills.truncated === true ? "true" : "false"}.`,
    ...items.map((item) => `- ${safePromptFact(item.name)} | ${safePromptFact(item.summary)} | version=${safePromptFact(item.version)} | commitId=${safePromptFact(item.commitId)} | manifest=${safePromptFact(item.manifestPath ?? item.source)}`)
  ].join("\n");
}

export function codexConversationFactsPrompt(conversationFacts) {
  if (!conversationFacts || typeof conversationFacts !== "object" || Number(conversationFacts.turnCount ?? 0) <= 0) {
    return null;
  }
  return [
    "Prior session facts (bounded, redacted; use for Chinese answers about previous turns):",
    `- sessionId=${safePromptFact(conversationFacts.sessionId)}; sessionStatus=${safePromptFact(conversationFacts.sessionStatus)}; sessionMode=${safePromptFact(conversationFacts.sessionMode)}; turnCount=${safePromptFact(conversationFacts.turnCount)}.`,
    `- provider/backend/runner=${safePromptFact(conversationFacts.latestProvider)}/${safePromptFact(conversationFacts.latestBackend)}/${safePromptFact(conversationFacts.runnerKind)}; capabilityLevel=${safePromptFact(conversationFacts.capabilityLevel)}.`,
    `- workspace=${safePromptFact(conversationFacts.workspace)}; sandbox=${safePromptFact(conversationFacts.sandbox)}.`,
    `- traceIds=${promptList(conversationFacts.traceIds, 6)}; latestTraceId=${safePromptFact(conversationFacts.latestTraceId)}.`,
    `- skills=${promptSkills(conversationFacts.latestSkills)}.`,
    `- toolCalls=${promptToolCalls(conversationFacts.recentToolCalls)}.`
  ].join("\n");
}

export function promptSkills(skills) {
  if (!skills || typeof skills !== "object") return "not_requested";
  const names = Array.isArray(skills.names) ? skills.names.filter(Boolean).slice(0, 8) : [];
  const count = skills.totalCount ?? skills.count ?? names.length;
  return `${safePromptFact(skills.status)} count=${safePromptFact(count)}${names.length ? ` names=${names.map(safePromptFact).join(",")}` : ""}`;
}

export function promptToolCalls(toolCalls) {
  if (!Array.isArray(toolCalls) || toolCalls.length === 0) return "none";
  return toolCalls
    .slice(-6)
    .map((toolCall) => [toolCall?.name, toolCall?.status, toolCall?.route].filter(Boolean).map(safePromptFact).join(":"))
    .filter(Boolean)
    .join(",") || "none";
}

export function promptList(values, limit) {
  return Array.isArray(values) && values.length > 0
    ? values.filter(Boolean).slice(-limit).map(safePromptFact).join(",")
    : "none";
}

export function safePromptFact(value) {
  if (value === undefined || value === null || value === "") return "none";
  return redactText(String(value).replace(/\s+/gu, " ").trim()).slice(0, 180);
}

export function resolveCodexWorkspace(env = process.env, options = {}) {
  return path.resolve(firstNonEmpty(
    options.workspace,
    env.HWLAB_CODE_AGENT_CODEX_WORKSPACE,
    env.HWLAB_CODE_AGENT_WORKSPACE,
    env.HWLAB_RUNNER_WORKSPACE,
    env.WORKSPACE,
    repoRoot
  ));
}

export function resolveCodexCommand(env = process.env, params = {}, options = {}) {
  const configured = firstNonEmpty(params.command, options.command, env.HWLAB_CODE_AGENT_CODEX_COMMAND);
  if (configured) return configured;

  const nodeModulesCodex = path.join(repoRoot, "node_modules", ".bin", "codex");
  if (existsSync(nodeModulesCodex)) return nodeModulesCodex;
  return DEFAULT_CODEX_STDIO_COMMAND;
}

export function resolveCodexSandbox(env = process.env, options = {}) {
  const value = firstNonEmpty(options.sandbox, env.HWLAB_CODE_AGENT_CODEX_SANDBOX, CODEX_STDIO_SANDBOX);
  return ["read-only", "workspace-write", "danger-full-access"].includes(value) ? value : CODEX_STDIO_SANDBOX;
}

export function resolveCodexHome(env = process.env) {
  return path.resolve(firstNonEmpty(
    env.CODEX_HOME,
    env.HWLAB_CODE_AGENT_CODEX_HOME,
    path.join(env.HOME || process.env.HOME || os.homedir(), ".codex")
  ));
}

export function codexStdioEnabled(env, params, options) {
  if (params.enabled === true || options.enabled === true) return true;
  return env.HWLAB_CODE_AGENT_CODEX_STDIO_ENABLED === "1" ||
    env.HWLAB_CODE_AGENT_CODEX_STDIO_ENABLED === "true";
}

export function supervisorState(env, params, options, enabled) {
  const configured = params.supervisorEnabled === true ||
    options.supervisorEnabled === true ||
    env.HWLAB_CODE_AGENT_CODEX_STDIO_SUPERVISOR === "repo-owned" ||
    env.HWLAB_CODE_AGENT_CODEX_STDIO_SUPERVISOR === "enabled" ||
    (enabled && env.HWLAB_CODE_AGENT_CODEX_STDIO_SUPERVISOR !== "disabled");
  return {
    configured,
    mode: configured ? "repo-owned-node-supervisor" : "disabled",
    processModel: "codex app-server --listen stdio://",
    cancelSupported: true,
    reapSupported: true,
    secretMaterialStored: false,
    valuesRedacted: true
  };
}

export function tokenBoundaryState(env = process.env, codexHomeFiles = null) {
  const authPresent = codexHomeFiles?.authPresent === true && codexHomeFiles?.authReadable === true;
  const present = authPresent ||
    env.HWLAB_CODE_AGENT_CODEX_TOKEN_BOUNDARY === "configured" ||
    env.HWLAB_CODE_AGENT_CODEX_TOKEN_BOUNDARY === "present";
  return {
    present,
    sources: [
      authPresent ? "CODEX_HOME/auth.json" : null,
      env.HWLAB_CODE_AGENT_CODEX_TOKEN_BOUNDARY ? "HWLAB_CODE_AGENT_CODEX_TOKEN_BOUNDARY" : null
    ].filter(Boolean),
    childEnvStripsProviderSecrets: true,
    secretMaterialRead: false,
    secretValuesPrinted: false,
    valuesRedacted: true
  };
}

export function egressState(env = process.env) {
  const baseUrl = firstNonEmpty(env.HWLAB_CODE_AGENT_OPENAI_BASE_URL, env.OPENAI_BASE_URL);
  const directPublicOpenAi = /^https:\/\/api\.openai\.com\/v1\/?/u.test(baseUrl);
  return {
    configured: Boolean(baseUrl),
    directPublicOpenAi,
    valueRedacted: true
  };
}

export function workspaceStateSync(workspace, sandbox) {
  const state = {
    exists: false,
    readable: false,
    writable: false,
    writeRequired: sandbox === "workspace-write"
  };
  try {
    state.exists = existsSync(workspace);
    if (!state.exists) return state;
    state.readable = accessSyncBoolean(workspace, fsConstants.R_OK);
    state.writable = accessSyncBoolean(workspace, fsConstants.W_OK);
    return state;
  } catch {
    return state;
  }
}

export function directoryStateSync(targetPath, { writableRequired = false } = {}) {
  const state = {
    path: targetPath,
    exists: false,
    readable: false,
    writable: false,
    writeRequired: writableRequired
  };
  try {
    state.exists = existsSync(targetPath);
    if (!state.exists) return state;
    state.readable = accessSyncBoolean(targetPath, fsConstants.R_OK);
    state.writable = accessSyncBoolean(targetPath, fsConstants.W_OK);
    return state;
  } catch {
    return state;
  }
}

export function codexHomeFilesStateSync(codexHome) {
  const configPath = path.join(codexHome, "config.toml");
  const authPath = path.join(codexHome, "auth.json");
  const configPresent = filePresentSync(configPath);
  const authPresent = filePresentSync(authPath);
  return {
    configPath,
    authPath,
    configPresent,
    configReadable: configPresent && accessSyncBoolean(configPath, fsConstants.R_OK),
    authPresent,
    authReadable: authPresent && accessSyncBoolean(authPath, fsConstants.R_OK),
    mountContract: "writable CODEX_HOME emptyDir with read-only config.toml/auth.json file mounts",
    secretMaterialRead: false,
    valuesRedacted: true
  };
}

export function filePresentSync(targetPath) {
  try {
    return existsSync(targetPath) && statSync(targetPath).isFile();
  } catch {
    return false;
  }
}

export function accessSyncBoolean(target, mode) {
  try {
    accessSync(target, mode);
    return true;
  } catch {
    return false;
  }
}

export function pathReadableSync(targetPath) {
  return accessSyncBoolean(targetPath, fsConstants.R_OK);
}

export function codexBinaryState(command, env = process.env) {
  const resolvedPath = commandPathSync(command, env);
  const present = Boolean(resolvedPath);
  const versionProbe = present ? codexVersionProbe(command, env) : {
    attempted: false,
    ok: false,
    version: null,
    error: null,
    exitCode: null
  };
  const nativeDependency = present ? codexNativeDependencyState(resolvedPath) : {
    present: false,
    path: null,
    evidence: "Codex CLI command was not present, so native dependency was not checked."
  };
  return {
    command,
    present,
    binaryPresent: present,
    path: resolvedPath,
    executable: versionProbe.ok,
    version: versionProbe.version,
    versionDetected: versionProbe.ok,
    versionProbe,
    nativeDependencyPresent: nativeDependency.present,
    nativeDependencyPath: nativeDependency.path,
    nativeDependencyEvidence: nativeDependency.evidence,
    nextEvidence: present
      ? versionProbe.ok
        ? "codex --version was probed without printing secrets; stdio protocol readiness is checked separately."
        : `Codex CLI command ${command} exists but --version did not complete successfully.`
      : `Install/provide a repo-controlled Codex CLI binary on PATH or set HWLAB_CODE_AGENT_CODEX_COMMAND to an approved binary path; checked command=${command}.`,
    secretMaterialRead: false,
    valuesRedacted: true
  };
}

export function codexNativeDependencyState(resolvedCommandPath) {
  const candidates = [];
  let resolved = path.resolve(resolvedCommandPath);
  try {
    resolved = realpathSync(resolved);
  } catch {
    // Fall back to the resolved command path; the binary blocker will report executability.
  }
  const packageRoot = findPackageRoot(resolved);
  if (packageRoot) {
    candidates.push(
      path.join(packageRoot, "..", "codex-linux-x64", "vendor", "x86_64-unknown-linux-musl", "codex", "codex"),
      path.join(packageRoot, "..", "codex-linux-arm64", "vendor", "aarch64-unknown-linux-musl", "codex", "codex"),
      path.join(packageRoot, "vendor", "x86_64-unknown-linux-musl", "codex", "codex"),
      path.join(packageRoot, "vendor", "aarch64-unknown-linux-musl", "codex", "codex")
    );
  }
  const existing = candidates.find((candidate) => existsSync(candidate) && accessSyncBoolean(candidate, fsConstants.X_OK)) ?? null;
  return {
    present: Boolean(existing),
    path: existing,
    evidence: existing
      ? "Native @openai/codex executable dependency exists and is executable."
      : "Expected native @openai/codex executable dependency was not found next to the CLI package."
  };
}

export function findPackageRoot(resolvedCommandPath) {
  let current = path.dirname(resolvedCommandPath);
  for (let depth = 0; depth < 8; depth += 1) {
    if (existsSync(path.join(current, "package.json"))) return current;
    const next = path.dirname(current);
    if (next === current) return null;
    current = next;
  }
  return null;
}

export function commandPathSync(command, env = process.env) {
  if (!command) return null;
  if (command.includes("/") || command.includes("\\")) {
    return existsSync(command) ? command : null;
  }
  const paths = String(Object.hasOwn(env, "PATH") ? env.PATH : process.env.PATH || "").split(path.delimiter).filter(Boolean);
  const resolved = paths.map((dir) => path.join(dir, command)).find((candidate) => existsSync(candidate));
  return resolved ?? null;
}

export function commandOnPathSync(command, env = process.env) {
  return Boolean(commandPathSync(command, env));
}

export function codexVersionProbe(command, env = process.env) {
  try {
    const result = spawnSync(command, ["--version"], {
      env: childProcessEnv(env),
      encoding: "utf8",
      timeout: 3000,
      windowsHide: true
    });
    const output = redactText(`${result.stdout ?? ""}\n${result.stderr ?? ""}`).trim();
    const version = output.split(/\s+/u).find((part) => /\d+\.\d+(?:\.\d+)?/u.test(part)) ?? (output ? tailText(output, 160) : null);
    return {
      attempted: true,
      ok: result.status === 0 && Boolean(version),
      version,
      exitCode: result.status,
      error: result.error ? redactText(result.error.message) : null
    };
  } catch (error) {
    return {
      attempted: true,
      ok: false,
      version: null,
      exitCode: null,
      error: redactText(error.message)
    };
  }
}

export function protocolState({ command, binary, supervisor, initialized }) {
  const protocolInitialized = initialized === true;
  const wired = binary.present === true && supervisor.configured === true && protocolInitialized;
  const probeReady = binary.present === true && supervisor.configured === true;
  return {
    transport: "stdio",
    command: `${command} app-server --listen stdio://`,
    protocolVersion: CODEX_APP_SERVER_PROTOCOL,
    adapter: "Codex app-server JSON-RPC line protocol",
    requiredMethods: [...CODEX_APP_SERVER_REQUIRED_METHODS],
    initialized: protocolInitialized,
    toolsObserved: [],
    missingTools: [],
    wired,
    probeReady,
    status: wired ? "wired" : "blocked",
    blocker: wired ? null : "stdio_protocol_not_wired",
    summary: wired
      ? "Codex app-server stdio protocol is initialized through the repo-owned JSON-RPC client."
      : "Codex app-server stdio protocol has not been proven through initialize/thread/start/turn/start.",
    nextEvidence: "Start `codex app-server --listen stdio://`, run initialize, then thread/start or thread/resume plus turn/start without printing token material.",
    secretMaterialRead: false,
    valuesRedacted: true
  };
}

export function cachedProtocolReady({ command, workspace, codexHome, probe }) {
  if (!probe || probe.ready !== true) return null;
  if (probe.command !== command || probe.workspace !== workspace || probe.codexHome !== codexHome) return null;
  if (Number.isFinite(probe.expiresAtMs) && probe.expiresAtMs <= Date.now()) return null;
  return probe.initialized === true;
}

export function cachedCommandProbe({ command, workspace, codexHome, probe }) {
  if (!probe || probe.ready !== true) return null;
  if (probe.command !== command || probe.workspace !== workspace || probe.codexHome !== codexHome) return null;
  if (Number.isFinite(probe.expiresAtMs) && probe.expiresAtMs <= Date.now()) return null;
  return probe;
}

export function protocolProbeSummary({ command, workspace, codexHome, probe }) {
  if (!probe || probe.command !== command || probe.workspace !== workspace || probe.codexHome !== codexHome) {
    return {
      status: "not_run",
      ready: false,
      initialized: false,
      requiredMethods: [...CODEX_APP_SERVER_REQUIRED_METHODS],
      secretMaterialRead: false,
      valuesRedacted: true
    };
  }
  return {
    status: probe.status,
    ready: probe.ready === true,
    initialized: probe.initialized === true,
    requiredMethods: Array.isArray(probe.requiredMethods) ? [...probe.requiredMethods] : [...CODEX_APP_SERVER_REQUIRED_METHODS],
    startedAt: probe.startedAt,
    finishedAt: probe.finishedAt,
    error: probe.error ?? null,
    secretMaterialRead: false,
    valuesRedacted: true
  };
}

export function commandProbeSummary({ command, workspace, codexHome, probe }) {
  if (!probe || probe.command !== command || probe.workspace !== workspace || probe.codexHome !== codexHome) {
    return {
      status: "not_run",
      ready: false,
      probe: "workspace.pwd",
      toolCalls: [],
      secretMaterialRead: false,
      valuesRedacted: true
    };
  }
  return {
    status: probe.status,
    ready: probe.ready === true,
    probe: probe.probe ?? "workspace.pwd",
    conversationId: probe.conversationId ?? null,
    traceId: probe.traceId ?? null,
    toolCalls: Array.isArray(probe.toolCalls) ? [...probe.toolCalls] : [],
    startedAt: probe.startedAt,
    finishedAt: probe.finishedAt,
    error: probe.error ?? null,
    timeoutMs: probe.timeoutMs ?? CODEX_STDIO_COMMAND_PROBE_TIMEOUT_MS,
    secretMaterialRead: false,
    valuesRedacted: true
  };
}

export function lifecycleState({ supervisor, idleTimeoutMs, maxSessions, activeSessions }) {
  const ready = supervisor.configured === true;
  return {
    implementationType: CODEX_STDIO_IMPLEMENTATION_TYPE,
    status: ready ? "present" : "blocked",
    present: ready,
    create: ready,
    reuse: ready,
    cancel: ready,
    reap: ready,
    idleTimeout: ready,
    idleTimeoutMs,
    traceCapture: ready,
    maxSessions,
    activeSessions,
    inMemoryIndex: ready,
    codexThreadIdCaptured: ready,
    blocker: ready ? null : "runner_lifecycle_missing",
    summary: ready
      ? "Repo-owned supervisor contract covers create/reuse/cancel/reap/idle timeout/trace capture."
      : "Repo-owned lifecycle supervisor is missing; long-lived Code Agent session gate must remain blocked.",
    secretMaterialStored: false,
    valuesRedacted: true
  };
}

export function workspaceContractState(workspaceInfo, sandbox, workspace) {
  const ready = workspaceInfo.exists && workspaceInfo.readable && (sandbox !== "workspace-write" || workspaceInfo.writable === true);
  return {
    path: workspace,
    status: ready ? "ready" : "blocked",
    mounted: workspaceInfo.exists,
    readable: workspaceInfo.readable,
    writable: workspaceInfo.writable,
    sandbox,
    writeRequired: sandbox === "workspace-write",
    blocker: ready ? null : "workspace_mount_missing",
    secretMaterialRead: false,
    valuesRedacted: true
  };
}

export function cancelReapTraceState(lifecycle) {
  const ready = lifecycle.cancel === true && lifecycle.reap === true && lifecycle.traceCapture === true;
  return {
    status: ready ? "ready" : "blocked",
    cancel: lifecycle.cancel === true,
    reap: lifecycle.reap === true,
    traceCapture: lifecycle.traceCapture === true,
    idleTimeout: lifecycle.idleTimeout === true,
    blocker: ready ? null : "runner_lifecycle_missing"
  };
}

export function runtimeContract({
  ready,
  binary,
  protocol,
  lifecycle,
  workspaceInfo,
  workspace,
  sandbox,
  codexHome,
  codexHomeInfo,
  codexHomeFiles,
  tokenBoundary,
  egress,
  childEnvBoundary,
  commandProbe
}) {
  return {
    contractVersion: "codex-runtime-feasibility-v1",
    status: ready ? "ready" : "blocked",
    ready,
    binary: {
      status: binary.present && binary.executable && binary.nativeDependencyPresent ? "present" : binary.present ? "blocked" : "missing",
      command: binary.command,
      present: binary.present,
      executable: binary.executable === true,
      nativeDependencyPresent: binary.nativeDependencyPresent === true,
      version: binary.version,
      versionDetected: binary.versionDetected,
      nextEvidence: binary.nextEvidence
    },
    stdioProtocol: {
      status: protocol.status,
      wired: protocol.wired,
      command: protocol.command,
      protocolVersion: protocol.protocolVersion,
      requiredMethods: [...protocol.requiredMethods],
      initialized: protocol.initialized === true,
      nextEvidence: protocol.nextEvidence
    },
    commandProbe: commandProbe ?? {
      status: "not_run",
      ready: false,
      probe: "workspace.pwd",
      toolCalls: [],
      secretMaterialRead: false,
      valuesRedacted: true
    },
    lifecycleSupervisor: {
      status: lifecycle.status,
      present: lifecycle.present,
      create: lifecycle.create,
      reuse: lifecycle.reuse,
      cancel: lifecycle.cancel,
      reap: lifecycle.reap,
      traceCapture: lifecycle.traceCapture,
      idleTimeoutMs: lifecycle.idleTimeoutMs
    },
    workspaceMount: workspaceContractState(workspaceInfo, sandbox, workspace),
    codexHome: codexHomeContractState(codexHomeInfo, codexHome, codexHomeFiles),
    sandbox,
    tokenBoundary: {
      status: tokenBoundary.present ? "present" : "blocked",
      present: tokenBoundary.present,
      sources: tokenBoundary.sources,
      secretMaterialRead: false,
      valuesRedacted: true
    },
    egress: {
      status: egress.directPublicOpenAi ? "blocked" : egress.configured ? "configured" : "not_configured",
      configured: egress.configured,
      directPublicOpenAi: egress.directPublicOpenAi,
      valueRedacted: true
    },
    childEnvBoundary,
    cancelReapTraceReadiness: cancelReapTraceState(lifecycle),
    secretBoundary: {
      secretsRead: false,
      secretValuesPrinted: false,
      kubeconfigRead: false,
      valuesRedacted: true
    }
  };
}

export function codexHomeContractState(codexHomeInfo, codexHome, codexHomeFiles = null) {
  const filesReady = codexHomeFiles?.configPresent === true &&
    codexHomeFiles?.configReadable === true &&
    codexHomeFiles?.authPresent === true &&
    codexHomeFiles?.authReadable === true;
  const ready = codexHomeInfo.exists && codexHomeInfo.readable && codexHomeInfo.writable && filesReady;
  return {
    path: codexHome,
    status: ready ? "ready" : "blocked",
    exists: codexHomeInfo.exists,
    readable: codexHomeInfo.readable,
    writable: codexHomeInfo.writable,
    configToml: {
      path: codexHomeFiles?.configPath ?? path.join(codexHome, "config.toml"),
      present: codexHomeFiles?.configPresent === true,
      readable: codexHomeFiles?.configReadable === true
    },
    authJson: {
      path: codexHomeFiles?.authPath ?? path.join(codexHome, "auth.json"),
      present: codexHomeFiles?.authPresent === true,
      readable: codexHomeFiles?.authReadable === true,
      valuesRedacted: true
    },
    writeRequired: true,
    blocker: ready
      ? null
      : !codexHomeInfo.exists || !codexHomeInfo.readable
        ? "codex_home_missing"
        : codexHomeInfo.writable !== true
          ? "codex_home_write_blocked"
          : codexHomeFiles?.configPresent !== true || codexHomeFiles?.configReadable !== true
            ? "codex_home_config_missing"
            : "codex_home_auth_missing",
    secretMaterialRead: false,
    valuesRedacted: true
  };
}

export function childProcessEnv(env = process.env) {
  const noProxy = mergeNoProxy(env.NO_PROXY, env.no_proxy, CODEX_CHILD_NO_PROXY_REQUIRED, codexProviderNoProxyEntries(env));
  return {
    PATH: Object.hasOwn(env, "PATH") ? env.PATH : process.env.PATH || "/usr/local/bin:/usr/bin:/bin",
    HOME: env.HOME || process.env.HOME || os.homedir(),
    CODEX_HOME: resolveCodexHome(env),
    CODEX_INTERNAL_ORIGINATOR_OVERRIDE: "hwlab_code_agent",
    UNIDESK_SKILLS_PATH: "/app/skills",
    ...(env.HWLAB_CLOUD_API_URL ? { HWLAB_CLOUD_API_URL: env.HWLAB_CLOUD_API_URL } : {}),
    ...(env.HWLAB_RUNTIME_API_URL ? { HWLAB_RUNTIME_API_URL: env.HWLAB_RUNTIME_API_URL } : {}),
    ...(env.HWLAB_RUNTIME_WEB_URL ? { HWLAB_RUNTIME_WEB_URL: env.HWLAB_RUNTIME_WEB_URL } : {}),
    ...(env.HWLAB_RUNTIME_NAMESPACE ? { HWLAB_RUNTIME_NAMESPACE: env.HWLAB_RUNTIME_NAMESPACE } : {}),
    ...(env.HWLAB_RUNTIME_LANE ? { HWLAB_RUNTIME_LANE: env.HWLAB_RUNTIME_LANE } : {}),
    ...(env.HWLAB_RUNTIME_ENDPOINT_SOURCE ? { HWLAB_RUNTIME_ENDPOINT_SOURCE: env.HWLAB_RUNTIME_ENDPOINT_SOURCE } : {}),
    ...(env.HWLAB_RUNTIME_ENDPOINT_LOCKED ? { HWLAB_RUNTIME_ENDPOINT_LOCKED: env.HWLAB_RUNTIME_ENDPOINT_LOCKED } : {}),
    ...(env.HWLAB_CODE_AGENT_ASSEMBLED_RUNTIME ? { HWLAB_CODE_AGENT_ASSEMBLED_RUNTIME: env.HWLAB_CODE_AGENT_ASSEMBLED_RUNTIME } : {}),
    ...(env.HWLAB_API_KEY ? { HWLAB_API_KEY: env.HWLAB_API_KEY } : {}),
    NO_PROXY: noProxy,
    no_proxy: noProxy,
    ...(env.LANG ? { LANG: env.LANG } : {}),
    ...(env.LC_ALL ? { LC_ALL: env.LC_ALL } : {}),
    ...(env.SSL_CERT_FILE ? { SSL_CERT_FILE: env.SSL_CERT_FILE } : {}),
    ...(env.NODE_EXTRA_CA_CERTS ? { NODE_EXTRA_CA_CERTS: env.NODE_EXTRA_CA_CERTS } : {}),
    ...(env.GIT_CONFIG_COUNT ? gitConfigEnv(env) : {})
  };
}

export function gitConfigEnv(env = process.env) {
  const count = Number.parseInt(env.GIT_CONFIG_COUNT ?? "", 10);
  if (!Number.isInteger(count) || count < 0 || count > 64) return {};
  const result = { GIT_CONFIG_COUNT: String(count) };
  for (let index = 0; index < count; index += 1) {
    const keyName = `GIT_CONFIG_KEY_${index}`;
    const valueName = `GIT_CONFIG_VALUE_${index}`;
    if (typeof env[keyName] === "string") result[keyName] = env[keyName];
    if (typeof env[valueName] === "string") result[valueName] = env[valueName];
  }
  return result;
}

export function childProcessEnvState(env = process.env) {
  const child = childProcessEnv(env);
  const inheritedForbidden = CODEX_CHILD_STRIPPED_ENV_KEYS.filter((key) => hasEnvValue(child, key));
  const sourceForbiddenPresent = CODEX_CHILD_STRIPPED_ENV_KEYS.filter((key) => hasEnvValue(env, key));
  const noProxyEntries = splitNoProxy(child.NO_PROXY);
  const requiredMissing = CODEX_CHILD_NO_PROXY_REQUIRED.filter((entry) => !noProxyEntries.includes(entry.toLowerCase()));
  const providerNoProxy = codexProviderNoProxyEntries(env);
  return {
    status: inheritedForbidden.length === 0 && requiredMissing.length === 0 ? "ready" : "blocked",
    forbiddenSourceEnvKeysPresent: sourceForbiddenPresent,
    forbiddenEnvPresent: inheritedForbidden.length > 0,
    strippedEnvKeys: sourceForbiddenPresent.filter((key) => !inheritedForbidden.includes(key)),
    inheritedForbiddenEnvKeys: inheritedForbidden,
    noProxy: {
      present: Boolean(child.NO_PROXY),
      required: [...CODEX_CHILD_NO_PROXY_REQUIRED],
      provider: providerNoProxy,
      missing: requiredMissing,
      merged: noProxyEntries
    },
    codeXHome: child.CODEX_HOME,
    secretMaterialRead: false,
    valuesRedacted: true
  };
}

export function codexProviderNoProxyEntries(env = process.env) {
  const baseUrl = codexAppServerProviderBaseUrl(env);
  if (!baseUrl) return [];
  try {
    const url = new URL(baseUrl);
    const hostname = url.hostname.replace(/^\[|\]$/gu, "");
    return [hostname, url.host].filter((entry, index, items) => entry && items.indexOf(entry) === index);
  } catch {
    return [];
  }
}

export function rpcClientConfigSignature({ availability, args = [], childEnv = {} } = {}) {
  return JSON.stringify({
    command: availability?.command ?? null,
    workspace: availability?.workspace ?? null,
    codexHome: childEnv.CODEX_HOME ?? null,
    args
  });
}

export function mergeNoProxy(...parts) {
  const entries = [];
  const seen = new Set();
  for (const entry of parts.flatMap((part) => Array.isArray(part) ? part : splitNoProxy(part))) {
    const normalized = String(entry ?? "").trim();
    if (!normalized) continue;
    const key = normalized.toLowerCase();
    if (seen.has(key)) continue;
    seen.add(key);
    entries.push(normalized);
  }
  return entries.join(",");
}

export function splitNoProxy(value) {
  return String(value ?? "")
    .split(/[,;\s]+/u)
    .map((item) => item.trim())
    .filter(Boolean);
}

export function codexStdioSafety() {
  return {
    secretsRead: false,
    secretValuesPrinted: false,
    kubeconfigRead: false,
    prodTouched: false,
    hardwareWritesAllowed: true,
    directGatewayCallsAllowed: true,
    directBoxSimuCallsAllowed: true,
    directPatchPanelCallsAllowed: true,
    hardwareControlPath: "codex-stdio-full-access",
    valuesRedacted: true
  };
}

export function codexStdioError(code, message, details = {}) {
  const error = new Error(message);
  error.code = code;
  Object.assign(error, {
    provider: CODEX_STDIO_PROVIDER,
    backend: CODEX_STDIO_BACKEND,
    capabilityLevel: "blocked",
    ...details
  });
  return error;
}

export function sessionExpired(session, timestampMs) {
  if (!Number.isFinite(timestampMs) || session.status === "expired") return session.status === "expired";
  return Date.parse(session.expiresAt) <= timestampMs;
}

export function timestampFor(now) {
  const value = typeof now === "function" ? now() : now;
  if (typeof value === "string" && !Number.isNaN(Date.parse(value))) return value;
  if (value instanceof Date && !Number.isNaN(value.valueOf())) return value.toISOString();
  return new Date().toISOString();
}

export function plusMs(timestampMs, offsetMs) {
  return new Date(timestampMs + offsetMs).toISOString();
}

export function requiredId(value, fallbackPrefix) {
  const id = optionalId(value);
  if (id) return id;
  return `${fallbackPrefix}_${randomUUID()}`;
}

export function optionalId(value) {
  return typeof value === "string" && value.trim() ? value.trim() : null;
}

export function positiveInteger(value, fallback) {
  return Number.isInteger(value) && value > 0 ? value : fallback;
}

export function firstNonEmpty(...values) {
  for (const value of values) {
    if (typeof value === "string" && value.trim()) return value.trim();
  }
  return "";
}

export function hasEnvValue(env, name) {
  return typeof env?.[name] === "string" && env[name].trim().length > 0;
}

export function effectiveTimeout(timeoutMs) {
  return effectiveActivityTimeout(timeoutMs);
}

export function effectiveActivityTimeout(timeoutMs) {
  return Number.isInteger(timeoutMs) && timeoutMs > 0 ? timeoutMs : DEFAULT_CODEX_STDIO_TURN_ACTIVITY_TIMEOUT_MS;
}

export function effectiveHardTimeout(hardTimeoutMs, activityTimeoutMs = null) {
  const activityMs = effectiveActivityTimeout(activityTimeoutMs);
  const configured = Number.isInteger(hardTimeoutMs) && hardTimeoutMs > 0
    ? hardTimeoutMs
    : DEFAULT_CODEX_STDIO_TURN_HARD_TIMEOUT_MS;
  return Math.max(configured, activityMs);
}

export function dropEmpty(value) {
  return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined && item !== null && item !== ""));
}

export function parseJsonOrNull(value) {
  try {
    return JSON.parse(value);
  } catch {
    return null;
  }
}

export function tailText(value, maxLength = 1200) {
  const text = String(value ?? "").trim();
  if (text.length <= maxLength) return text;
  return text.slice(text.length - maxLength);
}

export function boundToolOutput(value, maxLength = CODEX_STDIO_TOOL_OUTPUT_LIMIT) {
  const text = String(value ?? "");
  if (text.length <= maxLength) return { text, truncated: false };
  return {
    text: `${text.slice(0, maxLength)}\n[output truncated at ${maxLength} bytes]`,
    truncated: true
  };
}

export function redactText(value) {
  return String(value ?? "")
    .replace(/Bearer\s+[A-Za-z0-9._~+/=-]+/gu, "Bearer ***")
    .replace(/sk-[A-Za-z0-9._-]+/gu, "sk-***")
    .replace(/([A-Za-z0-9_]*TOKEN[A-Za-z0-9_]*=)[^\s]+/giu, "$1***")
    .replace(/([A-Za-z0-9_]*KEY[A-Za-z0-9_]*=)[^\s]+/giu, "$1***");
}
