export function createSidebarPageAiRuntime(context) {
  const {
    buildLocalFileOpenUrl,
    currentDocumentId,
    currentPageAggregate,
    currentPageOptions,
    currentRootUri,
    currentSourceKind,
    escapeHtml,
    cssEscape,
    openLocalResourceInActiveTab,
    pageUiState,
    resolveWorkspaceId,
    searchText,
  } = context;
  var PAGE_AI_SESSION_STORAGE_VERSION = 3;

  function textFromUnknown(value) {
    if (value == null) return '';
    if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') return String(value);
    if (Array.isArray(value)) return value.map(textFromUnknown).filter(Boolean).join(' ');
    if (typeof value !== 'object') return '';
    var parts = [];
    ['text', 'title', 'content', 'children', 'blocks', 'body'].forEach(function(key) {
      if (Object.prototype.hasOwnProperty.call(value, key)) {
        var text = textFromUnknown(value[key]);
        if (text) parts.push(text);
      }
    });
    return parts.join(' ');
  }

  function readLocalEditorBlocks() {
    var editor = document.querySelector('[data-testid="mnote-leptos-tiptap-island-editor-root"] .editor-surface .ProseMirror');
    if (!(editor instanceof HTMLElement)) return [];
    return Array.from(editor.children).filter(function(node) {
      return node instanceof HTMLElement;
    }).map(function(node, index) {
      var tag = String(node.tagName || '').toUpperCase();
      var headingMatch = tag.match(/^H([1-6])$/);
      var type = headingMatch ? 'heading' : 'paragraph';
      var text = searchText(node.textContent || '');
      var id = searchText(node.getAttribute('data-id') || node.id || 'local-block-' + index);
      var props = headingMatch ? { level: Number(headingMatch[1]) } : {};
      return { id: id, type: type, props: props, content: text };
    }).filter(function(block) {
      return block.content || block.type === 'heading';
    });
  }

  function buildPageAiLocalSubtree(blocks, title) {
    var documentId = currentDocumentId() || 'current-page';
    var rootNodeId = 'page:' + documentId;
    var headingCounters = [0, 0, 0, 0, 0, 0];
    var headingStack = [];
    var nodes = [{
      id: rootNodeId,
      nodeId: rootNodeId,
      nodeType: 'page',
      blockId: null,
      blockType: 'page',
      title: title || '',
      parentNodeId: null,
      headingLevel: null
    }];
    var outline = [];
    var evidence = [];
    blocks.forEach(function(block, index) {
      var level = block.type === 'heading' ? Math.max(1, Math.min(6, Number(block.props && block.props.level || 1))) : null;
      if (level != null) {
        while (headingStack.length && headingStack[headingStack.length - 1].level >= level) headingStack.pop();
      }
      var parentNodeId = headingStack.length ? headingStack[headingStack.length - 1].nodeId : rootNodeId;
      var nodeId = 'local-node:' + String(block.id || index);
      var titleText = block.content || (block.type === 'heading' ? '未命名标题' : '');
      nodes.push({
        id: nodeId,
        nodeId: nodeId,
        nodeType: 'block',
        blockId: block.id,
        blockType: block.type,
        title: titleText,
        parentNodeId: parentNodeId,
        headingLevel: level
      });
      if (level != null) {
        headingCounters[level - 1] += 1;
        for (var i = level; i < headingCounters.length; i += 1) headingCounters[i] = 0;
        var numbering = headingCounters.slice(0, level).filter(function(count) { return count > 0; }).join('.');
        outline.push({
          id: 'local-outline:' + String(block.id || index),
          nodeId: nodeId,
          anchorBlockId: block.id,
          level: level,
          title: titleText,
          numbering: numbering
        });
        headingStack.push({ level: level, nodeId: nodeId });
      }
      if (titleText) {
        evidence.push({
          id: 'local-evidence:' + String(block.id || index),
          nodeId: nodeId,
          anchorBlockId: block.id,
          text: titleText,
          kind: block.type
        });
      }
    });
    return {
      projectionId: 'local-editor-dom:' + documentId,
      rootNode: {
        id: rootNodeId,
        documentId: documentId,
        title: title || '',
        nodeType: 'page'
      },
      subtree: {
        rootNodeId: rootNodeId,
        nodes: nodes
      },
      outline: outline,
      evidence: evidence,
      stats: {
        nodeCount: nodes.length,
        headingCount: outline.length,
        evidenceCount: evidence.length
      },
      source: 'local'
    };
  }

  function currentPageAiContextSnapshot() {
    var aggregate = currentPageAggregate();
    var body = aggregate.body || {};
    var serverSubtree = aggregate.tree && aggregate.tree.pageSubtree ? aggregate.tree.pageSubtree : null;
    return {
      aggregate: aggregate,
      body: body,
      subtree: serverSubtree,
      pageSubtreeSource: serverSubtree ? 'server' : 'none'
    };
  }

  function currentPageAiSelectedText() {
    try {
      var selection = window.getSelection ? window.getSelection() : null;
      return selection ? searchText(selection.toString() || '') : '';
    } catch (_) {
      return '';
    }
  }

  function pageAiProjectionBlocks(aggregate) {
    var blocks = aggregate && aggregate.body && aggregate.body.blockDocument && aggregate.body.blockDocument.blocks;
    return Array.isArray(blocks) ? blocks : [];
  }

  function pageAiBlockText(block) {
    return searchText(block && (block.text || block.title || block.content) || '');
  }

  function pageAiSelectedBlockIdsFromSelection() {
    try {
      var selection = window.getSelection ? window.getSelection() : null;
      if (!selection || selection.rangeCount === 0 || searchText(selection.toString() || '') === '') return [];
      var range = selection.getRangeAt(0);
      var editor = document.querySelector('[data-testid="mnote-leptos-tiptap-island-editor-root"] .editor-surface .ProseMirror');
      if (!(editor instanceof HTMLElement)) return [];
      return Array.from(editor.children).filter(function(node) {
        if (!(node instanceof HTMLElement)) return false;
        try {
          return range.intersectsNode(node);
        } catch (_) {
          return false;
        }
      }).map(function(node) {
        return searchText(node.getAttribute('data-id') || node.id || '');
      }).filter(Boolean);
    } catch (_) {
      return [];
    }
  }

  function pageAiBlocksToPageXml(blocks, aggregate) {
    var revision = aggregate && aggregate.body ? String(aggregate.body.revision || '') : '';
    var pageId = currentDocumentId() || 'current-page';
    var lines = ['<page id="' + escapeHtml(pageId) + '" revision="' + escapeHtml(revision) + '">'];
    blocks.forEach(function(block) {
      var blockId = String(block && (block.blockId || block.id) || '');
      var type = String(block && block.type || 'paragraph');
      var revisionRef = String(block && block.revisionRef || '');
      var level = block && block.attrs && block.attrs.level ? ' level="' + escapeHtml(block.attrs.level) + '"' : '';
      lines.push('  <block id="' + escapeHtml(blockId) + '" type="' + escapeHtml(type) + '" revisionRef="' + escapeHtml(revisionRef) + '"' + level + '>' + escapeHtml(pageAiBlockText(block)) + '</block>');
    });
    lines.push('</page>');
    return lines.join('\n');
  }

  function buildPageAiContext(contextSnapshot, scope, selectedText) {
    var aggregate = contextSnapshot.aggregate || {};
    var body = aggregate.body || {};
    var allBlocks = pageAiProjectionBlocks(aggregate);
    var selectedBlockIds = scope === 'selection' ? pageAiSelectedBlockIdsFromSelection() : [];
    var selectedSet = {};
    selectedBlockIds.forEach(function(id) { selectedSet[id] = true; });
    var selectedBlocks = selectedBlockIds.length
      ? allBlocks.filter(function(block) { return selectedSet[String(block.blockId || block.id || '')]; })
      : [];
    var contextBlocks = selectedBlocks.length ? selectedBlocks : allBlocks.slice(0, 120);
    var truncated = !selectedBlocks.length && allBlocks.length > contextBlocks.length;
    return {
      schema: 'mnote.page_ai_context.v1',
      workspaceId: resolveWorkspaceId(document.body),
      documentId: currentDocumentId(),
      scope: scope,
      revision: body.revision || null,
      conflictDetectionKey: body.conflictDetectionKey || null,
      selectedText: selectedText || '',
      selectedBlockIds: selectedBlockIds,
      allowedTargetBlockIds: selectedBlockIds,
      selectedBlocks: selectedBlocks,
      contextBlocks: contextBlocks,
      pageText: contextBlocks.map(pageAiBlockText).filter(Boolean).join('\n'),
      pageXml: pageAiBlocksToPageXml(contextBlocks, aggregate),
      truncated: truncated,
      warnings: truncated ? [{ code: 'page_ai_context_truncated', message: '页面 AI context 已按前 120 个块裁剪' }] : []
    };
  }

  function pageAiScopedPageContext(contextSnapshot) {
    var aggregate = contextSnapshot.aggregate || {};
    var body = contextSnapshot.body || {};
    var subtree = contextSnapshot.subtree || null;
    var outline = subtree && subtree.outline ? subtree.outline : null;
    var scope = pageUiState.pageAiContextScope || 'page';
    var title = aggregate.head && aggregate.head.title ? aggregate.head.title : '';
    var selectedText = scope === 'selection' ? currentPageAiSelectedText() : '';
    var aiContext = buildPageAiContext(contextSnapshot, scope, selectedText);
    return {
      pageContext: {
        contextScope: scope,
        documentBlocks: null,
        node: {
          documentId: currentDocumentId(),
          title: title
        },
        subtree: null,
        outline: null,
        pageSubtreeSource: scope === 'page' ? (contextSnapshot.pageSubtreeSource || 'none') : 'scope:' + scope,
        evidence: selectedText ? [{ kind: 'selection', text: selectedText }] : null,
        pageOptions: scope === 'options' || scope === 'page' ? currentPageOptions() : null,
        contentAccess: 'mnote.doc.fetch',
        aiContext: aiContext
      },
      selectedText: selectedText,
      selectedBlockId: aiContext.selectedBlockIds[0] || null
    };
  }


  function updatePageAiTriggerState() {
    var trigger = document.querySelector('[data-testid="wolai-floating-ai"]');
    if (!(trigger instanceof HTMLElement)) return;
    trigger.setAttribute('data-state', pageUiState.pageAiOpen ? 'open' : 'closed');
    trigger.setAttribute('aria-expanded', pageUiState.pageAiOpen ? 'true' : 'false');
  }


  function pageAiSuggestions() {
    var title = searchText(document.querySelector('[data-page-title-current="true"]')?.textContent) || '当前页面';
    return [
      '帮我总结《' + title + '》当前内容',
      '把当前页面改写得更简洁一些',
      '提炼当前页的关键待办和行动项',
      '基于当前页内容生成一个三段式摘要'
    ];
  }

  function pageAiStorageKey() {
    return 'hermes_page_ai_session:' + currentDocumentId();
  }

  function pageAiTimestamp(value) {
    if (typeof value === 'number' && Number.isFinite(value)) return value;
    if (typeof value === 'string' && value.trim()) {
      var parsed = Date.parse(value);
      if (Number.isFinite(parsed)) return parsed;
    }
    return Date.now();
  }

  function pageAiBackendSessionQuery(extra) {
    var params = new URLSearchParams();
    params.set('source', 'acp');
    params.set('workspaceId', resolveWorkspaceId(document.body));
    params.set('documentId', currentDocumentId());
    params.set('profile', pageAiRunProfile());
    params.set('sourceKind', currentSourceKind());
    if (currentRootUri()) params.set('rootUri', currentRootUri());
    Object.keys(extra || {}).forEach(function(key) {
      var value = extra[key];
      if (value !== undefined && value !== null && String(value).trim() !== '') {
        params.set(key, String(value));
      }
    });
    return params.toString();
  }

  function pageAiNewSession(title) {
    var now = Date.now();
    return {
      id: 'sess_' + now + '_' + Math.random().toString(16).slice(2, 8),
      title: title || '新会话',
      profile: pageAiCurrentProfile(),
      acpRuntime: pageUiState.pageAiAcpRuntime || 'reasonix',
      createdAt: now,
      updatedAt: now,
      source: 'local',
      usage: null,
      status: 'idle',
      messages: []
    };
  }

  function pageAiUsageSummary(usage) {
    if (!usage || typeof usage !== 'object') return '';
    var used = usage.used ?? usage.contextUsed ?? usage.input_tokens ?? usage.inputTokens ?? usage.prompt_tokens ?? usage.promptTokens;
    var size = usage.size ?? usage.contextSize ?? usage.total_tokens ?? usage.totalTokens;
    var output = usage.output_tokens ?? usage.outputTokens ?? usage.completion_tokens ?? usage.completionTokens;
    var parts = [];
    if (Number.isFinite(Number(used))) parts.push('ctx ' + Number(used));
    if (Number.isFinite(Number(size)) && Number(size) > 0) parts.push('/ ' + Number(size));
    if (Number.isFinite(Number(output)) && Number(output) > 0) parts.push('out ' + Number(output));
    return parts.join(' ') || '';
  }

  function pageAiPermissionMessage(payload, eventType) {
    payload = payload && typeof payload === 'object' ? payload : {};
    var permissionId = String(payload.permissionId || payload.permission_id || payload.id || ('perm_' + Date.now())).trim();
    var toolName = String(payload.toolName || payload.tool || payload.name || payload.method || 'session/request_permission').trim();
    var args = payload.arguments || payload.args || payload.input || payload.params || payload;
    var decision = String(payload.decision || payload.result || '').trim();
    if (!decision && eventType === 'permission.denied') decision = 'denied';
    if (!decision && eventType === 'permission.allowed') decision = 'allowed';
    return {
      role: 'tool',
      kind: 'permission',
      permissionId: permissionId,
      toolName: toolName,
      argsSummary: pageAiPreviewValue(args),
      content: decision === 'denied' ? '已自动拒绝权限请求' : (decision === 'allowed' ? '已自动允许权限请求' : '等待权限确认'),
      resolved: decision === 'denied' || decision === 'allowed',
      decision: decision
    };
  }

  function pageAiApplyPermissionEvent(eventName, payloadText) {
    var payload = null;
    try {
      payload = JSON.parse(payloadText || 'null');
    } catch (_) {
      payload = {};
    }
    var message = pageAiPermissionMessage(payload, eventName);
    var existing = pageUiState.pageAiMessages.find(function(item) {
      return item.kind === 'permission' && item.permissionId === message.permissionId;
    });
    if (existing) {
      Object.assign(existing, message);
    } else {
      pageUiState.pageAiMessages.push(message);
    }
    pageUiState.pageAiPermissionRequests = pageUiState.pageAiPermissionRequests.filter(function(item) {
      return item.permissionId !== message.permissionId;
    }).concat([message]).slice(-20);
    if (!message.resolved) {
      pageAiShowPermissionDialog(message);
    } else {
      pageAiHidePermissionDialog();
    }
  }

  function pageAiResolvePermission(permissionId, decision) {
    permissionId = String(permissionId || '').trim();
    if (!permissionId) return;
    // 调用后端 resolve-permission 端点，让 ACP agent 得到真实响应
    var runId = pageUiState.pageAiCurrentRunId;
    if (runId) {
      fetch('/api/hermes/client/runs/' + encodeURIComponent(runId) + '/resolve-permission', {
        method: 'POST',
        headers: { 'content-type': 'application/json' },
        body: JSON.stringify({ permissionId: permissionId, decision: decision })
      }).then(function(response) {
        if (!response.ok) console.warn('resolve-permission 后端返回异常', response.status);
      }).catch(function(err) {
        console.warn('resolve-permission 请求失败', err);
      });
    } else {
      console.warn('resolve-permission: 无活跃 runId，只能本地更新');
    }
    // 本地乐观更新 UI
    pageUiState.pageAiMessages.forEach(function(item) {
      if (item.kind === 'permission' && item.permissionId === permissionId) {
        item.resolved = true;
        item.decision = decision;
        item.content = decision === 'allow' ? '已允许权限请求' : '已拒绝权限请求';
      }
    });
    var dialog = document.querySelector('[data-page-ai-permission-dialog]');
    if (dialog instanceof HTMLElement) dialog.hidden = true;
    renderPageAiConversation();
  }

  function pageAiOpenLocation(loc) {
    var path = String(loc || '').trim();
    if (!path) return;
    openLocalResourceInActiveTab({ path: path }).then(function(opened) {
      if (!opened) {
        var href = buildLocalFileOpenUrl(path, false);
        if (href) window.open(href, '_blank', 'noopener,noreferrer');
      }
    });
  }

  function pageAiHidePermissionDialog() {
    var dialog = document.querySelector('[data-page-ai-permission-dialog]');
    if (dialog instanceof HTMLElement) dialog.hidden = true;
  }

  function pageAiShowPermissionDialog(message) {
    if (!message || message.kind !== 'permission') return;
    if (message.resolved) {
      pageAiHidePermissionDialog();
      return;
    }
    var dialog = document.querySelector('[data-page-ai-permission-dialog]');
    if (!(dialog instanceof HTMLElement)) {
      dialog = document.createElement('div');
      dialog.className = 'wolai-page-ai-permission-dialog';
      dialog.setAttribute('data-page-ai-permission-dialog', 'true');
      dialog.innerHTML = '' +
        '<div class="wolai-page-ai-permission-panel" role="dialog" aria-modal="false">' +
          '<div class="wolai-page-ai-memory-title" data-page-ai-permission-tool></div>' +
          '<div class="wolai-page-ai-tool-meta" data-page-ai-permission-args></div>' +
          '<div class="wolai-page-ai-message-actions">' +
            '<button type="button" class="wolai-page-ai-ghost" data-page-ai-permission-action="allow" data-page-ai-permission-dialog-action>允许</button>' +
            '<button type="button" class="wolai-page-ai-ghost" data-page-ai-permission-action="deny" data-page-ai-permission-dialog-action>拒绝</button>' +
          '</div>' +
        '</div>';
      document.body.appendChild(dialog);
    }
    var tool = dialog.querySelector('[data-page-ai-permission-tool]');
    if (tool instanceof HTMLElement) tool.textContent = message.toolName || 'session/request_permission';
    var args = dialog.querySelector('[data-page-ai-permission-args]');
    if (args instanceof HTMLElement) args.textContent = message.argsSummary || message.content || '';
    dialog.querySelectorAll('[data-page-ai-permission-dialog-action]').forEach(function(button) {
      if (button instanceof HTMLButtonElement) {
        button.setAttribute('data-page-ai-permission-id', message.permissionId || '');
        button.disabled = Boolean(message.resolved);
      }
    });
    dialog.hidden = false;
  }

  function pageAiNormalizeSessions(sessions) {
    return (Array.isArray(sessions) ? sessions : [])
      .slice(0, 20)
      .map(function(session) {
        var sessionStorage = String(session && (session.sessionStorage || session.session_storage) || '').trim();
        var persistence = String(session && session.persistence || '').trim();
        return {
          id: String(session && session.id || '').trim() || pageAiNewSession().id,
          title: String(session && session.title || '').trim() || '新会话',
          profile: String(session && session.profile || pageAiCurrentProfile()).trim() || 'default',
          acpRuntime: String(session && session.acpRuntime || 'reasonix').trim(),
          createdAt: pageAiTimestamp(session && session.createdAt),
          updatedAt: pageAiTimestamp(session && session.updatedAt),
          source: String(session && session.source || 'local').trim() || 'local',
          persistence: persistence,
          sessionStorage: sessionStorage,
          permissionLevel: String(session && (session.permissionLevel || session.permission_level) || '').trim(),
          shareId: String(session && (session.shareId || session.share_id) || '').trim(),
          runId: String(session && (session.runId || session.run_id) || '').trim(),
          status: String(session && session.status || '').trim(),
          usage: session && session.usage && typeof session.usage === 'object' ? session.usage : null,
          preview: String(session && session.preview || '').trim(),
          messages: Array.isArray(session && session.messages) ? session.messages.slice(-300) : []
        };
      })
      .sort(function(a, b) {
        return Number(b.updatedAt || 0) - Number(a.updatedAt || 0);
      });
  }

  function pageAiNormalizeBackendSessionRow(row) {
    if (!row || typeof row !== 'object') return null;
    var payload = row.payload && typeof row.payload === 'object' ? row.payload : {};
    var sessionId = String(row.sessionId || row.session_id || '').trim();
    if (!sessionId) return null;
    var title = String(row.title || payload.title || payload.message || '').trim();
    if (title.length > 28) title = title.slice(0, 28) + '…';
    var persistence = String(row.persistence || payload.persistence || '').trim();
    var sessionStorage = String(row.sessionStorage || row.session_storage || payload.sessionStorage || '').trim();
    if (!sessionStorage && persistence === 'convex_acp_runtime_store') sessionStorage = 'cloud';
    if (!sessionStorage && persistence === 'local_ai_session_jsonl') sessionStorage = String(row.shareId || payload.shareId || '').trim() ? 'local_shared' : 'local_private';
    return {
      id: sessionId,
      title: title || '当前页问答',
      profile: String(row.profile || payload.profile || pageAiRunProfile()).trim() || 'default',
      acpRuntime: String(row.acpRuntime || row.acp_runtime || payload.acpRuntime || 'reasonix').trim(),
      createdAt: pageAiTimestamp(row.createdAt || row.created_at),
      updatedAt: pageAiTimestamp(row.updatedAt || row.updated_at),
      source: sessionStorage === 'local_private' || sessionStorage === 'local_shared' ? 'local' : 'acp',
      persistence: persistence,
      sessionStorage: sessionStorage,
      permissionLevel: String(row.permissionLevel || row.permission_level || payload.permissionLevel || '').trim(),
      shareId: String(row.shareId || row.share_id || payload.shareId || '').trim(),
      runId: String(row.runId || row.run_id || '').trim(),
      status: String(row.status || (row.runtime && row.runtime.status) || '').trim(),
      usage: row.usage && typeof row.usage === 'object' ? row.usage : null,
      preview: String(payload.message || row.snippet || '').trim(),
      messages: []
    };
  }

  function pageAiMergeSessions(localSessions, backendSessions) {
    var byId = {};
    pageAiNormalizeSessions(localSessions).forEach(function(session) {
      byId[session.id] = session;
    });
    pageAiNormalizeSessions(backendSessions).forEach(function(session) {
      var existing = byId[session.id];
      byId[session.id] = Object.assign({}, existing || {}, session, {
        messages: session.messages.length ? session.messages : (existing && existing.messages || [])
      });
    });
    return pageAiNormalizeSessions(Object.keys(byId).map(function(id) { return byId[id]; }));
  }

  function pageAiSessionStorageLabel(session) {
    var storage = String(session && (session.sessionStorage || session.session_storage) || '').trim();
    var persistence = String(session && session.persistence || '').trim();
    if (storage === 'local_shared') return '共享会话';
    if (storage === 'local_private') return '本地私有';
    if (storage === 'cloud' || persistence === 'convex_acp_runtime_store') return '云端会话';
    if (persistence === 'local_ai_session_jsonl') return '本地私有';
    return String(session && session.source || '').trim() === 'acp' ? '云端会话' : '本地私有';
  }

  function pageAiLoadSessions() {
    if (pageUiState.pageAiSessions.length && pageUiState.pageAiActiveSessionId) return;
    try {
      var raw = window.localStorage.getItem(pageAiStorageKey());
      var parsed = raw ? JSON.parse(raw) : null;
      var activeId = String(parsed && parsed.activeSessionId || '').trim();
      var activeProfile = String(parsed && parsed.activeProfileName || '').trim();
      var activeAcpRuntime = String(parsed && parsed.activeAcpRuntime || '').trim();
      var sessions = pageAiNormalizeSessions(parsed && parsed.sessions);
      var storageVersion = Number(parsed && parsed.version || 0);
      if (storageVersion >= PAGE_AI_SESSION_STORAGE_VERSION && activeAcpRuntime) pageUiState.pageAiAcpRuntime = activeAcpRuntime;
      if (activeProfile) pageAiSetActiveProfile(activeProfile);
      if (sessions.length) {
        pageUiState.pageAiSessions = sessions;
        var activeSession = sessions.find(function(session) { return session.id === activeId; }) || sessions[0];
        pageUiState.pageAiActiveSessionId = activeSession.id;
        if (activeSession.profile) pageAiSetActiveProfile(activeSession.profile);
        if (!pageUiState.pageAiAcpRuntime && activeSession.acpRuntime) pageUiState.pageAiAcpRuntime = activeSession.acpRuntime;
        pageUiState.pageAiMessages = Array.isArray(activeSession.messages) ? activeSession.messages.slice() : [];
        return;
      }
      if (activeId) {
        pageUiState.pageAiSessions = [pageAiNewSession('当前页问答')];
        pageUiState.pageAiSessions[0].id = activeId;
        pageUiState.pageAiActiveSessionId = activeId;
        pageUiState.pageAiMessages = [];
        return;
      }
    } catch (_) {}
    var fresh = pageAiNewSession();
    pageUiState.pageAiSessions = [fresh];
    pageUiState.pageAiActiveSessionId = fresh.id;
    pageUiState.pageAiMessages = [];
  }

  async function pageAiLoadBackendSessions() {
    var response = await fetch('/api/hermes/client/sessions?' + pageAiBackendSessionQuery({ limit: 50 }), {
      headers: { 'accept': 'application/json' }
    });
    var payload = await response.json().catch(function(){ return null; });
    if (!response.ok || !payload || payload.ok !== true) {
      throw new Error(pageAiErrorMessage(payload, 'session_list_failed_' + response.status));
    }
    var backendSessions = pageAiNormalizeArray(payload.sessions).map(pageAiNormalizeBackendSessionRow).filter(Boolean);
    if (!backendSessions.length) return [];
    pageUiState.pageAiSessions = pageAiMergeSessions(pageUiState.pageAiSessions, backendSessions);
    if (!pageUiState.pageAiActiveSessionId || !pageUiState.pageAiSessions.find(function(session) { return session.id === pageUiState.pageAiActiveSessionId; })) {
      pageUiState.pageAiActiveSessionId = pageUiState.pageAiSessions[0].id;
    }
    var active = pageAiCurrentSession();
    if (active) {
      if (active.profile) pageAiSetActiveProfile(active.profile);
      if (!pageUiState.pageAiAcpRuntime && active.acpRuntime) pageUiState.pageAiAcpRuntime = active.acpRuntime;
      pageUiState.pageAiMessages = Array.isArray(active.messages) ? active.messages.slice() : pageUiState.pageAiMessages;
    }
    pageUiState.pageAiSessionError = '';
    pageAiPersistSessions();
    renderPageAiConversation();
    renderPageAiControls();
    return backendSessions;
  }

  function pageAiMessageFromRuntimeEvent(event) {
    if (!event || typeof event !== 'object') return null;
    var eventType = String(event.eventType || event.event_type || event.event || '').trim();
    var payload = event.payload && typeof event.payload === 'object' ? event.payload : event;
    if (eventType === 'message.delta') {
      var delta = String(payload.delta || payload.text || payload.output_text || '').trim();
      return delta ? { role: 'assistant', content: delta } : null;
    }
    if (eventType === 'thought.delta') {
      var thought = String(payload.delta || payload.text || '').trim();
      return thought ? { role: 'assistant', kind: 'thought', content: thought } : null;
    }
    if (eventType === 'tool.started' || eventType === 'tool.completed' || eventType === 'tool.failed') {
      var toolName = String(payload.toolName || payload.tool || payload.name || eventType).trim();
      var rawLocations = payload.locations;
      return {
        role: 'tool',
        content: toolName,
        toolName: toolName,
        toolCallId: String(payload.toolCallId || payload.tool_call_id || payload.id || ''),
        toolKind: String(payload.kind || ''),
        status: eventType === 'tool.completed' ? 'completed' : (eventType === 'tool.failed' ? 'failed' : 'running'),
        argsSummary: pageAiPreviewValue(payload.args || payload.arguments || payload.input),
        resultSummary: pageAiPreviewValue(payload.summary || payload.result || payload.output || payload.error),
        locations: Array.isArray(rawLocations) ? rawLocations.filter(function(l) { return typeof l === 'string' || (typeof l === 'object' && l && l.path); }).map(function(l) { return typeof l === 'string' ? l : l.path; }) : [],
        traceId: String(payload.traceId || payload.trace_id || ''),
        auditId: String(payload.auditId || payload.audit_id || '')
      };
    }
    if (eventType === 'permission.requested' || eventType === 'permission.denied' || eventType === 'permission.allowed') {
      return pageAiPermissionMessage(payload, eventType);
    }
    return null;
  }

  function pageAiApplyBackendSessionDetail(payload) {
    var sessionPayload = payload && payload.session ? payload.session : {};
    var runs = pageAiNormalizeArray(sessionPayload.runs);
    var latest = runs.length ? pageAiNormalizeBackendSessionRow(runs[0]) : null;
    var events = pageAiNormalizeArray(payload && payload.events);
    var messages = pageAiNormalizeArray(sessionPayload.messages).map(function(message) {
      return {
        role: message.role === 'user' ? 'user' : (message.role === 'tool' ? 'tool' : 'assistant'),
        content: String(message.content || '')
      };
    }).filter(function(message) { return message.content; });
    events.forEach(function(event) {
      var message = pageAiMessageFromRuntimeEvent(event);
      if (message) messages.push(message);
    });
    var current = pageAiCurrentSession();
    if (latest && current) {
      Object.assign(current, latest);
    }
    if (current) {
      current.messages = messages.slice(-300);
      current.updatedAt = Math.max(Number(current.updatedAt || 0), Date.now());
      if (latest && latest.usage) current.usage = latest.usage;
    }
    pageAiApplyRuntimeState(payload && payload.runtime);
    pageUiState.pageAiMessages = messages.slice(-300);
    pageAiPersistSessions();
  }

  async function pageAiLoadBackendSessionDetail(sessionId) {
    sessionId = String(sessionId || pageUiState.pageAiActiveSessionId || '').trim();
    if (!sessionId) return null;
    var response = await fetch('/api/hermes/client/sessions/' + encodeURIComponent(sessionId) + '?' + pageAiBackendSessionQuery({ limit: 200 }), {
      headers: { 'accept': 'application/json' }
    });
    var payload = await response.json().catch(function(){ return null; });
    if (!response.ok || !payload || payload.ok !== true) {
      throw new Error(pageAiErrorMessage(payload, 'session_detail_failed_' + response.status));
    }
    pageAiApplyBackendSessionDetail(payload);
    renderPageAiConversation();
    renderPageAiControls();
    return payload;
  }

  async function pageAiSearchBackendSessions(query) {
    var q = String(query || '').trim();
    pageUiState.pageAiSessionSearchQuery = q;
    if (!q) {
      pageUiState.pageAiSessionSearchResults = [];
      renderPageAiConversation();
      return [];
    }
    var response = await fetch('/api/hermes/client/sessions/search?' + pageAiBackendSessionQuery({ q: q, limit: 20 }), {
      headers: { 'accept': 'application/json' }
    });
    var payload = await response.json().catch(function(){ return null; });
    if (!response.ok || !payload || payload.ok !== true) {
      throw new Error(pageAiErrorMessage(payload, 'session_search_failed_' + response.status));
    }
    pageUiState.pageAiSessionSearchResults = pageAiNormalizeArray(payload.results).map(function(row) {
      var normalized = pageAiNormalizeBackendSessionRow(row) || {};
      normalized.snippet = String(row && row.snippet || normalized.preview || '').trim();
      return normalized;
    }).filter(function(row) { return row.id; });
    renderPageAiConversation();
    return pageUiState.pageAiSessionSearchResults;
  }

  function pageAiPersistSessions() {
    document.documentElement.setAttribute('data-mnote-page-ai-session-owner', 'hermes');
    document.documentElement.setAttribute('data-mnote-page-ai-session-key', pageAiStorageKey());
    try {
      pageAiSyncCurrentSessionMessages();
      window.localStorage.setItem(pageAiStorageKey(), JSON.stringify({
        version: PAGE_AI_SESSION_STORAGE_VERSION,
        activeSessionId: pageUiState.pageAiActiveSessionId,
        activeProfileName: pageAiCurrentProfile(),
        activeAcpRuntime: pageUiState.pageAiAcpRuntime || 'reasonix',
        sessions: pageAiNormalizeSessions(pageUiState.pageAiSessions)
      }));
    } catch (_) {}
  }

  async function pageAiEnsureHermesSession(forceCreate) {
    pageAiLoadSessions();
    var current = pageAiCurrentSession();
    if (!forceCreate && current && String(current.id || '').startsWith('mnote_') && current.profile === pageAiCurrentProfile()) return current;
    var response = await fetch('/api/hermes/client/sessions', {
      method: 'POST',
      headers: { 'content-type': 'application/json' },
      body: JSON.stringify({
        workspaceId: resolveWorkspaceId(document.body),
        documentId: currentDocumentId(),
        sourceKind: currentSourceKind(),
        rootUri: currentRootUri(),
        traceId: 'page-ai-' + Date.now().toString(36),
        profile: pageAiCurrentProfile(),
        title: current && current.title ? current.title : '当前页问答'
      })
    });
    var payload = await response.json().catch(function(){ return null; });
    if (!response.ok || !payload || payload.ok !== true) {
      throw new Error(pageAiErrorMessage(payload, 'hermes_session_failed_' + response.status));
    }
    var session = {
      id: String(payload.sessionId || '').trim(),
      title: String(payload.title || '当前页问答'),
      profile: String(payload.profile || pageAiCurrentProfile()).trim() || 'default',
      acpRuntime: pageUiState.pageAiAcpRuntime || 'reasonix',
      persistence: String(payload.persistence || '').trim(),
      sessionStorage: String(payload.sessionStorage || '').trim(),
      permissionLevel: String(payload.permissionLevel || '').trim(),
      shareId: String(payload.shareId || '').trim(),
      createdAt: Date.now(),
      updatedAt: Date.now(),
      messages: pageUiState.pageAiMessages.slice()
    };
    pageUiState.pageAiSessions = pageAiNormalizeSessions([session]);
    pageUiState.pageAiActiveSessionId = session.id;
    pageAiPersistSessions();
    renderPageAiConversation();
    return session;
  }

  async function pageAiRestoreHermesSession() {
    var current = pageAiCurrentSession();
    if (!current || !String(current.id || '').startsWith('mnote_')) return;
    var response = await fetch('/api/hermes/client/sessions/' + encodeURIComponent(current.id) + '/resume?' + pageAiBackendSessionQuery({}), {
      method: 'POST',
      headers: { 'accept': 'application/json' }
    });
    if (!response.ok) return;
    var payload = await response.json().catch(function(){ return null; });
    if (payload && payload.persistence === 'convex_acp_runtime_store') {
      pageAiApplyBackendSessionDetail(payload);
      renderPageAiConversation();
      renderPageAiControls();
      return;
    }
    var session = payload && (payload.session || (payload.upstream && payload.upstream.session) || payload);
    var messages = session && Array.isArray(session.messages) ? session.messages : [];
    pageAiApplyRuntimeState(payload && payload.runtime);
    if (session && (session.profile || session.profileName)) {
      current.profile = String(session.profile || session.profileName || current.profile || pageAiCurrentProfile());
    }
    if (!messages.length) {
      renderPageAiControls();
      return;
    }
    pageUiState.pageAiMessages = messages.slice(-300).map(function(message) {
      return {
        role: message.role === 'user' ? 'user' : (message.role === 'tool' ? 'tool' : 'assistant'),
        content: String(message.content || '')
      };
    });
    current.messages = pageUiState.pageAiMessages.slice();
    current.updatedAt = Date.now();
    renderPageAiConversation();
    renderPageAiControls();
  }

  function pageAiCurrentSession() {
    return pageUiState.pageAiSessions.find(function(session) {
      return session.id === pageUiState.pageAiActiveSessionId;
    }) || null;
  }

  function pageAiSyncCurrentSessionMessages() {
    var session = pageAiCurrentSession();
    if (!session) return;
    session.messages = Array.isArray(pageUiState.pageAiMessages) ? pageUiState.pageAiMessages.slice(-300) : [];
    session.profile = pageAiCurrentProfile();
    session.acpRuntime = pageUiState.pageAiAcpRuntime || 'reasonix';
    session.updatedAt = Date.now();
  }

  function pageAiSetActiveSession(sessionId) {
    var session = pageUiState.pageAiSessions.find(function(item) { return item.id === sessionId; });
    if (!session) return;
    pageUiState.pageAiActiveSessionId = session.id;
    pageUiState.pageAiMessages = Array.isArray(session.messages) ? session.messages.slice() : [];
    pageUiState.pageAiPage = 'chat';
    pageAiPersistSessions();
    renderPageAiControls();
    renderPageAiConversation();
    if (session.source === 'acp' || String(session.id || '').startsWith('mnote_')) {
      void pageAiLoadBackendSessionDetail(session.id).catch(function(error) {
        pageUiState.pageAiSessionError = error instanceof Error ? error.message : String(error);
        renderPageAiControls();
      });
    }
  }

  function pageAiStartNewSession() {
    var session = pageAiNewSession();
    pageUiState.pageAiSessions = pageAiNormalizeSessions([session].concat(pageUiState.pageAiSessions));
    pageUiState.pageAiActiveSessionId = session.id;
    pageUiState.pageAiMessages = [];
    pageUiState.pageAiPage = 'chat';
    pageAiPersistSessions();
    renderPageAiControls();
    renderPageAiConversation();
  }

  async function pageAiRenameBackendSession(sessionId) {
    var session = pageUiState.pageAiSessions.find(function(item) { return item.id === sessionId; });
    if (!session) return;
    var title = window.prompt('重命名 AI 会话', session.title || '当前页问答');
    if (title === null) return;
    title = String(title || '').trim();
    if (!title) return;
    var response = await fetch('/api/hermes/client/sessions/' + encodeURIComponent(sessionId) + '/rename?' + pageAiBackendSessionQuery({}), {
      method: 'POST',
      headers: { 'content-type': 'application/json', 'accept': 'application/json' },
      body: JSON.stringify({ title: title })
    });
    var payload = await response.json().catch(function(){ return null; });
    if (!response.ok || !payload || payload.ok !== true) {
      throw new Error(pageAiErrorMessage(payload, 'session_rename_failed_' + response.status));
    }
    session.title = String((payload.result && payload.result.title) || title);
    session.updatedAt = Date.now();
    pageAiPersistSessions();
    renderPageAiConversation();
    renderPageAiControls();
  }

  async function pageAiDeleteBackendSession(sessionId) {
    var session = pageUiState.pageAiSessions.find(function(item) { return item.id === sessionId; });
    if (!session) return;
    if (!window.confirm('确定删除 AI 会话“' + (session.title || sessionId) + '”吗？')) return;
    var response = await fetch('/api/hermes/client/sessions/' + encodeURIComponent(sessionId) + '?' + pageAiBackendSessionQuery({}), {
      method: 'DELETE',
      headers: { 'accept': 'application/json' }
    });
    var payload = await response.json().catch(function(){ return null; });
    if (!response.ok || !payload || payload.ok !== true) {
      throw new Error(pageAiErrorMessage(payload, 'session_delete_failed_' + response.status));
    }
    pageUiState.pageAiSessions = pageUiState.pageAiSessions.filter(function(item) { return item.id !== sessionId; });
    if (pageUiState.pageAiActiveSessionId === sessionId) {
      var next = pageUiState.pageAiSessions[0] || pageAiNewSession();
      if (!pageUiState.pageAiSessions.length) pageUiState.pageAiSessions = [next];
      pageUiState.pageAiActiveSessionId = next.id;
      pageUiState.pageAiMessages = Array.isArray(next.messages) ? next.messages.slice() : [];
    }
    pageAiPersistSessions();
    renderPageAiConversation();
    renderPageAiControls();
  }

  async function pageAiResumeBackendSession(sessionId) {
    sessionId = String(sessionId || pageUiState.pageAiActiveSessionId || '').trim();
    if (!sessionId) return;
    pageAiSetActiveSession(sessionId);
    var response = await fetch('/api/hermes/client/sessions/' + encodeURIComponent(sessionId) + '/resume?' + pageAiBackendSessionQuery({}), {
      method: 'POST',
      headers: { 'accept': 'application/json' }
    });
    var payload = await response.json().catch(function(){ return null; });
    if (!response.ok || !payload || payload.ok !== true) {
      throw new Error(pageAiErrorMessage(payload, 'session_resume_failed_' + response.status));
    }
    pageAiApplyBackendSessionDetail(payload);
    pageUiState.pageAiPage = 'chat';
    renderPageAiConversation();
    renderPageAiControls();
  }

  function pageAiProviderLabel(provider) {
    if (provider === 'codex') return 'Codex';
    if (provider === 'claudecode') return 'ClaudeCode';
    return 'Hermes';
  }

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

  function pageAiDefaultAcpRuntimes() {
    return [
      {
        name: 'reasonix',
        title: 'ACP · Reasonix',
        description: '通过 ACP 协议直连 Reasonix（DeepSeek 缓存优先）',
        model: 'deepseek-chat',
        preset: 'auto'
      },
      {
        name: 'hermes',
        title: 'ACP · Hermes',
        description: '通过 ACP 协议直连 Hermes agent runtime'
      }
    ];
  }

  function pageAiNormalizeAcpRuntimes(runtimes) {
    var byName = {};
    pageAiDefaultAcpRuntimes().forEach(function(runtime) {
      byName[runtime.name] = Object.assign({}, runtime);
    });
    pageAiNormalizeArray(runtimes).forEach(function(runtime) {
      var name = String(runtime && runtime.name || '').trim();
      if (name !== 'reasonix' && name !== 'hermes') return;
      byName[name] = Object.assign({}, byName[name] || {}, runtime, { name: name });
    });
    return ['reasonix', 'hermes'].map(function(name) { return byName[name]; }).filter(Boolean);
  }

  function pageAiUnwrapUpstream(payload) {
    if (payload && typeof payload === 'object' && payload.upstream) return payload.upstream;
    return payload || null;
  }

  function pageAiProfileValue(profile) {
    if (profile && typeof profile === 'object') {
      return String(profile.name || profile.profile || profile.id || '').trim();
    }
    return String(profile || '').trim();
  }

  function pageAiCurrentProfile() {
    var active = String(pageUiState.pageAiActiveProfileName || '').trim();
    if (active) return active;
    var selected = pageUiState.pageAiProfiles.find(function(profile) {
      return profile && profile.active;
    });
    return pageAiProfileValue(selected) || 'mnoteai';
  }

  function pageAiRunProfile() {
    return String(pageUiState.pageAiAcpRuntime || 'reasonix').trim() === 'reasonix' ? 'reasonix' : pageAiCurrentProfile();
  }

  function pageAiMnoteToolModel() {
    return String(document.documentElement.getAttribute('data-mnote-page-ai-tool-model') || 'deepseek-v4-flash').trim() || 'deepseek-v4-flash';
  }

  function pageAiCurrentProfileRecord() {
    var active = pageAiCurrentProfile();
    return pageUiState.pageAiProfiles.find(function(profile) {
      return pageAiProfileValue(profile) === active;
    }) || null;
  }

  function pageAiCurrentModelLabel() {
    var profile = pageAiCurrentProfileRecord();
    var toolModel = pageAiMnoteToolModel();
    if (!profile) return 'tool: ' + toolModel;
    var model = String(profile.model || '').trim();
    var gateway = String(profile.gateway || '').trim();
    var profileLabel = [model, gateway].filter(Boolean).join(' · ') || '由 Hermes 决定';
    return 'tool: ' + toolModel + ' · profile: ' + profileLabel;
  }

  function pageAiNormalizeProfiles(payload) {
    var upstream = pageAiUnwrapUpstream(payload);
    var profiles = pageAiNormalizeArray(upstream && upstream.profiles ? upstream.profiles : upstream);
    return profiles.map(function(profile) {
      return {
        name: pageAiProfileValue(profile) || 'default',
        active: Boolean(profile && profile.active),
        model: String(profile && profile.model || '').trim(),
        gateway: String(profile && profile.gateway || '').trim(),
        alias: String(profile && profile.alias || '').trim()
      };
    });
  }

  function pageAiNormalizeSkills(payload) {
    var upstream = pageAiUnwrapUpstream(payload);
    var categories = pageAiNormalizeArray(upstream && upstream.categories ? upstream.categories : []);
    var archived = pageAiNormalizeArray(upstream && upstream.archived ? upstream.archived : []);
    return {
      categories: categories.map(function(category) {
        return {
          name: String(category && category.name || '').trim() || 'misc',
          description: String(category && category.description || '').trim(),
          skills: pageAiNormalizeArray(category && category.skills).map(function(skill) {
            return {
              name: String(skill && skill.name || '').trim(),
              description: String(skill && skill.description || '').trim(),
              enabled: skill && skill.enabled !== false,
              source: String(skill && skill.source || 'local').trim() || 'local',
              origin: String(skill && skill.origin || '').trim(),
              createdBy: String(skill && skill.createdBy || '').trim(),
              patchCount: Number(skill && skill.patchCount || 0),
              modified: Boolean(skill && skill.modified)
            };
          })
        };
      }),
      archived: archived.map(function(skill) {
        return {
          name: String(skill && skill.name || '').trim(),
          description: String(skill && skill.description || '').trim(),
          enabled: skill && skill.enabled !== false,
          source: String(skill && skill.source || 'local').trim() || 'local',
          origin: String(skill && skill.origin || '').trim(),
          createdBy: String(skill && skill.createdBy || '').trim(),
          patchCount: Number(skill && skill.patchCount || 0),
          modified: Boolean(skill && skill.modified)
        };
      })
    };
  }

  function pageAiSkillListEntries() {
    var result = [];
    pageAiNormalizeArray(pageUiState.pageAiSkills.categories).forEach(function(category) {
      pageAiNormalizeArray(category.skills).forEach(function(skill) {
        result.push({
          category: category.name,
          name: skill.name,
          description: skill.description,
          enabled: skill.enabled !== false,
          source: skill.source || 'local',
          origin: skill.origin || '',
          createdBy: skill.createdBy || '',
          patchCount: Number(skill.patchCount || 0),
          modified: Boolean(skill.modified)
        });
      });
    });
    return result.concat(pageAiNormalizeArray(pageUiState.pageAiSkills.archived));
  }

  function pageAiSetActiveProfile(profileName) {
    var next = String(profileName || '').trim() || 'mnoteai';
    pageUiState.pageAiActiveProfileName = next;
    document.documentElement.setAttribute('data-mnote-page-ai-profile', next);
    document.documentElement.setAttribute('data-mnote-page-ai-tool-model', pageAiMnoteToolModel());
  }

  function pageAiSetRunStatus(status, runId) {
    pageUiState.pageAiRunStatus = status || 'idle';
    pageUiState.pageAiCurrentRunId = runId || pageUiState.pageAiCurrentRunId || '';
    document.documentElement.setAttribute('data-mnote-page-ai-run-status', pageUiState.pageAiRunStatus);
    if (pageUiState.pageAiCurrentRunId) {
      document.documentElement.setAttribute('data-mnote-page-ai-run-id', pageUiState.pageAiCurrentRunId);
    }
  }

  function pageAiApplyRuntimeState(runtime) {
    if (!runtime || typeof runtime !== 'object') return;
    var status = String(runtime.status || '').trim();
    var runId = String(runtime.runId || runtime.run_id || '').trim();
    if (status) pageAiSetRunStatus(status, runId);
    var queueLength = Number(runtime.queueLength || runtime.queue_length || 0);
    if (Number.isFinite(queueLength)) pageUiState.pageAiQueueLength = Math.max(0, queueLength);
    var toolName = String(runtime.lastToolName || runtime.last_tool_name || '').trim();
    if (toolName) {
      pageUiState.pageAiLastToolCall = {
        event: String(runtime.lastEvent || runtime.last_event || ''),
        name: toolName,
        runId: runId,
        traceId: String(runtime.traceId || runtime.trace_id || ''),
        auditId: String(runtime.lastAuditId || runtime.last_audit_id || '')
      };
    }
  }

  function pageAiApplyQueuedRun(payload) {
    if (!payload || payload.queued !== true) return false;
    var queueId = String(payload.queueId || payload.queue_id || '').trim();
    var queueLength = Number(payload.queueLength || payload.queue_length || 0);
    pageUiState.pageAiQueueLength = Number.isFinite(queueLength) ? Math.max(0, queueLength) : 1;
    if (queueId) {
      pageUiState.pageAiQueuedItems = pageUiState.pageAiQueuedItems.filter(function(item) {
        return item.queueId !== queueId;
      }).concat([{
        queueId: queueId,
        sessionId: String(payload.sessionId || payload.session_id || pageUiState.pageAiActiveSessionId || ''),
        traceId: String(payload.traceId || payload.trace_id || ''),
        queuedAt: Number(payload.queuedAt || payload.queued_at || Date.now())
      }]);
    }
    pageAiSetRunStatus('queued', pageUiState.pageAiCurrentRunId);
    return true;
  }

  function pageAiPreviewValue(value) {
    if (value == null || value === '') return '';
    if (typeof value === 'string') return value.length > 120 ? value.slice(0, 120) + '…' : value;
    try {
      var text = JSON.stringify(value);
      return text.length > 120 ? text.slice(0, 120) + '…' : text;
    } catch (_) {
      return String(value);
    }
  }

  function pageAiNormalizeToolName(name) {
    return String(name || '').trim().replace(/_/g, '.');
  }

  function pageAiFormatChangedFiles(files) {
    return pageAiNormalizeArray(files).map(function(file) {
      var path = String(file && file.path || '').trim();
      var changeType = String(file && file.changeType || file.change_type || 'modified').trim();
      var summary = String(file && file.summary || '').trim();
      return [changeType, path, summary].filter(Boolean).join(' · ');
    }).filter(Boolean).join('\n');
  }

  function pageAiToolEventDeepFindString(value, keys, depth) {
    if (!value || typeof value !== 'object' || depth > 5) return '';
    for (var index = 0; index < keys.length; index += 1) {
      var key = keys[index];
      if (Object.prototype.hasOwnProperty.call(value, key) && typeof value[key] === 'string' && value[key].trim()) {
        return value[key].trim();
      }
    }
    if (Array.isArray(value)) {
      for (var arrayIndex = 0; arrayIndex < value.length; arrayIndex += 1) {
        var fromArray = pageAiToolEventDeepFindString(value[arrayIndex], keys, depth + 1);
        if (fromArray) return fromArray;
      }
      return '';
    }
    var preferred = ['audit', 'args', 'arguments', 'input', 'result', 'summary', 'output', 'upstream'];
    for (var prefIndex = 0; prefIndex < preferred.length; prefIndex += 1) {
      var child = value[preferred[prefIndex]];
      var fromPreferred = pageAiToolEventDeepFindString(child, keys, depth + 1);
      if (fromPreferred) return fromPreferred;
    }
    var objectKeys = Object.keys(value);
    for (var objectIndex = 0; objectIndex < objectKeys.length; objectIndex += 1) {
      var fromObject = pageAiToolEventDeepFindString(value[objectKeys[objectIndex]], keys, depth + 1);
      if (fromObject) return fromObject;
    }
    return '';
  }

  function pageAiNotifyToolWriteCompleted(toolName, toolEvent, runId, runTraceId) {
    var normalizedTool = pageAiNormalizeToolName(toolName);
    var writesCurrentPage = [
      'mnote.page.save',
      'mnote.page.update.title',
      'mnote.page.update.options',
      'mnote.doc.apply.block.ops',
      'mnote.block.replace',
      'mnote.block.insert.after',
      'mnote.block.delete',
      'mnote.block.move.after'
    ].indexOf(normalizedTool) >= 0 || [
      'mnote.page.update_title',
      'mnote.page.update_options',
      'mnote.doc.apply_block_ops',
      'mnote.block.replace',
      'mnote.block.insert_after',
      'mnote.block.delete',
      'mnote.block.move_after'
    ].indexOf(String(toolName || '').trim()) >= 0;
    if (!writesCurrentPage) return;
    var documentId = pageAiToolEventDeepFindString(toolEvent, ['documentId', 'document_id'], 0) || currentDocumentId();
    var workspaceId = pageAiToolEventDeepFindString(toolEvent, ['workspaceId', 'workspace_id'], 0) || resolveWorkspaceId(document.body);
    try {
      window.dispatchEvent(new CustomEvent('mnote:page-ai-tool-write-completed', {
        detail: {
          toolName: toolName,
          normalizedToolName: normalizedTool,
          documentId: documentId,
          workspaceId: workspaceId,
          runId: String(toolEvent && (toolEvent.run_id || toolEvent.runId) || runId || ''),
          traceId: String(toolEvent && (toolEvent.trace_id || toolEvent.traceId) || runTraceId || ''),
          toolCallId: String(toolEvent && (toolEvent.tool_call_id || toolEvent.toolCallId || toolEvent.id) || '')
        }
      }));
    } catch (error) {
      console.warn('mnote 页面 AI 写入刷新事件派发失败', error);
    }
  }

  function pageAiApplyToolEvent(eventName, payloadText, runId, runTraceId) {
    var toolEvent = null;
    try {
      toolEvent = JSON.parse(payloadText || 'null');
    } catch (_) {
      toolEvent = {};
    }
    var rawToolName = toolEvent && (toolEvent.name || toolEvent.tool || toolEvent.toolName || '');
    var toolName = String(rawToolName || eventName);
    var toolCallId = String(toolEvent && (toolEvent.tool_call_id || toolEvent.toolCallId || toolEvent.id) || (runId + ':' + toolName));
    var eventStatus = String(toolEvent && toolEvent.status || '').trim();
    var status = eventName === 'tool.completed'
      ? (toolEvent && toolEvent.error ? 'failed' : 'completed')
      : (eventName === 'tool.failed' ? 'failed' : (eventStatus || 'running'));
    if (status === 'in_progress' || status === 'pending') status = 'running';
    var traceId = String(toolEvent && (toolEvent.trace_id || toolEvent.traceId) || runTraceId || '');
    var auditId = String(toolEvent && (toolEvent.audit_id || toolEvent.auditId) || '');
    var argsSummary = pageAiPreviewValue(toolEvent && (toolEvent.arguments || toolEvent.args || toolEvent.input));
    var resultSource = toolEvent && (toolEvent.summary || toolEvent.result || toolEvent.output);
    if (!resultSource && status === 'failed') {
      resultSource = [toolEvent && toolEvent.code, toolEvent && toolEvent.error].filter(Boolean).join(' ');
    }
    var resultSummary = pageAiPreviewValue(resultSource);
    pageUiState.pageAiLastToolCall = {
      event: eventName,
      name: toolName,
      runId: String(toolEvent && (toolEvent.run_id || toolEvent.runId) || runId),
      traceId: traceId,
      auditId: auditId
    };
    var rawLocations = toolEvent && toolEvent.locations;
    var locations = Array.isArray(rawLocations) ? rawLocations.filter(function(l) { return typeof l === 'string' || (typeof l === 'object' && l && l.path); }).map(function(l) { return typeof l === 'string' ? l : l.path; }) : [];
    var existing = pageUiState.pageAiMessages.find(function(item) {
      return item.role === 'tool' && item.toolCallId === toolCallId;
    });
    if (!rawToolName && existing && existing.toolName) toolName = existing.toolName;
    if (!existing) {
      existing = {
        role: 'tool',
        content: toolName,
        toolCallId: toolCallId,
        toolName: toolName,
        toolKind: String(toolEvent && toolEvent.kind || ''),
        status: status,
        argsSummary: '',
        resultSummary: '',
        locations: locations,
        traceId: traceId,
        auditId: auditId
      };
      pageUiState.pageAiMessages.push(existing);
    }
    existing.content = toolName;
    existing.toolName = toolName;
    existing.toolKind = String(toolEvent && toolEvent.kind || existing.toolKind || '');
    existing.status = status;
    existing.traceId = traceId || existing.traceId || '';
    existing.auditId = auditId || existing.auditId || '';
    if (argsSummary) existing.argsSummary = argsSummary;
    if (resultSummary) existing.resultSummary = resultSummary;
    if (locations.length) existing.locations = locations;
    if (status === 'completed') {
      pageAiNotifyToolWriteCompleted(toolName, toolEvent, runId, runTraceId);
    }
  }

  async function pageAiCancelQueuedRun(queueId) {
    queueId = String(queueId || '').trim();
    if (!queueId) return;
    var item = pageUiState.pageAiQueuedItems.find(function(entry) {
      return entry.queueId === queueId;
    });
    var sessionId = String(item && item.sessionId || pageUiState.pageAiActiveSessionId || '').trim();
    if (!sessionId) return;
    try {
      var response = await fetch('/api/hermes/client/sessions/' + encodeURIComponent(sessionId) + '/queue/' + encodeURIComponent(queueId), {
        method: 'DELETE',
        headers: { 'accept': 'application/json' }
      });
      var payload = await response.json().catch(function(){ return null; });
      if (!response.ok) throw new Error(pageAiErrorMessage(payload, 'queue_cancel_failed_' + response.status));
      pageUiState.pageAiQueuedItems = pageUiState.pageAiQueuedItems.filter(function(entry) {
        return entry.queueId !== queueId;
      });
      var queueLength = Number(payload && (payload.queueLength || payload.queue_length || 0));
      pageUiState.pageAiQueueLength = Number.isFinite(queueLength) ? Math.max(0, queueLength) : pageUiState.pageAiQueuedItems.length;
      pageUiState.pageAiMessages.push({ role: 'assistant', content: '已取消一条 Hermes 队列项。' });
    } catch (error) {
      pageUiState.pageAiMessages.push({
        role: 'assistant',
        content: '取消 Hermes 队列项失败：' + (error instanceof Error ? error.message : String(error))
      });
    }
    renderPageAiConversation();
    renderPageAiControls();
  }

  function pageAiSetContextScope(scope) {
    var next = String(scope || '').trim() || 'page';
    pageUiState.pageAiContextScope = next;
    document.documentElement.setAttribute('data-mnote-page-ai-context-scope', next);
  }

  function pageAiRunStatusLabel(status) {
    if (status === 'queued') return '排队中';
    if (status === 'running') return '运行中';
    if (status === 'tool_calling') return '调用工具';
    if (status === 'completed') return '已完成';
    if (status === 'failed') return '失败';
    if (status === 'aborted') return '已停止';
    return '空闲';
  }

  function pageAiMemoryFileLabel(section) {
    if (section === 'soul') return 'SOUL.md';
    if (section === 'user') return 'USER.md';
    return 'MEMORY.md';
  }

  function pageAiContextScopeLabel(scope) {
    if (scope === 'selection') return '当前选区';
    if (scope === 'block') return '当前块';
    if (scope === 'options') return '页面设置';
    return '当前页';
  }

  function pageAiHermesSettingsUrl() {
    var configured = String(window.__mnoteHermesSettingsUrl || '').trim();
    return configured || '';
  }

  function pageAiOpenHermesSettings() {
    var url = pageAiHermesSettingsUrl();
    if (url) {
      window.open(url, '_blank', 'noopener,noreferrer');
      return;
    }
    pageUiState.pageAiProfileError = '未配置 Hermes 设置入口：请设置 MNOTE_WEB_HERMES_UPSTREAM_URL。';
    renderPageAiControls();
  }

  function pageAiNormalizeTools(payload) {
    var upstream = pageAiUnwrapUpstream(payload) || {};
    var tools = pageAiNormalizeArray(upstream.tools || upstream);
    return tools.map(function(tool) {
      var name = String(tool && (tool.name || tool.toolName || tool.tool) || '').trim();
      if (!name) return null;
      return {
        name: name,
        description: String(tool && tool.description || '').trim(),
        scope: String(tool && (tool.scope || tool.requiredScope || '') || '').trim(),
        kind: String(tool && (tool.kind || tool.mode || '') || '').trim(),
        status: String(tool && (tool.status || tool.permission || 'available') || '').trim(),
        enabled: tool && tool.enabled !== false,
        unavailableReason: String(tool && (tool.unavailableReason || tool.reason || '') || '').trim()
      };
    }).filter(Boolean);
  }

  function pageAiErrorMessage(payload, fallback) {
    if (!payload || typeof payload !== 'object') return fallback;
    return String(payload.message || payload.error || payload.code || fallback || '').trim() || fallback;
  }

  function pageAiNormalizeGatewayHealth(payload) {
    var upstream = pageAiUnwrapUpstream(payload) || payload || {};
    return {
      ok: Boolean(upstream.ok),
      profile: upstream.profile || null,
      gateway: upstream.gateway || null,
      suggestions: pageAiNormalizeArray(upstream.suggestions).map(function(item) {
        return String(item || '').trim();
      }).filter(Boolean)
    };
  }

  function pageAiSetDraftForSection(section, value) {
    pageUiState.pageAiProfileMemoryDrafts[section] = String(value == null ? '' : value);
  }

  function pageAiApplyProfileMemory(payload) {
    var upstream = pageAiUnwrapUpstream(payload) || {};
    pageUiState.pageAiProfileMemory = {
      memory: String(upstream.memory || ''),
      user: String(upstream.user || ''),
      soul: String(upstream.soul || '')
    };
    pageUiState.pageAiProfileMemoryDrafts = {
      memory: pageUiState.pageAiProfileMemory.memory,
      user: pageUiState.pageAiProfileMemory.user,
      soul: pageUiState.pageAiProfileMemory.soul
    };
  }

  async function pageAiLoadTools() {
    try {
      var response = await fetch('/api/hermes/client/tools?scope=mnote&profile=' + encodeURIComponent(pageAiCurrentProfile()), {
        headers: { 'accept': 'application/json' }
      });
      var payload = await response.json().catch(function(){ return null; });
      if (!response.ok) throw new Error(pageAiErrorMessage(payload, 'tools_failed_' + response.status));
      pageUiState.pageAiTools = pageAiNormalizeTools(payload);
      pageUiState.pageAiToolsError = '';
    } catch (error) {
      pageUiState.pageAiToolsError = error instanceof Error ? error.message : String(error);
    }
    renderPageAiControls();
  }

  async function pageAiLoadGatewayHealth() {
    try {
      var response = await fetch('/api/hermes/client/gateway/health?profile=' + encodeURIComponent(pageAiCurrentProfile()), {
        headers: { 'accept': 'application/json' }
      });
      var payload = await response.json().catch(function(){ return null; });
      if (!response.ok) throw new Error(pageAiErrorMessage(payload, 'gateway_health_failed_' + response.status));
      pageUiState.pageAiGatewayHealth = pageAiNormalizeGatewayHealth(payload);
      pageUiState.pageAiGatewayHealthError = '';
    } catch (error) {
      pageUiState.pageAiGatewayHealthError = error instanceof Error ? error.message : String(error);
    }
    renderPageAiControls();
  }

  async function pageAiStopRun() {
    var runId = String(pageUiState.pageAiCurrentRunId || '').trim();
    if (!runId || pageUiState.pageAiRunStatus === 'idle' || pageUiState.pageAiRunStatus === 'completed') return;
    pageUiState.pageAiStoppedRunIds[runId] = true;
    try {
      var response = await fetch('/api/hermes/client/runs/' + encodeURIComponent(runId) + '/abort', {
        method: 'POST',
        headers: { 'content-type': 'application/json' },
        body: JSON.stringify({
          sessionId: pageUiState.pageAiActiveSessionId,
          profile: pageAiRunProfile(),
          acpRuntime: pageUiState.pageAiAcpRuntime || 'reasonix',
          reason: 'page_ai_user_stop'
        })
      });
      var payload = await response.json().catch(function(){ return null; });
      if (!response.ok) throw new Error(pageAiErrorMessage(payload, 'run_abort_failed_' + response.status));
      pageAiApplyRuntimeState(payload && payload.runtime);
      pageAiSetRunStatus('aborted', runId);
      pageUiState.pageAiMessages.push({ role: 'assistant', content: '已请求 Hermes 停止当前 run。' });
    } catch (error) {
      pageAiSetRunStatus('failed', runId);
      pageUiState.pageAiMessages.push({
        role: 'assistant',
        content: '停止 Hermes run 失败：' + (error instanceof Error ? error.message : String(error))
      });
    }
    renderPageAiControls();
    renderPageAiConversation();
  }

  function pageAiFilteredSkillEntries() {
    var query = String(pageUiState.pageAiSkillQuery || '').trim().toLowerCase();
    return pageAiSkillListEntries().filter(function(skill) {
      if (!query) return true;
      return String(skill.name || '').toLowerCase().indexOf(query) >= 0
        || String(skill.description || '').toLowerCase().indexOf(query) >= 0
        || String(skill.category || '').toLowerCase().indexOf(query) >= 0
        || pageAiSkillOriginLabel(skill).toLowerCase().indexOf(query) >= 0;
    });
  }

  function pageAiSkillOriginLabel(skill) {
    var origin = String(skill && skill.origin || '').trim();
    if (origin === 'generated' || String(skill && skill.createdBy || '') === 'agent') return '生成';
    if (origin === 'installed') return '安装';
    if (origin === 'builtin') return '内置';
    if (origin === 'copied') return '本地';
    var source = String(skill && skill.source || '').trim();
    if (source === 'hub') return '安装';
    if (source === 'builtin') return '内置';
    if (source === 'reasonix') {
      if (origin === 'project') return 'Reasonix 项目';
      if (origin === 'global') return 'Reasonix 全局';
      return 'Reasonix';
    }
    return '本地';
  }

  async function pageAiLoadProfiles() {
    try {
      var response = await fetch('/api/hermes/client/profiles', {
        headers: { 'accept': 'application/json' }
      });
      var payload = await response.json().catch(function(){ return null; });
      if (!response.ok) throw new Error(pageAiErrorMessage(payload, 'profiles_failed_' + response.status));
      var profiles = pageAiNormalizeProfiles(payload);
      pageUiState.pageAiProfiles = profiles.length ? profiles : [{ name: 'mnoteai', active: true }];
      var acpRuntimes = Array.isArray(payload.acpRuntimes) ? payload.acpRuntimes : [];
      pageUiState.pageAiAcpRuntimes = pageAiNormalizeAcpRuntimes(acpRuntimes);
      var current = pageAiCurrentProfile();
      var hasCurrent = pageUiState.pageAiProfiles.some(function(profile) { return pageAiProfileValue(profile) === current; });
      var hasMnoteAi = pageUiState.pageAiProfiles.some(function(profile) { return pageAiProfileValue(profile) === 'mnoteai'; });
      var active = pageAiProfileValue(pageUiState.pageAiProfiles.find(function(profile) { return profile.active; }));
      pageAiSetActiveProfile(hasCurrent && current !== 'default' ? current : (hasMnoteAi ? 'mnoteai' : (active || current)));
      pageUiState.pageAiProfileError = '';
      void pageAiLoadGatewayHealth();
    } catch (error) {
      pageUiState.pageAiProfileError = error instanceof Error ? error.message : String(error);
      if (!pageUiState.pageAiProfiles.length) pageUiState.pageAiProfiles = [{ name: 'mnoteai', active: true }];
      pageUiState.pageAiAcpRuntimes = pageAiNormalizeAcpRuntimes(pageUiState.pageAiAcpRuntimes);
      pageAiSetActiveProfile(pageAiCurrentProfile());
    }
    renderPageAiProviderButtons();
    renderPageAiControls();
  }

  async function pageAiSwitchProfile(profileName) {
    var next = String(profileName || '').trim();
    if (!next) return;
    pageAiSetActiveProfile(next);
    renderPageAiProviderButtons();
    renderPageAiControls();
    try {
      var response = await fetch('/api/hermes/client/profiles/active', {
        method: 'PUT',
        headers: { 'content-type': 'application/json' },
        body: JSON.stringify({ name: next })
      });
      var payload = await response.json().catch(function(){ return null; });
      if (!response.ok) throw new Error(pageAiErrorMessage(payload, 'profile_switch_failed_' + response.status));
      pageUiState.pageAiSessions = pageUiState.pageAiSessions.filter(function(session) {
        return session && session.profile === next;
      });
      pageUiState.pageAiActiveSessionId = '';
      pageUiState.pageAiMessages = [];
      pageAiPersistSessions();
      await pageAiEnsureHermesSession(true);
      await pageAiLoadProfileMemory();
      await pageAiLoadSkills();
      await pageAiLoadTools();
      await pageAiLoadGatewayHealth();
      renderPageAiControls();
    } catch (error) {
      pageUiState.pageAiProfileError = error instanceof Error ? error.message : String(error);
    }
    renderPageAiConversation();
  }

  async function pageAiLoadProfileMemory() {
    try {
      var response = await fetch('/api/hermes/client/profile-memory?profile=' + encodeURIComponent(pageAiCurrentProfile()), {
        headers: { 'accept': 'application/json' }
      });
      var payload = await response.json().catch(function(){ return null; });
      if (!response.ok) throw new Error(pageAiErrorMessage(payload, 'profile_memory_failed_' + response.status));
      pageAiApplyProfileMemory(payload);
      pageUiState.pageAiProfileMemoryError = '';
    } catch (error) {
      pageUiState.pageAiProfileMemoryError = error instanceof Error ? error.message : String(error);
    }
    renderPageAiControls();
    renderPageAiConversation();
  }

  async function pageAiSaveProfileMemory(section) {
    var normalized = String(section || '').trim();
    if (['memory', 'user', 'soul'].indexOf(normalized) < 0) return;
    var content = String(pageUiState.pageAiProfileMemoryDrafts[normalized] || '');
    try {
      var response = await fetch('/api/hermes/client/profile-memory', {
        method: 'POST',
        headers: { 'content-type': 'application/json' },
        body: JSON.stringify({
          profile: pageAiCurrentProfile(),
          section: normalized,
          content: content
        })
      });
      var payload = await response.json().catch(function(){ return null; });
      if (!response.ok) throw new Error(pageAiErrorMessage(payload, 'profile_memory_save_failed_' + response.status));
      pageUiState.pageAiProfileMemory[normalized] = content;
      pageUiState.pageAiProfileMemoryError = '';
      document.documentElement.setAttribute('data-mnote-page-ai-memory-saved', normalized);
    } catch (error) {
      pageUiState.pageAiProfileMemoryError = error instanceof Error ? error.message : String(error);
    }
    renderPageAiControls();
    renderPageAiConversation();
  }

  async function pageAiLoadSkills() {
    try {
      var runtime = String(pageUiState.pageAiAcpRuntime || 'reasonix').trim();
      var params = runtime === 'reasonix'
        ? 'runtime=reasonix'
        : 'profile=' + encodeURIComponent(pageAiCurrentProfile());
      var response = await fetch('/api/hermes/client/skills?' + params, {
        headers: { 'accept': 'application/json' }
      });
      var payload = await response.json().catch(function(){ return null; });
      if (!response.ok) throw new Error(pageAiErrorMessage(payload, 'skills_failed_' + response.status));
      pageUiState.pageAiSkills = pageAiNormalizeSkills(payload);
      pageUiState.pageAiSkillError = '';
    } catch (error) {
      pageUiState.pageAiSkillError = error instanceof Error ? error.message : String(error);
      pageUiState.pageAiSkills = { categories: [], archived: [] };
    }
    renderPageAiControls();
    renderPageAiConversation();
  }

  async function pageAiToggleSkill(skillName, enabled) {
    var name = String(skillName || '').trim();
    if (!name) return;
    if (String(pageUiState.pageAiAcpRuntime || 'reasonix').trim() === 'reasonix') return;
    var previous = null;
    pageAiSkillListEntries().forEach(function(skill) {
      if (skill.name === name && previous == null) previous = skill.enabled !== false;
    });
    try {
      var response = await fetch('/api/hermes/client/skills/toggle', {
        method: 'PUT',
        headers: { 'content-type': 'application/json' },
        body: JSON.stringify({
          profile: pageAiCurrentProfile(),
          name: name,
          enabled: Boolean(enabled)
        })
      });
      var payload = await response.json().catch(function(){ return null; });
      if (!response.ok) throw new Error(pageAiErrorMessage(payload, 'skill_toggle_failed_' + response.status));
      pageAiNormalizeArray(pageUiState.pageAiSkills.categories).forEach(function(category) {
        pageAiNormalizeArray(category.skills).forEach(function(skill) {
          if (skill.name === name) skill.enabled = Boolean(enabled);
        });
      });
      document.documentElement.setAttribute('data-mnote-page-ai-skill-toggled', name);
      pageUiState.pageAiSkillError = '';
    } catch (error) {
      pageUiState.pageAiSkillError = error instanceof Error ? error.message : String(error);
      if (previous != null) {
        pageAiNormalizeArray(pageUiState.pageAiSkills.categories).forEach(function(category) {
          pageAiNormalizeArray(category.skills).forEach(function(skill) {
            if (skill.name === name) skill.enabled = previous;
          });
        });
      }
    }
    renderPageAiControls();
    renderPageAiConversation();
  }

  async function pageAiToggleTool(toolName, enabled) {
    var name = String(toolName || '').trim();
    if (!name) return;
    var previous = null;
    pageAiNormalizeArray(pageUiState.pageAiTools).forEach(function(tool) {
      if (tool.name === name && previous == null) previous = tool.enabled !== false;
    });
    try {
      var response = await fetch('/api/hermes/client/tools/toggle', {
        method: 'PUT',
        headers: { 'content-type': 'application/json' },
        body: JSON.stringify({
          profile: pageAiCurrentProfile(),
          name: name,
          enabled: Boolean(enabled)
        })
      });
      var payload = await response.json().catch(function(){ return null; });
      if (!response.ok) throw new Error(pageAiErrorMessage(payload, 'tool_toggle_failed_' + response.status));
      pageAiNormalizeArray(pageUiState.pageAiTools).forEach(function(tool) {
        if (tool.name === name) {
          tool.enabled = Boolean(enabled);
          tool.status = Boolean(enabled) ? 'available' : 'disabled';
          tool.unavailableReason = Boolean(enabled) ? '' : '当前 Hermes profile 已关闭该 mnote tool';
        }
      });
      document.documentElement.setAttribute('data-mnote-page-ai-tool-toggled', name);
      pageUiState.pageAiToolsError = '';
    } catch (error) {
      pageUiState.pageAiToolsError = error instanceof Error ? error.message : String(error);
      if (previous != null) {
        pageAiNormalizeArray(pageUiState.pageAiTools).forEach(function(tool) {
          if (tool.name === name) tool.enabled = previous;
        });
      }
    }
    renderPageAiControls();
  }

  function renderPageAiProviderButtons() {
    var drawer = ensurePageAiDrawer();
    var isAcp = pageUiState.pageAiAcpRuntime !== '';
    drawer.querySelectorAll('[data-page-ai-provider]').forEach(function(button) {
      var provider = button.getAttribute('data-page-ai-provider') || 'hermes';
      var active = provider === pageUiState.pageAiProvider;
      button.classList.toggle('is-active', active);
      button.setAttribute('aria-pressed', active ? 'true' : 'false');
    });
    var providerNode = drawer.querySelector('.wolai-page-ai-subtitle span:first-child');
    if (providerNode instanceof HTMLElement) {
      providerNode.textContent = isAcp ? 'ACP · ' + (pageUiState.pageAiAcpRuntime === 'reasonix' ? 'Reasonix' : 'Hermes') : pageAiProviderLabel(pageUiState.pageAiProvider);
    }
  }

  function renderPageAiControls() {
    var drawer = ensurePageAiDrawer();
    var isAcp = pageUiState.pageAiAcpRuntime !== '';
    var activeProfile = pageAiRunProfile();
    drawer.setAttribute('data-page-ai-page', pageUiState.pageAiPage || 'chat');
    drawer.setAttribute('data-mnote-acp-runtime', pageUiState.pageAiAcpRuntime || 'reasonix');
    // Populate ACP runtime dropdown
    var acpSelect = drawer.querySelector('[data-page-ai-acp-runtime]');
    if (acpSelect instanceof HTMLSelectElement) {
      var runtimes = pageUiState.pageAiAcpRuntimes.length ? pageUiState.pageAiAcpRuntimes : pageAiNormalizeAcpRuntimes([]);
      acpSelect.innerHTML = runtimes.map(function(rt) {
          return '<option value="' + escapeHtml(rt.name || '') + '"' + ((rt.name || '') === pageUiState.pageAiAcpRuntime ? ' selected' : '') + '>' + escapeHtml(rt.title || rt.name) + '</option>';
        }).join('');
      acpSelect.value = pageUiState.pageAiAcpRuntime || 'reasonix';
    }
    // Show/hide Hermes-specific profile select
    var profileLabel = drawer.querySelector('[data-page-ai-hermes-profile]');
    if (profileLabel instanceof HTMLElement) {
      profileLabel.style.display = pageUiState.pageAiAcpRuntime === 'reasonix' ? 'none' : '';
    }
    // When ACP is selected, populate agent panel with ACP runtime info
    var agentPanel = drawer.querySelector('[data-page-ai-agent-panel]');
    if (agentPanel instanceof HTMLElement) {
      if (pageUiState.pageAiAcpRuntime === 'reasonix') {
        var rt = pageUiState.pageAiAcpRuntimes.find(function(r) { return r.name === pageUiState.pageAiAcpRuntime; }) || {};
        var keyStatus = rt.apiKeyConfigured ? '已配置' : '未检测到 DEEPSEEK_API_KEY';
        agentPanel.innerHTML = '' +
          '<section class="wolai-page-ai-memory-card">' +
            '<div class="wolai-page-ai-memory-head"><div class="wolai-page-ai-memory-title">' + escapeHtml(rt.title || 'ACP Runtime') + '</div></div>' +
          '</section>' +
          '<section class="wolai-page-ai-memory-card">' +
            '<div class="wolai-page-ai-memory-head"><div class="wolai-page-ai-memory-title">模型</div><div class="wolai-page-ai-memory-scope">' + escapeHtml(rt.model || 'deepseek-chat') + '</div></div>' +
          '</section>' +
          '<section class="wolai-page-ai-memory-card">' +
            '<div class="wolai-page-ai-memory-head"><div class="wolai-page-ai-memory-title">Preset</div><div class="wolai-page-ai-memory-scope">' + escapeHtml(rt.preset || 'auto') + '</div></div>' +
          '</section>' +
          '<section class="wolai-page-ai-memory-card">' +
            '<div class="wolai-page-ai-memory-head"><div class="wolai-page-ai-memory-title">API Key</div><div class="wolai-page-ai-memory-scope">' + escapeHtml(keyStatus) + '</div></div>' +
          '</section>' +
          '<section class="wolai-page-ai-memory-card">' +
            '<div class="wolai-page-ai-memory-head"><div class="wolai-page-ai-memory-title">描述</div><div class="wolai-page-ai-memory-scope">' + escapeHtml(rt.description || '') + '</div></div>' +
          '</section>';
      }
    }
    var profileSummary = drawer.querySelector('[data-page-ai-profile-summary]');
    if (profileSummary instanceof HTMLElement) profileSummary.textContent = activeProfile;
    var profileSummary = drawer.querySelector('[data-page-ai-profile-summary]');
    if (profileSummary instanceof HTMLElement) profileSummary.textContent = activeProfile;
    var profileSelect = drawer.querySelector('[data-page-ai-profile-select]');
    if (profileSelect instanceof HTMLSelectElement) {
      var profiles = pageUiState.pageAiProfiles.length ? pageUiState.pageAiProfiles : [{ name: activeProfile, active: true }];
      profileSelect.innerHTML = profiles.map(function(profile) {
        var name = pageAiProfileValue(profile) || 'default';
        var model = [profile.model, profile.gateway].filter(Boolean).join(' / ');
        var label = [name, profile.alias, model].filter(Boolean).join(' · ');
        return '<option value="' + escapeHtml(name) + '"' + (name === activeProfile ? ' selected' : '') + '>' + escapeHtml(label) + '</option>';
      }).join('');
      profileSelect.value = activeProfile;
    }
    var runStatus = drawer.querySelector('[data-page-ai-run-status]');
    if (runStatus instanceof HTMLElement) {
      var queueSuffix = pageUiState.pageAiQueueLength > 0 ? ' · 队列 ' + pageUiState.pageAiQueueLength : '';
      runStatus.textContent = pageAiRunStatusLabel(pageUiState.pageAiRunStatus) + queueSuffix;
    }
    var stopButton = drawer.querySelector('[data-page-ai-action="stop-run"]');
    if (stopButton instanceof HTMLButtonElement) {
      var canStop = ['queued', 'running', 'tool_calling'].indexOf(pageUiState.pageAiRunStatus) >= 0 && pageUiState.pageAiCurrentRunId;
      stopButton.disabled = !canStop;
      stopButton.setAttribute('aria-disabled', canStop ? 'false' : 'true');
    }
    var settingsLink = drawer.querySelector('[data-page-ai-action="open-hermes-settings"]');
    if (settingsLink instanceof HTMLButtonElement) {
      settingsLink.disabled = !pageAiHermesSettingsUrl();
      settingsLink.title = pageAiHermesSettingsUrl() ? '打开 Hermes 设置' : '未配置 MNOTE_WEB_HERMES_UPSTREAM_URL';
    }
    var queueList = drawer.querySelector('[data-page-ai-queue-list]');
    if (queueList instanceof HTMLElement) {
      if (!pageUiState.pageAiQueuedItems.length) {
        queueList.innerHTML = '<div class="wolai-page-ai-empty">暂无排队项。</div>';
      } else {
        queueList.innerHTML = pageUiState.pageAiQueuedItems.map(function(item, index) {
          var label = '队列 ' + (index + 1);
          return '' +
            '<div class="wolai-page-ai-tool-row" data-page-ai-queue-item="' + escapeHtml(item.queueId) + '">' +
              '<div><strong>' + escapeHtml(label) + '</strong><br /><span>' + escapeHtml(item.queueId) + '</span></div>' +
              '<button type="button" class="wolai-page-ai-ghost" data-page-ai-action="cancel-queued-run" data-page-ai-queue-id="' + escapeHtml(item.queueId) + '">取消</button>' +
            '</div>';
        }).join('');
      }
    }
    var sessionNode = drawer.querySelector('[data-page-ai-session-status]');
    if (sessionNode instanceof HTMLElement) {
      var session = pageAiCurrentSession();
      var usageText = session && session.usage ? pageAiUsageSummary(session.usage) : '';
      sessionNode.textContent = session && session.id
        ? [String(session.title || '当前页问答'), pageAiSessionStorageLabel(session), usageText].filter(Boolean).join(' · ')
        : '等待 Hermes session';
    }
    var modelNode = drawer.querySelector('[data-page-ai-model-status]');
    if (modelNode instanceof HTMLElement) modelNode.textContent = pageAiCurrentModelLabel();
    var scopeSelect = drawer.querySelector('[data-page-ai-context-scope]');
    if (scopeSelect instanceof HTMLSelectElement) scopeSelect.value = pageUiState.pageAiContextScope;
    var scopeLabel = drawer.querySelector('[data-page-ai-context-scope-label]');
    if (scopeLabel instanceof HTMLElement) scopeLabel.textContent = pageAiContextScopeLabel(pageUiState.pageAiContextScope);
    drawer.querySelectorAll('[data-page-ai-tab]').forEach(function(button) {
      var target = button.getAttribute('data-page-ai-tab') || 'chat';
      var active = target === pageUiState.pageAiPage;
      button.classList.toggle('is-active', active);
      button.setAttribute('aria-selected', active ? 'true' : 'false');
    });
    drawer.querySelectorAll('[data-page-ai-panel]').forEach(function(panel) {
      if (panel instanceof HTMLElement) {
        panel.hidden = panel.getAttribute('data-page-ai-panel') !== pageUiState.pageAiPage;
      }
    });
    var profileError = drawer.querySelector('[data-page-ai-profile-error]');
    if (profileError instanceof HTMLElement) {
      profileError.textContent = pageUiState.pageAiProfileError || '';
      profileError.hidden = !pageUiState.pageAiProfileError;
    }
    var memoryError = drawer.querySelector('[data-page-ai-memory-error]');
    if (memoryError instanceof HTMLElement) {
      var memoryErrorText = pageUiState.pageAiProfileMemoryError || '';
      if (memoryErrorText && memoryErrorText === pageUiState.pageAiProfileError) memoryErrorText = '';
      memoryError.textContent = memoryErrorText;
      memoryError.hidden = !memoryErrorText;
    }
    var agentPanel = drawer.querySelector('[data-page-ai-agent-panel]');
    if (agentPanel instanceof HTMLElement && pageUiState.pageAiAcpRuntime !== 'reasonix') {
      agentPanel.innerHTML = ['soul', 'user', 'memory'].map(function(section) {
        var label = pageAiMemoryFileLabel(section);
        var value = pageUiState.pageAiProfileMemoryDrafts[section] || '';
        return '' +
          '<section class="wolai-page-ai-memory-card">' +
            '<div class="wolai-page-ai-memory-head">' +
              '<div>' +
                '<div class="wolai-page-ai-memory-title">' + escapeHtml(label) + '</div>' +
                '<div class="wolai-page-ai-memory-scope">保存到 Hermes profile: ' + escapeHtml(activeProfile) + '</div>' +
              '</div>' +
              '<button type="button" class="wolai-page-ai-ghost" data-page-ai-memory-save="' + escapeHtml(section) + '">保存</button>' +
            '</div>' +
            '<textarea class="wolai-page-ai-memory-editor" data-page-ai-memory-editor="' + escapeHtml(section) + '" spellcheck="false">' + escapeHtml(value) + '</textarea>' +
          '</section>';
      }).join('');
    }
    var skillError = drawer.querySelector('[data-page-ai-skill-error]');
    if (skillError instanceof HTMLElement) {
      skillError.textContent = pageUiState.pageAiSkillError || '';
      skillError.hidden = !pageUiState.pageAiSkillError;
    }
    var sessionError = drawer.querySelector('[data-page-ai-session-error]');
    if (sessionError instanceof HTMLElement) {
      sessionError.textContent = pageUiState.pageAiSessionError || '';
      sessionError.hidden = !pageUiState.pageAiSessionError;
    }
    var sessionSearch = drawer.querySelector('[data-page-ai-session-search]');
    if (sessionSearch instanceof HTMLInputElement && document.activeElement !== sessionSearch) {
      sessionSearch.value = pageUiState.pageAiSessionSearchQuery || '';
    }
    var skillSearch = drawer.querySelector('[data-page-ai-skill-search]');
    if (skillSearch instanceof HTMLInputElement && document.activeElement !== skillSearch) {
      skillSearch.value = pageUiState.pageAiSkillQuery;
    }
    var skillList = drawer.querySelector('[data-page-ai-skill-list]');
    if (skillList instanceof HTMLElement) {
      var skills = pageAiFilteredSkillEntries();
      if (!skills.length) {
        var emptyText = String(pageUiState.pageAiAcpRuntime || 'reasonix').trim() === 'reasonix'
          ? '没有匹配的 Reasonix skill。'
          : '没有匹配的 Hermes skill。';
        skillList.innerHTML = '<div class="wolai-page-ai-empty">' + escapeHtml(emptyText) + '</div>';
      } else {
        skillList.innerHTML = skills.map(function(skill) {
          var sourceText = pageAiSkillOriginLabel(skill) + (skill.modified ? ' · modified' : '');
          var description = String(skill.description || '').trim();
          var hasDescription = description && description !== '---' && description !== '无描述';
          var canToggle = skill.toggleable !== false && String(pageUiState.pageAiAcpRuntime || 'reasonix').trim() !== 'reasonix';
          return '' +
            '<div class="wolai-page-ai-skill-row">' +
              '<div class="wolai-page-ai-skill-copy">' +
                '<div class="wolai-page-ai-skill-main">' +
                  '<div class="wolai-page-ai-skill-name">' + escapeHtml(skill.name) + '</div>' +
                  '<div class="wolai-page-ai-skill-source">' + escapeHtml(sourceText) + '</div>' +
                '</div>' +
                (hasDescription ? '<div class="wolai-page-ai-skill-desc">' + escapeHtml(description) + '</div>' : '') +
              '</div>' +
              '<button type="button" class="wolai-page-ai-skill-switch' + (skill.enabled !== false ? ' is-on' : '') + '" data-page-ai-skill-toggle="' + escapeHtml(skill.name) + '" aria-pressed="' + (skill.enabled !== false ? 'true' : 'false') + '"' + (canToggle ? '' : ' disabled title="Reasonix skills 当前为只读展示"') + '>' +
                '<span></span>' +
              '</button>' +
            '</div>';
        }).join('');
      }
    }
    var toolsList = drawer.querySelector('[data-page-ai-tool-list]');
    if (toolsList instanceof HTMLElement) {
      var tools = pageAiNormalizeArray(pageUiState.pageAiTools);
      if (!tools.length) {
        toolsList.innerHTML = '<div class="wolai-page-ai-empty">尚未读取到 mnote tool manifest。</div>';
      } else {
        toolsList.innerHTML = tools.map(function(tool) {
          return '' +
            '<div class="wolai-page-ai-tool-row">' +
              '<div class="wolai-page-ai-skill-copy">' +
                '<div class="wolai-page-ai-tool-name">' + escapeHtml(tool.name) + '</div>' +
                '<div class="wolai-page-ai-tool-meta">' + escapeHtml(tool.scope || tool.kind || 'mnote') + ' · ' + escapeHtml(tool.status || 'available') + '</div>' +
                (tool.unavailableReason ? '<div class="wolai-page-ai-tool-meta">' + escapeHtml(tool.unavailableReason) + '</div>' : '') +
              '</div>' +
              '<button type="button" class="wolai-page-ai-skill-switch' + (tool.enabled !== false ? ' is-on' : '') + '" data-page-ai-tool-toggle="' + escapeHtml(tool.name) + '" aria-pressed="' + (tool.enabled !== false ? 'true' : 'false') + '">' +
                '<span></span>' +
              '</button>' +
            '</div>';
        }).join('');
      }
    }
    var gatewayError = drawer.querySelector('[data-page-ai-gateway-error]');
    if (gatewayError instanceof HTMLElement) {
      gatewayError.textContent = pageUiState.pageAiGatewayHealthError || '';
      gatewayError.hidden = !pageUiState.pageAiGatewayHealthError;
    }
    var gatewayStatusNode = drawer.querySelector('[data-page-ai-gateway-status]');
    var gatewayDetail = drawer.querySelector('[data-page-ai-gateway-detail]');
    var gatewayHealth = pageUiState.pageAiGatewayHealth;
    if (gatewayStatusNode instanceof HTMLElement) {
      if (!gatewayHealth) {
        gatewayStatusNode.textContent = '未检查';
      } else {
        var gateway = gatewayHealth.gateway || {};
        var profile = gatewayHealth.profile || {};
        gatewayStatusNode.textContent = gatewayHealth.ok ? '可用' : '需要设置';
        if (gateway.status) gatewayStatusNode.textContent += ' · ' + gateway.status;
        if (profile.name) gatewayStatusNode.textContent += ' · ' + profile.name;
      }
    }
    if (gatewayDetail instanceof HTMLElement) {
      if (!gatewayHealth) {
        gatewayDetail.innerHTML = '<div class="wolai-page-ai-empty">打开 Runtime 后会检查 Hermes gateway 与当前 profile。</div>';
      } else {
        var gatewayInfo = gatewayHealth.gateway || {};
        var profileInfo = gatewayHealth.profile || {};
        var suggestionText = pageAiNormalizeArray(gatewayHealth.suggestions).join('；');
        gatewayDetail.innerHTML = '' +
          '<div class="wolai-page-ai-tool-row">' +
            '<div class="wolai-page-ai-tool-name">' + escapeHtml(profileInfo.name || activeProfile) + '</div>' +
            '<div class="wolai-page-ai-tool-meta">model.default: ' + escapeHtml(profileInfo.modelDefault || '未设置') + '</div>' +
            '<div class="wolai-page-ai-tool-meta">provider: ' + escapeHtml(profileInfo.provider || '未设置') + ' · API key: ' + escapeHtml(profileInfo.apiKeyConfigured ? '已配置' : '未检测到') + '</div>' +
          '</div>' +
          '<div class="wolai-page-ai-tool-row">' +
            '<div class="wolai-page-ai-tool-name">' + escapeHtml(gatewayInfo.upstream || '未配置 upstream') + '</div>' +
            '<div class="wolai-page-ai-tool-meta">gateway: ' + escapeHtml(gatewayInfo.status || 'unknown') + (gatewayInfo.httpStatus ? ' · HTTP ' + escapeHtml(gatewayInfo.httpStatus) : '') + '</div>' +
            (suggestionText ? '<div class="wolai-page-ai-tool-meta">' + escapeHtml(suggestionText) + '</div>' : '') +
          '</div>';
      }
    }
    var toolError = drawer.querySelector('[data-page-ai-tool-error]');
    if (toolError instanceof HTMLElement) {
      toolError.textContent = pageUiState.pageAiToolsError || '';
      toolError.hidden = !pageUiState.pageAiToolsError;
    }
    var lastTool = drawer.querySelector('[data-page-ai-last-tool]');
    if (lastTool instanceof HTMLElement) {
      var call = pageUiState.pageAiLastToolCall;
      lastTool.textContent = call
        ? [call.event, call.name, call.runId, call.traceId || call.auditId].filter(Boolean).join(' · ')
        : '暂无 tool call';
    }
  }

  function humanizePageAiResponse(rawText, promptText) {
    var providerLabel = pageAiProviderLabel(pageUiState.pageAiProvider);
    var text = String(rawText || '').trim();
    if (!text) {
      return providerLabel + ' 已收到你的问题“' + searchText(promptText) + '”，但这次没有返回可读内容。';
    }
    if (text.startsWith('{')) {
      try {
        var payload = JSON.parse(text);
        var operation = payload && payload.operation ? payload.operation : {};
        var normalized = operation && operation.normalized_input ? operation.normalized_input : {};
        var args = normalized && normalized.args ? normalized.args : {};
        var documentId = searchText(args.documentId || args.pageId || currentDocumentId());
        var toolName = searchText(operation.tool_name || normalized.toolName || 'doc_get');
        return providerLabel + ' 已收到你的问题“' + searchText(promptText) + '”。当前已通过 Hermes 调用 mnote plugin 工具，目标页面是 ' + (documentId || '当前页面') + '，本次选择的工具是 ' + toolName + '。如果你继续追问，我会沿当前页面上下文继续回复。';
      } catch (_) {
        return providerLabel + ' 已返回结果，但当前结果是结构化文本，已为你保留原始内容。';
      }
    }
    return text;
  }

  function ensurePageAiDrawer() {
    var existing = document.querySelector('[data-testid="wolai-page-ai-drawer"]');
    if (existing instanceof HTMLElement) return existing;
    var drawer = document.createElement('aside');
    drawer.className = 'wolai-page-ai-drawer';
    drawer.setAttribute('data-testid', 'wolai-page-ai-drawer');
    drawer.setAttribute('data-mnote-surface', 'page-ai');
    drawer.hidden = true;
    drawer.innerHTML = '' +
      '<div class="wolai-page-ai-panel" role="dialog" aria-modal="false">' +
        '<div class="wolai-page-ai-header">' +
          '<div class="wolai-page-ai-header-copy">' +
            '<h2 class="wolai-page-ai-title" data-title="page-ai-title">页面 AI</h2>' +
            '<div class="wolai-page-ai-subtitle">' +
              '<span>Hermes</span>' +
              '<span data-page-ai-profile-summary>default</span>' +
              '<span data-page-ai-model-status>由 Hermes 决定</span>' +
            '</div>' +
          '</div>' +
          '<div class="wolai-page-ai-header-actions">' +
            '<button type="button" class="wolai-page-ai-icon" data-page-ai-tab="agent" aria-label="打开页面 AI 设置">⚙</button>' +
            '<button type="button" class="wolai-page-ai-icon" data-page-ai-action="close" aria-label="关闭页面 AI">×</button>' +
          '</div>' +
        '</div>' +
        '<div class="wolai-page-ai-body">' +
          '<section class="wolai-page-ai-panel-section" data-page-ai-panel="chat">' +
            '<div class="wolai-page-ai-chat-meta">' +
              '<button type="button" class="wolai-page-ai-session-button" data-page-ai-action="history">' +
                '<span data-page-ai-session-status>等待 Hermes session</span>' +
              '</button>' +
              '<span data-page-ai-context-scope-label>当前页</span>' +
              '<span data-page-ai-run-status>空闲</span>' +
            '</div>' +
            '<div class="wolai-page-ai-conversation" data-page-ai-conversation></div>' +
            '<div class="wolai-page-ai-suggestions">' +
              '<div class="wolai-page-ai-suggestions-header">' +
                '<span>推荐问题</span>' +
                '<div class="wolai-page-ai-intents">' +
                  '<button type="button" class="wolai-page-ai-ghost" data-page-ai-intent="create-summary">创建 Summary</button>' +
                  '<button type="button" class="wolai-page-ai-ghost" data-page-ai-intent="create-ai-note">创建 AI Note</button>' +
                  '<button type="button" class="wolai-page-ai-ghost" data-page-ai-action="rotate">换一换</button>' +
                '</div>' +
              '</div>' +
              '<div class="wolai-page-ai-suggestion-list" data-page-ai-suggestion-list></div>' +
            '</div>' +
          '</section>' +
          '<section class="wolai-page-ai-panel-section" data-page-ai-panel="agent" hidden>' +
            '<div class="wolai-page-ai-settings-head">' +
              '<button type="button" class="wolai-page-ai-back" data-page-ai-tab="chat">← 聊天</button>' +
              '<div class="wolai-page-ai-settings-tabs" role="tablist" aria-label="页面 AI 设置">' +
                '<button type="button" class="wolai-page-ai-tab is-active" data-page-ai-tab="agent" role="tab" aria-selected="true">Agent</button>' +
                '<button type="button" class="wolai-page-ai-tab" data-page-ai-tab="skills" role="tab" aria-selected="false">Skills</button>' +
                '<button type="button" class="wolai-page-ai-tab" data-page-ai-tab="runtime" role="tab" aria-selected="false">Runtime</button>' +
              '</div>' +
            '</div>' +
            '<div class="wolai-page-ai-settings-grid">' +
              '<label class="wolai-page-ai-profile-select">' +
                '<span>ACP</span>' +
                  '<select data-page-ai-acp-runtime></select>' +
              '</label>' +
              '<label class="wolai-page-ai-profile-select" data-page-ai-hermes-profile>' +
                '<span>agent / profile</span>' +
                '<select data-page-ai-profile-select></select>' +
              '</label>' +
              '<label class="wolai-page-ai-context-select">' +
                '<span>上下文</span>' +
                '<select data-page-ai-context-scope>' +
                  '<option value="page">当前页</option>' +
                  '<option value="selection">当前选区</option>' +
                  '<option value="block">当前块</option>' +
                  '<option value="options">页面设置</option>' +
                '</select>' +
              '</label>' +
            '</div>' +
            '<div class="wolai-page-ai-inline-error" data-page-ai-profile-error hidden></div>' +
            '<div class="wolai-page-ai-inline-error" data-page-ai-memory-error hidden></div>' +
            '<div class="wolai-page-ai-memory-grid" data-page-ai-agent-panel></div>' +
          '</section>' +
          '<section class="wolai-page-ai-panel-section" data-page-ai-panel="skills" hidden>' +
            '<div class="wolai-page-ai-settings-head">' +
              '<button type="button" class="wolai-page-ai-back" data-page-ai-tab="chat">← 聊天</button>' +
              '<div class="wolai-page-ai-settings-tabs" role="tablist" aria-label="页面 AI 设置">' +
                '<button type="button" class="wolai-page-ai-tab" data-page-ai-tab="agent" role="tab" aria-selected="false">Agent</button>' +
                '<button type="button" class="wolai-page-ai-tab is-active" data-page-ai-tab="skills" role="tab" aria-selected="true">Skills</button>' +
                '<button type="button" class="wolai-page-ai-tab" data-page-ai-tab="runtime" role="tab" aria-selected="false">Runtime</button>' +
              '</div>' +
            '</div>' +
            '<div class="wolai-page-ai-skills-toolbar">' +
              '<label class="wolai-page-ai-skill-search">' +
                '<span>搜索技能</span>' +
                '<input type="search" data-page-ai-skill-search placeholder="搜索技能…" />' +
              '</label>' +
            '</div>' +
            '<div class="wolai-page-ai-inline-error" data-page-ai-skill-error hidden></div>' +
            '<div class="wolai-page-ai-skill-list" data-page-ai-skill-list></div>' +
          '</section>' +
          '<section class="wolai-page-ai-panel-section" data-page-ai-panel="runtime" hidden>' +
            '<div class="wolai-page-ai-settings-head">' +
              '<button type="button" class="wolai-page-ai-back" data-page-ai-tab="chat">← 聊天</button>' +
              '<div class="wolai-page-ai-settings-tabs" role="tablist" aria-label="页面 AI 设置">' +
                '<button type="button" class="wolai-page-ai-tab" data-page-ai-tab="agent" role="tab" aria-selected="false">Agent</button>' +
                '<button type="button" class="wolai-page-ai-tab" data-page-ai-tab="skills" role="tab" aria-selected="false">Skills</button>' +
                '<button type="button" class="wolai-page-ai-tab is-active" data-page-ai-tab="runtime" role="tab" aria-selected="true">Runtime</button>' +
              '</div>' +
            '</div>' +
            '<button type="button" class="wolai-page-ai-settings-link" data-page-ai-action="open-hermes-settings">打开 Hermes 设置</button>' +
            '<div class="wolai-page-ai-inline-error" data-page-ai-gateway-error hidden></div>' +
            '<div class="wolai-page-ai-tools-panel">' +
              '<div class="wolai-page-ai-tools-head">' +
                '<span>gateway</span>' +
                '<span data-page-ai-gateway-status>未检查</span>' +
              '</div>' +
              '<div class="wolai-page-ai-tool-list" data-page-ai-gateway-detail></div>' +
            '</div>' +
            '<div class="wolai-page-ai-tools-panel">' +
              '<div class="wolai-page-ai-tools-head">' +
                '<span>queue</span>' +
                '<span data-page-ai-queue-status>由 mnote-web 管理</span>' +
              '</div>' +
              '<div class="wolai-page-ai-tool-list" data-page-ai-queue-list></div>' +
            '</div>' +
            '<div class="wolai-page-ai-tools-panel">' +
              '<div class="wolai-page-ai-tools-head">' +
                '<span>mnote tools</span>' +
                '<span data-page-ai-last-tool>暂无 tool call</span>' +
              '</div>' +
              '<div class="wolai-page-ai-inline-error" data-page-ai-tool-error hidden></div>' +
              '<div class="wolai-page-ai-tool-list" data-page-ai-tool-list></div>' +
            '</div>' +
          '</section>' +
          '<section class="wolai-page-ai-panel-section" data-page-ai-panel="history" hidden>' +
            '<div class="wolai-page-ai-settings-head">' +
              '<button type="button" class="wolai-page-ai-back" data-page-ai-tab="chat">← 聊天</button>' +
            '</div>' +
            '<label class="wolai-page-ai-skill-search">' +
              '<span>搜索会话</span>' +
              '<input type="search" data-page-ai-session-search placeholder="搜索后端 AI 会话…" />' +
            '</label>' +
            '<div class="wolai-page-ai-inline-error" data-page-ai-session-error hidden></div>' +
            '<div class="wolai-page-ai-conversation" data-page-ai-conversation></div>' +
          '</section>' +
        '</div>' +
        '<div class="wolai-page-ai-footer">' +
          '<div class="wolai-page-ai-composer">' +
            '<textarea class="wolai-page-ai-input" data-page-ai-input rows="3" placeholder="问 Hermes…"></textarea>' +
            '<div class="wolai-page-ai-composer-bar">' +
              '<button type="button" class="wolai-page-ai-tool-button" data-page-ai-action="new-session" title="新会话">＋</button>' +
              '<button type="button" class="wolai-page-ai-tool-button" data-page-ai-action="history" title="历史会话">⌕</button>' +
              '<span class="wolai-page-ai-composer-spacer"></span>' +
              '<button type="button" class="wolai-page-ai-stop" data-page-ai-action="stop-run" disabled>停止</button>' +
              '<button type="button" class="wolai-page-ai-send" data-page-ai-action="send" aria-label="发送">发送</button>' +
            '</div>' +
          '</div>' +
        '</div>' +
      '</div>';
    document.body.appendChild(drawer);
    return drawer;
  }

  function renderPageAiSuggestions() {
    var drawer = ensurePageAiDrawer();
    var list = drawer.querySelector('[data-page-ai-panel="chat"] [data-page-ai-suggestion-list]');
    if (!(list instanceof HTMLElement)) return;
    var container = list.closest('.wolai-page-ai-suggestions');
    if (container instanceof HTMLElement) {
      container.hidden = pageUiState.pageAiPage !== 'chat' || pageUiState.pageAiMessages.length > 0;
    }
    var suggestions = pageAiSuggestions();
    var offset = pageUiState.pageAiSuggestionIndex % suggestions.length;
    var ordered = suggestions.slice(offset).concat(suggestions.slice(0, offset)).slice(0, 3);
    list.innerHTML = ordered.map(function(text) {
      return '<button type="button" class="wolai-page-ai-suggestion" data-page-ai-suggestion="' + escapeHtml(text) + '">' + escapeHtml(text) + '</button>';
    }).join('');
  }

  function renderPageAiConversation() {
    var drawer = ensurePageAiDrawer();
    renderPageAiSuggestions();
    var conversation = drawer.querySelector('[data-page-ai-panel="' + cssEscape(pageUiState.pageAiPage || 'chat') + '"] [data-page-ai-conversation]');
    if (!(conversation instanceof HTMLElement)) return;
    if (pageUiState.pageAiPage === 'history') {
      var historyRows = pageUiState.pageAiSessionSearchResults.length
        ? pageUiState.pageAiSessionSearchResults
        : pageUiState.pageAiSessions;
      if (!historyRows.length) {
        conversation.innerHTML = '<div class="wolai-page-ai-empty">尚未产生历史会话。</div>';
        return;
      }
      conversation.innerHTML = historyRows.map(function(session) {
        var preview = Array.isArray(session.messages) && session.messages.length
          ? session.messages.slice(-1)[0].content
          : (session.snippet || session.preview || '暂无消息');
        var active = session.id === pageUiState.pageAiActiveSessionId;
        var usage = pageAiUsageSummary(session.usage);
        var meta = [pageAiSessionStorageLabel(session), session.permissionLevel, session.shareId, session.status, usage].filter(Boolean).join(' · ');
        return '' +
          '<div class="wolai-page-ai-message wolai-page-ai-message--history' + (active ? ' is-active' : '') + '" data-page-ai-session-row="' + escapeHtml(session.id) + '">' +
            '<button type="button" class="wolai-page-ai-message-text" data-page-ai-session="' + escapeHtml(session.id) + '">' +
              '<strong>' + escapeHtml(session.title || '新会话') + '</strong><br />' +
              '<span>' + escapeHtml(preview) + '</span>' +
              (meta ? '<br /><span class="wolai-page-ai-tool-meta">' + escapeHtml(meta) + '</span>' : '') +
            '</button>' +
            '<div class="wolai-page-ai-message-actions">' +
              '<button type="button" class="wolai-page-ai-ghost" data-page-ai-session-resume="' + escapeHtml(session.id) + '">恢复</button>' +
              '<button type="button" class="wolai-page-ai-ghost" data-page-ai-session-rename="' + escapeHtml(session.id) + '">重命名</button>' +
              '<button type="button" class="wolai-page-ai-ghost" data-page-ai-session-delete="' + escapeHtml(session.id) + '">删除</button>' +
            '</div>' +
          '</div>';
      }).join('');
      return;
    }
    if (!pageUiState.pageAiMessages.length) {
      conversation.innerHTML = '<div class="wolai-page-ai-empty">围绕当前页面提问，我会优先使用当前页内容、页面结构和已保存设置。</div>';
      return;
    }
    conversation.innerHTML = pageUiState.pageAiMessages.map(function(item) {
      var roleLabel = item.role === 'user' ? '你' : (item.role === 'tool' ? '工具' : 'AI');
      if (item.role === 'tool') {
        var statusLabel = item.status === 'completed' ? '完成' : (item.status === 'failed' ? '失败' : '运行中');
        var locationRows = Array.isArray(item.locations) && item.locations.length
          ? '<div class="wolai-page-ai-tool-meta">位置：' + item.locations.map(function(loc, idx) {
              return '<span style="display:inline-flex;align-items:center;gap:4px;margin-right:8px">' +
                '<span>' + escapeHtml(loc) + '</span>' +
                '<button type="button" class="wolai-page-ai-ghost" style="font-size:0.85em;padding:0 4px;min-width:unset" data-page-ai-open-location="' + escapeHtml(loc) + '" data-page-ai-tool-call-id="' + escapeHtml(item.toolCallId || '') + '" title="打开文件">打开</button>' +
                '</span>';
            }).join('') + '</div>'
          : '';
        var detailRows = [
          locationRows,
          item.argsSummary ? '<div class="wolai-page-ai-tool-meta">参数 ' + escapeHtml(item.argsSummary) + '</div>' : '',
          item.resultSummary ? '<div class="wolai-page-ai-tool-meta">结果 ' + escapeHtml(item.resultSummary) + '</div>' : '',
          item.changedFiles && item.changedFiles.length ? '<pre class="wolai-page-ai-tool-meta wolai-page-ai-changed-files">' + escapeHtml(pageAiFormatChangedFiles(item.changedFiles)) + '</pre>' : '',
          (item.traceId || item.auditId) ? '<div class="wolai-page-ai-tool-meta">' + escapeHtml([item.traceId, item.auditId].filter(Boolean).join(' · ')) + '</div>' : ''
        ].filter(Boolean).join('');
        return '' +
          '<div class="wolai-page-ai-message wolai-page-ai-message--tool" data-page-ai-tool-card data-page-ai-tool-call-id="' + escapeHtml(item.toolCallId || '') + '" data-page-ai-tool-status="' + escapeHtml(item.status || '') + '">' +
            '<div class="wolai-page-ai-message-role">' + escapeHtml(roleLabel) + '</div>' +
            '<div class="wolai-page-ai-message-text">' +
              '<details class="wolai-page-ai-tool-details">' +
                '<summary>' +
                  '<strong>' + escapeHtml(item.toolName || item.content || 'tool') + '</strong>' +
                  '<span>' + escapeHtml([statusLabel, item.toolKind, item.toolCallId].filter(Boolean).join(' · ')) + '</span>' +
                '</summary>' +
                (detailRows || '<div class="wolai-page-ai-tool-meta">暂无参数或结果详情。</div>') +
              '</details>' +
            '</div>' +
          '</div>';
      }
      if (item.kind === 'thought') {
        return '' +
          '<details class="wolai-page-ai-message wolai-page-ai-message--assistant" data-page-ai-thought-delta="true">' +
            '<summary class="wolai-page-ai-message-role" style="cursor:pointer;user-select:none;opacity:0.7;font-size:0.85em">思考过程</summary>' +
            '<div class="wolai-page-ai-message-text" style="margin-top:4px;opacity:0.6;font-size:0.9em;border-left:2px solid var(--wolai-border-color, #ddd);padding-left:8px">' + escapeHtml(item.content || '') + '</div>' +
          '</details>';
      }
      if (item.kind === 'permission') {
        var permissionActions = item.resolved ? '' : (
              '<div class="wolai-page-ai-message-actions">' +
                '<button type="button" class="wolai-page-ai-ghost" data-page-ai-permission-action="allow" data-page-ai-permission-id="' + escapeHtml(item.permissionId || '') + '">允许</button>' +
                '<button type="button" class="wolai-page-ai-ghost" data-page-ai-permission-action="deny" data-page-ai-permission-id="' + escapeHtml(item.permissionId || '') + '">拒绝</button>' +
              '</div>'
        );
        return '' +
          '<div class="wolai-page-ai-message wolai-page-ai-message--tool" data-page-ai-permission-request="' + escapeHtml(item.permissionId || '') + '">' +
            '<div class="wolai-page-ai-message-role">权限</div>' +
            '<div class="wolai-page-ai-message-text">' +
              '<strong>' + escapeHtml(item.toolName || 'session/request_permission') + '</strong><br />' +
              '<span>' + escapeHtml(item.argsSummary || item.content || '') + '</span>' +
              permissionActions +
            '</div>' +
          '</div>';
      }
      if (item.kind === 'plan') {
        var planEntries = Array.isArray(item.entries) ? item.entries : [];
        var listHtml = planEntries.map(function(entry, idx) {
          return '<li style="margin:2px 0">' + escapeHtml(String(entry || '')) + '</li>';
        }).join('');
        return '' +
          '<div class="wolai-page-ai-message wolai-page-ai-message--assistant" data-page-ai-plan="true">' +
            '<details class="wolai-page-ai-plan-details" open>' +
              '<summary class="wolai-page-ai-message-role" style="cursor:pointer;user-select:none;opacity:0.8;font-size:0.85em">执行计划 · ' + planEntries.length + ' 步</summary>' +
              '<div class="wolai-page-ai-message-text" style="margin-top:4px;opacity:0.75;font-size:0.9em;border-left:2px solid var(--wolai-border-color, #ddd);padding-left:8px">' +
                '<ol style="margin:4px 0;padding-left:20px">' + listHtml + '</ol>' +
              '</div>' +
            '</details>' +
          '</div>';
      }
      var streamingAttr = item.streaming ? ' data-page-ai-streaming="true"' : '';
      return '' +
        '<div class="wolai-page-ai-message wolai-page-ai-message--' + escapeHtml(item.role || 'assistant') + '"' + streamingAttr + '>' +
          '<div class="wolai-page-ai-message-role">' + escapeHtml(roleLabel) + '</div>' +
          '<div class="wolai-page-ai-message-text">' + escapeHtml(item.content || '') + '</div>' +
        '</div>';
    }).join('');
    conversation.scrollTop = conversation.scrollHeight;
  }

  function openPageAiDrawer() {
    pageAiLoadSessions();
    renderPageAiSuggestions();
    renderPageAiConversation();
    renderPageAiProviderButtons();
    renderPageAiControls();
    var drawer = ensurePageAiDrawer();
    drawer.hidden = false;
    pageUiState.pageAiOpen = true;
    updatePageAiTriggerState();
    Promise.all([
      pageAiLoadProfiles(),
      pageAiLoadProfileMemory(),
      pageAiLoadSkills(),
      pageAiLoadTools(),
      pageAiLoadGatewayHealth(),
      pageAiLoadBackendSessions()
    ]).then(function() {
      renderPageAiControls();
    }).catch(function() {}).then(function() {
      return pageAiEnsureHermesSession();
    }).then(function() {
      return pageAiRestoreHermesSession();
    }).then(function() {
      renderPageAiControls();
    }).catch(function(error) {
      pageUiState.pageAiMessages.push({
        role: 'assistant',
        content: 'Hermes 当前不可用：' + (error instanceof Error ? error.message : String(error))
      });
      renderPageAiConversation();
      renderPageAiControls();
    });
  }

  function closePageAiDrawer() {
    var drawer = document.querySelector('[data-testid="wolai-page-ai-drawer"]');
    if (drawer instanceof HTMLElement) drawer.hidden = true;
    pageUiState.pageAiOpen = false;
    updatePageAiTriggerState();
  }

  function isPageAiDrawerOpen() {
    var drawer = document.querySelector('[data-testid="wolai-page-ai-drawer"]');
    return drawer instanceof HTMLElement && !drawer.hidden;
  }

  async function streamPageAiResponse(response, onEvent) {
    if (!response.body || typeof response.body.getReader !== 'function') return;
    var reader = response.body.getReader();
    var decoder = new TextDecoder();
    var buffer = '';
    while (true) {
      var chunk = await reader.read();
      if (chunk.done) break;
      buffer += decoder.decode(chunk.value, { stream: true });
      var frames = buffer.split('\n\n');
      buffer = frames.pop() || '';
      frames.forEach(function(frame) {
        var eventName = '';
        var dataLines = [];
        frame.split('\n').forEach(function(line) {
          if (line.startsWith('event:')) eventName = line.slice(6).trim();
          if (line.startsWith('data:')) dataLines.push(line.slice(5).trim());
        });
        var payloadText = dataLines.join('\n');
        if (!eventName && payloadText) {
          try {
            var parsed = JSON.parse(payloadText);
            eventName = parsed && parsed.event ? String(parsed.event) : '';
          } catch (_) {}
        }
        if (eventName) onEvent(eventName, payloadText);
      });
    }
  }

  function pageAiDecodeDeltaText(payloadText) {
    try {
      var payload = JSON.parse(payloadText || 'null');
      return String((payload && (payload.text || payload.delta)) || '');
    } catch (_) {
      return String(payloadText || '');
    }
  }

  function pageAiEnsureStreamingAssistantMessage(runId) {
    var id = String(runId || pageUiState.pageAiCurrentRunId || '');
    var existing = pageUiState.pageAiMessages.find(function(item) {
      return item.role === 'assistant' && item.streaming === true && item.runId === id;
    });
    if (existing) return existing;
    existing = {
      role: 'assistant',
      content: '',
      runId: id,
      streaming: true
    };
    pageUiState.pageAiMessages.push(existing);
    return existing;
  }

  function pageAiAppendStreamingAssistantDelta(runId, deltaText) {
    var delta = String(deltaText || '');
    if (!delta) return '';
    var message = pageAiEnsureStreamingAssistantMessage(runId);
    message.content = String(message.content || '') + delta;
    pageAiSyncCurrentSessionMessages();
    renderPageAiConversation();
    return message.content;
  }

  function pageAiFinishStreamingAssistantMessage(runId, finalText, promptText) {
    var id = String(runId || pageUiState.pageAiCurrentRunId || '');
    var message = pageUiState.pageAiMessages.find(function(item) {
      return item.role === 'assistant' && item.streaming === true && item.runId === id;
    });
    var text = String(finalText || (message && message.content) || '');
    var content = humanizePageAiResponse(text, promptText);
    if (message) {
      message.content = content;
      message.streaming = false;
    } else if (content) {
      pageUiState.pageAiMessages.push({
        role: 'assistant',
        content: content,
        runId: id
      });
    }
    pageAiSyncCurrentSessionMessages();
    pageAiPersistSessions();
  }

  function pageAiLooksLikeBlockEdit(prompt) {
    var text = searchText(prompt);
    return ['新增', '添加', '插入', '删除', '删掉', '修改', '替换', '改成', '移动', '移到', 'move', 'replace', 'delete', 'insert'].some(function(word) {
      return text.indexOf(word) >= 0;
    });
  }

  async function pageAiTryBlockEditWorkflow(prompt, scopedContext) {
    if (currentSourceKind() === 'local_folder') return false;
    if (!pageAiLooksLikeBlockEdit(prompt)) return false;
    var runId = 'page-ai-fast-' + Date.now().toString(36);
    var traceId = 'page-ai-fast-' + Date.now().toString(36);
    pageAiSetRunStatus('running', runId);
    renderPageAiControls();
    var started = Date.now();
    var response = await fetch('/api/page-ai/block-edit-workflow', {
      method: 'POST',
      headers: { 'content-type': 'application/json' },
      body: JSON.stringify({
          workspaceId: resolveWorkspaceId(document.body),
          documentId: currentDocumentId(),
          sourceKind: currentSourceKind(),
          rootUri: currentRootUri(),
          sessionId: pageUiState.pageAiActiveSessionId,
          runId: runId,
          profile: pageAiCurrentProfile(),
        model: pageAiMnoteToolModel(),
        message: prompt,
        pageContext: scopedContext.pageContext,
        selectedBlockId: scopedContext.selectedBlockId,
        selectedText: scopedContext.selectedText,
        traceId: traceId
      })
    });
    var payload = await response.json().catch(function() { return null; });
    if (!response.ok || !payload || payload.ok !== true) {
      var code = payload && payload.code ? String(payload.code) : '';
      if (code === 'page_ai_workflow_not_block_edit') return false;
      pageUiState.pageAiMessages.push({
        role: 'tool',
        toolName: 'mnote.page_ai.block_edit_workflow',
        status: 'failed',
        toolCallId: runId,
        resultSummary: pageAiErrorMessage(payload, 'fast_path_failed_' + response.status)
      });
      renderPageAiConversation();
      pageAiSetRunStatus('failed', runId);
      renderPageAiControls();
      return true;
    }
    pageUiState.pageAiMessages.push({
      role: 'tool',
      toolName: 'mnote.page_ai.block_edit_workflow',
      status: 'completed',
      toolCallId: runId,
      resultSummary: 'operations=' + String(Array.isArray(payload.operations) ? payload.operations.length : 0) + ' · ' + String(Date.now() - started) + 'ms'
    });
    pageUiState.pageAiMessages.push({
      role: 'assistant',
      content: payload.message || '已通过页面块编辑快路径完成写入。'
    });
    pageAiSetRunStatus('completed', runId);
    try {
      window.dispatchEvent(new CustomEvent('mnote:page-ai-tool-write-completed', {
        detail: {
          toolName: 'mnote.doc.apply_block_ops',
          normalizedToolName: 'mnote.doc.apply.block.ops',
          documentId: currentDocumentId(),
          workspaceId: resolveWorkspaceId(document.body),
          runId: runId,
          traceId: traceId,
          toolCallId: runId
        }
      }));
    } catch (_) {}
    var currentSession = pageAiCurrentSession();
    if (currentSession) {
      currentSession.messages = pageUiState.pageAiMessages.slice();
      currentSession.updatedAt = Date.now();
    }
    pageAiPersistSessions();
    renderPageAiConversation();
    renderPageAiControls();
    return true;
  }

  async function sendPageAiMessage(text) {
    var allowQueue = ['running', 'tool_calling'].indexOf(pageUiState.pageAiRunStatus) >= 0;
    if (pageUiState.pageAiBusy && !allowQueue) return;
    var prompt = searchText(text);
    if (!prompt) return;
    if (!allowQueue) pageUiState.pageAiBusy = true;
    var currentSession = null;
    try {
      await pageAiEnsureHermesSession();
      if (!allowQueue) pageAiSetRunStatus('queued');
      renderPageAiControls();
      var contextSnapshot = currentPageAiContextSnapshot();
      var scopedContext = pageAiScopedPageContext(contextSnapshot);
      pageUiState.pageAiMessages.push({ role: 'user', content: prompt });
      currentSession = pageAiCurrentSession();
      if (currentSession) {
        if (currentSession.title === '新会话') {
          currentSession.title = prompt.length > 18 ? prompt.slice(0, 18) + '…' : prompt;
        }
        currentSession.messages = pageUiState.pageAiMessages.slice();
        currentSession.updatedAt = Date.now();
      }
      pageAiPersistSessions();
      renderPageAiConversation();
      if (!allowQueue && await pageAiTryBlockEditWorkflow(prompt, scopedContext)) {
        return;
      }
      var response = await fetch('/api/hermes/client/runs', {
        method: 'POST',
        headers: { 'content-type': 'application/json' },
        body: JSON.stringify({
          workspaceId: resolveWorkspaceId(document.body),
          documentId: currentDocumentId(),
          sourceKind: currentSourceKind(),
          rootUri: currentRootUri(),
          sessionId: pageUiState.pageAiActiveSessionId,
          profile: pageAiRunProfile(),
          acpRuntime: pageUiState.pageAiAcpRuntime || 'reasonix',
          contextScope: pageUiState.pageAiContextScope,
          message: prompt,
          model: pageAiMnoteToolModel(),
          pageContext: scopedContext.pageContext,
          selectedBlockId: scopedContext.selectedBlockId,
          selectedText: scopedContext.selectedText,
          traceId: 'page-ai-run-' + Date.now().toString(36)
        })
      });
      if (!response.ok) {
        var errorPayload = await response.json().catch(function(){ return null; });
        throw new Error(pageAiErrorMessage(errorPayload, 'page_ai_failed_' + response.status));
      }
      var runPayload = await response.json().catch(function(){ return null; });
      if (pageAiApplyQueuedRun(runPayload)) {
        pageUiState.pageAiMessages.push({
          role: 'assistant',
          content: '已加入 Hermes 队列，前一条 run 完成后继续处理。'
        });
        if (currentSession) {
          currentSession.messages = pageUiState.pageAiMessages.slice();
          currentSession.updatedAt = Date.now();
        }
        pageAiPersistSessions();
        renderPageAiConversation();
        renderPageAiControls();
        return;
      }
      if (allowQueue) {
        throw new Error('hermes_queue_expected_queued_response');
      }
      var upstream = runPayload && runPayload.upstream ? runPayload.upstream : runPayload;
      var runId = upstream && (upstream.run_id || upstream.runId);
      if (!runId) throw new Error('hermes_run_missing_run_id');
      var runTraceId = String((upstream && (upstream.trace_id || upstream.traceId)) || (runPayload && (runPayload.trace_id || runPayload.traceId)) || '');
      pageAiSetRunStatus('running', runId);
      renderPageAiControls();
      var eventResponse = await fetch('/api/hermes/client/events/' + encodeURIComponent(runId), {
        headers: { 'accept': 'text/event-stream' }
      });
      if (!eventResponse.ok) {
        var eventError = await eventResponse.json().catch(function(){ return null; });
        throw new Error(pageAiErrorMessage(eventError, 'hermes_events_failed_' + eventResponse.status));
      }
      var assistantText = '';
      await streamPageAiResponse(eventResponse, function(eventName, payloadText) {
        if (eventName === 'message.delta') {
          if (pageUiState.pageAiStoppedRunIds[runId]) return;
          assistantText = pageAiAppendStreamingAssistantDelta(runId, pageAiDecodeDeltaText(payloadText));
        }
        if (eventName === 'thought.delta') {
          try {
            var thoughtPayload = JSON.parse(payloadText || 'null');
            var thoughtText = String((thoughtPayload && (thoughtPayload.delta || thoughtPayload.text)) || '');
            if (thoughtText) {
              var msgs = pageUiState.pageAiMessages;
              var lastThought = msgs.length > 0 && msgs[msgs.length - 1].kind === 'thought' ? msgs[msgs.length - 1] : null;
              if (lastThought) {
                lastThought.content += thoughtText;
              } else {
                msgs.push({ role: 'assistant', kind: 'thought', content: thoughtText });
              }
              pageAiSyncCurrentSessionMessages();
              pageAiPersistSessions();
              renderPageAiConversation();
            }
          } catch (_) {}
        }
        if (eventName === 'usage.updated') {
          try {
            var usagePayload = JSON.parse(payloadText || 'null') || {};
            var sessionForUsage = pageAiCurrentSession();
            if (sessionForUsage) {
              sessionForUsage.usage = {
                source: 'usage_update',
                used: Number(usagePayload.used ?? usagePayload.contextUsed ?? 0),
                size: Number(usagePayload.size ?? usagePayload.contextSize ?? 0)
              };
              pageAiPersistSessions();
            }
          } catch (_) {}
          renderPageAiControls();
        }
        if (eventName === 'permission.requested' || eventName === 'permission.denied' || eventName === 'permission.allowed') {
          pageAiApplyPermissionEvent(eventName, payloadText);
          pageAiSyncCurrentSessionMessages();
          pageAiPersistSessions();
          renderPageAiConversation();
          renderPageAiControls();
        }
        if (eventName === 'run.completed') {
          try {
            var completed = JSON.parse(payloadText || 'null');
            if (completed && completed.output) assistantText = String(completed.output || '');
            if (completed && completed.usage) {
              var completedSession = pageAiCurrentSession();
              if (completedSession) completedSession.usage = completed.usage;
            }
            var agentAudit = completed && completed.agentAudit && typeof completed.agentAudit === 'object' ? completed.agentAudit : null;
            var changedFiles = pageAiNormalizeArray(agentAudit && agentAudit.changedFiles);
            if (changedFiles.length) {
              pageUiState.pageAiMessages.push({
                role: 'tool',
                content: 'agent.changed_files',
                toolCallId: runId + ':agent.changed_files',
                toolName: 'agent.changed_files',
                toolKind: 'audit',
                status: 'completed',
                argsSummary: String(agentAudit.rootUri || ''),
                resultSummary: String(agentAudit.diffSummary || changedFiles.length + ' changed file(s)'),
                changedFiles: changedFiles,
                traceId: runTraceId,
                auditId: String(agentAudit.eventId || '')
              });
              try {
                var currentId = currentDocumentId();
                var currentPath = String(currentId || '').replace(/^local-md:/, '').replace(/~2F/g, '/');
                var touchesCurrent = changedFiles.some(function(file) {
                  var path = String(file && (file.documentId || file.path || file.filePath || '') || '');
                  return path === currentId || (currentPath && path.indexOf(currentPath) >= 0);
                });
                if (touchesCurrent) {
                  window.dispatchEvent(new CustomEvent('mnote:page-ai-tool-write-completed', {
                    detail: {
                      toolName: 'agent.changed_files',
                      normalizedToolName: 'agent.changed_files',
                      documentId: currentId,
                      workspaceId: resolveWorkspaceId(document.body),
                      runId: runId,
                      traceId: runTraceId,
                      toolCallId: runId + ':agent.changed_files'
                    }
                  }));
                }
              } catch (_) {}
            }
          } catch (_) {}
          pageAiSetRunStatus('completed', runId);
        }
        if (eventName === 'run.failed') {
          try {
            var failed = JSON.parse(payloadText || 'null');
            assistantText = String((failed && (failed.message || failed.code || failed.error)) || 'Hermes run failed');
          } catch (_) {}
          pageAiSetRunStatus('failed', runId);
        }
        if (eventName === 'run.aborted' || eventName === 'run.cancelled' || eventName === 'run.canceled') {
          pageUiState.pageAiStoppedRunIds[runId] = true;
          pageAiSetRunStatus('aborted', runId);
        }
        if (eventName === 'session.info.updated') {
          try {
            var infoPayload = JSON.parse(payloadText || 'null') || {};
            var newTitle = String(infoPayload.title || '').trim();
            if (newTitle) {
              var sessionForTitle = pageAiCurrentSession();
              if (sessionForTitle) {
                sessionForTitle.title = newTitle;
                sessionForTitle.updatedAt = Date.now();
                pageAiPersistSessions();
                renderPageAiControls();
              }
            }
          } catch (_) {}
        }
        if (eventName === 'plan.updated') {
          try {
            var planPayload = JSON.parse(payloadText || 'null') || {};
            var planEntries = Array.isArray(planPayload.entries) ? planPayload.entries : [];
            if (planEntries.length) {
              var planMsgs = pageUiState.pageAiMessages;
              var existingPlan = planMsgs.length > 0 && planMsgs[planMsgs.length - 1].kind === 'plan' ? planMsgs[planMsgs.length - 1] : null;
              if (existingPlan) {
                existingPlan.entries = planEntries;
                existingPlan.updatedAt = Date.now();
              } else {
                planMsgs.push({ role: 'system', kind: 'plan', entries: planEntries, createdAt: Date.now(), updatedAt: Date.now() });
              }
              pageAiSyncCurrentSessionMessages();
              pageAiPersistSessions();
              renderPageAiConversation();
            }
          } catch (_) {}
        }
        if (eventName === 'tool.started' || eventName === 'tool.completed' || eventName === 'tool.failed') {
          pageAiApplyToolEvent(eventName, payloadText, runId, runTraceId);
          pageAiSyncCurrentSessionMessages();
          pageAiPersistSessions();
          renderPageAiConversation();
          pageAiSetRunStatus('tool_calling', runId);
          renderPageAiControls();
        }
      });
      if (!pageUiState.pageAiStoppedRunIds[runId]) {
        pageAiFinishStreamingAssistantMessage(runId, assistantText, prompt);
      }
      currentSession = pageAiCurrentSession();
      if (currentSession) {
        currentSession.messages = pageUiState.pageAiMessages.slice();
        currentSession.updatedAt = Date.now();
      }
      pageAiPersistSessions();
    } catch (error) {
      pageAiSetRunStatus('failed');
      pageUiState.pageAiMessages.push({
        role: 'assistant',
        content: 'Hermes 当前请求失败：' + (error instanceof Error ? error.message : String(error))
      });
      currentSession = pageAiCurrentSession();
      if (currentSession) {
        currentSession.messages = pageUiState.pageAiMessages.slice();
        currentSession.updatedAt = Date.now();
      }
      pageAiPersistSessions();
    } finally {
      if (!allowQueue) pageUiState.pageAiBusy = false;
      renderPageAiConversation();
      renderPageAiControls();
    }
  }


  return {
    openPageAiDrawer: (...args) => openPageAiDrawer(...args),
    closePageAiDrawer: (...args) => closePageAiDrawer(...args),
    isPageAiDrawerOpen: (...args) => isPageAiDrawerOpen(...args),
    ensurePageAiDrawer: (...args) => ensurePageAiDrawer(...args),
    sendPageAiMessage: (...args) => sendPageAiMessage(...args),
    pageAiOpenHermesSettings: (...args) => pageAiOpenHermesSettings(...args),
    pageAiStopRun: (...args) => pageAiStopRun(...args),
    pageAiLoadGatewayHealth: (...args) => pageAiLoadGatewayHealth(...args),
    renderPageAiControls: (...args) => renderPageAiControls(...args),
    renderPageAiConversation: (...args) => renderPageAiConversation(...args),
    renderPageAiProviderButtons: (...args) => renderPageAiProviderButtons(...args),
    renderPageAiSuggestions: (...args) => renderPageAiSuggestions(...args),
    pageAiSaveProfileMemory: (...args) => pageAiSaveProfileMemory(...args),
    pageAiToggleSkill: (...args) => pageAiToggleSkill(...args),
    pageAiToggleTool: (...args) => pageAiToggleTool(...args),
    pageAiResumeBackendSession: (...args) => pageAiResumeBackendSession(...args),
    pageAiRenameBackendSession: (...args) => pageAiRenameBackendSession(...args),
    pageAiDeleteBackendSession: (...args) => pageAiDeleteBackendSession(...args),
    pageAiResolvePermission: (...args) => pageAiResolvePermission(...args),
    pageAiOpenLocation: (...args) => pageAiOpenLocation(...args),
    pageAiSetActiveSession: (...args) => pageAiSetActiveSession(...args),
    pageAiStartNewSession: (...args) => pageAiStartNewSession(...args),
    pageAiLoadSessions: (...args) => pageAiLoadSessions(...args),
    pageAiLoadBackendSessions: (...args) => pageAiLoadBackendSessions(...args),
    pageAiCancelQueuedRun: (...args) => pageAiCancelQueuedRun(...args),
    pageAiSearchBackendSessions: (...args) => pageAiSearchBackendSessions(...args),
    pageAiPersistSessions: (...args) => pageAiPersistSessions(...args),
    pageAiLoadProfiles: (...args) => pageAiLoadProfiles(...args),
    pageAiLoadProfileMemory: (...args) => pageAiLoadProfileMemory(...args),
    pageAiLoadSkills: (...args) => pageAiLoadSkills(...args),
    pageAiSwitchProfile: (...args) => pageAiSwitchProfile(...args),
    pageAiSetContextScope: (...args) => pageAiSetContextScope(...args),
    updatePageAiTriggerState: (...args) => updatePageAiTriggerState(...args)
  };
}
