import { state } from '../state.js';
import { elements } from '../utils/dom.js';
import {
  LLM_PROVIDERS,
  REASONING_COLLAPSE_CHARS,
  REASONING_COLLAPSE_LINES,
  AUTO_COLLAPSE_MIN_MESSAGES,
  TOOL_CONTENT_COLLAPSE_CHARS,
  TOOL_CONTENT_COLLAPSE_LINES,
} from '../constants.js';
import { t, getLanguage, setLanguage } from '../i18n/index.js';
import {
  AGENT_UI_EVENT_TYPES,
  AGENT_UI_FORM_STATUSES,
  AGENT_UI_RENDERER_SURFACES,
  buildAgentUiFormCancelRequest,
  buildAgentUiFormSubmitRequest,
  createAgentUiEventState,
  createAgentUiRendererRegistry,
  isAgentUiFormSubmittable,
  normalizeAgentUiEvents,
  reduceAgentUiEventState,
  renderAgentUiSurface,
} from '../agent-ui-events.js';

// Legacy application module. The functions below are intentionally kept together
// for this pass because chat, settings, knowledge, and cowork flows share state
// and DOM assumptions heavily. Future refactors can move each section into
// src/features/* and src/components/* with focused tests.

const COWORK_DEFAULT_RUN_ROUNDS = 20;
const COWORK_DEFAULT_RUN_AGENTS = 3;
const COWORK_DEFAULT_RUN_AGENT_CALLS = 30;
const COWORK_NODE_CLICK_MOVE_THRESHOLD = 4;
const SIDEBAR_DRAWER_CLOSE_MS = 480;
const nativeFetch = window.fetch.bind(window);
const BOOTSTRAP_PATH = "/webui/bootstrap";
const DEFAULT_TOKEN_REFRESH_PATH = "/webui/refresh-token";

state.agentUi = state.agentUi || createAgentUiEventState();

const agentUiRenderers = createAgentUiRendererRegistry({
  [AGENT_UI_RENDERER_SURFACES.message]: ({ message, options = {} }) => createMessageNode(message, options),
  [AGENT_UI_RENDERER_SURFACES.reasoning]: ({ text, previousState = {} }) => createReasoningNode(text, previousState),
  [AGENT_UI_RENDERER_SURFACES.toolRun]: ({ toolCall, relatedMessages = [], message }) => (
    message ? createToolMessageNode(message) : createToolCallNode(toolCall, relatedMessages)
  ),
  [AGENT_UI_RENDERER_SURFACES.approval]: ({ sessionKey }) => loadApprovals(sessionKey).catch(console.error),
  [AGENT_UI_RENDERER_SURFACES.browserSnapshot]: ({ message, frame }) => (
    message ? createBrowserSnapshotNode(message) : updateBrowserFrame(frame || {})
  ),
  [AGENT_UI_RENDERER_SURFACES.memoryReferences]: ({ references }) => createMemoryReferencesNode(references || []),
  [AGENT_UI_RENDERER_SURFACES.recentContextReferences]: ({ references }) => createRecentContextReferencesNode(references || []),
  [AGENT_UI_RENDERER_SURFACES.usageStatus]: ({ usage }) => updateUsageDisplay(usage),
  [AGENT_UI_RENDERER_SURFACES.errorNotice]: ({ message, path = "" }) => {
    setError(message);
    if (path) {
      setFileError(message);
    }
  },
  [AGENT_UI_RENDERER_SURFACES.formRequest]: ({ form }) => createAgentUiFormNode(form),
});

function setStatus(text, kind = "idle") {
  if (!elements.connectionStatus) {
    return;
  }
  elements.connectionStatus.textContent = text;
  elements.connectionStatus.className = `status status-${kind}`;
}

function setError(text = "") {
  elements.errorText.textContent = text;
}

function setEditorStatus(text, kind = "idle") {
  if (elements.editorStatus) {
    elements.editorStatus.textContent = text;
    elements.editorStatus.className = `status status-${kind}`;
  }
}

function escapeHtml(value) {
  return String(value)
    .replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;")
    .replace(/'/g, "&#39;");
}

function formatMessage(key, values = {}) {
  let text = t(key);
  for (const [name, value] of Object.entries(values)) {
    text = text.replaceAll(`{${name}}`, String(value));
  }
  return text;
}

function renderMarkdown(target, content) {
  target.textContent = "";
  if (!content || !content.trim()) {
    return;
  }

  if (typeof marked === "undefined") {
    target.textContent = content;
    return;
  }

  try {
    marked.setOptions({
      breaks: true,
      gfm: true,
    });
    target.innerHTML = marked.parse(content);

    if (typeof hljs !== "undefined") {
      target.querySelectorAll("pre code").forEach((block) => {
        hljs.highlightElement(block);
      });
    }
  } catch {
    target.textContent = content;
  }
}

function renderCoworkMessageBody(target, content, { markdown = true } = {}) {
  target.classList.toggle("is-plain", !markdown);
  if (!markdown) {
    target.textContent = content || "";
    return;
  }
  renderMarkdown(target, content || "");
  target.querySelectorAll("a").forEach((link) => {
    link.setAttribute("target", "_blank");
    link.setAttribute("rel", "noreferrer");
  });
}

function setFileError(text = "") {
  elements.fileError.textContent = text;
}

function renderSidebarActionIcons() {
  const buttons = [
    {
      element: elements.newChatButton,
      title: t("ui.newChat"),
      className: "sidebar-icon-new-chat",
      svg: `
        <svg class="icon-chat-plus" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
          <path d="M21 15a4 4 0 0 1-4 4H8l-5 3V7a4 4 0 0 1 4-4h7"></path>
          <path d="M19 3v8"></path>
          <path d="M15 7h8"></path>
        </svg>
      `,
    },
    {
      element: elements.refreshButton,
      title: t("ui.refresh"),
      className: "sidebar-icon-refresh",
      svg: `
        <svg class="icon-refresh" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
          <path d="M21 12a9 9 0 1 1-2.64-6.36"></path>
          <path d="M21 3v6h-6"></path>
        </svg>
      `,
    },
  ];
  for (const button of buttons) {
    if (!button.element) continue;
    button.element.classList.add("button-icon", "sidebar-action-icon", button.className);
    button.element.removeAttribute("data-i18n");
    button.element.setAttribute("title", button.title);
    button.element.setAttribute("aria-label", button.title);
    button.element.innerHTML = button.svg;
  }
}

function updateUsageDisplay(usage) {
  state.lastUsage = usage;
  const container = document.querySelector("#status-usage");
  if (!usage || !container) {
    if (container) {
      container.innerHTML = "-";
    }
    return;
  }
  const prompt = usage.prompt_tokens || 0;
  const completion = usage.completion_tokens || 0;
  const total = usage.total_tokens || 0;
  const cached = usage.cached_tokens || 0;
  const contextWindow = state.contextWindowTokens || 65536;

  // 计算占比
  const ratio = Math.min(1, total / contextWindow);
  const percent = Math.round(ratio * 100);
  const formatCompactTokens = (value) => {
    if (value >= 1000000) return `${(value / 1000000).toFixed(value >= 10000000 ? 0 : 1)}m`;
    if (value >= 1000) return `${(value / 1000).toFixed(value >= 10000 ? 0 : 1)}k`;
    return `${value}`;
  };

  // 根据占比决定颜色: <50%绿色, 50-75%黄色, 75-90%橙色, >90%红色
  let colorClass = "usage-bar-safe";
  if (ratio >= 0.9) {
    colorClass = "usage-bar-danger";
  } else if (ratio >= 0.75) {
    colorClass = "usage-bar-warning";
  } else if (ratio >= 0.5) {
    colorClass = "usage-bar-caution";
  }

  // 构建进度条HTML
  const lang = getLanguage();
  const labelText = lang === "zh"
    ? `输入:${prompt} 输出:${completion}`
    : `In:${prompt} Out:${completion}`;
  const totalText = lang === "zh"
    ? `${total}/${contextWindow}`
    : `${total}/${contextWindow}`;
  const compactTotalText = `${formatCompactTokens(total)}/${formatCompactTokens(contextWindow)}`;
  const cachedText = cached > 0
    ? (lang === "zh" ? `缓存:${cached}` : `Cache:${cached}`)
    : "";

  container.innerHTML = `
    <div class="usage-bar-wrapper" title="${totalText} (${percent}%) · ${labelText}${cachedText ? ` · ${cachedText}` : ""}">
      <div class="usage-bar-track">
        <div class="usage-bar-fill ${colorClass}" style="width: ${percent}%"></div>
      </div>
      <div class="usage-bar-text">
        <span class="usage-total">${compactTotalText}</span>
        <span class="usage-detail">${labelText}</span>
        ${cachedText ? `<span class="usage-cached">${cachedText}</span>` : ""}
      </div>
    </div>
  `;
}

function setBrowserPanelCollapsed(collapsed) {
  state.browserPanelCollapsed = collapsed;
  if (!elements.browserPanel) {
    return;
  }
  elements.browserPanel.classList.toggle("collapsed", collapsed);
  if (elements.browserPanelToggle) {
    const label = collapsed ? t("browser.expandView") : t("browser.collapseView");
    elements.browserPanelToggle.setAttribute("aria-label", label);
    elements.browserPanelToggle.title = label;
  }
}

function updateBrowserFrame(payload) {
  if (!elements.browserPanel) {
    return;
  }

  const receivedAt = payload.captured_at || new Date().toISOString();
  state.browserFrame = {
    imageUrl: payload.image_url || "",
    sourceCommand: payload.source_command || payload.command || "",
    capturedAt: receivedAt,
  };

  const hasImage = Boolean(state.browserFrame.imageUrl);

  if (elements.browserFrameImage) {
    if (hasImage) {
      elements.browserFrameImage.src = state.browserFrame.imageUrl;
    }
    elements.browserFrameImage.hidden = !hasImage;
  }
  if (elements.browserFrameEmpty) {
    elements.browserFrameEmpty.hidden = hasImage;
  }
  if (elements.browserFrameStatus) {
    elements.browserFrameStatus.textContent = hasImage ? "Live frame" : "Waiting for browser activity";
  }
  if (elements.browserFrameTime) {
    elements.browserFrameTime.textContent = formatTime(receivedAt);
  }
  if (elements.browserFrameCommand) {
    elements.browserFrameCommand.textContent = state.browserFrame.sourceCommand || "";
    elements.browserFrameCommand.title = state.browserFrame.sourceCommand || "";
  }
  elements.browserPanel.classList.toggle("has-frame", hasImage);
}

function authHeaders() {
  return {
    Authorization: `Bearer ${state.token}`,
    "Content-Type": "application/json",
  };
}

function resolveRequestUrl(input) {
  const rawUrl = input instanceof Request ? input.url : String(input);
  return new URL(rawUrl, window.location.href);
}

function shouldRecoverAuth(input) {
  const url = resolveRequestUrl(input);
  if (url.origin !== window.location.origin) {
    return false;
  }
  return url.pathname !== BOOTSTRAP_PATH && url.pathname !== state.tokenRefreshPath;
}

function withFreshAuth(input, init = {}) {
  const headers = new Headers(input instanceof Request ? input.headers : undefined);
  if (init.headers) {
    new Headers(init.headers).forEach((value, key) => headers.set(key, value));
  }
  headers.set("Authorization", `Bearer ${state.token}`);

  if (input instanceof Request) {
    return [new Request(input, { ...init, headers }), undefined];
  }
  return [input, { ...init, headers }];
}

function applyBootstrapPayload(payload) {
  state.token = payload.token;
  state.wsPath = payload.ws_path || "/ws";
  state.sessionsPath = payload.sessions_path || "/api/sessions";
  state.workspaceFilesPath = payload.workspace_files_path || "/api/workspace/files";
  state.coworkApiPath = payload.cowork_path || "/api/cowork";
  state.tokenTtlS = payload.token_ttl_s || state.tokenTtlS || 300;
  state.tokenIssuedAt = Date.now();
  state.tokenRefreshPath = payload.refresh_token_path || DEFAULT_TOKEN_REFRESH_PATH;
  scheduleTokenRefresh();
}

async function fetchBootstrapToken() {
  const response = await nativeFetch(BOOTSTRAP_PATH, { cache: "no-store" });
  if (!response.ok) {
    throw new Error(`bootstrap failed: ${response.status}`);
  }
  const payload = await response.json();
  applyBootstrapPayload(payload);
  return payload;
}

async function refreshAuthToken({ allowBootstrap = true } = {}) {
  if (state.tokenRefreshPromise) {
    return state.tokenRefreshPromise;
  }

  state.tokenRefreshPromise = (async () => {
    if (state.token) {
      const response = await nativeFetch(state.tokenRefreshPath || DEFAULT_TOKEN_REFRESH_PATH, {
        method: "POST",
        headers: { Authorization: `Bearer ${state.token}` },
        cache: "no-store",
      });
      if (response.ok) {
        const payload = await response.json();
        state.token = payload.token || state.token;
        state.tokenTtlS = payload.token_ttl_s || state.tokenTtlS || 300;
        state.tokenIssuedAt = Date.now();
        scheduleTokenRefresh();
        return payload;
      }
      if (!allowBootstrap || response.status !== 401) {
        throw new Error(`token refresh failed: ${response.status}`);
      }
    }
    return fetchBootstrapToken();
  })();

  try {
    return await state.tokenRefreshPromise;
  } finally {
    state.tokenRefreshPromise = null;
  }
}

function scheduleTokenRefresh() {
  if (state.tokenRefreshTimer) {
    window.clearTimeout(state.tokenRefreshTimer);
  }
  if (!state.tokenTtlS) {
    return;
  }
  const delayMs = Math.max(30_000, Math.floor(state.tokenTtlS * 700));
  state.tokenRefreshTimer = window.setTimeout(() => {
    refreshAuthToken({ allowBootstrap: true }).catch((error) => {
      console.warn("Unable to refresh WebUI token", error);
    });
  }, delayMs);
}

window.fetch = async function fetchWithAuthRecovery(input, init = {}) {
  const response = await nativeFetch(input, init);
  if (response.status !== 401 || !shouldRecoverAuth(input)) {
    return response;
  }

  await refreshAuthToken({ allowBootstrap: true });
  const [retryInput, retryInit] = withFreshAuth(input, init);
  return nativeFetch(retryInput, retryInit);
};

function resizeComposer() {
  const maxHeight = Math.max(88, Math.min(132, Math.floor(window.innerHeight * 0.18)));
  elements.composerInput.style.height = "0px";
  const nextHeight = Math.max(48, Math.min(elements.composerInput.scrollHeight, maxHeight));
  elements.composerInput.style.height = `${nextHeight}px`;
  elements.composerInput.style.overflowY = elements.composerInput.scrollHeight > maxHeight ? "auto" : "hidden";
}

function scrollMessagesToBottom(force = false) {
  const nearBottom =
    elements.messageList.scrollHeight - elements.messageList.scrollTop - elements.messageList.clientHeight < 120;
  if (force || nearBottom) {
    elements.messageList.scrollTop = elements.messageList.scrollHeight;
  }
}

function formatTime(value) {
  if (!value) {
    return "";
  }
  const date = new Date(value);
  if (Number.isNaN(date.getTime())) {
    return "";
  }
  const lang = getLanguage();
  const locale = lang === "zh" ? "zh-CN" : "en-US";
  return new Intl.DateTimeFormat(locale, {
    month: "2-digit",
    day: "2-digit",
    hour: "2-digit",
    minute: "2-digit",
  }).format(date);
}

function sessionKeyForChat(chatId) {
  return chatId ? `websocket:${chatId}` : "";
}

function messageContentText(content) {
  if (typeof content === "string") {
    return content;
  }
  if (Array.isArray(content)) {
    return content
      .map((item) => {
        if (typeof item === "string") return item;
        if (item && typeof item === "object") return item.text || item.content || "";
        return "";
      })
      .filter(Boolean)
      .join(" ");
  }
  return content == null ? "" : String(content);
}

function compactSessionTitleFromMessages(messages) {
  for (const message of messages || []) {
    if (message.role !== "user") {
      continue;
    }
    const text = messageContentText(message.content)
      .replace(/[`#*_>~-]/g, " ")
      .replace(/\s+/g, " ")
      .trim();
    if (!text) {
      continue;
    }
    return text.length > 36 ? `${text.slice(0, 36).trim()}...` : text;
  }
  return "";
}

function sessionTitleForKey(sessionKey) {
  const item = state.sessionItems.find((entry) => entry.key === sessionKey);
  const messages = state.messages.get(sessionKey) || [];
  return compactSessionTitleFromMessages(messages) || item?.title || t("ui.newSessionTitle");
}

function updateActiveChatTitle() {
  elements.chatTitle.textContent = state.activeSessionKey
    ? sessionTitleForKey(state.activeSessionKey)
    : t("ui.notConnected");
  elements.chatTitle.title = state.activeChatId || "";
}

function setSessionResponding(sessionKey, responding) {
  if (!sessionKey) {
    return;
  }
  if (responding) {
    state.respondingSessionKeys.add(sessionKey);
  } else {
    state.respondingSessionKeys.delete(sessionKey);
  }
  renderSessions();
}

function clearAllSessionResponding() {
  if (!state.respondingSessionKeys.size) {
    return;
  }
  state.respondingSessionKeys.clear();
  renderSessions();
}

function setSidebarDropdown(section) {
  const isCowork = section === "cowork";
  document.body.classList.toggle("sidebar-cowork-open", isCowork);
  document.body.classList.toggle("sidebar-sessions-open", !isCowork);
  elements.sessionsToggle?.setAttribute("aria-expanded", String(!isCowork));
  elements.coworkToggle?.setAttribute("aria-expanded", String(isCowork));
}

function renderApprovalPanel() {
  if (!elements.approvalPanel || !elements.approvalList) {
    return;
  }
  const approvals = state.pendingApprovals || [];
  elements.approvalList.textContent = "";
  elements.approvalPanel.hidden = approvals.length === 0;
  if (!approvals.length) {
    return;
  }

  for (const approval of approvals) {
    const item = document.createElement("div");
    item.className = "approval-item";

    const body = document.createElement("div");
    body.className = "approval-body";

    const meta = document.createElement("div");
    meta.className = "approval-meta";
    meta.textContent = `${approval.risk || "risk"} / ${approval.category || approval.tool_name || "tool"}`;

    const summary = document.createElement("div");
    summary.className = "approval-summary";
    summary.textContent = approval.summary || approval.tool_name || approval.id;
    summary.title = approval.reason || "";

    body.append(meta, summary);

    const actions = document.createElement("div");
    actions.className = "approval-actions";

    const onceButton = document.createElement("button");
    onceButton.type = "button";
    onceButton.className = "button button-primary approval-action";
    onceButton.textContent = t("approval.approveOnce");
    onceButton.addEventListener("click", () => handleApprovalAction(approval.id, "approve", "once"));

    const sessionButton = document.createElement("button");
    sessionButton.type = "button";
    sessionButton.className = "button approval-action";
    sessionButton.textContent = t("approval.allowSession");
    sessionButton.addEventListener("click", () => handleApprovalAction(approval.id, "approve", "session"));

    const denyButton = document.createElement("button");
    denyButton.type = "button";
    denyButton.className = "button button-ghost approval-action approval-deny";
    denyButton.textContent = t("approval.deny");
    denyButton.addEventListener("click", () => handleApprovalAction(approval.id, "deny"));

    actions.append(onceButton, sessionButton, denyButton);
    item.append(body, actions);
    elements.approvalList.append(item);
  }
}

function setSidebarCollapsed(collapsed) {
  state.sidebarCollapsed = collapsed;
  localStorage.setItem("tinybot-sidebar-collapsed", collapsed ? "true" : "false");
  elements.shell?.classList.toggle("sidebar-collapsed", collapsed);
  elements.sidebar?.classList.toggle("collapsed", collapsed);
  if (elements.sidebarCollapseButton) {
    const label = collapsed ? t("a11y.expandSidebar") : t("a11y.collapseSidebar");
    elements.sidebarCollapseButton.setAttribute("aria-label", label);
    elements.sidebarCollapseButton.title = label;
    elements.sidebarCollapseButton.setAttribute("aria-expanded", collapsed ? "false" : "true");
  }
}

function roleLabel(role) {
  if (role === "assistant") {
    return "assistant";
  }
  if (role === "user") {
    return "user";
  }
  if (role === "tool") {
    return "tool";
  }
  if (role === "progress") {
    return "progress";
  }
  return "system";
}

function shouldCollapseReasoning(text) {
  const normalized = text || "";
  return normalized.length > REASONING_COLLAPSE_CHARS || normalized.split(/\r?\n/).length > REASONING_COLLAPSE_LINES;
}

function shouldCollapseToolContent(text) {
  const normalized = text || "";
  return normalized.length > TOOL_CONTENT_COLLAPSE_CHARS || normalized.split(/\r?\n/).length > TOOL_CONTENT_COLLAPSE_LINES;
}

function formatToolArguments(args) {
  if (args == null || args === "") {
    return "";
  }
  if (typeof args === "string") {
    const trimmed = args.trim();
    if (!trimmed) {
      return "";
    }
    try {
      return JSON.stringify(JSON.parse(trimmed), null, 2);
    } catch {
      return trimmed;
    }
  }
  try {
    return JSON.stringify(args, null, 2);
  } catch {
    return String(args);
  }
}

function summarizeToolArguments(argsText) {
  if (!argsText) {
    return t("message.toolNoArgs");
  }
  const compact = argsText.replace(/\s+/g, " ").trim();
  return compact.length > 120 ? `${compact.slice(0, 120)}...` : compact;
}

function parseToolArguments(args) {
  if (!args) {
    return {};
  }
  if (typeof args === "object") {
    return args;
  }
  if (typeof args === "string") {
    try {
      const parsed = JSON.parse(args);
      return parsed && typeof parsed === "object" ? parsed : {};
    } catch {
      return {};
    }
  }
  return {};
}

function firstRegexMatch(text = "", patterns = []) {
  const value = String(text || "");
  for (const pattern of patterns) {
    const match = value.match(pattern);
    if (match?.[1]) {
      return match[1].trim();
    }
  }
  return "";
}

function extractCommandFromToolArgs(args, argsText = "") {
  const parsed = parseToolArguments(args);
  if (typeof parsed.command === "string" && parsed.command.trim()) {
    return parsed.command.trim();
  }
  return firstRegexMatch(argsText, [
    /"command"\s*:\s*"([^"]+)"/i,
    /command\s*=\s*"([^"]+)"/i,
    /command\s*=\s*'([^']+)'/i,
  ]);
}

function parseOpenCliBrowserCommand(command = "") {
  const text = String(command || "");
  const commandMatch = text.match(/opencli(?:\.(?:cmd|ps1|bat|exe))?\s+browser\s+([^\s&|;]+)?/i);
  if (!commandMatch) {
    return null;
  }
  const action = (commandMatch[1] || "open").toLowerCase();
  const url = firstRegexMatch(text, [
    /opencli(?:\.(?:cmd|ps1|bat|exe))?\s+browser\s+open\s+["']?([^"'\s&|;]+)/i,
    /(https?:\/\/[^\s"']+)/i,
  ]);
  return { action, url };
}

function browserActionLabel(action = "") {
  const labels = {
    open: t("browser.action.open"),
    back: t("browser.action.back"),
    forward: t("browser.action.forward"),
    reload: t("browser.action.reload"),
    click: t("browser.action.click"),
    type: t("browser.action.type"),
    select: t("browser.action.select"),
    keys: t("browser.action.keys"),
    wait: t("browser.action.wait"),
    state: t("browser.action.state"),
    screenshot: t("browser.action.screenshot"),
    network: t("browser.action.network"),
  };
  return labels[action] || action || t("browser.action.activity");
}

function parseBrowserResponseMeta(responseText = "") {
  const text = String(responseText || "");
  return {
    navigatedTo: firstRegexMatch(text, [/^Navigated to:\s*(.+)$/im]),
    url: firstRegexMatch(text, [/^URL:\s*(.+)$/im, /^url:\s*(.+)$/im]),
    title: firstRegexMatch(text, [/^title:\s*(.+)$/im]),
    viewport: firstRegexMatch(text, [/^viewport:\s*(.+)$/im]),
    pageScroll: firstRegexMatch(text, [/^page_scroll:\s*(.+)$/im]),
  };
}

function splitBrowserSnapshotOutput(responseText = "") {
  const text = String(responseText || "").trim();
  if (!text) {
    return { metadata: "", snapshot: "" };
  }
  const parts = text.split(/\r?\n---\r?\n/);
  if (parts.length < 2) {
    return { metadata: "", snapshot: "" };
  }
  return {
    metadata: parts.shift().trim(),
    snapshot: parts.join("\n---\n").trim(),
  };
}

function buildBrowserActivity(name, args, argsText, responseText = "") {
  const command = extractCommandFromToolArgs(args, argsText);
  const commandInfo = parseOpenCliBrowserCommand(command);
  const responseInfo = parseBrowserResponseMeta(responseText);
  const output = splitBrowserSnapshotOutput(responseText);
  const hasBrowserResponse = Boolean(responseInfo.navigatedTo || responseInfo.url || responseInfo.title);
  if (!commandInfo && !(String(name || "").toLowerCase() === "exec" && hasBrowserResponse)) {
    return null;
  }

  const action = commandInfo?.action || (responseInfo.navigatedTo ? "open" : "activity");
  const url = responseInfo.url || responseInfo.navigatedTo || commandInfo?.url || "";
  return {
    action,
    actionLabel: browserActionLabel(action),
    command,
    url,
    title: responseInfo.title,
    viewport: responseInfo.viewport,
    pageScroll: responseInfo.pageScroll,
    responseText,
    metadataText: output.metadata,
    snapshotText: output.snapshot,
    argsText,
  };
}

function browserActivityPreview(activity) {
  const parts = [];
  if (activity.title) {
    parts.push(activity.title);
  }
  if (activity.url) {
    parts.push(activity.url);
  }
  if (activity.viewport) {
    parts.push(activity.viewport);
  }
  return compactText(parts.join(" | ") || activity.command || t("browser.activity"), 120);
}

function browserActivitySections(activity) {
  const sections = [{ type: "browserActivity", activity }];
  if (activity.command) {
    sections.push({ label: t("browser.command"), text: activity.command });
  }
  if (activity.snapshotText) {
    sections.push({ label: t("browser.pageSnapshot"), text: activity.snapshotText, collapsed: true });
  } else if (activity.responseText) {
    sections.push({ label: t("browser.rawOutput"), text: activity.responseText, collapsed: true });
  }
  return sections;
}

function clampPercent(value) {
  if (!Number.isFinite(value)) {
    return 0;
  }
  return Math.min(100, Math.max(0, value));
}

function taskStatusLabel(status) {
  const labels = {
    planning: "Planning",
    executing: "Executing",
    completed: "Completed",
    failed: "Failed",
    paused: "Paused",
    pending: "Pending",
    in_progress: "Running",
    skipped: "Skipped",
  };
  return labels[status] || status || "Task";
}

function taskStatusClass(status) {
  return String(status || "unknown").replace(/[^a-z0-9_-]/gi, "-").toLowerCase();
}

function taskProgressPayload(message) {
  return message?._task_progress && typeof message._task_progress === "object"
    ? message._task_progress
    : null;
}

function createReasoningNode(reasoningText, previousState = {}) {
  const details = document.createElement("details");
  details.className = "message-reasoning message-reasoning-details";
  const keepManualState = previousState.manual === true;
  details.open = keepManualState && previousState.open === true;
  if (keepManualState) {
    details.dataset.reasoningManual = "true";
  }

  const summary = document.createElement("summary");
  summary.className = "message-reasoning-summary";
  summary.addEventListener("click", () => {
    details.dataset.reasoningManual = "true";
  });

  const title = document.createElement("span");
  title.className = "message-reasoning-title";
  title.textContent = t("message.thinking");

  const meta = document.createElement("span");
  meta.className = "message-reasoning-meta";
  const updateMeta = () => {
    meta.textContent = details.open ? t("message.thinkingVisible") : t("message.thinkingCollapsed");
  };
  updateMeta();
  details.addEventListener("toggle", updateMeta);

  summary.append(title, meta);

  const body = document.createElement("div");
  body.className = "message-reasoning-body";
  body.textContent = reasoningText;

  details.append(summary, body);
  return details;
}

function createEmptyChatNode() {
  const empty = document.createElement("div");
  empty.className = "empty-state empty-chat";
  const title = document.createElement("div");
  title.className = "empty-chat-title";
  title.textContent = t("msg.noMessages");
  const actions = document.createElement("div");
  actions.className = "empty-chat-actions";
  const examples = [
    "chat.example.summarizeFile",
    "chat.example.explainConcept",
    "chat.example.askKnowledge",
    "chat.example.createReminder",
  ];
  for (const key of examples) {
    const button = document.createElement("button");
    button.type = "button";
    button.className = "button button-ghost button-small";
    button.textContent = t(key);
    button.addEventListener("click", () => {
      elements.composerInput.value = t(key);
      resizeComposer();
      elements.composerInput.focus();
    });
    actions.append(button);
  }
  empty.append(title, actions);
  return empty;
}

function getToolName(message) {
  return message?._tool_name || message?.name || "";
}

function getToolCallName(toolCall) {
  return toolCall?.function?.name || toolCall?.name || "";
}

function getToolCallId(toolCall) {
  return toolCall?.id || toolCall?.tool_call_id || "";
}

function getToolMessageCallId(message) {
  return message?.tool_call_id || message?._tool_call_id || "";
}

function getToolCalls(message) {
  if (Array.isArray(message?.tool_calls)) {
    return message.tool_calls;
  }
  if (message?.tool_calls && typeof message.tool_calls === "object") {
    return [message.tool_calls];
  }
  return [];
}

function hasToolCalls(message) {
  return getToolCalls(message).length > 0;
}

function isProgressToolDetail(message) {
  return message?.role === "progress" && message?._tool_detail;
}

function isProgressToolResult(message) {
  return message?.role === "progress" && message?._tool_result;
}

function isToolProcessMessage(message) {
  return message?.role === "tool" || message?.role === "progress" || hasToolCalls(message);
}

function isRunChainSourceMessage(message) {
  const hasUserFacingContent = Boolean((message?.content || "").trim()) && !hasToolCalls(message);
  return isToolProcessMessage(message) || (!hasUserFacingContent && Boolean(message?.reasoning_content && message.reasoning_content.trim()));
}

function isFinalAssistantContent(message) {
  return (
    message?.role === "assistant" &&
    !hasToolCalls(message) &&
    !message._stream_resuming &&
    !state.streamBuffers.has(message.message_id || "") &&
    Boolean((message.content || "").trim())
  );
}

function shouldAutoCollapseTurn(messages, startIndex, finalIndex) {
  const collapsibleCount = finalIndex - startIndex;
  if (collapsibleCount < AUTO_COLLAPSE_MIN_MESSAGES) {
    return false;
  }

  for (let index = startIndex; index < finalIndex; index += 1) {
    if (isToolProcessMessage(messages[index])) {
      return true;
    }
  }
  return false;
}

function findFinalAssistantIndex(messages, startIndex) {
  let finalIndex = -1;
  for (let index = startIndex; index < messages.length; index += 1) {
    const message = messages[index];
    if (message.role === "user") {
      break;
    }
    if (isFinalAssistantContent(message)) {
      finalIndex = index;
    }
  }
  return finalIndex;
}

function prepareMessageRelationships(messages) {
  for (const message of messages) {
    if (message && "_relatedToolMessages" in message) {
      delete message._relatedToolMessages;
    }
    if (message && "_pairedToolResponse" in message) {
      delete message._pairedToolResponse;
    }
    if (message && "_pairedToolResponseConsumed" in message) {
      delete message._pairedToolResponseConsumed;
    }
  }

  for (let index = 0; index < messages.length; index += 1) {
    const message = messages[index];
    if (!hasToolCalls(message)) {
      continue;
    }

    message._relatedToolMessages = [];
    const toolNames = new Set(getToolCalls(message).map((tc) => tc.function?.name || tc.name || "").filter(Boolean));
    let nextIndex = index + 1;
    while (nextIndex < messages.length) {
      const nextMessage = messages[nextIndex];
      if (nextMessage.role !== "tool" && nextMessage.role !== "progress") {
        break;
      }
      const nextToolName = getToolName(nextMessage);
      if (toolNames.size > 0 && nextToolName && !toolNames.has(nextToolName)) {
        break;
      }
      message._relatedToolMessages.push(nextMessage);
      nextIndex += 1;
    }
  }

  const pendingDetailsByName = new Map();
  for (const message of messages) {
    if (message?.role !== "progress") {
      pendingDetailsByName.clear();
      continue;
    }

    if (isProgressToolDetail(message)) {
      const name = getToolName(message) || "";
      const queue = pendingDetailsByName.get(name) || [];
      queue.push(message);
      pendingDetailsByName.set(name, queue);
      continue;
    }

    if (!isProgressToolResult(message)) {
      continue;
    }

    const name = getToolName(message) || "";
    const queue = pendingDetailsByName.get(name) || [];
    const detailMessage = queue.shift();
    if (!detailMessage) {
      continue;
    }

    detailMessage._pairedToolResponse = message;
    message._pairedToolResponseConsumed = true;
    if (queue.length === 0) {
      pendingDetailsByName.delete(name);
    }
  }
}

function relatedToolMessagesEndIndex(messages, startIndex) {
  const message = messages[startIndex];
  if (!hasToolCalls(message)) {
    return startIndex;
  }

  const toolNames = new Set(getToolCalls(message).map((tc) => tc.function?.name || tc.name || "").filter(Boolean));
  let nextIndex = startIndex + 1;
  while (nextIndex < messages.length) {
    const nextMessage = messages[nextIndex];
    if (nextMessage.role !== "tool" && nextMessage.role !== "progress") {
      break;
    }
    const nextToolName = getToolName(nextMessage);
    if (toolNames.size > 0 && nextToolName && !toolNames.has(nextToolName)) {
      break;
    }
    nextIndex += 1;
  }
  return nextIndex - 1;
}

function relatedToolMessageGroups(toolCalls, relatedMessages) {
  const groups = toolCalls.map(() => []);
  const usedMessageIndexes = new Set();
  const callIdToIndex = new Map();

  toolCalls.forEach((toolCall, index) => {
    const id = getToolCallId(toolCall);
    if (id) {
      callIdToIndex.set(id, index);
    }
  });

  relatedMessages.forEach((message, index) => {
    const messageCallId = getToolMessageCallId(message);
    const callIndex = messageCallId ? callIdToIndex.get(messageCallId) : undefined;
    if (callIndex !== undefined) {
      groups[callIndex].push(message);
      usedMessageIndexes.add(index);
    }
  });

  let fallbackCursor = 0;
  relatedMessages.forEach((message, messageIndex) => {
    if (usedMessageIndexes.has(messageIndex)) {
      return;
    }

    const messageName = getToolName(message);
    for (let offset = 0; offset < toolCalls.length; offset += 1) {
      const callIndex = (fallbackCursor + offset) % toolCalls.length;
      const callName = getToolCallName(toolCalls[callIndex]);
      if (!messageName || !callName || messageName === callName) {
        groups[callIndex].push(message);
        usedMessageIndexes.add(messageIndex);
        fallbackCursor = (callIndex + 1) % toolCalls.length;
        return;
      }
    }
  });

  return groups;
}

function createMessageDisplayItems(messages) {
  const items = [];
  let assistantMetaShownInTurn = false;

  const pushMessageItem = (message) => {
    const item = {
      type: "message",
      message,
    };
    if (message?.role === "user") {
      assistantMetaShownInTurn = false;
    }
    if (message?.role === "assistant" && Boolean((message.content || "").trim())) {
      item.hideMeta = assistantMetaShownInTurn;
      assistantMetaShownInTurn = true;
    }
    items.push(item);
  };
  const pushRunChainItem = (runChainMessages) => {
    items.push({
      type: "run-chain",
      messages: [...runChainMessages],
    });
    assistantMetaShownInTurn = true;
  };

  for (let index = 0; index < messages.length; index += 1) {
    const message = messages[index];
    if (isSessionFileProgressMessage(message)) {
      continue;
    }
    if (message?._pairedToolResponseConsumed) {
      continue;
    }
    const finalIndex = message?.role === "user" ? -1 : findFinalAssistantIndex(messages, index);

    if (finalIndex > index && shouldAutoCollapseTurn(messages, index, finalIndex)) {
      const turnMessages = messages.slice(index, finalIndex);
      const runChainMessages = [];
      const flushRunChain = () => {
        if (!runChainMessages.length) {
          return;
        }
        pushRunChainItem(runChainMessages);
        runChainMessages.length = 0;
      };
      for (const turnMessage of turnMessages) {
        if (turnMessage?._pairedToolResponseConsumed) {
          continue;
        }
        if (turnMessage._task_event) {
          flushRunChain();
          pushMessageItem(turnMessage);
          continue;
        }
        if (isRunChainSourceMessage(turnMessage)) {
          runChainMessages.push(turnMessage);
          continue;
        }
        flushRunChain();
        pushMessageItem(turnMessage);
      }
      flushRunChain();
      pushMessageItem(messages[finalIndex]);
      index = finalIndex;
      continue;
    }

    if (isRunChainSourceMessage(message)) {
      const runChainMessages = [];
      let chainIndex = index;
      while (chainIndex < messages.length) {
        const chainMessage = messages[chainIndex];
        if (chainMessage?._task_event || chainMessage?._pairedToolResponseConsumed || !isRunChainSourceMessage(chainMessage)) {
          break;
        }
        runChainMessages.push(chainMessage);
        if (hasToolCalls(chainMessage)) {
          chainIndex = relatedToolMessagesEndIndex(messages, chainIndex) + 1;
        } else {
          chainIndex += 1;
        }
      }
      if (runChainMessages.length) {
        pushRunChainItem(runChainMessages);
        index = chainIndex - 1;
        continue;
      }
    }

    pushMessageItem(message);

    if (hasToolCalls(message)) {
      index = relatedToolMessagesEndIndex(messages, index);
    }
  }

  return items;
}

function isSessionFileProgressMessage(message) {
  if (!message || message.role !== "progress") {
    return false;
  }
  const content = String(message.content || "");
  return content.startsWith(t("sessionFiles.uploaded")) || content.startsWith("临时文件已加入当前会话");
}

function createCollapsedMessagesNode(collapsedMessages) {
  const wrapper = document.createElement("article");
  wrapper.className = "message-collapse-group";

  const button = document.createElement("button");
  button.className = "message-collapse-summary";
  button.type = "button";
  button.setAttribute("aria-expanded", "false");

  const icon = document.createElement("span");
  icon.className = "message-collapse-icon";
  icon.setAttribute("aria-hidden", "true");
  icon.textContent = ">";

  const displayCount = collapsedMessages.filter((message) => !message?._pairedToolResponseConsumed).length;
  const labelText = t("message.collapsedCount").replace("{count}", String(displayCount));
  const label = document.createElement("span");
  label.className = "message-collapse-label";
  label.textContent = `${labelText} · ${t("message.collapsedHint")}`;

  button.append(icon, label);

  const body = document.createElement("div");
  body.className = "message-collapse-body";
  body.hidden = true;
  for (let index = 0; index < collapsedMessages.length; index += 1) {
    const message = collapsedMessages[index];
    if (message?._pairedToolResponseConsumed) {
      continue;
    }
    body.append(createMessageNode(message));
    if (hasToolCalls(message)) {
      index = relatedToolMessagesEndIndex(collapsedMessages, index);
    }
  }

  button.addEventListener("click", () => {
    const expanded = button.getAttribute("aria-expanded") === "true";
    button.setAttribute("aria-expanded", expanded ? "false" : "true");
    wrapper.classList.toggle("expanded", !expanded);
    body.hidden = expanded;
    label.textContent = `${labelText} · ${expanded ? t("message.collapsedHint") : t("message.collapseAgain")}`;
  });

  wrapper.append(button, body);
  return wrapper;
}

function runChainStatusClass(items) {
  if (items.some((item) => item.status === "failed")) return "failed";
  if (items.some((item) => item.status === "running")) return "running";
  return "completed";
}

function runChainStatusLabel(status) {
  const labels = {
    running: t("status.running"),
    completed: t("status.completed"),
    failed: t("runChain.needsAttention"),
  };
  return labels[status] || t("status.running");
}

function inferRunChainItemStatus(message, responseText = "") {
  const raw = String(message?._approval_status || message?.status || "").toLowerCase();
  if (raw.includes("fail") || raw.includes("error") || raw.includes("denied")) return "failed";
  if (message?.role === "tool" || message?._tool_result || message?._pairedToolResponse || responseText) return "completed";
  if (message?.role === "progress" || hasToolCalls(message)) return "running";
  return "completed";
}

function toolKindLabel(name = "") {
  const value = String(name || "").toLowerCase();
  if (value.includes("read")) return t("runChain.kind.read");
  if (value.includes("write") || value.includes("create")) return t("runChain.kind.file");
  if (value.includes("exec") || value.includes("shell") || value.includes("terminal")) return t("runChain.kind.command");
  if (value.includes("browser") || value.includes("web")) return t("runChain.kind.browser");
  if (value.includes("task") || value.includes("agent") || value.includes("spawn")) return t("runChain.kind.agent");
  return t("runChain.kind.tool");
}

function createRunChainItems(messages) {
  const items = [];
  for (let index = 0; index < messages.length; index += 1) {
    const message = messages[index];
    if (!message || message._pairedToolResponseConsumed) continue;

    if (message.reasoning_content && message.reasoning_content.trim()) {
      const reasoningText = message.reasoning_content.trim();
      items.push({
        key: `${message.message_id || `reasoning-${index}`}:planning`,
        kind: "planning",
        title: t("runChain.planning"),
        preview: compactText(reasoningText, 120),
        status: "completed",
        inspectable: true,
        detailTitle: t("runChain.planning"),
        detailSubtitle: t("runChain.thinkingTrace"),
        detailSections: [{ label: t("message.thinking"), text: reasoningText }],
      });
    }

    if (hasToolCalls(message)) {
      const relatedMessages = message._relatedToolMessages || [];
      const toolCalls = getToolCalls(message);
      const relatedGroups = relatedToolMessageGroups(toolCalls, relatedMessages);
      toolCalls.forEach((toolCall, toolIndex) => {
        const name = getToolCallName(toolCall) || "tool";
        const rawArgs = toolCall.function?.arguments ?? toolCall.arguments ?? "";
        const argsText = formatToolArguments(rawArgs);
        const responseText = (relatedGroups[toolIndex] || []).map((item) => item.content || "").filter(Boolean).join("\n\n");
        const browserActivity = buildBrowserActivity(name, rawArgs, argsText, responseText);
        items.push({
          key: `${message.message_id || `tool-call-${index}`}:${getToolCallId(toolCall) || toolIndex}`,
          kind: browserActivity ? "browser" : "tool",
          title: browserActivity ? `${t("runChain.kind.browser")} | ${browserActivity.actionLabel}` : `${toolKindLabel(name)} | ${name}`,
          preview: browserActivity ? browserActivityPreview(browserActivity) : (responseText ? compactText(responseText, 120) : summarizeToolArguments(argsText)),
          status: inferRunChainItemStatus(message, responseText),
          inspectable: true,
          detailTitle: browserActivity?.title || (browserActivity ? t("browser.activity") : name),
          detailSubtitle: browserActivity ? t("browser.activitySubtitle") : (responseText ? t("runChain.toolCallAndResponse") : t("message.toolCall")),
          detailSections: browserActivity
            ? browserActivitySections(browserActivity)
            : [
                { label: t("message.toolArgs"), text: argsText || t("message.toolNoArgs") },
                ...(responseText ? [{ label: t("message.toolResponse"), text: responseText }] : []),
              ],
        });
      });
      index = relatedToolMessagesEndIndex(messages, index);
      continue;
    }

    if (message.role === "tool" || message.role === "progress") {
      const name = getToolName(message) || "tool";
      const isResult = message._tool_result || message.role === "tool";
      const argsText = isResult ? "" : message.content || "";
      const responseText = isResult ? message.content || "" : message._pairedToolResponse?.content || "";
      const browserActivity = buildBrowserActivity(name, message.content || "", argsText, responseText);
      items.push({
        key: `${message.message_id || `tool-message-${index}`}:${message._tool_result ? "result" : "detail"}`,
        kind: browserActivity ? "browser" : "tool",
        title: browserActivity ? `${t("runChain.kind.browser")} | ${browserActivity.actionLabel}` : `${toolKindLabel(name)} | ${name}`,
        preview: browserActivity ? browserActivityPreview(browserActivity) : compactText(responseText || argsText || t("runChain.toolActivity"), 120),
        status: inferRunChainItemStatus(message, responseText),
        inspectable: true,
        detailTitle: browserActivity?.title || (browserActivity ? t("browser.activity") : name),
        detailSubtitle: browserActivity ? t("browser.activitySubtitle") : (responseText ? t("runChain.toolDetailAndResponse") : t("runChain.toolDetail")),
        detailSections: browserActivity
          ? browserActivitySections(browserActivity)
          : [
              ...(argsText ? [{ label: t("runChain.detail"), text: argsText }] : []),
              ...(responseText ? [{ label: t("message.toolResponse"), text: responseText }] : []),
            ],
      });
    }
  }
  return items;
}

function runChainSummaryText(items) {
  const toolCount = items.filter((item) => item.kind === "tool" || item.kind === "browser").length;
  const planningCount = items.filter((item) => item.kind === "planning").length;
  const status = runChainStatusClass(items);
  const parts = [runChainStatusLabel(status), formatMessage("runChain.itemsCount", { count: items.length })];
  if (toolCount) parts.push(formatMessage("runChain.toolsCount", { count: toolCount }));
  if (planningCount) parts.push(t("runChain.planningLower"));
  return parts.join(" | ");
}

function runChainKey(messages) {
  const parts = messages
    .map((message, index) => message?.message_id || message?.tool_call_id || message?._tool_name || message?.timestamp || `item-${index}`)
    .filter(Boolean);
  return `${state.activeSessionKey || "session"}:${parts.join("|")}`;
}

function renderInspectorSection(section) {
  if (section?.type === "browserActivity") {
    return renderBrowserActivityInspector(section.activity);
  }

  const block = document.createElement("section");
  block.className = "inspector-section";
  const label = document.createElement("h3");
  label.textContent = section.label;
  const pre = document.createElement("pre");
  pre.textContent = section.text || "";

  if (section.collapsed) {
    const details = document.createElement("details");
    details.className = "inspector-section-collapsible";
    const summary = document.createElement("summary");
    summary.append(label);
    details.append(summary, pre);
    block.append(details);
    return block;
  }

  block.append(label, pre);
  return block;
}

function renderBrowserActivityInspector(activity) {
  const block = document.createElement("section");
  block.className = "browser-activity-card";

  const header = document.createElement("div");
  header.className = "browser-activity-header";

  const badge = document.createElement("span");
  badge.className = "browser-activity-badge";
  badge.textContent = activity.actionLabel || t("browser.activity");

  const main = document.createElement("div");
  main.className = "browser-activity-main";

  const title = document.createElement("h3");
  title.textContent = activity.title || activity.url || t("browser.activity");

  const url = document.createElement("div");
  url.className = "browser-activity-url";
  url.textContent = activity.url || t("browser.urlUnavailable");
  url.title = activity.url || "";

  main.append(title, url);
  header.append(badge, main);

  const metrics = document.createElement("dl");
  metrics.className = "browser-activity-metrics";
  [
    [t("browser.metric.action"), activity.actionLabel],
    [t("browser.metric.viewport"), activity.viewport],
    [t("browser.metric.scroll"), activity.pageScroll],
  ].forEach(([label, value]) => {
    if (!value) return;
    const item = document.createElement("div");
    const dt = document.createElement("dt");
    dt.textContent = label;
    const dd = document.createElement("dd");
    dd.textContent = value;
    item.append(dt, dd);
    metrics.append(item);
  });

  block.append(header);
  if (metrics.children.length) {
    block.append(metrics);
  }
  return block;
}

function renderRunChainInspector(item) {
  if (!elements.inspectorPanel || !elements.inspectorBody) return;
  elements.inspectorTitle.textContent = item?.detailTitle || item?.title || t("runChain.item");
  elements.inspectorSubtitle.textContent = item?.detailSubtitle || item?.preview || "";
  elements.inspectorBody.textContent = "";
  const sections = item?.detailSections || [];
  if (!sections.length) {
    const empty = document.createElement("div");
    empty.className = "inspector-empty";
    empty.textContent = t("inspector.noSavedDetail");
    elements.inspectorBody.append(empty);
    return;
  }
  sections.forEach((section) => elements.inspectorBody.append(renderInspectorSection(section)));
}

function memoryReferenceFile(reference) {
  return reference?.view_file || reference?.file || "memory/MEMORY.md";
}

function memoryReferenceLine(reference) {
  return reference?.view_line || reference?.line || reference?.cursor || null;
}

function memoryReferenceKey(reference) {
  const file = memoryReferenceFile(reference);
  const line = memoryReferenceLine(reference) || "";
  const id = reference?.note_id || reference?.evidence_id || reference?.content || "";
  return `${file}:${line}:${id}`;
}

function memoryReferenceLocationLabel(reference) {
  const line = memoryReferenceLine(reference);
  if (line) {
    return formatMessage("memory.lineLabel", { line });
  }
  return t("memory.positionUnknown");
}

function resolveMemoryHighlightLine(lines, targetLine, reference) {
  const needles = [reference?.note_id, reference?.content]
    .filter((value) => typeof value === "string" && value.trim());
  for (const needle of needles) {
    const index = lines.findIndex((line) => line.includes(needle));
    if (index >= 0) {
      return index + 1;
    }
  }
  return Number(targetLine) || 0;
}

function renderMemorySourcePreview(content, targetLine, reference) {
  const wrapper = document.createElement("div");
  wrapper.className = "memory-source-preview";

  const lines = String(content || "").split(/\r?\n/);
  const highlightLine = resolveMemoryHighlightLine(lines, targetLine, reference);
  if (highlightLine) {
    wrapper.dataset.highlightLine = String(highlightLine);
  }

  lines.forEach((line, index) => {
    const lineNumber = index + 1;
    const row = document.createElement("div");
    row.className = "memory-source-line";
    row.dataset.line = String(lineNumber);
    if (lineNumber === highlightLine) {
      row.classList.add("highlighted");
    }

    const gutter = document.createElement("span");
    gutter.className = "memory-source-line-number";
    gutter.textContent = String(lineNumber);

    const code = document.createElement("code");
    code.textContent = line || " ";

    row.append(gutter, code);
    wrapper.append(row);
  });

  window.requestAnimationFrame(() => {
    wrapper.querySelector(".memory-source-line.highlighted")?.scrollIntoView({
      block: "center",
      behavior: "smooth",
    });
  });

  return wrapper;
}

async function renderMemoryReferenceInspector(reference) {
  if (!elements.inspectorPanel || !elements.inspectorBody) return;

  const file = memoryReferenceFile(reference);
  const line = memoryReferenceLine(reference);
  elements.inspectorTitle.textContent = file.split("/").pop() || file;
  elements.inspectorSubtitle.textContent = line
    ? formatMessage("memory.sourceSubtitle", { file, line })
    : file;
  elements.inspectorBody.textContent = "";

  const summary = document.createElement("section");
  summary.className = "inspector-section memory-inspector-summary";
  const summaryHeading = document.createElement("h3");
  summaryHeading.textContent = t("memory.selectedReference");
  const summaryText = document.createElement("pre");
  summaryText.textContent = reference?.content || "";
  summary.append(summaryHeading, summaryText);
  elements.inspectorBody.append(summary);

  const sourceSection = document.createElement("section");
  sourceSection.className = "inspector-section memory-source-section";
  const sourceHeading = document.createElement("h3");
  sourceHeading.textContent = t("memory.sourceFile");
  const loading = document.createElement("div");
  loading.className = "inspector-empty memory-source-loading";
  loading.textContent = t("memory.loadingSource");
  sourceSection.append(sourceHeading, loading);
  elements.inspectorBody.append(sourceSection);

  try {
    const response = await fetch(`${state.workspaceFilesPath}/${encodeURIComponent(file)}`, {
      headers: { Authorization: `Bearer ${state.token}` },
      cache: "no-store",
    });
    if (!response.ok) {
      throw new Error(`load memory source failed: ${response.status}`);
    }
    const payload = await response.json();
    const preview = renderMemorySourcePreview(payload.content || "", line, reference);
    const resolvedLine = Number(preview.dataset.highlightLine || line);
    if (resolvedLine) {
      elements.inspectorSubtitle.textContent = formatMessage("memory.sourceSubtitle", {
        file,
        line: resolvedLine,
      });
    }
    loading.replaceWith(preview);
  } catch (error) {
    console.warn("Unable to load memory source", error);
    loading.textContent = t("memory.sourceUnavailable");
  }
}

function openMemoryReferenceInspector(reference) {
  if (!reference) return;
  state.inspectionOpen = true;
  state.selectedChainItemKey = "";
  state.selectedChainItem = null;
  state.selectedMemoryReferenceKey = memoryReferenceKey(reference);
  state.selectedMemoryReference = reference;
  elements.shell?.classList.add("inspection-mode");
  elements.inspectorPanel?.setAttribute("aria-hidden", "false");
  renderMemoryReferenceInspector(reference);
  renderMessages(false);
}

function openInspectionMode(item) {
  if (!item?.inspectable) return;
  state.inspectionOpen = true;
  state.selectedChainItemKey = item.key;
  state.selectedChainItem = item;
  state.selectedMemoryReferenceKey = "";
  state.selectedMemoryReference = null;
  elements.shell?.classList.add("inspection-mode");
  elements.inspectorPanel?.setAttribute("aria-hidden", "false");
  renderRunChainInspector(item);
  renderMessages(false);
}

function closeInspectionMode() {
  state.inspectionOpen = false;
  state.selectedChainItemKey = "";
  state.selectedChainItem = null;
  state.selectedMemoryReferenceKey = "";
  state.selectedMemoryReference = null;
  elements.shell?.classList.remove("inspection-mode");
  elements.inspectorPanel?.setAttribute("aria-hidden", "true");
  elements.messageList?.querySelectorAll(".run-chain-item.selected").forEach((node) => {
    node.classList.remove("selected");
  });
  elements.messageList?.querySelectorAll(".memory-reference-item.selected").forEach((node) => {
    node.classList.remove("selected");
  });
}

function revealRunChainBody(wrapper, body) {
  window.requestAnimationFrame(() => {
    if (!elements.messageList || !wrapper || !body || body.hidden) return;

    const scrollerRect = elements.messageList.getBoundingClientRect();
    const bodyRect = body.getBoundingClientRect();
    const margin = 18;
    const overBottom = bodyRect.bottom - scrollerRect.bottom + margin;
    const overTop = scrollerRect.top - bodyRect.top + margin;

    if (bodyRect.height > scrollerRect.height * 0.68) {
      wrapper.scrollIntoView({ block: "start", behavior: "smooth" });
      return;
    }

    if (overBottom > 0) {
      elements.messageList.scrollBy({ top: overBottom, behavior: "smooth" });
    } else if (overTop > 0) {
      elements.messageList.scrollBy({ top: -overTop, behavior: "smooth" });
    }
  });
}

function toggleRunChain(wrapper, nextExpanded = null) {
  if (!wrapper) return;
  const body = wrapper.querySelector(".run-chain-body");
  const label = wrapper.querySelector(".run-chain-label");
  if (!body || !label) return;

  const isExpanded = wrapper.open === true;
  const expanded = nextExpanded === null ? !isExpanded : Boolean(nextExpanded);
  wrapper.open = expanded;
  wrapper.classList.toggle("expanded", expanded);

  const key = wrapper.dataset.runChainKey || "";
  if (key) {
    if (expanded) {
      state.expandedRunChains.add(key);
    } else {
      state.expandedRunChains.delete(key);
    }
  }

  const summary = wrapper.dataset.runChainSummary || t("status.running");
  label.textContent = `${summary} | ${expanded ? t("runChain.hideDetails") : t("runChain.showDetails")}`;
  if (expanded) {
    revealRunChainBody(wrapper, body);
  }
}

function createRunChainItemNode(item) {
  const row = document.createElement(item.inspectable ? "button" : "div");
  row.className = `run-chain-item run-chain-item-${item.kind} run-chain-item-${item.status}`;
  row.dataset.chainItemKey = item.key;
  if (state.selectedChainItemKey && state.selectedChainItemKey === item.key) {
    row.classList.add("selected");
  }
  if (item.inspectable) {
    row.type = "button";
    row.setAttribute("aria-label", formatMessage("inspector.inspectItem", { title: item.title }));
    row.addEventListener("click", () => openInspectionMode(item));
  }

  const dot = document.createElement("span");
  dot.className = "run-chain-dot";
  dot.setAttribute("aria-hidden", "true");

  const text = document.createElement("span");
  text.className = "run-chain-item-text";

  const title = document.createElement("span");
  title.className = "run-chain-item-title";
  title.textContent = item.title;

  const preview = document.createElement("span");
  preview.className = "run-chain-item-preview";
  preview.textContent = item.preview || "";

  text.append(title, preview);

  const action = document.createElement("span");
  action.className = "run-chain-item-action";
  action.textContent = item.inspectable ? t("inspector.inspect") : t("cowork.summary");

  row.append(dot, text, action);
  return row;
}

function createRunChainNode(runChainMessages) {
  const items = createRunChainItems(runChainMessages);
  if (!items.length) {
    const fallback = document.createElement("div");
    fallback.hidden = true;
    return fallback;
  }
  const key = runChainKey(runChainMessages);
  const expanded = state.expandedRunChains?.has(key) === true;
  const summaryText = runChainSummaryText(items);
  const wrapper = document.createElement("details");
  wrapper.className = `run-chain run-chain-${runChainStatusClass(items)}`;
  wrapper.classList.toggle("expanded", expanded);
  wrapper.open = expanded;
  wrapper.dataset.runChainKey = key;
  wrapper.dataset.runChainSummary = summaryText;

  const summary = document.createElement("summary");
  summary.className = "run-chain-summary";

  const icon = document.createElement("span");
  icon.className = "run-chain-icon";
  icon.setAttribute("aria-hidden", "true");
  icon.textContent = ">";

  const title = document.createElement("span");
  title.className = "run-chain-title";
  title.textContent = t("runChain.title");

  const label = document.createElement("span");
  label.className = "run-chain-label";
  label.textContent = `${summaryText} | ${expanded ? t("runChain.hideDetails") : t("runChain.showDetails")}`;

  summary.append(icon, title, label);

  const body = document.createElement("div");
  body.className = "run-chain-body";
  items.forEach((item) => body.append(createRunChainItemNode(item)));

  wrapper.addEventListener("toggle", () => toggleRunChain(wrapper, wrapper.open));
  wrapper.append(summary, body);
  return wrapper;
}

function createToolActivitySection({ label, text, className = "" }) {
  const section = document.createElement("div");
  section.className = `tool-activity-section ${className}`.trim();

  const labelEl = document.createElement("div");
  labelEl.className = "tool-activity-label";
  labelEl.textContent = label;

  const pre = document.createElement("pre");
  pre.className = "tool-activity-pre";
  pre.textContent = text;

  if (!shouldCollapseToolContent(text)) {
    section.append(labelEl, pre);
    return section;
  }

  const details = document.createElement("details");
  details.className = "tool-activity-content-details";

  const summary = document.createElement("summary");
  summary.className = "tool-activity-content-summary";

  const summaryLabel = document.createElement("span");
  summaryLabel.className = "tool-activity-label";
  summaryLabel.textContent = label;

  const summaryPreview = document.createElement("span");
  summaryPreview.className = "tool-activity-content-preview";
  summaryPreview.textContent = summarizeToolArguments(text);

  summary.append(summaryLabel, summaryPreview);
  details.append(summary, pre);
  section.append(details);
  return section;
}

function createToolActivityNode({ name, argsText = "", responseText = "", kind = "call", approvalStatus = "" }) {
  const callEl = document.createElement("details");
  callEl.className = "tool-activity";
  if (argsText && responseText) {
    callEl.classList.add("tool-activity-paired");
  }

  const summary = document.createElement("summary");
  summary.className = "tool-activity-summary";

  const icon = document.createElement("span");
  icon.className = "tool-activity-icon";
  icon.setAttribute("aria-hidden", "true");
  icon.textContent = ">";

  const main = document.createElement("span");
  main.className = "tool-activity-main";

  const title = document.createElement("span");
  title.className = "tool-activity-title";
  title.textContent = name || "unknown";

  const preview = document.createElement("span");
  preview.className = "tool-activity-preview";
  preview.textContent = summarizeToolArguments(argsText || responseText);

  main.append(title, preview);

  const badges = document.createElement("span");
  badges.className = "tool-activity-badges";

  if (approvalStatus === "approved") {
    const approvalBadge = document.createElement("span");
    approvalBadge.className = "tool-activity-badge tool-activity-approval-badge";
    approvalBadge.textContent = t("approval.approved");
    approvalBadge.title = t("approval.approvedToolTitle");
    badges.append(approvalBadge);
  }

  const badge = document.createElement("span");
  badge.className = "tool-activity-badge";
  badge.textContent = kind === "result" ? t("message.toolResult") : t("message.toolCall");
  badges.append(badge);

  summary.append(icon, main, badges);
  callEl.append(summary);

  const body = document.createElement("div");
  body.className = "tool-activity-body";

  if (argsText) {
    const argsSection = createToolActivitySection({
      label: t("message.toolArgs"),
      text: argsText,
      className: "tool-activity-section-call",
    });
    body.append(argsSection);
  }

  if (responseText) {
    const responseSection = createToolActivitySection({
      label: t("message.toolResponse"),
      text: responseText,
      className: "tool-activity-section-response",
    });
    body.append(responseSection);
  }

  if (!argsText && !responseText) {
    const empty = document.createElement("div");
    empty.className = "tool-activity-empty";
    empty.textContent = t("message.toolNoArgs");
    body.append(empty);
  }

  callEl.append(body);
  return callEl;
}

function createTaskToolCallNode(toolCall, argsText, responseText) {
  const args = parseToolArguments(toolCall.function?.arguments ?? toolCall.arguments ?? "");
  const action = args.action || "task";
  const titleParts = ["task", action].filter(Boolean);
  const node = createToolActivityNode({
    name: titleParts.join(":"),
    argsText,
    responseText,
    kind: responseText ? "result" : "call",
  });
  node.classList.add("task-tool-activity");
  return node;
}

function createToolCallNode(toolCall, relatedMessages = []) {
  const name = getToolCallName(toolCall) || "unknown";
  const args = toolCall.function?.arguments ?? toolCall.arguments ?? "";
  const argsText = formatToolArguments(args);
  const responseText = relatedMessages.map((message) => message.content || "").filter(Boolean).join("\n\n");
  const approvalStatus = relatedMessages.some((message) => message?._approval_status === "approved") ? "approved" : "";
  if (name === "task") {
    return createTaskToolCallNode(toolCall, argsText, responseText);
  }
  return createToolActivityNode({ name, argsText, responseText, kind: responseText ? "result" : "call", approvalStatus });
}

function createToolMessageNode(message) {
  const isResult = message._tool_result || message.role === "tool";
  const approvalStatus = message._approval_status || message._pairedToolResponse?._approval_status || "";
  return createToolActivityNode({
    name: getToolName(message) || "tool",
    argsText: isResult ? "" : message.content || "",
    responseText: isResult ? message.content || "" : message._pairedToolResponse?.content || "",
    kind: isResult || message._pairedToolResponse ? "result" : "call",
    approvalStatus,
  });
}

function renderMessages(forceScroll = true) {
  const key = state.activeSessionKey;
  const messages = state.messages.get(key) || [];
  const previousBottomOffset =
    elements.messageList.scrollHeight - elements.messageList.scrollTop - elements.messageList.clientHeight;
  const wasNearBottom = previousBottomOffset < 120;
  elements.messageList.textContent = "";

  if (!key) {
    const empty = document.createElement("div");
    empty.className = "empty-state";
    empty.textContent = t("msg.noSession");
    elements.messageList.append(empty);
    return;
  }

  if (messages.length === 0) {
    elements.messageList.append(createEmptyChatNode());
    return;
  }

  prepareMessageRelationships(messages);
  const displayItems = createMessageDisplayItems(messages);
  for (const item of displayItems) {
    const node = item.type === "run-chain"
      ? createRunChainNode(item.messages)
      : item.type === "collapse"
        ? createCollapsedMessagesNode(item.messages)
        : renderAgentUiSurface(agentUiRenderers, AGENT_UI_RENDERER_SURFACES.message, {
            message: item.message,
            options: { hideMeta: item.hideMeta },
          });
    elements.messageList.append(node);
  }

  if (forceScroll || wasNearBottom) {
    scrollMessagesToBottom(true);
  } else {
    elements.messageList.scrollTop = Math.max(
      0,
      elements.messageList.scrollHeight - elements.messageList.clientHeight - previousBottomOffset,
    );
  }
}

function createMessageNode(message, options = {}) {
  const node = elements.messageTemplate.content.firstElementChild.cloneNode(true);
  node.classList.add(`message-${roleLabel(message.role)}`);
  const hideMeta = options.hideMeta || message.role === "user";
  if (options.hideMeta) {
    node.classList.add("message-continuation");
  }
  node.dataset.messageId = message.message_id || "";

  // Handle role display for tool messages
  let roleDisplay = roleLabel(message.role);
  if (message.role === "tool" && message.name) {
    roleDisplay = `tool: ${message.name}`;
  }
  if (message.role === "progress") {
    roleDisplay = "tool";
    if (message._tool_name) {
      roleDisplay = `tool: ${message._tool_name}`;
    }
  }
  const metaEl = node.querySelector(".message-meta");
  if (hideMeta) {
    metaEl?.remove();
  } else {
    if (message.role === "assistant") {
      node.querySelector(".message-role")?.remove();
    } else {
      node.querySelector(".message-role").textContent = roleDisplay;
    }
    node.querySelector(".message-time").textContent = formatTime(message.timestamp);
  }

  const contentEl = node.querySelector(".message-content");
  updateMessageContent(contentEl, message);

  // Add a copy button only when the metadata row is visible.
  if (!hideMeta && (message.role === "user" || message.role === "assistant")) {
    if (metaEl) {
      const copyBtn = document.createElement("button");
      copyBtn.className = "message-copy-btn";
      copyBtn.title = t("ui.copyContent");
      copyBtn.setAttribute("aria-label", t("ui.copyContent"));
      copyBtn.dataset.tooltip = t("ui.copy");
      copyBtn.innerHTML = `
        <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
          <rect x="8" y="8" width="10" height="10" rx="2"></rect>
          <path d="M6 14H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h7a2 2 0 0 1 2 2v1"></path>
        </svg>
      `;
      copyBtn.addEventListener("click", async () => {
        try {
          const textToCopy = message.content || "";
          await navigator.clipboard.writeText(textToCopy);
          copyBtn.dataset.tooltip = t("ui.copied");
          setTimeout(() => {
            copyBtn.dataset.tooltip = t("ui.copy");
          }, 1500);
        } catch {
          copyBtn.dataset.tooltip = t("ui.copyFailed");
          setTimeout(() => {
            copyBtn.dataset.tooltip = t("ui.copy");
          }, 1500);
        }
      });
      metaEl.appendChild(copyBtn);
    }
  }
  if (!hideMeta && message.role === "assistant" && metaEl) {
    node.appendChild(metaEl);
  }

  return node;
}

function createTaskMetric(label, value) {
  const item = document.createElement("span");
  item.className = "task-progress-metric";

  const labelEl = document.createElement("span");
  labelEl.className = "task-progress-metric-label";
  labelEl.textContent = label;

  const valueEl = document.createElement("span");
  valueEl.className = "task-progress-metric-value";
  valueEl.textContent = String(value ?? 0);

  item.append(labelEl, valueEl);
  return item;
}

function createTaskProgressNode(message) {
  const data = taskProgressPayload(message) || {};
  const progress = data.progress || {};
  const planId = data.plan_id || progress.plan_id || message._task_plan_id || "";
  const title = data.plan_title || progress.title || "Task";
  const status = data.plan_status || progress.status || "executing";
  const total = Number(progress.total || 0);
  const completed = Number(progress.completed || 0);
  const percent = total > 0 ? clampPercent((completed / total) * 100) : 0;
  const subtasks = Array.isArray(data.subtasks) ? data.subtasks : [];

  const details = document.createElement("details");
  details.className = `task-progress-card task-progress-${taskStatusClass(status)}`;
  details.open = status !== "completed";

  const summary = document.createElement("summary");
  summary.className = "task-progress-summary";

  const marker = document.createElement("span");
  marker.className = `task-progress-status-dot task-status-${taskStatusClass(status)}`;
  marker.setAttribute("aria-hidden", "true");

  const main = document.createElement("span");
  main.className = "task-progress-main";

  const titleRow = document.createElement("span");
  titleRow.className = "task-progress-title-row";

  const titleEl = document.createElement("span");
  titleEl.className = "task-progress-title";
  titleEl.textContent = title;

  const badge = document.createElement("span");
  badge.className = `task-progress-badge task-badge-${taskStatusClass(status)}`;
  badge.textContent = taskStatusLabel(status);

  titleRow.append(titleEl, badge);

  const meta = document.createElement("span");
  meta.className = "task-progress-meta";
  const planText = planId ? `Plan ${planId}` : "Task plan";
  meta.textContent = `${planText} · ${completed}/${total || subtasks.length || 0}`;

  const bar = document.createElement("span");
  bar.className = "task-progress-bar";
  const fill = document.createElement("span");
  fill.className = "task-progress-bar-fill";
  fill.style.width = `${percent}%`;
  bar.append(fill);

  main.append(titleRow, meta, bar);
  summary.append(marker, main);
  details.append(summary);

  const body = document.createElement("div");
  body.className = "task-progress-body";

  const metrics = document.createElement("div");
  metrics.className = "task-progress-metrics";
  metrics.append(
    createTaskMetric("Done", progress.completed),
    createTaskMetric("Running", progress.in_progress),
    createTaskMetric("Pending", progress.pending),
    createTaskMetric("Failed", progress.failed),
  );
  body.append(metrics);

  if (subtasks.length) {
    const list = document.createElement("div");
    list.className = "task-progress-subtasks";
    subtasks.forEach((subtask) => {
      const row = document.createElement("div");
      row.className = `task-progress-subtask task-subtask-${taskStatusClass(subtask.status)}`;

      const subtaskDot = document.createElement("span");
      subtaskDot.className = `task-progress-subtask-dot task-status-${taskStatusClass(subtask.status)}`;
      subtaskDot.setAttribute("aria-hidden", "true");

      const text = document.createElement("span");
      text.className = "task-progress-subtask-text";

      const subtaskTitle = document.createElement("span");
      subtaskTitle.className = "task-progress-subtask-title";
      subtaskTitle.textContent = subtask.title || subtask.id || "Subtask";

      const subtaskMeta = document.createElement("span");
      subtaskMeta.className = "task-progress-subtask-meta";
      const metaParts = [subtask.id, taskStatusLabel(subtask.status)];
      if (Array.isArray(subtask.dependencies) && subtask.dependencies.length) {
        metaParts.push(`after ${subtask.dependencies.join(", ")}`);
      }
      if (subtask.parallel_safe === false) {
        metaParts.push("sequential");
      }
      subtaskMeta.textContent = metaParts.filter(Boolean).join(" · ");

      text.append(subtaskTitle, subtaskMeta);
      row.append(subtaskDot, text);
      list.append(row);
    });
    body.append(list);
  }

  details.append(body);
  return details;
}

function restoreAgentUiFormsFromMessages(messages) {
  state.agentUi = state.agentUi || createAgentUiEventState();
  for (const message of messages || []) {
    const display = message._agent_ui_form_display;
    if (!display || !display.form_id) {
      continue;
    }
    const schema = display.schema || {};
    state.agentUi.forms.set(display.form_id, {
      form_id: display.form_id,
      schema,
      title: schema.title || "",
      description: schema.description || "",
      fields: schema.fields || [],
      submit_label: schema.submit_label || "",
      cancel_label: schema.cancel_label || "",
      expires_at: display.expires_at || schema.expires_at || "",
      status: display.status || message._agent_ui_form_status || AGENT_UI_FORM_STATUSES.pending,
      correlation: display.correlation || schema.correlation || { form_id: display.form_id },
      values: display.values || {},
      errors: display.errors || {},
      chat_id: schema.correlation?.chat_id || "",
      message_id: message.message_id || schema.correlation?.message_id || "",
      run_id: schema.correlation?.run_id || "",
      updated_at: display.updated_at || message.timestamp || "",
    });
  }
}

function createBrowserSnapshotNode(message) {
  const snapshotEl = document.createElement("figure");
  snapshotEl.className = "browser-snapshot";

  if (message.image_url) {
    const img = document.createElement("img");
    img.className = "browser-snapshot-image";
    img.src = message.image_url;
    img.alt = "Browser snapshot";
    snapshotEl.append(img);
  }

  if (message.source_command) {
    const caption = document.createElement("figcaption");
    caption.className = "browser-snapshot-caption";
    caption.textContent = message.source_command;
    snapshotEl.append(caption);
  }

  return snapshotEl;
}

function updateMessageContent(contentEl, message) {
  const previousReasoning = contentEl.querySelector(".message-reasoning-details");
  const previousReasoningState = previousReasoning
    ? { open: previousReasoning.open, manual: previousReasoning.dataset.reasoningManual === "true" }
    : {};
  contentEl.textContent = "";

  if (message.reasoning_content && message.reasoning_content.trim()) {
    contentEl.append(renderAgentUiSurface(agentUiRenderers, AGENT_UI_RENDERER_SURFACES.reasoning, {
      text: message.reasoning_content,
      previousState: previousReasoningState,
    }));
  }

  if (message._browser_snapshot) {
    contentEl.append(renderAgentUiSurface(agentUiRenderers, AGENT_UI_RENDERER_SURFACES.browserSnapshot, { message }));
    return;
  }

  if (message._task_event) {
    contentEl.append(createTaskProgressNode(message));
    return;
  }

  if (message._agent_ui_form_id) {
    const form = state.agentUi?.forms?.get(message._agent_ui_form_id);
    if (form) {
      contentEl.append(renderAgentUiSurface(agentUiRenderers, AGENT_UI_RENDERER_SURFACES.formRequest, { form, message }));
      return;
    }
  }

  // Handle tool_calls for assistant messages
  if (message.role === "assistant" && hasToolCalls(message)) {
    const toolCallsEl = document.createElement("div");
    toolCallsEl.className = "tool-activities";
    const relatedMessages = message._relatedToolMessages || [];
    const toolCalls = getToolCalls(message);
    const relatedGroups = relatedToolMessageGroups(toolCalls, relatedMessages);
    toolCalls.forEach((tc, index) => {
      toolCallsEl.append(renderAgentUiSurface(agentUiRenderers, AGENT_UI_RENDERER_SURFACES.toolRun, {
        toolCall: tc,
        relatedMessages: relatedGroups[index] || [],
      }));
    });
    contentEl.append(toolCallsEl);
  }

  if (message.role === "tool" || message.role === "progress") {
    const toolCallsEl = document.createElement("div");
    toolCallsEl.className = "tool-activities";
    toolCallsEl.append(renderAgentUiSurface(agentUiRenderers, AGENT_UI_RENDERER_SURFACES.toolRun, { message }));
    contentEl.append(toolCallsEl);
    return;
  }

  if (message.content && message.content.trim()) {
    const textEl = document.createElement("div");
    textEl.className = "message-text";

    // 对assistant消息使用Markdown渲染
    if (message.role === "assistant" && typeof marked !== "undefined") {
      renderMarkdown(textEl, message.content);
      try {
        // 应用代码语法高亮和添加复制按钮
        if (typeof hljs !== "undefined") {
          textEl.querySelectorAll("pre code").forEach((block) => {
            // 添加复制按钮到代码块
            const pre = block.parentElement;
            if (pre && !pre.querySelector(".code-copy-btn")) {
              const copyBtn = document.createElement("button");
              copyBtn.className = "code-copy-btn";
              copyBtn.textContent = t("ui.copy");
              copyBtn.addEventListener("click", async () => {
                try {
                  await navigator.clipboard.writeText(block.textContent);
                  copyBtn.textContent = t("ui.copied");
                  setTimeout(() => {
                    copyBtn.textContent = t("ui.copy");
                  }, 1500);
                } catch {
                  copyBtn.textContent = t("ui.copyFailed");
                  setTimeout(() => {
                    copyBtn.textContent = t("ui.copy");
                  }, 1500);
                }
              });
              pre.appendChild(copyBtn);
            }
          });
        }
      } catch {
        textEl.textContent = message.content;
      }
    } else if (message.role === "tool" || message.role === "progress") {
      // Tool results are often truncated/preformatted, show as code-like
      textEl.textContent = message.content;
    } else {
      textEl.textContent = message.content;
    }

    contentEl.append(textEl);
  }

  if (message.role === "assistant" && Array.isArray(message._memory_references) && message._memory_references.length) {
    contentEl.append(renderAgentUiSurface(agentUiRenderers, AGENT_UI_RENDERER_SURFACES.memoryReferences, {
      references: message._memory_references,
    }));
  }
  if (message.role === "assistant" && Array.isArray(message._recent_context_references) && message._recent_context_references.length) {
    contentEl.append(renderAgentUiSurface(agentUiRenderers, AGENT_UI_RENDERER_SURFACES.recentContextReferences, {
      references: message._recent_context_references,
    }));
  }
}

function agentUiFormMessageId(form) {
  return form.message_id || `agent-ui-form:${form.form_id}`;
}

function upsertAgentUiFormMessage(event) {
  const formId = event.payload?.form_id || "";
  if (!formId || !event.chat_id) {
    return;
  }
  const sessionKey = sessionKeyForChat(event.chat_id);
  const bucket = ensureMessageBucket(sessionKey);
  const messageId = event.message_id || `agent-ui-form:${formId}`;
  const existing = bucket.find((message) => message._agent_ui_form_id === formId || message.message_id === messageId);
  if (existing) {
    existing.message_id = messageId;
    existing.timestamp = event.timestamp || existing.timestamp;
    existing._agent_ui_form_id = formId;
    existing._agent_ui_form_status = state.agentUi.forms.get(formId)?.status || existing._agent_ui_form_status || "";
  } else {
    bucket.push({
      role: "assistant",
      content: "",
      timestamp: event.timestamp || new Date().toISOString(),
      message_id: messageId,
      _agent_ui_form_id: formId,
      _agent_ui_form_status: state.agentUi.forms.get(formId)?.status || AGENT_UI_FORM_STATUSES.pending,
    });
  }
  if (sessionKey === state.activeSessionKey) {
    renderMessages(false);
  }
}

function isAgentUiFormEvent(event) {
  return event?.event_type && event.event_type.startsWith("ui.form.");
}

function formStatusLabel(status) {
  switch (status) {
    case AGENT_UI_FORM_STATUSES.submitted:
      return "Submitted";
    case AGENT_UI_FORM_STATUSES.cancelled:
      return "Cancelled";
    case AGENT_UI_FORM_STATUSES.expired:
      return "Expired";
    case AGENT_UI_FORM_STATUSES.validationFailed:
      return "Needs changes";
    default:
      return "Pending";
  }
}

function isFormReadonly(form) {
  return [
    AGENT_UI_FORM_STATUSES.submitted,
    AGENT_UI_FORM_STATUSES.cancelled,
    AGENT_UI_FORM_STATUSES.expired,
  ].includes(form?.status);
}

function formFieldInputId(form, field) {
  return `agent-ui-form-${form.form_id}-${field.name}`.replace(/[^A-Za-z0-9_-]/g, "-");
}

function formFieldValue(form, field) {
  if (form.values && Object.hasOwn(form.values, field.name)) {
    return form.values[field.name];
  }
  return field.default ?? "";
}

function setFormFieldValue(values, field, value) {
  if (field.type === "number") {
    values[field.name] = value === "" ? "" : Number(value);
  } else {
    values[field.name] = value;
  }
}

function agentUiFormOptionValue(field, optionIndex) {
  const index = Number(optionIndex);
  if (!Number.isInteger(index) || index < 0 || index >= (field.options || []).length) {
    return "";
  }
  return field.options[index].value;
}

function agentUiFormOptionMatches(value, optionValue) {
  return Object.is(value, optionValue);
}

function agentUiFormValueInOptions(field, value) {
  return (field.options || []).some((option) => agentUiFormOptionMatches(value, option.value));
}

function collectAgentUiFormValues(form, root) {
  const values = {};
  for (const field of form.fields || []) {
    const selector = `[data-agent-ui-form-field="${CSS.escape(field.name)}"]`;
    if (field.type === "checkbox") {
      values[field.name] = root.querySelector(selector)?.checked === true;
    } else if (field.type === "select") {
      const selected = root.querySelector(selector)?.selectedOptions?.[0];
      values[field.name] = selected ? agentUiFormOptionValue(field, selected.dataset.agentUiFormOptionIndex) : "";
    } else if (field.type === "multiselect") {
      values[field.name] = Array.from(root.querySelectorAll(`${selector}:checked`))
        .map((input) => agentUiFormOptionValue(field, input.dataset.agentUiFormOptionIndex));
    } else if (field.type === "radio") {
      const selected = root.querySelector(`${selector}:checked`);
      values[field.name] = selected ? agentUiFormOptionValue(field, selected.dataset.agentUiFormOptionIndex) : "";
    } else {
      setFormFieldValue(values, field, root.querySelector(selector)?.value ?? "");
    }
  }
  return values;
}

function validateAgentUiFormCardValues(form, values) {
  const errors = {};
  for (const field of form.fields || []) {
    const value = values[field.name];
    if (field.required) {
      const missing = Array.isArray(value) ? value.length === 0 : value === "" || value === null || value === undefined;
      if (missing) {
        errors[field.name] = "Required";
        continue;
      }
    }
    if (field.type === "number" && value !== "" && value !== null && value !== undefined) {
      if (!Number.isFinite(value)) {
        errors[field.name] = "Enter a valid number";
      } else if (typeof field.min === "number" && value < field.min) {
        errors[field.name] = `Must be at least ${field.min}`;
      } else if (typeof field.max === "number" && value > field.max) {
        errors[field.name] = `Must be ${field.max} or less`;
      }
    }
    if (typeof value === "string") {
      if (typeof field.min_length === "number" && value.length < field.min_length) {
        errors[field.name] = `Use at least ${field.min_length} characters`;
      } else if (typeof field.max_length === "number" && value.length > field.max_length) {
        errors[field.name] = `Use ${field.max_length} characters or fewer`;
      } else if (field.pattern && !new RegExp(field.pattern).test(value)) {
        errors[field.name] = "Use the requested format";
      }
    }
    if (["select", "radio"].includes(field.type) && value !== "" && value !== null && value !== undefined) {
      if (!agentUiFormValueInOptions(field, value)) {
        errors[field.name] = "Choose a listed option";
      }
    }
    if (field.type === "multiselect" && Array.isArray(value)) {
      if (value.some((item) => !agentUiFormValueInOptions(field, item))) {
        errors[field.name] = "Choose listed options";
      }
    }
  }
  return errors;
}

function applyAgentUiFormValidationErrors(form, values, errors) {
  const existing = state.agentUi.forms.get(form.form_id) || form;
  state.agentUi.forms.set(form.form_id, {
    ...existing,
    status: AGENT_UI_FORM_STATUSES.validationFailed,
    values,
    errors,
    submitting: false,
    updated_at: new Date().toISOString(),
  });
  renderMessages(false);
}

function focusFirstAgentUiFormError(formId) {
  requestAnimationFrame(() => {
    const card = elements.messageList.querySelector(`[data-agent-ui-form-id="${CSS.escape(formId)}"]`);
    const errorField = card?.querySelector("[aria-invalid='true']");
    errorField?.focus();
  });
}

async function submitAgentUiForm(form, root) {
  if (!isAgentUiFormSubmittable(form)) {
    return;
  }
  const values = collectAgentUiFormValues(form, root);
  const errors = validateAgentUiFormCardValues(form, values);
  if (Object.keys(errors).length) {
    applyAgentUiFormValidationErrors(form, values, errors);
    focusFirstAgentUiFormError(form.form_id);
    return;
  }
  const requestPayload = buildAgentUiFormSubmitRequest(form, values);
  if (!requestPayload) {
    return;
  }
  const existing = state.agentUi.forms.get(form.form_id) || form;
  state.agentUi.forms.set(form.form_id, {
    ...existing,
    submitting: true,
  });
  const response = await fetch(`/api/agent-ui/forms/${encodeURIComponent(form.form_id)}/submit`, {
    method: "POST",
    headers: authHeaders(),
    body: JSON.stringify(requestPayload),
  });
  const payload = await response.json().catch(() => ({}));
  if (!response.ok) {
    applyAgentUiFormValidationErrors(form, values, payload.errors || { _form: payload.error || "Submit failed" });
    focusFirstAgentUiFormError(form.form_id);
    return;
  }
  state.agentUi.forms.set(form.form_id, {
    ...existing,
    status: AGENT_UI_FORM_STATUSES.submitted,
    values: payload.values || values,
    errors: {},
    submitting: false,
    updated_at: new Date().toISOString(),
  });
  renderMessages(false);
}

async function cancelAgentUiForm(form) {
  const requestPayload = buildAgentUiFormCancelRequest(form);
  if (!requestPayload) {
    return;
  }
  const existing = state.agentUi.forms.get(form.form_id) || form;
  state.agentUi.forms.set(form.form_id, {
    ...existing,
    submitting: true,
  });
  const response = await fetch(`/api/agent-ui/forms/${encodeURIComponent(form.form_id)}/cancel`, {
    method: "POST",
    headers: authHeaders(),
    body: JSON.stringify(requestPayload),
  });
  if (!response.ok) {
    const payload = await response.json().catch(() => ({}));
    applyAgentUiFormValidationErrors(form, form.values || {}, { _form: payload.error || "Cancel failed" });
    return;
  }
  state.agentUi.forms.set(form.form_id, {
    ...existing,
    status: AGENT_UI_FORM_STATUSES.cancelled,
    errors: {},
    submitting: false,
    updated_at: new Date().toISOString(),
  });
  renderMessages(false);
}

function createAgentUiFieldHelp(text, id, className = "agent-ui-form-help") {
  const help = document.createElement("div");
  help.id = id;
  help.className = className;
  help.textContent = text;
  return help;
}

function createAgentUiChoiceField(form, field, readonly) {
  const group = document.createElement("div");
  group.className = `agent-ui-form-choice-group agent-ui-form-choice-${field.type}`;
  const current = formFieldValue(form, field);
  const currentValues = Array.isArray(current) ? current : [current];
  for (const [optionIndex, option] of (field.options || []).entries()) {
    const label = document.createElement("label");
    label.className = "agent-ui-form-choice";
    const input = document.createElement("input");
    input.type = field.type === "radio" ? "radio" : "checkbox";
    input.name = `${form.form_id}:${field.name}`;
    input.value = String(optionIndex);
    input.checked = currentValues.some((value) => agentUiFormOptionMatches(value, option.value));
    input.disabled = readonly;
    input.dataset.agentUiFormField = field.name;
    input.dataset.agentUiFormOptionIndex = String(optionIndex);
    const text = document.createElement("span");
    text.textContent = option.label;
    label.append(input, text);
    group.append(label);
  }
  return group;
}

function createAgentUiFormField(form, field, readonly) {
  const wrapper = document.createElement("div");
  wrapper.className = "agent-ui-form-field";
  const inputId = formFieldInputId(form, field);
  const errorText = form.errors?.[field.name] || "";
  const helpId = `${inputId}-help`;
  const errorId = `${inputId}-error`;

  const label = document.createElement("label");
  label.className = "agent-ui-form-label";
  label.htmlFor = inputId;
  label.textContent = field.label;
  if (field.required) {
    const required = document.createElement("span");
    required.className = "agent-ui-form-required";
    required.textContent = "Required";
    label.append(required);
  }

  let control;
  const value = formFieldValue(form, field);
  if (field.type === "textarea") {
    control = document.createElement("textarea");
    control.rows = 3;
    control.value = value || "";
  } else if (field.type === "select") {
    control = document.createElement("select");
    const empty = document.createElement("option");
    empty.value = "";
    empty.textContent = "";
    control.append(empty);
    for (const [optionIndex, option] of (field.options || []).entries()) {
      const optionEl = document.createElement("option");
      optionEl.value = String(optionIndex);
      optionEl.textContent = option.label;
      optionEl.selected = agentUiFormOptionMatches(value, option.value);
      optionEl.dataset.agentUiFormOptionIndex = String(optionIndex);
      control.append(optionEl);
    }
  } else if (field.type === "radio" || field.type === "multiselect") {
    control = createAgentUiChoiceField(form, field, readonly);
  } else {
    control = document.createElement("input");
    control.type = field.type === "number" ? "number" : field.type === "datetime" ? "datetime-local" : field.type === "file_path" ? "text" : field.type;
    control.value = value === undefined || value === null ? "" : String(value);
  }

  if (control instanceof HTMLInputElement || control instanceof HTMLTextAreaElement || control instanceof HTMLSelectElement) {
    control.id = inputId;
    control.className = "agent-ui-form-control";
    control.disabled = readonly;
    control.required = field.required === true;
    if (field.placeholder) {
      control.placeholder = field.placeholder;
    }
    if (typeof field.min === "number") {
      control.min = String(field.min);
    }
    if (typeof field.max === "number") {
      control.max = String(field.max);
    }
    if (typeof field.min_length === "number") {
      control.minLength = field.min_length;
    }
    if (typeof field.max_length === "number") {
      control.maxLength = field.max_length;
    }
    if (field.pattern) {
      control.pattern = field.pattern;
    }
    control.dataset.agentUiFormField = field.name;
    control.setAttribute("aria-describedby", [field.help ? helpId : "", errorText ? errorId : ""].filter(Boolean).join(" "));
    control.setAttribute("aria-invalid", errorText ? "true" : "false");
  } else {
    control.id = inputId;
    control.setAttribute("role", field.type === "radio" ? "radiogroup" : "group");
    control.setAttribute("aria-describedby", [field.help ? helpId : "", errorText ? errorId : ""].filter(Boolean).join(" "));
  }

  wrapper.append(label, control);
  if (field.help) {
    wrapper.append(createAgentUiFieldHelp(field.help, helpId));
  }
  if (errorText) {
    wrapper.append(createAgentUiFieldHelp(errorText, errorId, "agent-ui-form-error"));
  }
  return wrapper;
}

function createAgentUiFormNode(form) {
  const readonly = isFormReadonly(form);
  const card = document.createElement("section");
  card.className = `agent-ui-form-card agent-ui-form-${form.status || AGENT_UI_FORM_STATUSES.pending}`;
  card.dataset.agentUiFormId = form.form_id;

  const header = document.createElement("div");
  header.className = "agent-ui-form-header";
  const title = document.createElement("div");
  title.className = "agent-ui-form-title";
  title.textContent = form.title || "Form request";
  const status = document.createElement("span");
  status.className = "agent-ui-form-status";
  status.textContent = formStatusLabel(form.status);
  header.append(title, status);
  card.append(header);

  if (form.description) {
    const description = document.createElement("p");
    description.className = "agent-ui-form-description";
    description.textContent = form.description;
    card.append(description);
  }
  if (form.errors?._form) {
    const error = document.createElement("div");
    error.className = "agent-ui-form-error agent-ui-form-error-banner";
    error.textContent = form.errors._form;
    card.append(error);
  }

  const formEl = document.createElement("form");
  formEl.className = "agent-ui-form";
  formEl.noValidate = true;
  for (const field of form.fields || []) {
    formEl.append(createAgentUiFormField(form, field, readonly));
  }
  if (!readonly) {
    const actions = document.createElement("div");
    actions.className = "agent-ui-form-actions";
    const submit = document.createElement("button");
    submit.className = "button button-primary button-small agent-ui-form-submit";
    submit.type = "submit";
    submit.textContent = form.submit_label || "Submit";
    const cancel = document.createElement("button");
    cancel.className = "button button-ghost button-small agent-ui-form-cancel";
    cancel.type = "button";
    cancel.textContent = form.cancel_label || "Cancel";
    cancel.addEventListener("click", async () => {
      submit.disabled = true;
      cancel.disabled = true;
      await cancelAgentUiForm(form).finally(() => {
        submit.disabled = false;
        cancel.disabled = false;
      });
    });
    actions.append(submit, cancel);
    formEl.append(actions);
    formEl.addEventListener("submit", async (event) => {
      event.preventDefault();
      submit.disabled = true;
      cancel.disabled = true;
      await submitAgentUiForm(form, formEl).finally(() => {
        submit.disabled = false;
        cancel.disabled = false;
      });
    });
  }
  card.append(formEl);
  return card;
}

function createMemoryReferencesNode(references) {
  const details = document.createElement("details");
  details.className = "memory-references";
  details.open = references.some((reference) => memoryReferenceKey(reference) === state.selectedMemoryReferenceKey);

  const summary = document.createElement("summary");
  summary.className = "memory-references-summary";

  const count = references.length;
  const title = document.createElement("span");
  title.className = "memory-references-title";
  title.textContent = formatMessage("memory.referencesCount", { count });

  const hint = document.createElement("span");
  hint.className = "memory-references-hint";
  hint.textContent = t("memory.openSourceHint");

  summary.append(title, hint);
  details.append(summary);

  const list = document.createElement("div");
  list.className = "memory-references-list";

  references.forEach((reference) => {
    const item = document.createElement("article");
    item.className = "memory-reference-item";
    item.tabIndex = 0;
    item.setAttribute("role", "button");
    item.setAttribute("aria-label", formatMessage("memory.inspectReference", {
      file: memoryReferenceFile(reference),
      location: memoryReferenceLocationLabel(reference),
    }));
    if (memoryReferenceKey(reference) === state.selectedMemoryReferenceKey) {
      item.classList.add("selected");
    }

    const header = document.createElement("div");
    header.className = "memory-reference-header";

    const file = document.createElement("span");
    file.className = "memory-reference-file";
    file.textContent = memoryReferenceFile(reference);

    const location = document.createElement("span");
    location.className = "memory-reference-location";
    location.textContent = memoryReferenceLocationLabel(reference);

    header.append(file, location);

    const content = document.createElement("div");
    content.className = "memory-reference-content";
    content.textContent = reference.content || "";

    const meta = document.createElement("div");
    meta.className = "memory-reference-meta";
    const labelParts = [
      reference.scope,
      reference.type,
      reference.note_id,
    ].filter(Boolean);
    meta.textContent = labelParts.join(" | ");

    item.append(header, content);
    if (labelParts.length) {
      item.append(meta);
    }
    item.addEventListener("click", () => openMemoryReferenceInspector(reference));
    item.addEventListener("keydown", (event) => {
      if (event.key !== "Enter" && event.key !== " ") return;
      event.preventDefault();
      openMemoryReferenceInspector(reference);
    });
    list.append(item);
  });

  details.append(list);
  return details;
}

function createRecentContextReferencesNode(references) {
  const details = document.createElement("details");
  details.className = "memory-references recent-context-references";

  const summary = document.createElement("summary");
  summary.className = "memory-references-summary";

  const count = references.length;
  const title = document.createElement("span");
  title.className = "memory-references-title";
  title.textContent = `${count} recent conversation reference${count === 1 ? "" : "s"}`;

  const hint = document.createElement("span");
  hint.className = "memory-references-hint";
  hint.textContent = t("knowledge.viewSources");

  summary.append(title, hint);
  details.append(summary);

  const list = document.createElement("div");
  list.className = "memory-references-list";

  references.forEach((reference) => {
    const item = document.createElement("article");
    item.className = "memory-reference-item";

    const content = document.createElement("div");
    content.className = "memory-reference-content";
    content.textContent = reference.excerpt || reference.content || "";

    const meta = document.createElement("div");
    meta.className = "memory-reference-meta";
    const file = reference.file || "";
    const line = reference.line ? `:${reference.line}` : "";
    const labelParts = [
      reference.timestamp,
      reference.session_key,
      reference.role,
      reference.turn_id,
      reference.evidence_id,
      file ? `${file}${line}` : "",
    ].filter(Boolean);
    meta.textContent = labelParts.join(" · ");

    item.append(content, meta);
    list.append(item);
  });

  details.append(list);
  return details;
}

function updateStreamMessageDOM(messageId) {
  const streamState = state.streamBuffers.get(messageId);
  if (!streamState) return;

  const existingNode = elements.messageList.querySelector(`[data-message-id="${messageId}"]`);
  if (existingNode) {
    const contentEl = existingNode.querySelector(".message-content");
    updateMessageContent(contentEl, streamState.entry);
    scrollMessagesToBottom(false);
  }
}

function renderSessions() {
  elements.sessionList.textContent = "";
  elements.sessionCount.textContent = String(state.sessionItems.length);

  if (state.sessionItems.length === 0) {
    const empty = document.createElement("div");
    empty.className = "empty-state";
    empty.textContent = t("msg.noSessions");
    elements.sessionList.append(empty);
    return;
  }

  for (const item of state.sessionItems) {
    const wrapper = document.createElement("div");
    wrapper.className = "session-item-wrapper";

    const button = document.createElement("button");
    button.type = "button";
    button.className = "session-item";
    if (item.key === state.activeSessionKey) {
      button.classList.add("active");
      wrapper.classList.add("expanded");
    }
    const isResponding = state.respondingSessionKeys.has(item.key);
    if (isResponding) {
      button.classList.add("responding");
    }
    button.dataset.chatId = item.chat_id;

    const key = document.createElement("span");
    key.className = "session-key";
    key.textContent = sessionTitleForKey(item.key);
    key.title = item.chat_id;

    const responseSpinner = document.createElement("span");
    responseSpinner.className = "session-response-spinner";
    responseSpinner.setAttribute("aria-hidden", "true");
    if (isResponding) {
      responseSpinner.title = t("status.loading");
    }

    const deleteBtn = document.createElement("button");
    deleteBtn.type = "button";
    deleteBtn.className = "button button-ghost button-small session-delete";
    deleteBtn.dataset.chatId = item.chat_id;
    deleteBtn.dataset.sessionKey = item.key;
    deleteBtn.title = t("ui.deleteSession");
    deleteBtn.setAttribute("aria-label", t("ui.deleteSession"));
    deleteBtn.textContent = "×";

    button.append(key, responseSpinner, deleteBtn);
    wrapper.append(button);
    elements.sessionList.append(wrapper);
  }
}

function renderEditableFiles() {
  elements.fileSelect.textContent = "";
  for (const item of state.editableFiles) {
    const option = document.createElement("option");
    option.value = item.path;
    option.textContent = item.path;
    if (item.path === state.activeFilePath) {
      option.selected = true;
    }
    elements.fileSelect.append(option);
  }
}

function sessionFileIcon(fileType = "") {
  const value = fileType.toLowerCase();
  if (value === "md" || value === "markdown") return "MD";
  if (value === "pdf") return "PDF";
  return "TXT";
}

function renderSessionFiles() {
  const strip = elements.sessionFilesStrip;
  if (!strip) {
    return;
  }
  const items = state.sessionFiles.get(state.activeSessionKey) || [];
  strip.textContent = "";
  strip.hidden = items.length === 0;
  if (!items.length) {
    return;
  }

  const title = document.createElement("span");
  title.className = "session-files-label";
  title.textContent = t("sessionFiles.contextLabel");
  strip.append(title);

  for (const item of items) {
    const chip = document.createElement("span");
    chip.className = "session-file-chip";

    const icon = document.createElement("span");
    icon.className = "session-file-icon";
    icon.textContent = sessionFileIcon(item.file_type);

    const name = document.createElement("span");
    name.className = "session-file-name";
    name.textContent = item.name || t("sessionFiles.unnamed");
    name.title = item.name || "";

    const meta = document.createElement("span");
    meta.className = "session-file-meta";
    meta.textContent = `${item.chunk_count || 0} ${t("knowledge.chunks")}`;

    chip.append(icon, name, meta);
    strip.append(chip);
  }
}

async function loadSessionFiles(sessionKey) {
  if (!sessionKey) {
    return;
  }
  const response = await fetch(`${state.sessionsPath}/${encodeURIComponent(sessionKey)}/temporary-files`, {
    headers: { Authorization: `Bearer ${state.token}` },
  });
  if (!response.ok) {
    state.sessionFiles.set(sessionKey, []);
    renderSessionFiles();
    return;
  }
  const payload = await response.json();
  state.sessionFiles.set(sessionKey, payload.items || []);
  renderSessionFiles();
}

function activateChat(chatId) {
  state.activeChatId = chatId;
  state.activeSessionKey = sessionKeyForChat(chatId);
  setSidebarDropdown("sessions");
  updateActiveChatTitle();
  renderSessions();
  renderMessages();
  renderSessionFiles();
  state.pendingApprovals = [];
  renderApprovalPanel();
  loadApprovals(state.activeSessionKey).catch(console.error);
}

async function bootstrap() {
  setStatus(t("status.connecting"), "idle");
  await fetchBootstrapToken();
}

async function loadSessions() {
  const response = await fetch(state.sessionsPath, {
    headers: { Authorization: `Bearer ${state.token}` },
  });
  if (!response.ok) {
    throw new Error(`load sessions failed: ${response.status}`);
  }

  const payload = await response.json();
  state.sessionItems = payload.items || [];
  renderSessions();

  if (!state.activeChatId && state.sessionItems.length > 0) {
    await attachSession(state.sessionItems[0].chat_id);
  }
}

async function loadMessages(sessionKey) {
  const response = await fetch(`${state.sessionsPath}/${encodeURIComponent(sessionKey)}/messages`, {
    headers: { Authorization: `Bearer ${state.token}` },
  });
  if (!response.ok) {
    throw new Error(`load messages failed: ${response.status}`);
  }
  const payload = await response.json();
  const messages = payload.messages || [];
  restoreAgentUiFormsFromMessages(messages);
  state.messages.set(sessionKey, messages);
  updateActiveChatTitle();
  renderSessions();
  renderMessages();
  await loadSessionFiles(sessionKey);
  await loadApprovals(sessionKey);
}

async function loadApprovals(sessionKey = state.activeSessionKey) {
  if (!sessionKey || !state.token || state.approvalsLoading) {
    return;
  }
  state.approvalsLoading = true;
  try {
    const response = await fetch(`/api/approvals?session_key=${encodeURIComponent(sessionKey)}`, {
      headers: { Authorization: `Bearer ${state.token}` },
    });
    if (sessionKey !== state.activeSessionKey) {
      return;
    }
    if (!response.ok) {
      state.pendingApprovals = [];
      renderApprovalPanel();
      return;
    }
    const payload = await response.json();
    state.pendingApprovals = payload.approvals || [];
    renderApprovalPanel();
  } catch (error) {
    console.warn("load approvals failed", error);
  } finally {
    state.approvalsLoading = false;
  }
}

async function handleApprovalAction(approvalId, action, scope = "once") {
  if (!approvalId || !state.activeSessionKey) {
    return;
  }
  const endpoint = action === "deny" ? "deny" : "approve";
  try {
    const response = await fetch(`/api/approvals/${encodeURIComponent(approvalId)}/${endpoint}`, {
      method: "POST",
      headers: authHeaders(),
      body: JSON.stringify({
        session_key: state.activeSessionKey,
        scope,
        auto_retry: true,
      }),
    });
    if (!response.ok) {
      const payload = await response.json().catch(() => ({}));
      throw new Error(payload.error || `approval ${endpoint} failed: ${response.status}`);
    }
    state.pendingApprovals = state.pendingApprovals.filter((item) => item.id !== approvalId);
    renderApprovalPanel();
  } catch (error) {
    setError(error.message || String(error));
  }
}

function setCoworkError(text = "") {
  if (elements.coworkError) {
    elements.coworkError.textContent = text;
  }
}

function openCoworkModal() {
  if (window.location.pathname !== "/cowork") {
    window.history.pushState({}, "", "/cowork");
  }
  if (window.location.pathname === "/cowork") {
    document.body.classList.add("cowork-page-active");
  }
  setSidebarDropdown("cowork");
  elements.coworkModal?.classList.add("active");
  requestAnimationFrame(() => setCoworkWorkflowMode(elements.coworkWorkflowMode?.value || "adaptive_starter"));
  loadCoworkSessions().catch((error) => setCoworkError(error.message || String(error)));
}

function closeCoworkModal() {
  if (window.location.pathname === "/cowork") {
    window.history.pushState({}, "", "/");
  }
  document.body.classList.remove("cowork-page-active");
  setSidebarDropdown("sessions");
  elements.coworkModal?.classList.remove("active");
}

function leaveCoworkPage() {
  closeCoworkModal();
}

function coworkStatusClass(status = "") {
  return `cowork-status cowork-status-${String(status || "idle").toLowerCase()}`;
}

function compactText(text = "", max = 160) {
  const clean = String(text || "").replace(/\s+/g, " ").trim();
  return clean.length > max ? `${clean.slice(0, max - 1)}...` : clean;
}

function coworkEmpty(text) {
  const empty = document.createElement("div");
  empty.className = "cowork-empty";
  empty.textContent = text;
  return empty;
}

function coworkArray(value) {
  return Array.isArray(value) ? value : [];
}

function coworkSafeSession(session) {
  if (!session) return null;
  return {
    ...session,
    agents: coworkArray(session.agents),
    tasks: coworkArray(session.tasks),
    threads: coworkArray(session.threads),
    messages: coworkArray(session.messages),
    mailbox: coworkArray(session.mailbox),
    events: coworkArray(session.events),
    trace: coworkArray(session.trace),
    trace_spans: coworkArray(session.trace_spans),
    agent_steps: coworkArray(session.agent_steps),
    observation_details: session.observation_details || {},
    branch_results: coworkArray(session.branch_results),
    session_final_result: session.session_final_result || {},
    artifact_index: coworkArray(session.artifact_index),
    artifacts: coworkArray(session.artifacts),
    scheduler_decisions: coworkArray(session.scheduler_decisions),
    run_metrics: coworkArray(session.run_metrics),
    evaluation_results: coworkArray(session.evaluation_results),
    completion_decision: session.completion_decision || {},
    budget_state: session.budget_state || session.budget || { limits: {}, usage: {}, remaining: {} },
    graph: session.graph || null,
    blueprint: session.blueprint || null,
    swarm_plan: session.swarm_plan || {},
    swarm_queues: session.swarm_queues || {},
    swarm_organization: session.swarm_organization || {},
    large_swarm_summary: session.large_swarm_summary || {},
  };
}

function coworkPayloadText(payload, max = 1800) {
  if (!payload || (typeof payload === "object" && !Object.keys(payload).length)) return "";
  try {
    return compactText(JSON.stringify(payload, null, 2), max);
  } catch {
    return compactText(String(payload), max);
  }
}

function coworkMatchesConsoleFilter(item, fields = []) {
  const query = String(state.coworkConsoleFilter || "").trim().toLowerCase();
  if (!query) return true;
  const text = fields
    .map((field) => item?.[field])
    .concat([item?.id, item?.title, item?.name, item?.status, item?.kind, item?.summary, item?.description, item?.path_or_url])
    .map((value) => {
      if (value == null) return "";
      if (Array.isArray(value)) return value.join(" ");
      if (typeof value === "object") return JSON.stringify(value);
      return String(value);
    })
    .join(" ")
    .toLowerCase();
  return text.includes(query);
}

function coworkStatusTone(status = "") {
  const value = String(status || "").toLowerCase();
  if (["ok", "done", "complete", "completed", "replied", "success"].includes(value)) return "done";
  if (["failed", "error", "blocked", "expired", "issue"].includes(value)) return "issue";
  if (["running", "active", "in_progress", "delivered", "read"].includes(value)) return "running";
  if (["paused", "pending", "queued", "idle", "skipped"].includes(value)) return "pending";
  return value || "event";
}

function coworkTraceStage(trace) {
  const stage = String(trace?.stage || trace?.kind || "").toLowerCase();
  const type = String(trace?.type || "").toLowerCase();
  if (stage) return stage;
  if (type.includes("scheduler")) return "scheduler";
  if (type.includes("agent")) return "agent";
  if (type.includes("task")) return "task";
  if (type.includes("mailbox") || type.includes("thread")) return "mailbox";
  if (type.includes("review")) return "review";
  if (type.includes("workflow")) return "workflow";
  return "event";
}

function coworkTraceAction(trace) {
  return String(trace?.action || trace?.name || trace?.title || trace?.type || "Trace event").replaceAll("_", " ");
}

function coworkTraceTarget(trace) {
  if (trace?.next_node_id) return String(trace.next_node_id).replace(/^agent:/, "").replace(/^task:/, "task ");
  if (trace?.payload?.task_id) return `task ${trace.payload.task_id}`;
  if (trace?.payload?.assigned_agent_id) return trace.payload.assigned_agent_id;
  if (trace?.actor_name) return trace.actor_name;
  if (trace?.actor_id) return trace.actor_id;
  return "";
}

function coworkTraceActor(trace) {
  const stage = coworkTraceStage(trace);
  const actorName = String(trace?.actor_name || trace?.actor_id || "").trim();
  if (actorName) {
    return {
      key: `${stage}:${actorName}`,
      label: actorName,
      subtitle: stage === "agent"
        ? t("cowork.agentTraceCard")
        : formatMessage("cowork.genericTraceCard", { stage: stage.replaceAll("_", " ") }),
      kind: stage,
    };
  }
  if (stage === "scheduler") return { key: "scheduler", label: t("cowork.scheduler"), subtitle: t("cowork.schedulerTraceCard"), kind: "scheduler" };
  if (stage === "workflow") return { key: "workflow", label: t("cowork.workflow"), subtitle: t("cowork.workflowTraceCard"), kind: "workflow" };
  if (stage === "task") return { key: "task-runtime", label: t("cowork.taskRuntime"), subtitle: t("cowork.taskTraceCard"), kind: "task" };
  if (stage === "mailbox") return { key: "mailbox-runtime", label: t("cowork.mailbox"), subtitle: t("cowork.mailboxTraceCard"), kind: "mailbox" };
  return { key: "system-runtime", label: t("cowork.systemEntry"), subtitle: t("cowork.systemTraceCard"), kind: "system" };
}

function coworkTraceItems(session) {
  const safe = coworkSafeSession(session);
  if (!safe) return [];
  if (safe.trace.length) return safe.trace;
  const spanItems = safe.trace_spans.map((span) => ({
    id: span.id,
    type: `span.${span.kind || "trace"}`,
    stage: span.kind || "trace",
    action: span.name || span.kind || t("cowork.traceSpan"),
    detail: span.summary || span.error || span.output_ref || span.input_ref || "",
    actor_id: span.actor_id || "",
    actor_name: span.actor_id || "",
    at: span.ended_at || span.started_at || "",
    status: span.status || "active",
    payload: span.data || {},
    source: "trace_span",
  }));
  const eventItems = safe.events.map((event) => ({
    id: event.id,
    type: event.type || "event",
    stage: String(event.type || "").split(".")[0] || "event",
    action: coworkTimelineStatusLabel(event.type),
    detail: event.message || "",
    actor_id: event.actor_id || event.data?.agent_id || "",
    actor_name: event.actor_id || event.data?.agent_id || "",
    at: event.created_at || "",
    status: event.data?.status || String(event.type || "").split(".").pop() || "event",
    payload: event.data || {},
    source: "event",
  }));
  return [...eventItems, ...spanItems]
    .sort((a, b) => String(a.at || "").localeCompare(String(b.at || "")))
    .slice(-100);
}

function coworkConversationStatusKey(trace) {
  if (coworkTraceStage(trace) !== "task") return "";
  const payload = trace?.payload || trace?.data || {};
  const taskId = String(payload.task_id || payload.blocking_task_id || "").trim();
  if (!taskId) return "";
  const action = coworkTraceAction(trace).toLowerCase().replace(/\s+/g, " ").trim();
  const status = coworkStatusTone(trace?.status || trace?.type);
  return `task:${taskId}:${action}:${status}`;
}

function coworkTraceViewModel(session) {
  const filter = state.coworkTraceFilter || "all";
  const mode = state.coworkTraceMode || "key";
  const safe = coworkSafeSession(session);
  const stepsById = new Map((safe?.agent_steps || []).map((step) => [step.id, step]));
  const items = coworkTraceItems(session)
    .filter((trace) => {
      const stage = coworkTraceStage(trace);
      const status = String(trace.status || "").toLowerCase();
      const stageMatch = filter === "all" || stage === filter || (filter === "error" && ["failed", "error", "blocked"].includes(status));
      return stageMatch && coworkMatchesConsoleFilter(trace, ["action", "detail", "message", "actor_id", "actor_name", "type"]);
    })
    .map((trace, index) => {
      const id = trace.id || `${trace.type || "trace"}:${trace.at || index}:${index}`;
      const agentStep = stepsById.get(id) || stepsById.get(trace.payload?.step_id || "");
      return {
        id,
        action: coworkTraceAction(trace),
        detail: String(trace.detail || trace.summary || trace.message || "").trim(),
        target: coworkTraceTarget(trace),
        stage: coworkTraceStage(trace),
        status: coworkStatusTone(trace.status || trace.type),
        at: trace.at || trace.created_at || trace.started_at || "",
        payloadText: coworkPayloadText(trace.payload || trace.data || {}),
        observations: coworkTraceObservations(agentStep),
        stepDetailRef: agentStep?.detail_ref || agentStep?.summary?.detail_ref || "",
        raw: trace,
      };
    });
  const groups = [];
  for (const item of items) {
    const actor = coworkTraceActor(item.raw);
    const last = groups[groups.length - 1];
    if (last && last.key === actor.key && mode === "key") {
      last.steps.push(item);
      continue;
    }
    groups.push({ ...actor, steps: [item] });
  }
  return { mode, filter, items, groups };
}

function coworkTraceObservations(step) {
  if (!step) return [];
  const tools = coworkArray(step.tool_observations).map((observation) => ({
    ...observation,
    observation_kind: "tool",
  }));
  const browser = coworkArray(step.browser_observations).map((observation) => ({
    ...observation,
    observation_kind: "browser",
  }));
  return [...tools, ...browser];
}

function coworkObservationTitle(observation) {
  if (observation.observation_kind === "tool") {
    return observation.tool_name || observation.purpose || observation.id || t("cowork.toolObservation");
  }
  return observation.title || observation.resource_ref || observation.purpose || observation.id || t("cowork.browserObservation");
}

function coworkObservationSummary(observation) {
  const parts = [
    observation.purpose,
    observation.result_summary,
    observation.resource_ref,
    observation.duration_ms !== null && observation.duration_ms !== undefined ? `${observation.duration_ms}ms` : "",
    observation.redacted ? t("cowork.redacted") : "",
    observation.sensitive ? t("cowork.sensitive") : "",
  ].filter(Boolean);
  return compactText(parts.join(" - "), 220);
}

function renderCoworkObservationContent(detail, target) {
  target.textContent = "";
  const stateText = detail?.state || "unavailable";
  const summary = detail?.summary || detail?.unavailable_reason || "";
  const meta = document.createElement("small");
  meta.textContent = [
    stateText,
    detail?.content_type || "",
    detail?.redacted ? t("cowork.redacted") : "",
    detail?.sensitivity || "",
  ].filter(Boolean).join(" - ");
  target.append(meta);
  if (summary) {
    const paragraph = document.createElement("p");
    paragraph.textContent = compactText(summary, 320);
    target.append(paragraph);
  }
  const content = detail?.content || "";
  if (content) {
    const pre = document.createElement("pre");
    pre.className = "cowork-trace-json-block";
    pre.textContent = content;
    target.append(pre);
  } else if (detail?.unavailable_reason) {
    const paragraph = document.createElement("p");
    paragraph.textContent = detail.unavailable_reason;
    target.append(paragraph);
  }
}

async function loadCoworkObservationDetail(detailRef, target, button) {
  if (!state.activeCoworkSessionId || !detailRef) return;
  if (button) {
    button.disabled = true;
    button.textContent = t("status.loading");
  }
  try {
    const response = await fetch(
      `${state.coworkApiPath}/sessions/${encodeURIComponent(state.activeCoworkSessionId)}/observations/${encodeURIComponent(detailRef)}`,
      { headers: authHeaders() },
    );
    const payload = await response.json().catch(() => ({}));
    if (!response.ok && response.status !== 403) {
      throw new Error(payload.error || t("cowork.loadObservationFailed"));
    }
    renderCoworkObservationContent(payload.detail || {}, target);
    if (button) button.textContent = t("status.loaded");
  } catch (error) {
    target.textContent = error.message || String(error);
    if (button) {
      button.disabled = false;
      button.textContent = t("cowork.retryDetail");
    }
  }
}

function renderCoworkTraceObservations(observations) {
  if (!observations.length) return null;
  const panel = document.createElement("div");
  panel.className = "cowork-observation-list";
  const heading = document.createElement("strong");
  heading.textContent = formatMessage("cowork.observationsCount", { count: observations.length });
  panel.append(heading);
  for (const observation of observations) {
    const details = document.createElement("details");
    details.className = "cowork-observation-item";
    details.addEventListener("click", (event) => event.stopPropagation());
    const summary = document.createElement("summary");
    const title = document.createElement("span");
    const status = document.createElement("span");
    title.textContent = coworkObservationTitle(observation);
    status.className = coworkStatusClass(observation.status || observation.observation_kind || "active");
    status.textContent = observation.observation_kind === "tool" ? t("runChain.kind.tool") : t("browser.title");
    summary.append(title, status);
    const body = document.createElement("div");
    body.className = "cowork-observation-body";
    const text = document.createElement("p");
    text.textContent = coworkObservationSummary(observation) || t("cowork.noSummary");
    body.append(text);
    if (observation.observation_kind === "tool" && observation.parameter_summary && Object.keys(observation.parameter_summary).length) {
      const pre = document.createElement("pre");
      pre.className = "cowork-trace-json-block";
      pre.textContent = coworkPayloadText(observation.parameter_summary, 700);
      body.append(pre);
    }
    if (observation.detail_ref) {
      const actions = document.createElement("div");
      actions.className = "cowork-card-actions";
      const button = document.createElement("button");
      button.type = "button";
      button.className = "button button-small";
      button.textContent = t("cowork.loadDetail");
      const target = document.createElement("div");
      target.className = "cowork-observation-detail";
      button.addEventListener("click", (event) => {
        event.preventDefault();
        event.stopPropagation();
        loadCoworkObservationDetail(observation.detail_ref, target, button);
      });
      actions.append(button);
      body.append(actions, target);
    }
    details.append(summary, body);
    panel.append(details);
  }
  return panel;
}

function coworkConversationViewModel(session) {
  const safe = coworkSafeSession(session);
  if (!safe) return { items: [], finalDraft: "", blockers: [] };
  const agentsById = new Map(safe.agents.map((agent) => [agent.id, agent]));
  const threadsById = new Map(safe.threads.map((thread) => [thread.id, thread]));
  const messages = safe.messages.map((message, index) => {
    const sender = agentsById.get(message.sender_id);
    const thread = threadsById.get(message.thread_id);
    return {
      id: message.id || message.message_id || `message:${index}`,
      kind: "message",
      role: message.sender_id === "user" ? "user" : "agent",
      avatar: (sender?.name || message.sender_id || "?").slice(0, 1).toUpperCase(),
      title: sender ? `${sender.name} / ${sender.role || "Agent"}` : message.sender_id || "sender",
      meta: [thread?.topic || "Discussion", message.created_at || ""].filter(Boolean).join(" - "),
      body: message.content || "",
      at: message.created_at || "",
    };
  });
  const statusEvents = coworkTraceItems(safe)
    .filter((trace) => ["scheduler", "workflow", "task"].includes(coworkTraceStage(trace)))
    .filter((trace, index, traces) => {
      const key = coworkConversationStatusKey(trace);
      if (!key) return true;
      return traces.findIndex((candidate) => coworkConversationStatusKey(candidate) === key) === index;
    })
    .slice(-18)
    .map((trace, index) => ({
      id: trace.id || `status:${index}`,
      kind: "status",
      status: coworkStatusTone(trace.status || trace.type),
      title: coworkTraceAction(trace),
      body: String(trace.detail || "").trim(),
      meta: [coworkTraceStage(trace), trace.at || ""].filter(Boolean).join(" - "),
      at: trace.at || "",
    }));
  const blockers = [
    ...(safe.completion_decision.blocked || []),
    ...(safe.completion_decision.review_blockers || []),
    ...(safe.completion_decision.fanout_blockers || []),
    ...(safe.completion_decision.disagreements || []),
  ];
  const items = [...messages, ...statusEvents]
    .sort((a, b) => String(a.at || "").localeCompare(String(b.at || "")))
    .slice(-90);
  return {
    items,
    finalDraft: String(safe.final_draft || "").trim(),
    completionDecision: safe.completion_decision,
    blockers,
  };
}

function coworkGraphViewModel(session) {
  const safe = coworkSafeSession(session);
  const graph = coworkGraphNodes(safe);
  const stats = safe?.graph?.stats || {};
  const activeNodeId = state.activeCoworkGraphNode || graph.nodes[0]?.id || "session";
  const selectedNode = graph.nodes.find((node) => node.id === activeNodeId) || graph.nodes[0] || null;
  return {
    nodes: graph.nodes,
    edges: graph.edges,
    activeNodeId,
    selectedNode,
    stats,
    hiddenCounts: {
      nodes: stats.hidden_nodes || stats.hidden || 0,
      edges: stats.hidden_edges || 0,
      agents: Math.max(0, (stats.total_agents || safe?.agents?.length || 0) - (stats.agents || safe?.agents?.length || 0)),
      tasks: Math.max(0, (stats.total_tasks || safe?.tasks?.length || 0) - (stats.tasks || safe?.tasks?.length || 0)),
    },
  };
}

function coworkAgentMailboxState(session, agentId) {
  const mailbox = session?.mailbox || [];
  const pending = mailbox.filter((record) => (
    (record.recipient_ids || []).includes(agentId)
    && record.requires_reply
    && ["delivered", "read"].includes(record.status)
  ));
  const lastMessage = [...(session?.messages || [])]
    .filter((message) => message.sender_id === agentId || (message.recipient_ids || []).includes(agentId))
    .sort((a, b) => String(a.created_at || "").localeCompare(String(b.created_at || "")))
    .pop();
  return {
    pendingCount: pending.length,
    waitingOn: pending.map((record) => record.sender_id).filter(Boolean).join(", "),
    lastMessage: lastMessage?.content || "",
  };
}

function coworkTimelineStatus(event) {
  const type = String(event?.type || "");
  const message = String(event?.message || "");
  if (type.startsWith("thread.") || type.startsWith("mailbox.")) return null;
  if (type === "scheduler.round") {
    const match = message.match(/round\s+(\d+).*with\s+(.+)$/i);
    return match ? `Round ${match[1]}: ${match[2]}` : "Scheduler round";
  }
  if (type === "scheduler.lead_synthesis") return "Coordinator synthesis";
  if (type === "scheduler.idle") return "Idle";
  if (type === "scheduler.budget_exhausted") return "Run limit reached";
  if (type === "scheduler.agent_budget_exhausted") return "Agent run limit reached";
  if (type === "workflow.handoff") return "Peer handoff";
  if (type === "workflow.review_requested") return "Review requested";
  if (type === "workflow.handoff_rewritten") return "Handoff kept with current agent";
  if (type === "workflow.focus_updated") return "Focus updated";
  if (type === "workflow.step_complete") return "Workflow step complete";
  if (type === "workflow.blocked") return "Workflow blocked";
  if (type === "session.created") return "Session created";
  if (type === "session.reopened") return "Session reopened";
  if (type === "session.paused") return "Paused";
  if (type === "session.resumed") return "Resumed";
  if (type === "agent.started") return message.replace(/\s+started a cowork round\.?$/i, " started");
  if (type === "agent.ran") return message.replace(/\s+completed a cowork round\.?$/i, " finished");
  if (type === "agent.status") return message.replace(/\s+set status to\s+/i, " -> ");
  if (type === "agent.failed") return message;
  if (type === "task.completed") return message.replace(/^Task\s+'(.+)'\s+completed\.?$/i, "Task completed: $1");
  if (type === "task.failed") return message.replace(/^Task\s+'(.+)'\s+failed\.?$/i, "Task failed: $1");
  if (type === "task.skipped") return message.replace(/^Task\s+'(.+)'\s+skipped\.?$/i, "Task skipped: $1");
  if (type === "task.created") return "Task added";
  if (type === "task.assigned") return "Task assigned";
  if (type === "task.claimed") return "Task claimed";
  return null;
}

function coworkTimelineStatusLabel(type = "") {
  const head = String(type || "status").split(".")[0] || "status";
  const tail = String(type || "status").split(".").pop() || "status";
  return `${head}: ${tail}`.replaceAll("_", " ");
}

function coworkGraphStatusColor(status = "") {
  const value = String(status || "idle").toLowerCase();
  if (["completed", "replied"].includes(value)) return "var(--cowork-green)";
  if (["failed", "blocked", "expired"].includes(value)) return "var(--cowork-red)";
  if (["pending", "paused", "idle", "queued"].includes(value)) return "var(--cowork-amber)";
  return "var(--cowork-cyan)";
}

function coworkGraphNodeLabel(node) {
  return `${node.kind}: ${node.title}`;
}

function coworkGraphCreateSvg(tag, attributes = {}) {
  const element = document.createElementNS("http://www.w3.org/2000/svg", tag);
  for (const [key, value] of Object.entries(attributes)) {
    element.setAttribute(key, String(value));
  }
  return element;
}

function coworkMailboxRoute(record, fallbackRecipient = "team") {
  return `${record?.sender_id || "sender"} -> ${(record?.recipient_ids || []).join(", ") || fallbackRecipient}`;
}

function renderCoworkFieldOverlay() {
  if (!elements.coworkFieldOverlay) return;
  elements.coworkFieldOverlay.textContent = "";
  elements.coworkFieldOverlay.hidden = true;
}

function coworkApplyGraphNodePositions(sessionId, nodes) {
  const sessionPositions = state.coworkGraphNodePositions?.get(sessionId || "") || new Map();
  for (const node of nodes) {
    const position = sessionPositions.get(node.id);
    if (!position) continue;
    node.x = position.x;
    node.y = position.y;
  }
  return nodes;
}

function coworkRememberGraphNodePosition(sessionId, nodeId, x, y) {
  if (!state.coworkGraphNodePositions) {
    state.coworkGraphNodePositions = new Map();
  }
  const key = sessionId || "";
  const sessionPositions = state.coworkGraphNodePositions.get(key) || new Map();
  sessionPositions.set(nodeId, {
    x: Math.max(80, Math.min(1120, x)),
    y: Math.max(58, Math.min(582, y)),
  });
  state.coworkGraphNodePositions.set(key, sessionPositions);
}

function coworkGraphAgentId(nodeId = "") {
  const value = String(nodeId || "");
  return value.startsWith("agent:") ? value.slice("agent:".length) : "";
}

function selectCoworkGraphNode(session, nodeId, { render = true } = {}) {
  const { nodes } = coworkGraphNodes(session);
  const node = nodes.find((item) => item.id === nodeId);
  if (!node) return;
  state.activeCoworkGraphNode = node.id;
  if (node.kind === "agent") {
    selectCoworkEntity("agent", coworkGraphAgentId(node.id) || node.entity_id || node.id);
  } else if (node.kind === "task") {
    selectCoworkEntity("task", String(node.id || "").replace(/^task:/, ""));
  } else {
    selectCoworkEntity("node", node.id);
  }
  if (render) {
    renderCoworkGraph(session);
  }
}

function ensureCoworkAgentFieldPopover() {
  const bindPopoverEvents = (popover) => {
    if (!popover || popover.dataset.coworkPopoverBound === "true") return popover;
    popover.dataset.coworkPopoverBound = "true";
    popover.addEventListener("pointerdown", (event) => event.stopPropagation());
    popover.addEventListener("click", (event) => event.stopPropagation());
    popover.addEventListener("wheel", (event) => event.stopPropagation(), { passive: true });
    return popover;
  };
  if (elements.coworkAgentFieldPopover) {
    return bindPopoverEvents(elements.coworkAgentFieldPopover);
  }
  if (!elements.coworkGraphStage) {
    return null;
  }
  const existing = elements.coworkGraphStage.querySelector("#cowork-agent-field-popover");
  if (existing) {
    elements.coworkAgentFieldPopover = existing;
    return bindPopoverEvents(existing);
  }
  const popover = document.createElement("div");
  popover.id = "cowork-agent-field-popover";
  popover.className = "cowork-agent-field-popover";
  popover.hidden = true;
  elements.coworkGraphStage.append(popover);
  elements.coworkAgentFieldPopover = popover;
  return bindPopoverEvents(popover);
}

function coworkAgentFieldItems(session, agentId) {
  if (!session || !agentId) {
    return { inbox: [], sent: [], messages: [], pending: [] };
  }
  const mailbox = [...(session.mailbox || [])]
    .filter((record) => record.sender_id === agentId || (record.recipient_ids || []).includes(agentId))
    .sort((a, b) => String(b.updated_at || b.created_at || "").localeCompare(String(a.updated_at || a.created_at || "")));
  const messages = [...(session.messages || [])]
    .filter((message) => message.sender_id === agentId || (message.recipient_ids || []).includes(agentId))
    .sort((a, b) => String(b.created_at || "").localeCompare(String(a.created_at || "")));
  return {
    inbox: mailbox.filter((record) => (record.recipient_ids || []).includes(agentId)).slice(0, 5),
    sent: mailbox.filter((record) => record.sender_id === agentId).slice(0, 4),
    pending: mailbox.filter((record) => (record.recipient_ids || []).includes(agentId) && record.requires_reply && ["delivered", "read"].includes(String(record.status || "").toLowerCase())).slice(0, 4),
    messages: messages.slice(0, 5),
  };
}

function renderCoworkAgentFieldPopover(session, selectedNode) {
  const popover = ensureCoworkAgentFieldPopover();
  if (!popover) return;
  const agentId = coworkGraphAgentId(selectedNode?.id);
  const agent = (session?.agents || []).find((item) => item.id === agentId);
  if (!session || !selectedNode || selectedNode.kind !== "agent" || !agent) {
    popover.hidden = true;
    popover.textContent = "";
    return;
  }

  const field = coworkAgentFieldItems(session, agentId);
  const mailboxState = coworkAgentMailboxState(session, agentId);
  const currentFocus = agent.current_task_title || agent.goal || selectedNode.detail || t("cowork.standingBy");
  const section = (title, items, renderItem, emptyText) => `
    <section class="cowork-agent-field-section">
      <h5>${escapeHtml(title)}</h5>
      ${items.length ? items.map(renderItem).join("") : `<p class="cowork-agent-field-empty">${escapeHtml(emptyText)}</p>`}
    </section>
  `;

  popover.hidden = false;
  popover.innerHTML = `
    <div class="cowork-agent-field-head">
      <div>
        <span class="cowork-kicker">${escapeHtml(t("cowork.agentField"))}</span>
        <h4>${escapeHtml(agent.name || agent.id)}</h4>
        <p>${escapeHtml(agent.role || "Agent")}</p>
      </div>
      <span class="${coworkStatusClass(agent.status)}">${escapeHtml(agent.status || "idle")}</span>
    </div>
    <div class="cowork-agent-field-focus">
      <strong>${escapeHtml(t("cowork.currentFocus"))}</strong>
      <p>${escapeHtml(compactText(currentFocus, 180))}</p>
    </div>
    <div class="cowork-agent-field-stats">
      <span>${escapeHtml(formatMessage("cowork.inboxCount", { count: agent.inbox_count || 0 }))}</span>
      <span>${escapeHtml(formatMessage("cowork.pendingCount", { count: field.pending.length }))}</span>
      <span>${escapeHtml(formatMessage("cowork.roundsCount", { count: agent.rounds || 0 }))}</span>
      ${mailboxState.waitingOn ? `<span>${escapeHtml(formatMessage("cowork.waitingOn", { value: mailboxState.waitingOn }))}</span>` : ""}
    </div>
    ${section(t("runChain.needsAttention"), field.pending, (record) => `
      <article class="cowork-agent-field-item urgent">
        <strong class="cowork-agent-field-route">${escapeHtml(record.sender_id || "sender")} <span aria-hidden="true">-></span> ${escapeHtml((record.recipient_ids || []).join(", ") || agentId)}</strong>
        <p>${escapeHtml(compactText(record.content || "", 150))}</p>
        <small>${escapeHtml([record.request_type || "reply", record.status || "delivered", record.updated_at || record.created_at || ""].filter(Boolean).join(" - "))}</small>
      </article>
    `, t("cowork.noPendingReplies"))}
    ${section(t("cowork.inbox"), field.inbox, (record) => `
      <article class="cowork-agent-field-item">
        <strong class="cowork-agent-field-route">${escapeHtml(record.sender_id || "sender")} <span aria-hidden="true">-></span> ${escapeHtml((record.recipient_ids || []).join(", ") || agentId)}</strong>
        <p>${escapeHtml(compactText(record.content || "", 150))}</p>
        <small>${escapeHtml([record.kind || "message", record.status || "queued", record.created_at || ""].filter(Boolean).join(" - "))}</small>
      </article>
    `, t("cowork.noInboxItems"))}
    ${section(t("cowork.sent"), field.sent, (record) => `
      <article class="cowork-agent-field-item">
        <strong class="cowork-agent-field-route">${escapeHtml(record.sender_id || agentId)} <span aria-hidden="true">-></span> ${escapeHtml((record.recipient_ids || []).join(", ") || "team")}</strong>
        <p>${escapeHtml(compactText(record.content || "", 150))}</p>
        <small>${escapeHtml([record.kind || "message", record.status || "queued", record.created_at || ""].filter(Boolean).join(" - "))}</small>
      </article>
    `, t("cowork.noSentMailboxItems"))}
    ${section(t("cowork.recentDialog"), field.messages, (message) => `
      <article class="cowork-agent-field-item">
        <strong>${escapeHtml(message.sender_id || "sender")} -> ${escapeHtml((message.recipient_ids || []).join(", ") || "team")}</strong>
        <p>${escapeHtml(compactText(message.content || "", 150))}</p>
        <small>${escapeHtml(message.created_at || "")}</small>
      </article>
    `, t("cowork.noDialogYet"))}
  `;
}

function coworkGraphNodes(session) {
  if (!session) return { nodes: [], edges: [] };
  if (session.graph && Array.isArray(session.graph.nodes) && Array.isArray(session.graph.edges)) {
    const nodes = session.graph.nodes.map((node) => ({ ...node }));
    const edges = session.graph.edges.map((edge) => ({
        ...edge,
        from: edge.from || edge.source,
        to: edge.to || edge.target,
      }));
    coworkApplyGraphNodePositions(session.id, nodes);
    return coworkGraphWithMailboxOverlay(session, nodes, edges);
  }
  const nodes = [{
    id: "session",
    kind: "session",
    title: session.title || session.id || "Cowork",
    detail: compactText(session.goal || "Shared mission", 180),
    status: session.status || "active",
    x: 600,
    y: 310,
  }];
  const edges = [];
  const agents = session.agents || [];
  const tasks = session.tasks || [];
  const messages = session.messages || [];
  const mailbox = session.mailbox || [];
  const agentIds = new Set(agents.map((agent) => agent.id).filter(Boolean));
  const mailboxByAgent = new Map();
  const mailboxNoticeByAgent = new Map();
  const agentPositions = new Map();
  for (const record of mailbox) {
    for (const recipientId of record.recipient_ids || []) {
      const entry = mailboxByAgent.get(recipientId) || { inbox: 0, pending: 0 };
      entry.inbox += 1;
      if (record.requires_reply || ["queued", "delivered", "read"].includes(String(record.status || "").toLowerCase())) {
        entry.pending += 1;
      }
      mailboxByAgent.set(recipientId, entry);
      mailboxNoticeByAgent.set(recipientId, record);
    }
    if (record.sender_id) {
      mailboxNoticeByAgent.set(record.sender_id, record);
    }
  }
  const radiusX = Math.max(230, Math.min(350, 170 + agents.length * 28));
  const radiusY = Math.max(150, Math.min(230, 120 + agents.length * 16));
  agents.forEach((agent, index) => {
    const angle = -Math.PI / 2 + (Math.PI * 2 * index) / Math.max(agents.length, 1);
    const mailboxState = mailboxByAgent.get(agent.id) || { inbox: agent.inbox_count || 0, pending: 0 };
    const activeTask = agent.current_task_title || agent.goal || "Standing by";
    nodes.push({
      id: `agent:${agent.id}`,
      kind: "agent",
      title: agent.name || agent.id,
      detail: compactText(`${agent.role || "Agent"} - ${activeTask}`, 170),
      status: agent.status || "idle",
      badge: `in ${mailboxState.inbox} / wait ${mailboxState.pending} / r${agent.rounds || 0}`,
      notice: mailboxNoticeByAgent.get(agent.id)
        ? compactText(`${mailboxNoticeByAgent.get(agent.id).sender_id || "sender"} -> ${(mailboxNoticeByAgent.get(agent.id).recipient_ids || []).join(", ") || "team"}: ${mailboxNoticeByAgent.get(agent.id).content || ""}`, 82)
        : "",
      noticeUrgent: mailboxState.pending > 0,
      x: 600 + Math.cos(angle) * radiusX,
      y: 310 + Math.sin(angle) * radiusY,
    });
    agentPositions.set(agent.id, {
      x: 600 + Math.cos(angle) * radiusX,
      y: 310 + Math.sin(angle) * radiusY,
    });
    edges.push({ from: "session", to: `agent:${agent.id}`, kind: "agent" });
  });
  const taskSlots = tasks.slice(0, 12);
  taskSlots.forEach((task, index) => {
    const column = index % 2;
    const row = Math.floor(index / 2);
    const owner = task.assigned_agent_id ? `agent:${task.assigned_agent_id}` : "session";
    nodes.push({
      id: `task:${task.id || index}`,
      kind: "task",
      title: task.title || task.id || "Task",
      detail: compactText(task.result_data?.answer || task.result || task.description || "Waiting for output", 180),
      status: task.status || "pending",
      badge: task.assigned_agent_id ? `owner ${task.assigned_agent_id}` : "unassigned",
      x: column ? 1010 : 190,
      y: 100 + row * 76,
    });
    edges.push({ from: owner, to: `task:${task.id || index}`, kind: "task" });
  });
  const messagesByAgent = new Map();
  for (const message of messages) {
    if (!agentIds.has(message.sender_id) || !String(message.content || "").trim()) continue;
    const bucket = messagesByAgent.get(message.sender_id) || [];
    bucket.push(message);
    messagesByAgent.set(message.sender_id, bucket);
  }
  for (const [agentId, agentMessages] of messagesByAgent.entries()) {
    const position = agentPositions.get(agentId);
    if (!position) continue;
    const recent = agentMessages.slice(-2);
    recent.forEach((message, index) => {
      const messageId = `message:${message.id || message.message_id || `${agentId}:${message.created_at || index}`}`;
      const side = position.y > 330 ? -1 : 1;
      const spread = recent.length === 1 ? 0 : (index === 0 ? -66 : 66);
      nodes.push({
        id: messageId,
        kind: "message",
        title: message.sender_id || "Agent",
        detail: compactText(message.content || "", 180),
        status: "delivered",
        badge: compactText(message.created_at || "message", 26),
        x: Math.max(120, Math.min(1080, position.x + spread)),
        y: Math.max(74, Math.min(560, position.y + side * (92 + index * 10))),
      });
      edges.push({ from: `agent:${agentId}`, to: messageId, kind: "message", pulse: index === recent.length - 1 });
    });
  }
  messages.slice(-8).forEach((message, index) => {
    const sender = message.sender_id ? `agent:${message.sender_id}` : "session";
    const recipient = (message.recipient_ids || []).find(Boolean);
    if (recipient) {
      edges.push({ from: sender, to: `agent:${recipient}`, kind: "message", pulse: index >= 5 });
    }
  });
  const recentMailbox = mailbox.slice(-12);
  recentMailbox.forEach((record, index) => {
    const sender = record.sender_id ? `agent:${record.sender_id}` : "session";
    for (const recipient of record.recipient_ids || []) {
      edges.push({
        from: sender,
        to: `agent:${recipient}`,
        kind: "mailbox",
        pulse: index >= Math.max(0, recentMailbox.length - 4) || Boolean(record.requires_reply),
        label: `${record.sender_id || "sender"} -> ${recipient}: ${compactText(record.content || "", 70)}`,
      });
    }
  });
  coworkApplyGraphNodePositions(session.id, nodes);
  return coworkGraphWithMailboxOverlay(session, nodes, edges);
}

function coworkGraphWithMailboxOverlay(session, nodes, edges) {
  const nodeMap = new Map(nodes.map((node) => [node.id, node]));
  const agents = session?.agents || [];
  const agentNodes = new Map();
  for (const agent of agents) {
    const id = `agent:${agent.id}`;
    const node = nodeMap.get(id) || nodes.find((item) => item.kind === "agent" && (item.agent_id === agent.id || item.title === agent.name || item.title === agent.id));
    if (node) agentNodes.set(agent.id, node);
  }
  const recentMailbox = [...(session?.mailbox || [])]
    .filter((record) => record.sender_id && (record.recipient_ids || []).length && String(record.content || "").trim())
    .sort((a, b) => String(a.updated_at || a.created_at || "").localeCompare(String(b.updated_at || b.created_at || "")))
    .slice(-6);
  if (!recentMailbox.length) return { nodes, edges };

  recentMailbox.forEach((record, index) => {
    const recipients = (record.recipient_ids || []).filter(Boolean);
    const firstRecipient = recipients[0];
    const senderNode = agentNodes.get(record.sender_id) || nodeMap.get(`agent:${record.sender_id}`) || nodeMap.get("session");
    const recipientNode = agentNodes.get(firstRecipient) || nodeMap.get(`agent:${firstRecipient}`) || nodeMap.get("session");
    if (!senderNode || !recipientNode) return;

    const laneIndex = index % 3;
    const laneRow = Math.floor(index / 3);
    const topLane = index % 2 === 0;
    const laneX = 315 + laneIndex * 285;
    const laneY = topLane ? 112 + laneRow * 42 : 484 - laneRow * 42;
    const messageNodeId = `mailbox:${record.id || record.message_id || `${record.sender_id}:${firstRecipient}:${record.created_at || index}`}`;
    const route = coworkMailboxRoute(record, firstRecipient || "team");
    const messageNode = {
      id: messageNodeId,
      kind: "message",
      title: route,
      detail: compactText(record.content || "", 190),
      status: record.requires_reply ? "pending" : (record.status || "delivered"),
      badge: [record.requires_reply ? "reply needed" : record.status || "sent", record.updated_at || record.created_at || ""].filter(Boolean).join(" - "),
      x: Math.max(130, Math.min(1070, laneX)),
      y: Math.max(84, Math.min(560, laneY)),
    };
    nodes.push(messageNode);
    nodeMap.set(messageNodeId, messageNode);

    edges.push({
      from: senderNode.id,
      to: messageNodeId,
      kind: "mailbox",
      pulse: true,
    });
    for (const recipient of recipients) {
      const targetNode = agentNodes.get(recipient) || nodeMap.get(`agent:${recipient}`);
      if (!targetNode) continue;
      edges.push({
        from: messageNodeId,
        to: targetNode.id,
        kind: "mailbox",
        pulse: Boolean(record.requires_reply),
      });
    }
  });

  const latestByAgent = new Map();
  for (const record of recentMailbox) {
    if (record.sender_id) latestByAgent.set(record.sender_id, record);
    for (const recipient of record.recipient_ids || []) {
      latestByAgent.set(recipient, record);
    }
  }
  for (const [agentId, record] of latestByAgent.entries()) {
    const node = agentNodes.get(agentId) || nodeMap.get(`agent:${agentId}`);
    if (!node) continue;
    node.notice = compactText(`${coworkMailboxRoute(record)}: ${record.content || ""}`, 82);
    node.noticeUrgent = Boolean(record.requires_reply);
  }
  coworkApplyGraphNodePositions(session?.id, nodes);
  return { nodes, edges };
}

function renderCoworkGraph(session) {
  if (!elements.coworkGraphSvg) return;
  const svg = elements.coworkGraphSvg;
  svg.textContent = "";
  const { nodes, edges } = coworkGraphNodes(session);
  if (elements.coworkGraphCaption) {
    const stats = session?.graph?.stats;
    const spans = session?.trace_spans?.length || 0;
    const mailboxCount = session?.mailbox?.length || 0;
    const latestDecision = [...(session?.scheduler_decisions || [])].pop();
    elements.coworkGraphCaption.textContent = session
      ? `${coworkArchitectureLabel(session.architecture || session.workflow_mode)} - ${stats?.agents || Math.max(0, nodes.length - 1)} agents - ${mailboxCount} messages - ${spans} instructions${latestDecision?.reason ? ` - ${compactText(latestDecision.reason, 72)}` : ""}`
      : "Start or select a session to see the agent field.";
  }
  renderCoworkFieldOverlay(session);
  if (!nodes.length) {
    const empty = coworkGraphCreateSvg("text", { x: 600, y: 310, "text-anchor": "middle", class: "cowork-graph-empty-text" });
    empty.textContent = t("cowork.noSessionSelected");
    svg.append(empty);
    renderCoworkAgentFieldPopover(null, null);
    if (elements.coworkGraphInspector) {
      elements.coworkGraphInspector.textContent = t("cowork.selectOrStartSentence");
    }
    return;
  }
  const nodeMap = new Map(nodes.map((node) => [node.id, node]));
  const viewport = coworkGraphCreateSvg("g", {
    class: "cowork-graph-viewport",
    transform: `translate(${state.coworkGraphView.x} ${state.coworkGraphView.y}) scale(${state.coworkGraphView.scale})`,
  });
  const edgeLayer = coworkGraphCreateSvg("g", { class: "cowork-graph-edges" });
  const labelLayer = coworkGraphCreateSvg("g", { class: "cowork-graph-edge-labels" });
  const nodeLayer = coworkGraphCreateSvg("g", { class: "cowork-graph-nodes" });
  const agentNodes = nodes.filter((node) => node.kind === "agent");
  if (agentNodes.length >= 2) {
    const minX = Math.min(...agentNodes.map((node) => node.x)) - 110;
    const maxX = Math.max(...agentNodes.map((node) => node.x)) + 110;
    const minY = Math.min(...agentNodes.map((node) => node.y)) - 90;
    const maxY = Math.max(...agentNodes.map((node) => node.y)) + 100;
    const peerGroup = coworkGraphCreateSvg("g", { class: "cowork-graph-peer-zone" });
    const peerRect = coworkGraphCreateSvg("rect", {
      x: Math.max(44, minX),
      y: Math.max(44, minY),
      width: Math.min(1112, maxX) - Math.max(44, minX),
      height: Math.min(576, maxY) - Math.max(44, minY),
      rx: 28,
    });
    const peerTitle = coworkGraphCreateSvg("text", {
      x: Math.max(64, minX + 20),
      y: Math.max(70, minY + 28),
      class: "cowork-graph-peer-title",
    });
    const peerMeta = coworkGraphCreateSvg("text", {
      x: Math.max(64, minX + 20),
      y: Math.max(90, minY + 48),
      class: "cowork-graph-peer-meta",
    });
    peerTitle.textContent = t("cowork.peerZone");
    peerMeta.textContent = t("cowork.peerZoneDesc");
    peerGroup.append(peerRect, peerTitle, peerMeta);
    viewport.append(peerGroup);
  }
  for (const [index, edge] of edges.entries()) {
    const from = nodeMap.get(edge.from);
    const to = nodeMap.get(edge.to);
    if (!from || !to) continue;
    const line = coworkGraphCreateSvg("path", {
      d: `M ${from.x} ${from.y} C ${(from.x + to.x) / 2} ${from.y}, ${(from.x + to.x) / 2} ${to.y}, ${to.x} ${to.y}`,
      class: `cowork-graph-edge cowork-graph-edge-${edge.kind}${edge.pulse ? " pulse" : ""}`,
    });
    edgeLayer.append(line);
    if (edge.label && index >= Math.max(0, edges.length - 10)) {
      const labelGroup = coworkGraphCreateSvg("g", {
        class: `cowork-graph-edge-label cowork-graph-edge-label-${edge.kind}`,
        transform: `translate(${(from.x + to.x) / 2} ${(from.y + to.y) / 2})`,
      });
      const labelText = compactText(edge.label, 58);
      const labelWidth = Math.min(310, Math.max(118, labelText.length * 6.4 + 18));
      const labelRect = coworkGraphCreateSvg("rect", {
        x: -labelWidth / 2,
        y: -14,
        width: labelWidth,
        height: 24,
        rx: 12,
      });
      const label = coworkGraphCreateSvg("text", {
        x: 0,
        y: 2,
        "text-anchor": "middle",
      });
      label.textContent = labelText;
      labelGroup.append(labelRect, label);
      labelLayer.append(labelGroup);
    }
  }
  for (const node of nodes) {
    const group = coworkGraphCreateSvg("g", {
      class: `cowork-graph-node cowork-graph-node-${node.kind}${state.activeCoworkGraphNode === node.id ? " active" : ""}`,
      transform: `translate(${node.x} ${node.y})`,
      tabindex: "0",
      role: "button",
      "aria-label": coworkGraphNodeLabel(node),
    });
    const width = node.kind === "session" ? 204 : node.kind === "agent" ? 172 : node.kind === "message" ? 250 : node.kind === "thread" ? 174 : 154;
    const height = node.kind === "session" ? 82 : node.kind === "agent" ? 72 : node.kind === "message" ? 78 : node.kind === "thread" ? 66 : 64;
    const rect = coworkGraphCreateSvg("rect", {
      x: -width / 2,
      y: -height / 2,
      width,
      height,
      rx: 8,
      fill: "var(--cowork-surface)",
      stroke: coworkGraphStatusColor(node.status),
    });
    const dot = coworkGraphCreateSvg("circle", {
      cx: -width / 2 + 16,
      cy: -height / 2 + 16,
      r: 5,
      fill: coworkGraphStatusColor(node.status),
    });
    const title = coworkGraphCreateSvg("text", { x: -width / 2 + 28, y: -height / 2 + 20, class: "cowork-graph-node-title" });
    title.textContent = compactText(node.title, node.kind === "session" ? 28 : node.kind === "message" ? 34 : 20);
    const meta = coworkGraphCreateSvg("text", { x: -width / 2 + 14, y: 8, class: "cowork-graph-node-meta" });
    meta.textContent = compactText(String(node.detail || node.status || ""), node.kind === "session" ? 34 : node.kind === "message" ? 42 : 26);
    const badge = coworkGraphCreateSvg("text", { x: -width / 2 + 14, y: height / 2 - 12, class: "cowork-graph-node-badge" });
    badge.textContent = String(node.badge || node.status || node.kind).replaceAll("_", " ");
    group.append(rect, dot, title, meta, badge);
    if (node.kind === "agent" && node.notice) {
      const noticeText = compactText(node.notice, 46);
      const noticeWidth = Math.min(250, Math.max(108, noticeText.length * 6.2 + 18));
      const notice = coworkGraphCreateSvg("g", {
        class: `cowork-graph-node-notice${node.noticeUrgent ? " urgent" : ""}`,
        transform: `translate(${Math.max(-width / 2, -noticeWidth / 2)} ${height / 2 + 10})`,
      });
      const noticeRect = coworkGraphCreateSvg("rect", {
        x: 0,
        y: 0,
        width: noticeWidth,
        height: 24,
        rx: 12,
      });
      const noticeLabel = coworkGraphCreateSvg("text", {
        x: 10,
        y: 16,
      });
      noticeLabel.textContent = noticeText;
      notice.append(noticeRect, noticeLabel);
      group.append(notice);
    }
    group.addEventListener("pointerdown", (event) => {
      if (event.button !== 0) return;
      event.stopPropagation();
      event.preventDefault();
      elements.coworkGraphStage?.setPointerCapture?.(event.pointerId);
      state.activeCoworkGraphNode = node.id;
      state.coworkGraphDrag = {
        kind: "node",
        id: event.pointerId,
        nodeId: node.id,
        sessionId: session?.id || "",
        x: event.clientX,
        y: event.clientY,
        startNodeX: node.x,
        startNodeY: node.y,
        moved: false,
      };
    });
    group.addEventListener("click", (event) => {
      event.stopPropagation();
      if (state.coworkGraphSuppressClick === node.id) {
        state.coworkGraphSuppressClick = "";
        state.coworkGraphIgnoreStageClick = false;
        return;
      }
      selectCoworkGraphNode(session, node.id);
    });
    group.addEventListener("keydown", (event) => {
      if (event.key === "Enter" || event.key === " ") {
        event.preventDefault();
        group.dispatchEvent(new MouseEvent("click", { bubbles: true }));
      }
    });
    nodeLayer.append(group);
  }
  viewport.append(edgeLayer, labelLayer, nodeLayer);
  svg.append(viewport);
  const selected = nodeMap.get(state.activeCoworkGraphNode) || nodes[0];
  if (elements.coworkGraphInspector) {
    const totalAgents = session?.graph?.stats?.total_agents || session?.agents?.length || 0;
    const hiddenAgents = Math.max(0, totalAgents - (session?.graph?.stats?.agents || 0));
    const hiddenText = selected.kind === "session" && hiddenAgents ? ` - ${hiddenAgents} more agent(s) hidden` : "";
    elements.coworkGraphInspector.textContent = `${coworkGraphNodeLabel(selected)} - ${selected.status || "active"} - ${selected.detail || ""}${hiddenText}`;
  }
  renderCoworkAgentFieldPopover(session, selected);
}

function setCoworkGraphZoom(nextScale, origin = { x: 600, y: 310 }) {
  const oldScale = state.coworkGraphView.scale;
  const scale = Math.min(2.3, Math.max(0.45, nextScale));
  state.coworkGraphView.x = origin.x - ((origin.x - state.coworkGraphView.x) / oldScale) * scale;
  state.coworkGraphView.y = origin.y - ((origin.y - state.coworkGraphView.y) / oldScale) * scale;
  state.coworkGraphView.scale = scale;
  renderCoworkGraph(state.activeCoworkSession);
}

function resetCoworkGraphView() {
  state.coworkGraphView = { scale: 1, x: 0, y: 0 };
  renderCoworkGraph(state.activeCoworkSession);
}

function setupCoworkGraphInteractions() {
  const stage = elements.coworkGraphStage;
  if (!stage || !elements.coworkGraphSvg) return;
  elements.coworkGraphZoomOut?.addEventListener("click", () => setCoworkGraphZoom(state.coworkGraphView.scale * 0.86));
  elements.coworkGraphZoomIn?.addEventListener("click", () => setCoworkGraphZoom(state.coworkGraphView.scale * 1.16));
  elements.coworkGraphReset?.addEventListener("click", resetCoworkGraphView);
  stage.addEventListener("wheel", (event) => {
    if (event.target.closest?.(".cowork-agent-field-popover")) return;
    event.preventDefault();
    const rect = stage.getBoundingClientRect();
    const origin = {
      x: ((event.clientX - rect.left) / rect.width) * 1200,
      y: ((event.clientY - rect.top) / rect.height) * 620,
    };
    setCoworkGraphZoom(state.coworkGraphView.scale * (event.deltaY > 0 ? 0.92 : 1.08), origin);
  }, { passive: false });
  stage.addEventListener("pointerdown", (event) => {
    if (event.target.closest?.(".cowork-agent-field-popover")) return;
    if (event.target.closest?.(".cowork-graph-node")) return;
    if (event.button !== 0) return;
    stage.setPointerCapture(event.pointerId);
    state.coworkGraphDrag = {
      id: event.pointerId,
      x: event.clientX,
      y: event.clientY,
      startX: state.coworkGraphView.x,
      startY: state.coworkGraphView.y,
    };
  });
  stage.addEventListener("pointermove", (event) => {
    const drag = state.coworkGraphDrag;
    if (!drag || drag.id !== event.pointerId) return;
    const rect = stage.getBoundingClientRect();
    if (drag.kind === "node") {
      const pixelDx = event.clientX - drag.x;
      const pixelDy = event.clientY - drag.y;
      drag.moved = drag.moved || Math.hypot(pixelDx, pixelDy) > COWORK_NODE_CLICK_MOVE_THRESHOLD;
      if (!drag.moved) return;
      const dx = ((event.clientX - drag.x) / rect.width) * 1200 / state.coworkGraphView.scale;
      const dy = ((event.clientY - drag.y) / rect.height) * 620 / state.coworkGraphView.scale;
      const nextX = drag.startNodeX + dx;
      const nextY = drag.startNodeY + dy;
      coworkRememberGraphNodePosition(drag.sessionId, drag.nodeId, nextX, nextY);
      renderCoworkGraph(state.activeCoworkSession);
      return;
    }
    state.coworkGraphView.x = drag.startX + ((event.clientX - drag.x) / rect.width) * 1200;
    state.coworkGraphView.y = drag.startY + ((event.clientY - drag.y) / rect.height) * 620;
    renderCoworkGraph(state.activeCoworkSession);
  });
  stage.addEventListener("pointerup", (event) => {
    const drag = state.coworkGraphDrag;
    if (drag?.id === event.pointerId && stage.hasPointerCapture?.(event.pointerId)) {
      stage.releasePointerCapture(event.pointerId);
    }
    if (drag?.kind === "node") {
      state.coworkGraphSuppressClick = drag.nodeId;
      state.coworkGraphIgnoreStageClick = true;
      if (!drag.moved) {
        selectCoworkGraphNode(state.activeCoworkSession, drag.nodeId);
      }
    }
    state.coworkGraphDrag = null;
  });
  stage.addEventListener("pointercancel", () => {
    state.coworkGraphDrag = null;
  });
  stage.addEventListener("click", (event) => {
    if (state.coworkGraphIgnoreStageClick) {
      state.coworkGraphIgnoreStageClick = false;
      state.coworkGraphSuppressClick = "";
      return;
    }
    if (event.target.closest?.(".cowork-graph-node") || event.target.closest?.(".cowork-agent-field-popover")) return;
    state.activeCoworkGraphNode = "session";
    renderCoworkGraph(state.activeCoworkSession);
  });
}

function renderCoworkSessions() {
  if (elements.coworkSessionCount) {
    elements.coworkSessionCount.textContent = String(state.coworkSessions.length);
  }
  const lists = [elements.coworkSessionList, elements.coworkSidebarSessionList].filter(Boolean);
  if (!lists.length) {
    return;
  }
  for (const list of lists) {
    list.textContent = "";
  }
  if (state.coworkLoading) {
    for (const list of lists) {
      list.append(coworkEmpty(t("cowork.loadingSessions")));
    }
    return;
  }
  if (!state.coworkSessions.length) {
    for (const list of lists) {
      list.append(coworkEmpty(t("cowork.noSessions")));
    }
    return;
  }
  const createSessionButton = (session, compact = false) => {
    const button = document.createElement("button");
    button.type = "button";
    button.className = "cowork-session-item";
    if (compact) {
      button.classList.add("cowork-sidebar-session-item");
    }
    if (session.id === state.activeCoworkSessionId) {
      button.classList.add("active");
    }
    button.innerHTML = `
      <span class="cowork-session-title"></span>
      <span class="cowork-session-meta"></span>
    `;
    button.querySelector(".cowork-session-title").textContent = session.title || session.id;
    const decision = session.completion_decision || {};
    const budget = session.budget_state || session.budget || {};
    const usage = budget.usage || {};
    const blockerCount = (decision.blocked || []).length
      + (decision.review_blockers || []).length
      + (decision.fanout_blockers || []).length
      + (decision.disagreements || []).length;
    button.querySelector(".cowork-session-meta").textContent = [
      coworkArchitectureLabel(session.architecture || session.workflow_mode),
      session.status || "active",
      usage.agent_calls !== undefined ? `calls ${usage.agent_calls}` : "",
      blockerCount ? `${blockerCount} blockers` : "",
      decision.next_action || "",
      session.stop_reason || "",
      session.updated_at || "",
    ].filter(Boolean).join(" - ");
    button.addEventListener("click", () => {
      if (!document.body.classList.contains("cowork-page-active")) {
        openCoworkModal();
      } else {
        setSidebarDropdown("cowork");
      }
      loadCoworkSession(session.id).catch((error) => setCoworkError(error.message || String(error)));
    });
    return button;
  };
  for (const session of state.coworkSessions) {
    elements.coworkSessionList?.append(createSessionButton(session));
    if (elements.coworkSidebarSessionList) {
      const wrapper = document.createElement("div");
      wrapper.className = "cowork-sidebar-session-wrapper";
      if (session.id === state.activeCoworkSessionId) {
        wrapper.classList.add("expanded");
      }
      wrapper.append(createSessionButton(session, true));
      elements.coworkSidebarSessionList.append(wrapper);
    }
  }
}

function renderCoworkBranches(session) {
  const list = elements.coworkBranchList;
  if (!list) return;
  list.textContent = "";
  const branches = Array.isArray(session?.branches) ? session.branches : [];
  list.hidden = !session || !branches.length;
  if (!session || !branches.length) return;
  const currentBranchId = session.current_branch_id || session.current_branch?.id || "default";
  for (const branch of branches) {
    const button = document.createElement("button");
    button.type = "button";
    button.className = "cowork-branch-chip";
    const active = branch.id === currentBranchId || branch.current === true;
    button.classList.toggle("active", active);
    button.disabled = active || Boolean(state.coworkPendingAction);
    button.innerHTML = `
      <strong></strong>
      <span></span>
    `;
    const title = branch.title || branch.id || "branch";
    button.querySelector("strong").textContent = `${title}${branch.source_branch_id ? " (derived)" : ""}`;
    button.querySelector("span").textContent = [
      coworkArchitectureLabel(branch.architecture || session.architecture || session.workflow_mode),
      branch.status || "active",
      branch.source_branch_id ? `from ${branch.source_branch_id}` : "",
    ].filter(Boolean).join(" - ");
    button.addEventListener("click", () => selectCoworkBranch(branch.id));
    list.append(button);
  }
}

function coworkBranchResults(session) {
  const byId = new Map();
  for (const result of coworkArray(session?.branch_results)) {
    if (result?.id) byId.set(result.id, result);
  }
  for (const branch of coworkArray(session?.branches)) {
    const result = branch?.branch_result;
    if (result?.id && !byId.has(result.id)) byId.set(result.id, result);
  }
  return [...byId.values()].sort((a, b) => String(a.created_at || "").localeCompare(String(b.created_at || "")));
}

function renderCoworkBranchResults(session) {
  const list = elements.coworkBranchResultList;
  if (!list) return;
  list.textContent = "";
  const results = coworkBranchResults(session);
  const finalResult = session?.session_final_result || {};
  if (elements.coworkBranchResultSummary) {
    elements.coworkBranchResultSummary.textContent = finalResult.id
      ? `${results.length} result(s) / final ${finalResult.source || "selected"}`
      : (results.length ? `${results.length} result(s)` : "No results");
  }
  if (elements.coworkBranchMergeButton) {
    elements.coworkBranchMergeButton.disabled = results.length < 2 || Boolean(state.coworkPendingAction);
  }
  if (!results.length) {
    list.append(coworkEmpty(t("cowork.noCompletedBranchResults")));
    return;
  }
  const branches = new Map(coworkArray(session?.branches).map((branch) => [branch.id, branch]));
  for (const result of results.slice().reverse()) {
    const branch = branches.get(result.source_branch_id) || {};
    const selected = finalResult.selected_result_id === result.id || coworkArray(finalResult.source_result_ids).includes(result.id);
    const item = document.createElement("article");
    item.className = "cowork-card cowork-observable-card cowork-branch-result-card";
    item.innerHTML = `
      <div class="cowork-card-row">
        <label class="cowork-branch-result-choice">
          <input type="checkbox" checked />
          <strong></strong>
        </label>
        <span></span>
      </div>
      <p></p>
      <small></small>
      <div class="cowork-card-actions"></div>
    `;
    const checkbox = item.querySelector("input");
    checkbox.value = result.source_branch_id || "";
    checkbox.disabled = Boolean(state.coworkPendingAction);
    item.querySelector("strong").textContent = branch.title || result.source_branch_id || result.id;
    item.querySelector("span").className = coworkStatusClass(selected ? "completed" : "active");
    item.querySelector("span").textContent = selected ? "Final" : coworkArchitectureLabel(result.source_architecture || branch.architecture);
    item.querySelector("p").textContent = compactText(result.summary || "", 260);
    item.querySelector("small").textContent = [
      result.source_branch_id ? `Branch ${result.source_branch_id}` : "",
      result.confidence !== null && result.confidence !== undefined ? `Confidence ${Math.round(Number(result.confidence) * 100)}%` : "",
      result.artifacts?.length ? `${result.artifacts.length} artifact(s)` : "",
      result.created_at || "",
    ].filter(Boolean).join(" - ");
    const select = document.createElement("button");
    select.type = "button";
    select.className = "button button-small";
    select.textContent = selected ? "Selected" : "Select final";
    select.disabled = selected || Boolean(state.coworkPendingAction);
    select.addEventListener("click", (event) => {
      event.stopPropagation();
      selectCoworkBranchResult(result.source_branch_id, result.id).catch((error) => setCoworkError(error.message || String(error)));
    });
    item.querySelector(".cowork-card-actions").append(select);
    list.append(item);
  }
}

async function selectCoworkBranch(branchId) {
  if (!state.activeCoworkSessionId || !branchId || state.coworkPendingAction) return;
  state.coworkPendingAction = "select_branch";
  setCoworkError("");
  renderCoworkDetail();
  try {
    const response = await fetch(`${state.coworkApiPath}/sessions/${state.activeCoworkSessionId}/branches/${encodeURIComponent(branchId)}/select`, {
      method: "POST",
      headers: authHeaders(),
    });
    const payload = await response.json().catch(() => ({}));
    if (!response.ok) {
      throw new Error(payload.error || `select branch failed: ${response.status}`);
    }
    state.activeCoworkSession = coworkSafeSession(payload.session);
    state.activeCoworkSessionId = state.activeCoworkSession?.id || state.activeCoworkSessionId;
    renderCoworkDetail();
    await loadCoworkSessions();
  } catch (error) {
    setCoworkError(error.message || String(error));
  } finally {
    state.coworkPendingAction = "";
    renderCoworkDetail();
  }
}

async function selectCoworkBranchResult(branchId, resultId) {
  if (!state.activeCoworkSessionId || !branchId || state.coworkPendingAction) return;
  state.coworkPendingAction = "select_branch_result";
  setCoworkError("");
  renderCoworkDetail();
  try {
    const response = await fetch(`${state.coworkApiPath}/sessions/${encodeURIComponent(state.activeCoworkSessionId)}/branches/${encodeURIComponent(branchId)}/result/select-final`, {
      method: "POST",
      headers: authHeaders(),
      body: JSON.stringify({ result_id: resultId }),
    });
    const payload = await response.json().catch(() => ({}));
    if (!response.ok) {
      throw new Error(payload.error || `select result failed: ${response.status}`);
    }
    state.activeCoworkSession = coworkSafeSession(payload.session);
    renderCoworkDetail();
    await loadCoworkSessions();
  } finally {
    state.coworkPendingAction = "";
    renderCoworkDetail();
  }
}

async function mergeCoworkBranchResults() {
  if (!state.activeCoworkSessionId || state.coworkPendingAction) return;
  const checked = [...(elements.coworkBranchResultList?.querySelectorAll("input[type='checkbox']:checked") || [])];
  const branchIds = checked.map((input) => input.value).filter(Boolean);
  if (branchIds.length < 2) {
    setCoworkError("Select at least two branch results to merge.");
    return;
  }
  state.coworkPendingAction = "merge_branch_results";
  setCoworkError("");
  renderCoworkDetail();
  try {
    const response = await fetch(`${state.coworkApiPath}/sessions/${encodeURIComponent(state.activeCoworkSessionId)}/branch-results/merge`, {
      method: "POST",
      headers: authHeaders(),
      body: JSON.stringify({ branch_ids: branchIds }),
    });
    const payload = await response.json().catch(() => ({}));
    if (!response.ok) {
      throw new Error(payload.error || `merge results failed: ${response.status}`);
    }
    state.activeCoworkSession = coworkSafeSession(payload.session);
    renderCoworkDetail();
    await loadCoworkSessions();
  } finally {
    state.coworkPendingAction = "";
    renderCoworkDetail();
  }
}

function coworkSelectionId(type, item) {
  if (!item) return "";
  if (type === "task") return item.entity_id || item.id || "";
  if (type === "agent") return item.id || item.entity_id || "";
  if (type === "artifact") return item.id || item.path_or_url || item.title || "";
  if (type === "mailbox") return item.id || "";
  if (type === "span") return item.id || "";
  if (type === "work_unit") return item.id || "";
  if (type === "node") return item.id || "";
  return item.id || "";
}

function selectCoworkEntity(type, id) {
  state.activeCoworkSelection = { type, id };
  renderCoworkObservability(state.activeCoworkSession);
  renderCoworkSelectedInspector(state.activeCoworkSession);
}

function coworkFindSelected(session) {
  const selection = state.activeCoworkSelection || {};
  const type = selection.type || "";
  const id = selection.id || "";
  if (!session || !type || !id) return null;
  if (type === "task") {
    return { type, item: (session.tasks || []).find((task) => task.id === id) || null };
  }
  if (type === "agent") {
    return { type, item: (session.agents || []).find((agent) => agent.id === id) || null };
  }
  if (type === "span") {
    return { type, item: (session.trace_spans || []).find((span) => span.id === id) || null };
  }
  if (type === "artifact") {
    return { type, item: (session.artifact_index || []).find((artifact) => coworkSelectionId("artifact", artifact) === id) || null };
  }
  if (type === "work_unit") {
    return { type, item: (session.swarm_plan?.work_units || []).find((unit) => unit.id === id) || null };
  }
  if (type === "workstream") {
    return { type, item: (session.swarm_organization?.workstreams || []).find((stream) => stream.id === id) || null };
  }
  if (type === "mailbox") {
    return { type, item: (session.mailbox || []).find((record) => record.id === id) || null };
  }
  if (type === "node") {
    return { type, item: (session.task_dag?.nodes || []).find((node) => node.id === id) || null };
  }
  return null;
}

function ensureCoworkSelection(session) {
  const found = coworkFindSelected(session);
  if (found?.item) return;
  state.activeCoworkSelection = { type: "", id: "" };
}

function coworkInteractiveCard(type, id, className = "cowork-card cowork-observable-card") {
  const item = document.createElement("article");
  item.className = className;
  item.tabIndex = 0;
  if ((state.activeCoworkSelection?.type || "") === type && (state.activeCoworkSelection?.id || "") === id) {
    item.classList.add("active");
  }
  item.addEventListener("click", () => selectCoworkEntity(type, id));
  item.addEventListener("keydown", (event) => {
    if (event.key === "Enter" || event.key === " ") {
      event.preventDefault();
      selectCoworkEntity(type, id);
    }
  });
  return item;
}

function appendCoworkMetric(container, value, label) {
  const metric = document.createElement("div");
  metric.className = "cowork-metric";
  metric.innerHTML = "<span></span><small></small>";
  metric.querySelector("span").textContent = String(value ?? "-");
  metric.querySelector("small").textContent = label;
  container.append(metric);
}

function setCoworkFocusText(element, value) {
  if (element) element.textContent = value;
}

function renderCoworkFocusStrip(session) {
  if (!elements.coworkFocusStrip) return;
  const hasSession = Boolean(session);
  elements.coworkFocusStrip.hidden = !hasSession;
  if (!hasSession) {
    setCoworkFocusText(elements.coworkFocusProgress, "-");
    setCoworkFocusText(elements.coworkFocusProgressDetail, "No active session");
    setCoworkFocusText(elements.coworkFocusRunning, "-");
    setCoworkFocusText(elements.coworkFocusRunningDetail, "No active agents");
    setCoworkFocusText(elements.coworkFocusNext, "-");
    setCoworkFocusText(elements.coworkFocusNextDetail, "Select or start a session");
    setCoworkFocusText(elements.coworkFocusOutput, "-");
    setCoworkFocusText(elements.coworkFocusOutputDetail, "No final draft");
    return;
  }
  const tasks = Array.isArray(session.tasks) ? session.tasks : [];
  const units = Array.isArray(session.swarm_plan?.work_units) ? session.swarm_plan.work_units : [];
  const workItems = units.length ? units : tasks;
  const completeStatuses = new Set(["completed", "skipped"]);
  const runningStatuses = new Set(["running", "in_progress", "active"]);
  const blockedStatuses = new Set(["failed", "blocked", "needs_revision"]);
  const completed = workItems.filter((item) => completeStatuses.has(String(item.status || ""))).length;
  const running = workItems.filter((item) => runningStatuses.has(String(item.status || ""))).length;
  const blocked = workItems.filter((item) => blockedStatuses.has(String(item.status || ""))).length;
  const total = workItems.length || tasks.length;
  const agents = Array.isArray(session.agents) ? session.agents : [];
  const activeAgents = agents.filter((agent) => runningStatuses.has(String(agent.status || "")));
  const decision = session.completion_decision || {};
  const nextAction = String(decision.next_action || session.stop_reason || session.status || "observe").replaceAll("_", " ");
  const finalDraft = String(session.final_draft || "").trim();
  const artifactCount = Array.isArray(session.artifacts) ? session.artifacts.length : 0;
  const queues = session.swarm_queues?.counts || {};

  setCoworkFocusText(elements.coworkFocusProgress, total ? `${completed}/${total}` : "0/0");
  setCoworkFocusText(
    elements.coworkFocusProgressDetail,
    [
      units.length ? t("cowork.workUnits") : t("cowork.tasks"),
      blocked ? formatMessage("cowork.blockedCount", { count: blocked }) : "",
      queues.ready ? formatMessage("cowork.readyCount", { count: queues.ready }) : "",
    ].filter(Boolean).join(" - ") || t("cowork.noQueuedWork"),
  );
  setCoworkFocusText(elements.coworkFocusRunning, activeAgents.length ? String(activeAgents.length) : String(running || 0));
  setCoworkFocusText(
    elements.coworkFocusRunningDetail,
    activeAgents.length
      ? activeAgents.map((agent) => agent.name || agent.id).slice(0, 3).join(", ")
      : (running ? formatMessage("cowork.workItemsCount", { count: running }) : t("cowork.noActiveAgents")),
  );
  setCoworkFocusText(elements.coworkFocusNext, compactText(nextAction, 26));
  setCoworkFocusText(elements.coworkFocusNextDetail, compactText(decision.reason || session.current_focus_task || session.goal || "", 92));
  setCoworkFocusText(elements.coworkFocusOutput, finalDraft ? t("status.available") : (artifactCount ? formatMessage("cowork.artifactsCount", { count: artifactCount }) : t("status.pending")));
  setCoworkFocusText(elements.coworkFocusOutputDetail, finalDraft ? compactText(finalDraft, 92) : (artifactCount ? t("cowork.artifactsAvailable") : t("cowork.noFinalDraft")));
}

function renderCoworkRunMetrics(session) {
  if (!elements.coworkRunMetrics) return;
  elements.coworkRunMetrics.textContent = "";
  const latest = [...(session?.run_metrics || [])].pop();
  const budget = session?.budget_state || session?.budget || {};
  const usage = budget.usage || {};
  const limits = budget.limits || {};
  appendCoworkMetric(elements.coworkRunMetrics, usage.rounds ?? session?.rounds ?? latest?.rounds ?? 0, t("cowork.rounds"));
  appendCoworkMetric(elements.coworkRunMetrics, usage.agent_calls ?? latest?.agent_calls ?? 0, t("cowork.agentCalls"));
  appendCoworkMetric(elements.coworkRunMetrics, latest?.tasks_completed || (session?.tasks || []).filter((task) => task.status === "completed").length, t("cowork.done"));
  appendCoworkMetric(elements.coworkRunMetrics, session?.stop_reason || latest?.stop_reason || latest?.status || session?.status || "idle", t("cowork.stop"));
  appendCoworkMetric(elements.coworkRunMetrics, limits.parallel_width ?? "-", t("cowork.width"));
  const decision = [...(session?.scheduler_decisions || [])].pop();
  if (elements.coworkSchedulerSummary) {
    const selected = decision?.selected_agent_ids?.length ? ` -> ${decision.selected_agent_ids.join(", ")}` : "";
    elements.coworkSchedulerSummary.textContent = decision
      ? compactText(`${decision.reason || t("cowork.schedulerDecision")}${selected}`, 96)
      : t("cowork.noDecisions");
  }
}

function renderCoworkArchitectureProjection(session) {
  const list = elements.coworkArchitectureProjectionList;
  if (!list) return;
  list.textContent = "";
  const projection = session?.organization_projection || {};
  const sections = Array.isArray(projection.sections) ? projection.sections : [];
  if (elements.coworkArchitectureProjectionSummary) {
    elements.coworkArchitectureProjectionSummary.textContent = projection.architecture
      ? `${coworkArchitectureLabel(projection.architecture)} / ${formatMessage("cowork.sectionsCount", { count: sections.length })}`
      : t("cowork.noProjection");
  }
  if (!sections.length) {
    list.append(coworkEmpty(t("cowork.noArchitectureProjection")));
    return;
  }
  for (const section of sections) {
    const items = Array.isArray(section.items) ? section.items : [];
    const card = document.createElement("article");
    card.className = "cowork-card cowork-observable-card";
    card.innerHTML = `
      <div class="cowork-card-row">
        <strong></strong>
        <span></span>
      </div>
      <p></p>
      <small></small>
    `;
    card.querySelector("strong").textContent = section.title || section.id || t("cowork.projection");
    card.querySelector("span").className = coworkStatusClass(items.length ? "active" : "pending");
    card.querySelector("span").textContent = formatMessage("runChain.itemsCount", { count: items.length });
    card.querySelector("p").textContent = compactText(JSON.stringify(items.slice(0, 3), null, 2), 240);
    card.querySelector("small").textContent = [
      section.id || "",
      coworkArchitectureLabel(projection.architecture || session?.architecture || session?.workflow_mode),
    ].filter(Boolean).join(" - ");
    list.append(card);
  }
}

function renderCoworkSwarmPlan(session) {
  if (!elements.coworkSwarmPlanPanel) return;
  const panel = elements.coworkSwarmPlanPanel;
  panel.textContent = "";
  const plan = session?.swarm_plan || {};
  const units = Array.isArray(plan.work_units) ? plan.work_units : [];
  if (elements.coworkSwarmPlanSummary) {
    elements.coworkSwarmPlanSummary.textContent = plan.id
      ? `${plan.status || "active"} / ${formatMessage("cowork.unitsCount", { count: units.length })}`
      : t("cowork.noPlan");
  }
  if (!plan.id) {
    panel.append(coworkEmpty(t("cowork.noSwarmPlan")));
    return;
  }
  const budget = session?.budget_state || session?.budget || {};
  const queues = session?.swarm_queues || {};
  const organization = session?.swarm_organization || {};
  const metrics = organization.metrics || session?.swarm_metrics || {};
  const largeSummary = session?.large_swarm_summary || {};
  const limits = budget.limits || {};
  const usage = budget.usage || {};
  const card = document.createElement("article");
  card.className = "cowork-card cowork-observable-card";
  card.innerHTML = `
    <div class="cowork-card-row">
      <strong></strong>
      <span></span>
    </div>
    <p></p>
    <small></small>
  `;
  card.querySelector("strong").textContent = plan.strategy || "swarm";
  card.querySelector("span").className = coworkStatusClass(plan.status || "active");
  card.querySelector("span").textContent = plan.status || "active";
  card.querySelector("p").textContent = compactText(plan.goal || session?.goal || "", 180);
  card.querySelector("small").textContent = [
    limits.parallel_width !== undefined ? formatMessage("cowork.widthValue", { value: limits.parallel_width }) : "",
    limits.max_work_units !== undefined ? formatMessage("cowork.unitsProgress", { count: units.length, total: limits.max_work_units }) : formatMessage("cowork.unitsCount", { count: units.length }),
    queues.counts ? `${formatMessage("cowork.readyCount", { count: queues.counts.ready || 0 })} / ${formatMessage("cowork.runningCount", { count: queues.counts.running || 0 })}` : "",
    organization.grouped_counts ? formatMessage("cowork.streamsCount", { count: organization.grouped_counts.workstreams || 0 }) : "",
    usage.agent_calls !== undefined ? formatMessage("cowork.callsCount", { count: usage.agent_calls }) : "",
    session?.stop_reason || "",
  ].filter(Boolean).join(" - ");
  panel.append(card);
  if (queues.counts || organization.gates || largeSummary.enabled) {
    const queueCard = document.createElement("article");
    queueCard.className = "cowork-card cowork-observable-card";
    queueCard.innerHTML = `
      <div class="cowork-card-row">
        <strong></strong>
        <span></span>
      </div>
      <p></p>
      <small></small>
    `;
    queueCard.querySelector("strong").textContent = organization.schema_version ? t("cowork.swarmOrganization") : t("cowork.schedulerQueues");
    queueCard.querySelector("span").className = coworkStatusClass(queues.available_slots ? "ready" : "pending");
    queueCard.querySelector("span").textContent = formatMessage("cowork.slotsCount", { count: queues.available_slots ?? 0 });
    const counts = queues.counts || {};
    queueCard.querySelector("p").textContent = [
      formatMessage("cowork.readyCount", { count: counts.ready || 0 }),
      formatMessage("cowork.blockedCount", { count: counts.blocked || 0 }),
      formatMessage("cowork.retryCount", { count: counts.failed_retry || 0 }),
      formatMessage("cowork.doneCount", { count: counts.completed || 0 }),
    ].join(" - ");
    const gates = organization.gates || {};
    queueCard.querySelector("small").textContent = organization.schema_version
      ? [
        formatMessage("cowork.reducerStatus", { status: gates.reducer?.status || "not_ready" }),
        formatMessage("cowork.reviewerStatus", { status: gates.reviewer?.status || "not_ready" }),
        formatMessage("cowork.efficiencyValue", { value: metrics.parallel_efficiency ?? "-" }),
        formatMessage("cowork.coverageValue", { value: metrics.reducer_coverage ?? "-" }),
      ].join(" - ")
      : t("cowork.queueReplayHint");
    panel.append(queueCard);
  }
}

function coworkWorkUnitWorkstream(unit) {
  return unit.workstream_id || unit.workstream || unit.fanout_group_id || unit.team_id || unit.source_kind || unit.kind || unit.source_task_id || "default";
}

function renderCoworkWorkUnits(session) {
  if (!elements.coworkWorkUnitList) return;
  const list = elements.coworkWorkUnitList;
  list.textContent = "";
  const units = session?.swarm_plan?.work_units || [];
  const organization = session?.swarm_organization || {};
  const workstreams = Array.isArray(organization.workstreams) ? organization.workstreams : [];
  const counts = units.reduce((acc, unit) => {
    const status = unit.status || "unknown";
    acc[status] = (acc[status] || 0) + 1;
    return acc;
  }, {});
  if (elements.coworkWorkUnitSummary) {
    elements.coworkWorkUnitSummary.textContent = units.length
      ? Object.entries(counts).map(([status, count]) => `${status} ${count}`).join(" / ")
      : t("cowork.noUnits");
  }
  if (!units.length) {
    list.append(coworkEmpty(t("cowork.noSwarmWorkUnits")));
    return;
  }
  const consoleQuery = String(state.coworkConsoleFilter || "").trim();
  const shouldCluster = !consoleQuery && organization.schema_version && (organization.enabled || units.length >= 20);
  if (shouldCluster && workstreams.length) {
    const groups = workstreams.filter((group) => coworkMatchesConsoleFilter(group, ["agent_ids", "unit_counts", "blockers", "sample_unit_ids", "risk"])).slice(0, 24);
    for (const group of groups) {
      const item = coworkInteractiveCard("workstream", group.id, "cowork-card cowork-observable-card cowork-workstream-card");
      item.innerHTML = `
        <div class="cowork-card-row">
          <strong></strong>
          <span></span>
        </div>
        <p></p>
        <small></small>
      `;
      item.querySelector("strong").textContent = group.title || group.id || t("cowork.workstream");
      item.querySelector("span").className = coworkStatusClass(group.status || "active");
      item.querySelector("span").textContent = formatMessage("cowork.unitsCount", { count: Object.values(group.unit_counts || {}).reduce((sum, value) => sum + Number(value || 0), 0) });
      item.querySelector("p").textContent = Object.entries(group.unit_counts || {}).map(([status, count]) => `${status} ${count}`).join(" - ");
      item.querySelector("small").textContent = [
        group.agent_ids?.length ? formatMessage("cowork.agentsValue", { value: group.agent_ids.join(", ") }) : "",
        group.coverage !== undefined ? formatMessage("cowork.coverageValue", { value: `${Math.round(Number(group.coverage || 0) * 100)}%` }) : "",
        group.risk ? formatMessage("cowork.riskValue", { value: group.risk }) : "",
        group.blockers?.length ? formatMessage("cowork.blockersCount", { count: group.blockers.length }) : "",
      ].filter(Boolean).join(" - ");
      list.append(item);
    }
    return;
  }
  const visibleUnits = units.filter((unit) => coworkMatchesConsoleFilter(
    { ...unit, workstream: coworkWorkUnitWorkstream(unit) },
    ["assigned_agent_id", "result", "error", "dependencies", "evidence", "artifacts", "source_work_unit_ids", "workstream"],
  ));
  for (const unit of visibleUnits.slice(0, 60)) {
    const item = coworkInteractiveCard("work_unit", unit.id, "cowork-card cowork-observable-card");
    item.innerHTML = `
      <div class="cowork-card-row">
        <strong></strong>
        <span></span>
      </div>
      <p></p>
      <small></small>
      <div class="cowork-card-actions"></div>
    `;
    item.querySelector("strong").textContent = unit.title || unit.id;
    item.querySelector("span").className = coworkStatusClass(unit.status || "pending");
    item.querySelector("span").textContent = unit.kind ? `${unit.kind}:${unit.status || "pending"}` : (unit.status || "pending");
    item.querySelector("p").textContent = compactText(unit.result?.answer || unit.error || unit.description || "", 180);
    item.querySelector("small").textContent = [
      unit.assigned_agent_id ? formatMessage("cowork.agentValue", { value: unit.assigned_agent_id }) : "",
      formatMessage("cowork.streamValue", { value: coworkWorkUnitWorkstream(unit) }),
      unit.dependencies?.length ? formatMessage("cowork.depsValue", { value: unit.dependencies.join(", ") }) : "",
      unit.confidence !== null && unit.confidence !== undefined ? formatMessage("cowork.confidenceValue", { value: `${Math.round(Number(unit.confidence) * 100)}%` }) : "",
      unit.replan_reason || "",
    ].filter(Boolean).join(" - ");
    const actions = item.querySelector(".cowork-card-actions");
    if (["failed", "needs_revision"].includes(String(unit.status || ""))) {
      const retry = document.createElement("button");
      retry.type = "button";
      retry.className = "button button-small";
      retry.textContent = t("cowork.retry");
      retry.disabled = Boolean(state.coworkPendingAction);
      retry.addEventListener("click", (event) => {
        event.stopPropagation();
        mutateCoworkWorkUnit(unit.id, "retry", { reason: "retry from UI" }).catch((error) => setCoworkError(error.message || String(error)));
      });
      actions.append(retry);
    }
    if (["failed", "blocked", "pending", "ready", "needs_revision"].includes(String(unit.status || ""))) {
      const skip = document.createElement("button");
      skip.type = "button";
      skip.className = "button button-small";
      skip.textContent = t("cowork.skip");
      skip.disabled = Boolean(state.coworkPendingAction);
      skip.addEventListener("click", (event) => {
        event.stopPropagation();
        mutateCoworkWorkUnit(unit.id, "skip", { reason: "skipped from UI" }).catch((error) => setCoworkError(error.message || String(error)));
      });
      actions.append(skip);
    }
    if (["pending", "ready", "in_progress", "failed", "needs_revision"].includes(String(unit.status || ""))) {
      const cancel = document.createElement("button");
      cancel.type = "button";
      cancel.className = "button button-small";
      cancel.textContent = t("cowork.cancel");
      cancel.disabled = Boolean(state.coworkPendingAction);
      cancel.addEventListener("click", (event) => {
        event.stopPropagation();
        mutateCoworkWorkUnit(unit.id, "cancel", { reason: "cancelled from UI" }).catch((error) => setCoworkError(error.message || String(error)));
      });
      actions.append(cancel);
    }
    list.append(item);
  }
}

function renderCoworkEvaluations(session) {
  if (!elements.coworkEvaluationList) return;
  const list = elements.coworkEvaluationList;
  list.textContent = "";
  const evaluations = session?.evaluation_results || session?.completion_decision?.evaluations || [];
  const blockers = evaluations.filter((item) => ["block", "error"].includes(String(item.status || "")));
  if (elements.coworkEvaluationSummary) {
    elements.coworkEvaluationSummary.textContent = evaluations.length
      ? `${blockers.length} blockers / ${evaluations.length} evals`
      : "No evals";
  }
  if (!evaluations.length) {
    list.append(coworkEmpty(t("cowork.noEvaluationResults")));
    return;
  }
  for (const evaluation of evaluations.slice(-12).reverse()) {
    const item = document.createElement("article");
    item.className = "cowork-card cowork-observable-card";
    item.innerHTML = `
      <div class="cowork-card-row">
        <strong></strong>
        <span></span>
      </div>
      <p></p>
      <small></small>
    `;
    item.querySelector("strong").textContent = String(evaluation.kind || "evaluation").replaceAll("_", " ");
    item.querySelector("span").className = coworkStatusClass(evaluation.status || "warn");
    item.querySelector("span").textContent = evaluation.status || "warn";
    item.querySelector("p").textContent = compactText(evaluation.summary || "", 180);
    item.querySelector("small").textContent = [
      evaluation.score !== null && evaluation.score !== undefined ? `Score ${Math.round(Number(evaluation.score) * 100)}%` : "",
      evaluation.recommended_actions?.length ? evaluation.recommended_actions.join(", ") : "",
    ].filter(Boolean).join(" - ");
    list.append(item);
  }
}

function renderCoworkBlockers(session) {
  if (!elements.coworkBlockerPanel) return;
  const panel = elements.coworkBlockerPanel;
  panel.textContent = "";
  const decision = session?.completion_decision || {};
  const blockers = [
    ...(decision.blocked || []).map((item) => ({ ...item, kind: "reply" })),
    ...(decision.review_blockers || []).map((item) => ({ ...item, kind: "review" })),
    ...(decision.fanout_blockers || []).map((item) => ({ ...item, kind: "merge" })),
    ...(decision.disagreements || []).map((item) => ({ ...item, kind: "disagreement" })),
  ];
  if (elements.coworkBlockerSummary) {
    elements.coworkBlockerSummary.textContent = blockers.length
      ? `${blockers.length} open / ${session?.stop_reason || decision.next_action || "attention"}`
      : (session?.final_draft ? "Final output ready" : "No blockers");
  }
  if (!blockers.length) {
    panel.append(coworkEmpty(session?.final_draft ? "Final output is available." : "No unresolved blockers."));
    return;
  }
  for (const blocker of blockers.slice(0, 20)) {
    const id = blocker.id || blocker.task_id || blocker.fanout_group_id || blocker.kind;
    const item = coworkInteractiveCard(blocker.id ? "mailbox" : blocker.task_id ? "task" : "node", id, "cowork-card cowork-observable-card");
    item.innerHTML = `
      <div class="cowork-card-row">
        <strong></strong>
        <span></span>
      </div>
      <p></p>
      <small></small>
    `;
    item.querySelector("strong").textContent = blocker.task_title || blocker.request_type || blocker.kind || id;
    item.querySelector("span").className = coworkStatusClass(blocker.kind || "blocked");
    item.querySelector("span").textContent = blocker.kind || "blocked";
    item.querySelector("p").textContent = compactText(blocker.content || blocker.text || blocker.reason || "", 160);
    item.querySelector("small").textContent = [
      blocker.from ? `From ${blocker.from}` : "",
      blocker.to?.length ? `To ${blocker.to.join(", ")}` : "",
      blocker.blocking_task_id ? `Task ${blocker.blocking_task_id}` : "",
      blocker.review_status ? `Review ${blocker.review_status}` : "",
    ].filter(Boolean).join(" - ");
    panel.append(item);
  }
}

function renderCoworkTaskDag(session) {
  if (!elements.coworkTaskDagList) return;
  const list = elements.coworkTaskDagList;
  list.textContent = "";
  const nodes = session?.task_dag?.nodes || [];
  const edges = session?.task_dag?.edges || [];
  const taskNodes = nodes.filter((node) => node.kind !== "agent" && coworkMatchesConsoleFilter(node, ["detail", "owner"])).slice(0, 28);
  if (elements.coworkDagSummary) {
    elements.coworkDagSummary.textContent = nodes.length ? `${nodes.length} nodes / ${edges.length} edges` : "No graph";
  }
  if (!taskNodes.length) {
    list.append(coworkEmpty(t("cowork.noDagNodes")));
    return;
  }
  for (const node of taskNodes) {
    const type = node.kind === "task" ? "task" : node.kind === "blocker" ? "mailbox" : "node";
    const id = type === "task" ? node.entity_id : type === "mailbox" ? String(node.id || "").replace(/^blocker:/, "") : coworkSelectionId(type, node);
    const item = coworkInteractiveCard(type, id || node.id);
    item.innerHTML = `
      <div class="cowork-card-row">
        <strong></strong>
        <span></span>
      </div>
      <p></p>
      <small></small>
    `;
    item.querySelector("strong").textContent = node.title || node.label || node.id;
    item.querySelector("span").className = coworkStatusClass(node.status || node.kind);
    item.querySelector("span").textContent = node.kind || "node";
    item.querySelector("p").textContent = compactText(node.detail || "", 140);
    item.querySelector("small").textContent = [
      node.owner ? `Owner ${node.owner}` : "",
      node.confidence !== null && node.confidence !== undefined ? `Confidence ${Math.round(Number(node.confidence) * 100)}%` : "",
      node.updated_at || "",
    ].filter(Boolean).join(" - ");
    list.append(item);
  }
}

function renderCoworkTraceSpans(session) {
  if (!elements.coworkTraceList) return;
  const list = elements.coworkTraceList;
  list.textContent = "";
  if (elements.coworkTraceFilter && elements.coworkTraceFilter.value !== state.coworkTraceFilter) {
    elements.coworkTraceFilter.value = state.coworkTraceFilter || "all";
  }
  const view = coworkTraceViewModel(session);
  if (elements.coworkTraceModeTabs) {
    elements.coworkTraceModeTabs.dataset.active = view.mode;
    for (const button of elements.coworkTraceModeTabs.querySelectorAll("[data-mode]")) {
      const active = button.dataset.mode === view.mode;
      button.classList.toggle("active", active);
      button.setAttribute("aria-selected", active ? "true" : "false");
    }
  }
  if (!view.items.length) {
    list.append(coworkEmpty(t("cowork.noTraceEvents")));
    return;
  }
  if (view.mode === "detail") {
    for (const step of [...view.items].reverse()) {
      const item = coworkInteractiveCard("span", step.raw?.id || step.id, "cowork-card cowork-observable-card cowork-trace-card cowork-trace-detail-card");
      item.innerHTML = `
        <div class="cowork-card-row">
          <strong></strong>
          <span></span>
        </div>
        <p></p>
        <small></small>
      `;
      item.querySelector("strong").textContent = step.action;
      item.querySelector("span").className = coworkStatusClass(step.status);
      item.querySelector("span").textContent = step.stage;
      item.querySelector("p").textContent = compactText(step.detail || step.target || "Trace event", 220);
      item.querySelector("small").textContent = [step.target ? `Target ${step.target}` : "", step.at].filter(Boolean).join(" - ");
      if (step.payloadText) {
        const details = document.createElement("details");
        const summary = document.createElement("summary");
        const pre = document.createElement("pre");
        summary.textContent = t("cowork.payload");
        pre.textContent = step.payloadText;
        details.append(summary, pre);
        item.append(details);
      }
      const observations = renderCoworkTraceObservations(step.observations || []);
      if (observations) {
        item.append(observations);
      }
      list.append(item);
    }
    list.scrollTop = list.scrollHeight;
    return;
  }
  for (const group of view.groups.slice(-32).reverse()) {
    const card = document.createElement("article");
    card.className = `cowork-trace-group cowork-trace-group-${group.kind}`;
    const head = document.createElement("div");
    head.className = "cowork-trace-group-head";
    const titleWrap = document.createElement("div");
    const title = document.createElement("strong");
    const subtitle = document.createElement("span");
    const count = document.createElement("span");
    title.textContent = group.label;
    subtitle.textContent = group.subtitle;
    count.className = "cowork-status cowork-status-active";
    count.textContent = `${group.steps.length} steps`;
    titleWrap.append(title, subtitle);
    head.append(titleWrap, count);
    card.append(head);
    const flow = document.createElement("div");
    flow.className = "cowork-trace-flow";
    for (const step of group.steps.slice(-8)) {
      const row = coworkInteractiveCard("span", step.raw?.id || step.id, "cowork-trace-step");
      row.innerHTML = `
        <div class="cowork-trace-dot"></div>
        <div class="cowork-trace-step-body">
          <div class="cowork-trace-step-top">
            <strong></strong>
            <span></span>
          </div>
          <small></small>
          <p></p>
        </div>
      `;
      row.querySelector(".cowork-trace-step-top strong").textContent = step.action;
      row.querySelector(".cowork-trace-step-top span").className = coworkStatusClass(step.status);
      row.querySelector(".cowork-trace-step-top span").textContent = step.status;
      row.querySelector("small").textContent = [step.target ? `Target ${step.target}` : "", step.stage, step.at].filter(Boolean).join(" - ");
      row.querySelector("p").textContent = compactText(step.detail || "Trace event", 220);
      if (step.payloadText) {
        const pre = document.createElement("pre");
        pre.className = "cowork-trace-json-block";
        pre.textContent = step.payloadText;
        row.querySelector(".cowork-trace-step-body").append(pre);
      }
      const observations = renderCoworkTraceObservations(step.observations || []);
      if (observations) {
        row.querySelector(".cowork-trace-step-body").append(observations);
      }
      flow.append(row);
    }
    card.append(flow);
    list.append(card);
  }
  list.scrollTop = list.scrollHeight;
}

function renderCoworkArtifacts(session) {
  if (!elements.coworkObservableArtifactList) return;
  const list = elements.coworkObservableArtifactList;
  list.textContent = "";
  const artifacts = (session?.artifact_index || []).filter((artifact) => coworkMatchesConsoleFilter(artifact, ["source_agent_id", "source_task_id", "source_work_unit_id", "status"]));
  if (elements.coworkArtifactSummary) {
    elements.coworkArtifactSummary.textContent = artifacts.length ? `${artifacts.length} linked` : "No artifacts";
  }
  if (!artifacts.length) {
    list.append(coworkEmpty(t("cowork.noLinkedArtifacts")));
  } else {
    for (const artifact of artifacts.slice(-20).reverse()) {
      const id = coworkSelectionId("artifact", artifact);
      const item = coworkInteractiveCard("artifact", id, "cowork-card cowork-observable-card cowork-artifact-card");
      item.innerHTML = `
        <div class="cowork-card-row">
          <strong></strong>
          <span></span>
        </div>
        <p></p>
        <small></small>
      `;
      item.querySelector("strong").textContent = artifact.summary || artifact.path_or_url || artifact.id;
      item.querySelector("span").className = coworkStatusClass("completed");
      item.querySelector("span").textContent = artifact.kind || "file";
      item.querySelector("p").textContent = compactText(artifact.path_or_url || "", 140);
      item.querySelector("small").textContent = [
      artifact.source_task_title ? `Task ${artifact.source_task_title}` : "",
        artifact.source_work_unit_id ? `Unit ${artifact.source_work_unit_id}` : "",
        artifact.source_agent_id ? `Agent ${artifact.source_agent_id}` : "",
        artifact.status ? `Status ${artifact.status}` : "",
        artifact.created_at || "",
      ].filter(Boolean).join(" - ");
      list.append(item);
    }
  }
  if (elements.coworkFinalDraft) {
    const hasDraft = Boolean((session?.final_draft || "").trim());
    elements.coworkFinalDraft.hidden = !hasDraft;
    elements.coworkFinalDraft.textContent = hasDraft ? compactText(session.final_draft, 1400) : "";
  }
}

function renderCoworkObservability(session) {
  renderCoworkRunMetrics(session);
  renderCoworkArchitectureProjection(session);
  renderCoworkSwarmPlan(session);
  renderCoworkWorkUnits(session);
  renderCoworkTaskDag(session);
  renderCoworkTraceSpans(session);
  renderCoworkArtifacts(session);
  renderCoworkBranchResults(session);
  renderCoworkBlockers(session);
  renderCoworkEvaluations(session);
  renderCoworkSecondaryPanels();
}

function renderCoworkSecondaryPanels() {
  const activeTab = state.coworkSecondaryTab || "trace";
  if (elements.coworkSecondaryTabs) {
    elements.coworkSecondaryTabs.dataset.active = activeTab;
    for (const button of elements.coworkSecondaryTabs.querySelectorAll("[data-tab]")) {
      const active = button.dataset.tab === activeTab;
      button.classList.toggle("active", active);
      button.setAttribute("aria-selected", active ? "true" : "false");
    }
  }
  const root = elements.coworkModal;
  if (root) {
    root.dataset.coworkSecondaryTab = activeTab;
    root.classList.toggle("cowork-graph-collapsed", Boolean(state.coworkGraphCollapsed));
    root.classList.remove("cowork-trace-collapsed");
  }
  if (elements.coworkMapButton) {
    const expanded = !state.coworkGraphCollapsed;
    elements.coworkMapButton.classList.toggle("active", expanded);
    elements.coworkMapButton.setAttribute("aria-pressed", expanded ? "true" : "false");
    elements.coworkMapButton.textContent = expanded ? t("cowork.hideMap") : t("cowork.map");
  }
}

function setCoworkGraphCollapsed(collapsed) {
  state.coworkGraphCollapsed = Boolean(collapsed);
  renderCoworkSecondaryPanels();
}

function isCoworkGraphCurrentlyCollapsed() {
  return Boolean(elements.coworkModal?.classList.contains("cowork-graph-collapsed"));
}

function inspectorRow(label, value) {
  const row = document.createElement("div");
  row.className = "cowork-inspector-row";
  const key = document.createElement("span");
  key.textContent = label;
  const val = document.createElement("strong");
  val.textContent = value || "-";
  row.append(key, val);
  return row;
}

function renderCoworkSelectedInspector(session) {
  if (!elements.coworkSelectedInspector) return;
  ensureCoworkSelection(session);
  const found = coworkFindSelected(session);
  const item = found?.item;
  const type = found?.type || "";
  elements.coworkSelectedInspector.textContent = "";
  if (elements.coworkInspectorKind) {
    elements.coworkInspectorKind.textContent = item ? type : "Nothing selected";
  }
  if (!item) {
    elements.coworkSelectedInspector.append(coworkEmpty(t("cowork.selectInspectable")));
    updateCoworkTaskActionControls(null);
    return;
  }
  const title = document.createElement("h5");
  title.textContent = item.title || item.name || item.summary || item.name || item.id || type;
  const body = document.createElement("p");
  body.textContent = compactText(item.detail || item.description || item.summary || item.result || item.content || item.goal || item.error || "", 520);
  elements.coworkSelectedInspector.append(title);
  elements.coworkSelectedInspector.append(
    inspectorRow("Status", item.status || item.kind || "-"),
    inspectorRow("Owner", item.assigned_agent_id || item.owner || item.actor_id || item.source_agent_id || "-"),
    inspectorRow("Updated", item.updated_at || item.ended_at || item.created_at || item.started_at || "-"),
    body,
  );
  if (item.data || item.result_data || item.payload) {
    const details = document.createElement("details");
    const summary = document.createElement("summary");
    summary.textContent = t("cowork.payload");
    const pre = document.createElement("pre");
    pre.textContent = JSON.stringify(item.data || item.result_data || item.payload, null, 2);
    details.append(summary, pre);
    elements.coworkSelectedInspector.append(details);
  }
  updateCoworkTaskActionControls(type === "task" ? item : null);
}

function updateCoworkTaskActionControls(task) {
  const hasTask = Boolean(task && state.activeCoworkSessionId);
  const pending = Boolean(state.coworkPendingAction);
  for (const button of [elements.coworkTaskAssignButton, elements.coworkTaskRetryButton, elements.coworkTaskReviewButton]) {
    if (button) button.disabled = !hasTask || pending;
  }
  if (elements.coworkTaskRetryButton && task) {
    elements.coworkTaskRetryButton.disabled = !["failed", "blocked", "skipped"].includes(String(task.status || "").toLowerCase()) || pending;
  }
  if (elements.coworkTaskAgent) {
    elements.coworkTaskAgent.disabled = !hasTask || pending;
    if (task?.assigned_agent_id && [...elements.coworkTaskAgent.options].some((option) => option.value === task.assigned_agent_id)) {
      elements.coworkTaskAgent.value = task.assigned_agent_id;
    }
  }
}

function renderCoworkDetail() {
  const session = coworkSafeSession(state.activeCoworkSession);
  const hasSession = Boolean(session);
  if (elements.coworkActiveStatus) {
    elements.coworkActiveStatus.textContent = hasSession
      ? `${coworkArchitectureLabel(session.architecture || session.workflow_mode)} - ${session.rounds || 0} rounds`
      : "No session";
  }
  if (elements.coworkConsoleFilter && elements.coworkConsoleFilter.value !== (state.coworkConsoleFilter || "")) {
    elements.coworkConsoleFilter.value = state.coworkConsoleFilter || "";
  }
  if (elements.coworkActiveTitle) {
  elements.coworkActiveTitle.textContent = hasSession ? session.title : t("cowork.selectOrStart");
  }
  if (elements.coworkActiveGoal) {
    elements.coworkActiveGoal.textContent = hasSession ? session.goal : "";
    elements.coworkActiveGoal.hidden = !hasSession || !session.goal;
  }
  renderCoworkFocusStrip(session);
  renderCoworkBranches(session);
  if (elements.coworkComposerKicker) {
    elements.coworkComposerKicker.textContent = hasSession ? "Message" : "New mission";
  }
  if (elements.coworkComposerLabel) {
    elements.coworkComposerLabel.textContent = hasSession ? "Message" : "Goal";
  }
  if (elements.coworkGoalInput) {
    elements.coworkGoalInput.placeholder = hasSession
      ? "Send a constraint or update to the team..."
      : "Describe the shared goal...";
  }
  if (elements.coworkModeLabel) {
    elements.coworkModeLabel.hidden = hasSession;
  }
  if (elements.coworkModeTabs) {
    elements.coworkModeTabs.hidden = hasSession;
  }
  if (elements.coworkStartButton) {
    const label = hasSession ? "Send message" : "Start Cowork";
    elements.coworkStartButton.setAttribute("aria-label", label);
    elements.coworkStartButton.title = label;
  }
  for (const button of [
    elements.coworkSummaryButton,
    elements.coworkMessageButton,
    elements.coworkTaskButton,
    elements.coworkPauseButton,
    elements.coworkResumeButton,
    elements.coworkDeleteButton,
  ]) {
    if (button) button.disabled = !hasSession;
  }
  if (hasSession) {
    const pending = Boolean(state.coworkPendingAction);
    if (elements.coworkPauseButton) elements.coworkPauseButton.disabled = pending || session.status !== "active";
    if (elements.coworkResumeButton) elements.coworkResumeButton.disabled = pending || session.status !== "paused";
  }
  const threads = session?.threads || [];
  if (hasSession && !threads.some((thread) => thread.id === state.activeCoworkThreadId)) {
    state.activeCoworkThreadId = threads[0]?.id || "";
  }
  ensureCoworkSelection(session);
  renderCoworkObservability(session);
  renderCoworkAgents(session);
  renderCoworkTasks(session?.tasks || []);
  renderCoworkMailbox(session);
  renderCoworkThreads(threads);
  renderCoworkTimeline(session);
  renderCoworkStatusFeed(session, threads);
  renderCoworkGraph(session);
  renderCoworkAgentOptions(session?.agents || []);
  renderCoworkSelectedInspector(session);
}

function renderCoworkInsights(session) {
  if (!elements.coworkInsightPanel) return;
  if (!session) {
    elements.coworkInsightPanel.hidden = true;
    return;
  }
  const decision = session.completion_decision || {};
  const blockers = decision.blocked || [];
  const hasDraft = Boolean((session.final_draft || "").trim());
  const artifacts = Array.isArray(session.artifacts) ? session.artifacts : [];
  const focusText = session.current_focus_task || decision.focus_task || "";
  const hasContext = Boolean(session.workflow_mode || focusText || session.workspace_dir || artifacts.length);
  const hasDecision = Boolean(decision.next_action || decision.reason || blockers.length || hasDraft || hasContext);
  elements.coworkInsightPanel.hidden = !hasDecision;
  if (!hasDecision) return;
  if (elements.coworkInsightMode) {
    elements.coworkInsightMode.textContent = coworkArchitectureLabel(session.architecture || session.workflow_mode || decision.architecture || decision.workflow_mode);
  }
  if (elements.coworkInsightFocus) {
    elements.coworkInsightFocus.textContent = focusText ? compactText(focusText, 220) : "No active focus.";
  }
  if (elements.coworkInsightAction) {
    elements.coworkInsightAction.textContent = String(decision.next_action || "observe").replaceAll("_", " ");
  }
  if (elements.coworkInsightReason) {
    const goalReview = decision.goal_review?.reason ? `Goal review: ${decision.goal_review.reason}` : "";
    elements.coworkInsightReason.textContent = [decision.reason || "", goalReview].filter(Boolean).join(" ");
  }
  if (elements.coworkInsightBlockers) {
    elements.coworkInsightBlockers.textContent = blockers.length
      ? blockers.map((item) => `${item.request_type || "reply"}: ${compactText(item.content || "", 80)}`).join(" | ")
      : "No blocking replies.";
  }
  if (elements.coworkArtifactPanel) {
    const hasArtifacts = Boolean(session.workspace_dir || artifacts.length);
    elements.coworkArtifactPanel.hidden = !hasArtifacts;
    if (elements.coworkWorkspaceDir) {
      elements.coworkWorkspaceDir.textContent = session.workspace_dir || "No confirmed workspace.";
    }
    if (elements.coworkArtifactList) {
      elements.coworkArtifactList.textContent = "";
      for (const artifact of artifacts.slice(-8)) {
        const item = document.createElement("span");
        item.textContent = compactText(artifact, 64);
        item.title = artifact;
        elements.coworkArtifactList.append(item);
      }
    }
  }
  if (elements.coworkFinalDraft) {
    elements.coworkFinalDraft.hidden = !hasDraft;
    elements.coworkFinalDraft.textContent = hasDraft ? compactText(session.final_draft, 1200) : "";
  }
}

function coworkStatusFeedSection(title) {
  const section = document.createElement("section");
  section.className = "cowork-status-feed-section";
  const heading = document.createElement("h5");
  heading.textContent = title;
  section.append(heading);
  return section;
}

function coworkStatusBubble({ title, meta = "", body = "", status = "", tone = "", interactive = false }) {
  const item = document.createElement("article");
  item.className = `cowork-status-bubble${tone ? ` ${tone}` : ""}`;
  if (interactive) item.tabIndex = 0;
  item.innerHTML = `
    <div class="cowork-status-bubble-head">
      <strong></strong>
      <span></span>
    </div>
    <p></p>
    <small></small>
  `;
  item.querySelector("strong").textContent = title;
  const badge = item.querySelector("span");
  badge.className = coworkStatusClass(status || "active");
  badge.textContent = status || "active";
  item.querySelector("p").textContent = body;
  item.querySelector("small").textContent = meta;
  return item;
}

function appendCoworkStatusActivity(feed, session, threads) {
  const agentsById = new Map((session.agents || []).map((agent) => [agent.id, agent]));
  const threadById = new Map((threads || []).map((thread) => [thread.id, thread]));
  const messageItems = (session.messages || []).map((message) => ({
    kind: "message",
    at: message.created_at || "",
    message,
  }));
  const traceItems = Array.isArray(session.trace)
    ? session.trace.map((trace) => ({
      kind: "trace",
      at: trace.at || "",
      trace,
      statusText: trace.detail || trace.action || "",
    }))
    : [];
  const eventItems = traceItems.length
    ? traceItems
    : (session.events || [])
      .map((event) => ({
        kind: "event",
        at: event.created_at || "",
        event,
        statusText: coworkTimelineStatus(event),
      }))
      .filter((item) => item.statusText);
  const items = [...messageItems, ...eventItems]
    .sort((a, b) => String(a.at).localeCompare(String(b.at)))
    .slice(-36);
  const section = coworkStatusFeedSection("Activity");
  if (!items.length) {
    section.append(coworkEmpty(t("cowork.noActivity")));
    feed.append(section);
    return;
  }
  for (const itemData of items) {
    if (itemData.kind === "message") {
      const message = itemData.message;
      const sender = agentsById.get(message.sender_id);
      const thread = threadById.get(message.thread_id);
      const recipients = (message.recipient_ids || []).length ? `to ${(message.recipient_ids || []).join(", ")}` : "broadcast";
      section.append(coworkStatusBubble({
        title: sender ? `${sender.name} / ${sender.role}` : message.sender_id || "sender",
        status: message.sender_id === "user" ? "user" : "message",
        body: message.content || "",
        meta: `${thread?.topic || "Discussion"} - ${recipients}${message.created_at ? ` - ${message.created_at}` : ""}`,
        tone: message.sender_id === "user" ? "from-user" : "from-agent",
      }));
    } else if (itemData.kind === "trace") {
      const trace = itemData.trace;
      section.append(coworkStatusBubble({
        title: trace.action || "Trace",
        status: trace.status || trace.stage || "active",
        body: trace.detail || "",
        meta: [String(trace.stage || "trace").replaceAll("_", " "), trace.at || ""].filter(Boolean).join(" - "),
        tone: "event",
      }));
    } else {
      const event = itemData.event;
      section.append(coworkStatusBubble({
        title: coworkTimelineStatusLabel(event.type),
        status: event.type?.split(".").pop() || "active",
        body: itemData.statusText || "",
        meta: event.created_at || "",
        tone: "event",
      }));
    }
  }
  feed.append(section);
}

function renderCoworkStatusFeed(session, threads = []) {
  if (!elements.coworkStatusFeed) return;
  const feed = elements.coworkStatusFeed;
  feed.textContent = "";
  if (!session) {
    feed.append(coworkEmpty(t("cowork.selectOrStartSentence")));
    return;
  }

  const decision = session.completion_decision || {};
  const blockers = decision.blocked || [];
  const artifacts = Array.isArray(session.artifacts) ? session.artifacts : [];
  const focusText = session.current_focus_task || decision.focus_task || "";
  const overview = coworkStatusFeedSection("Current state");
  overview.append(coworkStatusBubble({
    title: coworkArchitectureLabel(session.architecture || session.workflow_mode || decision.architecture || decision.workflow_mode),
    status: decision.next_action || session.status || "observe",
    body: compactText([focusText || "No active focus.", decision.reason || ""].filter(Boolean).join(" "), 260),
    meta: [
      blockers.length ? `${blockers.length} blocker${blockers.length === 1 ? "" : "s"}` : "No blockers",
      session.workspace_dir ? `Workspace ${session.workspace_dir}` : "",
      artifacts.length ? `${artifacts.length} artifact${artifacts.length === 1 ? "" : "s"}` : "",
    ].filter(Boolean).join(" - "),
    tone: "overview",
  }));
  if ((session.final_draft || "").trim()) {
    overview.append(coworkStatusBubble({
      title: t("cowork.finalDraft"),
      status: "completed",
      body: compactText(session.final_draft, 420),
      meta: "Session output",
      tone: "overview",
    }));
  }
  feed.append(overview);

  const agentSection = coworkStatusFeedSection("Agents");
  const agents = session.agents || [];
  if (!agents.length) {
    agentSection.append(coworkEmpty(t("cowork.noAgentsSentence")));
  } else {
    for (const agent of agents) {
      const mailboxState = coworkAgentMailboxState(session, agent.id);
      agentSection.append(coworkStatusBubble({
        title: agent.name || agent.id,
        status: agent.status || "idle",
        body: `${agent.role || "Agent"} - ${compactText(agent.current_task_title || agent.goal, 160)}`,
        meta: [
          `Inbox ${agent.inbox_count || 0}`,
          `Pending replies ${mailboxState.pendingCount}`,
          mailboxState.waitingOn ? `waiting on ${mailboxState.waitingOn}` : "",
          `Rounds ${agent.rounds || 0}`,
        ].filter(Boolean).join(" - "),
        interactive: true,
      }));
      const item = agentSection.lastElementChild;
      item?.addEventListener("click", () => selectCoworkEntity("agent", agent.id));
    }
  }
  feed.append(agentSection);

  const taskSection = coworkStatusFeedSection("Tasks");
  const tasks = [...(session.tasks || [])].slice(-12);
  if (!tasks.length) {
    taskSection.append(coworkEmpty(t("cowork.noTasks")));
  } else {
    for (const task of tasks) {
      taskSection.append(coworkStatusBubble({
        title: task.title || task.id,
        status: task.status || "pending",
        body: compactText(task.result_data?.answer || task.result || task.description || "", 180),
        meta: [
          `Owner ${task.assigned_agent_id || "-"}`,
          task.confidence !== null && task.confidence !== undefined ? `Confidence ${Math.round(Number(task.confidence) * 100)}%` : "",
          task.dependencies?.length ? `Depends ${task.dependencies.join(", ")}` : "",
        ].filter(Boolean).join(" - "),
        interactive: true,
      }));
      const item = taskSection.lastElementChild;
      item?.addEventListener("click", () => selectCoworkEntity("task", task.id));
    }
  }
  feed.append(taskSection);

  const mailboxSection = coworkStatusFeedSection("Mailbox");
  const records = [...(session.mailbox || [])]
    .sort((a, b) => String(b.updated_at || b.created_at || "").localeCompare(String(a.updated_at || a.created_at || "")))
    .slice(0, 10);
  if (!records.length) {
    mailboxSection.append(coworkEmpty(t("cowork.noMailboxRecords")));
  } else {
    for (const record of records) {
      mailboxSection.append(coworkStatusBubble({
        title: `${record.sender_id || "sender"} -> ${(record.recipient_ids || []).join(", ") || "none"}`,
        status: record.status || "queued",
        body: compactText(record.content || "", 180),
        meta: [
          record.kind || "message",
          record.requires_reply ? "reply required" : "",
          record.priority ? `priority ${record.priority}` : "",
          record.correlation_id ? `corr ${record.correlation_id}` : "",
        ].filter(Boolean).join(" - "),
        interactive: true,
      }));
      const item = mailboxSection.lastElementChild;
      item?.addEventListener("click", () => selectCoworkEntity("mailbox", record.id));
    }
  }
  feed.append(mailboxSection);

  const topicSection = coworkStatusFeedSection("Topics");
  if (!threads.length) {
    topicSection.append(coworkEmpty(t("cowork.noTopics")));
  } else {
    for (const thread of threads) {
      const item = coworkStatusBubble({
        title: thread.topic || thread.id,
        status: thread.status || "topic",
        body: (thread.participant_ids || []).join(", "),
        meta: [`${thread.message_count || 0} msgs`, thread.last_message_at || ""].filter(Boolean).join(" - "),
        interactive: true,
      });
      if (thread.id === state.activeCoworkThreadId) item.classList.add("active");
      item.addEventListener("click", () => {
        state.activeCoworkThreadId = thread.id;
        renderCoworkDetail();
      });
      item.addEventListener("keydown", (event) => {
        if (event.key === "Enter" || event.key === " ") {
          event.preventDefault();
          state.activeCoworkThreadId = thread.id;
          renderCoworkDetail();
        }
      });
      topicSection.append(item);
    }
  }
  feed.append(topicSection);

  appendCoworkStatusActivity(feed, session, threads);
  feed.scrollTop = feed.scrollHeight;
}

function renderCoworkAgents(session) {
  if (!elements.coworkAgentList) return;
  const agents = (session?.agents || []).filter((agent) => coworkMatchesConsoleFilter(agent, ["role", "goal", "current_task_title", "lifecycle_status"]));
  if (elements.coworkAgentSummary) {
    elements.coworkAgentSummary.textContent = agents.length ? `${agents.length} agents` : "No agents";
  }
  elements.coworkAgentList.textContent = "";
  if (!agents.length) {
    elements.coworkAgentList.append(coworkEmpty(t("cowork.noAgentsSentence")));
    return;
  }
  for (const agent of agents) {
    const item = coworkInteractiveCard("agent", agent.id, "cowork-card cowork-observable-card cowork-agent-lane");
    item.innerHTML = `
      <div class="cowork-card-row">
        <strong></strong>
        <span></span>
      </div>
      <p></p>
      <small></small>
    `;
    const mailboxState = coworkAgentMailboxState(session, agent.id);
    item.querySelector("strong").textContent = agent.name || agent.id;
    item.querySelector("span").className = coworkStatusClass(agent.status);
    item.querySelector("span").textContent = agent.status || "idle";
    item.querySelector("p").textContent = `${agent.role || "Agent"} - ${compactText(agent.current_task_title || agent.goal, 120)}`;
    item.querySelector("small").textContent = [
      `Inbox ${agent.inbox_count || 0}`,
      `Pending replies ${mailboxState.pendingCount}`,
      mailboxState.waitingOn ? `waiting on ${mailboxState.waitingOn}` : "",
      `Rounds ${agent.rounds || 0}`,
    ].filter(Boolean).join(" - ");
    elements.coworkAgentList.append(item);
  }
}

function renderCoworkMailbox(session) {
  if (!elements.coworkMailboxList) return;
  elements.coworkMailboxList.textContent = "";
  if (!session) {
    elements.coworkMailboxList.append(coworkEmpty(t("cowork.noMailbox")));
    return;
  }
  const records = [...(session.mailbox || [])]
    .sort((a, b) => String(b.updated_at || b.created_at || "").localeCompare(String(a.updated_at || a.created_at || "")))
    .slice(0, 16);
  if (!records.length) {
    elements.coworkMailboxList.append(coworkEmpty(t("cowork.noMailboxRecords")));
    return;
  }
  for (const record of records) {
    const item = document.createElement("article");
    item.className = "cowork-card cowork-mailbox-card";
    item.innerHTML = `
      <div class="cowork-card-row">
        <strong></strong>
        <span></span>
      </div>
      <p></p>
      <small></small>
    `;
    item.querySelector("strong").textContent = `${record.sender_id || "sender"} -> ${(record.recipient_ids || []).join(", ") || "none"}`;
    item.querySelector("span").className = coworkStatusClass(record.status);
    item.querySelector("span").textContent = record.status || "queued";
    item.querySelector("p").textContent = compactText(record.content || "", 120);
    item.querySelector("small").textContent = [
      record.kind || "message",
      record.requires_reply ? "reply required" : "",
      record.priority ? `priority ${record.priority}` : "",
      record.correlation_id ? `corr ${record.correlation_id}` : "",
    ].filter(Boolean).join(" - ");
    elements.coworkMailboxList.append(item);
  }
}

function renderCoworkTasks(tasks) {
  if (!elements.coworkTaskList) return;
  elements.coworkTaskList.textContent = "";
  tasks = tasks.filter((task) => coworkMatchesConsoleFilter(task, ["assigned_agent_id", "description", "result", "result_data", "dependencies"]));
  if (!tasks.length) {
    elements.coworkTaskList.append(coworkEmpty(t("cowork.noTasks")));
    return;
  }
  const groups = [
    ["pending", "Pending"],
    ["in_progress", "In progress"],
    ["completed", "Completed"],
    ["failed", "Failed"],
    ["skipped", "Skipped"],
  ];
  for (const [status, label] of groups) {
    const groupTasks = tasks.filter((task) => task.status === status);
    if (!groupTasks.length) continue;
    const heading = document.createElement("div");
    heading.className = "cowork-lane-label";
    heading.textContent = label;
    elements.coworkTaskList.append(heading);
    for (const task of groupTasks) {
    const item = document.createElement("article");
    item.className = "cowork-card";
    item.innerHTML = `
      <div class="cowork-card-row">
        <strong></strong>
        <span></span>
      </div>
      <p></p>
      <small></small>
    `;
    item.querySelector("strong").textContent = task.title || task.id;
    item.querySelector("span").className = coworkStatusClass(task.status);
    item.querySelector("span").textContent = task.status || "pending";
    const answer = task.result_data?.answer || task.result || task.description || "";
    item.querySelector("p").textContent = compactText(answer, 130);
    item.querySelector("small").textContent = [
      `Owner ${task.assigned_agent_id || "-"}`,
      task.confidence !== null && task.confidence !== undefined ? `Confidence ${Math.round(Number(task.confidence) * 100)}%` : "",
      task.dependencies?.length ? `Depends ${task.dependencies.join(", ")}` : "",
    ].filter(Boolean).join(" - ");
    elements.coworkTaskList.append(item);
    }
  }
}

function renderCoworkThreads(threads) {
  if (!elements.coworkThreadList) return;
  elements.coworkThreadList.textContent = "";
  if (!threads.length) {
    elements.coworkThreadList.append(coworkEmpty(t("cowork.noTopics")));
    return;
  }
  for (const thread of threads) {
    const item = document.createElement("article");
    item.className = "cowork-card";
    item.innerHTML = `
      <div class="cowork-card-row">
        <strong></strong>
        <span></span>
      </div>
      <p></p>
    `;
    item.querySelector("strong").textContent = thread.topic || thread.id;
    item.querySelector("span").className = coworkStatusClass(thread.status);
    item.querySelector("span").textContent = `${thread.message_count || 0} msgs`;
    item.querySelector("p").textContent = `${(thread.participant_ids || []).join(", ")}${thread.last_message_at ? ` - ${thread.last_message_at}` : ""}`;
    if (thread.id === state.activeCoworkThreadId) {
      item.classList.add("active");
    }
    item.tabIndex = 0;
    item.addEventListener("click", () => {
      state.activeCoworkThreadId = thread.id;
      renderCoworkDetail();
    });
    item.addEventListener("keydown", (event) => {
      if (event.key === "Enter" || event.key === " ") {
        event.preventDefault();
        state.activeCoworkThreadId = thread.id;
        renderCoworkDetail();
      }
    });
    elements.coworkThreadList.append(item);
  }
}

function renderCoworkTimeline(session) {
  if (!elements.coworkMessageList) return;
  elements.coworkMessageList.textContent = "";
  if (!session) {
    elements.coworkMessageList.append(coworkEmpty(t("cowork.selectOrStartSentence")));
    return;
  }
  const conversationView = coworkConversationViewModel(session);
  if (conversationView.blockers.length) {
    const blocker = document.createElement("article");
    blocker.className = "cowork-chat-event cowork-chat-blocker";
    blocker.innerHTML = `<span></span><strong></strong>`;
    blocker.querySelector("span").className = coworkStatusClass("blocked");
    blocker.querySelector("span").textContent = `${conversationView.blockers.length} blocker${conversationView.blockers.length === 1 ? "" : "s"}`;
    blocker.querySelector("strong").textContent = compactText(conversationView.blockers.map((item) => item.content || item.reason || item.task_title || item.kind || "Needs attention").join(" | "), 220);
    elements.coworkMessageList.append(blocker);
  }
  if (conversationView.finalDraft) {
    const output = document.createElement("article");
    output.className = "cowork-final-output-card";
    const title = document.createElement("strong");
    const body = document.createElement("div");
    body.className = "cowork-chat-body";
    title.textContent = t("cowork.sessionOutput");
    renderCoworkMessageBody(body, compactText(conversationView.finalDraft, 1800));
    output.append(title, body);
    elements.coworkMessageList.append(output);
  }
  if (!conversationView.items.length) {
    elements.coworkMessageList.append(coworkEmpty(t("cowork.noTeamActivity")));
    return;
  }
  for (const itemData of conversationView.items) {
    const item = document.createElement("article");
    if (itemData.kind === "message") {
      item.className = `cowork-chat-message ${itemData.role === "user" ? "from-user" : "from-agent"}`;
      item.innerHTML = `
        <div class="cowork-chat-avatar"></div>
        <div class="cowork-chat-bubble">
          <div class="cowork-chat-meta"><strong></strong><span></span></div>
          <div class="cowork-chat-body"></div>
        </div>
      `;
      item.querySelector(".cowork-chat-avatar").textContent = itemData.avatar;
      item.querySelector("strong").textContent = itemData.title;
      item.querySelector("span").textContent = itemData.meta;
      renderCoworkMessageBody(item.querySelector(".cowork-chat-body"), itemData.body, {
        markdown: itemData.role !== "user",
      });
    } else {
      item.className = "cowork-chat-event";
      item.innerHTML = `<span></span><strong></strong>`;
      item.querySelector("span").className = coworkStatusClass(itemData.status);
      item.querySelector("span").textContent = itemData.status || "event";
      item.querySelector("strong").textContent = compactText([itemData.title, itemData.body, itemData.meta].filter(Boolean).join(" - "), 220);
    }
    elements.coworkMessageList.append(item);
  }
  elements.coworkMessageList.scrollTop = elements.coworkMessageList.scrollHeight;
  return;
  const agentsById = new Map((session.agents || []).map((agent) => [agent.id, agent]));
  const threadById = new Map((session.threads || []).map((thread) => [thread.id, thread]));
  const messageItems = (session.messages || []).map((message) => ({
    kind: "message",
    at: message.created_at || "",
    message,
  }));
  const traceItems = Array.isArray(session.trace)
    ? session.trace.map((trace) => ({
      kind: "trace",
      at: trace.at || "",
      trace,
      statusText: trace.detail || trace.action || "",
    }))
    : [];
  const eventItems = traceItems.length
    ? traceItems
    : (session.events || [])
      .map((event) => ({
        kind: "event",
        at: event.created_at || "",
        event,
        statusText: coworkTimelineStatus(event),
      }))
      .filter((item) => item.statusText);
  const items = [...messageItems, ...eventItems]
    .sort((a, b) => String(a.at).localeCompare(String(b.at)))
    .slice(-80);
  if (!items.length) {
    elements.coworkMessageList.append(coworkEmpty(t("cowork.noTeamActivity")));
    return;
  }
  for (const itemData of items) {
    const item = document.createElement("article");
    if (itemData.kind === "message") {
      const message = itemData.message;
      const sender = agentsById.get(message.sender_id);
      const thread = threadById.get(message.thread_id);
      item.className = `cowork-chat-message ${message.sender_id === "user" ? "from-user" : "from-agent"}`;
      item.innerHTML = `
        <div class="cowork-chat-avatar"></div>
        <div class="cowork-chat-bubble">
          <div class="cowork-chat-meta"><strong></strong><span></span></div>
          <div class="cowork-chat-body"></div>
        </div>
      `;
      item.querySelector(".cowork-chat-avatar").textContent = (sender?.name || message.sender_id || "?").slice(0, 1).toUpperCase();
      item.querySelector("strong").textContent = sender ? `${sender.name} / ${sender.role}` : message.sender_id || "sender";
      const recipients = (message.recipient_ids || []).length ? `to ${(message.recipient_ids || []).join(", ")}` : "broadcast";
      item.querySelector("span").textContent = `${thread?.topic || "Discussion"} - ${recipients} - ${message.created_at || ""}`;
      renderCoworkMessageBody(item.querySelector(".cowork-chat-body"), message.content || "", {
        markdown: message.sender_id !== "user",
      });
    } else if (itemData.kind === "trace") {
      const trace = itemData.trace;
      item.className = "cowork-chat-event";
      item.innerHTML = `<span></span><strong></strong>`;
      item.querySelector("span").className = coworkStatusClass(trace.status || trace.stage || "active");
      item.querySelector("span").textContent = String(trace.stage || "trace").replaceAll("_", " ");
      item.querySelector("strong").textContent = `${trace.action || "Trace"} - ${trace.detail || ""}${trace.at ? ` - ${trace.at}` : ""}`;
    } else {
      const event = itemData.event;
      item.className = "cowork-chat-event";
      item.innerHTML = `<span></span><strong></strong>`;
      item.querySelector("span").className = coworkStatusClass(event.type?.split(".").pop() || "active");
      item.querySelector("span").textContent = coworkTimelineStatusLabel(event.type);
      item.querySelector("strong").textContent = `${itemData.statusText || ""}${event.created_at ? ` · ${event.created_at}` : ""}`;
    }
    elements.coworkMessageList.append(item);
  }
  elements.coworkMessageList.scrollTop = elements.coworkMessageList.scrollHeight;
}

function renderCoworkEvents(events) {
  if (!elements.coworkEventList) return;
  elements.coworkEventList.textContent = "";
  const recent = [...events].slice(-12).reverse();
  if (!recent.length) {
    elements.coworkEventList.append(coworkEmpty(t("cowork.noEvents")));
    return;
  }
  for (const event of recent) {
    const item = document.createElement("article");
    item.className = "cowork-event";
    item.innerHTML = `<strong></strong><span></span>`;
    item.querySelector("strong").textContent = event.type || "event";
    item.querySelector("span").textContent = `${event.message || ""} ${event.created_at || ""}`;
    elements.coworkEventList.append(item);
  }
}

function renderCoworkAgentOptions(agents) {
  if (!elements.coworkTaskAgent) return;
  const current = elements.coworkTaskAgent.value;
  elements.coworkTaskAgent.textContent = "";
  for (const agent of agents) {
    const option = document.createElement("option");
    option.value = agent.id;
    option.textContent = `${agent.name || agent.id} (${agent.role || "Agent"})`;
    elements.coworkTaskAgent.append(option);
  }
  if (current && agents.some((agent) => agent.id === current)) {
    elements.coworkTaskAgent.value = current;
  }
}

async function loadCoworkSessions() {
  state.coworkLoading = true;
  renderCoworkSessions();
  const includeCompleted = elements.coworkIncludeCompleted?.checked ? "?include_completed=true" : "";
  try {
    const response = await fetch(`${state.coworkApiPath}/sessions${includeCompleted}`, {
      headers: { Authorization: `Bearer ${state.token}` },
    });
    if (!response.ok) {
      throw new Error(`load cowork sessions failed: ${response.status}`);
    }
    const payload = await response.json();
    state.coworkSessions = payload.items || [];
    if (!state.activeCoworkSessionId && state.coworkSessions.length) {
      state.activeCoworkSessionId = state.coworkSessions[0].id;
      await loadCoworkSession(state.activeCoworkSessionId);
      return;
    }
  } finally {
    state.coworkLoading = false;
  }
  renderCoworkSessions();
}

async function loadCoworkSession(sessionId) {
  const response = await fetch(`${state.coworkApiPath}/sessions/${encodeURIComponent(sessionId)}`, {
    headers: { Authorization: `Bearer ${state.token}` },
  });
  if (!response.ok) {
    throw new Error(`load cowork session failed: ${response.status}`);
  }
  const payload = await response.json();
  const previousSessionId = state.activeCoworkSessionId;
  state.activeCoworkSession = payload.session || null;
  state.activeCoworkSessionId = state.activeCoworkSession?.id || "";
  if (!state.activeCoworkSession?.threads?.some((thread) => thread.id === state.activeCoworkThreadId)) {
    state.activeCoworkThreadId = state.activeCoworkSession?.threads?.[0]?.id || "";
  }
  if (previousSessionId !== state.activeCoworkSessionId) {
    state.activeCoworkGraphNode = "session";
    state.activeCoworkSelection = { type: "", id: "" };
    state.coworkGraphView = { scale: 1, x: 0, y: 0 };
  }
  renderCoworkSessions();
  renderCoworkDetail();
}

function scheduleCoworkRefresh(sessionId = "") {
  if (!document.body.classList.contains("cowork-page-active")) return;
  const targetSessionId = sessionId || state.activeCoworkSessionId;
  if (state.coworkRefreshTimer) {
    clearTimeout(state.coworkRefreshTimer);
  }
  state.coworkRefreshTimer = setTimeout(() => {
    state.coworkRefreshTimer = null;
    loadCoworkSessions()
      .then(() => {
        if (targetSessionId && (!state.activeCoworkSessionId || state.activeCoworkSessionId === targetSessionId)) {
          return loadCoworkSession(targetSessionId);
        }
        if (state.activeCoworkSessionId) {
          return loadCoworkSession(state.activeCoworkSessionId);
        }
        return null;
      })
      .catch((error) => setCoworkError(error.message || String(error)));
  }, 250);
}

function coworkArchitectureValue(value = "") {
  const architecture = String(value || "adaptive_starter");
  return architecture === "hybrid" ? "adaptive_starter" : architecture;
}

function coworkArchitectureLabel(value = "") {
  const architecture = coworkArchitectureValue(value);
  const labels = {
    adaptive_starter: "Adaptive Starter",
    orchestrator: "Orchestrator",
    supervisor: "Supervisor",
    team: "Agent Team",
    generator_verifier: "Generator-Verifier",
    message_bus: "Message Bus",
    shared_state: "Shared State",
    peer_handoff: "Peer Handoff",
    swarm: "Swarm",
  };
  return labels[architecture] || architecture.replaceAll("_", " ");
}

function setCoworkWorkflowMode(mode) {
  mode = coworkArchitectureValue(mode);
  if (elements.coworkWorkflowMode) {
    elements.coworkWorkflowMode.value = mode;
  }
  const tabs = elements.coworkModeTabs?.querySelectorAll(".cowork-mode-tab") || [];
  let activeTab = null;
  tabs.forEach((tab) => {
    const active = tab.dataset.mode === mode;
    tab.classList.toggle("active", active);
    tab.setAttribute("aria-checked", active ? "true" : "false");
    if (active) activeTab = tab;
  });
  if (elements.coworkModeTabs && activeTab) {
    elements.coworkModeTabs.style.setProperty("--cowork-mode-indicator-x", `${activeTab.offsetLeft}px`);
    elements.coworkModeTabs.style.setProperty("--cowork-mode-indicator-w", `${activeTab.offsetWidth}px`);
  }
}

function coworkBlueprintPayload() {
  const text = elements.coworkBlueprintInput?.value.trim() || "";
  if (!text) return null;
  try {
    return JSON.parse(text);
  } catch (error) {
    throw new Error(`Blueprint JSON is invalid: ${error.message || error}`);
  }
}

async function validateCoworkBlueprint({ preview = false } = {}) {
  const blueprint = coworkBlueprintPayload();
  if (!blueprint) {
    if (elements.coworkBlueprintStatus) elements.coworkBlueprintStatus.textContent = t("cowork.noBlueprintJson");
    return null;
  }
  const response = await fetch(`${state.coworkApiPath}/blueprints/${preview ? "preview" : "validate"}`, {
    method: "POST",
    headers: authHeaders(),
    body: JSON.stringify({ blueprint }),
  });
  const payload = await response.json().catch(() => ({}));
  const diagnostics = payload.diagnostics || [];
  const errors = diagnostics.filter((item) => item.severity === "error").length;
  const warnings = diagnostics.filter((item) => item.severity === "warning").length;
  if (elements.coworkBlueprintStatus) {
    elements.coworkBlueprintStatus.textContent = payload.ok
      ? `Valid${warnings ? ` / ${warnings} warning(s)` : ""}`
      : `${errors || diagnostics.length} error(s)`;
  }
  if (!response.ok) {
    setCoworkError((diagnostics[0]?.message || payload.error || `blueprint ${preview ? "preview" : "validation"} failed`));
  } else {
    setCoworkError("");
  }
  if (preview && payload.graph_preview && state.activeCoworkSession) {
    state.activeCoworkSession.graph = payload.graph_preview;
    renderCoworkGraph(state.activeCoworkSession);
  }
  return payload;
}

async function startCoworkSession() {
  const goal = elements.coworkGoalInput?.value.trim() || "";
  let blueprint = null;
  try {
    blueprint = coworkBlueprintPayload();
  } catch (error) {
    setCoworkError(error.message || String(error));
    return;
  }
  if (!goal && !blueprint) {
    setCoworkError("Goal is required.");
    return;
  }
  setCoworkError("");
  if (elements.coworkStartButton) {
    elements.coworkStartButton.disabled = true;
  }
  try {
    const response = await fetch(`${state.coworkApiPath}/sessions`, {
      method: "POST",
      headers: authHeaders(),
      body: JSON.stringify({
        goal,
        blueprint,
        architecture: coworkArchitectureValue(elements.coworkWorkflowMode?.value),
        workflow_mode: coworkArchitectureValue(elements.coworkWorkflowMode?.value),
        auto_run: true,
        max_rounds: COWORK_DEFAULT_RUN_ROUNDS,
        max_agents: COWORK_DEFAULT_RUN_AGENTS,
        max_agent_calls: COWORK_DEFAULT_RUN_AGENT_CALLS,
        run_until_idle: true,
      }),
    });
    if (!response.ok) {
      const payload = await response.json().catch(() => ({}));
      throw new Error(payload.error || `start cowork failed: ${response.status}`);
    }
    const payload = await response.json();
    state.activeCoworkSession = payload.session || null;
    state.activeCoworkSessionId = state.activeCoworkSession?.id || "";
    if (elements.coworkGoalInput) {
      elements.coworkGoalInput.value = "";
    }
    if (elements.coworkBlueprintInput && blueprint) {
      elements.coworkBlueprintInput.value = "";
    }
    await loadCoworkSessions();
    if (state.activeCoworkSessionId) {
      await loadCoworkSession(state.activeCoworkSessionId);
    }
  } catch (error) {
    setCoworkError(error.message || String(error));
  } finally {
    if (elements.coworkStartButton) {
      elements.coworkStartButton.disabled = false;
    }
  }
}

async function submitCoworkComposer() {
  if (state.activeCoworkSessionId) {
    await sendCoworkMessage();
    return;
  }
  await startCoworkSession();
}

async function runCoworkRound() {
  if (!state.activeCoworkSessionId) return;
  const triggerButton = elements.coworkStartButton;
  state.coworkPendingAction = "run_until_idle";
  if (triggerButton) {
    triggerButton.disabled = true;
  }
  try {
    const response = await fetch(`${state.coworkApiPath}/sessions/${encodeURIComponent(state.activeCoworkSessionId)}/run`, {
      method: "POST",
      headers: authHeaders(),
      body: JSON.stringify({
        max_rounds: COWORK_DEFAULT_RUN_ROUNDS,
        max_agents: COWORK_DEFAULT_RUN_AGENTS,
        max_agent_calls: COWORK_DEFAULT_RUN_AGENT_CALLS,
        run_until_idle: true,
        stop_on_blocker: false,
      }),
    });
    if (!response.ok) throw new Error(`run cowork failed: ${response.status}`);
    const payload = await response.json();
    state.activeCoworkSession = payload.session || state.activeCoworkSession;
    renderCoworkDetail();
    await loadCoworkSessions();
  } catch (error) {
    setCoworkError(error.message || String(error));
  } finally {
    state.coworkPendingAction = "";
    if (triggerButton) {
      triggerButton.disabled = false;
    }
    renderCoworkDetail();
  }
}

async function setCoworkSessionStatus(action) {
  if (!state.activeCoworkSessionId) return;
  state.coworkPendingAction = action;
  renderCoworkDetail();
  try {
    const response = await fetch(`${state.coworkApiPath}/sessions/${encodeURIComponent(state.activeCoworkSessionId)}/${action}`, {
      method: "POST",
      headers: authHeaders(),
    });
    if (!response.ok) {
      setCoworkError(`${action} cowork failed: ${response.status}`);
      return;
    }
    const payload = await response.json();
    state.activeCoworkSession = payload.session || state.activeCoworkSession;
    renderCoworkDetail();
    await loadCoworkSessions();
  } finally {
    state.coworkPendingAction = "";
    renderCoworkDetail();
  }
}

function coworkSelectedTask() {
  const found = coworkFindSelected(state.activeCoworkSession);
  return found?.type === "task" ? found.item : null;
}

async function mutateCoworkTask(action, payload = {}) {
  const task = coworkSelectedTask();
  if (!state.activeCoworkSessionId || !task?.id) return;
  state.coworkPendingAction = action;
  renderCoworkDetail();
  try {
    const response = await fetch(
      `${state.coworkApiPath}/sessions/${encodeURIComponent(state.activeCoworkSessionId)}/tasks/${encodeURIComponent(task.id)}/${action}`,
      {
        method: "POST",
        headers: authHeaders(),
        body: JSON.stringify(payload),
      },
    );
    const responsePayload = await response.json().catch(() => ({}));
    if (!response.ok) {
      throw new Error(responsePayload.error || responsePayload.result || `${action} task failed: ${response.status}`);
    }
    state.activeCoworkSession = responsePayload.session || state.activeCoworkSession;
    renderCoworkDetail();
    await loadCoworkSessions();
  } catch (error) {
    setCoworkError(error.message || String(error));
  } finally {
    state.coworkPendingAction = "";
    renderCoworkDetail();
  }
}

async function mutateCoworkWorkUnit(workUnitId, action, payload = {}) {
  if (!state.activeCoworkSessionId || !workUnitId) return;
  state.coworkPendingAction = `work_unit_${action}`;
  renderCoworkDetail();
  try {
    const response = await fetch(
      `${state.coworkApiPath}/sessions/${encodeURIComponent(state.activeCoworkSessionId)}/work-units/${encodeURIComponent(workUnitId)}/${action}`,
      {
        method: "POST",
        headers: authHeaders(),
        body: JSON.stringify(payload),
      },
    );
    const responsePayload = await response.json().catch(() => ({}));
    if (!response.ok) {
      throw new Error(responsePayload.error || responsePayload.result || `${action} work unit failed: ${response.status}`);
    }
    state.activeCoworkSession = responsePayload.session || state.activeCoworkSession;
    renderCoworkDetail();
    await loadCoworkSessions();
  } finally {
    state.coworkPendingAction = "";
    renderCoworkDetail();
  }
}

async function assignSelectedCoworkTask() {
  const assignedAgentId = elements.coworkTaskAgent?.value || "";
  if (!assignedAgentId) {
    setCoworkError("Choose an agent before assigning.");
    return;
  }
  await mutateCoworkTask("assign", { assigned_agent_id: assignedAgentId });
}

async function retrySelectedCoworkTask() {
  await mutateCoworkTask("retry");
}

async function requestSelectedCoworkTaskReview() {
  await mutateCoworkTask("review");
}

async function deleteCoworkSession() {
  if (!state.activeCoworkSessionId) return;
  const sessionId = state.activeCoworkSessionId;
  const response = await fetch(`${state.coworkApiPath}/sessions/${encodeURIComponent(sessionId)}`, {
    method: "DELETE",
    headers: authHeaders(),
  });
  if (!response.ok) {
    setCoworkError(`delete cowork failed: ${response.status}`);
    return;
  }
  state.activeCoworkSession = null;
  state.activeCoworkSessionId = "";
  state.activeCoworkThreadId = "";
  await loadCoworkSessions();
  if (state.coworkSessions.length) {
    await loadCoworkSession(state.coworkSessions[0].id);
  } else {
    renderCoworkDetail();
  }
}

async function sendCoworkMessage() {
  if (!state.activeCoworkSessionId) return;
  const content = elements.coworkGoalInput?.value.trim() || elements.coworkMessageInput?.value.trim() || "";
  if (!content) return;
  if (elements.coworkStartButton) {
    elements.coworkStartButton.disabled = true;
  }
  if (elements.coworkMessageButton) {
    elements.coworkMessageButton.disabled = true;
  }
  try {
    const response = await fetch(`${state.coworkApiPath}/sessions/${encodeURIComponent(state.activeCoworkSessionId)}/messages`, {
      method: "POST",
      headers: authHeaders(),
      body: JSON.stringify({ content, recipient_ids: [] }),
    });
    if (!response.ok) {
      setCoworkError(`send message failed: ${response.status}`);
      return;
    }
    const payload = await response.json();
    state.activeCoworkSession = payload.session || state.activeCoworkSession;
    if (elements.coworkGoalInput) {
      elements.coworkGoalInput.value = "";
    }
    if (elements.coworkMessageInput) {
      elements.coworkMessageInput.value = "";
    }
    renderCoworkDetail();
    await runCoworkRound();
  } finally {
    if (elements.coworkStartButton) {
      elements.coworkStartButton.disabled = false;
    }
    if (elements.coworkMessageButton) {
      elements.coworkMessageButton.disabled = false;
    }
  }
}

async function addCoworkTask() {
  if (!state.activeCoworkSessionId) return;
  const title = elements.coworkTaskTitle?.value.trim() || "";
  const assigned = elements.coworkTaskAgent?.value || "";
  if (!title || !assigned) return;
  if (elements.coworkTaskButton) {
    elements.coworkTaskButton.disabled = true;
  }
  try {
    const response = await fetch(`${state.coworkApiPath}/sessions/${encodeURIComponent(state.activeCoworkSessionId)}/tasks`, {
      method: "POST",
      headers: authHeaders(),
      body: JSON.stringify({ title, assigned_agent_id: assigned }),
    });
    if (!response.ok) {
      setCoworkError(`add task failed: ${response.status}`);
      return;
    }
    const payload = await response.json();
    state.activeCoworkSession = payload.session || state.activeCoworkSession;
    if (elements.coworkTaskTitle) {
      elements.coworkTaskTitle.value = "";
    }
    renderCoworkDetail();
    await runCoworkRound();
  } finally {
    if (elements.coworkTaskButton) {
      elements.coworkTaskButton.disabled = false;
    }
  }
}

async function showCoworkSummary() {
  if (!state.activeCoworkSessionId) return;
  const response = await fetch(`${state.coworkApiPath}/sessions/${encodeURIComponent(state.activeCoworkSessionId)}/summary`, {
    headers: { Authorization: `Bearer ${state.token}` },
  });
  if (!response.ok) {
    setCoworkError(`load summary failed: ${response.status}`);
    return;
  }
  const payload = await response.json();
  showSimpleModal("Cowork summary", payload.summary || "");
}

async function clearSession(sessionKey) {
  const response = await fetch(`${state.sessionsPath}/${encodeURIComponent(sessionKey)}/clear`, {
    method: "POST",
    headers: authHeaders(),
  });
  if (!response.ok) {
    throw new Error(`clear session failed: ${response.status}`);
  }
  state.messages.set(sessionKey, []);
  state.sessionFiles.set(sessionKey, []);
  state.respondingSessionKeys.delete(sessionKey);
  state.pendingApprovals = [];
  closeInspectionMode();
  updateActiveChatTitle();
  renderSessions();
  renderMessages();
  renderSessionFiles();
  renderApprovalPanel();
  setEditorStatus(t("status.cleared"), "connected");
}

async function deleteSession(sessionKey, chatId) {
  const response = await fetch(`${state.sessionsPath}/${encodeURIComponent(sessionKey)}`, {
    method: "DELETE",
    headers: authHeaders(),
  });
  if (!response.ok) {
    throw new Error(`delete session failed: ${response.status}`);
  }

  // Remove from local state
  state.messages.delete(sessionKey);
  state.sessionFiles.delete(sessionKey);
  state.respondingSessionKeys.delete(sessionKey);
  state.sessionItems = state.sessionItems.filter((item) => item.key !== sessionKey);

  // If deleted session was active, switch to another
  if (state.activeSessionKey === sessionKey) {
    if (state.sessionItems.length > 0) {
      await attachSession(state.sessionItems[0].chat_id);
    } else {
      state.activeChatId = "";
      state.activeSessionKey = "";
      updateActiveChatTitle();
    }
  }

  renderSessions();
  renderMessages();
}

async function loadEditableFiles() {
  const response = await fetch(state.workspaceFilesPath, {
    headers: { Authorization: `Bearer ${state.token}` },
  });
  if (!response.ok) {
    throw new Error(`load files failed: ${response.status}`);
  }
  const payload = await response.json();
  state.editableFiles = payload.items || [];
  if (!state.activeFilePath && state.editableFiles.length > 0) {
    state.activeFilePath = state.editableFiles[0].path;
  }
  renderEditableFiles();
  if (state.activeFilePath) {
    await loadFile(state.activeFilePath);
  }
}

async function loadSystemStatus() {
  const response = await fetch("/api/status", {
    headers: { Authorization: `Bearer ${state.token}` },
  });
  if (!response.ok) {
    throw new Error(`load status failed: ${response.status}`);
  }
  const payload = await response.json();

  // Update status display
  if (payload.provider && payload.provider.name) {
    elements.statusProvider.textContent = payload.provider.profile
      ? `${payload.provider.name} / ${payload.provider.profile}`
      : payload.provider.name;
  } else {
    elements.statusProvider.textContent = t("status.notConfigured");
  }

  if (payload.model) {
    elements.statusModel.textContent = payload.model;
  } else {
    elements.statusModel.textContent = t("status.notConfigured");
  }

  if (payload.channels && payload.channels.websocket) {
    const wsChannel = payload.channels.websocket;
    elements.statusChannel.textContent = wsChannel.running ? t("status.running") : t("status.stopped");
    elements.statusChannel.className = wsChannel.running ? "status-value status-ok" : "status-value status-warn";
  }
}

async function loadTools() {
  const response = await fetch("/api/tools", {
    headers: { Authorization: `Bearer ${state.token}` },
  });
  if (!response.ok) {
    throw new Error(`load tools failed: ${response.status}`);
  }
  const payload = await response.json();
  state.tools = payload.tools || [];
  renderTools();
  // If tools modal is open, update the list
  if (elements.toolsModal && elements.toolsModal.classList.contains("active")) {
    renderToolsModalList();
  }
}

async function loadSkills() {
  const response = await fetch("/api/skills", {
    headers: { Authorization: `Bearer ${state.token}` },
  });
  if (!response.ok) {
    throw new Error(`load skills failed: ${response.status}`);
  }
  const payload = await response.json();
  state.skills = payload.skills || [];
  renderSkills();
  // If skills modal is open, update the list
  if (elements.skillsModal && elements.skillsModal.classList.contains("active")) {
    renderSkillsModalList();
  }
}

function renderTools() {
  // 只更新统计信息
  elements.toolsCount.textContent = state.tools.length;
}

function toolDisplayName(toolName) {
  const names = {
    read_file: t("tool.display.readFile"),
    write_file: t("tool.display.writeFile"),
    exec: t("tool.display.exec"),
    spawn: t("tool.display.spawn"),
    cron: t("tool.display.cron"),
  };
  return names[toolName] || toolName;
}

function toolRiskHint(toolName) {
  if (["write_file", "exec"].includes(toolName)) {
    return t("tool.risk.modifyFiles");
  }
  if (["spawn", "cron"].includes(toolName)) {
    return t("tool.risk.background");
  }
  return "";
}

function renderSkills() {
  // 只更新统计信息
  const enabledSkills = state.config?.skills?.enabled || null;
  const isAllEnabled = !enabledSkills || enabledSkills.includes("*");

  let enabledCount = 0;
  for (const skill of state.skills) {
    if (skill.available && skill.always) {
      enabledCount++;
    } else if (skill.available) {
      if (isAllEnabled || enabledSkills.includes(skill.name)) {
        enabledCount++;
      }
    }
  }

  elements.skillsCount.textContent = state.skills.length;
  elements.skillsEnabledCount.textContent = enabledCount;
}

// 弹窗内渲染工具列表
function renderToolsModalList() {
  elements.toolsModalList.textContent = "";

  if (state.tools.length === 0) {
    const empty = document.createElement("div");
    empty.className = "empty-state";
    empty.textContent = t("msg.noTools");
    elements.toolsModalList.append(empty);
    return;
  }

  for (const tool of state.tools) {
    const item = document.createElement("div");
    item.className = "modal-list-item";

    const name = document.createElement("span");
    name.className = "modal-list-item-name";
    name.textContent = toolDisplayName(tool.name);
    name.title = tool.name;

    const desc = document.createElement("span");
    desc.className = "modal-list-item-desc";
    desc.textContent = tool.description || t("msg.noDescription");

    const riskHint = toolRiskHint(tool.name);
    if (riskHint) {
      const badge = document.createElement("span");
      badge.className = "tool-risk-badge";
      badge.textContent = riskHint;
      desc.append(" ");
      desc.append(badge);
    }

    item.append(name, desc);
    item.addEventListener("click", () => viewTool(tool.name));
    elements.toolsModalList.append(item);
  }
}

// 弹窗内渲染技能列表
function renderSkillsModalList() {
  elements.skillsModalList.textContent = "";

  if (state.skills.length === 0) {
    const empty = document.createElement("div");
    empty.className = "empty-state";
    empty.textContent = t("msg.noSkills");
    elements.skillsModalList.append(empty);
    return;
  }

  const enabledSkills = state.config?.skills?.enabled || null;
  const isAllEnabled = !enabledSkills || enabledSkills.includes("*");

  for (const skill of state.skills) {
    const item = document.createElement("div");
    item.className = "modal-list-item";

    // Name
    const name = document.createElement("span");
    name.className = "modal-list-item-name";
    name.textContent = skill.name;
    name.style.cursor = "pointer";
    name.addEventListener("click", () => viewSkill(skill.name));

    // Toggle section
    const toggleSection = document.createElement("div");
    toggleSection.className = "skill-toggle-section";

    const toggleSwitch = document.createElement("div");
    toggleSwitch.className = "toggle-switch";

    if (!skill.available) {
      toggleSwitch.classList.add("toggle-unavailable");
      toggleSwitch.innerHTML = `<span class="toggle-label">${t("status.unavailable")}</span>`;
    } else if (skill.always) {
      toggleSwitch.classList.add("toggle-always", "toggle-on");
      toggleSwitch.innerHTML = `<span class="toggle-label">${t("status.always")}</span>`;
    } else {
      const isEnabled = isAllEnabled || enabledSkills.includes(skill.name);
      toggleSwitch.classList.add(isEnabled ? "toggle-on" : "toggle-off");
      toggleSwitch.classList.add("toggle-clickable");
      toggleSwitch.innerHTML = `<span class="toggle-slider"></span>`;
      toggleSwitch.title = isEnabled ? t("ui.clickToDisable") : t("ui.clickToEnable");
      toggleSwitch.addEventListener("click", () => toggleSkill(skill.name, !isEnabled));
    }

    // Delete button for workspace skills
    if (skill.source === "workspace") {
      const deleteBtn = document.createElement("button");
      deleteBtn.className = "skill-delete-btn";
      deleteBtn.textContent = "×";
      deleteBtn.title = t("ui.delete");
      deleteBtn.addEventListener("click", () => deleteSkill(skill.name));
      toggleSection.append(deleteBtn);
    }

    toggleSection.append(toggleSwitch);
    item.append(name, toggleSection);
    elements.skillsModalList.append(item);
  }
}

function viewTool(toolName) {
  const tool = state.tools.find(t => t.name === toolName);
  if (!tool) return;

  elements.toolModalTitle.textContent = toolDisplayName(toolName);
  elements.toolModalName.textContent = `${toolDisplayName(toolName)} (${toolName})`;
  elements.toolModalDesc.textContent = tool.description || t("msg.noDescription");

  // Display schema/parameters
  if (tool.parameters) {
    try {
      elements.toolModalSchema.textContent = JSON.stringify(tool.parameters, null, 2);
    } catch {
      elements.toolModalSchema.textContent = tool.parameters;
    }
  } else {
    elements.toolModalSchema.textContent = t("msg.noParameters") || "无参数定义";
  }

  elements.toolModal.classList.add("active");
}

function closeToolModal() {
  elements.toolModal.classList.remove("active");
}

// 打开工具列表弹窗
function openSidebarDrawer(modal, trigger) {
  if (!modal) return;
  window.clearTimeout(Number(modal.dataset.closeTimer || 0));
  modal.dataset.closeTimer = "";
  modal.classList.add("sidebar-drawer-modal");
  modal.classList.remove("closing");
  modal.classList.add("active", "opening");
  window.requestAnimationFrame(() => {
    window.requestAnimationFrame(() => {
      modal.classList.remove("opening");
    });
  });
  trigger?.setAttribute("aria-expanded", "true");
}

function closeSidebarDrawer(modal, trigger) {
  if (!modal || !modal.classList.contains("active")) return;
  window.clearTimeout(Number(modal.dataset.closeTimer || 0));
  modal.classList.add("closing");
  trigger?.setAttribute("aria-expanded", "false");
  const finish = () => {
    modal.classList.remove("active", "closing", "opening");
    modal.dataset.closeTimer = "";
  };
  modal.dataset.closeTimer = String(window.setTimeout(finish, SIDEBAR_DRAWER_CLOSE_MS));
}

function openToolsModal() {
  renderToolsModalList();

  // 检查是否有工具未启用，显示配置提示
  const tools = state.config?.tools || {};
  const webEnabled = tools.web?.enable === true;
  const execEnabled = tools.exec?.enable === true;
  // 如果Web或Exec未启用，显示提示
  const showHint = !webEnabled || !execEnabled;
  const configHint = document.getElementById("tools-config-hint");
  if (configHint) {
    configHint.style.display = showHint ? "flex" : "none";
  }

  openSidebarDrawer(elements.toolsModal, elements.toolsToggle);
}

function closeToolsModal() {
  closeSidebarDrawer(elements.toolsModal, elements.toolsToggle);
}

// 打开技能列表弹窗
function openSkillsModal() {
  renderSkillsModalList();
  openSidebarDrawer(elements.skillsModal, elements.skillsToggle);
}

function closeSkillsModal() {
  closeSidebarDrawer(elements.skillsModal, elements.skillsToggle);
}

async function toggleSkill(skillName, enable) {
  try {
    // Get current enabled skills
    let currentEnabled = state.config?.skills?.enabled || [];
    const isAllEnabled = !currentEnabled || currentEnabled.includes("*");

    // Build new enabled list
    let newEnabled;
    if (enable) {
      // Enable this skill
      if (isAllEnabled) {
        // If all enabled, keep "*"
        newEnabled = ["*"];
      } else {
        // Add skill to list if not already there
        newEnabled = currentEnabled.includes(skillName) ? currentEnabled : [...currentEnabled, skillName];
      }
    } else {
      // Disable this skill
      if (isAllEnabled) {
        // If all enabled, we need to build explicit list excluding this skill
        const allSkillNames = state.skills
          .filter(s => s.available && s.name !== skillName && !s.always)
          .map(s => s.name);
        newEnabled = allSkillNames;
      } else {
        // Remove skill from list
        newEnabled = currentEnabled.filter(s => s !== skillName);
      }
    }

    // Send PATCH request
    const response = await fetch("/api/config", {
      method: "PATCH",
      headers: authHeaders(),
      body: JSON.stringify({ skills: { enabled: newEnabled } }),
    });

    if (!response.ok) {
      const errorData = await response.json();
      throw new Error(errorData.error || `${t("settings.saveFailed")}: ${response.status}`);
    }

    const result = await response.json();
    state.config = result.config || state.config;
    // Update skills config in state
    if (state.config && result.config?.skills) {
      state.config.skills = result.config.skills;
    }

    // Reload skills to refresh enabled status
    await loadSkills();
  } catch (error) {
    console.error(error);
    setError(error.message || t("status.failed"));
  }
}

// ============ Skill Modal Functions ============

function openSkillModal() {
  elements.skillModal.classList.add("active");
  elements.skillError.textContent = "";
  elements.skillSuccess.textContent = "";
  elements.skillValidationResult.textContent = "";
  elements.skillValidationResult.className = "skill-validation-result";
}

function closeSkillModal() {
  elements.skillModal.classList.remove("active");
  state.activeSkill = null;
  state.skillMode = "view";
}

async function viewSkill(skillName) {
  try {
    const response = await fetch(`${state.skillsApiPath}/${encodeURIComponent(skillName)}`, {
      headers: { Authorization: `Bearer ${state.token}` },
    });
    if (!response.ok) {
      throw new Error(`load skill failed: ${response.status}`);
    }

    const payload = await response.json();
    state.activeSkill = payload;
    state.skillMode = "edit";

    // Populate modal
    elements.skillModalTitle.textContent = payload.name;
    elements.skillNameInput.value = payload.name;
    elements.skillNameInput.disabled = true;  // Can't rename existing skill
    elements.skillDescInput.value = payload.tinybot_meta?.description || payload.metadata?.description || "";
    elements.skillAlwaysCheckbox.checked = payload.tinybot_meta?.always || payload.metadata?.always || false;
    // Get source from state.skills (API doesn't return source in detail endpoint)
    const skillInfo = state.skills.find(s => s.name === skillName);
    elements.skillSourceDisplay.textContent = skillInfo?.source || "unknown";
    elements.skillContentEditor.value = payload.content || "";

    // Show/hide delete button based on source
    elements.skillDeleteButton.style.display = skillInfo?.source === "workspace" ? "inline-block" : "none";

    openSkillModal();
  } catch (error) {
    console.error(error);
    setError(error.message || t("status.failed"));
  }
}

async function createNewSkill() {
  state.activeSkill = null;
  state.skillMode = "create";

  elements.skillModalTitle.textContent = t("skill.new");
  elements.skillNameInput.value = "";
  elements.skillNameInput.disabled = false;
  elements.skillDescInput.value = "";
  elements.skillAlwaysCheckbox.checked = false;
  elements.skillSourceDisplay.textContent = t("skill.workspaceNew");
  elements.skillContentEditor.value = "";
  elements.skillDeleteButton.style.display = "none";

  openSkillModal();
}

async function saveSkill() {
  elements.skillError.textContent = "";
  elements.skillSuccess.textContent = "";

  const name = elements.skillNameInput.value.trim();
  const description = elements.skillDescInput.value.trim();
  const always = elements.skillAlwaysCheckbox.checked;
  const content = elements.skillContentEditor.value;

  if (!name) {
    elements.skillError.textContent = t("skill.nameRequired");
    return;
  }

  try {
    if (state.skillMode === "create") {
      // Create new skill
      const response = await fetch(state.skillsApiPath, {
        method: "POST",
        headers: authHeaders(),
        body: JSON.stringify({ name, description, content, always }),
      });

      if (!response.ok) {
        const errorData = await response.json();
        throw new Error(errorData.error || t("skill.createFailed"));
      }

      const result = await response.json();
      elements.skillSuccess.textContent = t("skill.created");
      state.activeSkill = { name: result.name };
      state.skillMode = "edit";
      elements.skillNameInput.disabled = true;
      elements.skillDeleteButton.style.display = "inline-block";
    } else {
      // Update existing skill
      const response = await fetch(`${state.skillsApiPath}/${encodeURIComponent(state.activeSkill.name)}`, {
        method: "PATCH",
        headers: authHeaders(),
        body: JSON.stringify({ description, content, always }),
      });

      if (!response.ok) {
        const errorData = await response.json();
        throw new Error(errorData.error || t("skill.updateFailed"));
      }

      elements.skillSuccess.textContent = t("skill.updated");
    }

    // Reload skills list
    await loadSkills();
  } catch (error) {
    console.error(error);
    elements.skillError.textContent = error.message || t("status.failed");
  }
}

async function deleteSkill(skillName) {
  if (!confirm(`${t("ui.confirmDelete")} ${skillName}?`)) {
    return;
  }

  try {
    const response = await fetch(`${state.skillsApiPath}/${encodeURIComponent(skillName)}`, {
      method: "DELETE",
      headers: authHeaders(),
    });

    if (!response.ok) {
      const errorData = await response.json();
      throw new Error(errorData.error || t("skill.deleteFailed"));
    }

    // Close modal if deleting the active skill
    if (state.activeSkill?.name === skillName) {
      closeSkillModal();
    }

    // Reload skills list
    await loadSkills();
  } catch (error) {
    console.error(error);
    if (state.activeSkill?.name === skillName) {
      elements.skillError.textContent = error.message || t("status.failed");
    } else {
      setError(error.message || t("status.failed"));
    }
  }
}

async function validateSkill() {
  if (!state.activeSkill?.name) {
    elements.skillValidationResult.textContent = t("skill.saveFirst");
    elements.skillValidationResult.className = "skill-validation-result invalid";
    return;
  }

  try {
    const response = await fetch(`${state.skillsApiPath}/${encodeURIComponent(state.activeSkill.name)}/validate`, {
      method: "POST",
      headers: authHeaders(),
    });

    if (!response.ok) {
      const errorData = await response.json();
      throw new Error(errorData.error || t("skill.validateFailed"));
    }

    const result = await response.json();
    if (result.valid) {
      elements.skillValidationResult.textContent = `✓ ${t("skill.valid")}`;
      elements.skillValidationResult.className = "skill-validation-result valid";
    } else {
      elements.skillValidationResult.textContent = `✗ ${result.message || t("skill.invalid")}`;
      elements.skillValidationResult.className = "skill-validation-result invalid";
    }
  } catch (error) {
    console.error(error);
    elements.skillValidationResult.textContent = error.message || t("status.failed");
    elements.skillValidationResult.className = "skill-validation-result invalid";
  }
}

// ============ Knowledge Functions ============

async function loadKnowledgeStats() {
  try {
    const response = await fetch(`${state.knowledgeApiPath}/stats`, {
      cache: "no-store",
      headers: { Authorization: `Bearer ${state.token}` },
    });
    if (!response.ok) {
      if (response.status === 503) {
        // Knowledge store not initialized
        elements.statsDocs.textContent = "-";
        elements.statsChunks.textContent = "-";
        if (elements.modalStatsDocs) elements.modalStatsDocs.textContent = "-";
        if (elements.modalStatsChunks) elements.modalStatsChunks.textContent = "-";
        if (elements.modalStatsEntities) elements.modalStatsEntities.textContent = "-";
        if (elements.modalStatsClaims) elements.modalStatsClaims.textContent = "-";
        if (elements.modalStatsRelations) elements.modalStatsRelations.textContent = "-";
        if (elements.modalStatsCommunities) elements.modalStatsCommunities.textContent = "-";
        if (elements.modalStatsReports) elements.modalStatsReports.textContent = "-";
        return;
      }
      throw new Error(`load knowledge stats failed: ${response.status}`);
    }

    const contentType = response.headers.get("content-type") || "";
    if (!contentType.includes("application/json")) {
      throw new Error("knowledge stats API returned non-JSON response");
    }
    const payload = await response.json();
    state.knowledgeStats = payload;
    elements.statsDocs.textContent = payload.total_documents || 0;
    elements.statsChunks.textContent = payload.total_chunks || 0;
    if (elements.modalStatsDocs) elements.modalStatsDocs.textContent = payload.total_documents || 0;
    if (elements.modalStatsChunks) elements.modalStatsChunks.textContent = payload.total_chunks || 0;
    if (elements.modalStatsEntities) elements.modalStatsEntities.textContent = payload.entity_count || 0;
    if (elements.modalStatsClaims) elements.modalStatsClaims.textContent = payload.claim_count || 0;
    if (elements.modalStatsRelations) elements.modalStatsRelations.textContent = payload.relation_count || 0;
    if (elements.modalStatsCommunities) elements.modalStatsCommunities.textContent = payload.community_count || 0;
    if (elements.modalStatsReports) elements.modalStatsReports.textContent = payload.community_report_count || 0;
    renderKnowledgeOverview();
  } catch (error) {
    console.error(error);
    elements.statsDocs.textContent = "-";
    elements.statsChunks.textContent = "-";
    if (elements.modalStatsDocs) elements.modalStatsDocs.textContent = "-";
    if (elements.modalStatsChunks) elements.modalStatsChunks.textContent = "-";
    if (elements.modalStatsEntities) elements.modalStatsEntities.textContent = "-";
    if (elements.modalStatsClaims) elements.modalStatsClaims.textContent = "-";
    if (elements.modalStatsRelations) elements.modalStatsRelations.textContent = "-";
    if (elements.modalStatsCommunities) elements.modalStatsCommunities.textContent = "-";
    if (elements.modalStatsReports) elements.modalStatsReports.textContent = "-";
  }
}

function renderKnowledgeOverview() {
  if (!state.knowledgeWorkbenchReady) {
    return;
  }
  if (state.knowledgeRebuilding) {
    renderKnowledgeRebuildPlaceholder();
    return;
  }
  const stats = state.knowledgeStats || {};
  const docs = Number(stats.total_documents || 0);
  const chunks = Number(stats.total_chunks || 0);
  const entities = Number(stats.entity_count || 0);
  const relations = Number(stats.relation_count || 0);
  const communities = Number(stats.community_count || 0);
  const reports = Number(stats.community_report_count || 0);
  const communityLevels = stats.community_count_by_level || stats.communityCountByLevel || {};
  const communityLevelText = Object.entries(communityLevels)
    .sort(([left], [right]) => Number(left) - Number(right))
    .map(([level, count]) => t("knowledge.graphLevelCount").replace("{level}", level).replace("{count}", count))
    .join(", ");
  const indexedDense = Number(stats.indexed_dense || 0);
  const indexedSparse = Number(stats.indexed_sparse || 0);

  const checks = [
    docs > 0,
    chunks > 0,
    indexedDense > 0 || indexedSparse > 0,
    entities > 0,
    relations > 0,
    communities > 0,
    reports > 0,
  ];
  const score = Math.round((checks.filter(Boolean).length / checks.length) * 100);
  if (elements.knowledgeHealthScore) elements.knowledgeHealthScore.textContent = `${score}%`;
  if (elements.knowledgeHealthBar) elements.knowledgeHealthBar.style.width = `${score}%`;
  if (elements.knowledgeHealthTitle) {
    elements.knowledgeHealthTitle.textContent = score >= 85
      ? t("knowledge.healthReady")
      : score >= 55
        ? t("knowledge.healthSearchable")
        : docs > 0
          ? t("knowledge.healthNeedsSemantic")
          : t("knowledge.healthEmpty");
  }
  if (elements.knowledgeHealthDesc) {
    elements.knowledgeHealthDesc.textContent = docs
      ? t("knowledge.healthDesc")
        .replace("{docs}", docs)
        .replace("{chunks}", chunks)
        .replace("{entities}", entities)
        .replace("{relations}", relations)
        .replace("{communities}", communities)
      : t("knowledge.healthDescEmpty");
  }

  if (!elements.knowledgeOverviewInsights) {
    return;
  }
  elements.knowledgeOverviewInsights.textContent = "";
  const insights = [
    {
      title: t("knowledge.insightRetrievalIndex"),
      text: indexedDense || indexedSparse
        ? t("knowledge.insightRetrievalReady").replace("{dense}", indexedDense).replace("{sparse}", indexedSparse)
        : t("knowledge.insightRetrievalEmpty"),
    },
    {
      title: t("knowledge.insightSemanticModel"),
      text: entities
        ? t("knowledge.insightSemanticReady").replace("{entities}", entities).replace("{relations}", relations)
        : t("knowledge.insightSemanticEmpty"),
    },
    {
      title: t("knowledge.insightGraphRagLayer"),
      text: communities
        ? t("knowledge.insightGraphRagReady")
          .replace("{communities}", communities)
          .replace("{levels}", communityLevelText ? ` (${communityLevelText})` : "")
          .replace("{reports}", reports)
        : t("knowledge.insightGraphRagEmpty"),
    },
  ];
  for (const insight of insights) {
    const item = document.createElement("div");
    item.className = "knowledge-insight-item";
    item.innerHTML = `<strong>${escapeHtml(insight.title)}</strong><span>${escapeHtml(insight.text)}</span>`;
    elements.knowledgeOverviewInsights.append(item);
  }
}

async function loadKnowledgeDocs() {
  try {
    const response = await fetch(`${state.knowledgeApiPath}/documents`, {
      cache: "no-store",
      headers: { Authorization: `Bearer ${state.token}` },
    });
    if (!response.ok) {
      if (response.status === 503) {
        // Knowledge store not initialized
        elements.docsList.textContent = "";
        const empty = document.createElement("div");
        empty.className = "empty-state";
        empty.textContent = t("status.unavailable");
        elements.docsList.append(empty);
        return;
      }
      throw new Error(`load knowledge docs failed: ${response.status}`);
    }

    const contentType = response.headers.get("content-type") || "";
    if (!contentType.includes("application/json")) {
      throw new Error("knowledge API returned non-JSON response");
    }
    const payload = await response.json();
    state.knowledgeDocs = payload.data || [];
    renderKnowledgeDocs();
  } catch (error) {
    console.error(error);
    elements.docsList.textContent = "";
    const empty = document.createElement("div");
    empty.className = "empty-state";
    empty.textContent = t("status.loadFailed");
    elements.docsList.append(empty);
  }
}

function formatKnowledgeJobMessage(job, fallback = "") {
  if (!job) {
    return fallback || t("knowledge.indexingDesc");
  }
  const processed = Number(job.processed || 0);
  const total = Math.max(1, Number(job.total || 1));
  const percent = Math.min(100, Math.max(0, Math.round((processed / total) * 100)));
  const base = job.message || fallback || t("knowledge.indexingDesc");
  if (job.status === "failed") {
    return job.error ? `${base}: ${job.error}` : base;
  }
  if (job.status === "completed") {
    return t("knowledge.indexingDone");
  }
  return `${base} (${percent}%)`;
}

function setKnowledgeIndexingState(active, message = "", job = null) {
  state.knowledgeIndexing = active;
  if (elements.knowledgeIndexingStatus) {
    elements.knowledgeIndexingStatus.hidden = true;
  }
  setGlobalKnowledgeToast(active, message, job);

  const disabledButtons = [
    elements.rebuildIndexButton,
    elements.uploadDocButton,
    elements.docSaveButton,
  ];
  for (const button of disabledButtons) {
    if (button) {
      button.disabled = active;
      button.setAttribute("aria-busy", active ? "true" : "false");
    }
  }
}

function ensureGlobalKnowledgeToast() {
  if (elements.globalKnowledgeToast) {
    return;
  }
  const toast = document.createElement("div");
  toast.id = "global-knowledge-toast";
  toast.className = "global-knowledge-toast";
  toast.setAttribute("role", "status");
  toast.setAttribute("aria-live", "polite");
  toast.hidden = true;
  toast.innerHTML = `
    <div class="global-knowledge-spinner" aria-hidden="true"></div>
    <div class="global-knowledge-copy">
      <div id="global-knowledge-toast-title" class="global-knowledge-title">${escapeHtml(t("knowledge.indexingTitle"))}</div>
      <div id="global-knowledge-toast-desc" class="global-knowledge-desc"></div>
      <div class="global-knowledge-progress" aria-hidden="true">
        <div id="global-knowledge-progress-bar" class="global-knowledge-progress-bar"></div>
      </div>
    </div>
  `;
  document.body.append(toast);
  elements.globalKnowledgeToast = toast;
  elements.globalKnowledgeToastTitle = toast.querySelector("#global-knowledge-toast-title");
  elements.globalKnowledgeToastDesc = toast.querySelector("#global-knowledge-toast-desc");
  elements.globalKnowledgeProgressBar = toast.querySelector("#global-knowledge-progress-bar");
}

function setGlobalKnowledgeToast(active, message = "", job = null) {
  ensureGlobalKnowledgeToast();
  const toast = elements.globalKnowledgeToast;
  if (!toast) {
    return;
  }
  if (active) {
    if (elements.globalKnowledgeToastTitle) {
      elements.globalKnowledgeToastTitle.textContent = t("knowledge.indexingTitle");
    }
    if (elements.globalKnowledgeToastDesc) {
      elements.globalKnowledgeToastDesc.textContent = formatKnowledgeJobMessage(job, message);
    }
    if (elements.globalKnowledgeProgressBar) {
      const processed = Number(job?.processed || 0);
      const total = Math.max(1, Number(job?.total || 1));
      const percent = job ? Math.min(100, Math.max(0, Math.round((processed / total) * 100))) : 8;
      elements.globalKnowledgeProgressBar.style.width = `${percent}%`;
    }
    toast.hidden = false;
    requestAnimationFrame(() => toast.classList.add("active"));
    return;
  }
  toast.classList.remove("active");
  window.setTimeout(() => {
    if (!state.knowledgeIndexing) {
      toast.hidden = true;
    }
  }, 240);
}

function stopKnowledgeJobPolling() {
  if (state.knowledgeJobPollTimer) {
    window.clearTimeout(state.knowledgeJobPollTimer);
    state.knowledgeJobPollTimer = null;
  }
  state.activeKnowledgeJobId = "";
}

function scheduleKnowledgeJobPoll(jobId, message = "") {
  stopKnowledgeJobPolling();
  if (!jobId) {
    return;
  }
  state.activeKnowledgeJobId = jobId;

  const poll = async () => {
    try {
      const response = await fetch(`${state.knowledgeApiPath}/jobs/${encodeURIComponent(jobId)}`, {
        cache: "no-store",
        headers: { Authorization: `Bearer ${state.token}` },
      });
      if (!response.ok) {
        throw new Error(`load knowledge job failed: ${response.status}`);
      }
      const job = await response.json();
      setKnowledgeIndexingState(job.status !== "completed" && job.status !== "failed", message, job);
      if (job.status === "completed") {
        stopKnowledgeJobPolling();
        state.knowledgeRebuilding = false;
        elements.docSuccess.textContent = t("knowledge.indexingDone");
        await loadKnowledgeStats();
        await loadKnowledgeDocs();
        await loadKnowledgeGraph();
        return;
      }
      if (job.status === "failed") {
        stopKnowledgeJobPolling();
        state.knowledgeRebuilding = false;
        elements.docError.textContent = formatKnowledgeJobMessage(job, t("knowledge.indexingFailed"));
        await loadKnowledgeStats();
        await loadKnowledgeGraph();
        return;
      }
      state.knowledgeJobPollTimer = window.setTimeout(poll, 1200);
    } catch (error) {
      console.error(error);
      state.knowledgeJobPollTimer = window.setTimeout(poll, 2500);
    }
  };

  poll();
}

function setupKnowledgeWorkbench() {
  if (state.knowledgeWorkbenchReady || !elements.knowledgeModal) {
    return;
  }

  const body = elements.knowledgeModal.querySelector(".modal-body");
  const stats = body?.querySelector(".knowledge-stats");
  const graphPanel = body?.querySelector(".knowledge-graph-panel");
  const docsPanel = body?.querySelector(".knowledge-docs-panel");
  const queryPanel = body?.querySelector(".knowledge-query-panel");
  if (!body || !stats || !graphPanel || !docsPanel || !queryPanel) {
    return;
  }

  const tabs = document.createElement("div");
  tabs.className = "knowledge-tabs";
  tabs.setAttribute("role", "tablist");
  tabs.setAttribute("aria-label", t("knowledge.views"));
  const tabItems = [
    ["overview", t("knowledge.overview")],
    ["graph", t("knowledge.graph")],
    ["documents", t("knowledge.documents")],
    ["query", t("knowledge.query")],
  ];
  for (const [key, label] of tabItems) {
    const button = document.createElement("button");
    button.className = `knowledge-tab${key === state.activeKnowledgeTab ? " active" : ""}`;
    button.type = "button";
    button.dataset.knowledgeTab = key;
    button.setAttribute("role", "tab");
    button.setAttribute("aria-selected", key === state.activeKnowledgeTab ? "true" : "false");
    button.textContent = label;
    button.addEventListener("click", () => setKnowledgeTab(key));
    tabs.append(button);
  }

  const panels = document.createElement("div");
  panels.className = "knowledge-tab-panels";

  const overviewPanel = createKnowledgeTabPanel("overview");
  const overviewGrid = document.createElement("div");
  overviewGrid.className = "knowledge-overview-grid";
  overviewGrid.innerHTML = `
    <div class="knowledge-health-card">
      <div class="knowledge-health-main">
        <div>
          <div class="knowledge-card-kicker">${escapeHtml(t("knowledge.readiness"))}</div>
          <div id="knowledge-health-title" class="knowledge-health-title">${escapeHtml(t("knowledge.healthEmpty"))}</div>
        </div>
        <div id="knowledge-health-score" class="knowledge-health-score">0%</div>
      </div>
      <div class="knowledge-health-track"><div id="knowledge-health-bar" class="knowledge-health-bar" style="width: 0%"></div></div>
      <p id="knowledge-health-desc" class="knowledge-health-desc">${escapeHtml(t("knowledge.healthDescEmpty"))}</p>
    </div>
    <div class="knowledge-insight-list" id="knowledge-overview-insights"></div>
  `;
  overviewPanel.append(stats, overviewGrid);

  const graphPanelWrap = createKnowledgeTabPanel("graph");
  const graphHeader = graphPanel.querySelector(".graph-header");
  if (graphHeader) {
    const title = graphHeader.firstElementChild;
    if (title) {
      const scope = document.createElement("span");
      scope.id = "knowledge-graph-scope";
      scope.className = "graph-scope";
      scope.textContent = t("knowledge.allKnowledge");
      title.append(scope);
      elements.knowledgeGraphScope = scope;
    }
    const actions = graphHeader.querySelector(".graph-actions");
    if (actions) {
      const levelSelect = document.createElement("select");
      levelSelect.id = "knowledge-graph-level";
      levelSelect.className = "graph-level-select";
      levelSelect.setAttribute("aria-label", t("settings.knowledge.graphRagCommunityLevel"));
      for (const level of [0, 1]) {
        const option = document.createElement("option");
        option.value = String(level);
        option.textContent = t("knowledge.graphLevel").replace("{level}", level);
        levelSelect.append(option);
      }
      levelSelect.value = String(state.knowledgeGraphLevel);
      levelSelect.addEventListener("change", () => {
        state.knowledgeGraphLevel = Number.parseInt(levelSelect.value, 10) || 0;
        state.knowledgeGraphSelection = null;
        loadKnowledgeGraph();
      });
      actions.insertBefore(levelSelect, actions.firstChild);
      elements.knowledgeGraphLevelSelect = levelSelect;

      const clear = document.createElement("button");
      clear.id = "clear-graph-filter-button";
      clear.className = "button button-ghost button-small";
      clear.type = "button";
      clear.hidden = true;
      clear.textContent = t("knowledge.clearFilter");
      clear.addEventListener("click", () => setKnowledgeGraphDocumentFilter("", ""));
      actions.insertBefore(clear, actions.firstChild);
      elements.clearGraphFilterButton = clear;
    }
  }

  const graphWorkspace = document.createElement("div");
  graphWorkspace.className = "knowledge-graph-workspace";
  const graphHost = elements.knowledgeGraph;
  const inspector = document.createElement("aside");
  inspector.id = "knowledge-graph-inspector";
  inspector.className = "knowledge-graph-inspector";
  graphWorkspace.append(graphHost, inspector);
  elements.knowledgeGraphInspector = inspector;
  graphPanel.append(graphWorkspace);
  graphPanelWrap.append(graphPanel);

  const docsPanelWrap = createKnowledgeTabPanel("documents");
  docsPanelWrap.append(docsPanel);

  const queryPanelWrap = createKnowledgeTabPanel("query");
  const queryHeader = queryPanel.querySelector(".query-header");
  if (queryHeader) {
    const hint = document.createElement("span");
    hint.id = "query-mode-hint";
    hint.className = "query-mode-hint";
    queryHeader.append(hint);
    elements.queryModeHint = hint;
  }
  queryPanelWrap.append(queryPanel);

  panels.append(overviewPanel, graphPanelWrap, docsPanelWrap, queryPanelWrap);
  body.append(tabs, panels);
  elements.knowledgeHealthTitle = document.querySelector("#knowledge-health-title");
  elements.knowledgeHealthScore = document.querySelector("#knowledge-health-score");
  elements.knowledgeHealthBar = document.querySelector("#knowledge-health-bar");
  elements.knowledgeHealthDesc = document.querySelector("#knowledge-health-desc");
  elements.knowledgeOverviewInsights = document.querySelector("#knowledge-overview-insights");
  state.knowledgeWorkbenchReady = true;
  setKnowledgeTab(state.activeKnowledgeTab);
  renderKnowledgeOverview();
  renderKnowledgeGraphInspector();
  updateQueryModeHint();
}

function createKnowledgeTabPanel(key) {
  const panel = document.createElement("section");
  panel.className = `knowledge-tab-panel${key === state.activeKnowledgeTab ? " active" : ""}`;
  panel.dataset.knowledgePanel = key;
  panel.setAttribute("role", "tabpanel");
  return panel;
}

function setKnowledgeTab(key) {
  state.activeKnowledgeTab = key;
  document.querySelectorAll(".knowledge-tab").forEach((tab) => {
    const active = tab.dataset.knowledgeTab === key;
    tab.classList.toggle("active", active);
    tab.setAttribute("aria-selected", active ? "true" : "false");
  });
  document.querySelectorAll(".knowledge-tab-panel").forEach((panel) => {
    panel.classList.toggle("active", panel.dataset.knowledgePanel === key);
  });
  if (key === "graph") {
    loadKnowledgeGraph();
  }
  if (key === "overview") {
    renderKnowledgeOverview();
  }
}

async function loadKnowledgeGraph() {
  if (!elements.knowledgeGraph) {
    return;
  }

  try {
    elements.knowledgeGraph.textContent = "";
    const loading = document.createElement("div");
    loading.className = "empty-state";
    loading.textContent = t("status.loading");
    elements.knowledgeGraph.append(loading);

    state.knowledgeGraph = await loadKnowledgeGraphPayload();
    if (!state.knowledgeGraph) {
      return;
    }
    renderKnowledgeGraph(state.knowledgeGraph);
  } catch (error) {
    console.error(error);
    renderKnowledgeGraph(null, t("status.loadFailed"));
  }
}

async function loadKnowledgeGraphPayload() {
  const docParam = state.knowledgeGraphFilterDocId
    ? `&doc_id=${encodeURIComponent(state.knowledgeGraphFilterDocId)}`
    : "";
  const levelParam = `&level=${encodeURIComponent(String(state.knowledgeGraphLevel || 0))}`;
  const graphragResponse = await fetch(`${state.knowledgeApiPath}/graphrag?min_confidence=0&include_reports=true&include_covariates=true${levelParam}${docParam}`, {
    cache: "no-store",
    headers: { Authorization: `Bearer ${state.token}` },
  });

  if (graphragResponse.ok) {
    const graphragPayload = await parseKnowledgeJsonResponse(graphragResponse, "GraphRAG index");
    const graph = normalizeGraphRagIndex(graphragPayload);
    if (graph.nodes.length || graph.edges.length) {
      return graph;
    }
  } else if (graphragResponse.status === 503) {
    renderKnowledgeGraph(null, t("status.unavailable"));
    return null;
  } else if (![404, 405].includes(graphragResponse.status)) {
    console.warn(`load GraphRAG index failed: ${graphragResponse.status}; falling back to graph API`);
  }

  const graphResponse = await fetch(`${state.knowledgeApiPath}/graph?limit=80&edge_limit=160${docParam}`, {
    cache: "no-store",
    headers: { Authorization: `Bearer ${state.token}` },
  });
  if (!graphResponse.ok) {
    if (graphResponse.status === 503) {
      renderKnowledgeGraph(null, t("status.unavailable"));
      return null;
    }
    throw new Error(`load knowledge graph failed: ${graphResponse.status}`);
  }
  return parseKnowledgeJsonResponse(graphResponse, "knowledge graph");
}

async function parseKnowledgeJsonResponse(response, label) {
  const contentType = response.headers.get("content-type") || "";
  if (!contentType.includes("application/json")) {
    throw new Error(`${label} API returned non-JSON response`);
  }
  return response.json();
}

function normalizeGraphRagIndex(index) {
  if (!index || index.object !== "graphrag_index") {
    return index || { nodes: [], edges: [], stats: {} };
  }

  const documents = new Map((index.documents || []).map((doc) => [doc.id, doc]));
  const textUnits = new Map((index.text_units || []).map((unit) => [unit.id, unit]));
  const claims = new Map((index.covariates || []).map((claim) => [claim.id, claim]));
  const reportByCommunity = new Map((index.community_reports || []).map((report) => [report.community, report]));
  const communities = (index.communities || []).map((community) => ({
    ...community,
    report: reportByCommunity.get(community.community) || null,
  }));
  const communityByEntity = new Map();
  for (const community of communities) {
    for (const entityId of community.entity_ids || []) {
      if (!communityByEntity.has(entityId)) {
        communityByEntity.set(entityId, community);
      }
    }
  }
  const entityByName = new Map();

  const nodes = (index.entities || []).map((entity) => {
    const community = communityByEntity.get(entity.id);
    const report = community?.report || null;
    const node = {
      id: entity.id,
      label: entity.title || entity.id,
      canonical_name: entity.title || entity.id,
      type: entity.type || "concept",
      aliases: entity.aliases || [],
      doc_ids: entity.doc_ids || [],
      doc_names: (entity.doc_ids || []).map((docId) => documents.get(docId)?.title || docId),
      mention_count: entity.frequency || 0,
      degree: entity.degree || 0,
      confidence: entity.confidence || 0,
      description: entity.description || "",
      community_id: community?.community ?? null,
      community_title: community?.title || "",
      community_level: community?.level ?? null,
      community_parent: community?.parent ?? null,
      community_report_id: report?.id || "",
      community_report_summary: report?.summary || "",
      score: (entity.degree || 0) + (entity.frequency || 0),
    };
    entityByName.set(node.label, node);
    return node;
  });

  const edges = (index.relationships || [])
    .map((relationship) => {
      const source = entityByName.get(relationship.source);
      const target = entityByName.get(relationship.target);
      const evidence = buildGraphRagEvidence(relationship, textUnits, documents, claims);
      return {
        id: relationship.id || `${source?.id || relationship.source}:${relationship.predicate}:${target?.id || relationship.target}`,
        source: source?.id || relationship.source,
        target: target?.id || relationship.target,
        predicate: relationship.predicate || "related_to",
        count: relationship.relation_ids?.length || Math.max(1, Math.round(relationship.weight || 1)),
        confidence: relationship.confidence || 0,
        confidence_avg: relationship.confidence || 0,
        weight: relationship.weight || 0,
        strength: relationship.strength || relationship.weight || 0,
        combined_degree: relationship.combined_degree || 0,
        text_unit_ids: relationship.text_unit_ids || [],
        community_id: source?.community_id === target?.community_id ? source?.community_id : null,
        community_title: source?.community_id === target?.community_id ? source?.community_title : "",
        evidence,
        doc_names: Array.from(new Set(evidence.map((item) => item.doc_name).filter(Boolean))),
        description: relationship.description || "",
      };
    })
    .filter((edge) => edge.source && edge.target);

  return {
    object: "knowledge_graph",
    source: "graphrag_index",
    nodes,
    edges,
    communities,
    community_reports: index.community_reports || [],
    stats: {
      ...(index.stats || {}),
      node_count: nodes.length,
      edge_count: edges.length,
      level: index.stats?.level ?? state.knowledgeGraphLevel ?? 0,
    },
  };
}

function buildGraphRagEvidence(relationship, textUnits, documents, claims) {
  const evidence = [];
  for (const textUnitId of relationship.text_unit_ids || []) {
    const textUnit = textUnits.get(textUnitId);
    const document = textUnit ? documents.get(textUnit.document_id) : null;
    const claimTexts = (textUnit?.covariate_ids || [])
      .map((claimId) => claims.get(claimId)?.source_text || claims.get(claimId)?.description || "")
      .filter(Boolean);
    evidence.push({
      relation_id: relationship.id,
      claim_id: textUnit?.covariate_ids?.[0] || "",
      chunk_id: textUnitId,
      doc_id: textUnit?.document_id || "",
      doc_name: document?.title || textUnit?.document_id || "",
      file_path: "",
      line_start: textUnit?.line_start || 0,
      line_end: textUnit?.line_end || 0,
      page: textUnit?.page || null,
      section_path: textUnit?.section_path || "",
      text: claimTexts[0] || relationship.description || textUnit?.text || "",
      confidence: relationship.confidence || 0,
    });
    if (evidence.length >= 4) {
      break;
    }
  }
  if (!evidence.length && relationship.description) {
    evidence.push({
      relation_id: relationship.id,
      claim_id: "",
      chunk_id: "",
      doc_id: "",
      doc_name: "",
      file_path: "",
      line_start: 0,
      line_end: 0,
      page: null,
      section_path: "",
      text: relationship.description,
      confidence: relationship.confidence || 0,
    });
  }
  return evidence;
}

function renderKnowledgeGraph(graph, fallbackText = t("knowledge.noGraph")) {
  if (!elements.knowledgeGraph) {
    return;
  }

  if (state.knowledgeGraphRuntime?.destroy) {
    state.knowledgeGraphRuntime.destroy();
    state.knowledgeGraphRuntime = null;
  }
  elements.knowledgeGraph.textContent = "";
  const nodes = graph?.nodes || [];
  const edges = graph?.edges || [];
  if (elements.knowledgeGraphMeta) {
    const communityCount = graph?.stats?.community_count || 0;
    const reportCount = graph?.stats?.community_report_count || 0;
    const graphLevel = graph?.stats?.level ?? state.knowledgeGraphLevel ?? 0;
    const graphMeta = [
      t("knowledge.graphMetaLevel").replace("{level}", graphLevel),
      t("knowledge.graphMetaNodes").replace("{count}", nodes.length),
      t("knowledge.graphMetaEdges").replace("{count}", edges.length),
    ];
    if (communityCount) graphMeta.push(t("knowledge.graphMetaCommunities").replace("{count}", communityCount));
    if (reportCount) graphMeta.push(t("knowledge.graphMetaReports").replace("{count}", reportCount));
    elements.knowledgeGraphMeta.textContent = graphMeta.join(" / ");
  }
  if (elements.knowledgeGraphLevelSelect) {
    elements.knowledgeGraphLevelSelect.value = String(state.knowledgeGraphLevel || 0);
  }
  if (elements.knowledgeGraphScope) {
    const levelText = t("knowledge.graphLevel").replace("{level}", state.knowledgeGraphLevel || 0);
    elements.knowledgeGraphScope.textContent = state.knowledgeGraphFilterDocName
      ? t("knowledge.filteredScope").replace("{name}", state.knowledgeGraphFilterDocName).replace("{level}", levelText)
      : `${t("knowledge.allKnowledge")} / ${levelText}`;
  }
  if (elements.clearGraphFilterButton) {
    elements.clearGraphFilterButton.hidden = !state.knowledgeGraphFilterDocId;
  }
  state.knowledgeGraphSelection = null;
  renderKnowledgeGraphInspector();

  if (!nodes.length || !edges.length) {
    const empty = document.createElement("div");
    empty.className = "empty-state";
    empty.textContent = fallbackText;
    elements.knowledgeGraph.append(empty);
    return;
  }

  const canvas = document.createElement("div");
  canvas.className = "graph-canvas";

  const controls = document.createElement("div");
  controls.className = "graph-controls";
  const zoomOut = createKnowledgeGraphControlButton("-", "Zoom out");
  const zoomReset = createKnowledgeGraphControlButton("1:1", "Reset view");
  const zoomIn = createKnowledgeGraphControlButton("+", "Zoom in");
  const layoutToggle = createKnowledgeGraphControlButton(t("knowledge.freeze"), t("knowledge.freezeLayout"));
  controls.append(zoomOut, zoomReset, zoomIn, layoutToggle);

  const hint = document.createElement("div");
  hint.className = "graph-hint";
  hint.textContent = t("knowledge.graphHint");

  const canvasSurface = document.createElement("canvas");
  canvasSurface.className = "knowledge-graph-canvas";
  canvasSurface.setAttribute("aria-label", t("knowledge.interactiveGraph"));
  canvasSurface.setAttribute("role", "img");
  canvasSurface.tabIndex = 0;
  canvas.append(canvasSurface, controls, hint);
  const evidenceList = renderKnowledgeGraphEvidence(nodes, edges);
  const communityPanel = renderKnowledgeGraphCommunities(graph?.communities || [], nodes);
  elements.knowledgeGraph.append(canvas, communityPanel, evidenceList);
  state.knowledgeGraphRuntime = renderKnowledgeGraphCanvas(canvasSurface, nodes, edges, {
    zoomIn,
    zoomOut,
    zoomReset,
    layoutToggle,
    onSelect: renderKnowledgeGraphSelection,
  });
}

function createKnowledgeGraphControlButton(label, title) {
  const button = document.createElement("button");
  button.className = "graph-control-button";
  button.type = "button";
  button.textContent = label;
  button.title = title;
  button.setAttribute("aria-label", title);
  return button;
}

function renderKnowledgeGraphSelection(selection) {
  state.knowledgeGraphSelection = selection?.node || selection?.edge
    ? { node: selection.node || null, edge: selection.edge || null }
    : null;
  renderKnowledgeGraphInspector();
}

function renderKnowledgeGraphInspector() {
  const inspector = elements.knowledgeGraphInspector;
  if (!inspector) {
    return;
  }
  inspector.textContent = "";
  const selection = state.knowledgeGraphSelection;
  if (!selection?.node && !selection?.edge) {
    const graph = state.knowledgeGraph || {};
    const nodes = graph.nodes || [];
    const edges = graph.edges || [];
    const topNodes = [...nodes]
      .sort((a, b) => ((b.degree || 0) + (b.mention_count || 0)) - ((a.degree || 0) + (a.mention_count || 0)))
      .slice(0, 5);
    const empty = document.createElement("div");
    empty.className = "inspector-empty";
    const overviewTitle = document.createElement("div");
    overviewTitle.className = "inspector-title";
    overviewTitle.textContent = t("knowledge.graphOverview");
    const overviewDesc = document.createElement("p");
    overviewDesc.textContent = t("knowledge.graphOverviewDesc")
      .replace("{entities}", nodes.length)
      .replace("{relationships}", edges.length);
    empty.append(overviewTitle, overviewDesc);
    inspector.append(empty);
    if (topNodes.length) {
      inspector.append(createInspectorSection(t("knowledge.coreEntities"), topNodes.map((node) => `${node.label} (${node.degree || 0})`)));
    }
    const communities = graph.communities || [];
    if (communities.length) {
      inspector.append(createInspectorSection(t("knowledge.communities"), communities.slice(0, 5).map((item) => item.title || t("knowledge.communityName").replace("{id}", item.community))));
    }
    return;
  }

  if (selection.node) {
    const node = selection.node;
    inspector.append(createInspectorTitle(node.label || node.id, node.type || "entity"));
    inspector.append(createInspectorText(node.description || node.community_report_summary || t("knowledge.noDescriptionAvailable")));
    inspector.append(createInspectorMetrics([
      [t("knowledge.degree"), node.degree || 0],
      [t("knowledge.frequency"), node.mention_count || 0],
      [t("knowledge.community"), node.community_level == null ? "-" : `L${node.community_level}`],
    ]));
    if (node.community_title) {
      inspector.append(createInspectorSection(t("knowledge.community"), [node.community_title]));
    }
    if (node.doc_names?.length) {
      inspector.append(createInspectorSection(t("knowledge.sourceDocuments"), node.doc_names.slice(0, 6)));
    }
    return;
  }

  if (selection.edge) {
    const edge = selection.edge;
    inspector.append(createInspectorTitle(edge.predicate || t("knowledge.relationship"), t("knowledge.relationship")));
    inspector.append(createInspectorText(edge.description || t("knowledge.noRelationshipDescription")));
    inspector.append(createInspectorMetrics([
      [t("knowledge.weight"), formatInspectorNumber(edge.weight || edge.count)],
      [t("knowledge.strength"), formatInspectorNumber(edge.strength || edge.weight || edge.count)],
      [t("knowledge.evidence"), edge.evidence?.length || 0],
    ]));
    if (edge.doc_names?.length) {
      inspector.append(createInspectorSection(t("knowledge.sourceDocuments"), edge.doc_names.slice(0, 6)));
    }
    if (edge.evidence?.length) {
      const evidence = edge.evidence.slice(0, 4).map((item) => {
        const where = [item.doc_name, item.line_start ? `L${item.line_start}-${item.line_end || item.line_start}` : ""].filter(Boolean).join(" / ");
        return `${where ? `${where}: ` : ""}${item.text || ""}`;
      });
      inspector.append(createInspectorSection(t("knowledge.evidence"), evidence));
    }
  }
}

function createInspectorTitle(title, kicker) {
  const block = document.createElement("div");
  block.className = "inspector-heading";
  const small = document.createElement("div");
  small.className = "inspector-kicker";
  small.textContent = kicker;
  const main = document.createElement("div");
  main.className = "inspector-title";
  main.textContent = title;
  block.append(small, main);
  return block;
}

function createInspectorText(text) {
  const item = document.createElement("p");
  item.className = "inspector-text";
  item.textContent = text;
  return item;
}

function createInspectorMetrics(items) {
  const grid = document.createElement("div");
  grid.className = "inspector-metrics";
  for (const [label, value] of items) {
    const metric = document.createElement("div");
    metric.className = "inspector-metric";
    metric.innerHTML = `<span>${escapeHtml(label)}</span><strong>${escapeHtml(value)}</strong>`;
    grid.append(metric);
  }
  return grid;
}

function createInspectorSection(title, rows) {
  const section = document.createElement("div");
  section.className = "inspector-section";
  const heading = document.createElement("div");
  heading.className = "inspector-section-title";
  heading.textContent = title;
  section.append(heading);
  for (const row of rows) {
    const item = document.createElement("div");
    item.className = "inspector-row";
    item.textContent = row;
    section.append(item);
  }
  return section;
}

function formatInspectorNumber(value) {
  if (value == null || value === "") {
    return "-";
  }
  const num = Number(value);
  return Number.isFinite(num) ? num.toFixed(num >= 10 ? 0 : 3) : String(value);
}

function normalizeKnowledgeMatchText(value) {
  return String(value || "").toLowerCase().replace(/\s+/g, " ").trim();
}

function renderKnowledgeGraphCanvas(canvas, nodes, edges, controls = {}) {
  const ctx = canvas.getContext("2d");
  const nodeMap = new Map();
  const graphNodes = nodes.map((node, index) => {
    const angle = (index / Math.max(1, nodes.length)) * Math.PI * 2;
    const radius = 90 + Math.sqrt(Math.max(1, nodes.length)) * 24;
    const item = {
      ...node,
      x: Math.cos(angle) * radius + (Math.random() - 0.5) * 40,
      y: Math.sin(angle) * radius + (Math.random() - 0.5) * 40,
      vx: 0,
      vy: 0,
      radius: Math.min(16, 5 + Math.sqrt((node.degree || 0) + (node.mention_count || 0)) * 2.4),
      color: knowledgeGraphNodeColor(node),
      communityColor: knowledgeGraphCommunityColor(node.community_id),
    };
    nodeMap.set(node.id, item);
    return item;
  });
  const links = edges
    .map((edge) => ({
      ...edge,
      id: edge.id || `${edge.source}:${edge.predicate}:${edge.target}`,
      sourceNode: nodeMap.get(edge.source),
      targetNode: nodeMap.get(edge.target),
    }))
    .filter((edge) => edge.sourceNode && edge.targetNode);

  const neighbors = new Map();
  for (const node of graphNodes) {
    neighbors.set(node.id, new Set());
  }
  for (const edge of links) {
    neighbors.get(edge.source)?.add(edge.target);
    neighbors.get(edge.target)?.add(edge.source);
  }

  function highlightMatches(kind, value) {
    const terms = Array.isArray(value) ? value : [value];
    const normalized = terms.map((item) => normalizeKnowledgeMatchText(item)).filter(Boolean);
    if (!normalized.length) {
      return () => false;
    }
    if (kind === "node") {
      return (node) => normalized.some((term) => normalizeKnowledgeMatchText([
        node.label,
        node.canonical_name,
        node.description,
        ...(node.aliases || []),
      ].join(" ")).includes(term));
    }
    if (kind === "edge") {
      return (edge) => normalized.some((term) => normalizeKnowledgeMatchText([
        edge.predicate,
        edge.description,
        ...(edge.evidence || []).map((item) => item.text).join(" "),
      ].join(" ")).includes(term));
    }
    if (kind === "community") {
      return (node) => normalized.some((term) => normalizeKnowledgeMatchText([
        node.community_title,
        node.community_report_summary,
      ].join(" ")).includes(term));
    }
    return () => false;
  }

  const view = {
    scale: state.knowledgeGraphView?.scale || 1,
    x: state.knowledgeGraphView?.x || 0,
    y: state.knowledgeGraphView?.y || 0,
  };
  const runtime = {
    frame: 0,
    destroyed: false,
    frozen: false,
    alpha: 1,
    selectedNodeId: "",
    selectedEdgeId: "",
    hoverNodeId: "",
    hoverEdgeId: "",
    drag: null,
    width: 1,
    height: 1,
    dpr: 1,
  };

  function resizeCanvas() {
    const rect = canvas.getBoundingClientRect();
    runtime.width = Math.max(320, rect.width || 720);
    runtime.height = Math.max(260, rect.height || 420);
    runtime.dpr = window.devicePixelRatio || 1;
    canvas.width = Math.floor(runtime.width * runtime.dpr);
    canvas.height = Math.floor(runtime.height * runtime.dpr);
    ctx.setTransform(runtime.dpr, 0, 0, runtime.dpr, 0, 0);
  }

  function screenToWorld(clientX, clientY) {
    const rect = canvas.getBoundingClientRect();
    return {
      x: (clientX - rect.left - runtime.width / 2 - view.x) / view.scale,
      y: (clientY - rect.top - runtime.height / 2 - view.y) / view.scale,
    };
  }

  function worldToScreen(node) {
    return {
      x: runtime.width / 2 + view.x + node.x * view.scale,
      y: runtime.height / 2 + view.y + node.y * view.scale,
    };
  }

  function findNodeAt(clientX, clientY) {
    const point = screenToWorld(clientX, clientY);
    for (let i = graphNodes.length - 1; i >= 0; i -= 1) {
      const node = graphNodes[i];
      const dx = point.x - node.x;
      const dy = point.y - node.y;
      const hitRadius = Math.max(12 / view.scale, node.radius + 5 / view.scale);
      if (dx * dx + dy * dy <= hitRadius * hitRadius) {
        return node;
      }
    }
    return null;
  }

  function findEdgeAt(clientX, clientY) {
    const point = screenToWorld(clientX, clientY);
    let best = null;
    let bestDistance = 12 / view.scale;
    for (const edge of links) {
      const distance = distanceToSegment(point, edge.sourceNode, edge.targetNode);
      if (distance < bestDistance) {
        best = edge;
        bestDistance = distance;
      }
    }
    return best;
  }

  function updateEvidenceHighlight() {
    document.querySelectorAll(".graph-evidence-item").forEach((row) => {
      row.classList.toggle("is-selected", !!runtime.selectedEdgeId && row.dataset.edgeId === runtime.selectedEdgeId);
      row.classList.toggle("is-connected", !!runtime.selectedNodeId && (row.dataset.source === runtime.selectedNodeId || row.dataset.target === runtime.selectedNodeId));
    });
  }

  function setSelection({ nodeId = "", edgeId = "" }) {
    runtime.selectedNodeId = nodeId;
    runtime.selectedEdgeId = edgeId;
    updateEvidenceHighlight();
    const selectedNode = nodeId ? nodeMap.get(nodeId) : null;
    const selectedEdge = edgeId ? links.find((item) => item.id === edgeId) : null;
    controls.onSelect?.({
      node: selectedNode || null,
      edge: selectedEdge || null,
      graph: { nodes: graphNodes, edges: links },
    });
  }

  function setZoom(nextScale, anchorX = runtime.width / 2, anchorY = runtime.height / 2) {
    const oldScale = view.scale;
    view.scale = Math.min(5, Math.max(0.25, nextScale));
    const worldX = (anchorX - runtime.width / 2 - view.x) / oldScale;
    const worldY = (anchorY - runtime.height / 2 - view.y) / oldScale;
    view.x = anchorX - runtime.width / 2 - worldX * view.scale;
    view.y = anchorY - runtime.height / 2 - worldY * view.scale;
    state.knowledgeGraphView = { ...view };
  }

  function resetView() {
    view.scale = 1;
    view.x = 0;
    view.y = 0;
    runtime.alpha = Math.max(runtime.alpha, 0.4);
    state.knowledgeGraphView = { ...view };
  }

  function tickPhysics() {
    if (runtime.frozen) {
      return;
    }
    const alpha = runtime.alpha;
    for (let i = 0; i < graphNodes.length; i += 1) {
      for (let j = i + 1; j < graphNodes.length; j += 1) {
        const a = graphNodes[i];
        const b = graphNodes[j];
        const dx = a.x - b.x || 0.01;
        const dy = a.y - b.y || 0.01;
        const distanceSq = Math.max(80, dx * dx + dy * dy);
        const force = (4600 * alpha) / distanceSq;
        const distance = Math.sqrt(distanceSq);
        const fx = (dx / distance) * force;
        const fy = (dy / distance) * force;
        a.vx += fx;
        a.vy += fy;
        b.vx -= fx;
        b.vy -= fy;
      }
    }

    for (const edge of links) {
      const a = edge.sourceNode;
      const b = edge.targetNode;
      const dx = b.x - a.x;
      const dy = b.y - a.y;
      const distance = Math.max(1, Math.sqrt(dx * dx + dy * dy));
      const preferred = 80 + Math.min(90, 18 * Math.sqrt(edge.count || 1));
      const force = (distance - preferred) * 0.012 * alpha;
      const fx = (dx / distance) * force;
      const fy = (dy / distance) * force;
      a.vx += fx;
      a.vy += fy;
      b.vx -= fx;
      b.vy -= fy;
    }

    for (const node of graphNodes) {
      node.vx += -node.x * 0.004 * alpha;
      node.vy += -node.y * 0.004 * alpha;
      node.vx *= 0.82;
      node.vy *= 0.82;
      if (runtime.drag?.node !== node) {
        node.x += node.vx;
        node.y += node.vy;
      }
    }
    runtime.alpha = Math.max(0.018, runtime.alpha * 0.986);
  }

  function drawGrid() {
    const spacing = 36 * view.scale;
    if (spacing < 9) {
      return;
    }
    const offsetX = ((runtime.width / 2 + view.x) % spacing + spacing) % spacing;
    const offsetY = ((runtime.height / 2 + view.y) % spacing + spacing) % spacing;
    ctx.fillStyle = "rgba(148, 163, 184, 0.16)";
    for (let x = offsetX; x < runtime.width; x += spacing) {
      for (let y = offsetY; y < runtime.height; y += spacing) {
        ctx.beginPath();
        ctx.arc(x, y, 1, 0, Math.PI * 2);
        ctx.fill();
      }
    }
  }

  function isNodeActive(node) {
    const highlight = state.knowledgeGraphHighlight;
    if (highlight) {
      const entityTerms = highlight.entities?.length ? highlight.entities : highlight.query;
      const communityTerms = highlight.communities?.length ? highlight.communities : "";
      if (highlightMatches("node", entityTerms || "")(node)) return true;
      if (highlightMatches("community", communityTerms)(node)) return true;
    }
    const focusNodeId = runtime.hoverNodeId || runtime.selectedNodeId;
    const focusEdgeId = runtime.hoverEdgeId || runtime.selectedEdgeId;
    const selectedNode = focusNodeId ? nodeMap.get(focusNodeId) : null;
    if (node.id === focusNodeId) return true;
    if (focusNodeId && neighbors.get(focusNodeId)?.has(node.id)) return true;
    if (selectedNode && selectedNode.community_id != null && selectedNode.community_id === node.community_id) return true;
    if (focusEdgeId) {
      const edge = links.find((item) => item.id === focusEdgeId);
      return edge && (edge.source === node.id || edge.target === node.id);
    }
    return false;
  }

  function isEdgeActive(edge) {
    const highlight = state.knowledgeGraphHighlight;
    if (highlight) {
      const relationTerms = highlight.relations?.length ? highlight.relations : highlight.query;
      if (highlightMatches("edge", relationTerms || "")(edge)) {
        return true;
      }
    }
    const focusNodeId = runtime.hoverNodeId || runtime.selectedNodeId;
    const focusEdgeId = runtime.hoverEdgeId || runtime.selectedEdgeId;
    const selectedNode = focusNodeId ? nodeMap.get(focusNodeId) : null;
    return edge.id === focusEdgeId
      || (!!focusNodeId && (edge.source === focusNodeId || edge.target === focusNodeId))
      || (!!selectedNode && selectedNode.community_id != null && edge.community_id === selectedNode.community_id);
  }

  function draw() {
    ctx.clearRect(0, 0, runtime.width, runtime.height);
    const gradient = ctx.createRadialGradient(runtime.width * 0.52, runtime.height * 0.46, 40, runtime.width * 0.5, runtime.height * 0.5, Math.max(runtime.width, runtime.height) * 0.72);
    gradient.addColorStop(0, "#151a26");
    gradient.addColorStop(1, "#090b10");
    ctx.fillStyle = gradient;
    ctx.fillRect(0, 0, runtime.width, runtime.height);
    drawGrid();

    ctx.save();
    ctx.translate(runtime.width / 2 + view.x, runtime.height / 2 + view.y);
    ctx.scale(view.scale, view.scale);

    for (const edge of links) {
      const active = isEdgeActive(edge);
      ctx.beginPath();
      ctx.moveTo(edge.sourceNode.x, edge.sourceNode.y);
      ctx.lineTo(edge.targetNode.x, edge.targetNode.y);
      ctx.strokeStyle = active ? knowledgeGraphCommunityStroke(edge.community_id) : "rgba(116, 129, 154, 0.28)";
      ctx.lineWidth = (active ? 1.8 : 0.8) / view.scale + Math.min(1.4, Math.sqrt(edge.count || 1) * 0.18);
      ctx.stroke();
    }

    for (const node of graphNodes) {
      const active = isNodeActive(node);
      const selected = node.id === runtime.selectedNodeId || node.id === runtime.hoverNodeId;
      const radius = node.radius * (selected ? 1.18 : 1);
      if (active) {
        ctx.beginPath();
        ctx.arc(node.x, node.y, radius + 7 / view.scale, 0, Math.PI * 2);
        ctx.fillStyle = "rgba(96, 165, 250, 0.16)";
        ctx.fill();
      }

      ctx.beginPath();
      ctx.arc(node.x, node.y, radius, 0, Math.PI * 2);
      ctx.fillStyle = node.community_id != null ? node.communityColor : node.color;
      ctx.fill();
      ctx.lineWidth = selected ? 2.2 / view.scale : 1.2 / view.scale;
      ctx.strokeStyle = selected ? "rgba(219, 234, 254, 0.98)" : "rgba(226, 232, 240, 0.56)";
      ctx.stroke();
    }

    const shouldShowAllLabels = view.scale > 0.78 || graphNodes.length <= 28;
    for (const node of graphNodes) {
      const active = isNodeActive(node);
      if (!active && !shouldShowAllLabels && (node.degree || 0) < 2) {
        continue;
      }
      ctx.font = `${active ? 600 : 500} ${Math.max(10, 11 / view.scale)}px "Segoe UI", system-ui, sans-serif`;
      ctx.textAlign = "center";
      ctx.textBaseline = "top";
      const label = compactGraphLabel(node.label || node.id);
      const labelY = node.y + node.radius + 5 / view.scale;
      ctx.lineWidth = 4 / view.scale;
      ctx.strokeStyle = "rgba(9, 11, 16, 0.92)";
      ctx.strokeText(label, node.x, labelY);
      ctx.fillStyle = active ? "#f8fafc" : "rgba(226, 232, 240, 0.82)";
      ctx.fillText(label, node.x, labelY);
    }
    ctx.restore();

  if (runtime.hoverNodeId) {
    const node = nodeMap.get(runtime.hoverNodeId);
    const subtitle = node?.community_title || node?.type || "concept";
    drawGraphTooltip(node?.label || "", subtitle, node ? worldToScreen(node) : null);
  }
  }

  function drawGraphTooltip(title, subtitle, point) {
    if (!point || !title) return;
    const text = `${title} · ${subtitle}`;
    ctx.font = '12px "Segoe UI", system-ui, sans-serif';
    const width = Math.min(280, ctx.measureText(text).width + 18);
    const x = Math.min(runtime.width - width - 12, Math.max(12, point.x + 12));
    const y = Math.min(runtime.height - 34, Math.max(12, point.y - 28));
    ctx.fillStyle = "rgba(15, 23, 42, 0.94)";
    ctx.strokeStyle = "rgba(148, 163, 184, 0.28)";
    ctx.beginPath();
    ctx.roundRect(x, y, width, 26);
    ctx.fill();
    ctx.stroke();
    ctx.fillStyle = "#e5e7eb";
    ctx.fillText(text, x + 9, y + 8);
  }

  function frame() {
    if (runtime.destroyed) return;
    tickPhysics();
    draw();
    runtime.frame = requestAnimationFrame(frame);
  }

  canvas.addEventListener("wheel", (event) => {
    event.preventDefault();
    const rect = canvas.getBoundingClientRect();
    const direction = event.deltaY < 0 ? 1.12 : 0.88;
    setZoom(view.scale * direction, event.clientX - rect.left, event.clientY - rect.top);
  }, { passive: false });

  canvas.addEventListener("dblclick", (event) => {
    event.preventDefault();
    resetView();
  });

  canvas.addEventListener("pointerdown", (event) => {
    canvas.setPointerCapture(event.pointerId);
    canvas.classList.add("is-dragging");
    const node = findNodeAt(event.clientX, event.clientY);
    if (node) {
      const point = screenToWorld(event.clientX, event.clientY);
      runtime.drag = { type: "node", node, offsetX: point.x - node.x, offsetY: point.y - node.y };
      runtime.alpha = 0.8;
      setSelection({ nodeId: node.id });
      return;
    }
    const edge = findEdgeAt(event.clientX, event.clientY);
    if (edge) {
      setSelection({ edgeId: edge.id });
    } else {
      setSelection({});
    }
    runtime.drag = {
      type: "pan",
      startX: event.clientX,
      startY: event.clientY,
      originX: view.x,
      originY: view.y,
    };
  });

  canvas.addEventListener("pointermove", (event) => {
    if (runtime.drag?.type === "node") {
      const point = screenToWorld(event.clientX, event.clientY);
      runtime.drag.node.x = point.x - runtime.drag.offsetX;
      runtime.drag.node.y = point.y - runtime.drag.offsetY;
      runtime.drag.node.vx = 0;
      runtime.drag.node.vy = 0;
      runtime.alpha = 0.55;
      return;
    }
    if (runtime.drag?.type === "pan") {
      view.x = runtime.drag.originX + event.clientX - runtime.drag.startX;
      view.y = runtime.drag.originY + event.clientY - runtime.drag.startY;
      state.knowledgeGraphView = { ...view };
      return;
    }

    const node = findNodeAt(event.clientX, event.clientY);
    const edge = node ? null : findEdgeAt(event.clientX, event.clientY);
    runtime.hoverNodeId = node?.id || "";
    runtime.hoverEdgeId = edge?.id || "";
    canvas.style.cursor = node ? "grab" : edge ? "pointer" : "grab";
  });

  canvas.addEventListener("pointerup", (event) => {
    runtime.drag = null;
    canvas.classList.remove("is-dragging");
    if (canvas.hasPointerCapture(event.pointerId)) {
      canvas.releasePointerCapture(event.pointerId);
    }
  });

  canvas.addEventListener("pointerleave", () => {
    runtime.hoverNodeId = "";
    runtime.hoverEdgeId = "";
  });

  controls.zoomIn?.addEventListener("click", () => setZoom(view.scale * 1.2));
  controls.zoomOut?.addEventListener("click", () => setZoom(view.scale / 1.2));
  controls.zoomReset?.addEventListener("click", resetView);
  controls.layoutToggle?.addEventListener("click", () => {
    runtime.frozen = !runtime.frozen;
    controls.layoutToggle.textContent = runtime.frozen ? t("knowledge.release") : t("knowledge.freeze");
    controls.layoutToggle.title = runtime.frozen ? t("knowledge.releaseLayout") : t("knowledge.freezeLayout");
    runtime.alpha = runtime.frozen ? runtime.alpha : 0.8;
  });

  const resizeObserver = new ResizeObserver(resizeCanvas);
  resizeObserver.observe(canvas);
  resizeCanvas();
  frame();

  return {
    destroy() {
      runtime.destroyed = true;
      cancelAnimationFrame(runtime.frame);
      resizeObserver.disconnect();
    },
  };
}

function knowledgeGraphNodeColor(node) {
  const type = (node.type || "concept").toLowerCase();
  if (["technology", "system", "product", "module", "api"].includes(type)) {
    return "#60a5fa";
  }
  if (["person", "organization", "team"].includes(type)) {
    return "#34d399";
  }
  if (["file", "location", "document"].includes(type)) {
    return "#f59e0b";
  }
  return "#a78bfa";
}

const KNOWLEDGE_COMMUNITY_COLORS = [
  "#60a5fa",
  "#34d399",
  "#f59e0b",
  "#f472b6",
  "#a78bfa",
  "#2dd4bf",
  "#fb7185",
  "#c084fc",
  "#22c55e",
  "#eab308",
];

function knowledgeGraphCommunityColor(communityId) {
  if (communityId == null || Number.isNaN(Number(communityId))) {
    return "";
  }
  return KNOWLEDGE_COMMUNITY_COLORS[Math.abs(Number(communityId)) % KNOWLEDGE_COMMUNITY_COLORS.length];
}

function knowledgeGraphCommunityStroke(communityId) {
  const color = knowledgeGraphCommunityColor(communityId);
  return color ? `${color}cc` : "rgba(125, 173, 255, 0.9)";
}

function distanceToSegment(point, start, end) {
  const dx = end.x - start.x;
  const dy = end.y - start.y;
  const lengthSq = dx * dx + dy * dy;
  if (lengthSq === 0) {
    return Math.hypot(point.x - start.x, point.y - start.y);
  }
  const t = Math.max(0, Math.min(1, ((point.x - start.x) * dx + (point.y - start.y) * dy) / lengthSq));
  const x = start.x + t * dx;
  const y = start.y + t * dy;
  return Math.hypot(point.x - x, point.y - y);
}

function renderKnowledgeGraphEvidence(nodes, edges) {
  const nodeMap = new Map(nodes.map((node) => [node.id, node]));
  const list = document.createElement("div");
  list.className = "graph-evidence-list";
  for (const edge of edges.slice(0, 6)) {
    const row = document.createElement("div");
    row.className = "graph-evidence-item";
    row.dataset.edgeId = edge.id || `${edge.source}:${edge.predicate}:${edge.target}`;
    row.dataset.source = edge.source;
    row.dataset.target = edge.target;

    const title = document.createElement("div");
    title.className = "graph-evidence-title";
    title.textContent = `${nodeMap.get(edge.source)?.label || edge.source} -[${edge.predicate}]-> ${nodeMap.get(edge.target)?.label || edge.target}`;

    const evidence = edge.evidence?.[0];
    const meta = document.createElement("div");
    meta.className = "graph-evidence-meta";
    const metricText = edge.weight ? `weight ${formatInspectorNumber(edge.weight)}` : "";
    meta.textContent = evidence
      ? [evidence.doc_name, evidence.line_start ? `L${evidence.line_start}-${evidence.line_end || evidence.line_start}` : ""].filter(Boolean).join(" · ")
      : "";
    meta.textContent = [meta.textContent, metricText].filter(Boolean).join(" · ");

    const text = document.createElement("div");
    text.className = "graph-evidence-text";
    text.textContent = evidence?.text || "";

    row.append(title, meta, text);
    list.append(row);
  }
  return list;
}

function renderKnowledgeGraphCommunities(communities, nodes) {
  const panel = document.createElement("div");
  panel.className = "graph-community-panel";
  const nodeById = new Map(nodes.map((node) => [node.id, node]));
  const visibleCommunities = (communities || [])
    .map((community) => {
      const entityCount = (community.entity_ids || []).filter((entityId) => nodeById.has(entityId)).length;
      return { ...community, entityCount };
    })
    .filter((community) => community.entityCount > 0)
    .sort((a, b) => b.entityCount - a.entityCount)
    .slice(0, 6);

  if (!visibleCommunities.length) {
    return panel;
  }

  const title = document.createElement("div");
  title.className = "graph-community-panel-title";
  title.textContent = t("knowledge.communityReports");
  panel.append(title);

  for (const community of visibleCommunities) {
    const report = community.report || {};
    const item = document.createElement("div");
    item.className = "graph-community-item";
    item.dataset.communityId = String(community.community);

    const header = document.createElement("div");
    header.className = "graph-community-header";
    const swatch = document.createElement("span");
    swatch.className = "graph-community-swatch";
    swatch.style.background = knowledgeGraphCommunityColor(community.community);
    const name = document.createElement("span");
    name.className = "graph-community-name";
    name.textContent = community.title || report.title || t("knowledge.communityName").replace("{id}", community.community);
    const count = document.createElement("span");
    count.className = "graph-community-count";
    const childCount = (community.children || []).length;
    const countParts = [
      t("knowledge.graphLevelShort").replace("{level}", community.level || 0),
      t("knowledge.graphMetaEntities").replace("{count}", community.entityCount),
    ];
    if (report.rank != null) {
      countParts.push(t("knowledge.rank").replace("{rank}", formatInspectorNumber(report.rank)));
    }
    if (childCount) {
      countParts.push(t("knowledge.childCount").replace("{count}", childCount));
    } else if (community.parent != null && community.parent >= 0) {
      countParts.push(t("knowledge.parentCommunity").replace("{id}", community.parent));
    }
    count.textContent = countParts.join(" / ");
    header.append(swatch, name, count);

    const summary = document.createElement("div");
    summary.className = "graph-community-summary";
    summary.textContent = report.summary || "";

    item.append(header, summary);
    panel.append(item);
  }
  return panel;
}

function compactGraphLabel(label) {
  const text = String(label || "");
  return text.length > 18 ? `${text.slice(0, 16)}…` : text;
}

function renderKnowledgeDocs() {
  elements.docsList.textContent = "";

  if (state.knowledgeDocs.length === 0) {
    const empty = document.createElement("div");
    empty.className = "empty-state";
    empty.textContent = t("knowledge.noDocs");
    elements.docsList.append(empty);
    return;
  }

  for (const doc of state.knowledgeDocs) {
    const item = document.createElement("div");
    item.className = "doc-item";

    const headerRow = document.createElement("div");
    headerRow.className = "doc-header-row";

    const nameSection = document.createElement("div");
    nameSection.className = "doc-name-section";

    const name = document.createElement("span");
    name.className = "doc-name doc-name-clickable";
    name.textContent = doc.name;
    name.title = t("ui.clickToView");
    name.addEventListener("click", () => viewDoc(doc.id));

    nameSection.append(name);

    const metaSection = document.createElement("div");
    metaSection.className = "doc-meta-section";

    const chunks = document.createElement("span");
    chunks.className = "doc-chunks";
    chunks.textContent = `${doc.chunk_count || 0} ${t("knowledge.chunks")}`;

    const graphBtn = document.createElement("button");
    graphBtn.className = "doc-graph-btn";
    graphBtn.type = "button";
    graphBtn.textContent = t("knowledge.graphButton");
    graphBtn.title = t("knowledge.viewDocGraph");
    graphBtn.addEventListener("click", () => setKnowledgeGraphDocumentFilter(doc.id, doc.name));

    const deleteBtn = document.createElement("button");
    deleteBtn.className = "doc-delete-btn";
    deleteBtn.textContent = "×";
    deleteBtn.title = t("ui.delete");
    deleteBtn.addEventListener("click", () => deleteDoc(doc.id, doc.name));

    metaSection.append(chunks, graphBtn, deleteBtn);
    headerRow.append(nameSection, metaSection);

    const descRow = document.createElement("div");
    descRow.className = "doc-desc-row";

    const category = doc.category || t("knowledge.noCategory");
    const categorySpan = document.createElement("span");
    categorySpan.className = "doc-category";
    categorySpan.textContent = category;

    const tags = doc.tags || [];
    if (tags.length > 0) {
      const tagsSpan = document.createElement("span");
      tagsSpan.className = "doc-tags";
      tagsSpan.textContent = tags.join(", ");
      descRow.append(categorySpan, tagsSpan);
    } else {
      descRow.append(categorySpan);
    }

    item.append(headerRow, descRow);
    elements.docsList.append(item);
  }
}

async function setKnowledgeGraphDocumentFilter(docId, docName) {
  state.knowledgeGraphFilterDocId = docId || "";
  state.knowledgeGraphFilterDocName = docName || "";
  state.knowledgeGraphHighlight = null;
  state.knowledgeGraphSelection = null;
  if (state.activeKnowledgeTab === "graph") {
    await loadKnowledgeGraph();
  } else {
    setKnowledgeTab("graph");
  }
}

function openKnowledgeModal() {
  setupKnowledgeWorkbench();
  // Update modal stats from sidebar stats
  elements.modalStatsDocs.textContent = elements.statsDocs.textContent;
  elements.modalStatsChunks.textContent = elements.statsChunks.textContent;

  // 检查知识库是否启用，显示配置提示
  const knowledgeEnabled = state.config?.knowledge?.enabled === true;
  const configHint = document.getElementById("knowledge-config-hint");
  if (configHint) {
    configHint.style.display = knowledgeEnabled ? "none" : "flex";
  }

  openSidebarDrawer(elements.knowledgeModal, elements.knowledgeToggle);
  loadKnowledgeGraph();
}

function closeKnowledgeModal() {
  closeSidebarDrawer(elements.knowledgeModal, elements.knowledgeToggle);
}

function openWorkspaceModal() {
  openSidebarDrawer(elements.workspaceModal, elements.workspaceToggle);
}

function closeWorkspaceModal() {
  closeSidebarDrawer(elements.workspaceModal, elements.workspaceToggle);
}

function openDocModal() {
  elements.docModal.classList.add("active");
  elements.docError.textContent = "";
  elements.docSuccess.textContent = "";
}

function closeDocModal() {
  elements.docModal.classList.remove("active");
  state.activeDoc = null;
}

function openDocViewModal() {
  elements.docViewModal.classList.add("active");
}

function closeDocViewModal() {
  elements.docViewModal.classList.remove("active");
  state.activeDoc = null;
}

async function viewDoc(docId) {
  try {
    const response = await fetch(`${state.knowledgeApiPath}/documents/${encodeURIComponent(docId)}`, {
      headers: { Authorization: `Bearer ${state.token}` },
    });
    if (!response.ok) {
      throw new Error(`load doc failed: ${response.status}`);
    }

    const doc = await response.json();
    state.activeDoc = doc;

    elements.docViewId.textContent = doc.id;
    elements.docViewName.textContent = doc.name;
    elements.docViewCategory.textContent = doc.category || t("knowledge.noCategory");
    elements.docViewTags.textContent = (doc.tags || []).join(", ") || "-";
    elements.docViewCreated.textContent = doc.created_at ? formatTime(doc.created_at) : "-";
    renderMarkdown(elements.docViewContent, doc.content || "");

    openDocViewModal();
  } catch (error) {
    console.error(error);
    setError(error.message || t("status.failed"));
  }
}

async function addDoc() {
  elements.docError.textContent = "";
  elements.docSuccess.textContent = "";

  const name = elements.docNameInput.value.trim();
  const content = elements.docContentEditor.value;

  if (!name) {
    elements.docError.textContent = t("knowledge.nameRequired");
    return;
  }
  if (!content) {
    elements.docError.textContent = t("knowledge.contentRequired");
    return;
  }

  const tags = elements.docTagsInput.value.trim()
    ? elements.docTagsInput.value.split(",").map(t => t.trim()).filter(t => t)
    : [];
  const category = elements.docCategoryInput.value.trim() || "";
  const fileType = elements.docFileTypeSelect.value;

  try {
    setKnowledgeIndexingState(true, t("knowledge.indexingAddDoc"));
    elements.docSuccess.textContent = t("knowledge.indexingAddDoc");
    const response = await fetch(`${state.knowledgeApiPath}/documents?async_index=true`, {
      method: "POST",
      headers: authHeaders(),
      body: JSON.stringify({
        name,
        content,
        tags,
        category,
        file_type: fileType,
      }),
    });

    if (!response.ok) {
      const errorData = await response.json();
      throw new Error(errorData.error?.message || errorData.error || t("knowledge.addFailed"));
    }

    const result = await response.json();
    elements.docSuccess.textContent = result.job_id ? t("knowledge.uploadAccepted") : t("knowledge.docAdded");
    elements.docNameInput.value = "";
    elements.docCategoryInput.value = "";
    elements.docTagsInput.value = "";
    elements.docContentEditor.value = "";

    await loadKnowledgeStats();
    await loadKnowledgeDocs();
    if (result.job_id) {
      scheduleKnowledgeJobPoll(result.job_id, t("knowledge.indexingAddDoc"));
    } else {
      await loadKnowledgeGraph();
      setKnowledgeIndexingState(false);
    }
  } catch (error) {
    console.error(error);
    elements.docError.textContent = error.message || t("status.failed");
    setKnowledgeIndexingState(false);
  } finally {
    if (!state.activeKnowledgeJobId) {
      setKnowledgeIndexingState(false);
    }
  }
}

async function uploadDoc() {
  const fileInput = elements.docFileUpload;
  const file = fileInput.files[0];
  if (!file) {
    return;
  }

  // Reset the input so the same file can be uploaded again
  fileInput.value = "";

  // Build FormData
  const formData = new FormData();
  formData.append("file", file);

  // Optional: category and tags
  const category = elements.docCategoryInput.value.trim();
  const tags = elements.docTagsInput.value.trim();
  if (category) {
    formData.append("category", category);
  }
  if (tags) {
    formData.append("tags", tags);
  }

  try {
    setKnowledgeIndexingState(true, t("knowledge.indexingUploadDoc"));
    elements.docSuccess.textContent = t("knowledge.indexingUploadDoc");
    const response = await fetch(`${state.knowledgeApiPath}/documents/upload?async_index=true`, {
      method: "POST",
      headers: {
        Authorization: `Bearer ${state.token}`,
      },
      body: formData,
    });

    if (!response.ok) {
      const errorData = await response.json();
      throw new Error(errorData.error?.message || errorData.error || t("knowledge.uploadFailed"));
    }

    const result = await response.json();

    // Show success feedback
    elements.docSuccess.textContent = result.job_id
      ? t("knowledge.uploadAccepted")
      : (t("knowledge.uploadSuccess") || `File "${result.name}" uploaded (${result.size_bytes} bytes)`);

    // Refresh docs list
    await loadKnowledgeStats();
    await loadKnowledgeDocs();
    if (result.job_id) {
      scheduleKnowledgeJobPoll(result.job_id, t("knowledge.indexingUploadDoc"));
    } else {
      await loadKnowledgeGraph();
      setKnowledgeIndexingState(false);
    }
  } catch (error) {
    console.error(error);
    elements.docError.textContent = error.message || t("status.failed");
    setKnowledgeIndexingState(false);
  } finally {
    if (!state.activeKnowledgeJobId) {
      setKnowledgeIndexingState(false);
    }
  }
}

async function uploadTemporaryFile() {
  const fileInput = elements.temporaryFileUpload;
  const file = fileInput?.files?.[0];
  if (!file) {
    return;
  }
  fileInput.value = "";

  if (!state.activeSessionKey) {
    setError(t("msg.noSession"));
    return;
  }

  const formData = new FormData();
  formData.append("file", file);

  try {
    setError("");
    setStatus(t("sessionFiles.uploading"), "idle");
    if (elements.temporaryFileButton) {
      elements.temporaryFileButton.disabled = true;
    }
    const response = await fetch(
      `${state.sessionsPath}/${encodeURIComponent(state.activeSessionKey)}/temporary-files`,
      {
        method: "POST",
        headers: {
          Authorization: `Bearer ${state.token}`,
        },
        body: formData,
      },
    );

    if (!response.ok) {
      const errorData = await response.json();
      throw new Error(errorData.error || t("sessionFiles.uploadFailed"));
    }

    const result = await response.json();
    const currentItems = state.sessionFiles.get(state.activeSessionKey) || [];
    state.sessionFiles.set(state.activeSessionKey, [...currentItems, result]);
    renderSessionFiles();
    setStatus(t("sessionFiles.uploaded"), "connected");
    window.setTimeout(() => setStatus(t("status.connected"), "connected"), 2200);
  } catch (error) {
    console.error(error);
    setError(error.message || t("sessionFiles.uploadFailed"));
  } finally {
    if (elements.temporaryFileButton) {
      elements.temporaryFileButton.disabled = false;
    }
  }
}

async function deleteDoc(docId, docName) {
  if (!confirm(`${t("ui.confirmDelete")} ${docName}?`)) {
    return;
  }

  try {
    const response = await fetch(`${state.knowledgeApiPath}/documents/${encodeURIComponent(docId)}`, {
      method: "DELETE",
      headers: authHeaders(),
    });

    if (!response.ok) {
      const errorData = await response.json();
      throw new Error(errorData.error?.message || errorData.error || t("knowledge.deleteFailed"));
    }

    // Close view modal if deleting the active doc
    if (state.activeDoc?.id === docId) {
      closeDocViewModal();
    }

    await loadKnowledgeStats();
    await loadKnowledgeDocs();
    await loadKnowledgeGraph();
  } catch (error) {
    console.error(error);
    setError(error.message || t("status.failed"));
  }
}

async function rebuildKnowledgeIndex() {
  if (!confirm(t("knowledge.rebuildConfirm"))) {
    return;
  }

  try {
    setKnowledgeRebuildUi(true);
    setKnowledgeIndexingState(true, t("knowledge.indexingRebuild"));
    const response = await fetch(`${state.knowledgeApiPath}/rebuild-index?type=all&async_index=true`, {
      method: "POST",
      headers: authHeaders(),
    });

    if (!response.ok) {
      const errorData = await response.json();
      throw new Error(errorData.error?.message || errorData.error || t("knowledge.rebuildFailed"));
    }

    const result = await response.json();
    if (result.job_id) {
      elements.docSuccess.textContent = t("knowledge.rebuildAccepted");
      scheduleKnowledgeJobPoll(result.job_id, t("knowledge.indexingRebuild"));
      return;
    }

    await loadKnowledgeStats();
    await loadKnowledgeDocs();
    await loadKnowledgeGraph();
  } catch (error) {
    console.error(error);
    setError(error.message || t("status.failed"));
    state.knowledgeRebuilding = false;
    await loadKnowledgeStats();
    await loadKnowledgeGraph();
    setKnowledgeIndexingState(false);
  } finally {
    if (!state.activeKnowledgeJobId) {
      setKnowledgeIndexingState(false);
    }
  }
}

function setKnowledgeStatsPending() {
  const fields = [
    elements.statsDocs,
    elements.statsChunks,
    elements.modalStatsDocs,
    elements.modalStatsChunks,
    elements.modalStatsEntities,
    elements.modalStatsClaims,
    elements.modalStatsRelations,
    elements.modalStatsCommunities,
    elements.modalStatsReports,
  ];
  for (const field of fields) {
    if (field) field.textContent = "-";
  }
}

function renderKnowledgeRebuildPlaceholder() {
  if (elements.knowledgeHealthScore) elements.knowledgeHealthScore.textContent = "0%";
  if (elements.knowledgeHealthBar) elements.knowledgeHealthBar.style.width = "0%";
  if (elements.knowledgeHealthTitle) elements.knowledgeHealthTitle.textContent = t("knowledge.rebuildingTitle");
  if (elements.knowledgeHealthDesc) elements.knowledgeHealthDesc.textContent = t("knowledge.rebuildingDesc");
  if (!elements.knowledgeOverviewInsights) {
    return;
  }
  elements.knowledgeOverviewInsights.textContent = "";
  const insights = [
    [t("knowledge.insightRetrievalIndex"), t("knowledge.rebuildingRetrieval")],
    [t("knowledge.insightSemanticModel"), t("knowledge.rebuildingSemantic")],
    [t("knowledge.insightGraphRagLayer"), t("knowledge.rebuildingGraph")],
  ];
  for (const [title, text] of insights) {
    const item = document.createElement("div");
    item.className = "knowledge-insight-item";
    item.innerHTML = `<strong>${escapeHtml(title)}</strong><span>${escapeHtml(text)}</span>`;
    elements.knowledgeOverviewInsights.append(item);
  }
}

function renderKnowledgeGraphRebuildPlaceholder() {
  if (!elements.knowledgeGraph) {
    return;
  }
  if (state.knowledgeGraphRuntime?.destroy) {
    state.knowledgeGraphRuntime.destroy();
    state.knowledgeGraphRuntime = null;
  }
  elements.knowledgeGraph.textContent = "";
  const empty = document.createElement("div");
  empty.className = "empty-state";
  empty.textContent = t("knowledge.rebuildingGraph");
  elements.knowledgeGraph.append(empty);
  if (elements.knowledgeGraphMeta) {
    elements.knowledgeGraphMeta.textContent = t("knowledge.indexingRebuild");
  }
}

function setKnowledgeRebuildUi(active) {
  state.knowledgeRebuilding = active;
  if (!active) {
    return;
  }
  state.knowledgeStats = null;
  state.knowledgeGraph = null;
  state.knowledgeGraphSelection = null;
  setKnowledgeStatsPending();
  renderKnowledgeOverview();
  renderKnowledgeGraphRebuildPlaceholder();
}

async function queryKnowledge() {
  if (state.knowledgeIndexing) {
    return;
  }
  const query = elements.queryInput.value.trim();
  if (!query) {
    elements.queryResults.textContent = "";
    const empty = document.createElement("div");
    empty.className = "empty-state";
    empty.textContent = t("knowledge.queryRequired");
    elements.queryResults.append(empty);
    return;
  }

  const mode = elements.queryMode.value;
  const topK = parseInt(elements.queryTopK.value) || 5;

  try {
    elements.queryResults.textContent = "";
    const loading = document.createElement("div");
    loading.className = "empty-state";
    loading.textContent = t("status.loading");
    elements.queryResults.append(loading);

    const response = await fetch(`${state.knowledgeApiPath}/query`, {
      method: "POST",
      headers: authHeaders(),
      body: JSON.stringify({
        query,
        top_k: topK,
        mode,
      }),
    });

    if (!response.ok) {
      const errorData = await response.json();
      throw new Error(errorData.error?.message || errorData.error || t("knowledge.queryFailed"));
    }

    const result = await response.json();
    renderQueryResults(result);
  } catch (error) {
    console.error(error);
    elements.queryResults.textContent = "";
    const empty = document.createElement("div");
    empty.className = "empty-state error-text";
    empty.textContent = error.message || t("status.failed");
    elements.queryResults.append(empty);
  }
}

function renderQueryResults(result) {
  elements.queryResults.textContent = "";

  if (!result.data || result.data.length === 0) {
    const empty = document.createElement("div");
    empty.className = "empty-state";
    empty.textContent = t("knowledge.noResults");
    elements.queryResults.append(empty);
    return;
  }

  const items = result.data;
  const lowConfidence = items.every((item) => knowledgeRelevanceLevel(item) === "low");
  const summary = document.createElement("div");
  summary.className = `query-result-summary${lowConfidence ? " query-result-summary-low" : ""}`;
  const docs = [...new Set(items.map((item) => item.doc_name).filter(Boolean))].slice(0, 3);
  summary.textContent = lowConfidence
    ? t("knowledge.lowConfidence")
    : t("knowledge.resultSummary")
      .replace("{count}", items.length)
      .replace("{docs}", docs.length ? docs.join("、") : t("knowledge.unknownSource"));
  elements.queryResults.append(summary);

  for (const item of items) {
    const resultItem = document.createElement("div");
    resultItem.className = "query-result-item";
    resultItem.classList.add(`query-result-${knowledgeRelevanceLevel(item)}`);

    const header = document.createElement("div");
    header.className = "query-result-header";

    const docName = document.createElement("span");
    docName.className = "query-result-doc-name";
    docName.textContent = item.doc_name || "unknown";

    const score = document.createElement("span");
    score.className = "query-result-score";
    score.textContent = formatKnowledgeScore(item);

    header.append(docName, score);

    const meta = document.createElement("div");
    meta.className = "query-result-meta";
    const methods = item.matched_methods && item.matched_methods.length
      ? item.matched_methods.join("+")
      : (item.method || "unknown");
    const lineText = item.line_start && item.line_end
      ? `L${item.line_start}-${item.line_end}`
      : "";
  const parts = [
    methods,
    item.rerank_model ? `rerank ${item.rerank_model}` : "",
    item.section_path || "",
    lineText,
    item.block_type || "",
    ].filter(Boolean);
    meta.textContent = parts.join(" · ");

    const why = document.createElement("div");
    why.className = "query-result-why";
    why.textContent = queryResultWhyMatched(item);

    const content = document.createElement("div");
    content.className = "query-result-content";
    content.textContent = item.content || "";

    const debug = document.createElement("details");
    debug.className = "query-result-debug";
    const debugSummary = document.createElement("summary");
    debugSummary.textContent = t("knowledge.rawDetails");
    const debugBody = document.createElement("div");
    debugBody.textContent = formatKnowledgeDebug(item) || t("knowledge.noRawDetails");
    debug.append(debugSummary, debugBody);

    const actions = document.createElement("div");
    actions.className = "query-result-actions";
    const locate = document.createElement("button");
    locate.className = "button button-ghost button-small";
    locate.type = "button";
    locate.textContent = t("knowledge.highlightInGraph");
    locate.addEventListener("click", () => highlightKnowledgeQueryResult(item, result.query));
    actions.append(locate);

    resultItem.append(header, meta, why, content, debug, actions);
    elements.queryResults.append(resultItem);
  }
}

function knowledgeNumericScore(item) {
  if (item.rerank_score != null) return Number(item.rerank_score);
  if (item.rrf_score != null) return Number(item.rrf_score);
  if (item.semantic_fusion_score != null) return Number(item.semantic_fusion_score);
  if (item.semantic_score != null) return Number(item.semantic_score);
  if (item.bm25_score != null) return Number(item.bm25_score);
  if (item.score != null) return Number(item.score);
  return 0;
}

function knowledgeRelevanceLevel(item) {
  if (item.rerank_score != null) {
    return Number(item.rerank_score) >= 0.35 ? "high" : "low";
  }
  if (item.dense_distance != null && knowledgeNumericScore(item) <= 0) {
    return Number(item.dense_distance) <= 0.65 ? "medium" : "low";
  }
  const score = knowledgeNumericScore(item);
  if (score >= 0.5) return "high";
  if (score >= 0.18) return "medium";
  return "low";
}

function queryResultWhyMatched(item) {
  const reasons = [];
  if (item.section_path) {
    reasons.push(t("knowledge.reasonSection").replace("{section}", item.section_path));
  }
  if (item.matched_entities?.length) {
    reasons.push(t("knowledge.reasonEntities").replace("{entities}", item.matched_entities.slice(0, 3).join("、")));
  }
  if (item.matched_methods?.length || item.method) {
    const methods = item.matched_methods?.length ? item.matched_methods.join("+") : item.method;
    reasons.push(t("knowledge.reasonMethod").replace("{method}", methods));
  }
  if (item.content) {
    reasons.push(t("knowledge.reasonContent"));
  }
  return `${t("knowledge.whyMatched")} ${reasons.slice(0, 3).join("；") || t("knowledge.reasonFallback")}`;
}

function highlightKnowledgeQueryResult(item, query) {
  state.knowledgeGraphHighlight = {
    query,
    entities: item.matched_entities || [],
    relations: item.matched_relations || [],
    communities: item.matched_communities || [],
  };
  if (item.doc_id) {
    state.knowledgeGraphFilterDocId = item.doc_id;
    state.knowledgeGraphFilterDocName = item.doc_name || item.doc_id;
  }
  if (state.activeKnowledgeTab === "graph") {
    loadKnowledgeGraph();
  } else {
    setKnowledgeTab("graph");
  }
}

function formatKnowledgeScore(item) {
  if (item.rerank_score != null) {
    return `rerank ${Number(item.rerank_score).toFixed(4)}`;
  }
  if (item.rrf_score != null) {
    return `rrf ${Number(item.rrf_score).toFixed(4)}`;
  }
  if (item.semantic_fusion_score != null) {
    return `graph ${Number(item.semantic_fusion_score).toFixed(4)}`;
  }
  if (item.semantic_score != null) {
    return `semantic ${Number(item.semantic_score).toFixed(3)}`;
  }
  if (item.bm25_score != null) {
    return `bm25 ${Number(item.bm25_score).toFixed(3)}`;
  }
  if (item.dense_distance != null) {
    return `dist ${Number(item.dense_distance).toFixed(3)}`;
  }
  return `${Number(item.score || 0).toFixed(3)}`;
}

function formatKnowledgeDebug(item) {
  const parts = [];
  if (item.rerank_rank != null) {
    const rerank = item.rerank_score != null
      ? `rerank #${item.rerank_rank} score ${Number(item.rerank_score).toFixed(4)}`
      : `rerank #${item.rerank_rank}`;
    parts.push(rerank);
  }
  if (item.dense_rank != null) {
    const dense = item.dense_distance != null
      ? `dense #${item.dense_rank} dist ${Number(item.dense_distance).toFixed(3)}`
      : `dense #${item.dense_rank}`;
    parts.push(dense);
  }
  if (item.sparse_rank != null) {
    const sparse = item.bm25_score != null
      ? `sparse #${item.sparse_rank} bm25 ${Number(item.bm25_score).toFixed(3)}`
      : `sparse #${item.sparse_rank}`;
    parts.push(sparse);
  }
  if (item.semantic_rank != null) {
    const semantic = item.semantic_score != null
      ? `semantic #${item.semantic_rank} score ${Number(item.semantic_score).toFixed(3)}`
      : `semantic #${item.semantic_rank}`;
    parts.push(semantic);
  }
  if (item.matched_communities?.length) {
    parts.push(`communities: ${item.matched_communities.slice(0, 3).join("; ")}`);
  }
  if (item.matched_entities?.length) {
    parts.push(`entities: ${item.matched_entities.slice(0, 5).join(", ")}`);
  }
  if (item.matched_relations?.length) {
    parts.push(`relations: ${item.matched_relations.slice(0, 3).join("; ")}`);
  }
  if (item.matched_claims?.length) {
    const label = ["global", "drift"].includes(item.method) ? "findings" : "claims";
    parts.push(`${label}: ${item.matched_claims.slice(0, 3).join(" | ")}`);
  }
  return parts.join(" · ");
}

function updateQueryModeHint() {
  if (!elements.queryModeHint || !elements.queryMode) {
    return;
  }
  const hints = {
    hybrid: t("knowledge.queryHintHybrid"),
    dense: t("knowledge.queryHintDense"),
    sparse: t("knowledge.queryHintSparse"),
    semantic: t("knowledge.queryHintSemantic"),
    local: t("knowledge.queryHintLocal"),
    global: t("knowledge.queryHintGlobal"),
    drift: t("knowledge.queryHintDrift"),
  };
  elements.queryModeHint.textContent = hints[elements.queryMode.value] || "";
}

function toggleKnowledgePanel() {
  toggleCollapsibleSection(elements.knowledgeSection);
  elements.knowledgeToggle.setAttribute("aria-expanded",
    !elements.knowledgeSection.classList.contains("collapsed"));
}

function toggleCollapsibleSection(section) {
  if (section.classList.contains("collapsed")) {
    section.classList.remove("collapsed");
    section.setAttribute("aria-expanded", "true");
  } else {
    section.classList.add("collapsed");
    section.setAttribute("aria-expanded", "false");
  }
}

function toggleEditorPanel() {
  toggleCollapsibleSection(elements.editorSection);
  elements.editorToggle.setAttribute("aria-expanded",
    !elements.editorSection.classList.contains("collapsed"));
}

// ============ Theme Functions ============

function initTheme() {
  // 从 localStorage 读取主题偏好，默认为 light
  const savedTheme = localStorage.getItem("tinybot-theme") || "light";
  state.theme = savedTheme;
  applyTheme(savedTheme);
}

function applyTheme(theme) {
  document.documentElement.setAttribute("data-theme", theme);
  state.theme = theme;
  // 保存到 localStorage
  localStorage.setItem("tinybot-theme", theme);
  // 同步切换highlight.js主题
  const lightTheme = document.getElementById("hljs-light-theme");
  const darkTheme = document.getElementById("hljs-dark-theme");
  if (lightTheme && darkTheme) {
    if (theme === "dark") {
      lightTheme.disabled = true;
      darkTheme.disabled = false;
    } else {
      lightTheme.disabled = false;
      darkTheme.disabled = true;
    }
  }
  // 重新高亮所有代码块
  if (typeof hljs !== "undefined") {
    document.querySelectorAll(".message-text pre code").forEach((block) => {
      hljs.highlightElement(block);
    });
  }
}

function playThemeTransition() {
  const root = document.documentElement;
  if (window.matchMedia?.("(prefers-reduced-motion: reduce)").matches) {
    return;
  }
  root.classList.remove("theme-switching");
  // Restart the keyframe if the user toggles twice in quick succession.
  void root.offsetWidth;
  root.classList.add("theme-switching");
  window.setTimeout(() => {
    root.classList.remove("theme-switching");
  }, 620);
}

function toggleTheme() {
  const newTheme = state.theme === "light" ? "dark" : "light";
  playThemeTransition();
  applyTheme(newTheme);
}

function applyUiMode(mode) {
  state.uiMode = mode === "advanced" ? "advanced" : "basic";
  markAdvancedSettings();
  document.documentElement.dataset.uiMode = state.uiMode;
  localStorage.setItem("tinybot-ui-mode", state.uiMode);
  document.querySelectorAll("[data-ui-mode]").forEach((button) => {
    button.classList.toggle("active", button.dataset.uiMode === state.uiMode);
  });
}

function markAdvancedSettings() {
  const advancedGroups = ["embedding", "provider", "tools", "gateway", "channels"];
  for (const group of advancedGroups) {
    document.querySelector(`[data-group="${group}"]`)?.closest(".config-group")?.classList.add("advanced-only");
    document.querySelector(`[data-config-jump="${group}"]`)?.classList.add("advanced-only");
  }
  const advancedFieldIds = [
    "config-active-profile",
    "config-provider",
    "config-temperature",
    "config-max-tokens",
    "config-context-window",
    "config-max-tool-iterations",
    "config-reasoning-effort",
    "config-knowledge-max-chunks",
    "config-knowledge-chunk-size",
    "config-knowledge-chunk-overlap",
    "config-knowledge-retrieval-mode",
    "config-knowledge-rerank-enabled",
    "config-knowledge-rerank-model",
    "config-knowledge-rerank-api-key",
    "config-knowledge-rerank-api-key-env-var",
    "config-knowledge-rerank-api-base",
    "config-knowledge-rerank-top-n",
    "config-knowledge-generate-summary",
    "config-knowledge-semantic-extraction-mode",
    "config-knowledge-semantic-llm-max-tokens",
    "config-knowledge-semantic-llm-timeout",
    "config-knowledge-graphrag-community-algorithm",
    "config-knowledge-graphrag-community-level",
    "config-knowledge-graphrag-report-llm-enabled",
    "config-knowledge-graphrag-report-max-tokens",
    "config-knowledge-graphrag-entity-summary-enabled",
  ];
  for (const id of advancedFieldIds) {
    document.getElementById(id)?.closest(".config-field")?.classList.add("advanced-only");
  }
}

function openModal() {
  elements.modal.classList.add("active");
  elements.configError.textContent = "";
  elements.configSuccess.textContent = "";
  applyConfigSearch();
  updateConfigDirtyState();
  window.setTimeout(() => elements.configSearch?.focus(), 80);
}

function closeModal() {
  elements.modal.classList.remove("active");
}

async function loadConfig() {
  const response = await fetch("/api/config", {
    headers: { Authorization: `Bearer ${state.token}` },
  });
  if (!response.ok) {
    throw new Error(`load config failed: ${response.status}`);
  }
  const payload = await response.json();
  state.config = payload;
  populateConfigForm(payload);
}

function populateConfigForm(config) {
  // Agent defaults - API返回camelCase格式
  const agents = config.agents || {};
  const defaults = agents.defaults || {};

  // 填充所有字段，使用当前配置值（兼容camelCase和snake_case）
  elements.configWorkspace.value = defaults.workspace || defaults.workspacePath || "~/.tinybot/workspace";
  elements.configModel.value = defaults.model || "";
  if (elements.configActiveProfile) {
    elements.configActiveProfile.value = defaults.activeProfile || defaults.active_profile || "";
  }
  const tempValue = defaults.temperature !== undefined ? defaults.temperature : 0.1;
  elements.configTemperature.value = tempValue;
  // 更新 Temperature Slider 显示值
  const tempValueDisplay = document.getElementById("temperature-value");
  if (tempValueDisplay) {
    tempValueDisplay.textContent = tempValue;
  }
  elements.configMaxTokens.value = defaults.maxTokens || defaults.max_tokens || 8192;
  elements.configContextWindow.value = defaults.contextWindowTokens || defaults.context_window_tokens || 65536;
  state.contextWindowTokens = defaults.contextWindowTokens || defaults.context_window_tokens || 65536;
  elements.configMaxToolIterations.value = defaults.maxToolIterations || defaults.max_tool_iterations || 200;
  elements.configReasoningEffort.value = defaults.reasoningEffort || defaults.reasoning_effort || "";
  elements.configTimezone.value = defaults.timezone || "UTC";

  // Knowledge config
  const knowledge = config.knowledge || {};
  elements.configKnowledgeEnabled.checked = knowledge.enabled === true;
  elements.configKnowledgeAutoRetrieve.checked = knowledge.autoRetrieve || knowledge.auto_retrieve === true;
  elements.configKnowledgeMaxChunks.value = knowledge.maxChunks || knowledge.max_chunks || 5;
  elements.configKnowledgeChunkSize.value = knowledge.chunkSize || knowledge.chunk_size || 500;
  elements.configKnowledgeChunkOverlap.value = knowledge.chunkOverlap || knowledge.chunk_overlap || 100;
  elements.configKnowledgeRetrievalMode.value = knowledge.retrievalMode || knowledge.retrieval_mode || "hybrid";
  elements.configKnowledgeRerankEnabled.checked = knowledge.rerankEnabled || knowledge.rerank_enabled === true;
  elements.configKnowledgeRerankModel.value = knowledge.rerankModel || knowledge.rerank_model || "qwen3-rerank";
  elements.configKnowledgeRerankApiKey.value = knowledge.rerankApiKey || knowledge.rerank_api_key || "";
  elements.configKnowledgeRerankApiKeyEnvVar.value = knowledge.rerankApiKeyEnvVar || knowledge.rerank_api_key_env_var || "DASHSCOPE_API_KEY";
  elements.configKnowledgeRerankApiBase.value = knowledge.rerankApiBase || knowledge.rerank_api_base || "https://dashscope.aliyuncs.com/compatible-api/v1";
  elements.configKnowledgeRerankTopN.value = knowledge.rerankTopN || knowledge.rerank_top_n || 0;
  elements.configKnowledgeGenerateSummary.checked = knowledge.generateSummary || knowledge.generate_summary === true;
  elements.configKnowledgeSemanticExtractionMode.value = knowledge.semanticExtractionMode || knowledge.semantic_extraction_mode || "rule";
  elements.configKnowledgeSemanticLlmMaxTokens.value = knowledge.semanticLlmMaxTokens || knowledge.semantic_llm_max_tokens || 1200;
  elements.configKnowledgeSemanticLlmTimeout.value = knowledge.semanticLlmTimeout || knowledge.semantic_llm_timeout || 30.0;
  if (elements.configKnowledgeGraphRagCommunityAlgorithm) {
    elements.configKnowledgeGraphRagCommunityAlgorithm.value = knowledge.graphragCommunityAlgorithm || knowledge.graphrag_community_algorithm || "greedy";
  }
  if (elements.configKnowledgeGraphRagCommunityLevel) {
    const level = knowledge.graphragCommunityLevel ?? knowledge.graphrag_community_level ?? 0;
    elements.configKnowledgeGraphRagCommunityLevel.value = level;
    state.knowledgeGraphLevel = Number.parseInt(String(level), 10) || 0;
  }
  if (elements.configKnowledgeGraphRagReportLlmEnabled) {
    elements.configKnowledgeGraphRagReportLlmEnabled.checked = knowledge.graphragReportLlmEnabled || knowledge.graphrag_report_llm_enabled === true;
  }
  if (elements.configKnowledgeGraphRagReportMaxTokens) {
    elements.configKnowledgeGraphRagReportMaxTokens.value = knowledge.graphragReportMaxTokens || knowledge.graphrag_report_max_tokens || 1200;
  }
  if (elements.configKnowledgeGraphRagEntitySummaryEnabled) {
    elements.configKnowledgeGraphRagEntitySummaryEnabled.checked = knowledge.graphragEntitySummaryEnabled ?? knowledge.graphrag_entity_summary_enabled ?? true;
  }

  // Embedding config (nested in agents.defaults)
  const embedding = defaults.embedding || {};
  elements.configEmbeddingProvider.value = embedding.provider || "local";
  elements.configEmbeddingModelName.value = embedding.modelName || embedding.model_name || "all-MiniLM-L6-v2";
  elements.configEmbeddingApiKey.value = embedding.apiKey || embedding.api_key || "";
  elements.configEmbeddingApiBase.value = embedding.apiBase || embedding.api_base || "";

  // Providers - 根据当前provider选择加载对应的配置
  const providers = config.providers || {};
  const rawProviderName = defaults.provider || "auto";
  const currentProviderName = rawProviderName === "auto" || LLM_PROVIDERS.includes(rawProviderName)
    ? rawProviderName
    : "auto";
  elements.configProvider.value = currentProviderName;

  // Auto mode keeps model-based routing; show DeepSeek credentials by default.
  const displayProvider = currentProviderName === "auto" ? "deepseek" : currentProviderName;
  if (elements.configProfileId) {
    elements.configProfileId.value = defaults.activeProfile || defaults.active_profile || displayProvider;
  }
  elements.configProviderSelect.value = displayProvider;
  loadProviderConfig(providers, displayProvider);

  // Tools
  const tools = config.tools || {};
  const web = tools.web || {};
  elements.configWebEnable.checked = web.enable === true;
  elements.configWebProxy.value = web.proxy || "";
  const searchConfig = web.search || {};
  elements.configSearchProvider.value = searchConfig.provider || "duckduckgo";

  const exec = tools.exec || {};
  elements.configExecEnable.checked = exec.enable === true;
  elements.configExecTimeout.value = exec.timeout || 60;

  const mcpServers = tools.mcpServers || tools.mcp_servers || {};
  if (elements.configMcpServers) {
    elements.configMcpServers.value = Object.keys(mcpServers).length
      ? JSON.stringify(mcpServers, null, 2)
      : "";
  }

  elements.configRestrictWorkspace.checked = tools.restrictToWorkspace || tools.restrict_to_workspace === true;

  // Gateway
  const gateway = config.gateway || {};
  elements.configGatewayHost.value = gateway.host || "0.0.0.0";
  elements.configGatewayPort.value = gateway.port || 18790;

  const heartbeat = gateway.heartbeat || {};
  elements.configHeartbeatEnable.checked = heartbeat.enabled === true;
  elements.configHeartbeatInterval.value = heartbeat.intervalS || heartbeat.interval_s || 1800;

  // Channels
  const channels = config.channels || {};
  elements.configSendProgress.checked = channels.sendProgress || channels.send_progress === true;
  elements.configSendToolHints.checked = channels.sendToolHints || channels.send_tool_hints === true;
  elements.configSendRetries.value = channels.sendMaxRetries || channels.send_max_retries || 3;
  markConfigClean();
  applyConfigSearch();
}

function loadLegacyProviderConfigUnused(providers, providerName) {
  const provider = providers[providerName] || {};
  // API返回的是camelCase格式 (apiKey, apiBase)
  elements.configApiKey.value = provider.apiKey || provider.api_key || "";
  elements.configApiBase.value = provider.apiBase || provider.api_base || "";
}

function getProviderProfiles(providers = {}) {
  return providers.profiles || {};
}

function getProfileConfig(providers, profileId, fallbackProvider) {
  const profiles = getProviderProfiles(providers);
  if (profileId && profiles[profileId]) {
    return profiles[profileId];
  }
  const legacyProvider = providers[fallbackProvider] || {};
  return {
    provider: fallbackProvider,
    apiKey: legacyProvider.apiKey || legacyProvider.api_key || "",
    api_key: legacyProvider.api_key || legacyProvider.apiKey || "",
    apiBase: legacyProvider.apiBase || legacyProvider.api_base || "",
    api_base: legacyProvider.api_base || legacyProvider.apiBase || "",
    models: [],
    supportsModelDiscovery: true,
    supports_model_discovery: true,
  };
}

function setDatalistOptions(datalist, values) {
  if (!datalist) {
    return;
  }
  datalist.innerHTML = "";
  values.filter(Boolean).forEach((value) => {
    const option = document.createElement("option");
    option.value = value;
    datalist.appendChild(option);
  });
}

function setModelSelectOptions(models) {
  if (!elements.configModelSelect) {
    return;
  }
  const uniqueModels = Array.from(new Set(models.filter(Boolean)));
  const currentModel = elements.configModel?.value?.trim() || "";
  elements.configModelSelect.innerHTML = "";

  const placeholder = document.createElement("option");
  placeholder.value = "";
  placeholder.textContent = t("settings.provider.modelSelectPlaceholder");
  elements.configModelSelect.appendChild(placeholder);

  uniqueModels.forEach((model) => {
    const option = document.createElement("option");
    option.value = model;
    option.textContent = model;
    elements.configModelSelect.appendChild(option);
  });

  const hasModels = uniqueModels.length > 0;
  const modelInList = uniqueModels.includes(currentModel);
  elements.configModelSelect.hidden = !hasModels;
  elements.configModelSelect.value = modelInList ? currentModel : "";
  if (elements.configModel) {
    elements.configModel.hidden = hasModels && modelInList;
  }
}

function parseModelList(value) {
  return String(value || "")
    .split(/[\n,]/)
    .map((item) => item.trim())
    .filter(Boolean);
}

function refreshProfileHints(config) {
  const providers = config?.providers || {};
  const profiles = getProviderProfiles(providers);
  setDatalistOptions(elements.configProfileOptions, Object.keys(profiles));

  const activeProfile = elements.configActiveProfile?.value?.trim();
  const profileId = elements.configProfileId?.value?.trim() || activeProfile;
  const profile = profileId ? profiles[profileId] : null;
  const models = Array.isArray(profile?.models) ? profile.models : parseModelList(elements.configProviderModels?.value);
  setDatalistOptions(elements.configModelOptions, models);
  setModelSelectOptions(models);
}

function setModelDiscoveryStatus(message, kind = "") {
  if (!elements.configModelDiscoveryStatus) {
    return;
  }
  elements.configModelDiscoveryStatus.textContent = message || "";
  elements.configModelDiscoveryStatus.classList.remove("success", "warning");
  if (kind) {
    elements.configModelDiscoveryStatus.classList.add(kind);
  }
}

function scheduleModelDiscovery() {
  if (state.modelDiscoveryTimer) {
    window.clearTimeout(state.modelDiscoveryTimer);
  }
  state.modelDiscoveryTimer = window.setTimeout(() => {
    state.modelDiscoveryTimer = null;
    discoverProviderModels({ silent: true });
  }, 350);
}

async function discoverProviderModels({ silent = false } = {}) {
  if (!elements.configSupportsModelDiscovery?.checked) {
    return false;
  }
  const provider = elements.configProviderSelect?.value || "deepseek";
  const apiBase = elements.configApiBase?.value?.trim() || "";
  const apiKey = elements.configApiKey?.value?.trim() || "";
  const profile = elements.configProfileId?.value?.trim() || elements.configActiveProfile?.value?.trim() || "";

  if (!silent) {
    setModelDiscoveryStatus(t("settings.provider.modelsLoading"));
  }
  if (elements.configDiscoverModelsButton) {
    elements.configDiscoverModelsButton.disabled = true;
  }

  try {
    const response = await fetch("/api/provider-models", {
      method: "POST",
      headers: authHeaders(),
      body: JSON.stringify({
        provider,
        profile,
        api_key: apiKey,
        api_base: apiBase,
      }),
    });
    const result = await response.json();
    if (!response.ok || !result.ok || !Array.isArray(result.models) || !result.models.length) {
      throw new Error(result.error || `${t("settings.provider.modelsFailed")}: ${response.status}`);
    }
    elements.configProviderModels.value = result.models.join("\n");
    refreshProfileHints(state.config);
    if (!elements.configModel.value && result.models[0]) {
      elements.configModel.value = result.models[0];
    }
    setModelDiscoveryStatus(
      `${t("settings.provider.modelsLoaded")} ${result.models.length}`,
      "success",
    );
    updateConfigDirtyState();
    return true;
  } catch (error) {
    if (!silent) {
      setModelDiscoveryStatus(
        `${t("settings.provider.modelsFallback")} ${error.message || ""}`.trim(),
        "warning",
      );
    }
    refreshProfileHints(state.config);
    return false;
  } finally {
    if (elements.configDiscoverModelsButton) {
      elements.configDiscoverModelsButton.disabled = false;
    }
  }
}

function loadProviderConfig(providers, providerName) {
  const activeProfile = elements.configActiveProfile?.value?.trim() || "";
  const profileId = elements.configProfileId?.value?.trim() || activeProfile;
  const provider = getProfileConfig(providers, profileId, providerName);
  if (elements.configProfileId) {
    elements.configProfileId.value = profileId;
  }
  if (elements.configProviderSelect && provider.provider) {
    elements.configProviderSelect.value = provider.provider;
  }
  elements.configApiKey.value = provider.apiKey || provider.api_key || "";
  elements.configApiBase.value = provider.apiBase || provider.api_base || "";
  if (elements.configProviderModels) {
    const models = provider.models || [];
    elements.configProviderModels.value = Array.isArray(models) ? models.join("\n") : "";
  }
  if (elements.configSupportsModelDiscovery) {
    elements.configSupportsModelDiscovery.checked = provider.supportsModelDiscovery ?? provider.supports_model_discovery ?? true;
  }
  refreshProfileHints(state.config);
  scheduleModelDiscovery();
}

function getConfigGroups() {
  return Array.from(elements.modal.querySelectorAll(".config-group"));
}

function getConfigFields() {
  return Array.from(elements.modal.querySelectorAll("input, select, textarea"))
    .filter((field) => field.id && field.id.startsWith("config-") && field.id !== "config-search");
}

function getConfigFieldValue(field) {
  if (field.type === "checkbox") {
    return field.checked ? "1" : "0";
  }
  return field.value;
}

function captureConfigSnapshot() {
  const snapshot = {};
  getConfigFields().forEach((field) => {
    snapshot[field.id] = getConfigFieldValue(field);
  });
  return snapshot;
}

function getConfigGroupTitle(group) {
  const title = group.querySelector(".config-group-title");
  const label = title?.querySelector("[data-i18n]") || title;
  return label?.textContent?.trim() || "";
}

function markConfigClean() {
  state.configBaseline = captureConfigSnapshot();
  updateConfigDirtyState();
}

function getChangedConfigFields() {
  const baseline = state.configBaseline || {};
  return getConfigFields().filter((field) => baseline[field.id] !== getConfigFieldValue(field));
}

function updateConfigDirtyState() {
  const changedFields = getChangedConfigFields();
  const changedIds = new Set(changedFields.map((field) => field.id));
  const dirtyGroups = new Set();

  getConfigFields().forEach((field) => {
    const isDirty = changedIds.has(field.id);
    field.closest(".config-field")?.classList.toggle("dirty", isDirty);
    if (isDirty) {
      const groupTitle = field.closest(".config-group")?.querySelector(".config-group-title");
      if (groupTitle) {
        dirtyGroups.add(getConfigGroupTitle(field.closest(".config-group")));
      }
    }
  });

  getConfigGroups().forEach((group) => {
    const title = group.querySelector(".config-group-title");
    title?.classList.toggle("dirty", dirtyGroups.has(getConfigGroupTitle(group)));
  });

  if (elements.saveConfigButton) {
    elements.saveConfigButton.disabled = changedFields.length === 0;
  }
  if (elements.resetConfigButton) {
    elements.resetConfigButton.disabled = changedFields.length === 0;
  }
  if (elements.configDirtySummary) {
    if (!changedFields.length) {
      elements.configDirtySummary.textContent = t("settings.noChanges");
    } else {
      const groupText = Array.from(dirtyGroups).filter(Boolean).slice(0, 3).join(", ");
      elements.configDirtySummary.textContent = `${t("settings.unsavedChanges")} ${changedFields.length}${groupText ? ` · ${groupText}` : ""}`;
    }
  }
}

function applyConfigSearch() {
  const query = (elements.configSearch?.value || "").trim().toLowerCase();
  let visibleGroups = 0;

  getConfigGroups().forEach((group) => {
    const title = group.querySelector(".config-group-title");
    const groupText = getConfigGroupTitle(group).toLowerCase();
    const fields = Array.from(group.querySelectorAll(".config-field"));
    let visibleFields = 0;

    fields.forEach((field) => {
      const fieldText = [
        field.textContent,
        field.querySelector("input, select, textarea")?.value,
        field.querySelector("input, select, textarea")?.placeholder,
        field.querySelector(".config-help")?.getAttribute("data-help"),
      ].filter(Boolean).join(" ").toLowerCase();
      const isVisible = !query || groupText.includes(query) || fieldText.includes(query);
      field.hidden = !isVisible;
      if (isVisible) {
        visibleFields += 1;
      }
    });

    const groupVisible = !query || visibleFields > 0 || groupText.includes(query);
    group.hidden = !groupVisible;
    if (groupVisible) {
      visibleGroups += 1;
      if (query && title) {
        setConfigGroupExpanded(title, true);
      }
    }
  });

  if (elements.configEmptySearch) {
    elements.configEmptySearch.hidden = visibleGroups > 0;
  }
}

function jumpToConfigGroup(groupName) {
  if (elements.configSearch?.value) {
    elements.configSearch.value = "";
    applyConfigSearch();
  }
  const title = elements.modal.querySelector(`.config-group-title[data-group="${groupName}"]`);
  if (!title) {
    return;
  }
  setConfigGroupExpanded(title, true);
  elements.configQuickNav?.querySelectorAll(".config-nav-chip").forEach((button) => {
    button.classList.toggle("active", button.dataset.configJump === groupName);
  });
  title.closest(".config-group")?.scrollIntoView({ block: "start", behavior: "smooth" });
  title.focus({ preventScroll: true });
}

function toggleConfigGroup(groupTitle) {
  const groupContent = groupTitle.nextElementSibling;
  const isCollapsed = groupContent.classList.contains("collapsed");
  if (isCollapsed) {
    groupContent.classList.remove("collapsed");
    groupTitle.classList.remove("collapsed");
    groupTitle.setAttribute("aria-expanded", "true");
  } else {
    groupContent.classList.add("collapsed");
    groupTitle.classList.add("collapsed");
    groupTitle.setAttribute("aria-expanded", "false");
  }
}

function setConfigGroupExpanded(groupTitle, expanded) {
  const groupContent = groupTitle.nextElementSibling;
  if (!groupContent) {
    return;
  }
  groupContent.classList.toggle("collapsed", !expanded);
  groupTitle.classList.toggle("collapsed", !expanded);
  groupTitle.setAttribute("aria-expanded", expanded ? "true" : "false");
}

function syncConfigGroupStates() {
  document.querySelectorAll(".config-group-title.clickable").forEach((title) => {
    const groupContent = title.nextElementSibling;
    if (!groupContent) {
      return;
    }
    setConfigGroupExpanded(title, !groupContent.classList.contains("collapsed"));
  });
}

// ========== 实时验证系统 ==========

function setupValidation() {
  // Model 验证 - 非空
  if (elements.configModel) {
    elements.configModel.addEventListener("input", () => {
      const ok = elements.configModel.value.trim().length > 0;
      setFieldState(elements.configModel, ok ? "neutral" : "error", "modelEmpty");
    });
  }

  // Timezone 验证 - 格式
  if (elements.configTimezone) {
    elements.configTimezone.addEventListener("input", () => {
      validateField(elements.configTimezone, validateTimezone, "timezoneError");
    });
  }

  // Gateway Port 验证 - 范围
  if (elements.configGatewayPort) {
    elements.configGatewayPort.addEventListener("input", () => {
      validateField(elements.configGatewayPort, validatePortRange, "portRange");
    });
  }

  if (elements.configMcpServers) {
    elements.configMcpServers.addEventListener("input", () => {
      const val = elements.configMcpServers.value.trim();
      if (val === "") {
        setFieldState(elements.configMcpServers, "neutral");
        return;
      }
      validateField(elements.configMcpServers, validateJsonObject, "jsonObjectError");
    });
  }

  // API Base URL 验证
  if (elements.configApiBase) {
    elements.configApiBase.addEventListener("input", () => {
      const val = elements.configApiBase.value.trim();
      if (val === "") {
        setFieldState(elements.configApiBase, "neutral");
      } else {
        validateField(elements.configApiBase, validateUrl, "urlError");
      }
    });
  }

  // Embedding API Base URL 验证
  const embeddingApiBase = document.getElementById("config-embedding-api-base");
  if (embeddingApiBase) {
    embeddingApiBase.addEventListener("input", () => {
      const val = embeddingApiBase.value.trim();
      if (val === "") {
        setFieldState(embeddingApiBase, "neutral");
      } else {
        validateField(embeddingApiBase, validateUrl, "urlError");
      }
    });
  }

  // Rerank API Base URL 验证
  const rerankApiBase = document.getElementById("config-knowledge-rerank-api-base");
  if (rerankApiBase) {
    rerankApiBase.addEventListener("input", () => {
      const val = rerankApiBase.value.trim();
      if (val === "") {
        setFieldState(rerankApiBase, "neutral");
      } else {
        validateField(rerankApiBase, validateUrl, "urlError");
      }
    });
  }
}

function validateField(input, validator, errorKey) {
  const value = input.value.trim();
  if (value === "" && errorKey !== "modelEmpty") {
    setFieldState(input, "neutral");
    return true;
  }
  const isValid = validator(value);
  if (isValid) {
    setFieldState(input, "success");
  } else {
    setFieldState(input, "error", errorKey);
  }
  return isValid;
}

function setFieldState(input, state, errorKey = null) {
  // 清除之前的状态
  input.classList.remove("error", "success");
  // 移除验证提示
  const parent = input.closest(".config-field") || input.closest(".config-sensitive-group");
  if (parent) {
    const existingMsg = parent.querySelector(".config-validation-msg");
    if (existingMsg) {
      existingMsg.remove();
    }
  }

  if (state === "neutral") {
    return;
  }

  input.classList.add(state);

  // 添加验证提示
  if (parent && (errorKey || state === "success")) {
    const msgDiv = document.createElement("div");
    msgDiv.className = `config-validation-msg ${state}`;
    if (state === "success") {
      msgDiv.innerHTML = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 6L9 17l-5-5"/></svg><span>${t("settings.validation.valid")}</span>`;
    } else if (errorKey) {
      msgDiv.innerHTML = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" y2="15"/></svg><span>${t(`settings.validation.${errorKey}`)}</span>`;
    }
    parent.appendChild(msgDiv);
  }
}

function validateTimezone(value) {
  // 简单验证：格式应该是 Area/City 或 Area/SubArea/City
  if (!value) return false;
  const parts = value.split("/");
  if (parts.length < 2) return false;
  // 检查常见时区前缀
  const validPrefixes = ["Africa", "America", "Asia", "Atlantic", "Australia", "Europe", "Indian", "Pacific", "UTC", "GMT"];
  return validPrefixes.some(p => parts[0] === p) || parts[0] === "Etc";
}

function validateUrl(value) {
  try {
    new URL(value);
    return true;
  } catch {
    return false;
  }
}

function validatePortRange(value) {
  const port = parseInt(value, 10);
  return port >= 1 && port <= 65535;
}

function validateJsonObject(value) {
  try {
    const parsed = JSON.parse(value);
    return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed);
  } catch {
    return false;
  }
}

async function saveConfig() {
  elements.configError.textContent = "";
  elements.configSuccess.textContent = "";
  elements.saveConfigButton.disabled = true;
  elements.saveConfigButton.classList.add("loading");

  // Helper: only include non-empty values
  const getValue = (el, type = "string") => {
    let val = type === "number" ? (el.value ? parseFloat(el.value) || parseInt(el.value) : null) : el.value.trim();
    if (type === "string" && val === "") val = null;
    if (type === "number" && (val === null || val === "" || isNaN(val))) val = null;
    return val;
  };

  const parseJsonObject = (el, fallback = {}) => {
    if (!el || el.value.trim() === "") {
      return fallback;
    }
    const parsed = JSON.parse(el.value);
    if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
      throw new Error(t("settings.validation.jsonObjectError"));
    }
    return parsed;
  };

  let mcpServers = {};
  try {
    mcpServers = parseJsonObject(elements.configMcpServers, {});
  } catch (error) {
    elements.configError.textContent = error.message || t("settings.validation.jsonObjectError");
    if (elements.configMcpServers) {
      setFieldState(elements.configMcpServers, "error", "jsonObjectError");
    }
    updateConfigDirtyState();
    elements.saveConfigButton.classList.remove("loading");
    return;
  }

  // Build payload - matching backend schema structure
  // agents.defaults contains the actual agent config fields
  const payload = {
    agents: {
      defaults: {
        model: getValue(elements.configModel),
        active_profile: getValue(elements.configActiveProfile),
        provider: getValue(elements.configProvider),
        workspace: getValue(elements.configWorkspace),
        temperature: getValue(elements.configTemperature, "number"),
        max_tokens: getValue(elements.configMaxTokens, "number"),
        context_window_tokens: getValue(elements.configContextWindow, "number"),
        max_tool_iterations: getValue(elements.configMaxToolIterations, "number"),
        reasoning_effort: getValue(elements.configReasoningEffort),
        timezone: getValue(elements.configTimezone),
        embedding: {
          provider: getValue(elements.configEmbeddingProvider),
          model_name: getValue(elements.configEmbeddingModelName),
          api_key: (() => { const v = getValue(elements.configEmbeddingApiKey); return v === null ? "" : v; })(),  // api_key schema requires string
          api_base: getValue(elements.configEmbeddingApiBase),
        },
      },
    },
    knowledge: {
      enabled: elements.configKnowledgeEnabled.checked,
      auto_retrieve: elements.configKnowledgeAutoRetrieve.checked,
      max_chunks: getValue(elements.configKnowledgeMaxChunks, "number"),
      chunk_size: getValue(elements.configKnowledgeChunkSize, "number"),
      chunk_overlap: getValue(elements.configKnowledgeChunkOverlap, "number"),
      retrieval_mode: getValue(elements.configKnowledgeRetrievalMode),
      rerank_enabled: elements.configKnowledgeRerankEnabled.checked,
      rerank_model: getValue(elements.configKnowledgeRerankModel),
      rerank_api_key: getValue(elements.configKnowledgeRerankApiKey),
      rerank_api_key_env_var: getValue(elements.configKnowledgeRerankApiKeyEnvVar),
      rerank_api_base: getValue(elements.configKnowledgeRerankApiBase),
      rerank_top_n: getValue(elements.configKnowledgeRerankTopN, "number"),
      generate_summary: elements.configKnowledgeGenerateSummary.checked,
      semantic_extraction_mode: getValue(elements.configKnowledgeSemanticExtractionMode),
      semantic_llm_max_tokens: getValue(elements.configKnowledgeSemanticLlmMaxTokens, "number"),
      semantic_llm_timeout: getValue(elements.configKnowledgeSemanticLlmTimeout, "number"),
      graphrag_community_algorithm: getValue(elements.configKnowledgeGraphRagCommunityAlgorithm),
      graphrag_community_level: getValue(elements.configKnowledgeGraphRagCommunityLevel, "number"),
      graphrag_report_llm_enabled: elements.configKnowledgeGraphRagReportLlmEnabled?.checked === true,
      graphrag_report_max_tokens: getValue(elements.configKnowledgeGraphRagReportMaxTokens, "number"),
      graphrag_entity_summary_enabled: elements.configKnowledgeGraphRagEntitySummaryEnabled?.checked !== false,
    },
    tools: {
      web: {
        enable: elements.configWebEnable.checked,
        proxy: getValue(elements.configWebProxy),
        search: {
          provider: getValue(elements.configSearchProvider),
        },
      },
      exec: {
        enable: elements.configExecEnable.checked,
        timeout: getValue(elements.configExecTimeout, "number"),
      },
      mcp_servers: mcpServers,
      restrict_to_workspace: elements.configRestrictWorkspace.checked,
    },
    gateway: {
      host: getValue(elements.configGatewayHost),
      port: getValue(elements.configGatewayPort, "number"),
      heartbeat: {
        enabled: elements.configHeartbeatEnable.checked,
        interval_s: getValue(elements.configHeartbeatInterval, "number"),
      },
    },
    channels: {
      send_progress: elements.configSendProgress.checked,
      send_tool_hints: elements.configSendToolHints.checked,
      send_max_retries: getValue(elements.configSendRetries, "number"),
    },
    providers: {},
  };

  // Add selected provider config and named profile. Keep the legacy provider
  // section populated so older configs continue to work.
  const providerName = LLM_PROVIDERS.includes(elements.configProviderSelect.value)
    ? elements.configProviderSelect.value
    : "deepseek";
  const apiKeyValue = getValue(elements.configApiKey);
  const existingProfiles = { ...getProviderProfiles(state.config?.providers || {}) };
  const profileId = getValue(elements.configProfileId) || getValue(elements.configActiveProfile);
  if (profileId) {
    payload.agents.defaults.active_profile = profileId;
    payload.providers.profiles = {
      ...existingProfiles,
      [profileId]: {
        provider: providerName,
        api_key: apiKeyValue === null ? "" : apiKeyValue,
        api_base: getValue(elements.configApiBase),
        models: parseModelList(elements.configProviderModels?.value),
        supports_model_discovery: elements.configSupportsModelDiscovery?.checked === true,
      },
    };
  }
  payload.providers[providerName] = {
    api_key: apiKeyValue === null ? "" : apiKeyValue,  // api_key schema requires string, not None
    api_base: getValue(elements.configApiBase),
  };

  try {
    const response = await fetch("/api/config", {
      method: "PATCH",
      headers: authHeaders(),
      body: JSON.stringify(payload),
    });

    if (!response.ok) {
      const errorData = await response.json();
      throw new Error(errorData.error || `${t("settings.saveFailed")}: ${response.status}`);
    }

    const result = await response.json();
    elements.configSuccess.textContent = t("settings.saved");
    state.config = result.config;
    markConfigClean();
    await loadSystemStatus();
  } catch (error) {
    elements.configError.textContent = error.message || t("settings.saveFailed");
    updateConfigDirtyState();
  } finally {
    elements.saveConfigButton.classList.remove("loading");
  }
}

async function loadFile(path, { preserveDraft = false } = {}) {
  const response = await fetch(`${state.workspaceFilesPath}/${encodeURIComponent(path)}`, {
    headers: { Authorization: `Bearer ${state.token}` },
  });
  if (!response.ok) {
    throw new Error(`load file failed: ${response.status}`);
  }

  const payload = await response.json();
  state.activeFilePath = payload.path;
  state.activeFileUpdatedAt = payload.updated_at;
  state.fileDraftDirty = false;
  // Update modal title and sidebar file name
  if (elements.workspaceModalTitle) {
    elements.workspaceModalTitle.textContent = payload.path;
  }
  if (elements.editorTitle) {
    elements.editorTitle.textContent = payload.path;
  }
  elements.fileSelect.value = payload.path;
  elements.fileEditor.value = preserveDraft ? elements.fileEditor.value : payload.content || "";
  elements.fileMeta.textContent = payload.updated_at
    ? `${t("ui.lastUpdate")} ${formatTime(payload.updated_at)}`
    : t("ui.fileNotCreated");
  // Update sidebar compact file name
  if (elements.currentFileName) {
    elements.currentFileName.textContent = payload.path;
  }
  setEditorStatus(t("status.loaded"), "connected");
  setFileError("");
}

async function saveActiveFile() {
  if (!state.activeFilePath) {
    return;
  }

  setEditorStatus(t("status.saving"), "idle");
  setFileError("");

  const response = await fetch(`${state.workspaceFilesPath}/${encodeURIComponent(state.activeFilePath)}`, {
    method: "PUT",
    headers: authHeaders(),
    body: JSON.stringify({
      content: elements.fileEditor.value,
      expected_updated_at: state.activeFileUpdatedAt,
    }),
  });

  if (response.status === 409) {
    const payload = await response.json();
    setEditorStatus(t("status.conflict"), "error");
    setFileError(t("msg.versionConflict"));
    state.activeFileUpdatedAt = payload.updated_at || null;
    return;
  }

  if (!response.ok) {
    throw new Error(`save file failed: ${response.status}`);
  }

  const payload = await response.json();
  state.activeFileUpdatedAt = payload.updated_at;
  state.fileDraftDirty = false;
  elements.fileMeta.textContent = `${t("ui.lastUpdate")} ${formatTime(payload.updated_at)}`;
  setEditorStatus(t("status.saved"), "connected");
  await loadEditableFiles();
}

function websocketUrl() {
  const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
  return `${protocol}//${window.location.host}${state.wsPath}?token=${encodeURIComponent(state.token)}`;
}

function ensureMessageBucket(sessionKey) {
  if (!state.messages.has(sessionKey)) {
    state.messages.set(sessionKey, []);
  }
  return state.messages.get(sessionKey);
}

function pushMessage(sessionKey, message) {
  const bucket = ensureMessageBucket(sessionKey);
  bucket.push(message);
  const item = state.sessionItems.find((entry) => entry.key === sessionKey);
  if (item && !item.title && message.role === "user") {
    item.title = compactSessionTitleFromMessages(bucket);
  }
  if (sessionKey === state.activeSessionKey) {
    updateActiveChatTitle();
    renderSessions();
    renderMessages(false);
  }
}

function upsertTaskProgressMessage(sessionKey, message) {
  const bucket = ensureMessageBucket(sessionKey);
  const data = taskProgressPayload(message) || {};
  const progress = data.progress || {};
  const planId = data.plan_id || progress.plan_id || message._task_plan_id || "";
  let existing = null;
  if (planId) {
    for (let index = bucket.length - 1; index >= 0; index -= 1) {
      if (bucket[index]._task_event && bucket[index]._task_plan_id === planId) {
        existing = bucket[index];
        break;
      }
    }
  }

  if (existing) {
    Object.assign(existing, message, {
      timestamp: new Date().toISOString(),
      message_id: existing.message_id || message.message_id,
    });
  } else {
    bucket.push(message);
  }

  if (sessionKey === state.activeSessionKey) {
    updateActiveChatTitle();
    renderSessions();
    renderMessages(false);
  }
}

function upsertStreamMessage(chatId, messageId, deltaText, isReasoning = false) {
  const sessionKey = sessionKeyForChat(chatId);
  const bucket = ensureMessageBucket(sessionKey);
  let streamState = state.streamBuffers.get(messageId);
  const isNewMessage = !streamState;
  const hadContentBefore = Boolean((streamState?.entry?.content || "").trim());

  if (isNewMessage) {
    const entry = {
      role: "assistant",
      content: "",
      reasoning_content: "",
      timestamp: new Date().toISOString(),
      message_id: messageId,
    };
    bucket.push(entry);
    streamState = { sessionKey, entry };
    state.streamBuffers.set(messageId, streamState);
  }

  if (isReasoning) {
    streamState.entry.reasoning_content += deltaText;
  } else {
    streamState.entry.content += deltaText;
  }

  if (sessionKey === state.activeSessionKey) {
    if (isNewMessage || (!hadContentBefore && !isReasoning && deltaText.trim())) {
      // Render the list when a new visible answer starts so the streaming node is placed.
      renderMessages(false);
    } else {
      updateStreamMessageDOM(messageId);
    }
  }
}

async function connectWebSocket() {
  return new Promise((resolve, reject) => {
    const socket = new WebSocket(websocketUrl());
    state.socket = socket;

    socket.addEventListener("open", () => {
      setStatus(t("status.connected"), "connected");
      setError("");
    });

    socket.addEventListener("message", async (event) => {
      const payload = JSON.parse(event.data);
      const agentUiEvents = normalizeAgentUiEvents(payload);
      for (const agentUiEvent of agentUiEvents) {
        reduceAgentUiEventState(state.agentUi, agentUiEvent);
      }

      if (payload.event === "ready") {
        resolve();
        return;
      }

      if (payload.event === "chat_created") {
        activateChat(payload.chat_id);
        await loadSessions();
        setStatus(t("status.connected"), "connected");

        // 如果有待发送的消息，自动发送
        if (state.pendingMessage) {
          const content = state.pendingMessage;
          state.pendingMessage = null;

          const sessionKey = state.activeSessionKey;
          pushMessage(sessionKey, {
            role: "user",
            content,
            timestamp: new Date().toISOString(),
          });

          setSessionResponding(sessionKey, true);
          try {
            sendSocketMessage({
              type: "message",
              chat_id: state.activeChatId,
              content,
              use_persistent_rag: elements.persistentRagToggle?.checked !== false,
            });
          } catch (error) {
            setSessionResponding(sessionKey, false);
            throw error;
          }
        }
        return;
      }

      if (payload.event === "attached") {
        activateChat(payload.chat_id);
        await loadMessages(sessionKeyForChat(payload.chat_id));
        return;
      }

      if (payload.event === "agent_ui_event") {
        for (const agentUiEvent of agentUiEvents) {
          if (isAgentUiFormEvent(agentUiEvent)) {
            upsertAgentUiFormMessage(agentUiEvent);
            if (agentUiEvent.event_type === AGENT_UI_EVENT_TYPES["ui.form.requested"]) {
              setSessionResponding(sessionKeyForChat(agentUiEvent.chat_id || payload.chat_id), false);
            }
          } else if (agentUiEvent.event_type === AGENT_UI_EVENT_TYPES["error.raised"]) {
            renderAgentUiSurface(agentUiRenderers, AGENT_UI_RENDERER_SURFACES.errorNotice, {
              message: agentUiEvent.payload.message || t("status.serverError"),
              path: agentUiEvent.payload.path || "",
            });
            setSessionResponding(sessionKeyForChat(agentUiEvent.chat_id || payload.chat_id), false);
          } else if (
            agentUiEvent.event_type === AGENT_UI_EVENT_TYPES["message.completed"] ||
            agentUiEvent.event_type === AGENT_UI_EVENT_TYPES["message.stream.completed"]
          ) {
            setSessionResponding(sessionKeyForChat(agentUiEvent.chat_id || payload.chat_id), false);
          }
        }
        return;
      }

      if (payload.event === "delta") {
        for (const agentUiEvent of agentUiEvents) {
          if (
            agentUiEvent.event_type === AGENT_UI_EVENT_TYPES["message.delta"] ||
            agentUiEvent.event_type === AGENT_UI_EVENT_TYPES["reasoning.delta"]
          ) {
            upsertStreamMessage(
              agentUiEvent.chat_id,
              agentUiEvent.message_id,
              agentUiEvent.payload.text || "",
              agentUiEvent.event_type === AGENT_UI_EVENT_TYPES["reasoning.delta"],
            );
          }
        }
        return;
      }

      if (payload.event === "message") {
        // Check if this is a progress/tool hint message
        if (payload._progress) {
          const taskProgress = payload._task_progress || null;
          const planId = taskProgress?.plan_id || taskProgress?.progress?.plan_id || "";
          if (payload._task_event) {
            upsertTaskProgressMessage(sessionKeyForChat(payload.chat_id), {
              role: "progress",
              content: payload.text || "",
              timestamp: new Date().toISOString(),
              message_id: payload.message_id || (planId ? `task-${planId}` : crypto.randomUUID()),
              _task_event: true,
              _task_progress: taskProgress,
              _task_plan_id: planId,
              _tool_name: "task",
            });
            return;
          }
          // Progress messages are temporary hints, not persisted messages
          pushMessage(sessionKeyForChat(payload.chat_id), {
            role: "progress",
            content: payload.text || "",
            timestamp: new Date().toISOString(),
            _tool_hint: payload._tool_hint || false,
            _tool_detail: payload._tool_detail || false,
            _tool_result: payload._tool_result || false,
            _tool_name: payload._tool_name || "",
            _approval_status: payload._approval_status || "",
            _approval_id: payload._approval_id || "",
          });
        } else {
          // Regular assistant message
          setSessionResponding(sessionKeyForChat(payload.chat_id), false);
          pushMessage(sessionKeyForChat(payload.chat_id), {
            role: "assistant",
            content: payload.text || "",
            timestamp: new Date().toISOString(),
            message_id: payload.message_id || "",
            _memory_references: payload._memory_references || [],
            _recent_context_references: payload._recent_context_references || [],
          });
        }
        renderAgentUiSurface(agentUiRenderers, AGENT_UI_RENDERER_SURFACES.approval, {
          sessionKey: sessionKeyForChat(payload.chat_id),
        });
        return;
      }

      if (payload.event === "approval_pending") {
        setSessionResponding(sessionKeyForChat(payload.chat_id), false);
        renderAgentUiSurface(agentUiRenderers, AGENT_UI_RENDERER_SURFACES.approval, {
          sessionKey: sessionKeyForChat(payload.chat_id),
        });
        return;
      }

      if (payload.event === "browser_frame" || payload.event === "browser_snapshot") {
        const browserEvent = agentUiEvents.find((item) => item.event_type === AGENT_UI_EVENT_TYPES["browser.frame.updated"]);
        renderAgentUiSurface(agentUiRenderers, AGENT_UI_RENDERER_SURFACES.browserSnapshot, {
          frame: browserEvent ? {
            ...payload,
            image_url: browserEvent.payload.image_url,
            url: browserEvent.payload.image_url || payload.url,
            source_command: browserEvent.payload.command,
            captured_at: browserEvent.payload.captured_at,
          } : payload,
        });
        return;
      }

      if (payload.event === "stream_end") {
        if (payload.message_id) {
          const streamState = state.streamBuffers.get(payload.message_id);
          const memoryReferences = state.agentUi.memoryReferences.get(payload.message_id);
          const recentContextReferences = state.agentUi.recentContextReferences.get(payload.message_id);
          if (streamState?.entry) {
            streamState.entry._stream_resuming = payload.resuming === true;
            if (Array.isArray(memoryReferences)) {
              streamState.entry._memory_references = memoryReferences;
            }
            if (Array.isArray(recentContextReferences)) {
              streamState.entry._recent_context_references = recentContextReferences;
            }
          }
          state.streamBuffers.delete(payload.message_id);
          if (payload.resuming !== true && streamState?.sessionKey) {
            setSessionResponding(streamState.sessionKey, false);
          }
          if (payload.resuming !== true && streamState?.sessionKey === state.activeSessionKey) {
            renderMessages(false);
          }
          if (streamState?.sessionKey) {
            renderAgentUiSurface(agentUiRenderers, AGENT_UI_RENDERER_SURFACES.approval, {
              sessionKey: streamState.sessionKey,
            });
          }
        }
        return;
      }

      if (payload.event === "usage") {
        renderAgentUiSurface(agentUiRenderers, AGENT_UI_RENDERER_SURFACES.usageStatus, {
          usage: state.agentUi.usage || payload.usage,
        });
        return;
      }

      if (payload.event === "file_updated") {
        await loadEditableFiles();
        if (payload.path === state.activeFilePath && !state.fileDraftDirty) {
          await loadFile(payload.path);
        } else if (payload.path === state.activeFilePath) {
          setFileError(t("msg.externalFileUpdate"));
          setEditorStatus(t("status.externalUpdate"), "error");
        }
        return;
      }

      if (payload.event === "cowork_updated") {
        if (!state.activeCoworkSessionId || payload.session_id === state.activeCoworkSessionId) {
          scheduleCoworkRefresh(payload.session_id || "");
        } else {
          scheduleCoworkRefresh("");
        }
        return;
      }

      if (payload.event === "error") {
        const errorEvent = agentUiEvents.find((item) => item.event_type === AGENT_UI_EVENT_TYPES["error.raised"]);
        const message = errorEvent?.payload.message || payload.message || t("status.serverError");
        renderAgentUiSurface(agentUiRenderers, AGENT_UI_RENDERER_SURFACES.errorNotice, {
          message,
          path: payload.path,
        });
        setSessionResponding(sessionKeyForChat(payload.chat_id || state.activeChatId), false);
      }
    });

    socket.addEventListener("close", () => {
      setStatus(t("status.disconnected"), "error");
      clearAllSessionResponding();
    });

    socket.addEventListener("error", () => {
      setStatus(t("status.failed"), "error");
      clearAllSessionResponding();
      reject(new Error("websocket connection failed"));
    });
  });
}

function sendSocketMessage(payload) {
  if (!state.socket || state.socket.readyState !== WebSocket.OPEN) {
    throw new Error("websocket is not connected");
  }
  state.socket.send(JSON.stringify(payload));
}

async function createNewChat() {
  sendSocketMessage({ type: "new_chat" });
}

async function attachSession(chatId) {
  closeInspectionMode();
  sendSocketMessage({ type: "attach", chat_id: chatId });
}

async function submitMessage() {
  const content = elements.composerInput.value.trim();
  if (!content) {
    return;
  }

  // 触发发送按钮动画
  const sendBtn = elements.composerForm.querySelector(".composer-send");
  if (sendBtn) {
    sendBtn.classList.add("sending");
    setTimeout(() => {
      sendBtn.classList.remove("sending");
    }, 600);
  }

  // 如果没有活跃会话，先创建新会话
  if (!state.activeChatId) {
    state.pendingMessage = content;  // 保存待发送的消息
    elements.composerInput.value = "";
    resizeComposer();
    setError("");
    setStatus(t("status.creatingSession"), "idle");
    createNewChat();
    return;
  }

  const sessionKey = state.activeSessionKey;
  pushMessage(sessionKey, {
    role: "user",
    content,
    timestamp: new Date().toISOString(),
  });

  elements.composerInput.value = "";
  resizeComposer();
  setError("");

  setSessionResponding(sessionKey, true);
  try {
    sendSocketMessage({
      type: "message",
      chat_id: state.activeChatId,
      content,
      use_persistent_rag: elements.persistentRagToggle?.checked !== false,
    });
  } catch (error) {
    setSessionResponding(sessionKey, false);
    throw error;
  }
}

function bindEvents() {
  document.querySelectorAll("[data-ui-mode]").forEach((button) => {
    button.addEventListener("click", () => applyUiMode(button.dataset.uiMode));
  });

  elements.messageList?.addEventListener("click", (event) => {
    const target = event.target instanceof Element ? event.target : event.target?.parentElement;
    const summary = target?.closest(".run-chain-summary");
    if (!summary || !elements.messageList.contains(summary)) return;
    event.preventDefault();
    toggleRunChain(summary.closest(".run-chain"));
  });

  document.addEventListener("click", (event) => {
    if (!state.inspectionOpen) return;
    const target = event.target instanceof Element ? event.target : event.target?.parentElement;
    if (!target) return;
    if (elements.inspectorPanel?.contains(target)) return;
    if (target.closest(".run-chain-item")) return;
    if (target.closest(".memory-references")) return;
    closeInspectionMode();
  }, true);

  elements.composerInput.addEventListener("input", resizeComposer);
  elements.composerInput.addEventListener("keydown", async (event) => {
    if (event.key === "Enter" && !event.shiftKey) {
      event.preventDefault();
      await submitMessage();
    }
  });

  elements.composerForm.addEventListener("submit", async (event) => {
    event.preventDefault();
    await submitMessage();
  });

  elements.temporaryFileButton?.addEventListener("click", () => {
    if (!state.activeSessionKey) {
      setError(t("msg.noSession"));
      return;
    }
    elements.temporaryFileUpload?.click();
  });

  elements.temporaryFileUpload?.addEventListener("change", async () => {
    await uploadTemporaryFile();
  });

  elements.newChatButton.addEventListener("click", async () => {
    if (document.body.classList.contains("cowork-page-active")) {
      closeCoworkModal();
    } else {
      setSidebarDropdown("sessions");
    }
    await createNewChat();
  });

  elements.refreshButton.addEventListener("click", async () => {
    setSidebarDropdown("sessions");
    await loadSessions();
    await loadEditableFiles();
    await loadSystemStatus();
  });

  if (elements.clearMessagesButton) {
    elements.clearMessagesButton.addEventListener("click", async () => {
      if (state.activeSessionKey) {
        try {
          await clearSession(state.activeSessionKey);
        } catch (error) {
          console.error(error);
          setError(error.message || t("status.failed"));
        }
      }
    });
  }

  elements.sessionList.addEventListener("click", async (event) => {
    // Handle delete button
    const deleteBtn = event.target.closest(".session-delete");
    if (deleteBtn) {
      event.preventDefault();
      event.stopPropagation();
      const sessionKey = deleteBtn.dataset.sessionKey;
      const chatId = deleteBtn.dataset.chatId;
      if (!deleteBtn.classList.contains("confirming")) {
        elements.sessionList.querySelectorAll(".session-delete.confirming").forEach((button) => {
          button.classList.remove("confirming");
          button.textContent = "×";
          button.title = t("ui.deleteSession");
          button.setAttribute("aria-label", t("ui.deleteSession"));
        });
        deleteBtn.classList.add("confirming");
        deleteBtn.textContent = t("ui.confirm");
        deleteBtn.title = t("ui.confirmDelete");
        deleteBtn.setAttribute("aria-label", t("ui.confirmDelete"));
        return;
      }

      try {
        await deleteSession(sessionKey, chatId);
      } catch (error) {
        console.error(error);
        setError(error.message || t("status.failed"));
      }
      return;
    }

    // Handle session selection
    const target = event.target.closest(".session-item");
    if (!target) {
      return;
    }
    if (document.body.classList.contains("cowork-page-active")) {
      closeCoworkModal();
    }
    await attachSession(target.dataset.chatId);
  });

  elements.fileSelect.addEventListener("change", async (event) => {
    const nextPath = event.target.value;
    if (!nextPath) {
      return;
    }
    await loadFile(nextPath);
  });

  elements.fileEditor.addEventListener("input", () => {
    state.fileDraftDirty = true;
    setEditorStatus(t("status.editing"), "idle");
  });

  elements.saveFileButton.addEventListener("click", async () => {
    try {
      await saveActiveFile();
    } catch (error) {
      console.error(error);
      setEditorStatus(t("status.saveFailed"), "error");
      setFileError(error.message || t("status.saveFailed"));
    }
  });

  elements.reloadFileButton.addEventListener("click", async () => {
    try {
      await loadFile(state.activeFilePath);
    } catch (error) {
      console.error(error);
      setEditorStatus(t("status.loadFailed"), "error");
      setFileError(error.message || t("status.loadFailed"));
    }
  });

  elements.refreshSkillsButton.addEventListener("click", async () => {
    try {
      await loadSkills();
    } catch (error) {
      console.error(error);
    }
  });

  elements.newSkillButton.addEventListener("click", async () => {
    await createNewSkill();
  });

  // Tools modal events
  elements.toolsToggle.addEventListener("click", openToolsModal);
  elements.toolsToggle.addEventListener("keydown", (event) => {
    if (event.key === "Enter" || event.key === " ") {
      event.preventDefault();
      openToolsModal();
    }
  });
  elements.toolsModalOverlay.addEventListener("click", closeToolsModal);
  elements.toolsModalClose.addEventListener("click", closeToolsModal);

  // 工具配置提示 - 前往设置按钮
  const gotoToolsConfigBtn = document.getElementById("goto-tools-config");
  if (gotoToolsConfigBtn) {
    gotoToolsConfigBtn.addEventListener("click", () => {
      closeToolsModal();
      openModal();
      // 展开Tools配置组
      const toolsGroupTitle = document.querySelector('[data-group="tools"]');
      if (toolsGroupTitle) {
        setConfigGroupExpanded(toolsGroupTitle, true);
      }
    });
  }

  // Skills modal events
  elements.skillsToggle.addEventListener("click", openSkillsModal);
  elements.skillsToggle.addEventListener("keydown", (event) => {
    if (event.key === "Enter" || event.key === " ") {
      event.preventDefault();
      openSkillsModal();
    }
  });
  elements.skillsModalOverlay.addEventListener("click", closeSkillsModal);
  elements.skillsModalClose.addEventListener("click", closeSkillsModal);

  elements.sessionsToggle?.addEventListener("click", () => {
    if (document.body.classList.contains("cowork-page-active")) {
      closeCoworkModal();
    } else {
      setSidebarDropdown("sessions");
    }
  });
  elements.sessionsToggle?.addEventListener("keydown", (event) => {
    if (event.key === "Enter" || event.key === " ") {
      event.preventDefault();
      if (document.body.classList.contains("cowork-page-active")) {
        closeCoworkModal();
      } else {
        setSidebarDropdown("sessions");
      }
    }
  });

  // Cowork modal events
  elements.coworkToggle?.addEventListener("click", openCoworkModal);
  elements.coworkToggle?.addEventListener("keydown", (event) => {
    if (event.key === "Enter" || event.key === " ") {
      event.preventDefault();
      openCoworkModal();
    }
  });
  elements.coworkModalOverlay?.addEventListener("click", closeCoworkModal);
  elements.coworkModalClose?.addEventListener("click", closeCoworkModal);
  elements.coworkStartButton?.addEventListener("click", () => {
    submitCoworkComposer().catch((error) => setCoworkError(error.message || String(error)));
  });
  elements.coworkModeTabs?.addEventListener("click", (event) => {
    const tab = event.target.closest?.(".cowork-mode-tab");
    if (!tab) return;
    setCoworkWorkflowMode(tab.dataset.mode || "adaptive_starter");
  });
  window.addEventListener("resize", () => {
    setCoworkWorkflowMode(elements.coworkWorkflowMode?.value || "adaptive_starter");
  });
  elements.coworkGoalInput?.addEventListener("keydown", (event) => {
    if (event.key === "Enter" && !event.shiftKey) {
      event.preventDefault();
      submitCoworkComposer().catch((error) => setCoworkError(error.message || String(error)));
    }
  });
  elements.coworkRefreshButton?.addEventListener("click", () => {
    loadCoworkSessions().catch((error) => setCoworkError(error.message || String(error)));
  });
  elements.coworkIncludeCompleted?.addEventListener("change", () => {
    loadCoworkSessions().catch((error) => setCoworkError(error.message || String(error)));
  });
  elements.coworkBlueprintValidateButton?.addEventListener("click", () => {
    validateCoworkBlueprint().catch((error) => setCoworkError(error.message || String(error)));
  });
  elements.coworkBlueprintPreviewButton?.addEventListener("click", () => {
    validateCoworkBlueprint({ preview: true }).catch((error) => setCoworkError(error.message || String(error)));
  });
  elements.coworkTraceFilter?.addEventListener("change", () => {
    state.coworkTraceFilter = elements.coworkTraceFilter?.value || "all";
    renderCoworkTraceSpans(state.activeCoworkSession);
  });
  elements.coworkConsoleFilter?.addEventListener("input", () => {
    state.coworkConsoleFilter = elements.coworkConsoleFilter?.value || "";
    renderCoworkDetail();
  });
  elements.coworkTraceModeTabs?.addEventListener("click", (event) => {
    const button = event.target.closest?.("[data-mode]");
    if (!button) return;
    state.coworkTraceMode = button.dataset.mode || "key";
    renderCoworkTraceSpans(state.activeCoworkSession);
  });
  elements.coworkBranchMergeButton?.addEventListener("click", () => {
    mergeCoworkBranchResults().catch((error) => setCoworkError(error.message || String(error)));
  });
  elements.coworkSecondaryTabs?.addEventListener("click", (event) => {
    const button = event.target.closest?.("[data-tab]");
    if (!button) return;
    state.coworkSecondaryTab = button.dataset.tab || "trace";
    renderCoworkSecondaryPanels();
  });
  elements.coworkMapButton?.addEventListener("click", () => {
    setCoworkGraphCollapsed(!isCoworkGraphCurrentlyCollapsed());
  });
  elements.coworkPauseButton?.addEventListener("click", () => {
    setCoworkSessionStatus("pause").catch((error) => setCoworkError(error.message || String(error)));
  });
  elements.coworkResumeButton?.addEventListener("click", () => {
    setCoworkSessionStatus("resume").catch((error) => setCoworkError(error.message || String(error)));
  });
  elements.coworkSummaryButton?.addEventListener("click", showCoworkSummary);
  elements.coworkDeleteButton?.addEventListener("click", () => {
    deleteCoworkSession().catch((error) => setCoworkError(error.message || String(error)));
  });
  elements.coworkMessageButton?.addEventListener("click", sendCoworkMessage);
  elements.coworkMessageInput?.addEventListener("keydown", (event) => {
    if (event.key === "Enter" && !event.shiftKey) {
      event.preventDefault();
      sendCoworkMessage();
    }
  });
  elements.coworkTaskButton?.addEventListener("click", addCoworkTask);
  elements.coworkTaskAssignButton?.addEventListener("click", () => {
    assignSelectedCoworkTask().catch((error) => setCoworkError(error.message || String(error)));
  });
  elements.coworkTaskRetryButton?.addEventListener("click", () => {
    retrySelectedCoworkTask().catch((error) => setCoworkError(error.message || String(error)));
  });
  elements.coworkTaskReviewButton?.addEventListener("click", () => {
    requestSelectedCoworkTaskReview().catch((error) => setCoworkError(error.message || String(error)));
  });
  setupCoworkGraphInteractions();

  // Workspace modal events
  elements.workspaceToggle.addEventListener("click", openWorkspaceModal);
  elements.workspaceToggle.addEventListener("keydown", (event) => {
    if (event.key === "Enter" || event.key === " ") {
      event.preventDefault();
      openWorkspaceModal();
    }
  });
  elements.workspaceModalOverlay.addEventListener("click", closeWorkspaceModal);
  elements.workspaceModalClose.addEventListener("click", closeWorkspaceModal);

  // 设置弹窗事件
  if (elements.browserPanelToggle) {
    elements.browserPanelToggle.addEventListener("click", () => {
      setBrowserPanelCollapsed(!state.browserPanelCollapsed);
    });
  }
  if (elements.sidebarCollapseButton) {
    elements.sidebarCollapseButton.addEventListener("click", () => {
      setSidebarCollapsed(!state.sidebarCollapsed);
    });
  }
  elements.inspectorClose?.addEventListener("click", closeInspectionMode);

  elements.settingsButton.addEventListener("click", openModal);
  elements.modalOverlay.addEventListener("click", closeModal);
  elements.modalClose.addEventListener("click", closeModal);

  // Skill detail modal events
  elements.skillModalOverlay.addEventListener("click", closeSkillModal);
  elements.skillModalClose.addEventListener("click", closeSkillModal);
  elements.skillValidateButton.addEventListener("click", async () => {
    await validateSkill();
  });
  elements.skillSaveButton.addEventListener("click", async () => {
    await saveSkill();
  });
  elements.skillDeleteButton.addEventListener("click", async () => {
    if (state.activeSkill?.name) {
      await deleteSkill(state.activeSkill.name);
    }
  });

  // Tool detail modal events
  elements.toolModalOverlay.addEventListener("click", closeToolModal);
  elements.toolModalClose.addEventListener("click", closeToolModal);
  elements.toolModalCloseButton.addEventListener("click", closeToolModal);

  // Knowledge modal events
  elements.knowledgeToggle.addEventListener("click", openKnowledgeModal);
  elements.knowledgeToggle.addEventListener("keydown", (event) => {
    if (event.key === "Enter" || event.key === " ") {
      event.preventDefault();
      openKnowledgeModal();
    }
  });
  elements.knowledgeModalOverlay.addEventListener("click", closeKnowledgeModal);
  elements.knowledgeModalClose.addEventListener("click", closeKnowledgeModal);

  // 知识库配置提示 - 前往设置按钮
  const gotoKnowledgeConfigBtn = document.getElementById("goto-knowledge-config");
  if (gotoKnowledgeConfigBtn) {
    gotoKnowledgeConfigBtn.addEventListener("click", () => {
      closeKnowledgeModal();
      openModal();
      // 展开Knowledge配置组
      const knowledgeGroupTitle = document.querySelector('[data-group="knowledge"]');
      if (knowledgeGroupTitle) {
        setConfigGroupExpanded(knowledgeGroupTitle, true);
      }
    });
  }

  // Knowledge panel events
  elements.refreshDocsButton.addEventListener("click", async () => {
    await loadKnowledgeStats();
    await loadKnowledgeDocs();
    await loadKnowledgeGraph();
  });
  if (elements.refreshGraphButton) {
    elements.refreshGraphButton.addEventListener("click", loadKnowledgeGraph);
  }
  elements.rebuildIndexButton.addEventListener("click", rebuildKnowledgeIndex);
  elements.addDocButton.addEventListener("click", openDocModal);
  elements.uploadDocButton.addEventListener("click", () => {
    elements.docFileUpload.click();
  });
  elements.docFileUpload.addEventListener("change", uploadDoc);
  elements.queryButton.addEventListener("click", queryKnowledge);
  elements.queryMode.addEventListener("change", updateQueryModeHint);
  elements.queryInput.addEventListener("keydown", (event) => {
    if (event.key === "Enter" && !event.shiftKey) {
      event.preventDefault();
      queryKnowledge();
    }
  });

  // Doc modal events
  elements.docModalOverlay.addEventListener("click", closeDocModal);
  elements.docModalClose.addEventListener("click", closeDocModal);
  elements.docSaveButton.addEventListener("click", async () => {
    await addDoc();
  });

  // Doc view modal events
  elements.docViewModalOverlay.addEventListener("click", closeDocViewModal);
  elements.docViewModalClose.addEventListener("click", closeDocViewModal);
  elements.docViewCloseButton.addEventListener("click", closeDocViewModal);
  elements.docViewDeleteButton.addEventListener("click", async () => {
    if (state.activeDoc?.id) {
      await deleteDoc(state.activeDoc.id, state.activeDoc.name);
    }
  });

  // ESC键关闭弹窗 + 全局快捷键
  document.addEventListener("keydown", (event) => {
    // ESC关闭弹窗
    if (event.key === "Escape") {
      if (state.helpOverlay) {
        closeHelpOverlay();
        return;
      }
      if (elements.modal.classList.contains("active")) {
        closeModal();
      }
      if (elements.skillModal.classList.contains("active")) {
        closeSkillModal();
      }
      if (elements.coworkModal?.classList.contains("active")) {
        closeCoworkModal();
      }
      if (elements.docModal.classList.contains("active")) {
        closeDocModal();
      }
      if (elements.docViewModal.classList.contains("active")) {
        closeDocViewModal();
      }
      return;
    }

    // 全局快捷键（需要在输入框外生效）
    if (event.ctrlKey || event.metaKey) {
      // Ctrl+N: 新建会话
      if (event.key === "n" || event.key === "N") {
        event.preventDefault();
        createNewChat();
        return;
      }

      // Ctrl+L: 清空当前会话
      if (event.key === "l" || event.key === "L") {
        event.preventDefault();
        if (state.activeSessionKey) {
          clearSession(state.activeSessionKey).catch(console.error);
        }
        return;
      }

      // Ctrl+S: 保存文件编辑
      if (event.key === "s" || event.key === "S") {
        event.preventDefault();
        if (state.activeFilePath && state.fileDraftDirty) {
          saveActiveFile().catch(console.error);
        }
        return;
      }

      // Ctrl+/: 显示快捷键帮助
      if (event.shiftKey && (event.key === "/" || event.key === "?")) {
        event.preventDefault();
        showPageHelp();
        return;
      }

      if (event.key === "/" || event.key === "?") {
        event.preventDefault();
        showShortcutHelp();
        return;
      }
    }
  });

  // 配置组折叠
  syncConfigGroupStates();
  document.querySelectorAll(".config-group-title.clickable").forEach((title) => {
    title.addEventListener("click", () => {
      toggleConfigGroup(title);
    });
    title.addEventListener("keydown", (event) => {
      if (event.key === "Enter" || event.key === " ") {
        event.preventDefault();
        toggleConfigGroup(title);
      }
    });
  });

  if (elements.configSearch) {
    elements.configSearch.addEventListener("input", applyConfigSearch);
  }
  if (elements.configExpandAll) {
    elements.configExpandAll.addEventListener("click", () => {
      elements.modal.querySelectorAll(".config-group-title.clickable").forEach((title) => {
        setConfigGroupExpanded(title, true);
      });
    });
  }
  if (elements.configCollapseAll) {
    elements.configCollapseAll.addEventListener("click", () => {
      elements.modal.querySelectorAll(".config-group-title.clickable").forEach((title) => {
        setConfigGroupExpanded(title, false);
      });
    });
  }
  if (elements.configQuickNav) {
    elements.configQuickNav.addEventListener("click", (event) => {
      const button = event.target.closest("[data-config-jump]");
      if (!button) {
        return;
      }
      jumpToConfigGroup(button.dataset.configJump);
    });
  }
  if (elements.resetConfigButton) {
    elements.resetConfigButton.addEventListener("click", () => {
      if (!state.config) {
        return;
      }
      populateConfigForm(state.config);
      elements.configError.textContent = "";
      elements.configSuccess.textContent = "";
    });
  }
  getConfigFields().forEach((field) => {
    field.addEventListener("input", updateConfigDirtyState);
    field.addEventListener("change", updateConfigDirtyState);
  });

  // Provider select change - 更新Provider配置区域
  elements.configProviderSelect.addEventListener("change", () => {
    if (!elements.configProfileId?.value.trim()) {
      const providers = state.config?.providers || {};
      loadProviderConfig(providers, elements.configProviderSelect.value);
    }
    updateConfigDirtyState();
  });

  // Agent Provider select change - 同步更新Provider配置区域的选择
  elements.configProvider.addEventListener("change", () => {
    const selectedProvider = elements.configProvider.value;
    // Auto mode keeps model-based routing; show DeepSeek credentials by default.
    const displayProvider = selectedProvider === "auto" ? "deepseek" : selectedProvider;
    elements.configProviderSelect.value = displayProvider;
    const providers = state.config?.providers || {};
    loadProviderConfig(providers, displayProvider);
    updateConfigDirtyState();
  });

  if (elements.configActiveProfile) {
    elements.configActiveProfile.addEventListener("change", () => {
      if (elements.configProfileId) {
        elements.configProfileId.value = elements.configActiveProfile.value.trim();
      }
      const providerName = elements.configProviderSelect?.value || "deepseek";
      loadProviderConfig(state.config?.providers || {}, providerName);
      updateConfigDirtyState();
    });
  }

  if (elements.configProfileId) {
    elements.configProfileId.addEventListener("change", () => {
      if (elements.configActiveProfile && !elements.configActiveProfile.value.trim()) {
        elements.configActiveProfile.value = elements.configProfileId.value.trim();
      }
      const providerName = elements.configProviderSelect?.value || "deepseek";
      loadProviderConfig(state.config?.providers || {}, providerName);
      updateConfigDirtyState();
    });
  }

  if (elements.configProviderModels) {
    elements.configProviderModels.addEventListener("input", () => {
      refreshProfileHints(state.config);
    });
  }

  if (elements.configModelSelect) {
    elements.configModelSelect.addEventListener("change", () => {
      const model = elements.configModelSelect.value;
      if (!model) {
        if (elements.configModel) {
          elements.configModel.hidden = false;
          window.setTimeout(() => elements.configModel.focus(), 0);
        }
        return;
      }
      elements.configModel.value = model;
      elements.configModel.hidden = true;
      setFieldState(elements.configModel, "neutral");
      updateConfigDirtyState();
    });
  }

  if (elements.configModel) {
    elements.configModel.addEventListener("input", () => {
      const models = parseModelList(elements.configProviderModels?.value);
      if (elements.configModelSelect) {
        const value = elements.configModel.value.trim();
        elements.configModelSelect.value = models.includes(value) ? value : "";
      }
    });
  }

  if (elements.configDiscoverModelsButton) {
    elements.configDiscoverModelsButton.addEventListener("click", async () => {
      await discoverProviderModels({ silent: false });
    });
  }

  if (elements.configSupportsModelDiscovery) {
    elements.configSupportsModelDiscovery.addEventListener("change", () => {
      if (elements.configSupportsModelDiscovery.checked) {
        scheduleModelDiscovery();
      } else {
        setModelDiscoveryStatus("");
      }
    });
  }

  [elements.configApiBase, elements.configApiKey].forEach((field) => {
    field?.addEventListener("change", () => {
      scheduleModelDiscovery();
    });
  });

  elements.saveConfigButton.addEventListener("click", async () => {
    await saveConfig();
  });

  // 语言切换按钮
  renderSidebarActionIcons();
  setSidebarCollapsed(state.sidebarCollapsed);
  updateLanguageButton();
  elements.languageToggle.addEventListener("click", () => {
    const newLang = getLanguage() === "zh" ? "en" : "zh";
    setLanguage(newLang);
    updateLanguageButton();
    renderDynamicContent();
  });

  // 主题切换按钮
  elements.themeToggle.addEventListener("click", toggleTheme);
  if (elements.helpTourButton) {
    elements.helpTourButton.addEventListener("click", () => startFeatureTour(0));
  }

  // 监听语言变化事件（来自 i18n.js）
  window.addEventListener("languagechange", () => {
    renderSidebarActionIcons();
    updateLanguageButton();
    renderDynamicContent();
    updateConfigDirtyState();
    applyConfigSearch();
    // 重新渲染usage显示
    if (state.lastUsage) {
      updateUsageDisplay(state.lastUsage);
    }
  });

  // Temperature Slider 值显示
  if (elements.configTemperature) {
    elements.configTemperature.addEventListener("input", (e) => {
      const valueEl = document.getElementById("temperature-value");
      if (valueEl) {
        valueEl.textContent = e.target.value;
      }
    });
  }

  // 敏感字段显示/隐藏按钮
  document.querySelectorAll(".config-sensitive-toggle").forEach((btn) => {
    btn.addEventListener("click", () => {
      const targetId = btn.getAttribute("data-target");
      const input = document.getElementById(targetId);
      const eyeIcon = btn.querySelector(".eye-icon");
      const eyeOffIcon = btn.querySelector(".eye-off-icon");
      if (input && eyeIcon && eyeOffIcon) {
        if (input.type === "password") {
          input.type = "text";
          eyeIcon.style.display = "none";
          eyeOffIcon.style.display = "block";
        } else {
          input.type = "password";
          eyeIcon.style.display = "block";
          eyeOffIcon.style.display = "none";
        }
      }
    });
  });

  // 实时验证逻辑
  setupValidation();
}

function updateLanguageButton() {
  const lang = getLanguage();
  elements.languageToggle.textContent = lang === "zh" ? "EN" : "中文";
  elements.languageToggle.setAttribute("aria-label", t("ui.switchLanguage"));
}

function renderDynamicContent() {
  // 重新渲染所有动态内容
  renderSessions();
  renderMessages();
  renderTools();
  renderSkills();
  renderCoworkSessions();
  renderCoworkDetail();
  renderKnowledgeDocs();
  // 更新文件编辑器状态
  if (state.activeFilePath && state.activeFileUpdatedAt) {
    elements.fileMeta.textContent = `${t("ui.lastUpdate")} ${formatTime(state.activeFileUpdatedAt)}`;
  } else if (state.activeFilePath) {
    elements.fileMeta.textContent = t("ui.fileNotCreated");
  }
  // 更新聊天标题
  if (state.activeChatId) {
    updateActiveChatTitle();
  } else {
    updateActiveChatTitle();
  }
}

// 快捷键帮助
const helpTargets = [
  { selector: ".sidebar", title: "tour.sidebar.title", desc: "tour.sidebar.desc" },
  { selector: "#new-chat-button", title: "tour.newChat.title", desc: "tour.newChat.desc" },
  { selector: "#session-list", title: "tour.sessions.title", desc: "tour.sessions.desc" },
  { selector: "#system-status", title: "tour.status.title", desc: "tour.status.desc" },
  { selector: ".chat-panel", title: "tour.chat.title", desc: "tour.chat.desc" },
  { selector: "#message-list", title: "tour.messages.title", desc: "tour.messages.desc" },
  { selector: "#temporary-file-button", title: "tour.upload.title", desc: "tour.upload.desc" },
  { selector: "#composer-input", title: "tour.composer.title", desc: "tour.composer.desc" },
  { selector: "#send-button", title: "tour.send.title", desc: "tour.send.desc" },
  { selector: ".session-rag-toggle", title: "tour.ragToggle.title", desc: "tour.ragToggle.desc" },
  { selector: ".mode-switch", title: "tour.mode.title", desc: "tour.mode.desc" },
  { selector: "#tools-toggle", title: "tour.tools.title", desc: "tour.tools.desc" },
  { selector: "#knowledge-toggle", title: "tour.knowledge.title", desc: "tour.knowledge.desc" },
  { selector: "#skills-toggle", title: "tour.skills.title", desc: "tour.skills.desc" },
  { selector: "#workspace-toggle", title: "tour.workspace.title", desc: "tour.workspace.desc" },
  { selector: "#language-toggle", title: "tour.language.title", desc: "tour.language.desc" },
  { selector: "#theme-toggle", title: "tour.theme.title", desc: "tour.theme.desc" },
  { selector: "#settings-button", title: "tour.settingsButton.title", desc: "tour.settingsButton.desc" },
  { selector: ".settings-bar", title: "tour.settings.title", desc: "tour.settings.desc" },
];

function getVisibleHelpTargets() {
  return helpTargets
    .map((item) => ({ ...item, element: document.querySelector(item.selector) }))
    .filter((item) => {
      if (!item.element) {
        return false;
      }
      const rect = item.element.getBoundingClientRect();
      return rect.width > 0 && rect.height > 0;
    });
}

function clamp(value, min, max) {
  if (max < min) {
    return min;
  }
  return Math.min(Math.max(value, min), max);
}

function closeHelpOverlay() {
  if (state.helpOverlay) {
    if (state.helpOverlayCleanup) {
      state.helpOverlayCleanup();
      state.helpOverlayCleanup = null;
    }
    state.helpOverlay.remove();
    state.helpOverlay = null;
  }
}

function createHelpOverlay(mode) {
  closeHelpOverlay();
  const overlay = document.createElement("div");
  overlay.className = `help-overlay help-overlay-${mode}`;
  overlay.setAttribute("role", "dialog");
  overlay.setAttribute("aria-modal", "true");
  document.body.appendChild(overlay);
  state.helpOverlay = overlay;
  return overlay;
}

function registerHelpOverlayRefresh(render) {
  const refresh = () => {
    if (!state.helpOverlay) {
      return;
    }
    window.requestAnimationFrame(render);
  };
  window.addEventListener("resize", refresh);
  window.addEventListener("scroll", refresh, true);
  state.helpOverlayCleanup = () => {
    window.removeEventListener("resize", refresh);
    window.removeEventListener("scroll", refresh, true);
  };
}

function renderPageHelp() {
  const overlay = state.helpOverlay;
  if (!overlay) {
    return;
  }
  const targets = getVisibleHelpTargets();
  overlay.innerHTML = `
    <div class="help-backdrop"></div>
    <div class="help-bar">
      <div>
        <h2>${escapeHtml(t("tour.pageHelp.title"))}</h2>
        <p>${escapeHtml(t("tour.pageHelp.subtitle"))}</p>
      </div>
      <div class="help-actions">
        <button class="button button-primary button-small" type="button" data-help-action="start-tour">${escapeHtml(t("tour.start"))}</button>
        <button class="button button-ghost button-small" type="button" data-help-action="close">${escapeHtml(t("ui.close"))}</button>
      </div>
    </div>
    <div class="help-items"></div>
  `;

  const items = overlay.querySelector(".help-items");
  targets.forEach((target, index) => {
    const rect = target.element.getBoundingClientRect();
    const box = document.createElement("div");
    box.className = "help-target-box";
    box.style.left = `${Math.max(8, rect.left - 4)}px`;
    box.style.top = `${Math.max(8, rect.top - 4)}px`;
    box.style.width = `${rect.width + 8}px`;
    box.style.height = `${rect.height + 8}px`;
    items.appendChild(box);

    const note = document.createElement("article");
    note.className = "help-note";
    note.innerHTML = `
      <span class="help-note-index">${index + 1}</span>
      <h3>${escapeHtml(t(target.title))}</h3>
      <p>${escapeHtml(t(target.desc))}</p>
    `;

    const noteWidth = 240;
    const left = clamp(rect.left, 12, window.innerWidth - noteWidth - 12);
    const top = rect.top + rect.height + 10 <= window.innerHeight - 90
      ? rect.top + rect.height + 10
      : Math.max(12, rect.top - 102);
    note.style.left = `${left}px`;
    note.style.top = `${top}px`;
    items.appendChild(note);
  });

  overlay.querySelector('[data-help-action="close"]').addEventListener("click", closeHelpOverlay);
  overlay.querySelector('[data-help-action="start-tour"]').addEventListener("click", () => startFeatureTour(0));
}

function showPageHelp() {
  createHelpOverlay("map");
  renderPageHelp();
  registerHelpOverlayRefresh(renderPageHelp);
}

function placeTourCard(card, rect) {
  const gap = 16;
  const width = Math.min(360, window.innerWidth - 24);
  card.style.width = `${width}px`;

  let left = rect.right + gap;
  if (left + width > window.innerWidth - 12) {
    left = rect.left - width - gap;
  }
  if (left < 12) {
    left = clamp(rect.left, 12, window.innerWidth - width - 12);
  }

  let top = rect.top;
  if (top + 230 > window.innerHeight - 12) {
    top = window.innerHeight - 242;
  }
  top = Math.max(12, top);

  card.style.left = `${left}px`;
  card.style.top = `${top}px`;
}

function renderFeatureTour() {
  const overlay = state.helpOverlay;
  if (!overlay) {
    return;
  }

  const targets = getVisibleHelpTargets();
  if (!targets.length) {
    closeHelpOverlay();
    return;
  }

  state.activeTourIndex = clamp(state.activeTourIndex, 0, targets.length - 1);
  const target = targets[state.activeTourIndex];
  const rect = target.element.getBoundingClientRect();

  overlay.innerHTML = `
    <div class="help-backdrop"></div>
    <div class="tour-spotlight"></div>
    <article class="tour-card">
      <div class="tour-step">${escapeHtml(t("tour.step"))} ${state.activeTourIndex + 1} / ${targets.length}</div>
      <h2>${escapeHtml(t(target.title))}</h2>
      <p>${escapeHtml(t(target.desc))}</p>
      <div class="tour-actions">
        <button class="button button-ghost button-small" type="button" data-tour-action="close">${escapeHtml(t("tour.skip"))}</button>
        <button class="button button-small" type="button" data-tour-action="back" ${state.activeTourIndex === 0 ? "disabled" : ""}>${escapeHtml(t("tour.back"))}</button>
        <button class="button button-primary button-small" type="button" data-tour-action="next">${escapeHtml(state.activeTourIndex === targets.length - 1 ? t("tour.done") : t("tour.next"))}</button>
      </div>
    </article>
  `;

  const spotlight = overlay.querySelector(".tour-spotlight");
  spotlight.style.left = `${Math.max(8, rect.left - 6)}px`;
  spotlight.style.top = `${Math.max(8, rect.top - 6)}px`;
  spotlight.style.width = `${rect.width + 12}px`;
  spotlight.style.height = `${rect.height + 12}px`;

  placeTourCard(overlay.querySelector(".tour-card"), rect);

  overlay.querySelector('[data-tour-action="close"]').addEventListener("click", closeHelpOverlay);
  overlay.querySelector('[data-tour-action="back"]').addEventListener("click", () => {
    state.activeTourIndex -= 1;
    renderFeatureTour();
  });
  overlay.querySelector('[data-tour-action="next"]').addEventListener("click", () => {
    if (state.activeTourIndex >= targets.length - 1) {
      closeHelpOverlay();
      return;
    }
    state.activeTourIndex += 1;
    const next = targets[state.activeTourIndex];
    next.element.scrollIntoView({ block: "center", inline: "center", behavior: "smooth" });
    window.setTimeout(renderFeatureTour, 180);
  });
}

function startFeatureTour(index = 0) {
  const targets = getVisibleHelpTargets();
  if (!targets.length) {
    return;
  }
  state.activeTourIndex = clamp(index, 0, targets.length - 1);
  targets[state.activeTourIndex].element.scrollIntoView({ block: "center", inline: "center" });
  createHelpOverlay("tour");
  renderFeatureTour();
  registerHelpOverlayRefresh(renderFeatureTour);
}

function showShortcutHelp() {
  const shortcuts = [
    { key: "Ctrl+N", desc: t("shortcuts.newChat") },
    { key: "Ctrl+L", desc: t("shortcuts.clearSession") },
    { key: "Ctrl+S", desc: t("shortcuts.saveEdit") },
    { key: "Ctrl+/", desc: t("shortcuts.showHelp") },
    { key: "Ctrl+Shift+/", desc: t("shortcuts.showPageHelp") },
    { key: "Enter", desc: t("shortcuts.sendMessage") },
    { key: "Shift+Enter", desc: t("shortcuts.newLine") },
    { key: "Esc", desc: t("shortcuts.closeModal") },
  ];

  // 创建快捷键帮助弹窗
  const modal = document.createElement("div");
  modal.className = "modal active";
  modal.innerHTML = `
    <div class="modal-overlay"></div>
    <div class="modal-content" style="width: min(90vw, 400px);">
      <header class="modal-header">
        <h2>${t("shortcuts.title")}</h2>
        <button class="button button-ghost" type="button">✕</button>
      </header>
      <div class="modal-body">
        <div class="shortcut-list">
          ${shortcuts.map(s => `
            <div class="shortcut-item">
              <span class="shortcut-key">${s.key}</span>
              <span class="shortcut-desc">${s.desc}</span>
            </div>
          `).join("")}
        </div>
      </div>
    </div>
  `;

  // 点击关闭
  const closeBtn = modal.querySelector(".button-ghost");
  const overlay = modal.querySelector(".modal-overlay");
  closeBtn.addEventListener("click", () => modal.remove());
  overlay.addEventListener("click", () => modal.remove());

  document.body.appendChild(modal);
}

async function init() {
  // 显示初始化骨架屏
  showInitSkeleton();

  initTheme();
  applyUiMode(state.uiMode);
  bindEvents();
  resizeComposer();
  const wantsCoworkPage = window.location.pathname === "/cowork";
  if (wantsCoworkPage) {
    document.body.classList.add("cowork-page-active");
    setSidebarDropdown("cowork");
    elements.coworkModal?.classList.add("active");
  } else {
    setSidebarDropdown("sessions");
  }

  try {
    await bootstrap();
    await connectWebSocket();
    await loadSessions();
    await loadEditableFiles();
    await loadSystemStatus();
    await loadTools();
    await loadSkills();
    await loadCoworkSessions().catch((error) => console.warn("Cowork init failed", error));
    await loadKnowledgeStats().catch((error) => console.warn("Knowledge stats init failed", error));
    await loadKnowledgeDocs().catch((error) => console.warn("Knowledge docs init failed", error));
    await loadKnowledgeGraph().catch((error) => console.warn("Knowledge graph init failed", error));
    await loadConfig().catch((error) => console.warn("Config init failed", error));
    if (wantsCoworkPage) openCoworkModal();
    if (state.activeFilePath) {
      sendSocketMessage({ type: "subscribe_file", path: state.activeFilePath });
    }
    // 移除骨架屏
    removeInitSkeleton();
    setStatus(t("status.connected"), "connected");
  } catch (error) {
    console.error(error);
    removeInitSkeleton();
    if (wantsCoworkPage) {
      document.body.classList.add("cowork-page-active");
      setSidebarDropdown("cowork");
      elements.coworkModal?.classList.add("active");
    }
    setStatus(t("status.initFailed"), "error");
    setError(error.message || t("status.initFailed"));
  }
}

function showInitSkeleton() {
  const skeletonHTML = `
    <div class="skeleton-overlay" id="init-skeleton">
      <div class="skeleton-container">
        <div class="skeleton-message">
          <div class="skeleton-meta">
            <div class="skeleton skeleton-role"></div>
            <div class="skeleton skeleton-time"></div>
          </div>
          <div class="skeleton skeleton-content"></div>
          <div class="skeleton skeleton-content-short"></div>
        </div>
        <div class="skeleton-message" style="align-self: flex-end; width: 70%;">
          <div class="skeleton-meta">
            <div class="skeleton skeleton-role"></div>
            <div class="skeleton skeleton-time"></div>
          </div>
          <div class="skeleton skeleton-content" style="width: 80%;"></div>
        </div>
        <div class="init-loading-text">${t("ui.initializing")}</div>
      </div>
    </div>
  `;
  elements.messageList.innerHTML = skeletonHTML;
}

function removeInitSkeleton() {
  const skeleton = document.getElementById("init-skeleton");
  if (skeleton) {
    skeleton.remove();
  }
}

export { init };
