#!/usr/bin/env node

const crypto = require('crypto');
const path = require('path');
const readline = require('readline');
const { spawn } = require('child_process');

const args = process.argv.slice(2);

if (args[0] !== 'app-server') {
  const child = spawn(process.execPath, [path.join(__dirname, 'mock-codex.js'), ...args], {
    stdio: 'inherit',
  });
  child.on('exit', (code, signal) => {
    if (signal) process.kill(process.pid, signal);
    process.exit(code || 0);
  });
  child.on('error', (err) => {
    console.error(err.stack || err.message);
    process.exit(1);
  });
  return;
}

const threads = new Map();
const pendingServerRequests = new Map();
const resumeMismatchThreads = new Set();
let nextServerRequestId = 1;
let mcpReloadCount = 0;

function send(message) {
  process.stdout.write(`${JSON.stringify(message)}\n`);
}

function textFromInput(input) {
  return (Array.isArray(input) ? input : [])
    .map((item) => {
      if (item?.type === 'text') return item.text || '';
      if (item?.type === 'localImage') return `[image:${path.basename(item.path || 'image')}]`;
      return '';
    })
    .filter(Boolean)
    .join('\n')
    .trim();
}

function tokenUsage(text) {
  const inputTokens = Math.max(1, Math.ceil(String(text || '').length / 4));
  const outputTokens = 7;
  return {
    last: {
      inputTokens,
      cachedInputTokens: 1,
      outputTokens,
      reasoningOutputTokens: 0,
      totalTokens: inputTokens + outputTokens + 1,
    },
    total: {
      inputTokens,
      cachedInputTokens: 1,
      outputTokens,
      reasoningOutputTokens: 0,
      totalTokens: inputTokens + outputTokens + 1,
    },
  };
}

function retryScenarioKey(text, marker) {
  return new RegExp(marker.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i').test(String(text || ''))
    ? marker
    : String(text || '');
}

function collaborationSummary(params = {}) {
  const collaborationMode = params.collaborationMode;
  const settings = collaborationMode?.settings || {};
  return JSON.stringify({
    mode: collaborationMode?.mode || null,
    hasModel: Boolean(settings.model),
    hasDeveloperInstructions: /Codex sub-agent spawning rules/.test(String(settings.developer_instructions || '')),
    hasWaitAgentRetryGuidance: /wait_agent[\s\S]*timeout_ms[\s\S]*additional wait_agent rounds/.test(String(settings.developer_instructions || '')),
    hasReasoningEffort: Object.prototype.hasOwnProperty.call(settings, 'reasoning_effort'),
    hasTopLevelModel: Object.prototype.hasOwnProperty.call(params, 'model'),
    hasTopLevelEffort: Object.prototype.hasOwnProperty.call(params, 'effort'),
  });
}

function ensureThread(threadId, params = {}) {
  const id = threadId || `app-thread-${crypto.randomUUID()}`;
  if (!threads.has(id)) {
    threads.set(id, {
      id,
      cwd: params.cwd || process.cwd(),
      dynamicTools: Array.isArray(params.dynamicTools) ? params.dynamicTools : [],
      config: params.config && typeof params.config === 'object' ? params.config : {},
      activeTurnId: null,
      timer: null,
      steers: [],
      capacityRetryAttempts: new Map(),
      reconnectRetryAttempts: new Map(),
      goal: null,
    });
  }
  const thread = threads.get(id);
  if (Array.isArray(params.dynamicTools)) thread.dynamicTools = params.dynamicTools;
  if (params.config && typeof params.config === 'object') thread.config = params.config;
  return thread;
}

function threadPayload(thread) {
  return {
    id: thread.id,
    cwd: thread.cwd,
    status: thread.activeTurnId ? 'running' : 'idle',
    turns: [],
  };
}

function emitChildCollabTurn(threadId, turnId, finalMessage) {
  const childThread = ensureThread(threadId);
  childThread.activeTurnId = turnId;
  send({
    method: 'turn/started',
    params: {
      threadId,
      turn: {
        id: turnId,
        status: 'running',
        items: [],
      },
    },
  });
  send({
    method: 'item/agentMessage/delta',
    params: {
      threadId,
      turnId,
      itemId: 'agent-msg',
      delta: finalMessage,
    },
  });
  send({
    method: 'item/completed',
    params: {
      threadId,
      turnId,
      completedAtMs: Date.now(),
      item: {
        id: 'agent-msg',
        type: 'agentMessage',
        content: [{ type: 'text', text: finalMessage }],
        status: 'completed',
      },
    },
  });
  send({
    method: 'turn/completed',
    params: {
      threadId,
      turn: {
        id: turnId,
        status: 'completed',
        items: [],
      },
    },
  });
  childThread.activeTurnId = null;
}

function completeTurn(thread, turnId, text, status = 'completed') {
  if (thread.activeTurnId !== turnId) return;
  const suffix = thread.steers.length > 0 ? ` | steer: ${thread.steers.join(' | ')}` : '';
  const responseText = `Codex App mock handled: ${text}${suffix}`;

  send({
    method: 'item/agentMessage/delta',
    params: {
      threadId: thread.id,
      turnId,
      itemId: 'agent-msg',
      delta: responseText,
    },
  });

  if (/tool/i.test(text)) {
    send({
      method: 'item/started',
      params: {
        threadId: thread.id,
        turnId,
        startedAtMs: Date.now(),
        item: {
          id: 'tool-cmd',
          type: 'commandExecution',
          command: '/bin/bash -lc echo codexapp',
          status: 'inProgress',
        },
      },
    });
    send({
      method: 'item/commandExecution/outputDelta',
      params: {
        threadId: thread.id,
        turnId,
        itemId: 'tool-cmd',
        delta: 'codexapp\n',
      },
    });
    send({
      method: 'item/completed',
      params: {
        threadId: thread.id,
        turnId,
        completedAtMs: Date.now(),
        item: {
          id: 'tool-cmd',
          type: 'commandExecution',
          command: '/bin/bash -lc echo codexapp',
          aggregatedOutput: 'codexapp\n',
          exitCode: 0,
          status: 'completed',
        },
      },
    });
  }

  if (/huge output/i.test(text)) {
    const hugeOutput = `huge-output-start\n${'0123456789abcdef'.repeat(30000)}\nhuge-output-end\n`;
    send({
      method: 'item/started',
      params: {
        threadId: thread.id,
        turnId,
        startedAtMs: Date.now(),
        item: {
          id: 'huge-tool',
          type: 'commandExecution',
          command: '/bin/bash -lc huge-output',
          status: 'inProgress',
        },
      },
    });
    send({
      method: 'item/commandExecution/outputDelta',
      params: {
        threadId: thread.id,
        turnId,
        itemId: 'huge-tool',
        delta: hugeOutput,
      },
    });
    send({
      method: 'item/completed',
      params: {
        threadId: thread.id,
        turnId,
        completedAtMs: Date.now(),
        item: {
          id: 'huge-tool',
          type: 'commandExecution',
          command: '/bin/bash -lc huge-output',
          aggregatedOutput: hugeOutput,
          exitCode: 0,
          status: 'completed',
        },
      },
    });
  }

  if (/subagent/i.test(text)) {
    send({
      method: 'item/started',
      params: {
        threadId: thread.id,
        turnId,
        startedAtMs: Date.now(),
        item: {
          id: 'tool-collab',
          type: 'collabAgentToolCall',
          tool: 'spawn_agent',
          prompt: '整理前端改动并回报状态',
          receiverThreadIds: ['child-thread-a', 'child-thread-b'],
          agentsStates: {
            'child-thread-a': {
              name: '实现代理',
              role: 'frontend',
              status: 'in_progress',
              summary: '正在补充结构化渲染与样式',
            },
            'child-thread-b': {
              name: '验证代理',
              role: 'qa',
              status: 'completed',
              summary: '已完成基础事件链检查',
            },
          },
          status: 'inProgress',
        },
      },
    });
    send({
      method: 'item/completed',
      params: {
        threadId: thread.id,
        turnId,
        completedAtMs: Date.now(),
        item: {
          id: 'tool-collab',
          type: 'collabAgentToolCall',
          tool: 'spawn_agent',
          prompt: '整理前端改动并回报状态',
          receiverThreadIds: ['child-thread-a', 'child-thread-b'],
          agentsStates: {
            'child-thread-a': {
              name: '实现代理',
              role: 'frontend',
              status: 'completed',
              summary: '结构化渲染已完成',
            },
            'child-thread-b': {
              name: '验证代理',
              role: 'qa',
              status: 'completed',
              summary: '事件链与持久化检查通过',
            },
          },
          status: 'completed',
        },
      },
    });
    emitChildCollabTurn(
      'child-thread-a',
      'child-turn-a',
      '子代理最终消息：结构化渲染和关闭按钮链路已完成。'
    );
    emitChildCollabTurn(
      'child-thread-b',
      'child-turn-b',
      '子代理最终消息：事件路由、持久化和状态推送检查通过。'
    );
  }

  send({
    method: 'thread/tokenUsage/updated',
    params: {
      threadId: thread.id,
      turnId,
      tokenUsage: tokenUsage(`${text}${suffix}`),
    },
  });
  send({
    method: 'turn/completed',
    params: {
      threadId: thread.id,
      turn: {
        id: turnId,
        status,
        items: [],
      },
    },
  });
  thread.activeTurnId = null;
  thread.timer = null;
  thread.steers = [];
}

function requestClient(method, params, callback) {
  const id = `mock-server-request-${nextServerRequestId++}`;
  pendingServerRequests.set(id, callback);
  send({ id, method, params });
}

function completeDynamicToolTurn(thread, turnId, text) {
  const callId = 'dynamic-ccweb-list';
  send({
    method: 'item/started',
    params: {
      threadId: thread.id,
      turnId,
      startedAtMs: Date.now(),
      item: {
        id: callId,
        type: 'dynamicToolCall',
        namespace: 'ccweb',
        tool: 'ccweb_list_conversations',
        arguments: { limit: 5 },
        status: 'inProgress',
      },
    },
  });
  requestClient('item/tool/call', {
    threadId: thread.id,
    turnId,
    callId,
    namespace: 'ccweb',
    tool: 'ccweb_list_conversations',
    arguments: { limit: 5 },
  }, (message) => {
    const result = message.result || {};
    send({
      method: 'item/completed',
      params: {
        threadId: thread.id,
        turnId,
        completedAtMs: Date.now(),
        item: {
          id: callId,
          type: 'dynamicToolCall',
          namespace: 'ccweb',
          tool: 'ccweb_list_conversations',
          arguments: { limit: 5 },
          status: result.success === false ? 'failed' : 'completed',
          contentItems: result.contentItems || [],
          success: result.success !== false,
          durationMs: 1,
        },
      },
    });
    completeTurn(thread, turnId, `dynamic result: ${JSON.stringify(result)}`);
  });
}

function completeMcpToolTurn(thread, turnId) {
  const itemId = 'mcp-ccweb-list';
  const ccwebConfig = thread.config?.['mcp_servers.ccweb'] || null;
  const projectConfig = thread.config?.['mcp_servers.reg-app-project'] || null;
  const env = ccwebConfig?.env || {};
  let urlSourceSessionId = null;
  let urlSourceHopCount = null;
  try {
    if (ccwebConfig?.url) {
      const parsedUrl = new URL(ccwebConfig.url);
      urlSourceSessionId = parsedUrl.searchParams.get('sourceSessionId') || null;
      urlSourceHopCount = parsedUrl.searchParams.get('sourceHopCount') || null;
    }
  } catch {}
  const payload = {
    ok: true,
    currentConversationId: env.CC_WEB_SOURCE_SESSION_ID || urlSourceSessionId,
    sourceHopCount: env.CC_WEB_CROSS_HOP_COUNT || urlSourceHopCount,
    hasCcwebMcpConfig: Boolean(ccwebConfig),
    hasProjectMcpConfig: Boolean(projectConfig),
    ccwebType: ccwebConfig?.type || (ccwebConfig?.url ? 'streamable_http' : (ccwebConfig?.command ? 'stdio' : null)),
    ccwebUrl: ccwebConfig?.url || null,
    ccwebBearerTokenEnvVar: ccwebConfig?.bearer_token_env_var || null,
    ccwebCommand: ccwebConfig?.command || null,
    ccwebArgs: ccwebConfig?.args || null,
  };
  const itemBase = {
    id: itemId,
    type: 'mcpToolCall',
    server: 'ccweb',
    tool: 'ccweb_list_conversations',
    arguments: { limit: 5 },
  };

  send({
    method: 'item/started',
    params: {
      threadId: thread.id,
      turnId,
      startedAtMs: Date.now(),
      item: {
        ...itemBase,
        status: 'inProgress',
      },
    },
  });
  send({
    method: 'item/completed',
    params: {
      threadId: thread.id,
      turnId,
      completedAtMs: Date.now(),
      item: {
        ...itemBase,
        status: 'completed',
        result: {
          content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }],
          structuredContent: payload,
          isError: false,
        },
      },
    },
  });
  completeTurn(thread, turnId, `mcp result: ${JSON.stringify(payload)}`);
}

function emitCapacityError(thread, turnId) {
  send({
    method: 'error',
    params: {
      threadId: thread.id,
      turnId,
      type: 'error',
      error: {
        type: 'service_unavailable_error',
        code: 'server_is_overloaded',
        message: 'Our servers are currently overloaded. Please try again later.',
        param: null,
      },
      sequence_number: 2,
    },
  });
  thread.activeTurnId = null;
}

function emitPartialCapacityOutput(thread, turnId) {
  send({
    method: 'item/agentMessage/delta',
    params: {
      threadId: thread.id,
      turnId,
      itemId: 'agent-msg',
      delta: 'partial capacity output before retry',
    },
  });
  send({
    method: 'item/started',
    params: {
      threadId: thread.id,
      turnId,
      startedAtMs: Date.now(),
      item: {
        id: 'capacity-tool',
        type: 'commandExecution',
        command: '/bin/bash -lc echo capacity',
        status: 'inProgress',
      },
    },
  });
  send({
    method: 'item/commandExecution/outputDelta',
    params: {
      threadId: thread.id,
      turnId,
      itemId: 'capacity-tool',
      delta: 'capacity tool output\n',
    },
  });
}

function completeGuidedInputTurn(thread, turnId) {
  requestClient('item/tool/requestUserInput', {
    threadId: thread.id,
    turnId,
    itemId: 'guided-input-call',
    questions: [{
      id: 'choice',
      header: '选择',
      question: '选择一个测试答案',
      isOther: true,
      isSecret: false,
      options: [
        { label: 'A', description: '测试选项 A' },
        { label: 'B', description: '测试选项 B' },
      ],
    }],
  }, (message) => {
    const answer = message.result?.answers?.choice?.answers?.[0] || 'empty';
    completeTurn(thread, turnId, `guided answer: ${answer}`);
  });
}

function completeApprovalTurn(thread, turnId) {
  requestClient('item/commandExecution/requestApproval', {
    threadId: thread.id,
    turnId,
    itemId: 'approval-command-call',
    reason: 'Need to run an approval-gated command',
    command: 'echo approved',
    cwd: thread.cwd,
  }, (message) => {
    const decision = message.result?.decision || 'missing';
    completeTurn(thread, turnId, `approval decision: ${decision}`);
  });
}

function completeEmptyReasoningTurn(thread, turnId, text) {
  send({
    method: 'item/started',
    params: {
      threadId: thread.id,
      turnId,
      startedAtMs: Date.now(),
      item: {
        id: 'reasoning-empty',
        type: 'reasoning',
        content: [],
        summary: [],
        status: 'inProgress',
      },
    },
  });
  send({
    method: 'item/completed',
    params: {
      threadId: thread.id,
      turnId,
      completedAtMs: Date.now(),
      item: {
        id: 'reasoning-empty',
        type: 'reasoning',
        content: [],
        summary: [],
        status: 'completed',
      },
    },
  });
  completeTurn(thread, turnId, text);
}

function startTurn(params) {
  const thread = ensureThread(params.threadId, params);
  const turnId = `app-turn-${crypto.randomUUID()}`;
  const text = textFromInput(params.input);
  thread.activeTurnId = turnId;
  thread.steers = [];

  send({
    method: 'turn/started',
    params: {
      threadId: thread.id,
      turn: {
        id: turnId,
        status: 'running',
        items: [],
      },
    },
  });

  if (/runtime warning/i.test(text)) {
    const message = 'Heads up: Long threads and multiple compactions can cause the model to be less accurate. Start a new thread when possible to keep threads small and targeted.';
    for (let i = 0; i < 2; i += 1) {
      send({
        method: 'warning',
        params: {
          threadId: thread.id,
          turnId,
          message,
        },
      });
    }
  }

  if (/codexapp capacity retry/i.test(text)) {
    const retryKey = retryScenarioKey(text, 'codexapp capacity retry');
    const attempts = (thread.capacityRetryAttempts.get(retryKey) || 0) + 1;
    thread.capacityRetryAttempts.set(retryKey, attempts);
    if (attempts <= 2) {
      if (attempts === 2) emitPartialCapacityOutput(thread, turnId);
      emitCapacityError(thread, turnId);
      return { turn: { id: turnId, status: 'running', items: [] } };
    }
  }

  if (/codexapp reconnect retry/i.test(text)) {
    const retryKey = retryScenarioKey(text, 'codexapp reconnect retry');
    const attempts = (thread.reconnectRetryAttempts.get(retryKey) || 0) + 1;
    thread.reconnectRetryAttempts.set(retryKey, attempts);
    if (attempts === 1) {
      emitPartialCapacityOutput(thread, turnId);
      send({
        method: 'error',
        params: {
          threadId: thread.id,
          turnId,
          message: 'Reconnecting... 1/5',
        },
      });
      thread.activeTurnId = null;
      return { turn: { id: turnId, status: 'running', items: [] } };
    }
  }

  if (/codexapp retry thread mismatch/i.test(text)) {
    const retryKey = retryScenarioKey(text, 'codexapp retry thread mismatch');
    const attempts = (thread.capacityRetryAttempts.get(retryKey) || 0) + 1;
    thread.capacityRetryAttempts.set(retryKey, attempts);
    if (attempts === 1) {
      resumeMismatchThreads.add(thread.id);
      emitCapacityError(thread, turnId);
      return { turn: { id: turnId, status: 'running', items: [] } };
    }
  }

  if (/collaboration/i.test(text)) {
    completeTurn(thread, turnId, `collaboration mode: ${collaborationSummary(params)}`);
    return { turn: { id: turnId, status: 'running', items: [] } };
  }

  if (/empty reasoning/i.test(text)) {
    completeEmptyReasoningTurn(thread, turnId, text);
    return { turn: { id: turnId, status: 'running', items: [] } };
  }

  if (/dynamic/i.test(text)) {
    completeMcpToolTurn(thread, turnId);
    return { turn: { id: turnId, status: 'running', items: [] } };
  }

  if (/guided/i.test(text)) {
    if (!params.collaborationMode || params.collaborationMode.mode !== 'plan') {
      completeTurn(thread, turnId, 'guided input unavailable outside plan');
      return { turn: { id: turnId, status: 'running', items: [] } };
    }
    completeGuidedInputTurn(thread, turnId);
    return { turn: { id: turnId, status: 'running', items: [] } };
  }

  if (/approval/i.test(text)) {
    completeApprovalTurn(thread, turnId);
    return { turn: { id: turnId, status: 'running', items: [] } };
  }

  const delay = /recover/i.test(text) ? 5000 : /slow/i.test(text) ? 900 : 80;
  if (/recover/i.test(text)) {
    send({
      method: 'item/agentMessage/delta',
      params: {
        threadId: thread.id,
        turnId,
        itemId: 'agent-msg',
        delta: `partial before restart: ${text}`,
      },
    });
    send({
      method: 'item/started',
      params: {
        threadId: thread.id,
        turnId,
        startedAtMs: Date.now(),
        item: {
          id: 'recover-tool',
          type: 'commandExecution',
          command: '/bin/bash -lc echo recover',
          status: 'inProgress',
        },
      },
    });
    send({
      method: 'item/commandExecution/outputDelta',
      params: {
        threadId: thread.id,
        turnId,
        itemId: 'recover-tool',
        delta: 'recover tool output\n',
      },
    });
  }
  thread.timer = setTimeout(() => completeTurn(thread, turnId, text), delay);
  return { turn: { id: turnId, status: 'running', items: [] } };
}

function interruptTurn(params) {
  const thread = ensureThread(params.threadId, params);
  if (thread.timer) clearTimeout(thread.timer);
  if (thread.activeTurnId === params.turnId) {
    completeTurn(thread, params.turnId, 'interrupted', 'interrupted');
  }
  return {};
}

function steerTurn(params) {
  const thread = ensureThread(params.threadId, params);
  if (!thread.activeTurnId || thread.activeTurnId !== params.expectedTurnId) {
    return {
      error: {
        code: -32001,
        message: 'expectedTurnId does not match active turn',
      },
    };
  }
  const text = textFromInput(params.input);
  if (text) thread.steers.push(text);
  send({
    method: 'item/agentMessage/delta',
    params: {
      threadId: thread.id,
      turnId: thread.activeTurnId,
      itemId: 'agent-msg',
      delta: `\n[steer accepted: ${text}]`,
    },
  });
  return { result: {} };
}

function handleRequest(message) {
  const id = message.id;
  const method = message.method;
  const params = message.params || {};

  if (id && !method && pendingServerRequests.has(id)) {
    const callback = pendingServerRequests.get(id);
    pendingServerRequests.delete(id);
    callback(message);
    return;
  }

  try {
    if (method === 'initialize') {
      send({ id, result: { serverInfo: { name: 'mock-codex-app-server', version: '0.0.0' } } });
      return;
    }
    if (method === 'experimentalFeature/enablement/set') {
      send({ id, result: { enablement: params.enablement || {} } });
      return;
    }
    if (method === 'collaborationMode/list') {
      send({ id, result: { data: [{ mode: 'default' }, { mode: 'plan' }] } });
      return;
    }
    if (method === 'config/mcpServer/reload') {
      mcpReloadCount += 1;
      send({
        method: 'mcpServer/startupStatus/updated',
        params: {
          server: 'ccweb',
          state: 'starting',
          message: 'ccweb MCP starting',
          threadId: null,
        },
      });
      send({
        method: 'mcpServer/startupStatus/updated',
        params: {
          name: 'ccweb',
          status: 'ready',
          message: 'ccweb MCP ready CC_WEB_MCP_TOKEN=mock-secret-token',
          threadId: null,
        },
      });
      send({ id, result: { reloaded: true, reloadCount: mcpReloadCount } });
      return;
    }
    if (method === 'thread/start') {
      const thread = ensureThread(null, params);
      send({ id, result: { thread: threadPayload(thread), model: params.model || 'gpt-5.5', cwd: thread.cwd, modelProvider: 'mock', approvalPolicy: params.approvalPolicy || 'never', approvalsReviewer: 'user', sandbox: params.sandbox || 'danger-full-access' } });
      return;
    }
    if (method === 'thread/resume') {
      if (params.threadId && resumeMismatchThreads.delete(params.threadId)) {
        const thread = ensureThread(null, params);
        send({ id, result: { thread: threadPayload(thread), model: params.model || 'gpt-5.5', cwd: thread.cwd, modelProvider: 'mock', approvalPolicy: params.approvalPolicy || 'never', approvalsReviewer: 'user', sandbox: params.sandbox || 'danger-full-access' } });
        return;
      }
      const thread = ensureThread(params.threadId, params);
      send({ id, result: { thread: threadPayload(thread), model: params.model || 'gpt-5.5', cwd: thread.cwd, modelProvider: 'mock', approvalPolicy: params.approvalPolicy || 'never', approvalsReviewer: 'user', sandbox: params.sandbox || 'danger-full-access' } });
      return;
    }
    if (method === 'thread/goal/get') {
      const thread = ensureThread(params.threadId, params);
      send({ id, result: { goal: thread.goal } });
      return;
    }
    if (method === 'thread/goal/set') {
      const thread = ensureThread(params.threadId, params);
      const now = Date.now();
      const previous = thread.goal || {};
      const objective = String(params.objective || previous.objective || 'mock goal').trim();
      thread.goal = {
        threadId: thread.id,
        objective,
        status: String(params.status || previous.status || 'active'),
        tokenBudget: previous.tokenBudget ?? null,
        tokensUsed: previous.tokensUsed ?? 3,
        timeUsedSeconds: previous.timeUsedSeconds ?? 0,
        createdAt: previous.createdAt || now,
        updatedAt: now,
      };
      send({
        method: 'thread/goal/updated',
        params: { threadId: thread.id, goal: thread.goal },
      });
      setTimeout(() => {
        send({ id, result: { goal: thread.goal } });
      }, 250);
      return;
    }
    if (method === 'thread/goal/clear') {
      const thread = ensureThread(params.threadId, params);
      const cleared = !!thread.goal;
      thread.goal = null;
      send({
        method: 'thread/goal/cleared',
        params: { threadId: thread.id },
      });
      send({ id, result: { cleared } });
      return;
    }
    if (method === 'turn/start') {
      send({ id, result: startTurn(params) });
      return;
    }
    if (method === 'turn/steer') {
      const result = steerTurn(params);
      send({ id, ...result });
      return;
    }
    if (method === 'turn/interrupt') {
      send({ id, result: interruptTurn(params) });
      return;
    }
    if (method === 'initialized') return;

    send({ id, error: { code: -32601, message: `Unknown mock method: ${method}` } });
  } catch (err) {
    send({ id, error: { code: -32603, message: err.message || String(err) } });
  }
}

const rl = readline.createInterface({ input: process.stdin });
rl.on('line', (line) => {
  if (!line.trim()) return;
  let message;
  try {
    message = JSON.parse(line);
  } catch {
    return;
  }
  if (Object.prototype.hasOwnProperty.call(message, 'id')) handleRequest(message);
});
