const fs = require('node:fs');
const path = require('node:path');
const crypto = require('node:crypto');
const Database = require('better-sqlite3');
const { getAgentRuntimeDir, getBundledOpencodeBinaryPath } = require('../../utils/paths.cjs');
const { trackAgentRuntime } = require('../agent/agentRuntimeAnalytics.cjs');
const { startOpenCodeSidecar, closeOpenCodeSidecar } = require('./opencodeServerRunner.cjs');
const { createSession, sendPrompt, getSessionDiff } = require('./opencodeHttpClient.cjs');
const { writeAgentInstructionsFile } = require('../agent/agentToolEnvironment.cjs');
const {
  SELF_CHECK_TASK_ID,
  SELF_CHECK_OUTPUT_FILE,
  SELF_CHECK_TIMEOUT_MS,
  buildSelfCheckPrompt,
  compactSelfCheckError,
  createOpenCodeDiagnosticSections,
  createEnvironmentSnapshot,
  createSelfCheckConclusion,
  createSelfCheckLogger,
  createSelfCheckSteps,
  formatSelfCheckDetails,
  getCurrentSelfCheckStage,
  runIntegratedToolSelfCheck,
  runDirectModelSelfCheck,
  safeStat,
  snapshotWorkspace,
  summarizeTextModelConfig,
  updateSelfCheckStep,
  validateSelfCheckOutput,
} = require('./opencodeSelfCheckService.cjs');

const DEFAULT_AGENT_IDLE_TIMEOUT_MS = 10 * 60 * 1000;
const DEFAULT_PROVIDER_TIMEOUT_MS = 30 * 60 * 1000;
const HEALTH_INTERVAL_MS = 30 * 1000;
const HEALTH_FAILURE_LIMIT = 3;
const STATUS_TICK_MS = 1000;
const WORKSPACE_WATCH_INTERVAL_MS = 2000;
const OPENCODE_EVENT_POLL_INTERVAL_MS = 1000;
const OPENCODE_EVENT_BATCH_LIMIT = 120;
const BUSY_MESSAGE = 'Agent 正在处理其他任务，请耐心等待';
const DEFAULT_AGENT_MAX_RETRIES = 1;
const MAX_AGENT_MAX_RETRIES = 3;

function nowIso() {
  return new Date().toISOString();
}

function normalizeTimeoutMs(value, fallback = DEFAULT_AGENT_IDLE_TIMEOUT_MS) {
  const number = Number(value);
  return Number.isFinite(number) && number > 0 ? Math.floor(number) : fallback;
}

function normalizeMaxRetries(value, fallback = DEFAULT_AGENT_MAX_RETRIES) {
  const number = Number(value);
  if (!Number.isFinite(number)) return fallback;
  return Math.max(0, Math.min(MAX_AGENT_MAX_RETRIES, Math.floor(number)));
}

function extractTextFromPromptResult(result) {
  const parts = Array.isArray(result?.parts) ? result.parts : [];
  return parts
    .filter((part) => part?.type === 'text' && typeof part.text === 'string')
    .map((part) => part.text)
    .join('\n')
    .trim();
}

function markAgentValidationError(error) {
  if (error && typeof error === 'object') {
    error.agentValidationFailed = true;
  }
  return error;
}

function compactErrorText(error, maxLength = 1200) {
  const lines = [
    error?.message || String(error || '未知错误'),
    error?.cause?.message || error?.cause?.code || error?.openCodeCause || '',
  ].filter(Boolean);
  const text = lines.join('\n').trim();
  return text.length > maxLength ? `${text.slice(0, maxLength - 1)}…` : text;
}

function buildAgentRetryPrompt({ outputFile, attempt, maxRetries, error }) {
  return `上一轮 Agent 执行没有通过程序校验或执行过程中失败。

失败信息：
${compactErrorText(error)}

请继续使用当前会话和当前工作区已有文件，不要重新开始任务，不要清空工作区。
请先检查 ${outputFile} 和必要的输入文件，定位失败原因，只做必要修复。
修复后仍然把最终结果写入 ${outputFile}。

这是第 ${attempt}/${maxRetries} 次自动修复机会。`;
}

function createRetryAttemptSummary({ attempt, error, outputContent }) {
  return {
    attempt,
    at: nowIso(),
    error: compactErrorText(error, 600),
    output_chars: String(outputContent || '').length,
  };
}

function safeRelativePath(value) {
  const raw = String(value || '').replace(/\\/g, '/').replace(/^\/+/, '');
  if (!raw || raw.includes('..')) {
    throw new Error(`非法文件路径：${value}`);
  }
  const lower = raw.toLowerCase();
  const reserved =
    lower === 'opencode.json'
    || lower === 'opencode.jsonc'
    || lower === 'agents.md'
    || lower === 'claude.md'
    || lower.startsWith('.opencode/')
    || lower.startsWith('.config/opencode/')
    || lower.startsWith('.claude/');
  if (reserved) {
    throw new Error(`OpenCode 保留路径或指令文件不允许作为任务输入：${value}`);
  }
  return raw;
}

function safeTaskPathSegment(value) {
  return String(value || crypto.randomUUID())
    .replace(/[^a-zA-Z0-9_-]/g, '_')
    .replace(/^_+|_+$/g, '')
    .slice(0, 120) || crypto.randomUUID();
}

function ensureInsideRoot(rootDir, targetPath, sourcePath) {
  const resolvedRoot = path.resolve(rootDir);
  const resolvedTarget = path.resolve(targetPath);
  if (resolvedTarget !== resolvedRoot && !resolvedTarget.startsWith(`${resolvedRoot}${path.sep}`)) {
    throw new Error(`文件路径越界：${sourcePath}`);
  }
  return resolvedTarget;
}

function writeWorkspaceFiles(workspaceDir, files = []) {
  fs.mkdirSync(workspaceDir, { recursive: true });
  files.forEach((file) => {
    const relativePath = safeRelativePath(file.path);
    const targetPath = ensureInsideRoot(workspaceDir, path.join(workspaceDir, relativePath), file.path);
    fs.mkdirSync(path.dirname(targetPath), { recursive: true });
    fs.writeFileSync(targetPath, String(file.content || ''), 'utf-8');
  });
}

function clearDirectoryContents(dir) {
  fs.mkdirSync(dir, { recursive: true });
  for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
    fs.rmSync(path.join(dir, entry.name), { recursive: true, force: true });
  }
}

function createDefaultAgentPrompt({ task, outputFile }) {
  return `请只在当前工作目录内工作。

任务：
${task}

要求：
1. 先阅读当前目录中的输入文件。
2. 自主判断下一步需要做什么。
3. 如需产出结果，请写入 ${outputFile}。
4. 不要访问当前工作目录外的文件。
5. 不要联网。
6. 最终回复请包含：发现的问题、处理动作、输出文件路径。`;
}

function readOutputContent(workspaceDir, outputFile) {
  const relativePath = safeRelativePath(outputFile);
  const outputPath = ensureInsideRoot(workspaceDir, path.join(workspaceDir, relativePath), outputFile);
  return {
    path: outputPath,
    content: fs.existsSync(outputPath) ? fs.readFileSync(outputPath, 'utf-8') : '',
  };
}

function annotateAgentError(error, meta = {}) {
  if (!error || typeof error !== 'object') return error;
  error.agentTaskId = meta.taskId || error.agentTaskId || '';
  error.agentTitle = meta.title || error.agentTitle || '';
  error.agentWorkspaceDir = meta.workspaceDir || error.agentWorkspaceDir || '';
  error.agentRuntimeRoot = meta.runtimeRoot || error.agentRuntimeRoot || '';
  error.agentOutputFile = meta.outputFile || error.agentOutputFile || '';
  error.agentOutputPath = meta.outputPath || error.agentOutputPath || '';
  error.agentPartialOutput = meta.outputContent || error.agentPartialOutput || '';
  error.agentPartialOutputChars = String(meta.outputContent || error.agentPartialOutput || '').length;
  error.agentValidationFailed = Boolean(error.agentValidationFailed);
  error.agentRetryAttempts = Array.isArray(error.agentRetryAttempts) ? error.agentRetryAttempts : [];
  error.openCodeRequestLog = Array.isArray(meta.requestLog) ? meta.requestLog : error.openCodeRequestLog || [];
  error.openCodeStderrTail = meta.stderrTail || error.openCodeStderrTail || '';
  error.openCodeStdoutTail = meta.stdoutTail || error.openCodeStdoutTail || '';
  return error;
}

function isUserCancelOrPause(error) {
  const code = error?.code || error?.cause?.code;
  const message = String(error?.message || error || '');
  return code === 'CONTENT_GENERATION_PAUSED'
    || code === 'AI_QUEUE_SCOPE_PAUSED'
    || code === 'ABORT_ERR'
    || message === 'CONTENT_GENERATION_PAUSED'
    || message.includes('请求已取消')
    || message.includes('任务已取消');
}

function isWatchdogStall(error) {
  return error?.code === 'AGENT_STALLED';
}

function createStallError() {
  const error = new Error('Agent 长时间无进展，已停止本轮任务');
  error.code = 'AGENT_STALLED';
  return error;
}

function createSelfCheckStageError(stage, message) {
  const error = new Error(message);
  error.selfCheckStage = stage;
  return error;
}

function parseJsonObject(raw) {
  if (!raw || typeof raw !== 'string') return null;
  try {
    const value = JSON.parse(raw);
    return value && typeof value === 'object' ? value : null;
  } catch {
    return null;
  }
}

function compactActivityText(value, maxLength = 180) {
  const text = String(value || '').replace(/\s+/g, ' ').trim();
  if (!text) return '';
  return text.length > maxLength ? `${text.slice(0, maxLength - 1)}…` : text;
}

function basenameFromAnyPath(value) {
  const normalized = String(value || '').replace(/\\/g, '/');
  const parts = normalized.split('/').filter(Boolean);
  return parts[parts.length - 1] || normalized;
}

function formatTodoDetail(input) {
  const todos = Array.isArray(input?.todos) ? input.todos : [];
  const current = todos.find((item) => item?.status === 'in_progress')
    || todos.find((item) => item?.status === 'pending')
    || todos.find((item) => item?.status === 'completed')
    || todos[0];
  return compactActivityText(current?.content || '', 120);
}

function formatToolDetail(tool, input) {
  if (!input || typeof input !== 'object') return '';
  if (tool === 'read') return basenameFromAnyPath(input.filePath || input.path || input.file || '');
  if (tool === 'write') return basenameFromAnyPath(input.filePath || input.path || input.file || '');
  if (tool === 'edit' || tool === 'multiedit') return basenameFromAnyPath(input.filePath || input.path || input.file || '');
  if (tool === 'glob') return compactActivityText(input.pattern || input.path || '', 120);
  if (tool === 'grep') return compactActivityText(input.pattern || input.query || input.include || '', 120);
  if (tool === 'bash') return compactActivityText(input.description || input.command || '', 140);
  if (tool === 'todowrite') return formatTodoDetail(input);
  return compactActivityText(input.filePath || input.path || input.pattern || input.query || input.description || '', 120);
}

function formatToolActivity(part) {
  const tool = String(part?.tool || '').trim();
  if (!tool) return '';
  const state = part?.state && typeof part.state === 'object' ? part.state : {};
  const status = String(state.status || '').trim();
  const input = state.input && typeof state.input === 'object' ? state.input : {};
  const labels = {
    bash: '执行命令',
    edit: '编辑文件',
    glob: '查找文件',
    grep: '搜索内容',
    multiedit: '批量编辑文件',
    read: '读取文件',
    todowrite: '更新任务清单',
    write: '写入文件',
  };
  const label = labels[tool] || `调用工具 ${tool}`;
  const detail = formatToolDetail(tool, input);
  const suffix = detail ? `：${detail}` : '';

  if (status === 'pending' && !detail) return '';
  if (status === 'completed') return `${label}完成${suffix}`;
  if (status === 'error') return `${label}失败${suffix}`;
  if (status === 'running' || status === 'pending') return `${label}中${suffix}`;
  return `${label}${suffix}`;
}

function formatOpenCodePartActivity(part) {
  const type = String(part?.type || '').trim();
  if (type === 'tool') return formatToolActivity(part);
  if (type === 'text') return compactActivityText(part?.text || '', 200);
  return '';
}

function getOpenCodePartStage(part) {
  const type = String(part?.type || '').trim();
  if (type === 'tool') return 'tool';
  if (type === 'text') return 'assistant_text';
  if (type === 'step-start') return 'step_start';
  if (type === 'step-finish') return 'step_finish';
  return 'opencode_event';
}

function getMessageRole(db, cache, messageId) {
  const id = String(messageId || '').trim();
  if (!id) return '';
  if (cache.has(id)) return cache.get(id);

  let role = '';
  try {
    const row = db.prepare('SELECT data FROM message WHERE id = ?').get(id);
    const data = parseJsonObject(row?.data || '');
    role = String(data?.role || '').trim();
  } catch {
    role = '';
  }
  cache.set(id, role);
  return role;
}

function createOpenCodeRuntimeService({ app, configStore, runtime }) {
  const runtimeRoot = path.join(getAgentRuntimeDir(app), runtime.id);
  const serviceRuntimeRoot = path.join(runtimeRoot, 'service');
  const serviceWorkspaceDir = path.join(serviceRuntimeRoot, 'workspace');
  const tasksRoot = path.join(runtimeRoot, 'tasks');
  const diagnostics = createRuntimeDiagnostics();
  const listeners = new Set();

  let phase = 'stopped';
  let healthy = false;
  let message = 'Agent 服务未启动';
  let updatedAt = nowIso();
  let lastHealthAt = '';
  let lastHealthError = '';
  let lastExitCode = null;
  let lastExitSignal = '';
  let restartPending = false;
  let restartPendingReason = '';
  let sidecar = null;
  let startPromise = null;
  let closePromise = null;
  let activeTask = null;
  let activeTaskAbortController = null;
  const taskQueue = [];
  let taskQueueDraining = false;
  let healthTimer = null;
  let statusTimer = null;
  let healthFailureCount = 0;
  let healthRestartAttempted = false;

  function ensureRuntimeDirs() {
    fs.mkdirSync(serviceRuntimeRoot, { recursive: true });
    fs.mkdirSync(serviceWorkspaceDir, { recursive: true });
    fs.mkdirSync(tasksRoot, { recursive: true });
  }

  function appendRuntimeEvent(event = {}) {
    diagnostics.record('runtime.event', event);
  }

  function getActiveTaskSummary() {
    if (!activeTask) return null;
    const now = Date.now();
    const startedAt = new Date(activeTask.started_at).getTime();
    const lastActivityAt = new Date(activeTask.last_activity_at).getTime();
    return {
      task_id: activeTask.task_id,
      title: activeTask.title,
      stage: activeTask.stage,
      progress_text: activeTask.progress_text,
      started_at: activeTask.started_at,
      last_activity_at: activeTask.last_activity_at,
      last_progress_at: activeTask.last_progress_at,
      elapsed_seconds: Math.max(0, Math.floor((now - startedAt) / 1000)),
      idle_seconds: Math.max(0, Math.floor((now - lastActivityAt) / 1000)),
    };
  }

  function getQueuedTaskSummaries() {
    return taskQueue.map((entry, index) => ({
      task_id: entry.taskId,
      title: entry.title,
      queued_at: entry.queuedAt,
      position: index + 1,
    }));
  }

  function getStatus() {
    return {
      phase,
      healthy,
      message,
      updated_at: updatedAt,
      last_health_at: lastHealthAt,
      last_health_error: lastHealthError,
      restart_pending: restartPending,
      restart_pending_reason: restartPendingReason,
      active_task: getActiveTaskSummary(),
      queued_count: taskQueue.length,
      queued_tasks: getQueuedTaskSummaries(),
      proxy: sidecar?.getProxyStatus?.() || { active: 0, queued: 0, limit: 0 },
      opencode: {
        pid: sidecar?.pid || sidecar?.child?.pid || 0,
        base_url: sidecar?.baseUrl || '',
        port: sidecar?.port || 0,
        last_exit_code: lastExitCode,
        last_exit_signal: lastExitSignal,
      },
    };
  }

  function emitStatus() {
    const status = getStatus();
    listeners.forEach((listener) => {
      try { listener(status); } catch {}
    });
  }

  let emitStatusTimer = null;
  function emitStatusThrottled() {
    if (emitStatusTimer) return;
    emitStatusTimer = setTimeout(() => {
      emitStatusTimer = null;
      emitStatus();
    }, 200);
  }

  function setPhase(nextPhase, nextMessage) {
    phase = nextPhase;
    healthy = nextPhase === 'idle' || nextPhase === 'running' || nextPhase === 'starting' || nextPhase === 'restarting';
    message = nextMessage || message;
    updatedAt = nowIso();
    appendRuntimeEvent({ phase, message, source: 'runtime.phase' });
    emitStatusThrottled();
    if (phase === 'idle' && restartPending && !activeTask) {
      setTimeout(() => {
        if (phase === 'idle' && restartPending && !activeTask) {
          void restart(restartPendingReason || 'config changed').catch((error) => {
            lastHealthError = error?.message || String(error || 'Agent 服务重启失败');
            setPhase('unhealthy', 'Agent 服务重启失败');
          });
        }
      }, 0);
    }
  }

  function touchActivity(event = {}) {
    if (!activeTask) {
      appendRuntimeEvent({ ...event, at: nowIso(), ignored: true, reason: 'no-active-task' });
      return;
    }
    if (!event.task_token || event.task_token !== activeTask.activity_token) {
      appendRuntimeEvent({ ...event, at: nowIso(), stale: true });
      return;
    }

    const now = nowIso();
    if (event.activity === true) {
      activeTask.last_activity_at = now;
    }
    if (event.visible !== false) {
      activeTask.stage = event.stage || activeTask.stage;
      activeTask.progress_text = event.message || activeTask.progress_text;
      activeTask.last_progress_at = now;
      message = activeTask.progress_text;
      updatedAt = now;
    }
    appendRuntimeEvent({ ...event, at: now });
    if (typeof activeTask.activity_handler === 'function') {
      try {
        activeTask.activity_handler({ ...event, at: now });
      } catch (error) {
        appendRuntimeEvent({ at: nowIso(), source: 'task-activity-handler', message: error?.message || String(error) });
      }
    }
    emitStatusThrottled();
  }

  function createTaskActivity(taskRef) {
    const taskToken = taskRef.activity_token;
    return (event = {}) => touchActivity({ ...event, task_token: taskToken });
  }

  function createActiveTask({ taskId, title, timeoutMs, onActivity }) {
    const now = nowIso();
    return {
      task_id: taskId,
      title,
      stage: 'starting',
      progress_text: '',
      started_at: now,
      last_activity_at: now,
      last_progress_at: now,
      timeout_ms: timeoutMs,
      activity_token: crypto.randomUUID(),
      activity_handler: typeof onActivity === 'function' ? onActivity : null,
    };
  }

  function createBusyResult() {
    return {
      success: false,
      status: 'busy',
      skipped: true,
      message: BUSY_MESSAGE,
      active_task: getActiveTaskSummary(),
    };
  }

  function createAbortReason(signal, fallbackMessage = 'Agent 任务已取消') {
    const reason = signal?.reason;
    if (reason instanceof Error) {
      return reason;
    }
    const error = new Error(reason ? String(reason) : fallbackMessage);
    if (reason && typeof reason === 'object' && reason.code) {
      error.code = reason.code;
    }
    return error;
  }

  function removeQueuedTask(entry, reason) {
    const index = taskQueue.indexOf(entry);
    if (index < 0 || entry.started) {
      return false;
    }
    taskQueue.splice(index, 1);
    entry.cleanup?.();
    entry.reject(reason);
    appendRuntimeEvent({
      source: 'runtime.queue',
      message: `Agent 排队任务已取消：${entry.title}`,
      task_id: entry.taskId,
      queue_length: taskQueue.length,
    });
    emitStatusThrottled();
    return true;
  }

  function rejectQueuedTasks(error) {
    const pending = taskQueue.splice(0, taskQueue.length);
    for (const entry of pending) {
      entry.cleanup?.();
      entry.reject(error);
    }
    if (pending.length) {
      appendRuntimeEvent({
        source: 'runtime.queue',
        message: `Agent 排队任务已全部取消：${pending.length} 个`,
        queue_length: 0,
      });
      emitStatusThrottled();
    }
  }

  function notifyQueuedTask(entry, position) {
    if (typeof entry.payload.onActivity !== 'function') {
      return;
    }
    try {
      entry.payload.onActivity({
        stage: 'queued',
        message: position > 1
          ? `Agent 任务排队中，前方还有 ${position - 1} 个任务。`
          : 'Agent 任务排队中，等待当前任务结束后执行。',
        source: 'runtime.queue',
        visible: true,
        activity: false,
        meta: { task_id: entry.taskId, position, queue_length: taskQueue.length },
      });
    } catch (error) {
      appendRuntimeEvent({ at: nowIso(), source: 'queue-activity-handler', message: error?.message || String(error) });
    }
  }

  function onStatus(listener) {
    if (typeof listener !== 'function') return () => {};
    listeners.add(listener);
    return () => listeners.delete(listener);
  }

  function startStatusTimer() {
    if (statusTimer) return;
    statusTimer = setInterval(() => {
      if (activeTask) emitStatus();
    }, STATUS_TICK_MS);
  }

  function stopStatusTimer() {
    if (statusTimer) clearInterval(statusTimer);
    statusTimer = null;
  }

  async function checkSidecarHealth() {
    if (!sidecar) throw new Error('OpenCode sidecar 未启动');
    const controller = new AbortController();
    const timer = setTimeout(() => controller.abort(new Error('Agent 服务健康检查超时')), 5000);
    try {
      const opencodeResponse = await fetch(`${sidecar.baseUrl}/global/health`, {
        headers: { Authorization: sidecar.authHeader },
        signal: controller.signal,
      });
      if (!opencodeResponse.ok) {
        throw new Error(`OpenCode health status ${opencodeResponse.status}`);
      }
      const proxyResponse = await fetch(`${sidecar.aiProxyBaseUrl}/health`, { signal: controller.signal });
      if (!proxyResponse.ok) {
        throw new Error(`Agent proxy health status ${proxyResponse.status}`);
      }
    } finally {
      clearTimeout(timer);
    }
  }

  function stopIdleHealthTimer() {
    if (healthTimer) clearInterval(healthTimer);
    healthTimer = null;
  }

  function startIdleHealthTimer() {
    if (healthTimer) return;
    healthTimer = setInterval(() => {
      if (phase !== 'idle' || activeTask || !sidecar) return;
      void checkSidecarHealth()
        .then(() => {
          healthFailureCount = 0;
          healthRestartAttempted = false;
          lastHealthAt = nowIso();
          lastHealthError = '';
          updatedAt = lastHealthAt;
          emitStatusThrottled();
        })
        .catch((error) => {
          healthFailureCount += 1;
          lastHealthError = error?.message || String(error || 'Agent 服务健康检查失败');
          updatedAt = nowIso();
          appendRuntimeEvent({ at: updatedAt, source: 'health', message: lastHealthError, failure_count: healthFailureCount });
          if (healthFailureCount >= HEALTH_FAILURE_LIMIT) {
            setPhase('unhealthy', 'Agent 服务健康检查失败');
            if (!healthRestartAttempted) {
              healthRestartAttempted = true;
              void restart('idle health failed').catch((restartError) => {
                lastHealthError = restartError?.message || String(restartError || lastHealthError);
                setPhase('unhealthy', 'Agent 服务异常');
              });
            }
          }
          emitStatusThrottled();
        });
    }, HEALTH_INTERVAL_MS);
  }

  async function ensureStarted() {
    if (sidecar && phase !== 'unhealthy' && phase !== 'stopped' && phase !== 'closing') return sidecar;
    if (startPromise) return startPromise;

    startPromise = (async () => {
      setPhase(phase === 'unhealthy' ? 'restarting' : 'starting', phase === 'unhealthy' ? '正在重启 Agent 服务' : '正在启动 Agent 服务');
      ensureRuntimeDirs();
      if (sidecar) {
        await closeOpenCodeSidecar(sidecar);
        sidecar = null;
      }
      sidecar = await startOpenCodeSidecar({
        app,
        configStore,
        runtime,
        runtimeRoot: serviceRuntimeRoot,
        workspaceDir: serviceWorkspaceDir,
        timeoutMs: DEFAULT_PROVIDER_TIMEOUT_MS,
        diagnostics,
        onStage: (stage, status, stageMessage, meta = {}) => {
          if (!activeTask) {
            appendRuntimeEvent({
              at: nowIso(),
              source: 'opencode-start',
              stage,
              message: stageMessage,
              meta: { ...meta, status },
              ignored: true,
              reason: 'no-active-task',
            });
            return;
          }

          touchActivity({
            task_token: activeTask.activity_token,
            task_id: activeTask.task_id,
            stage,
            message: stageMessage,
            source: 'opencode-start',
            visible: false,
            activity: false,
            meta: { ...meta, status },
          });
        },
        onActivity: touchActivity,
        getActivityContext: () => activeTask
          ? { task_token: activeTask.activity_token, task_id: activeTask.task_id }
          : null,
        onExit: handleOpenCodeExit,
      });
      if (phase === 'closing' || phase === 'stopped') {
        await closeOpenCodeSidecar(sidecar);
        sidecar = null;
        throw new Error('Agent 服务正在关闭');
      }
      healthFailureCount = 0;
      healthRestartAttempted = false;
      lastHealthAt = nowIso();
      lastHealthError = '';
      setPhase(activeTask ? 'running' : 'idle', activeTask ? '等待 Agent 返回真实进度' : 'Agent 服务空闲');
      startIdleHealthTimer();
      startStatusTimer();
      return sidecar;
    })();

    try {
      return await startPromise;
    } catch (error) {
      if (phase !== 'closing' && phase !== 'stopped') {
        setPhase('unhealthy', error?.message || 'Agent 服务启动失败');
      }
      throw error;
    } finally {
      startPromise = null;
    }
  }

  function handleOpenCodeExit({ code, signal }) {
    lastExitCode = code ?? null;
    lastExitSignal = signal || '';
    appendRuntimeEvent({ at: nowIso(), source: 'opencode.exit', code, signal });
    if (phase === 'closing' || phase === 'stopped') return;
    if (activeTaskAbortController && !activeTaskAbortController.signal.aborted) {
      activeTaskAbortController.abort(new Error('OpenCode Server 已退出'));
    }
    setPhase('unhealthy', 'Agent 服务异常退出');
  }

  function bindParentSignal(parentSignal, controller) {
    if (!parentSignal) return () => {};
    const abortFromParent = () => {
      if (!controller.signal.aborted) {
        controller.abort(parentSignal.reason || new Error('Agent 任务已取消'));
      }
    };
    if (parentSignal.aborted) abortFromParent();
    else parentSignal.addEventListener('abort', abortFromParent, { once: true });
    return () => {
      try { parentSignal.removeEventListener('abort', abortFromParent); } catch {}
    };
  }

  function startActivityWatchdog({ timeoutMs, abort, taskActivity }) {
    const timer = setInterval(() => {
      if (!activeTask) return;
      const idleMs = Date.now() - new Date(activeTask.last_activity_at).getTime();
      if (idleMs >= timeoutMs) {
        taskActivity({
          stage: 'stalled',
          message: 'Agent 长时间无进展，正在停止本轮任务',
          source: 'watchdog',
          activity: false,
        });
        abort(createStallError());
      }
    }, 2000);
    return () => clearInterval(timer);
  }

  function prepareStagingWorkspace(payload) {
    clearDirectoryContents(serviceWorkspaceDir);
    writeWorkspaceFiles(serviceWorkspaceDir, payload.files || []);
    writeAgentInstructionsFile(serviceWorkspaceDir);
  }

  function cleanupStagingWorkspace() {
    clearDirectoryContents(serviceWorkspaceDir);
  }

  function archiveTaskWorkspace(taskId) {
    const taskDir = path.join(tasksRoot, safeTaskPathSegment(taskId));
    const archiveWorkspaceDir = path.join(taskDir, 'workspace');
    fs.rmSync(taskDir, { recursive: true, force: true });
    fs.mkdirSync(taskDir, { recursive: true });
    fs.cpSync(serviceWorkspaceDir, archiveWorkspaceDir, { recursive: true });
    return archiveWorkspaceDir;
  }

  function writeTaskDiagnostics(taskId, payload = {}) {
    try {
      const taskDir = path.join(tasksRoot, safeTaskPathSegment(taskId));
      fs.mkdirSync(taskDir, { recursive: true });
      fs.writeFileSync(path.join(taskDir, 'diagnostics.json'), JSON.stringify(payload, null, 2), 'utf-8');
    } catch {}
  }

  function writeTaskResult(taskId, payload = {}) {
    try {
      const taskDir = path.join(tasksRoot, safeTaskPathSegment(taskId));
      fs.mkdirSync(taskDir, { recursive: true });
      fs.writeFileSync(path.join(taskDir, 'result.json'), JSON.stringify(payload, null, 2), 'utf-8');
    } catch {}
  }

  function collectDiagnostics({ taskId, title, outputFile }) {
    let output = { path: '', content: '' };
    try { output = readOutputContent(serviceWorkspaceDir, outputFile); } catch {}
    return {
      taskId,
      title,
      workspaceDir: serviceWorkspaceDir,
      runtimeRoot: serviceRuntimeRoot,
      outputFile,
      outputPath: output.path,
      outputContent: output.content,
      requestLog: sidecar?.requestLog || [],
      stderrTail: sidecar?.getStderrTail?.(8000) || '',
      stdoutTail: sidecar?.getStdoutTail?.(8000) || '',
      status: getStatus(),
      events: diagnostics.events.slice(-120),
    };
  }

  function moveDiagnosticsToArchivedWorkspace(diagnosticsPayload, archivedWorkspaceDir, outputFile) {
    if (!diagnosticsPayload || !archivedWorkspaceDir) return diagnosticsPayload;
    diagnosticsPayload.workspaceDir = archivedWorkspaceDir;
    try {
      const relativePath = safeRelativePath(outputFile);
      diagnosticsPayload.outputPath = ensureInsideRoot(archivedWorkspaceDir, path.join(archivedWorkspaceDir, relativePath), outputFile);
    } catch {
      diagnosticsPayload.outputPath = path.join(archivedWorkspaceDir, path.basename(outputFile || 'agent-result.md'));
    }
    return diagnosticsPayload;
  }

  async function runOpenCodeTaskWithRetry({ title, prompt, outputFile, signal, agent, taskActivity, onSessionCreated, validateOutput, maxRetries, retryAttempts }) {
    const session = await createSession(sidecar, title, { signal, onActivity: taskActivity });
    const sessionId = session?.id || session?.sessionID || session?.session_id || '';
    if (!sessionId) {
      throw new Error('OpenCode session 创建成功但缺少 session id');
    }
    onSessionCreated?.(session);
    let nextPrompt = prompt;
    let lastMessageResult = null;
    let lastText = '';
    let validationResult = null;

    for (let attemptIndex = 0; attemptIndex <= maxRetries; attemptIndex += 1) {
      const attempt = attemptIndex + 1;
      try {
        lastMessageResult = await sendPrompt(sidecar, sessionId, nextPrompt, { signal, agent, onActivity: taskActivity });
        lastText = extractTextFromPromptResult(lastMessageResult);
        const output = readOutputContent(serviceWorkspaceDir, outputFile);
        const candidateResult = {
          success: true,
          title,
          output_file: outputFile,
          output_content: output.content,
          assistant_text: lastText,
          session_id: sessionId,
          retry_count: attemptIndex,
          retry_attempts: [...retryAttempts],
        };
        if (typeof validateOutput === 'function') {
          try {
            validationResult = await validateOutput(candidateResult, {
              attempt,
              max_retries: maxRetries,
              task_id: activeTask?.task_id || '',
              title,
              output_file: outputFile,
              workspace_dir: serviceWorkspaceDir,
              session_id: sessionId,
              retry_attempts: [...retryAttempts],
            });
          } catch (validationError) {
            throw markAgentValidationError(validationError);
          }
        }
        const diff = await getSessionDiff(sidecar, sessionId, { signal, onActivity: taskActivity }).catch(() => []);
        return {
          session,
          message: lastMessageResult?.info || null,
          parts: Array.isArray(lastMessageResult?.parts) ? lastMessageResult.parts : [],
          text: lastText,
          diff: Array.isArray(diff) ? diff : [],
          output,
          validation_result: validationResult,
          retry_count: attemptIndex,
          retry_attempts: [...retryAttempts],
        };
      } catch (error) {
        if (isUserCancelOrPause(error) || signal?.aborted || attemptIndex >= maxRetries) {
          if (error && typeof error === 'object') {
            error.agentRetryAttempts = [...retryAttempts];
          }
          throw error;
        }

        let output = { content: '' };
        try { output = readOutputContent(serviceWorkspaceDir, outputFile); } catch {}
        retryAttempts.push(createRetryAttemptSummary({ attempt, error, outputContent: output.content }));
        const retryAttempt = retryAttempts.length;
        taskActivity({
          stage: 'retry',
          message: `Agent 执行未通过，正在同一会话自动修复 ${retryAttempt}/${maxRetries}：${compactErrorText(error, 160)}`,
          source: 'runtime.retry',
          activity: true,
          meta: {
            attempt,
            retry_attempt: retryAttempt,
            max_retries: maxRetries,
            validation_failed: Boolean(error?.agentValidationFailed),
          },
        });
        nextPrompt = buildAgentRetryPrompt({
          outputFile,
          attempt: retryAttempt,
          maxRetries,
          error,
        });
      }
    }

    throw new Error('Agent 自动修复流程异常结束');
  }

  function startOpenCodeEventWatcher(sessionId, taskActivity) {
    const normalizedSessionId = String(sessionId || '').trim();
    if (!normalizedSessionId) return () => {};

    const dbPath = path.join(serviceRuntimeRoot, 'home', '.local', 'share', 'opencode', 'opencode.db');
    const messageRoleCache = new Map();
    let lastSeq = -1;
    let stopped = false;
    let timer = null;

    function handlePart(db, row, part) {
      if (!part || typeof part !== 'object') return;
      const partSessionId = part.sessionID || part.session_id || '';
      if (partSessionId && partSessionId !== normalizedSessionId) return;

      const role = getMessageRole(db, messageRoleCache, part.messageID || part.message_id || '');
      if (role && role !== 'assistant') return;
      if (!role && String(part.type || '') === 'text') return;

      const text = formatOpenCodePartActivity(part);
      taskActivity({
        stage: getOpenCodePartStage(part),
        message: text,
        source: 'opencode.part',
        visible: Boolean(text),
        activity: true,
        meta: {
          session_id: normalizedSessionId,
          seq: row.seq,
          event_type: row.type,
          part_id: part.id || '',
          part_type: part.type || '',
          message_id: part.messageID || part.message_id || '',
          tool: part.tool || '',
          tool_status: part.state?.status || '',
        },
      });
    }

    function poll() {
      if (stopped || !fs.existsSync(dbPath)) return;

      let db = null;
      try {
        db = new Database(dbPath, { readonly: true, fileMustExist: true });
        const rows = db.prepare(`
          SELECT seq, type, data
          FROM event
          WHERE aggregate_id = ? AND seq > ?
          ORDER BY seq ASC
          LIMIT ?
        `).all(normalizedSessionId, lastSeq, OPENCODE_EVENT_BATCH_LIMIT);

        for (const row of rows) {
          lastSeq = Math.max(lastSeq, Number(row.seq || 0));
          taskActivity({
            stage: 'opencode_event',
            message: '',
            source: 'opencode.event',
            visible: false,
            activity: true,
            meta: { session_id: normalizedSessionId, seq: row.seq, event_type: row.type },
          });

          if (String(row.type || '').startsWith('message.part.updated')) {
            const data = parseJsonObject(row.data || '');
            handlePart(db, row, data?.part || null);
          }
        }
      } catch (error) {
        diagnostics.record('opencode.event_watcher.failed', {
          session_id: normalizedSessionId,
          message: error?.message || String(error),
        });
      } finally {
        try { db?.close?.(); } catch {}
      }
    }

    poll();
    timer = setInterval(poll, OPENCODE_EVENT_POLL_INTERVAL_MS);
    return () => {
      stopped = true;
      if (timer) clearInterval(timer);
      timer = null;
    };
  }

  function startOutputWatcher(outputFile, taskActivity) {
    let previousKey = '';
    const outputPath = ensureInsideRoot(serviceWorkspaceDir, path.join(serviceWorkspaceDir, safeRelativePath(outputFile)), outputFile);
    const timer = setInterval(() => {
      try {
        if (!fs.existsSync(outputPath)) return;
        const stat = fs.statSync(outputPath);
        const nextKey = `${stat.size}:${stat.mtimeMs}`;
        if (previousKey && nextKey !== previousKey) {
          taskActivity({
            stage: 'tool',
            message: `输出文件已更新：${path.basename(outputFile)}（${stat.size} 字节）`,
            source: 'workspace.output',
            activity: true,
            meta: { size: stat.size },
          });
        }
        previousKey = nextKey;
      } catch {}
    }, WORKSPACE_WATCH_INTERVAL_MS);
    return () => clearInterval(timer);
  }

  async function runTaskNow(payload = {}) {
    const taskId = payload.task_id || crypto.randomUUID();
    const title = payload.title || '易标智能体任务';
    const outputFile = payload.output_file || 'agent-result.md';
    const timeoutMs = normalizeTimeoutMs(payload.timeout_ms, DEFAULT_AGENT_IDLE_TIMEOUT_MS);
    const maxRetries = normalizeMaxRetries(payload.max_retries);
    const retryAttempts = [];

    activeTask = createActiveTask({ taskId, title, timeoutMs, onActivity: payload.onActivity });
    const taskActivity = createTaskActivity(activeTask);
    setPhase('running', '等待 Agent 返回真实进度');
    emitStatus();

    activeTaskAbortController = new AbortController();
    const stopParentAbort = bindParentSignal(payload.signal, activeTaskAbortController);
    const stopWatchdog = startActivityWatchdog({
      timeoutMs,
      abort: (error) => {
        if (!activeTaskAbortController.signal.aborted) activeTaskAbortController.abort(error);
      },
      taskActivity,
    });
    let stopOutputWatcher = null;
    let stopOpenCodeEventWatcher = null;
    let mustRestartAfterTask = false;
    let archivedWorkspaceDir = '';

    try {
      await ensureStarted();
      if (activeTaskAbortController.signal.aborted) throw activeTaskAbortController.signal.reason;

      taskActivity({ stage: 'workspace', message: '', source: 'runtime', visible: false, activity: false });
      prepareStagingWorkspace(payload);
      stopOutputWatcher = startOutputWatcher(outputFile, taskActivity);

      const result = await runOpenCodeTaskWithRetry({
        title,
        prompt: payload.prompt || createDefaultAgentPrompt({ task: payload.task || '请分析当前输入文件，并输出可执行结果。', outputFile }),
        outputFile,
        signal: activeTaskAbortController.signal,
        agent: payload.agent || 'build',
        taskActivity,
        validateOutput: payload.validateOutput,
        maxRetries,
        retryAttempts,
        onSessionCreated: (session) => {
          stopOpenCodeEventWatcher?.();
          stopOpenCodeEventWatcher = startOpenCodeEventWatcher(session?.id || session?.sessionID || session?.session_id || '', taskActivity);
        },
      });

      taskActivity({ stage: 'output', message: '', source: 'runtime', visible: false, activity: false });
      const output = result.output || readOutputContent(serviceWorkspaceDir, outputFile);

      taskActivity({ stage: 'archive', message: '', source: 'runtime', visible: false, activity: false });
      archivedWorkspaceDir = archiveTaskWorkspace(taskId);
      const diagnosticsPayload = moveDiagnosticsToArchivedWorkspace(collectDiagnostics({ taskId, title, outputFile }), archivedWorkspaceDir, outputFile);
      diagnosticsPayload.retryAttempts = [...retryAttempts];
      writeTaskDiagnostics(taskId, diagnosticsPayload);

      trackAgentRuntime(app, configStore, runtime.id, 'success', { retryCount: result.retry_count || 0 });

      const taskResult = {
        success: true,
        task_id: taskId,
        title,
        workspace_dir: archivedWorkspaceDir,
        runtime_workspace_dir: serviceWorkspaceDir,
        runtime_root: serviceRuntimeRoot,
        output_file: outputFile,
        output_content: output.content,
        assistant_text: result.text,
        diff: result.diff,
        session_id: result.session?.id || result.session?.sessionID || result.session?.session_id || '',
        retry_count: result.retry_count || 0,
        retry_attempts: result.retry_attempts || [],
        validation_result: result.validation_result,
        opencode_request_log: sidecar?.requestLog || [],
        opencode_stderr_tail: sidecar?.getStderrTail?.(8000) || '',
        opencode_stdout_tail: sidecar?.getStdoutTail?.(8000) || '',
      };
      writeTaskResult(taskId, taskResult);
      return taskResult;
    } catch (error) {
      if (isUserCancelOrPause(error)) {
        mustRestartAfterTask = true;
        throw annotateAgentError(error, collectDiagnostics({ taskId, title, outputFile }));
      }
      if (isWatchdogStall(error)) {
        mustRestartAfterTask = true;
      }
      trackAgentRuntime(app, configStore, runtime.id, 'failed', { retryCount: Array.isArray(error?.agentRetryAttempts) ? error.agentRetryAttempts.length : retryAttempts.length });
      const diagnosticsPayload = collectDiagnostics({ taskId, title, outputFile });
      if (error && typeof error === 'object') {
        error.agentRetryAttempts = Array.isArray(error.agentRetryAttempts) ? error.agentRetryAttempts : [...retryAttempts];
      }
      diagnosticsPayload.retryAttempts = Array.isArray(error?.agentRetryAttempts) ? error.agentRetryAttempts : [...retryAttempts];
      diagnosticsPayload.validationFailed = Boolean(error?.agentValidationFailed);
      try {
        archivedWorkspaceDir = archiveTaskWorkspace(taskId);
        moveDiagnosticsToArchivedWorkspace(diagnosticsPayload, archivedWorkspaceDir, outputFile);
      } catch (archiveError) {
        diagnosticsPayload.archiveError = archiveError?.message || String(archiveError || '归档失败');
      }
      writeTaskDiagnostics(taskId, diagnosticsPayload);
      throw annotateAgentError(error, diagnosticsPayload);
    } finally {
      stopOpenCodeEventWatcher?.();
      stopOutputWatcher?.();
      stopWatchdog?.();
      stopParentAbort?.();
      const shouldRestart = mustRestartAfterTask || phase === 'unhealthy';
      activeTask = null;
      activeTaskAbortController = null;
      try { cleanupStagingWorkspace(); } catch (error) { lastHealthError = error?.message || String(error); }

      if (phase !== 'closing' && phase !== 'stopped') {
        if (shouldRestart) {
          await restart('task aborted or stalled').catch((restartError) => {
            lastHealthError = restartError?.message || String(restartError || 'Agent 服务重启失败');
            setPhase('unhealthy', 'Agent 服务重启失败');
          });
        } else if (restartPending) {
          await restart('config changed').catch((restartError) => {
            lastHealthError = restartError?.message || String(restartError || 'Agent 服务重启失败');
            setPhase('unhealthy', 'Agent 服务重启失败');
          });
        } else {
          setPhase(sidecar ? 'idle' : 'unhealthy', sidecar ? 'Agent 服务空闲' : 'Agent 服务异常');
        }
      }
      emitStatus();
    }
  }

  function drainAgentTaskQueue() {
    if (taskQueueDraining || activeTask || closePromise) {
      return;
    }
    taskQueueDraining = true;
    void (async () => {
      try {
        while (!activeTask && taskQueue.length && !closePromise) {
          if (restartPending && phase === 'idle') {
            await restart(restartPendingReason || 'config changed');
          }

          const entry = taskQueue.shift();
          if (!entry) {
            continue;
          }
          entry.started = true;
          entry.cleanup?.();
          emitStatusThrottled();

          if (entry.payload.signal?.aborted) {
            entry.reject(createAbortReason(entry.payload.signal));
            continue;
          }

          appendRuntimeEvent({
            source: 'runtime.queue',
            message: `Agent 排队任务开始执行：${entry.title}`,
            task_id: entry.taskId,
            queue_length: taskQueue.length,
          });

          try {
            const result = await runTaskNow(entry.payload);
            entry.resolve(result);
          } catch (error) {
            entry.reject(error);
          }
        }
      } finally {
        taskQueueDraining = false;
        emitStatusThrottled();
        if (taskQueue.length && !activeTask && !closePromise) {
          setTimeout(drainAgentTaskQueue, 0);
        }
      }
    })();
  }

  async function runTask(payload = {}) {
    if (phase === 'closing' || closePromise) {
      throw new Error('Agent 服务正在关闭，无法执行任务');
    }
    if (payload.signal?.aborted) {
      throw createAbortReason(payload.signal);
    }

    const taskId = payload.task_id || crypto.randomUUID();
    const title = payload.title || '易标智能体任务';
    const queuedAt = nowIso();
    return new Promise((resolve, reject) => {
      const entry = {
        taskId,
        title,
        queuedAt,
        payload: { ...payload, task_id: taskId },
        resolve,
        reject,
        started: false,
        cleanup: null,
      };

      if (payload.signal?.addEventListener) {
        const onAbort = () => {
          removeQueuedTask(entry, createAbortReason(payload.signal));
        };
        payload.signal.addEventListener('abort', onAbort, { once: true });
        entry.cleanup = () => payload.signal.removeEventListener('abort', onAbort);
      }

      taskQueue.push(entry);
      appendRuntimeEvent({
        source: 'runtime.queue',
        message: `Agent 任务已入队：${title}`,
        task_id: taskId,
        queue_length: taskQueue.length,
      });
      notifyQueuedTask(entry, taskQueue.length);
      emitStatusThrottled();
      drainAgentTaskQueue();
    });
  }

  async function warmup() {
    try {
      await ensureStarted();
      return getStatus();
    } catch (error) {
      lastHealthError = error?.message || String(error || 'Agent 服务启动失败');
      setPhase('unhealthy', 'Agent 服务启动失败');
      throw error;
    }
  }

  async function restart(reason = 'manual') {
    if (activeTask) {
      restartPending = true;
      restartPendingReason = reason;
      emitStatusThrottled();
      return getStatus();
    }
    restartPending = false;
    restartPendingReason = '';
    stopIdleHealthTimer();
    setPhase('restarting', '正在重启 Agent 服务');
    await closeOpenCodeSidecar(sidecar);
    sidecar = null;
    try { cleanupStagingWorkspace(); } catch {}
    await ensureStarted();
    return getStatus();
  }

  function markRestartPending(reason) {
    restartPending = true;
    restartPendingReason = reason || 'config changed';
    emitStatusThrottled();
    if (!activeTask && phase === 'idle') {
      void restart(restartPendingReason).catch((error) => {
        lastHealthError = error?.message || String(error || 'Agent 服务重启失败');
        setPhase('unhealthy', 'Agent 服务重启失败');
      });
    }
  }

  function handleConfigChanged(nextConfig = {}, previousConfig = {}) {
    if (Number(nextConfig.context_length_limit || 0) !== Number(previousConfig.context_length_limit || 0)) {
      markRestartPending('context_length_limit changed');
    }
  }

  async function runSelfCheck() {
    if (activeTask || taskQueue.length) {
      const busyResult = {
        success: false,
        status: 'busy',
        message: BUSY_MESSAGE,
        conclusion: 'Agent 子服务正在执行任务，自检已跳过；这不是 OpenCode 故障。',
        checked_at: nowIso(),
        duration_ms: 0,
        log_dir: '',
        log_file: '',
        runtime_root: serviceRuntimeRoot,
        workspace_dir: serviceWorkspaceDir,
        output_file: SELF_CHECK_OUTPUT_FILE,
        output_path: path.join(serviceWorkspaceDir, SELF_CHECK_OUTPUT_FILE),
        opencode_binary_path: '',
        isolation_check: null,
        runtime_status: getStatus(),
        steps: [],
        detail_text: '',
      };
      busyResult.detail_text = formatSelfCheckDetails(busyResult);
      return busyResult;
    }

    const checkedAt = nowIso();
    const startedAt = Date.now();
    const steps = createSelfCheckSteps();
    const logger = createSelfCheckLogger(app, `${runtime.id}-self-check`);
    let opencodeBinaryPath = '';
    let config = null;
    let modelConfig = null;
    let environment = null;
    let isolationCheck = null;
    let directModelTest = null;
    let toolCheckResult = null;
    let agentResult = null;
    let workspaceSnapshot = null;
    let agentTaskStarted = false;

    function setStep(id, status, stepMessage, meta = {}) {
      const step = steps.find((item) => item.id === id);
      if (!step) return;
      if (step.status === 'error' && status !== 'error') return;
      if (step.status === 'success' && status === 'running') return;
      updateSelfCheckStep(steps, id, status, stepMessage);
      logger.write('step', { id, status, message: stepMessage || '', ...meta });
    }

    function completeStep(id, stepMessage) {
      const step = steps.find((item) => item.id === id);
      if (!step || step.status === 'success' || step.status === 'error') return;
      setStep(id, 'success', stepMessage);
    }

    function completeRuntimeSteps() {
      if (!sidecar) return;
      completeStep('ai-proxy-start', sidecar.aiProxyBaseUrl || '常驻 OpenCode AI proxy 可用');
      completeStep('opencode-config-write', path.join(serviceRuntimeRoot, 'opencode.json'));
      completeStep('opencode-server-start', sidecar.baseUrl || '常驻 OpenCode Server 可用');
      completeStep('opencode-health', sidecar.baseUrl || '常驻 OpenCode Server 健康检查通过');
    }

    function inferActivityStatus(event, successPattern) {
      const status = event?.meta?.status;
      if (status === 'success' || status === 'error' || status === 'running') return status;
      return successPattern.test(String(event?.message || '')) ? 'success' : 'running';
    }

    function handleInternalActivity(event = {}) {
      logger.write('activity', event);
      const stage = String(event.stage || '');
      const messageText = String(event.message || '');
      const route = String(event.meta?.route || '');

      if (['ai-proxy-start', 'opencode-config-write', 'opencode-server-start', 'opencode-health'].includes(stage)) {
        setStep(stage, inferActivityStatus(event, /成功|完成|可用|通过/), messageText);
        return;
      }

      if (stage === 'session') {
        setStep('session-create', inferActivityStatus(event, /已创建|完成/), messageText);
        return;
      }

      if (stage === 'message') {
        setStep('message-wait', inferActivityStatus(event, /完成/), messageText);
        return;
      }

      if (stage === 'output' && route.includes('/diff')) {
        const nextStatus = inferActivityStatus(event, /已读取|完成/);
        if (nextStatus !== 'error') setStep('diff-fetch', nextStatus, messageText);
        return;
      }

      if ((stage === 'tool' || event.source === 'workspace.output') && messageText) {
        setStep('message-wait', 'running', messageText);
      }
    }

    function failCurrentStep(error) {
      const existingError = steps.find((step) => step.status === 'error');
      if (existingError) {
        if (error && typeof error === 'object' && !error.selfCheckStage) error.selfCheckStage = existingError.id;
        return;
      }
      const currentStage = error?.selfCheckStage && steps.some((step) => step.id === error.selfCheckStage)
        ? error.selfCheckStage
        : getCurrentSelfCheckStage(steps);
      const stageId = steps.some((step) => step.id === currentStage) ? currentStage : 'message-wait';
      if (error && typeof error === 'object' && !error.selfCheckStage) error.selfCheckStage = stageId;
      setStep(stageId, 'error', error?.message || String(error || '智能体自检失败'));
    }

    function createBaseResult({ success, status, resultMessage, error }) {
      const runtimeStatus = getStatus();
      const diagnosticsPayload = error ? compactSelfCheckError(error) : {
        opencode_binary_path: opencodeBinaryPath,
        opencode_base_url: sidecar?.baseUrl || '',
        opencode_port: sidecar?.port || 0,
        opencode_exit_code: lastExitCode,
        opencode_exit_signal: lastExitSignal,
        opencode_stdout_tail: sidecar?.getStdoutTail?.(8000) || '',
        opencode_stderr_tail: sidecar?.getStderrTail?.(8000) || '',
        opencode_request_log: agentResult?.opencode_request_log || sidecar?.requestLog || [],
      };
      diagnosticsPayload.opencode_binary_path = diagnosticsPayload.opencode_binary_path || opencodeBinaryPath;
      diagnosticsPayload.opencode_base_url = diagnosticsPayload.opencode_base_url || sidecar?.baseUrl || '';
      diagnosticsPayload.opencode_port = diagnosticsPayload.opencode_port || sidecar?.port || 0;
      diagnosticsPayload.opencode_exit_code = diagnosticsPayload.opencode_exit_code ?? lastExitCode;
      diagnosticsPayload.opencode_exit_signal = diagnosticsPayload.opencode_exit_signal || lastExitSignal;
      diagnosticsPayload.opencode_stdout_tail = diagnosticsPayload.opencode_stdout_tail || sidecar?.getStdoutTail?.(8000) || agentResult?.opencode_stdout_tail || '';
      diagnosticsPayload.opencode_stderr_tail = diagnosticsPayload.opencode_stderr_tail || sidecar?.getStderrTail?.(8000) || agentResult?.opencode_stderr_tail || '';
      if (!diagnosticsPayload.opencode_request_log?.length) {
        diagnosticsPayload.opencode_request_log = agentResult?.opencode_request_log || sidecar?.requestLog || [];
      }
      diagnosticsPayload.runtime_status = runtimeStatus;

      const workspaceDir = agentResult?.workspace_dir || error?.agentWorkspaceDir || serviceWorkspaceDir;
      const outputPath = agentResult?.workspace_dir
        ? path.join(agentResult.workspace_dir, SELF_CHECK_OUTPUT_FILE)
        : error?.agentOutputPath || path.join(serviceWorkspaceDir, SELF_CHECK_OUTPUT_FILE);
      const result = {
        success,
        status,
        message: resultMessage,
        checked_at: checkedAt,
        duration_ms: Date.now() - startedAt,
        log_dir: logger.logDir,
        log_file: logger.logFile,
        runtime_root: serviceRuntimeRoot,
        workspace_dir: workspaceDir,
        output_file: SELF_CHECK_OUTPUT_FILE,
        output_path: outputPath,
        output_content: agentResult?.output_content || error?.agentPartialOutput || '',
        opencode_binary_path: opencodeBinaryPath,
        model_config: modelConfig,
        environment,
        isolation_check: isolationCheck || error?.isolationCheck || sidecar?.isolationCheck || null,
        direct_model_test: directModelTest,
        tool_check_summary: toolCheckResult?.summary || '',
        tool_check_environment: toolCheckResult ? {
          runtime_tools_bin_dir: toolCheckResult.runtime_tools_bin_dir || '',
          bundled_tools_bin_dir: toolCheckResult.bundled_tools_bin_dir || '',
          path_entries: toolCheckResult.path_entries || [],
        } : null,
        tool_checks: toolCheckResult?.items || [],
        opencode_request_log: agentResult?.opencode_request_log || diagnosticsPayload.opencode_request_log || [],
        proxy_diagnostics: { events: diagnostics.events.slice(-200) },
        workspace_snapshot: workspaceSnapshot,
        runtime_status: runtimeStatus,
        agent_result: agentResult ? {
          session_id: agentResult.session_id || '',
          assistant_text_chars: String(agentResult.assistant_text || '').length,
          diff_count: Array.isArray(agentResult.diff) ? agentResult.diff.length : 0,
        } : null,
        steps,
        diagnostics: diagnosticsPayload,
        error: error ? diagnosticsPayload : undefined,
        detail_text: '',
      };
      result.sections = createOpenCodeDiagnosticSections(result);
      return result;
    }

    try {
      opencodeBinaryPath = getBundledOpencodeBinaryPath(app);
      config = configStore.load();
      modelConfig = summarizeTextModelConfig(config);

      setStep('prepare', 'running', '正在清理上一轮自检日志和旧归档');
      if (logger.getSetupError()) {
        throw createSelfCheckStageError('prepare', `自检日志目录不可写：${logger.getSetupError()}`);
      }
      ensureRuntimeDirs();
      fs.rmSync(path.join(tasksRoot, safeTaskPathSegment(SELF_CHECK_TASK_ID)), { recursive: true, force: true });
      setStep('prepare', 'success', '自检日志和旧归档已清理');

      setStep('environment-snapshot', 'running', '正在采集本机环境和模型配置摘要');
      environment = createEnvironmentSnapshot(app, opencodeBinaryPath, config);
      setStep('environment-snapshot', 'success', '环境快照已采集');

      setStep('binary-check', 'running', '正在检查 OpenCode 程序文件');
      const binaryStat = safeStat(opencodeBinaryPath);
      if (!binaryStat?.exists || !binaryStat.is_file) {
        throw createSelfCheckStageError('binary-check', `OpenCode binary 不存在或不可访问：${opencodeBinaryPath}`);
      }
      setStep('binary-check', 'success', `size=${binaryStat.size}`);

      setStep('runtime-write-check', 'running', '正在检查常驻运行目录写入能力');
      fs.mkdirSync(serviceRuntimeRoot, { recursive: true });
      fs.mkdirSync(serviceWorkspaceDir, { recursive: true });
      const writeCheckPath = path.join(serviceRuntimeRoot, `.self-check-write-${Date.now()}.tmp`);
      fs.writeFileSync(writeCheckPath, 'ok', 'utf-8');
      fs.rmSync(writeCheckPath, { force: true });
      setStep('runtime-write-check', 'success', '运行目录可写');

      setStep('tool-check', 'running', '正在校验已集成命令工具');
      toolCheckResult = runIntegratedToolSelfCheck({
        app,
        runtimeRoot: serviceRuntimeRoot,
        workspaceDir: serviceWorkspaceDir,
        logger,
      });
      if (!toolCheckResult.success) {
        const toolCheckMessage = `集成工具校验失败：${toolCheckResult.summary || '存在不可用的关键工具'}`;
        setStep('tool-check', 'error', toolCheckMessage);
        throw createSelfCheckStageError('tool-check', toolCheckMessage);
      }
      setStep('tool-check', 'success', toolCheckResult.summary || '集成工具校验通过');

      setStep('ai-proxy-start', 'running', '正在确认常驻 OpenCode AI proxy');
      const runningSidecar = await ensureStarted();
      completeRuntimeSteps();

      setStep('isolation-check', 'running', '正在检查 OpenCode 逻辑隔离');
      isolationCheck = runningSidecar?.isolationCheck || null;
      if (!isolationCheck?.success) {
        const violations = Array.isArray(isolationCheck?.violations)
          ? isolationCheck.violations.filter(Boolean).join('；')
          : '';
        const isolationError = createSelfCheckStageError(
          'isolation-check',
          violations ? `OpenCode 逻辑隔离检查失败：${violations}` : 'OpenCode 逻辑隔离未返回有效结果'
        );
        isolationError.isolationCheck = isolationCheck;
        throw isolationError;
      }
      setStep(
        'isolation-check',
        'success',
        `外部目录规则已拒绝，已加载 ${isolationCheck.loaded_skills?.length || 0} 个 Skill`
      );

      setStep('direct-model-test', 'running', '正在直接请求当前文本模型');
      directModelTest = await runDirectModelSelfCheck(config);
      if (!directModelTest.success) {
        throw createSelfCheckStageError('direct-model-test', directModelTest.message || '直接模型测试失败');
      }
      setStep('direct-model-test', 'success', directModelTest.message || '直接模型测试成功');

      agentTaskStarted = true;
      agentResult = await runTask({
        task_id: SELF_CHECK_TASK_ID,
        title: '易标智能体自检',
        output_file: SELF_CHECK_OUTPUT_FILE,
        files: [{ path: 'self-check-input.txt', content: 'YIBIAO_AGENT_SELF_CHECK_INPUT' }],
        prompt: buildSelfCheckPrompt(),
        timeout_ms: SELF_CHECK_TIMEOUT_MS,
        max_retries: 0,
        onActivity: handleInternalActivity,
      });

      if (agentResult?.status === 'busy') {
        const busyResult = createBaseResult({ success: false, status: 'busy', resultMessage: BUSY_MESSAGE });
        busyResult.conclusion = 'Agent 子服务正在执行任务，自检已跳过；这不是 OpenCode 故障。';
        busyResult.detail_text = formatSelfCheckDetails(busyResult);
        logger.write('result', busyResult);
        return busyResult;
      }

      completeRuntimeSteps();
      completeStep('session-create', `session_id=${agentResult?.session_id || '-'}`);
      completeStep('message-wait', 'Agent 任务执行完成');
      completeStep('diff-fetch', `diff_count=${Array.isArray(agentResult?.diff) ? agentResult.diff.length : 0}`);

      setStep('output-check', 'running', '正在校验输出内容');
      validateSelfCheckOutput(agentResult?.output_content || '');
      setStep('output-check', 'success', '输出内容符合预期');

      setStep('workspace-snapshot', 'running', '正在采集自检工作目录快照');
      workspaceSnapshot = snapshotWorkspace(agentResult?.workspace_dir || serviceWorkspaceDir);
      setStep('workspace-snapshot', 'success', `files=${workspaceSnapshot?.files?.length || 0}`);

      const result = createBaseResult({ success: true, status: 'normal', resultMessage: '智能体自检正常' });
      result.conclusion = createSelfCheckConclusion(result);
      result.detail_text = formatSelfCheckDetails(result);
      logger.write('result', result);
      return result;
    } catch (error) {
      if (agentTaskStarted) completeRuntimeSteps();
      failCurrentStep(error);
      const workspaceRoot = agentResult?.workspace_dir || error?.agentWorkspaceDir || serviceWorkspaceDir;
      workspaceSnapshot = snapshotWorkspace(workspaceRoot);
      const result = createBaseResult({
        success: false,
        status: 'error',
        resultMessage: error?.message || '智能体自检失败',
        error,
      });
      result.conclusion = createSelfCheckConclusion(result);
      result.detail_text = formatSelfCheckDetails(result);
      logger.write('result', result);
      return result;
    }
  }

  async function close() {
    if (closePromise) return closePromise;
    closePromise = (async () => {
      setPhase('closing', '正在关闭 Agent 服务');
      stopIdleHealthTimer();
      stopStatusTimer();
      if (emitStatusTimer) {
        clearTimeout(emitStatusTimer);
        emitStatusTimer = null;
      }
      if (activeTaskAbortController && !activeTaskAbortController.signal.aborted) {
        activeTaskAbortController.abort(new Error('Agent 服务正在关闭'));
      }
      rejectQueuedTasks(new Error('Agent 服务正在关闭'));
      if (startPromise) {
        await startPromise.catch(() => undefined);
      }
      activeTask = null;
      activeTaskAbortController = null;
      await closeOpenCodeSidecar(sidecar);
      sidecar = null;
      try { cleanupStagingWorkspace(); } catch {}
      setPhase('stopped', 'Agent 服务已停止');
      healthy = false;
      emitStatus();
    })().finally(() => {
      closePromise = null;
    });
    return closePromise;
  }

  startStatusTimer();

  return {
    warmup,
    runTask,
    runSelfCheck,
    getStatus,
    restart,
    markRestartPending,
    handleConfigChanged,
    onStatus,
    close,
  };
}

function createRuntimeDiagnostics(limit = 500) {
  const events = [];
  return {
    events,
    record(event, payload = {}) {
      events.push({ at: nowIso(), event, ...payload });
      if (events.length > limit) {
        events.splice(0, events.length - limit);
      }
    },
  };
}

module.exports = {
  createOpenCodeRuntimeService,
};
