#
tokens: 47870/50000 20/120 files (page 2/8)
lines: off (toggle) GitHub
raw markdown copy
This is page 2 of 8. Use http://codebase.md/hangwin/mcp-chrome?page={x} to view the full context.

# Directory Structure

```
├── .gitattributes
├── .github
│   └── workflows
│       └── build-release.yml
├── .gitignore
├── .husky
│   ├── commit-msg
│   └── pre-commit
├── .prettierignore
├── .prettierrc.json
├── .vscode
│   └── extensions.json
├── app
│   ├── chrome-extension
│   │   ├── _locales
│   │   │   ├── de
│   │   │   │   └── messages.json
│   │   │   ├── en
│   │   │   │   └── messages.json
│   │   │   ├── ja
│   │   │   │   └── messages.json
│   │   │   ├── ko
│   │   │   │   └── messages.json
│   │   │   ├── zh_CN
│   │   │   │   └── messages.json
│   │   │   └── zh_TW
│   │   │       └── messages.json
│   │   ├── .env.example
│   │   ├── assets
│   │   │   └── vue.svg
│   │   ├── common
│   │   │   ├── constants.ts
│   │   │   ├── message-types.ts
│   │   │   └── tool-handler.ts
│   │   ├── entrypoints
│   │   │   ├── background
│   │   │   │   ├── index.ts
│   │   │   │   ├── native-host.ts
│   │   │   │   ├── semantic-similarity.ts
│   │   │   │   ├── storage-manager.ts
│   │   │   │   └── tools
│   │   │   │       ├── base-browser.ts
│   │   │   │       ├── browser
│   │   │   │       │   ├── bookmark.ts
│   │   │   │       │   ├── common.ts
│   │   │   │       │   ├── console.ts
│   │   │   │       │   ├── file-upload.ts
│   │   │   │       │   ├── history.ts
│   │   │   │       │   ├── index.ts
│   │   │   │       │   ├── inject-script.ts
│   │   │   │       │   ├── interaction.ts
│   │   │   │       │   ├── keyboard.ts
│   │   │   │       │   ├── network-capture-debugger.ts
│   │   │   │       │   ├── network-capture-web-request.ts
│   │   │   │       │   ├── network-request.ts
│   │   │   │       │   ├── screenshot.ts
│   │   │   │       │   ├── vector-search.ts
│   │   │   │       │   ├── web-fetcher.ts
│   │   │   │       │   └── window.ts
│   │   │   │       └── index.ts
│   │   │   ├── content.ts
│   │   │   ├── offscreen
│   │   │   │   ├── index.html
│   │   │   │   └── main.ts
│   │   │   └── popup
│   │   │       ├── App.vue
│   │   │       ├── components
│   │   │       │   ├── ConfirmDialog.vue
│   │   │       │   ├── icons
│   │   │       │   │   ├── BoltIcon.vue
│   │   │       │   │   ├── CheckIcon.vue
│   │   │       │   │   ├── DatabaseIcon.vue
│   │   │       │   │   ├── DocumentIcon.vue
│   │   │       │   │   ├── index.ts
│   │   │       │   │   ├── TabIcon.vue
│   │   │       │   │   ├── TrashIcon.vue
│   │   │       │   │   └── VectorIcon.vue
│   │   │       │   ├── ModelCacheManagement.vue
│   │   │       │   └── ProgressIndicator.vue
│   │   │       ├── index.html
│   │   │       ├── main.ts
│   │   │       └── style.css
│   │   ├── eslint.config.js
│   │   ├── inject-scripts
│   │   │   ├── click-helper.js
│   │   │   ├── fill-helper.js
│   │   │   ├── inject-bridge.js
│   │   │   ├── interactive-elements-helper.js
│   │   │   ├── keyboard-helper.js
│   │   │   ├── network-helper.js
│   │   │   ├── screenshot-helper.js
│   │   │   └── web-fetcher-helper.js
│   │   ├── LICENSE
│   │   ├── package.json
│   │   ├── public
│   │   │   ├── icon
│   │   │   │   ├── 128.png
│   │   │   │   ├── 16.png
│   │   │   │   ├── 32.png
│   │   │   │   ├── 48.png
│   │   │   │   └── 96.png
│   │   │   ├── libs
│   │   │   │   └── ort.min.js
│   │   │   └── wxt.svg
│   │   ├── README.md
│   │   ├── tsconfig.json
│   │   ├── utils
│   │   │   ├── content-indexer.ts
│   │   │   ├── i18n.ts
│   │   │   ├── image-utils.ts
│   │   │   ├── lru-cache.ts
│   │   │   ├── model-cache-manager.ts
│   │   │   ├── offscreen-manager.ts
│   │   │   ├── semantic-similarity-engine.ts
│   │   │   ├── simd-math-engine.ts
│   │   │   ├── text-chunker.ts
│   │   │   └── vector-database.ts
│   │   ├── workers
│   │   │   ├── ort-wasm-simd-threaded.jsep.mjs
│   │   │   ├── ort-wasm-simd-threaded.jsep.wasm
│   │   │   ├── ort-wasm-simd-threaded.mjs
│   │   │   ├── ort-wasm-simd-threaded.wasm
│   │   │   ├── simd_math_bg.wasm
│   │   │   ├── simd_math.js
│   │   │   └── similarity.worker.js
│   │   └── wxt.config.ts
│   └── native-server
│       ├── debug.sh
│       ├── install.md
│       ├── jest.config.js
│       ├── package.json
│       ├── README.md
│       ├── src
│       │   ├── cli.ts
│       │   ├── constant
│       │   │   └── index.ts
│       │   ├── file-handler.ts
│       │   ├── index.ts
│       │   ├── mcp
│       │   │   ├── mcp-server-stdio.ts
│       │   │   ├── mcp-server.ts
│       │   │   ├── register-tools.ts
│       │   │   └── stdio-config.json
│       │   ├── native-messaging-host.ts
│       │   ├── scripts
│       │   │   ├── browser-config.ts
│       │   │   ├── build.ts
│       │   │   ├── constant.ts
│       │   │   ├── postinstall.ts
│       │   │   ├── register-dev.ts
│       │   │   ├── register.ts
│       │   │   ├── run_host.bat
│       │   │   ├── run_host.sh
│       │   │   └── utils.ts
│       │   ├── server
│       │   │   ├── index.ts
│       │   │   └── server.test.ts
│       │   └── util
│       │       └── logger.ts
│       └── tsconfig.json
├── commitlint.config.cjs
├── docs
│   ├── ARCHITECTURE_zh.md
│   ├── ARCHITECTURE.md
│   ├── CHANGELOG.md
│   ├── CONTRIBUTING_zh.md
│   ├── CONTRIBUTING.md
│   ├── TOOLS_zh.md
│   ├── TOOLS.md
│   ├── TROUBLESHOOTING_zh.md
│   ├── TROUBLESHOOTING.md
│   └── WINDOWS_INSTALL_zh.md
├── eslint.config.js
├── LICENSE
├── package.json
├── packages
│   ├── shared
│   │   ├── package.json
│   │   ├── src
│   │   │   ├── constants.ts
│   │   │   ├── index.ts
│   │   │   ├── tools.ts
│   │   │   └── types.ts
│   │   └── tsconfig.json
│   └── wasm-simd
│       ├── .gitignore
│       ├── BUILD.md
│       ├── Cargo.toml
│       ├── package.json
│       ├── README.md
│       └── src
│           └── lib.rs
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
├── prompt
│   ├── content-analize.md
│   ├── excalidraw-prompt.md
│   └── modify-web.md
├── README_zh.md
├── README.md
├── releases
│   ├── chrome-extension
│   │   └── latest
│   │       └── chrome-mcp-server-lastest.zip
│   └── README.md
└── test-inject-script.js
```

# Files

--------------------------------------------------------------------------------
/app/chrome-extension/utils/text-chunker.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Text chunking utility
 * Based on semantic chunking strategy, splits long text into small chunks suitable for vectorization
 */

export interface TextChunk {
  text: string;
  source: string;
  index: number;
  wordCount: number;
}

export interface ChunkingOptions {
  maxWordsPerChunk?: number;
  overlapSentences?: number;
  minChunkLength?: number;
  includeTitle?: boolean;
}

export class TextChunker {
  private readonly defaultOptions: Required<ChunkingOptions> = {
    maxWordsPerChunk: 80,
    overlapSentences: 1,
    minChunkLength: 20,
    includeTitle: true,
  };

  public chunkText(content: string, title?: string, options?: ChunkingOptions): TextChunk[] {
    const opts = { ...this.defaultOptions, ...options };
    const chunks: TextChunk[] = [];

    if (opts.includeTitle && title?.trim() && title.trim().length > 5) {
      chunks.push({
        text: title.trim(),
        source: 'title',
        index: 0,
        wordCount: title.trim().split(/\s+/).length,
      });
    }

    const cleanContent = content.trim();
    if (!cleanContent) {
      return chunks;
    }

    const sentences = this.splitIntoSentences(cleanContent);

    if (sentences.length === 0) {
      return this.fallbackChunking(cleanContent, chunks, opts);
    }

    const hasLongSentences = sentences.some(
      (s: string) => s.split(/\s+/).length > opts.maxWordsPerChunk,
    );

    if (hasLongSentences) {
      return this.mixedChunking(sentences, chunks, opts);
    }

    return this.groupSentencesIntoChunks(sentences, chunks, opts);
  }

  private splitIntoSentences(content: string): string[] {
    const processedContent = content
      .replace(/([。!?])\s*/g, '$1\n')
      .replace(/([.!?])\s+(?=[A-Z])/g, '$1\n')
      .replace(/([.!?]["'])\s+(?=[A-Z])/g, '$1\n')
      .replace(/([.!?])\s*$/gm, '$1\n')
      .replace(/([。!?][""])\s*/g, '$1\n')
      .replace(/\n\s*\n/g, '\n');

    const sentences = processedContent
      .split('\n')
      .map((s) => s.trim())
      .filter((s) => s.length > 15);

    if (sentences.length < 3 && content.length > 500) {
      return this.aggressiveSentenceSplitting(content);
    }

    return sentences;
  }

  private aggressiveSentenceSplitting(content: string): string[] {
    const sentences = content
      .replace(/([.!?。!?])/g, '$1\n')
      .replace(/([;;::])/g, '$1\n')
      .replace(/([))])\s*(?=[\u4e00-\u9fa5A-Z])/g, '$1\n')
      .split('\n')
      .map((s) => s.trim())
      .filter((s) => s.length > 15);

    const maxWordsPerChunk = 80;
    const finalSentences: string[] = [];

    for (const sentence of sentences) {
      const words = sentence.split(/\s+/);
      if (words.length <= maxWordsPerChunk) {
        finalSentences.push(sentence);
      } else {
        const overlapWords = 5;
        for (let i = 0; i < words.length; i += maxWordsPerChunk - overlapWords) {
          const chunkWords = words.slice(i, i + maxWordsPerChunk);
          const chunkText = chunkWords.join(' ');
          if (chunkText.length > 15) {
            finalSentences.push(chunkText);
          }
        }
      }
    }

    return finalSentences;
  }

  /**
   * Group sentences into chunks
   */
  private groupSentencesIntoChunks(
    sentences: string[],
    existingChunks: TextChunk[],
    options: Required<ChunkingOptions>,
  ): TextChunk[] {
    const chunks = [...existingChunks];
    let chunkIndex = chunks.length;

    let i = 0;
    while (i < sentences.length) {
      let currentChunkText = '';
      let currentWordCount = 0;
      let sentencesUsed = 0;

      while (i + sentencesUsed < sentences.length && currentWordCount < options.maxWordsPerChunk) {
        const sentence = sentences[i + sentencesUsed];
        const sentenceWords = sentence.split(/\s+/).length;

        if (currentWordCount + sentenceWords > options.maxWordsPerChunk && currentWordCount > 0) {
          break;
        }

        currentChunkText += (currentChunkText ? ' ' : '') + sentence;
        currentWordCount += sentenceWords;
        sentencesUsed++;
      }

      if (currentChunkText.trim().length > options.minChunkLength) {
        chunks.push({
          text: currentChunkText.trim(),
          source: `content_chunk_${chunkIndex}`,
          index: chunkIndex,
          wordCount: currentWordCount,
        });
        chunkIndex++;
      }

      i += Math.max(1, sentencesUsed - options.overlapSentences);
    }
    return chunks;
  }

  /**
   * Mixed chunking method (handles long sentences)
   */
  private mixedChunking(
    sentences: string[],
    existingChunks: TextChunk[],
    options: Required<ChunkingOptions>,
  ): TextChunk[] {
    const chunks = [...existingChunks];
    let chunkIndex = chunks.length;

    for (const sentence of sentences) {
      const sentenceWords = sentence.split(/\s+/).length;

      if (sentenceWords <= options.maxWordsPerChunk) {
        chunks.push({
          text: sentence.trim(),
          source: `sentence_chunk_${chunkIndex}`,
          index: chunkIndex,
          wordCount: sentenceWords,
        });
        chunkIndex++;
      } else {
        const words = sentence.split(/\s+/);
        for (let i = 0; i < words.length; i += options.maxWordsPerChunk) {
          const chunkWords = words.slice(i, i + options.maxWordsPerChunk);
          const chunkText = chunkWords.join(' ');

          if (chunkText.length > options.minChunkLength) {
            chunks.push({
              text: chunkText,
              source: `long_sentence_chunk_${chunkIndex}_part_${Math.floor(i / options.maxWordsPerChunk)}`,
              index: chunkIndex,
              wordCount: chunkWords.length,
            });
          }
        }
        chunkIndex++;
      }
    }

    return chunks;
  }

  /**
   * Fallback chunking (when sentence splitting fails)
   */
  private fallbackChunking(
    content: string,
    existingChunks: TextChunk[],
    options: Required<ChunkingOptions>,
  ): TextChunk[] {
    const chunks = [...existingChunks];
    let chunkIndex = chunks.length;

    const paragraphs = content
      .split(/\n\s*\n/)
      .filter((p) => p.trim().length > options.minChunkLength);

    if (paragraphs.length > 1) {
      paragraphs.forEach((paragraph, index) => {
        const cleanParagraph = paragraph.trim();
        if (cleanParagraph.length > 0) {
          const words = cleanParagraph.split(/\s+/);
          const maxWordsPerChunk = 150;

          for (let i = 0; i < words.length; i += maxWordsPerChunk) {
            const chunkWords = words.slice(i, i + maxWordsPerChunk);
            const chunkText = chunkWords.join(' ');

            if (chunkText.length > options.minChunkLength) {
              chunks.push({
                text: chunkText,
                source: `paragraph_${index}_chunk_${Math.floor(i / maxWordsPerChunk)}`,
                index: chunkIndex,
                wordCount: chunkWords.length,
              });
              chunkIndex++;
            }
          }
        }
      });
    } else {
      const words = content.trim().split(/\s+/);
      const maxWordsPerChunk = 150;

      for (let i = 0; i < words.length; i += maxWordsPerChunk) {
        const chunkWords = words.slice(i, i + maxWordsPerChunk);
        const chunkText = chunkWords.join(' ');

        if (chunkText.length > options.minChunkLength) {
          chunks.push({
            text: chunkText,
            source: `content_chunk_${Math.floor(i / maxWordsPerChunk)}`,
            index: chunkIndex,
            wordCount: chunkWords.length,
          });
          chunkIndex++;
        }
      }
    }

    return chunks;
  }
}

```

--------------------------------------------------------------------------------
/app/chrome-extension/entrypoints/background/tools/browser/web-fetcher.ts:
--------------------------------------------------------------------------------

```typescript
import { createErrorResponse, ToolResult } from '@/common/tool-handler';
import { BaseBrowserToolExecutor } from '../base-browser';
import { TOOL_NAMES } from 'chrome-mcp-shared';
import { TOOL_MESSAGE_TYPES } from '@/common/message-types';

interface WebFetcherToolParams {
  htmlContent?: boolean; // get the visible HTML content of the current page. default: false
  textContent?: boolean; // get the visible text content of the current page. default: true
  url?: string; // optional URL to fetch content from (if not provided, uses active tab)
  selector?: string; // optional CSS selector to get content from a specific element
}

class WebFetcherTool extends BaseBrowserToolExecutor {
  name = TOOL_NAMES.BROWSER.WEB_FETCHER;

  /**
   * Execute web fetcher operation
   */
  async execute(args: WebFetcherToolParams): Promise<ToolResult> {
    // Handle mutually exclusive parameters: if htmlContent is true, textContent is forced to false
    const htmlContent = args.htmlContent === true;
    const textContent = htmlContent ? false : args.textContent !== false; // Default is true, unless htmlContent is true or textContent is explicitly set to false
    const url = args.url;
    const selector = args.selector;

    console.log(`Starting web fetcher with options:`, {
      htmlContent,
      textContent,
      url,
      selector,
    });

    try {
      // Get tab to fetch content from
      let tab;

      if (url) {
        // If URL is provided, check if it's already open
        console.log(`Checking if URL is already open: ${url}`);
        const allTabs = await chrome.tabs.query({});

        // Find tab with matching URL
        const matchingTabs = allTabs.filter((t) => {
          // Normalize URLs for comparison (remove trailing slashes)
          const tabUrl = t.url?.endsWith('/') ? t.url.slice(0, -1) : t.url;
          const targetUrl = url.endsWith('/') ? url.slice(0, -1) : url;
          return tabUrl === targetUrl;
        });

        if (matchingTabs.length > 0) {
          // Use existing tab
          tab = matchingTabs[0];
          console.log(`Found existing tab with URL: ${url}, tab ID: ${tab.id}`);
        } else {
          // Create new tab with the URL
          console.log(`No existing tab found with URL: ${url}, creating new tab`);
          tab = await chrome.tabs.create({ url, active: true });

          // Wait for page to load
          console.log('Waiting for page to load...');
          await new Promise((resolve) => setTimeout(resolve, 3000));
        }
      } else {
        // Use active tab
        const tabs = await chrome.tabs.query({ active: true, currentWindow: true });
        if (!tabs[0]) {
          return createErrorResponse('No active tab found');
        }
        tab = tabs[0];
      }

      if (!tab.id) {
        return createErrorResponse('Tab has no ID');
      }

      // Make sure tab is active
      await chrome.tabs.update(tab.id, { active: true });

      // Prepare result object
      const result: any = {
        success: true,
        url: tab.url,
        title: tab.title,
      };

      await this.injectContentScript(tab.id, ['inject-scripts/web-fetcher-helper.js']);

      // Get HTML content if requested
      if (htmlContent) {
        const htmlResponse = await this.sendMessageToTab(tab.id, {
          action: TOOL_MESSAGE_TYPES.WEB_FETCHER_GET_HTML_CONTENT,
          selector: selector,
        });

        if (htmlResponse.success) {
          result.htmlContent = htmlResponse.htmlContent;
        } else {
          console.error('Failed to get HTML content:', htmlResponse.error);
          result.htmlContentError = htmlResponse.error;
        }
      }

      // Get text content if requested (and htmlContent is not true)
      if (textContent) {
        const textResponse = await this.sendMessageToTab(tab.id, {
          action: TOOL_MESSAGE_TYPES.WEB_FETCHER_GET_TEXT_CONTENT,
          selector: selector,
        });

        if (textResponse.success) {
          result.textContent = textResponse.textContent;

          // Include article metadata if available
          if (textResponse.article) {
            result.article = {
              title: textResponse.article.title,
              byline: textResponse.article.byline,
              siteName: textResponse.article.siteName,
              excerpt: textResponse.article.excerpt,
              lang: textResponse.article.lang,
            };
          }

          // Include page metadata if available
          if (textResponse.metadata) {
            result.metadata = textResponse.metadata;
          }
        } else {
          console.error('Failed to get text content:', textResponse.error);
          result.textContentError = textResponse.error;
        }
      }

      // Interactive elements feature has been removed

      return {
        content: [
          {
            type: 'text',
            text: JSON.stringify(result),
          },
        ],
        isError: false,
      };
    } catch (error) {
      console.error('Error in web fetcher:', error);
      return createErrorResponse(
        `Error fetching web content: ${error instanceof Error ? error.message : String(error)}`,
      );
    }
  }
}

export const webFetcherTool = new WebFetcherTool();

interface GetInteractiveElementsToolParams {
  textQuery?: string; // Text to search for within interactive elements (fuzzy search)
  selector?: string; // CSS selector to filter interactive elements
  includeCoordinates?: boolean; // Include element coordinates in the response (default: true)
  types?: string[]; // Types of interactive elements to include (default: all types)
}

class GetInteractiveElementsTool extends BaseBrowserToolExecutor {
  name = TOOL_NAMES.BROWSER.GET_INTERACTIVE_ELEMENTS;

  /**
   * Execute get interactive elements operation
   */
  async execute(args: GetInteractiveElementsToolParams): Promise<ToolResult> {
    const { textQuery, selector, includeCoordinates = true, types } = args;

    console.log(`Starting get interactive elements with options:`, args);

    try {
      // Get current tab
      const tabs = await chrome.tabs.query({ active: true, currentWindow: true });
      if (!tabs[0]) {
        return createErrorResponse('No active tab found');
      }

      const tab = tabs[0];
      if (!tab.id) {
        return createErrorResponse('Active tab has no ID');
      }

      // Ensure content script is injected
      await this.injectContentScript(tab.id, ['inject-scripts/interactive-elements-helper.js']);

      // Send message to content script
      const result = await this.sendMessageToTab(tab.id, {
        action: TOOL_MESSAGE_TYPES.GET_INTERACTIVE_ELEMENTS,
        textQuery,
        selector,
        includeCoordinates,
        types,
      });

      if (!result.success) {
        return createErrorResponse(result.error || 'Failed to get interactive elements');
      }

      return {
        content: [
          {
            type: 'text',
            text: JSON.stringify({
              success: true,
              elements: result.elements,
              count: result.elements.length,
              query: {
                textQuery,
                selector,
                types: types || 'all',
              },
            }),
          },
        ],
        isError: false,
      };
    } catch (error) {
      console.error('Error in get interactive elements operation:', error);
      return createErrorResponse(
        `Error getting interactive elements: ${error instanceof Error ? error.message : String(error)}`,
      );
    }
  }
}

export const getInteractiveElementsTool = new GetInteractiveElementsTool();

```

--------------------------------------------------------------------------------
/app/chrome-extension/entrypoints/background/native-host.ts:
--------------------------------------------------------------------------------

```typescript
import { NativeMessageType } from 'chrome-mcp-shared';
import { BACKGROUND_MESSAGE_TYPES } from '@/common/message-types';
import {
  NATIVE_HOST,
  ICONS,
  NOTIFICATIONS,
  STORAGE_KEYS,
  ERROR_MESSAGES,
  SUCCESS_MESSAGES,
} from '@/common/constants';
import { handleCallTool } from './tools';

let nativePort: chrome.runtime.Port | null = null;
export const HOST_NAME = NATIVE_HOST.NAME;

/**
 * Server status management interface
 */
interface ServerStatus {
  isRunning: boolean;
  port?: number;
  lastUpdated: number;
}

let currentServerStatus: ServerStatus = {
  isRunning: false,
  lastUpdated: Date.now(),
};

/**
 * Save server status to chrome.storage
 */
async function saveServerStatus(status: ServerStatus): Promise<void> {
  try {
    await chrome.storage.local.set({ [STORAGE_KEYS.SERVER_STATUS]: status });
  } catch (error) {
    console.error(ERROR_MESSAGES.SERVER_STATUS_SAVE_FAILED, error);
  }
}

/**
 * Load server status from chrome.storage
 */
async function loadServerStatus(): Promise<ServerStatus> {
  try {
    const result = await chrome.storage.local.get([STORAGE_KEYS.SERVER_STATUS]);
    if (result[STORAGE_KEYS.SERVER_STATUS]) {
      return result[STORAGE_KEYS.SERVER_STATUS];
    }
  } catch (error) {
    console.error(ERROR_MESSAGES.SERVER_STATUS_LOAD_FAILED, error);
  }
  return {
    isRunning: false,
    lastUpdated: Date.now(),
  };
}

/**
 * Broadcast server status change to all listeners
 */
function broadcastServerStatusChange(status: ServerStatus): void {
  chrome.runtime
    .sendMessage({
      type: BACKGROUND_MESSAGE_TYPES.SERVER_STATUS_CHANGED,
      payload: status,
    })
    .catch(() => {
      // Ignore errors if no listeners are present
    });
}

/**
 * Connect to the native messaging host
 */
export function connectNativeHost(port: number = NATIVE_HOST.DEFAULT_PORT) {
  if (nativePort) {
    return;
  }

  try {
    nativePort = chrome.runtime.connectNative(HOST_NAME);

    nativePort.onMessage.addListener(async (message) => {
      // chrome.notifications.create({
      //   type: NOTIFICATIONS.TYPE,
      //   iconUrl: chrome.runtime.getURL(ICONS.NOTIFICATION),
      //   title: 'Message from native host',
      //   message: `Received data from host: ${JSON.stringify(message)}`,
      //   priority: NOTIFICATIONS.PRIORITY,
      // });

      if (message.type === NativeMessageType.PROCESS_DATA && message.requestId) {
        const requestId = message.requestId;
        const requestPayload = message.payload;

        nativePort?.postMessage({
          responseToRequestId: requestId,
          payload: {
            status: 'success',
            message: SUCCESS_MESSAGES.TOOL_EXECUTED,
            data: requestPayload,
          },
        });
      } else if (message.type === NativeMessageType.CALL_TOOL && message.requestId) {
        const requestId = message.requestId;
        try {
          const result = await handleCallTool(message.payload);
          nativePort?.postMessage({
            responseToRequestId: requestId,
            payload: {
              status: 'success',
              message: SUCCESS_MESSAGES.TOOL_EXECUTED,
              data: result,
            },
          });
        } catch (error) {
          nativePort?.postMessage({
            responseToRequestId: requestId,
            payload: {
              status: 'error',
              message: ERROR_MESSAGES.TOOL_EXECUTION_FAILED,
              error: error instanceof Error ? error.message : String(error),
            },
          });
        }
      } else if (message.type === NativeMessageType.SERVER_STARTED) {
        const port = message.payload?.port;
        currentServerStatus = {
          isRunning: true,
          port: port,
          lastUpdated: Date.now(),
        };
        await saveServerStatus(currentServerStatus);
        broadcastServerStatusChange(currentServerStatus);
        console.log(`${SUCCESS_MESSAGES.SERVER_STARTED} on port ${port}`);
      } else if (message.type === NativeMessageType.SERVER_STOPPED) {
        currentServerStatus = {
          isRunning: false,
          port: currentServerStatus.port, // Keep last known port for reconnection
          lastUpdated: Date.now(),
        };
        await saveServerStatus(currentServerStatus);
        broadcastServerStatusChange(currentServerStatus);
        console.log(SUCCESS_MESSAGES.SERVER_STOPPED);
      } else if (message.type === NativeMessageType.ERROR_FROM_NATIVE_HOST) {
        console.error('Error from native host:', message.payload?.message || 'Unknown error');
      } else if (message.type === 'file_operation_response') {
        // Forward file operation response back to the requesting tool
        chrome.runtime.sendMessage(message).catch(() => {
          // Ignore if no listeners
        });
      }
    });

    nativePort.onDisconnect.addListener(() => {
      console.error(ERROR_MESSAGES.NATIVE_DISCONNECTED, chrome.runtime.lastError);
      nativePort = null;
    });

    nativePort.postMessage({ type: NativeMessageType.START, payload: { port } });
  } catch (error) {
    console.error(ERROR_MESSAGES.NATIVE_CONNECTION_FAILED, error);
  }
}

/**
 * Initialize native host listeners and load initial state
 */
export const initNativeHostListener = () => {
  // Initialize server status from storage
  loadServerStatus()
    .then((status) => {
      currentServerStatus = status;
    })
    .catch((error) => {
      console.error(ERROR_MESSAGES.SERVER_STATUS_LOAD_FAILED, error);
    });

  chrome.runtime.onStartup.addListener(connectNativeHost);

  chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
    if (
      message === NativeMessageType.CONNECT_NATIVE ||
      message.type === NativeMessageType.CONNECT_NATIVE
    ) {
      const port =
        typeof message === 'object' && message.port ? message.port : NATIVE_HOST.DEFAULT_PORT;
      connectNativeHost(port);
      sendResponse({ success: true, port });
      return true;
    }

    if (message.type === NativeMessageType.PING_NATIVE) {
      const connected = nativePort !== null;
      sendResponse({ connected });
      return true;
    }

    if (message.type === NativeMessageType.DISCONNECT_NATIVE) {
      if (nativePort) {
        nativePort.disconnect();
        nativePort = null;
        sendResponse({ success: true });
      } else {
        sendResponse({ success: false, error: 'No active connection' });
      }
      return true;
    }

    if (message.type === BACKGROUND_MESSAGE_TYPES.GET_SERVER_STATUS) {
      sendResponse({
        success: true,
        serverStatus: currentServerStatus,
        connected: nativePort !== null,
      });
      return true;
    }

    if (message.type === BACKGROUND_MESSAGE_TYPES.REFRESH_SERVER_STATUS) {
      loadServerStatus()
        .then((storedStatus) => {
          currentServerStatus = storedStatus;
          sendResponse({
            success: true,
            serverStatus: currentServerStatus,
            connected: nativePort !== null,
          });
        })
        .catch((error) => {
          console.error(ERROR_MESSAGES.SERVER_STATUS_LOAD_FAILED, error);
          sendResponse({
            success: false,
            error: ERROR_MESSAGES.SERVER_STATUS_LOAD_FAILED,
            serverStatus: currentServerStatus,
            connected: nativePort !== null,
          });
        });
      return true;
    }

    // Forward file operation messages to native host
    if (message.type === 'forward_to_native' && message.message) {
      if (nativePort) {
        nativePort.postMessage(message.message);
        sendResponse({ success: true });
      } else {
        sendResponse({ success: false, error: 'Native host not connected' });
      }
      return true;
    }
  });
};

```

--------------------------------------------------------------------------------
/app/native-server/src/scripts/browser-config.ts:
--------------------------------------------------------------------------------

```typescript
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import { execSync } from 'child_process';
import { HOST_NAME } from './constant';

export enum BrowserType {
  CHROME = 'chrome',
  CHROMIUM = 'chromium',
}

export interface BrowserConfig {
  type: BrowserType;
  displayName: string;
  userManifestPath: string;
  systemManifestPath: string;
  registryKey?: string; // Windows only
  systemRegistryKey?: string; // Windows only
}

/**
 * Get the user-level manifest path for a specific browser
 */
function getUserManifestPathForBrowser(browser: BrowserType): string {
  const platform = os.platform();

  if (platform === 'win32') {
    const appData = process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming');
    switch (browser) {
      case BrowserType.CHROME:
        return path.join(appData, 'Google', 'Chrome', 'NativeMessagingHosts', `${HOST_NAME}.json`);
      case BrowserType.CHROMIUM:
        return path.join(appData, 'Chromium', 'NativeMessagingHosts', `${HOST_NAME}.json`);
      default:
        return path.join(appData, 'Google', 'Chrome', 'NativeMessagingHosts', `${HOST_NAME}.json`);
    }
  } else if (platform === 'darwin') {
    const home = os.homedir();
    switch (browser) {
      case BrowserType.CHROME:
        return path.join(
          home,
          'Library',
          'Application Support',
          'Google',
          'Chrome',
          'NativeMessagingHosts',
          `${HOST_NAME}.json`,
        );
      case BrowserType.CHROMIUM:
        return path.join(
          home,
          'Library',
          'Application Support',
          'Chromium',
          'NativeMessagingHosts',
          `${HOST_NAME}.json`,
        );
      default:
        return path.join(
          home,
          'Library',
          'Application Support',
          'Google',
          'Chrome',
          'NativeMessagingHosts',
          `${HOST_NAME}.json`,
        );
    }
  } else {
    // Linux
    const home = os.homedir();
    switch (browser) {
      case BrowserType.CHROME:
        return path.join(
          home,
          '.config',
          'google-chrome',
          'NativeMessagingHosts',
          `${HOST_NAME}.json`,
        );
      case BrowserType.CHROMIUM:
        return path.join(home, '.config', 'chromium', 'NativeMessagingHosts', `${HOST_NAME}.json`);
      default:
        return path.join(
          home,
          '.config',
          'google-chrome',
          'NativeMessagingHosts',
          `${HOST_NAME}.json`,
        );
    }
  }
}

/**
 * Get the system-level manifest path for a specific browser
 */
function getSystemManifestPathForBrowser(browser: BrowserType): string {
  const platform = os.platform();

  if (platform === 'win32') {
    const programFiles = process.env.ProgramFiles || 'C:\\Program Files';
    switch (browser) {
      case BrowserType.CHROME:
        return path.join(
          programFiles,
          'Google',
          'Chrome',
          'NativeMessagingHosts',
          `${HOST_NAME}.json`,
        );
      case BrowserType.CHROMIUM:
        return path.join(programFiles, 'Chromium', 'NativeMessagingHosts', `${HOST_NAME}.json`);
      default:
        return path.join(
          programFiles,
          'Google',
          'Chrome',
          'NativeMessagingHosts',
          `${HOST_NAME}.json`,
        );
    }
  } else if (platform === 'darwin') {
    switch (browser) {
      case BrowserType.CHROME:
        return path.join(
          '/Library',
          'Google',
          'Chrome',
          'NativeMessagingHosts',
          `${HOST_NAME}.json`,
        );
      case BrowserType.CHROMIUM:
        return path.join(
          '/Library',
          'Application Support',
          'Chromium',
          'NativeMessagingHosts',
          `${HOST_NAME}.json`,
        );
      default:
        return path.join(
          '/Library',
          'Google',
          'Chrome',
          'NativeMessagingHosts',
          `${HOST_NAME}.json`,
        );
    }
  } else {
    // Linux
    switch (browser) {
      case BrowserType.CHROME:
        return path.join('/etc', 'opt', 'chrome', 'native-messaging-hosts', `${HOST_NAME}.json`);
      case BrowserType.CHROMIUM:
        return path.join('/etc', 'chromium', 'native-messaging-hosts', `${HOST_NAME}.json`);
      default:
        return path.join('/etc', 'opt', 'chrome', 'native-messaging-hosts', `${HOST_NAME}.json`);
    }
  }
}

/**
 * Get Windows registry keys for a browser
 */
function getRegistryKeys(browser: BrowserType): { user: string; system: string } | undefined {
  if (os.platform() !== 'win32') return undefined;

  const browserPaths: Record<BrowserType, { user: string; system: string }> = {
    [BrowserType.CHROME]: {
      user: `HKCU\\Software\\Google\\Chrome\\NativeMessagingHosts\\${HOST_NAME}`,
      system: `HKLM\\Software\\Google\\Chrome\\NativeMessagingHosts\\${HOST_NAME}`,
    },
    [BrowserType.CHROMIUM]: {
      user: `HKCU\\Software\\Chromium\\NativeMessagingHosts\\${HOST_NAME}`,
      system: `HKLM\\Software\\Chromium\\NativeMessagingHosts\\${HOST_NAME}`,
    },
  };

  return browserPaths[browser];
}

/**
 * Get browser configuration
 */
export function getBrowserConfig(browser: BrowserType): BrowserConfig {
  const registryKeys = getRegistryKeys(browser);

  return {
    type: browser,
    displayName: browser.charAt(0).toUpperCase() + browser.slice(1),
    userManifestPath: getUserManifestPathForBrowser(browser),
    systemManifestPath: getSystemManifestPathForBrowser(browser),
    registryKey: registryKeys?.user,
    systemRegistryKey: registryKeys?.system,
  };
}

/**
 * Detect installed browsers on the system
 */
export function detectInstalledBrowsers(): BrowserType[] {
  const detectedBrowsers: BrowserType[] = [];
  const platform = os.platform();

  if (platform === 'win32') {
    // Check Windows registry for installed browsers
    const browsers: Array<{ type: BrowserType; registryPath: string }> = [
      { type: BrowserType.CHROME, registryPath: 'HKLM\\SOFTWARE\\Google\\Chrome' },
      { type: BrowserType.CHROMIUM, registryPath: 'HKLM\\SOFTWARE\\Chromium' },
    ];

    for (const browser of browsers) {
      try {
        execSync(`reg query "${browser.registryPath}" 2>nul`, { stdio: 'pipe' });
        detectedBrowsers.push(browser.type);
      } catch {
        // Browser not installed
      }
    }
  } else if (platform === 'darwin') {
    // Check macOS Applications folder
    const browsers: Array<{ type: BrowserType; appPath: string }> = [
      { type: BrowserType.CHROME, appPath: '/Applications/Google Chrome.app' },
      { type: BrowserType.CHROMIUM, appPath: '/Applications/Chromium.app' },
    ];

    for (const browser of browsers) {
      if (fs.existsSync(browser.appPath)) {
        detectedBrowsers.push(browser.type);
      }
    }
  } else {
    // Check Linux paths using which command
    const browsers: Array<{ type: BrowserType; commands: string[] }> = [
      { type: BrowserType.CHROME, commands: ['google-chrome', 'google-chrome-stable'] },
      { type: BrowserType.CHROMIUM, commands: ['chromium', 'chromium-browser'] },
    ];

    for (const browser of browsers) {
      for (const cmd of browser.commands) {
        try {
          execSync(`which ${cmd} 2>/dev/null`, { stdio: 'pipe' });
          detectedBrowsers.push(browser.type);
          break; // Found one command, no need to check others
        } catch {
          // Command not found
        }
      }
    }
  }

  return detectedBrowsers;
}

/**
 * Get all supported browser configs
 */
export function getAllBrowserConfigs(): BrowserConfig[] {
  return Object.values(BrowserType).map((browser) => getBrowserConfig(browser));
}

/**
 * Parse browser type from string
 */
export function parseBrowserType(browserStr: string): BrowserType | undefined {
  const normalized = browserStr.toLowerCase();
  return Object.values(BrowserType).find((type) => type === normalized);
}

```

--------------------------------------------------------------------------------
/packages/wasm-simd/src/lib.rs:
--------------------------------------------------------------------------------

```rust
use wasm_bindgen::prelude::*;
use wide::f32x4;

// 设置 panic hook 以便在浏览器中调试
#[wasm_bindgen(start)]
pub fn main() {
    console_error_panic_hook::set_once();
}

#[wasm_bindgen]
pub struct SIMDMath;

#[wasm_bindgen]
impl SIMDMath {
    #[wasm_bindgen(constructor)]
    pub fn new() -> SIMDMath {
        SIMDMath
    }

    // 辅助函数:仅计算点积 (SIMD)
    #[inline]
    fn dot_product_simd_only(&self, vec_a: &[f32], vec_b: &[f32]) -> f32 {
        let len = vec_a.len();
        let simd_lanes = 4;
        let simd_len = len - (len % simd_lanes);
        let mut dot_sum_simd = f32x4::ZERO;

        for i in (0..simd_len).step_by(simd_lanes) {
            // 使用 try_from 和 new 方法,这是 wide 库的正确 API
            let a_array: [f32; 4] = vec_a[i..i + simd_lanes].try_into().unwrap();
            let b_array: [f32; 4] = vec_b[i..i + simd_lanes].try_into().unwrap();
            let a_chunk = f32x4::new(a_array);
            let b_chunk = f32x4::new(b_array);
            dot_sum_simd = a_chunk.mul_add(b_chunk, dot_sum_simd);
        }

        let mut dot_product = dot_sum_simd.reduce_add();
        for i in simd_len..len {
            dot_product += vec_a[i] * vec_b[i];
        }
        dot_product
    }

    #[wasm_bindgen]
    pub fn cosine_similarity(&self, vec_a: &[f32], vec_b: &[f32]) -> f32 {
        if vec_a.len() != vec_b.len() || vec_a.is_empty() {
            return 0.0;
        }

        let len = vec_a.len();
        let simd_lanes = 4;
        let simd_len = len - (len % simd_lanes);

        let mut dot_sum_simd = f32x4::ZERO;
        let mut norm_a_sum_simd = f32x4::ZERO;
        let mut norm_b_sum_simd = f32x4::ZERO;

        // SIMD 处理
        for i in (0..simd_len).step_by(simd_lanes) {
            let a_array: [f32; 4] = vec_a[i..i + simd_lanes].try_into().unwrap();
            let b_array: [f32; 4] = vec_b[i..i + simd_lanes].try_into().unwrap();
            let a_chunk = f32x4::new(a_array);
            let b_chunk = f32x4::new(b_array);

            // 使用 Fused Multiply-Add (FMA)
            dot_sum_simd = a_chunk.mul_add(b_chunk, dot_sum_simd);
            norm_a_sum_simd = a_chunk.mul_add(a_chunk, norm_a_sum_simd);
            norm_b_sum_simd = b_chunk.mul_add(b_chunk, norm_b_sum_simd);
        }

        // 水平求和
        let mut dot_product = dot_sum_simd.reduce_add();
        let mut norm_a_sq = norm_a_sum_simd.reduce_add();
        let mut norm_b_sq = norm_b_sum_simd.reduce_add();

        // 处理剩余元素
        for i in simd_len..len {
            dot_product += vec_a[i] * vec_b[i];
            norm_a_sq += vec_a[i] * vec_a[i];
            norm_b_sq += vec_b[i] * vec_b[i];
        }

        // 优化的数值稳定性处理
        let norm_a = norm_a_sq.sqrt();
        let norm_b = norm_b_sq.sqrt();

        if norm_a == 0.0 || norm_b == 0.0 {
            return 0.0;
        }

        let magnitude = norm_a * norm_b;
        // 限制结果在 [-1.0, 1.0] 范围内,处理浮点精度误差
        (dot_product / magnitude).max(-1.0).min(1.0)
    }

    #[wasm_bindgen]
    pub fn batch_similarity(&self, vectors: &[f32], query: &[f32], vector_dim: usize) -> Vec<f32> {
        if vector_dim == 0 { return Vec::new(); }
        if vectors.len() % vector_dim != 0 { return Vec::new(); }
        if query.len() != vector_dim { return Vec::new(); }

        let num_vectors = vectors.len() / vector_dim;
        let mut results = Vec::with_capacity(num_vectors);

        // 预计算查询向量的范数
        let query_norm_sq = self.compute_norm_squared_simd(query);
        if query_norm_sq == 0.0 {
            return vec![0.0; num_vectors];
        }
        let query_norm = query_norm_sq.sqrt();

        for i in 0..num_vectors {
            let start = i * vector_dim;
            let vector_slice = &vectors[start..start + vector_dim];

            // dot_product_and_norm_simd 计算 vector_slice (vec_a) 的范数
            let (dot_product, vector_norm_sq) = self.dot_product_and_norm_simd(vector_slice, query);

            if vector_norm_sq == 0.0 {
                results.push(0.0);
            } else {
                let vector_norm = vector_norm_sq.sqrt();
                let similarity = dot_product / (vector_norm * query_norm);
                results.push(similarity.max(-1.0).min(1.0));
            }
        }
        results
    }

    // 辅助函数:SIMD 计算范数平方
    #[inline]
    fn compute_norm_squared_simd(&self, vec: &[f32]) -> f32 {
        let len = vec.len();
        let simd_lanes = 4;
        let simd_len = len - (len % simd_lanes);
        let mut norm_sum_simd = f32x4::ZERO;

        for i in (0..simd_len).step_by(simd_lanes) {
            let array: [f32; 4] = vec[i..i + simd_lanes].try_into().unwrap();
            let chunk = f32x4::new(array);
            norm_sum_simd = chunk.mul_add(chunk, norm_sum_simd);
        }

        let mut norm_sq = norm_sum_simd.reduce_add();
        for i in simd_len..len {
            norm_sq += vec[i] * vec[i];
        }
        norm_sq
    }

    // 辅助函数:同时计算点积和vec_a的范数平方
    #[inline]
    fn dot_product_and_norm_simd(&self, vec_a: &[f32], vec_b: &[f32]) -> (f32, f32) {
        let len = vec_a.len(); // 假设 vec_a.len() == vec_b.len()
        let simd_lanes = 4;
        let simd_len = len - (len % simd_lanes);

        let mut dot_sum_simd = f32x4::ZERO;
        let mut norm_a_sum_simd = f32x4::ZERO;

        for i in (0..simd_len).step_by(simd_lanes) {
            let a_array: [f32; 4] = vec_a[i..i + simd_lanes].try_into().unwrap();
            let b_array: [f32; 4] = vec_b[i..i + simd_lanes].try_into().unwrap();
            let a_chunk = f32x4::new(a_array);
            let b_chunk = f32x4::new(b_array);

            dot_sum_simd = a_chunk.mul_add(b_chunk, dot_sum_simd);
            norm_a_sum_simd = a_chunk.mul_add(a_chunk, norm_a_sum_simd);
        }

        let mut dot_product = dot_sum_simd.reduce_add();
        let mut norm_a_sq = norm_a_sum_simd.reduce_add();

        for i in simd_len..len {
            dot_product += vec_a[i] * vec_b[i];
            norm_a_sq += vec_a[i] * vec_a[i];
        }
        (dot_product, norm_a_sq)
    }

    // 批量矩阵相似度计算 - 优化版
    #[wasm_bindgen]
    pub fn similarity_matrix(&self, vectors_a: &[f32], vectors_b: &[f32], vector_dim: usize) -> Vec<f32> {
        if vector_dim == 0 || vectors_a.len() % vector_dim != 0 || vectors_b.len() % vector_dim != 0 {
            return Vec::new();
        }

        let num_a = vectors_a.len() / vector_dim;
        let num_b = vectors_b.len() / vector_dim;
        let mut results = Vec::with_capacity(num_a * num_b);

        // 1. 预计算 vectors_a 的范数
        let norms_a: Vec<f32> = (0..num_a)
            .map(|i| {
                let start = i * vector_dim;
                let vec_a_slice = &vectors_a[start..start + vector_dim];
                self.compute_norm_squared_simd(vec_a_slice).sqrt()
            })
            .collect();

        // 2. 预计算 vectors_b 的范数
        let norms_b: Vec<f32> = (0..num_b)
            .map(|j| {
                let start = j * vector_dim;
                let vec_b_slice = &vectors_b[start..start + vector_dim];
                self.compute_norm_squared_simd(vec_b_slice).sqrt()
            })
            .collect();

        for i in 0..num_a {
            let start_a = i * vector_dim;
            let vec_a = &vectors_a[start_a..start_a + vector_dim];
            let norm_a = norms_a[i];

            if norm_a == 0.0 {
                // 如果 norm_a 为 0,所有相似度都为 0
                for _ in 0..num_b {
                    results.push(0.0);
                }
                continue;
            }

            for j in 0..num_b {
                let start_b = j * vector_dim;
                let vec_b = &vectors_b[start_b..start_b + vector_dim];
                let norm_b = norms_b[j];

                if norm_b == 0.0 {
                    results.push(0.0);
                    continue;
                }

                // 使用专用的点积函数
                let dot_product = self.dot_product_simd_only(vec_a, vec_b);
                let magnitude = norm_a * norm_b;

                // magnitude 不应该为零,因为已经检查了 norm_a/norm_b
                let similarity = (dot_product / magnitude).max(-1.0).min(1.0);
                results.push(similarity);
            }
        }

        results
    }
}

```

--------------------------------------------------------------------------------
/test-inject-script.js:
--------------------------------------------------------------------------------

```javascript
(() => {
  const SCRIPT_ID = 'excalidraw-control-script';
  if (window[SCRIPT_ID]) {
    return;
  }
  function getExcalidrawAPIFromDOM(domElement) {
    if (!domElement) {
      return null;
    }
    const reactFiberKey = Object.keys(domElement).find(
      (key) => key.startsWith('__reactFiber$') || key.startsWith('__reactInternalInstance$'),
    );
    if (!reactFiberKey) {
      return null;
    }
    let fiberNode = domElement[reactFiberKey];
    if (!fiberNode) {
      return null;
    }
    function isExcalidrawAPI(obj) {
      return (
        typeof obj === 'object' &&
        obj !== null &&
        typeof obj.updateScene === 'function' &&
        typeof obj.getSceneElements === 'function' &&
        typeof obj.getAppState === 'function'
      );
    }
    function findApiInObject(objToSearch) {
      if (isExcalidrawAPI(objToSearch)) {
        return objToSearch;
      }
      if (typeof objToSearch === 'object' && objToSearch !== null) {
        for (const key in objToSearch) {
          if (Object.prototype.hasOwnProperty.call(objToSearch, key)) {
            const found = findApiInObject(objToSearch[key]);
            if (found) {
              return found;
            }
          }
        }
      }
      return null;
    }
    let excalidrawApiInstance = null;
    let attempts = 0;
    const MAX_TRAVERSAL_ATTEMPTS = 25;
    while (fiberNode && attempts < MAX_TRAVERSAL_ATTEMPTS) {
      if (fiberNode.stateNode && fiberNode.stateNode.props) {
        const api = findApiInObject(fiberNode.stateNode.props);
        if (api) {
          excalidrawApiInstance = api;
          break;
        }
        if (isExcalidrawAPI(fiberNode.stateNode.props.excalidrawAPI)) {
          excalidrawApiInstance = fiberNode.stateNode.props.excalidrawAPI;
          break;
        }
      }
      if (fiberNode.memoizedProps) {
        const api = findApiInObject(fiberNode.memoizedProps);
        if (api) {
          excalidrawApiInstance = api;
          break;
        }
        if (isExcalidrawAPI(fiberNode.memoizedProps.excalidrawAPI)) {
          excalidrawApiInstance = fiberNode.memoizedProps.excalidrawAPI;
          break;
        }
      }

      if (fiberNode.tag === 1 && fiberNode.stateNode && fiberNode.stateNode.state) {
        const api = findApiInObject(fiberNode.stateNode.state);
        if (api) {
          excalidrawApiInstance = api;
          break;
        }
      }

      if (
        fiberNode.tag === 0 ||
        fiberNode.tag === 2 ||
        fiberNode.tag === 14 ||
        fiberNode.tag === 15 ||
        fiberNode.tag === 11
      ) {
        if (fiberNode.memoizedState) {
          let currentHook = fiberNode.memoizedState;
          let hookAttempts = 0;
          const MAX_HOOK_ATTEMPTS = 15;
          while (currentHook && hookAttempts < MAX_HOOK_ATTEMPTS) {
            const api = findApiInObject(currentHook.memoizedState);
            if (api) {
              excalidrawApiInstance = api;
              break;
            }
            currentHook = currentHook.next;
            hookAttempts++;
          }
          if (excalidrawApiInstance) break;
        }
      }
      if (fiberNode.stateNode) {
        const api = findApiInObject(fiberNode.stateNode);
        if (api && api !== fiberNode.stateNode.props && api !== fiberNode.stateNode.state) {
          excalidrawApiInstance = api;
          break;
        }
      }
      if (
        fiberNode.tag === 9 &&
        fiberNode.memoizedProps &&
        typeof fiberNode.memoizedProps.value !== 'undefined'
      ) {
        const api = findApiInObject(fiberNode.memoizedProps.value);
        if (api) {
          excalidrawApiInstance = api;
          break;
        }
      }

      if (fiberNode.return) {
        fiberNode = fiberNode.return;
      } else {
        break;
      }
      attempts++;
    }

    if (excalidrawApiInstance) {
      window.excalidrawAPI = excalidrawApiInstance;
      console.log('现在您可以通过 `window.foundExcalidrawAPI` 在控制台访问它。');
    } else {
      console.error('在检查组件树后未能找到 excalidrawAPI。');
    }
    return excalidrawApiInstance;
  }

  function createFullExcalidrawElement(skeleton) {
    const id = Math.random().toString(36).substring(2, 9);

    const seed = Math.floor(Math.random() * 2 ** 31);
    const versionNonce = Math.floor(Math.random() * 2 ** 31);

    const defaults = {
      isDeleted: false,
      fillStyle: 'hachure',
      strokeWidth: 1,
      strokeStyle: 'solid',
      roughness: 1,
      opacity: 100,
      angle: 0,
      groupIds: [],
      strokeColor: '#000000',
      backgroundColor: 'transparent',
      version: 1,
      locked: false,
    };

    const fullElement = {
      id: id,
      seed: seed,
      versionNonce: versionNonce,
      updated: Date.now(),
      ...defaults,
      ...skeleton,
    };

    return fullElement;
  }

  let targetElementForAPI = document.querySelector('.excalidraw-app');

  if (targetElementForAPI) {
    getExcalidrawAPIFromDOM(targetElementForAPI);
  }

  const eventHandler = {
    getSceneElements: () => {
      try {
        return window.excalidrawAPI.getSceneElements();
      } catch (error) {
        return {
          error: true,
          msg: JSON.stringify(error),
        };
      }
    },
    addElement: (param) => {
      try {
        const existingElements = window.excalidrawAPI.getSceneElements();
        const newElements = [...existingElements];
        param.eles.forEach((ele, idx) => {
          const newEle = createFullExcalidrawElement(ele);
          newEle.index = `a${existingElements.length + idx + 1}`;
          newElements.push(newEle);
        });
        console.log('newElements ==>', newElements);
        const appState = window.excalidrawAPI.getAppState();
        window.excalidrawAPI.updateScene({
          elements: newElements,
          appState: appState,
          commitToHistory: true,
        });
        return {
          success: true,
        };
      } catch (error) {
        return {
          error: true,
          msg: JSON.stringify(error),
        };
      }
    },
    deleteElement: (param) => {
      try {
        const existingElements = window.excalidrawAPI.getSceneElements();
        const newElements = [...existingElements];
        const idx = newElements.findIndex((e) => e.id === param.id);
        if (idx >= 0) {
          newElements.splice(idx, 1);
          const appState = window.excalidrawAPI.getAppState();
          window.excalidrawAPI.updateScene({
            elements: newElements,
            appState: appState,
            commitToHistory: true,
          });
          return {
            success: true,
          };
        } else {
          return {
            error: true,
            msg: 'element not found',
          };
        }
      } catch (error) {
        return {
          error: true,
          msg: JSON.stringify(error),
        };
      }
    },
    updateElement: (param) => {
      try {
        const existingElements = window.excalidrawAPI.getSceneElements();
        const resIds = [];
        for (let i = 0; i < param.length; i++) {
          const idx = existingElements.findIndex((e) => e.id === param[i].id);
          if (idx >= 0) {
            resIds.push[idx];
            window.excalidrawAPI.mutateElement(existingElements[idx], { ...param[i] });
          }
        }
        return {
          success: true,
          msg: `已更新元素:${resIds.join(',')}`,
        };
      } catch (error) {
        return {
          error: true,
          msg: JSON.stringify(error),
        };
      }
    },
    cleanup: () => {
      try {
        window.excalidrawAPI.resetScene();
        return {
          success: true,
        };
      } catch (error) {
        return {
          error: true,
          msg: JSON.stringify(error),
        };
      }
    },
  };

  const handleExecution = (event) => {
    const { action, payload, requestId } = event.detail;
    const param = JSON.parse(payload || '{}');
    let data, error;
    try {
      const handler = eventHandler[action];
      if (!handler) {
        error = 'event name not found';
      }
      data = handler(param);
    } catch (e) {
      error = e.message;
    }
    window.dispatchEvent(
      new CustomEvent('chrome-mcp:response', { detail: { requestId, data, error } }),
    );
  };

  // --- Lifecycle Functions ---
  const initialize = () => {
    window.addEventListener('chrome-mcp:execute', handleExecution);
    window.addEventListener('chrome-mcp:cleanup', cleanup);
    window[SCRIPT_ID] = true;
  };

  const cleanup = () => {
    window.removeEventListener('chrome-mcp:execute', handleExecution);
    window.removeEventListener('chrome-mcp:cleanup', cleanup);
    delete window[SCRIPT_ID];
    delete window.excalidrawAPI;
  };

  initialize();
})();

```

--------------------------------------------------------------------------------
/app/native-server/src/scripts/postinstall.ts:
--------------------------------------------------------------------------------

```typescript
#!/usr/bin/env node

import fs from 'fs';
import os from 'os';
import path from 'path';
import { COMMAND_NAME } from './constant';
import { colorText, tryRegisterUserLevelHost } from './utils';

// Check if this script is run directly
const isDirectRun = require.main === module;

// Detect global installation for both npm and pnpm
function detectGlobalInstall(): boolean {
  // npm uses npm_config_global
  if (process.env.npm_config_global === 'true') {
    return true;
  }

  // pnpm detection methods
  // Method 1: Check if PNPM_HOME is set and current path contains it
  if (process.env.PNPM_HOME && __dirname.includes(process.env.PNPM_HOME)) {
    return true;
  }

  // Method 2: Check if we're in a global pnpm directory structure
  // pnpm global packages are typically installed in ~/.local/share/pnpm/global/5/node_modules
  // Windows: %APPDATA%\pnpm\global\5\node_modules
  const globalPnpmPatterns =
    process.platform === 'win32'
      ? ['\\pnpm\\global\\', '\\pnpm-global\\', '\\AppData\\Roaming\\pnpm\\']
      : ['/pnpm/global/', '/.local/share/pnpm/', '/pnpm-global/'];

  if (globalPnpmPatterns.some((pattern) => __dirname.includes(pattern))) {
    return true;
  }

  // Method 3: Check npm_config_prefix for pnpm
  if (process.env.npm_config_prefix && __dirname.includes(process.env.npm_config_prefix)) {
    return true;
  }

  // Method 4: Windows-specific global installation paths
  if (process.platform === 'win32') {
    const windowsGlobalPatterns = [
      '\\npm\\node_modules\\',
      '\\AppData\\Roaming\\npm\\node_modules\\',
      '\\Program Files\\nodejs\\node_modules\\',
      '\\nodejs\\node_modules\\',
    ];

    if (windowsGlobalPatterns.some((pattern) => __dirname.includes(pattern))) {
      return true;
    }
  }

  return false;
}

const isGlobalInstall = detectGlobalInstall();

/**
 * Write Node.js path for run_host scripts to avoid fragile relative paths
 */
async function writeNodePath(): Promise<void> {
  try {
    const nodePath = process.execPath;
    const nodePathFile = path.join(__dirname, '..', 'node_path.txt');

    console.log(colorText(`Writing Node.js path: ${nodePath}`, 'blue'));
    fs.writeFileSync(nodePathFile, nodePath, 'utf8');
    console.log(colorText('✓ Node.js path written for run_host scripts', 'green'));
  } catch (error: any) {
    console.warn(colorText(`⚠️ Failed to write Node.js path: ${error.message}`, 'yellow'));
  }
}

/**
 * 确保执行权限(无论是否为全局安装)
 */
async function ensureExecutionPermissions(): Promise<void> {
  if (process.platform === 'win32') {
    // Windows 平台处理
    await ensureWindowsFilePermissions();
    return;
  }

  // Unix/Linux 平台处理
  const filesToCheck = [
    path.join(__dirname, '..', 'index.js'),
    path.join(__dirname, '..', 'run_host.sh'),
    path.join(__dirname, '..', 'cli.js'),
  ];

  for (const filePath of filesToCheck) {
    if (fs.existsSync(filePath)) {
      try {
        fs.chmodSync(filePath, '755');
        console.log(
          colorText(`✓ Set execution permissions for ${path.basename(filePath)}`, 'green'),
        );
      } catch (err: any) {
        console.warn(
          colorText(
            `⚠️ Unable to set execution permissions for ${path.basename(filePath)}: ${err.message}`,
            'yellow',
          ),
        );
      }
    } else {
      console.warn(colorText(`⚠️ File not found: ${filePath}`, 'yellow'));
    }
  }
}

/**
 * Windows 平台文件权限处理
 */
async function ensureWindowsFilePermissions(): Promise<void> {
  const filesToCheck = [
    path.join(__dirname, '..', 'index.js'),
    path.join(__dirname, '..', 'run_host.bat'),
    path.join(__dirname, '..', 'cli.js'),
  ];

  for (const filePath of filesToCheck) {
    if (fs.existsSync(filePath)) {
      try {
        // 检查文件是否为只读,如果是则移除只读属性
        const stats = fs.statSync(filePath);
        if (!(stats.mode & parseInt('200', 8))) {
          // 检查写权限
          // 尝试移除只读属性
          fs.chmodSync(filePath, stats.mode | parseInt('200', 8));
          console.log(
            colorText(`✓ Removed read-only attribute from ${path.basename(filePath)}`, 'green'),
          );
        }

        // 验证文件可读性
        fs.accessSync(filePath, fs.constants.R_OK);
        console.log(
          colorText(`✓ Verified file accessibility for ${path.basename(filePath)}`, 'green'),
        );
      } catch (err: any) {
        console.warn(
          colorText(
            `⚠️ Unable to verify file permissions for ${path.basename(filePath)}: ${err.message}`,
            'yellow',
          ),
        );
      }
    } else {
      console.warn(colorText(`⚠️ File not found: ${filePath}`, 'yellow'));
    }
  }
}

async function tryRegisterNativeHost(): Promise<void> {
  try {
    console.log(colorText('Attempting to register Chrome Native Messaging host...', 'blue'));

    // Always ensure execution permissions, regardless of installation type
    await ensureExecutionPermissions();

    if (isGlobalInstall) {
      // First try user-level installation (no elevated permissions required)
      const userLevelSuccess = await tryRegisterUserLevelHost();

      if (!userLevelSuccess) {
        // User-level installation failed, suggest using register command
        console.log(
          colorText(
            'User-level installation failed, system-level installation may be needed',
            'yellow',
          ),
        );
        console.log(
          colorText('Please run the following command for system-level installation:', 'blue'),
        );
        console.log(`  ${COMMAND_NAME} register --system`);
        printManualInstructions();
      }
    } else {
      // Local installation mode, don't attempt automatic registration
      console.log(
        colorText('Local installation detected, skipping automatic registration', 'yellow'),
      );
      printManualInstructions();
    }
  } catch (error) {
    console.log(
      colorText(
        `注册过程中出现错误: ${error instanceof Error ? error.message : String(error)}`,
        'red',
      ),
    );
    printManualInstructions();
  }
}

/**
 * 打印手动安装指南
 */
function printManualInstructions(): void {
  console.log('\n' + colorText('===== Manual Registration Guide =====', 'blue'));

  console.log(colorText('1. Try user-level installation (recommended):', 'yellow'));
  if (isGlobalInstall) {
    console.log(`  ${COMMAND_NAME} register`);
  } else {
    console.log(`  npx ${COMMAND_NAME} register`);
  }

  console.log(
    colorText('\n2. If user-level installation fails, try system-level installation:', 'yellow'),
  );

  console.log(colorText('   Use --system parameter (auto-elevate permissions):', 'yellow'));
  if (isGlobalInstall) {
    console.log(`  ${COMMAND_NAME} register --system`);
  } else {
    console.log(`  npx ${COMMAND_NAME} register --system`);
  }

  console.log(colorText('\n   Or use administrator privileges directly:', 'yellow'));
  if (os.platform() === 'win32') {
    console.log(
      colorText(
        '   Please run Command Prompt or PowerShell as administrator and execute:',
        'yellow',
      ),
    );
    if (isGlobalInstall) {
      console.log(`  ${COMMAND_NAME} register`);
    } else {
      console.log(`  npx ${COMMAND_NAME} register`);
    }
  } else {
    console.log(colorText('   Please run the following command in terminal:', 'yellow'));
    if (isGlobalInstall) {
      console.log(`  sudo ${COMMAND_NAME} register`);
    } else {
      console.log(`  sudo npx ${COMMAND_NAME} register`);
    }
  }

  console.log(
    '\n' +
      colorText(
        'Ensure Chrome extension is installed and refresh the extension to connect to local service.',
        'blue',
      ),
  );
}

/**
 * 主函数
 */
async function main(): Promise<void> {
  console.log(colorText(`Installing ${COMMAND_NAME}...`, 'green'));

  // Debug information
  console.log(colorText('Installation environment debug info:', 'blue'));
  console.log(`  __dirname: ${__dirname}`);
  console.log(`  npm_config_global: ${process.env.npm_config_global}`);
  console.log(`  PNPM_HOME: ${process.env.PNPM_HOME}`);
  console.log(`  npm_config_prefix: ${process.env.npm_config_prefix}`);
  console.log(`  isGlobalInstall: ${isGlobalInstall}`);

  // Always ensure execution permissions first
  await ensureExecutionPermissions();

  // Write Node.js path for run_host scripts to use
  await writeNodePath();

  // If global installation, try automatic registration
  if (isGlobalInstall) {
    await tryRegisterNativeHost();
  } else {
    console.log(colorText('Local installation detected', 'yellow'));
    printManualInstructions();
  }
}

// Only execute main function when running this script directly
if (isDirectRun) {
  main().catch((error) => {
    console.error(
      colorText(
        `Installation script error: ${error instanceof Error ? error.message : String(error)}`,
        'red',
      ),
    );
  });
}

```

--------------------------------------------------------------------------------
/app/chrome-extension/entrypoints/background/tools/browser/vector-search.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Vectorized tab content search tool
 * Uses vector database for efficient semantic search
 */

import { createErrorResponse, ToolResult } from '@/common/tool-handler';
import { BaseBrowserToolExecutor } from '../base-browser';
import { TOOL_NAMES } from 'chrome-mcp-shared';
import { ContentIndexer } from '@/utils/content-indexer';
import { LIMITS, ERROR_MESSAGES } from '@/common/constants';
import type { SearchResult } from '@/utils/vector-database';

interface VectorSearchResult {
  tabId: number;
  url: string;
  title: string;
  semanticScore: number;
  matchedSnippet: string;
  chunkSource: string;
  timestamp: number;
}

/**
 * Tool for vectorized search of tab content using semantic similarity
 */
class VectorSearchTabsContentTool extends BaseBrowserToolExecutor {
  name = TOOL_NAMES.BROWSER.SEARCH_TABS_CONTENT;
  private contentIndexer: ContentIndexer;
  private isInitialized = false;

  constructor() {
    super();
    this.contentIndexer = new ContentIndexer({
      autoIndex: true,
      maxChunksPerPage: LIMITS.MAX_SEARCH_RESULTS,
      skipDuplicates: true,
    });
  }

  private async initializeIndexer(): Promise<void> {
    try {
      await this.contentIndexer.initialize();
      this.isInitialized = true;
      console.log('VectorSearchTabsContentTool: Content indexer initialized successfully');
    } catch (error) {
      console.error('VectorSearchTabsContentTool: Failed to initialize content indexer:', error);
      this.isInitialized = false;
    }
  }

  async execute(args: { query: string }): Promise<ToolResult> {
    try {
      const { query } = args;

      if (!query || query.trim().length === 0) {
        return createErrorResponse(
          ERROR_MESSAGES.INVALID_PARAMETERS + ': Query parameter is required and cannot be empty',
        );
      }

      console.log(`VectorSearchTabsContentTool: Starting vector search with query: "${query}"`);

      // Check semantic engine status
      if (!this.contentIndexer.isSemanticEngineReady()) {
        if (this.contentIndexer.isSemanticEngineInitializing()) {
          return createErrorResponse(
            'Vector search engine is still initializing (model downloading). Please wait a moment and try again.',
          );
        } else {
          // Try to initialize
          console.log('VectorSearchTabsContentTool: Initializing content indexer...');
          await this.initializeIndexer();

          // Check semantic engine status again
          if (!this.contentIndexer.isSemanticEngineReady()) {
            return createErrorResponse('Failed to initialize vector search engine');
          }
        }
      }

      // Execute vector search, get more results for deduplication
      const searchResults = await this.contentIndexer.searchContent(query, 50);

      // Convert search results format
      const vectorSearchResults = this.convertSearchResults(searchResults);

      // Deduplicate by tab, keep only the highest similarity fragment per tab
      const deduplicatedResults = this.deduplicateByTab(vectorSearchResults);

      // Sort by similarity and get top 10 results
      const topResults = deduplicatedResults
        .sort((a, b) => b.semanticScore - a.semanticScore)
        .slice(0, 10);

      // Get index statistics
      const stats = this.contentIndexer.getStats();

      const result = {
        success: true,
        totalTabsSearched: stats.totalTabs,
        matchedTabsCount: topResults.length,
        vectorSearchEnabled: true,
        indexStats: {
          totalDocuments: stats.totalDocuments,
          totalTabs: stats.totalTabs,
          indexedPages: stats.indexedPages,
          semanticEngineReady: stats.semanticEngineReady,
          semanticEngineInitializing: stats.semanticEngineInitializing,
        },
        matchedTabs: topResults.map((result) => ({
          tabId: result.tabId,
          url: result.url,
          title: result.title,
          semanticScore: result.semanticScore,
          matchedSnippets: [result.matchedSnippet],
          chunkSource: result.chunkSource,
          timestamp: result.timestamp,
        })),
      };

      console.log(
        `VectorSearchTabsContentTool: Found ${topResults.length} results with vector search`,
      );

      return {
        content: [
          {
            type: 'text',
            text: JSON.stringify(result, null, 2),
          },
        ],
        isError: false,
      };
    } catch (error) {
      console.error('VectorSearchTabsContentTool: Search failed:', error);
      return createErrorResponse(
        `Vector search failed: ${error instanceof Error ? error.message : String(error)}`,
      );
    }
  }

  /**
   * Ensure all tabs are indexed
   */
  private async ensureTabsIndexed(tabs: chrome.tabs.Tab[]): Promise<void> {
    const indexPromises = tabs
      .filter((tab) => tab.id)
      .map(async (tab) => {
        try {
          await this.contentIndexer.indexTabContent(tab.id!);
        } catch (error) {
          console.warn(`VectorSearchTabsContentTool: Failed to index tab ${tab.id}:`, error);
        }
      });

    await Promise.allSettled(indexPromises);
  }

  /**
   * Convert search results format
   */
  private convertSearchResults(searchResults: SearchResult[]): VectorSearchResult[] {
    return searchResults.map((result) => ({
      tabId: result.document.tabId,
      url: result.document.url,
      title: result.document.title,
      semanticScore: result.similarity,
      matchedSnippet: this.extractSnippet(result.document.chunk.text),
      chunkSource: result.document.chunk.source,
      timestamp: result.document.timestamp,
    }));
  }

  /**
   * Deduplicate by tab, keep only the highest similarity fragment per tab
   */
  private deduplicateByTab(results: VectorSearchResult[]): VectorSearchResult[] {
    const tabMap = new Map<number, VectorSearchResult>();

    for (const result of results) {
      const existingResult = tabMap.get(result.tabId);

      // If this tab has no result yet, or current result has higher similarity, update it
      if (!existingResult || result.semanticScore > existingResult.semanticScore) {
        tabMap.set(result.tabId, result);
      }
    }

    return Array.from(tabMap.values());
  }

  /**
   * Extract text snippet for display
   */
  private extractSnippet(text: string, maxLength: number = 200): string {
    if (text.length <= maxLength) {
      return text;
    }

    // Try to truncate at sentence boundary
    const truncated = text.substring(0, maxLength);
    const lastSentenceEnd = Math.max(
      truncated.lastIndexOf('.'),
      truncated.lastIndexOf('!'),
      truncated.lastIndexOf('?'),
      truncated.lastIndexOf('。'),
      truncated.lastIndexOf('!'),
      truncated.lastIndexOf('?'),
    );

    if (lastSentenceEnd > maxLength * 0.7) {
      return truncated.substring(0, lastSentenceEnd + 1);
    }

    // If no suitable sentence boundary found, truncate at word boundary
    const lastSpaceIndex = truncated.lastIndexOf(' ');
    if (lastSpaceIndex > maxLength * 0.8) {
      return truncated.substring(0, lastSpaceIndex) + '...';
    }

    return truncated + '...';
  }

  /**
   * Get index statistics
   */
  public async getIndexStats() {
    if (!this.isInitialized) {
      // Don't automatically initialize - just return basic stats
      return {
        totalDocuments: 0,
        totalTabs: 0,
        indexSize: 0,
        indexedPages: 0,
        isInitialized: false,
        semanticEngineReady: false,
        semanticEngineInitializing: false,
      };
    }
    return this.contentIndexer.getStats();
  }

  /**
   * Manually rebuild index
   */
  public async rebuildIndex(): Promise<void> {
    if (!this.isInitialized) {
      await this.initializeIndexer();
    }

    try {
      // Clear existing indexes
      await this.contentIndexer.clearAllIndexes();

      // Get all tabs and reindex
      const windows = await chrome.windows.getAll({ populate: true });
      const allTabs: chrome.tabs.Tab[] = [];

      for (const window of windows) {
        if (window.tabs) {
          allTabs.push(...window.tabs);
        }
      }

      const validTabs = allTabs.filter(
        (tab) =>
          tab.id &&
          tab.url &&
          !tab.url.startsWith('chrome://') &&
          !tab.url.startsWith('chrome-extension://') &&
          !tab.url.startsWith('edge://') &&
          !tab.url.startsWith('about:'),
      );

      await this.ensureTabsIndexed(validTabs);

      console.log(`VectorSearchTabsContentTool: Rebuilt index for ${validTabs.length} tabs`);
    } catch (error) {
      console.error('VectorSearchTabsContentTool: Failed to rebuild index:', error);
      throw error;
    }
  }

  /**
   * Manually index specified tab
   */
  public async indexTab(tabId: number): Promise<void> {
    if (!this.isInitialized) {
      await this.initializeIndexer();
    }

    await this.contentIndexer.indexTabContent(tabId);
  }

  /**
   * Remove index for specified tab
   */
  public async removeTabIndex(tabId: number): Promise<void> {
    if (!this.isInitialized) {
      return;
    }

    await this.contentIndexer.removeTabIndex(tabId);
  }
}

// Export tool instance
export const vectorSearchTabsContentTool = new VectorSearchTabsContentTool();

```

--------------------------------------------------------------------------------
/app/native-server/src/native-messaging-host.ts:
--------------------------------------------------------------------------------

```typescript
import { stdin, stdout } from 'process';
import { Server } from './server';
import { v4 as uuidv4 } from 'uuid';
import { NativeMessageType } from 'chrome-mcp-shared';
import { TIMEOUTS } from './constant';
import fileHandler from './file-handler';

interface PendingRequest {
  resolve: (value: any) => void;
  reject: (reason?: any) => void;
  timeoutId: NodeJS.Timeout;
}

export class NativeMessagingHost {
  private associatedServer: Server | null = null;
  private pendingRequests: Map<string, PendingRequest> = new Map();

  public setServer(serverInstance: Server): void {
    this.associatedServer = serverInstance;
  }

  // add message handler to wait for start server
  public start(): void {
    try {
      this.setupMessageHandling();
    } catch (error: any) {
      process.exit(1);
    }
  }

  private setupMessageHandling(): void {
    let buffer = Buffer.alloc(0);
    let expectedLength = -1;

    stdin.on('readable', () => {
      let chunk;
      while ((chunk = stdin.read()) !== null) {
        buffer = Buffer.concat([buffer, chunk]);

        if (expectedLength === -1 && buffer.length >= 4) {
          expectedLength = buffer.readUInt32LE(0);
          buffer = buffer.slice(4);
        }

        if (expectedLength !== -1 && buffer.length >= expectedLength) {
          const messageBuffer = buffer.slice(0, expectedLength);
          buffer = buffer.slice(expectedLength);

          try {
            const message = JSON.parse(messageBuffer.toString());
            this.handleMessage(message);
          } catch (error: any) {
            this.sendError(`Failed to parse message: ${error.message}`);
          }
          expectedLength = -1; // reset to get next data
        }
      }
    });

    stdin.on('end', () => {
      this.cleanup();
    });

    stdin.on('error', () => {
      this.cleanup();
    });
  }

  private async handleMessage(message: any): Promise<void> {
    if (!message || typeof message !== 'object') {
      this.sendError('Invalid message format');
      return;
    }

    if (message.responseToRequestId) {
      const requestId = message.responseToRequestId;
      const pending = this.pendingRequests.get(requestId);

      if (pending) {
        clearTimeout(pending.timeoutId);
        if (message.error) {
          pending.reject(new Error(message.error));
        } else {
          pending.resolve(message.payload);
        }
        this.pendingRequests.delete(requestId);
      } else {
        // just ignore
      }
      return;
    }

    // Handle directive messages from Chrome
    try {
      switch (message.type) {
        case NativeMessageType.START:
          await this.startServer(message.payload?.port || 3000);
          break;
        case NativeMessageType.STOP:
          await this.stopServer();
          break;
        // Keep ping/pong for simple liveness detection, but this differs from request-response pattern
        case 'ping_from_extension':
          this.sendMessage({ type: 'pong_to_extension' });
          break;
        case 'file_operation':
          await this.handleFileOperation(message);
          break;
        default:
          // Double check when message type is not supported
          if (!message.responseToRequestId) {
            this.sendError(
              `Unknown message type or non-response message: ${message.type || 'no type'}`,
            );
          }
      }
    } catch (error: any) {
      this.sendError(`Failed to handle directive message: ${error.message}`);
    }
  }

  /**
   * Handle file operations from the extension
   */
  private async handleFileOperation(message: any): Promise<void> {
    try {
      const result = await fileHandler.handleFileRequest(message.payload);
      
      if (message.requestId) {
        // Send response back with the request ID
        this.sendMessage({
          type: 'file_operation_response',
          responseToRequestId: message.requestId,
          payload: result,
        });
      } else {
        // No request ID, just send result
        this.sendMessage({
          type: 'file_operation_result',
          payload: result,
        });
      }
    } catch (error: any) {
      const errorResponse = {
        success: false,
        error: error.message || 'Unknown error during file operation',
      };
      
      if (message.requestId) {
        this.sendMessage({
          type: 'file_operation_response',
          responseToRequestId: message.requestId,
          error: errorResponse.error,
        });
      } else {
        this.sendError(`File operation failed: ${errorResponse.error}`);
      }
    }
  }

  /**
   * Send request to Chrome and wait for response
   * @param messagePayload Data to send to Chrome
   * @param timeoutMs Timeout for waiting response (milliseconds)
   * @returns Promise, resolves to Chrome's returned payload on success, rejects on failure
   */
  public sendRequestToExtensionAndWait(
    messagePayload: any,
    messageType: string = 'request_data',
    timeoutMs: number = TIMEOUTS.DEFAULT_REQUEST_TIMEOUT,
  ): Promise<any> {
    return new Promise((resolve, reject) => {
      const requestId = uuidv4(); // Generate unique request ID

      const timeoutId = setTimeout(() => {
        this.pendingRequests.delete(requestId); // Remove from Map after timeout
        reject(new Error(`Request timed out after ${timeoutMs}ms`));
      }, timeoutMs);

      // Store request's resolve/reject functions and timeout ID
      this.pendingRequests.set(requestId, { resolve, reject, timeoutId });

      // Send message with requestId to Chrome
      this.sendMessage({
        type: messageType, // Define a request type, e.g. 'request_data'
        payload: messagePayload,
        requestId: requestId, // <--- Key: include request ID
      });
    });
  }

  /**
   * Start Fastify server (now accepts Server instance)
   */
  private async startServer(port: number): Promise<void> {
    if (!this.associatedServer) {
      this.sendError('Internal error: server instance not set');
      return;
    }
    try {
      if (this.associatedServer.isRunning) {
        this.sendMessage({
          type: NativeMessageType.ERROR,
          payload: { message: 'Server is already running' },
        });
        return;
      }

      await this.associatedServer.start(port, this);

      this.sendMessage({
        type: NativeMessageType.SERVER_STARTED,
        payload: { port },
      });

    } catch (error: any) {
      this.sendError(`Failed to start server: ${error.message}`);
    }
  }

  /**
   * Stop Fastify server
   */
  private async stopServer(): Promise<void> {
    if (!this.associatedServer) {
      this.sendError('Internal error: server instance not set');
      return;
    }
    try {
      // Check status through associatedServer
      if (!this.associatedServer.isRunning) {
        this.sendMessage({
          type: NativeMessageType.ERROR,
          payload: { message: 'Server is not running' },
        });
        return;
      }

      await this.associatedServer.stop();
      // this.serverStarted = false; // Server should update its own status after successful stop

      this.sendMessage({ type: NativeMessageType.SERVER_STOPPED }); // Distinguish from previous 'stopped'
    } catch (error: any) {
      this.sendError(`Failed to stop server: ${error.message}`);
    }
  }

  /**
   * Send message to Chrome extension
   */
  public sendMessage(message: any): void {
    try {
      const messageString = JSON.stringify(message);
      const messageBuffer = Buffer.from(messageString);
      const headerBuffer = Buffer.alloc(4);
      headerBuffer.writeUInt32LE(messageBuffer.length, 0);
      // Ensure atomic write
      stdout.write(Buffer.concat([headerBuffer, messageBuffer]), (err) => {
        if (err) {
          // Consider how to handle write failure, may affect request completion
        } else {
          // Message sent successfully, no action needed
        }
      });
    } catch (error: any) {
      // Catch JSON.stringify or Buffer operation errors
      // If preparation stage fails, associated request may never be sent
      // Need to consider whether to reject corresponding Promise (if called within sendRequestToExtensionAndWait)
    }
  }

  /**
   * Send error message to Chrome extension (mainly for sending non-request-response type errors)
   */
  private sendError(errorMessage: string): void {
    this.sendMessage({
      type: NativeMessageType.ERROR_FROM_NATIVE_HOST, // Use more explicit type
      payload: { message: errorMessage },
    });
  }



  /**
   * Clean up resources
   */
  private cleanup(): void {
    // Reject all pending requests
    this.pendingRequests.forEach((pending) => {
      clearTimeout(pending.timeoutId);
      pending.reject(new Error('Native host is shutting down or Chrome disconnected.'));
    });
    this.pendingRequests.clear();

    if (this.associatedServer && this.associatedServer.isRunning) {
      this.associatedServer
        .stop()
        .then(() => {
          process.exit(0);
        })
        .catch(() => {
          process.exit(1);
        });
    } else {
      process.exit(0);
    }
  }
}

const nativeMessagingHostInstance = new NativeMessagingHost();
export default nativeMessagingHostInstance;

```

--------------------------------------------------------------------------------
/app/chrome-extension/utils/i18n.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Chrome Extension i18n utility
 * Provides safe access to chrome.i18n.getMessage with fallbacks
 */

// Fallback messages for when Chrome APIs aren't available (English)
const fallbackMessages: Record<string, string> = {
  // Extension metadata
  extensionName: 'chrome-mcp-server',
  extensionDescription: 'Exposes browser capabilities with your own chrome',

  // Section headers
  nativeServerConfigLabel: 'Native Server Configuration',
  semanticEngineLabel: 'Semantic Engine',
  embeddingModelLabel: 'Embedding Model',
  indexDataManagementLabel: 'Index Data Management',
  modelCacheManagementLabel: 'Model Cache Management',

  // Status labels
  statusLabel: 'Status',
  runningStatusLabel: 'Running Status',
  connectionStatusLabel: 'Connection Status',
  lastUpdatedLabel: 'Last Updated:',

  // Connection states
  connectButton: 'Connect',
  disconnectButton: 'Disconnect',
  connectingStatus: 'Connecting...',
  connectedStatus: 'Connected',
  disconnectedStatus: 'Disconnected',
  detectingStatus: 'Detecting...',

  // Server states
  serviceRunningStatus: 'Service Running (Port: {0})',
  serviceNotConnectedStatus: 'Service Not Connected',
  connectedServiceNotStartedStatus: 'Connected, Service Not Started',

  // Configuration labels
  mcpServerConfigLabel: 'MCP Server Configuration',
  connectionPortLabel: 'Connection Port',
  refreshStatusButton: 'Refresh Status',
  copyConfigButton: 'Copy Configuration',

  // Action buttons
  retryButton: 'Retry',
  cancelButton: 'Cancel',
  confirmButton: 'Confirm',
  saveButton: 'Save',
  closeButton: 'Close',
  resetButton: 'Reset',

  // Progress states
  initializingStatus: 'Initializing...',
  processingStatus: 'Processing...',
  loadingStatus: 'Loading...',
  clearingStatus: 'Clearing...',
  cleaningStatus: 'Cleaning...',
  downloadingStatus: 'Downloading...',

  // Semantic engine states
  semanticEngineReadyStatus: 'Semantic Engine Ready',
  semanticEngineInitializingStatus: 'Semantic Engine Initializing...',
  semanticEngineInitFailedStatus: 'Semantic Engine Initialization Failed',
  semanticEngineNotInitStatus: 'Semantic Engine Not Initialized',
  initSemanticEngineButton: 'Initialize Semantic Engine',
  reinitializeButton: 'Reinitialize',

  // Model states
  downloadingModelStatus: 'Downloading Model... {0}%',
  switchingModelStatus: 'Switching Model...',
  modelLoadedStatus: 'Model Loaded',
  modelFailedStatus: 'Model Failed to Load',

  // Model descriptions
  lightweightModelDescription: 'Lightweight Multilingual Model',
  betterThanSmallDescription: 'Slightly larger than e5-small, but better performance',
  multilingualModelDescription: 'Multilingual Semantic Model',

  // Performance levels
  fastPerformance: 'Fast',
  balancedPerformance: 'Balanced',
  accuratePerformance: 'Accurate',

  // Error messages
  networkErrorMessage: 'Network connection error, please check network and retry',
  modelCorruptedErrorMessage: 'Model file corrupted or incomplete, please retry download',
  unknownErrorMessage: 'Unknown error, please check if your network can access HuggingFace',
  permissionDeniedErrorMessage: 'Permission denied',
  timeoutErrorMessage: 'Operation timed out',

  // Data statistics
  indexedPagesLabel: 'Indexed Pages',
  indexSizeLabel: 'Index Size',
  activeTabsLabel: 'Active Tabs',
  vectorDocumentsLabel: 'Vector Documents',
  cacheSizeLabel: 'Cache Size',
  cacheEntriesLabel: 'Cache Entries',

  // Data management
  clearAllDataButton: 'Clear All Data',
  clearAllCacheButton: 'Clear All Cache',
  cleanExpiredCacheButton: 'Clean Expired Cache',
  exportDataButton: 'Export Data',
  importDataButton: 'Import Data',

  // Dialog titles
  confirmClearDataTitle: 'Confirm Clear Data',
  settingsTitle: 'Settings',
  aboutTitle: 'About',
  helpTitle: 'Help',

  // Dialog messages
  clearDataWarningMessage:
    'This operation will clear all indexed webpage content and vector data, including:',
  clearDataList1: 'All webpage text content index',
  clearDataList2: 'Vector embedding data',
  clearDataList3: 'Search history and cache',
  clearDataIrreversibleWarning:
    'This operation is irreversible! After clearing, you need to browse webpages again to rebuild the index.',
  confirmClearButton: 'Confirm Clear',

  // Cache states
  cacheDetailsLabel: 'Cache Details',
  noCacheDataMessage: 'No cache data',
  loadingCacheInfoStatus: 'Loading cache information...',
  processingCacheStatus: 'Processing cache...',
  expiredLabel: 'Expired',

  // Browser integration
  bookmarksBarLabel: 'Bookmarks Bar',
  newTabLabel: 'New Tab',
  currentPageLabel: 'Current Page',

  // Accessibility
  menuLabel: 'Menu',
  navigationLabel: 'Navigation',
  mainContentLabel: 'Main Content',

  // Future features
  languageSelectorLabel: 'Language',
  themeLabel: 'Theme',
  lightTheme: 'Light',
  darkTheme: 'Dark',
  autoTheme: 'Auto',
  advancedSettingsLabel: 'Advanced Settings',
  debugModeLabel: 'Debug Mode',
  verboseLoggingLabel: 'Verbose Logging',

  // Notifications
  successNotification: 'Operation completed successfully',
  warningNotification: 'Warning: Please review before proceeding',
  infoNotification: 'Information',
  configCopiedNotification: 'Configuration copied to clipboard',
  dataClearedNotification: 'Data cleared successfully',

  // Units
  bytesUnit: 'bytes',
  kilobytesUnit: 'KB',
  megabytesUnit: 'MB',
  gigabytesUnit: 'GB',
  itemsUnit: 'items',
  pagesUnit: 'pages',

  // Legacy keys for backwards compatibility
  nativeServerConfig: 'Native Server Configuration',
  runningStatus: 'Running Status',
  refreshStatus: 'Refresh Status',
  lastUpdated: 'Last Updated:',
  mcpServerConfig: 'MCP Server Configuration',
  connectionPort: 'Connection Port',
  connecting: 'Connecting...',
  disconnect: 'Disconnect',
  connect: 'Connect',
  semanticEngine: 'Semantic Engine',
  embeddingModel: 'Embedding Model',
  retry: 'Retry',
  indexDataManagement: 'Index Data Management',
  clearing: 'Clearing...',
  clearAllData: 'Clear All Data',
  copyConfig: 'Copy Configuration',
  serviceRunning: 'Service Running (Port: {0})',
  connectedServiceNotStarted: 'Connected, Service Not Started',
  serviceNotConnected: 'Service Not Connected',
  detecting: 'Detecting...',
  lightweightModel: 'Lightweight Multilingual Model',
  betterThanSmall: 'Slightly larger than e5-small, but better performance',
  multilingualModel: 'Multilingual Semantic Model',
  fast: 'Fast',
  balanced: 'Balanced',
  accurate: 'Accurate',
  semanticEngineReady: 'Semantic Engine Ready',
  semanticEngineInitializing: 'Semantic Engine Initializing...',
  semanticEngineInitFailed: 'Semantic Engine Initialization Failed',
  semanticEngineNotInit: 'Semantic Engine Not Initialized',
  downloadingModel: 'Downloading Model... {0}%',
  switchingModel: 'Switching Model...',
  networkError: 'Network connection error, please check network and retry',
  modelCorrupted: 'Model file corrupted or incomplete, please retry download',
  unknownError: 'Unknown error, please check if your network can access HuggingFace',
  reinitialize: 'Reinitialize',
  initializing: 'Initializing...',
  initSemanticEngine: 'Initialize Semantic Engine',
  indexedPages: 'Indexed Pages',
  indexSize: 'Index Size',
  activeTabs: 'Active Tabs',
  vectorDocuments: 'Vector Documents',
  confirmClearData: 'Confirm Clear Data',
  clearDataWarning:
    'This operation will clear all indexed webpage content and vector data, including:',
  clearDataIrreversible:
    'This operation is irreversible! After clearing, you need to browse webpages again to rebuild the index.',
  confirmClear: 'Confirm Clear',
  cancel: 'Cancel',
  confirm: 'Confirm',
  processing: 'Processing...',
  modelCacheManagement: 'Model Cache Management',
  cacheSize: 'Cache Size',
  cacheEntries: 'Cache Entries',
  cacheDetails: 'Cache Details',
  noCacheData: 'No cache data',
  loadingCacheInfo: 'Loading cache information...',
  processingCache: 'Processing cache...',
  cleaning: 'Cleaning...',
  cleanExpiredCache: 'Clean Expired Cache',
  clearAllCache: 'Clear All Cache',
  expired: 'Expired',
  bookmarksBar: 'Bookmarks Bar',
};

/**
 * Safe i18n message getter with fallback support
 * @param key Message key
 * @param substitutions Optional substitution values
 * @returns Localized message or fallback
 */
export function getMessage(key: string, substitutions?: string[]): string {
  try {
    // Check if Chrome extension APIs are available
    if (typeof chrome !== 'undefined' && chrome.i18n && chrome.i18n.getMessage) {
      const message = chrome.i18n.getMessage(key, substitutions);
      if (message) {
        return message;
      }
    }
  } catch (error) {
    console.warn(`Failed to get i18n message for key "${key}":`, error);
  }

  // Fallback to English messages
  let fallback = fallbackMessages[key] || key;

  // Handle substitutions in fallback messages
  if (substitutions && substitutions.length > 0) {
    substitutions.forEach((value, index) => {
      fallback = fallback.replace(`{${index}}`, value);
    });
  }

  return fallback;
}

/**
 * Check if Chrome extension i18n APIs are available
 */
export function isI18nAvailable(): boolean {
  try {
    return (
      typeof chrome !== 'undefined' && chrome.i18n && typeof chrome.i18n.getMessage === 'function'
    );
  } catch {
    return false;
  }
}

```

--------------------------------------------------------------------------------
/app/chrome-extension/entrypoints/background/tools/browser/file-upload.ts:
--------------------------------------------------------------------------------

```typescript
import { createErrorResponse, ToolResult } from '@/common/tool-handler';
import { BaseBrowserToolExecutor } from '../base-browser';
import { TOOL_NAMES } from 'chrome-mcp-shared';

interface FileUploadToolParams {
  selector: string; // CSS selector for the file input element
  filePath?: string; // Local file path
  fileUrl?: string; // URL to download file from
  base64Data?: string; // Base64 encoded file data
  fileName?: string; // Optional filename when using base64 or URL
  multiple?: boolean; // Whether to allow multiple files
}

/**
 * Tool for uploading files to web forms using Chrome DevTools Protocol
 * Similar to Playwright's setInputFiles implementation
 */
class FileUploadTool extends BaseBrowserToolExecutor {
  name = TOOL_NAMES.BROWSER.FILE_UPLOAD;
  private activeDebuggers: Map<number, boolean> = new Map();

  constructor() {
    super();
    // Clean up debuggers on tab removal
    chrome.tabs.onRemoved.addListener((tabId) => {
      if (this.activeDebuggers.has(tabId)) {
        this.cleanupDebugger(tabId);
      }
    });
  }

  /**
   * Execute file upload operation using Chrome DevTools Protocol
   */
  async execute(args: FileUploadToolParams): Promise<ToolResult> {
    const { selector, filePath, fileUrl, base64Data, fileName, multiple = false } = args;

    console.log(`Starting file upload operation with options:`, args);

    // Validate input
    if (!selector) {
      return createErrorResponse('Selector is required for file upload');
    }

    if (!filePath && !fileUrl && !base64Data) {
      return createErrorResponse(
        'One of filePath, fileUrl, or base64Data must be provided',
      );
    }

    let tabId: number | undefined;

    try {
      // Get current tab
      const tabs = await chrome.tabs.query({ active: true, currentWindow: true });
      if (!tabs[0]?.id) {
        return createErrorResponse('No active tab found');
      }
      tabId = tabs[0].id;

      // Prepare file paths
      let files: string[] = [];

      if (filePath) {
        // Direct file path provided
        files = [filePath];
      } else if (fileUrl || base64Data) {
        // For URL or base64, we need to use the native messaging host
        // to download or save the file temporarily
        const tempFilePath = await this.prepareFileFromRemote({
          fileUrl,
          base64Data,
          fileName: fileName || 'uploaded-file',
        });
        if (!tempFilePath) {
          return createErrorResponse('Failed to prepare file for upload');
        }
        files = [tempFilePath];
      }

      // Attach debugger to the tab
      await this.attachDebugger(tabId);

      // Enable necessary CDP domains
      await chrome.debugger.sendCommand({ tabId }, 'DOM.enable', {});
      await chrome.debugger.sendCommand({ tabId }, 'Runtime.enable', {});

      // Get the document
      const { root } = await chrome.debugger.sendCommand(
        { tabId },
        'DOM.getDocument',
        { depth: -1, pierce: true },
      ) as { root: { nodeId: number } };

      // Find the file input element using the selector
      const { nodeId } = await chrome.debugger.sendCommand(
        { tabId },
        'DOM.querySelector',
        {
          nodeId: root.nodeId,
          selector: selector,
        },
      ) as { nodeId: number };

      if (!nodeId || nodeId === 0) {
        throw new Error(`Element with selector "${selector}" not found`);
      }

      // Verify it's actually a file input
      const { node } = await chrome.debugger.sendCommand(
        { tabId },
        'DOM.describeNode',
        { nodeId },
      ) as { node: { nodeName: string; attributes?: string[] } };

      if (node.nodeName !== 'INPUT') {
        throw new Error(`Element with selector "${selector}" is not an input element`);
      }

      // Check if it's a file input by looking for type="file" in attributes
      const attributes = node.attributes || [];
      let isFileInput = false;
      for (let i = 0; i < attributes.length; i += 2) {
        if (attributes[i] === 'type' && attributes[i + 1] === 'file') {
          isFileInput = true;
          break;
        }
      }

      if (!isFileInput) {
        throw new Error(`Element with selector "${selector}" is not a file input (type="file")`);
      }

      // Set the files on the input element
      // This is the key CDP command that Playwright and Puppeteer use
      await chrome.debugger.sendCommand(
        { tabId },
        'DOM.setFileInputFiles',
        {
          nodeId: nodeId,
          files: files,
        },
      );

      // Trigger change event to ensure the page reacts to the file upload
      await chrome.debugger.sendCommand(
        { tabId },
        'Runtime.evaluate',
        {
          expression: `
            (function() {
              const element = document.querySelector('${selector.replace(/'/g, "\\'")}');
              if (element) {
                const event = new Event('change', { bubbles: true });
                element.dispatchEvent(event);
                return true;
              }
              return false;
            })()
          `,
        },
      );

      // Clean up debugger
      await this.detachDebugger(tabId);

      return {
        content: [
          {
            type: 'text',
            text: JSON.stringify({
              success: true,
              message: 'File(s) uploaded successfully',
              files: files,
              selector: selector,
              fileCount: files.length,
            }),
          },
        ],
        isError: false,
      };
    } catch (error) {
      console.error('Error in file upload operation:', error);
      
      // Clean up debugger if attached
      if (tabId !== undefined && this.activeDebuggers.has(tabId)) {
        await this.detachDebugger(tabId);
      }

      return createErrorResponse(
        `Error uploading file: ${error instanceof Error ? error.message : String(error)}`,
      );
    }
  }

  /**
   * Attach debugger to a tab
   */
  private async attachDebugger(tabId: number): Promise<void> {
    // Check if debugger is already attached
    const targets = await chrome.debugger.getTargets();
    const existingTarget = targets.find(
      (t) => t.tabId === tabId && t.attached,
    );

    if (existingTarget) {
      if (existingTarget.extensionId === chrome.runtime.id) {
        // Our extension already attached
        console.log('Debugger already attached by this extension');
        return;
      } else {
        throw new Error(
          'Debugger is already attached to this tab by another extension or DevTools',
        );
      }
    }

    // Attach debugger
    await chrome.debugger.attach({ tabId }, '1.3');
    this.activeDebuggers.set(tabId, true);
    console.log(`Debugger attached to tab ${tabId}`);
  }

  /**
   * Detach debugger from a tab
   */
  private async detachDebugger(tabId: number): Promise<void> {
    if (!this.activeDebuggers.has(tabId)) {
      return;
    }

    try {
      await chrome.debugger.detach({ tabId });
      console.log(`Debugger detached from tab ${tabId}`);
    } catch (error) {
      console.warn(`Error detaching debugger from tab ${tabId}:`, error);
    } finally {
      this.activeDebuggers.delete(tabId);
    }
  }

  /**
   * Clean up debugger connection
   */
  private cleanupDebugger(tabId: number): void {
    this.activeDebuggers.delete(tabId);
  }

  /**
   * Prepare file from URL or base64 data using native messaging host
   */
  private async prepareFileFromRemote(options: {
    fileUrl?: string;
    base64Data?: string;
    fileName: string;
  }): Promise<string | null> {
    const { fileUrl, base64Data, fileName } = options;

    return new Promise((resolve) => {
      const requestId = `file-upload-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
      const timeout = setTimeout(() => {
        console.error('File preparation request timed out');
        resolve(null);
      }, 30000); // 30 second timeout

      // Create listener for the response
      const handleMessage = (message: any) => {
        if (message.type === 'file_operation_response' && 
            message.responseToRequestId === requestId) {
          clearTimeout(timeout);
          chrome.runtime.onMessage.removeListener(handleMessage);
          
          if (message.payload?.success && message.payload?.filePath) {
            resolve(message.payload.filePath);
          } else {
            console.error('Native host failed to prepare file:', message.error || message.payload?.error);
            resolve(null);
          }
        }
      };

      // Add listener
      chrome.runtime.onMessage.addListener(handleMessage);

      // Send message to background script to forward to native host
      chrome.runtime.sendMessage({
        type: 'forward_to_native',
        message: {
          type: 'file_operation',
          requestId: requestId,
          payload: {
            action: 'prepareFile',
            fileUrl,
            base64Data,
            fileName,
          },
        },
      }).catch((error) => {
        console.error('Error sending message to background:', error);
        clearTimeout(timeout);
        chrome.runtime.onMessage.removeListener(handleMessage);
        resolve(null);
      });
    });
  }
}

export const fileUploadTool = new FileUploadTool();
```

--------------------------------------------------------------------------------
/app/native-server/src/server/index.ts:
--------------------------------------------------------------------------------

```typescript
import Fastify, { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify';
import cors from '@fastify/cors';
import {
  NATIVE_SERVER_PORT,
  TIMEOUTS,
  SERVER_CONFIG,
  HTTP_STATUS,
  ERROR_MESSAGES,
} from '../constant';
import { NativeMessagingHost } from '../native-messaging-host';
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
import { randomUUID } from 'node:crypto';
import { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js';
import { getMcpServer } from '../mcp/mcp-server';

// Define request body type (if data needs to be retrieved from HTTP requests)
interface ExtensionRequestPayload {
  data?: any; // Data you want to pass to the extension
}

export class Server {
  private fastify: FastifyInstance;
  public isRunning = false; // Changed to public or provide a getter
  private nativeHost: NativeMessagingHost | null = null;
  private transportsMap: Map<string, StreamableHTTPServerTransport | SSEServerTransport> =
    new Map();

  constructor() {
    this.fastify = Fastify({ logger: SERVER_CONFIG.LOGGER_ENABLED });
    this.setupPlugins();
    this.setupRoutes();
  }
  /**
   * Associate NativeMessagingHost instance
   */
  public setNativeHost(nativeHost: NativeMessagingHost): void {
    this.nativeHost = nativeHost;
  }

  private async setupPlugins(): Promise<void> {
    await this.fastify.register(cors, {
      origin: SERVER_CONFIG.CORS_ORIGIN,
    });
  }

  private setupRoutes(): void {
    // for ping
    this.fastify.get(
      '/ask-extension',
      async (request: FastifyRequest<{ Body: ExtensionRequestPayload }>, reply: FastifyReply) => {

        if (!this.nativeHost) {
          return reply
            .status(HTTP_STATUS.INTERNAL_SERVER_ERROR)
            .send({ error: ERROR_MESSAGES.NATIVE_HOST_NOT_AVAILABLE });
        }
        if (!this.isRunning) {
          return reply
            .status(HTTP_STATUS.INTERNAL_SERVER_ERROR)
            .send({ error: ERROR_MESSAGES.SERVER_NOT_RUNNING });
        }

        try {
          // wait from extension message
          const extensionResponse = await this.nativeHost.sendRequestToExtensionAndWait(
            request.query,
            'process_data',
            TIMEOUTS.EXTENSION_REQUEST_TIMEOUT,
          );
          return reply.status(HTTP_STATUS.OK).send({ status: 'success', data: extensionResponse });
        } catch (error: any) {
          if (error.message.includes('timed out')) {
            return reply
              .status(HTTP_STATUS.GATEWAY_TIMEOUT)
              .send({ status: 'error', message: ERROR_MESSAGES.REQUEST_TIMEOUT });
          } else {
            return reply.status(HTTP_STATUS.INTERNAL_SERVER_ERROR).send({
              status: 'error',
              message: `Failed to get response from extension: ${error.message}`,
            });
          }
        }
      },
    );

    // Compatible with SSE
    this.fastify.get('/sse', async (_, reply) => {
      try {
        // Set SSE headers
        reply.raw.writeHead(HTTP_STATUS.OK, {
          'Content-Type': 'text/event-stream',
          'Cache-Control': 'no-cache',
          Connection: 'keep-alive',
        });

        // Create SSE transport
        const transport = new SSEServerTransport('/messages', reply.raw);
        this.transportsMap.set(transport.sessionId, transport);

        reply.raw.on('close', () => {
          this.transportsMap.delete(transport.sessionId);
        });

        const server = getMcpServer();
        await server.connect(transport);

        // Keep connection open
        reply.raw.write(':\n\n');
      } catch (error) {
        if (!reply.sent) {
          reply.code(HTTP_STATUS.INTERNAL_SERVER_ERROR).send(ERROR_MESSAGES.INTERNAL_SERVER_ERROR);
        }
      }
    });

    // Compatible with SSE
    this.fastify.post('/messages', async (req, reply) => {
      try {
        const { sessionId } = req.query as any;
        const transport = this.transportsMap.get(sessionId) as SSEServerTransport;
        if (!sessionId || !transport) {
          reply.code(HTTP_STATUS.BAD_REQUEST).send('No transport found for sessionId');
          return;
        }

        await transport.handlePostMessage(req.raw, reply.raw, req.body);
      } catch (error) {
        if (!reply.sent) {
          reply.code(HTTP_STATUS.INTERNAL_SERVER_ERROR).send(ERROR_MESSAGES.INTERNAL_SERVER_ERROR);
        }
      }
    });

    // POST /mcp: Handle client-to-server messages
    this.fastify.post('/mcp', async (request, reply) => {
      const sessionId = request.headers['mcp-session-id'] as string | undefined;
      let transport: StreamableHTTPServerTransport | undefined = this.transportsMap.get(
        sessionId || '',
      ) as StreamableHTTPServerTransport;
      if (transport) {
        // transport found, do nothing
      } else if (!sessionId && isInitializeRequest(request.body)) {
        const newSessionId = randomUUID(); // Generate session ID
        transport = new StreamableHTTPServerTransport({
          sessionIdGenerator: () => newSessionId, // Use pre-generated ID
          onsessioninitialized: (initializedSessionId) => {
            // Ensure transport instance exists and session ID matches
            if (transport && initializedSessionId === newSessionId) {
              this.transportsMap.set(initializedSessionId, transport);
            }
          },
        });

        transport.onclose = () => {
          if (transport?.sessionId && this.transportsMap.get(transport.sessionId)) {
            this.transportsMap.delete(transport.sessionId);
          }
        };
        await getMcpServer().connect(transport);
      } else {
        reply.code(HTTP_STATUS.BAD_REQUEST).send({ error: ERROR_MESSAGES.INVALID_MCP_REQUEST });
        return;
      }

      try {
        await transport.handleRequest(request.raw, reply.raw, request.body);
      } catch (error) {
        if (!reply.sent) {
          reply
            .code(HTTP_STATUS.INTERNAL_SERVER_ERROR)
            .send({ error: ERROR_MESSAGES.MCP_REQUEST_PROCESSING_ERROR });
        }
      }
    });

    this.fastify.get('/mcp', async (request, reply) => {
      const sessionId = request.headers['mcp-session-id'] as string | undefined;
      const transport = sessionId
        ? (this.transportsMap.get(sessionId) as StreamableHTTPServerTransport)
        : undefined;
      if (!transport) {
        reply.code(HTTP_STATUS.BAD_REQUEST).send({ error: ERROR_MESSAGES.INVALID_SSE_SESSION });
        return;
      }

      reply.raw.setHeader('Content-Type', 'text/event-stream');
      reply.raw.setHeader('Cache-Control', 'no-cache');
      reply.raw.setHeader('Connection', 'keep-alive');
      reply.raw.flushHeaders(); // Ensure headers are sent immediately

      try {
        // transport.handleRequest will take over the response stream
        await transport.handleRequest(request.raw, reply.raw);
        if (!reply.sent) {
          // If transport didn't send anything (unlikely for SSE initial handshake)
          reply.hijack(); // Prevent Fastify from automatically sending response
        }
      } catch (error) {
        if (!reply.raw.writableEnded) {
          reply.raw.end();
        }
      }

      request.socket.on('close', () => {
        request.log.info(`SSE client disconnected for session: ${sessionId}`);
        // transport's onclose should handle its own cleanup
      });
    });

    this.fastify.delete('/mcp', async (request, reply) => {
      const sessionId = request.headers['mcp-session-id'] as string | undefined;
      const transport = sessionId
        ? (this.transportsMap.get(sessionId) as StreamableHTTPServerTransport)
        : undefined;

      if (!transport) {
        reply.code(HTTP_STATUS.BAD_REQUEST).send({ error: ERROR_MESSAGES.INVALID_SESSION_ID });
        return;
      }

      try {
        await transport.handleRequest(request.raw, reply.raw);
        // Assume transport.handleRequest will send response or transport.onclose will cleanup
        if (!reply.sent) {
          reply.code(HTTP_STATUS.NO_CONTENT).send();
        }
      } catch (error) {
        if (!reply.sent) {
          reply
            .code(HTTP_STATUS.INTERNAL_SERVER_ERROR)
            .send({ error: ERROR_MESSAGES.MCP_SESSION_DELETION_ERROR });
        }
      }
    });
  }

  public async start(port = NATIVE_SERVER_PORT, nativeHost: NativeMessagingHost): Promise<void> {
    if (!this.nativeHost) {
      this.nativeHost = nativeHost; // Ensure nativeHost is set
    } else if (this.nativeHost !== nativeHost) {
      this.nativeHost = nativeHost; // Update to the passed instance
    }

    if (this.isRunning) {
      return;
    }

    try {
      await this.fastify.listen({ port, host: SERVER_CONFIG.HOST });
      this.isRunning = true; // Update running status
      // No need to return, Promise resolves void by default
    } catch (err) {
      this.isRunning = false; // Startup failed, reset status
      // Throw error instead of exiting directly, let caller (possibly NativeHost) handle
      throw err; // or return Promise.reject(err);
      // process.exit(1); // Not recommended to exit directly here
    }
  }

  public async stop(): Promise<void> {
    if (!this.isRunning) {
      return;
    }
    // this.nativeHost = null; // Not recommended to nullify here, association relationship may still be needed
    try {
      await this.fastify.close();
      this.isRunning = false; // Update running status
    } catch (err) {
      // Even if closing fails, mark as not running, but log the error
      this.isRunning = false;
      throw err; // Throw error
    }
  }

  public getInstance(): FastifyInstance {
    return this.fastify;
  }
}

const serverInstance = new Server();
export default serverInstance;

```

--------------------------------------------------------------------------------
/docs/TOOLS.md:
--------------------------------------------------------------------------------

```markdown
# Chrome MCP Server API Reference 📚

Complete reference for all available tools and their parameters.

## 📋 Table of Contents

- [Browser Management](#browser-management)
- [Screenshots & Visual](#screenshots--visual)
- [Network Monitoring](#network-monitoring)
- [Content Analysis](#content-analysis)
- [Interaction](#interaction)
- [Data Management](#data-management)
- [Response Format](#response-format)

## 📊 Browser Management

### `get_windows_and_tabs`

List all currently open browser windows and tabs.

**Parameters**: None

**Response**:

```json
{
  "windowCount": 2,
  "tabCount": 5,
  "windows": [
    {
      "windowId": 123,
      "tabs": [
        {
          "tabId": 456,
          "url": "https://example.com",
          "title": "Example Page",
          "active": true
        }
      ]
    }
  ]
}
```

### `chrome_navigate`

Navigate to a URL with optional viewport control.

**Parameters**:

- `url` (string, required): URL to navigate to
- `newWindow` (boolean, optional): Create new window (default: false)
- `width` (number, optional): Viewport width in pixels (default: 1280)
- `height` (number, optional): Viewport height in pixels (default: 720)

**Example**:

```json
{
  "url": "https://example.com",
  "newWindow": true,
  "width": 1920,
  "height": 1080
}
```

### `chrome_close_tabs`

Close specific tabs or windows.

**Parameters**:

- `tabIds` (array, optional): Array of tab IDs to close
- `windowIds` (array, optional): Array of window IDs to close

**Example**:

```json
{
  "tabIds": [123, 456],
  "windowIds": [789]
}
```

### `chrome_switch_tab`

Switch to a specific browser tab.

**Parameters**:

- `tabId` (number, required): The ID of the tab to switch to.
- `windowId` (number, optional): The ID of the window where the tab is located.

**Example**:

```json
{
  "tabId": 456,
  "windowId": 123
}
```

### `chrome_go_back_or_forward`

Navigate browser history.

**Parameters**:

- `direction` (string, required): "back" or "forward"
- `tabId` (number, optional): Specific tab ID (default: active tab)

**Example**:

```json
{
  "direction": "back",
  "tabId": 123
}
```

## 📸 Screenshots & Visual

### `chrome_screenshot`

Take advanced screenshots with various options.

**Parameters**:

- `name` (string, optional): Screenshot filename
- `selector` (string, optional): CSS selector for element screenshot
- `width` (number, optional): Width in pixels (default: 800)
- `height` (number, optional): Height in pixels (default: 600)
- `storeBase64` (boolean, optional): Return base64 data (default: false)
- `fullPage` (boolean, optional): Capture full page (default: true)

**Example**:

```json
{
  "selector": ".main-content",
  "fullPage": true,
  "storeBase64": true,
  "width": 1920,
  "height": 1080
}
```

**Response**:

```json
{
  "success": true,
  "base64": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...",
  "dimensions": {
    "width": 1920,
    "height": 1080
  }
}
```

## 🌐 Network Monitoring

### `chrome_network_capture_start`

Start capturing network requests using webRequest API.

**Parameters**:

- `url` (string, optional): URL to navigate to and capture
- `maxCaptureTime` (number, optional): Maximum capture time in ms (default: 30000)
- `inactivityTimeout` (number, optional): Stop after inactivity in ms (default: 3000)
- `includeStatic` (boolean, optional): Include static resources (default: false)

**Example**:

```json
{
  "url": "https://api.example.com",
  "maxCaptureTime": 60000,
  "includeStatic": false
}
```

### `chrome_network_capture_stop`

Stop network capture and return collected data.

**Parameters**: None

**Response**:

```json
{
  "success": true,
  "capturedRequests": [
    {
      "url": "https://api.example.com/data",
      "method": "GET",
      "status": 200,
      "requestHeaders": {...},
      "responseHeaders": {...},
      "responseTime": 150
    }
  ],
  "summary": {
    "totalRequests": 15,
    "captureTime": 5000
  }
}
```

### `chrome_network_debugger_start`

Start capturing with Chrome Debugger API (includes response bodies).

**Parameters**:

- `url` (string, optional): URL to navigate to and capture

### `chrome_network_debugger_stop`

Stop debugger capture and return data with response bodies.

### `chrome_network_request`

Send custom HTTP requests.

**Parameters**:

- `url` (string, required): Request URL
- `method` (string, optional): HTTP method (default: "GET")
- `headers` (object, optional): Request headers
- `body` (string, optional): Request body

**Example**:

```json
{
  "url": "https://api.example.com/data",
  "method": "POST",
  "headers": {
    "Content-Type": "application/json"
  },
  "body": "{\"key\": \"value\"}"
}
```

## 🔍 Content Analysis

### `search_tabs_content`

AI-powered semantic search across browser tabs.

**Parameters**:

- `query` (string, required): Search query

**Example**:

```json
{
  "query": "machine learning tutorials"
}
```

**Response**:

```json
{
  "success": true,
  "totalTabsSearched": 10,
  "matchedTabsCount": 3,
  "vectorSearchEnabled": true,
  "indexStats": {
    "totalDocuments": 150,
    "totalTabs": 10,
    "semanticEngineReady": true
  },
  "matchedTabs": [
    {
      "tabId": 123,
      "url": "https://example.com/ml-tutorial",
      "title": "Machine Learning Tutorial",
      "semanticScore": 0.85,
      "matchedSnippets": ["Introduction to machine learning..."],
      "chunkSource": "content"
    }
  ]
}
```

### `chrome_get_web_content`

Extract HTML or text content from web pages.

**Parameters**:

- `format` (string, optional): "html" or "text" (default: "text")
- `selector` (string, optional): CSS selector for specific elements
- `tabId` (number, optional): Specific tab ID (default: active tab)

**Example**:

```json
{
  "format": "text",
  "selector": ".article-content"
}
```

### `chrome_get_interactive_elements`

Find clickable and interactive elements on the page.

**Parameters**:

- `tabId` (number, optional): Specific tab ID (default: active tab)

**Response**:

```json
{
  "elements": [
    {
      "selector": "#submit-button",
      "type": "button",
      "text": "Submit",
      "visible": true,
      "clickable": true
    }
  ]
}
```

## 🎯 Interaction

### `chrome_click_element`

Click elements using CSS selectors.

**Parameters**:

- `selector` (string, required): CSS selector for target element
- `tabId` (number, optional): Specific tab ID (default: active tab)

**Example**:

```json
{
  "selector": "#submit-button"
}
```

### `chrome_fill_or_select`

Fill form fields or select options.

**Parameters**:

- `selector` (string, required): CSS selector for target element
- `value` (string, required): Value to fill or select
- `tabId` (number, optional): Specific tab ID (default: active tab)

**Example**:

```json
{
  "selector": "#email-input",
  "value": "[email protected]"
}
```

### `chrome_keyboard`

Simulate keyboard input and shortcuts.

**Parameters**:

- `keys` (string, required): Key combination (e.g., "Ctrl+C", "Enter")
- `selector` (string, optional): Target element selector
- `delay` (number, optional): Delay between keystrokes in ms (default: 0)

**Example**:

```json
{
  "keys": "Ctrl+A",
  "selector": "#text-input",
  "delay": 100
}
```

## 📚 Data Management

### `chrome_history`

Search browser history with filters.

**Parameters**:

- `text` (string, optional): Search text in URL/title
- `startTime` (string, optional): Start date (ISO format)
- `endTime` (string, optional): End date (ISO format)
- `maxResults` (number, optional): Maximum results (default: 100)
- `excludeCurrentTabs` (boolean, optional): Exclude current tabs (default: true)

**Example**:

```json
{
  "text": "github",
  "startTime": "2024-01-01",
  "maxResults": 50
}
```

### `chrome_bookmark_search`

Search bookmarks by keywords.

**Parameters**:

- `query` (string, optional): Search keywords
- `maxResults` (number, optional): Maximum results (default: 100)
- `folderPath` (string, optional): Search within specific folder

**Example**:

```json
{
  "query": "documentation",
  "maxResults": 20,
  "folderPath": "Work/Resources"
}
```

### `chrome_bookmark_add`

Add new bookmarks with folder support.

**Parameters**:

- `url` (string, optional): URL to bookmark (default: current tab)
- `title` (string, optional): Bookmark title (default: page title)
- `parentId` (string, optional): Parent folder ID or path
- `createFolder` (boolean, optional): Create folder if not exists (default: false)

**Example**:

```json
{
  "url": "https://example.com",
  "title": "Example Site",
  "parentId": "Work/Resources",
  "createFolder": true
}
```

### `chrome_bookmark_delete`

Delete bookmarks by ID or URL.

**Parameters**:

- `bookmarkId` (string, optional): Bookmark ID to delete
- `url` (string, optional): URL to find and delete

**Example**:

```json
{
  "url": "https://example.com"
}
```

## 📋 Response Format

All tools return responses in the following format:

```json
{
  "content": [
    {
      "type": "text",
      "text": "JSON string containing the actual response data"
    }
  ],
  "isError": false
}
```

For errors:

```json
{
  "content": [
    {
      "type": "text",
      "text": "Error message describing what went wrong"
    }
  ],
  "isError": true
}
```

## 🔧 Usage Examples

### Complete Workflow Example

```javascript
// 1. Navigate to a page
await callTool('chrome_navigate', {
  url: 'https://example.com',
});

// 2. Take a screenshot
const screenshot = await callTool('chrome_screenshot', {
  fullPage: true,
  storeBase64: true,
});

// 3. Start network monitoring
await callTool('chrome_network_capture_start', {
  maxCaptureTime: 30000,
});

// 4. Interact with the page
await callTool('chrome_click_element', {
  selector: '#load-data-button',
});

// 5. Search content semantically
const searchResults = await callTool('search_tabs_content', {
  query: 'user data analysis',
});

// 6. Stop network capture
const networkData = await callTool('chrome_network_capture_stop');

// 7. Save bookmark
await callTool('chrome_bookmark_add', {
  title: 'Data Analysis Page',
  parentId: 'Work/Analytics',
});
```

This API provides comprehensive browser automation capabilities with AI-enhanced content analysis and semantic search features.

```

--------------------------------------------------------------------------------
/app/chrome-extension/workers/simd_math.js:
--------------------------------------------------------------------------------

```javascript
let wasm;

const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } );

if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); };

let cachedUint8ArrayMemory0 = null;

function getUint8ArrayMemory0() {
    if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) {
        cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer);
    }
    return cachedUint8ArrayMemory0;
}

function getStringFromWasm0(ptr, len) {
    ptr = ptr >>> 0;
    return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len));
}

let WASM_VECTOR_LEN = 0;

const cachedTextEncoder = (typeof TextEncoder !== 'undefined' ? new TextEncoder('utf-8') : { encode: () => { throw Error('TextEncoder not available') } } );

const encodeString = (typeof cachedTextEncoder.encodeInto === 'function'
    ? function (arg, view) {
    return cachedTextEncoder.encodeInto(arg, view);
}
    : function (arg, view) {
    const buf = cachedTextEncoder.encode(arg);
    view.set(buf);
    return {
        read: arg.length,
        written: buf.length
    };
});

function passStringToWasm0(arg, malloc, realloc) {

    if (realloc === undefined) {
        const buf = cachedTextEncoder.encode(arg);
        const ptr = malloc(buf.length, 1) >>> 0;
        getUint8ArrayMemory0().subarray(ptr, ptr + buf.length).set(buf);
        WASM_VECTOR_LEN = buf.length;
        return ptr;
    }

    let len = arg.length;
    let ptr = malloc(len, 1) >>> 0;

    const mem = getUint8ArrayMemory0();

    let offset = 0;

    for (; offset < len; offset++) {
        const code = arg.charCodeAt(offset);
        if (code > 0x7F) break;
        mem[ptr + offset] = code;
    }

    if (offset !== len) {
        if (offset !== 0) {
            arg = arg.slice(offset);
        }
        ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0;
        const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len);
        const ret = encodeString(arg, view);

        offset += ret.written;
        ptr = realloc(ptr, len, offset, 1) >>> 0;
    }

    WASM_VECTOR_LEN = offset;
    return ptr;
}

let cachedDataViewMemory0 = null;

function getDataViewMemory0() {
    if (cachedDataViewMemory0 === null || cachedDataViewMemory0.buffer.detached === true || (cachedDataViewMemory0.buffer.detached === undefined && cachedDataViewMemory0.buffer !== wasm.memory.buffer)) {
        cachedDataViewMemory0 = new DataView(wasm.memory.buffer);
    }
    return cachedDataViewMemory0;
}

export function main() {
    wasm.main();
}

let cachedFloat32ArrayMemory0 = null;

function getFloat32ArrayMemory0() {
    if (cachedFloat32ArrayMemory0 === null || cachedFloat32ArrayMemory0.byteLength === 0) {
        cachedFloat32ArrayMemory0 = new Float32Array(wasm.memory.buffer);
    }
    return cachedFloat32ArrayMemory0;
}

function passArrayF32ToWasm0(arg, malloc) {
    const ptr = malloc(arg.length * 4, 4) >>> 0;
    getFloat32ArrayMemory0().set(arg, ptr / 4);
    WASM_VECTOR_LEN = arg.length;
    return ptr;
}

function getArrayF32FromWasm0(ptr, len) {
    ptr = ptr >>> 0;
    return getFloat32ArrayMemory0().subarray(ptr / 4, ptr / 4 + len);
}

const SIMDMathFinalization = (typeof FinalizationRegistry === 'undefined')
    ? { register: () => {}, unregister: () => {} }
    : new FinalizationRegistry(ptr => wasm.__wbg_simdmath_free(ptr >>> 0, 1));

export class SIMDMath {

    __destroy_into_raw() {
        const ptr = this.__wbg_ptr;
        this.__wbg_ptr = 0;
        SIMDMathFinalization.unregister(this);
        return ptr;
    }

    free() {
        const ptr = this.__destroy_into_raw();
        wasm.__wbg_simdmath_free(ptr, 0);
    }
    constructor() {
        const ret = wasm.simdmath_new();
        this.__wbg_ptr = ret >>> 0;
        SIMDMathFinalization.register(this, this.__wbg_ptr, this);
        return this;
    }
    /**
     * @param {Float32Array} vec_a
     * @param {Float32Array} vec_b
     * @returns {number}
     */
    cosine_similarity(vec_a, vec_b) {
        const ptr0 = passArrayF32ToWasm0(vec_a, wasm.__wbindgen_malloc);
        const len0 = WASM_VECTOR_LEN;
        const ptr1 = passArrayF32ToWasm0(vec_b, wasm.__wbindgen_malloc);
        const len1 = WASM_VECTOR_LEN;
        const ret = wasm.simdmath_cosine_similarity(this.__wbg_ptr, ptr0, len0, ptr1, len1);
        return ret;
    }
    /**
     * @param {Float32Array} vectors
     * @param {Float32Array} query
     * @param {number} vector_dim
     * @returns {Float32Array}
     */
    batch_similarity(vectors, query, vector_dim) {
        const ptr0 = passArrayF32ToWasm0(vectors, wasm.__wbindgen_malloc);
        const len0 = WASM_VECTOR_LEN;
        const ptr1 = passArrayF32ToWasm0(query, wasm.__wbindgen_malloc);
        const len1 = WASM_VECTOR_LEN;
        const ret = wasm.simdmath_batch_similarity(this.__wbg_ptr, ptr0, len0, ptr1, len1, vector_dim);
        var v3 = getArrayF32FromWasm0(ret[0], ret[1]).slice();
        wasm.__wbindgen_free(ret[0], ret[1] * 4, 4);
        return v3;
    }
    /**
     * @param {Float32Array} vectors_a
     * @param {Float32Array} vectors_b
     * @param {number} vector_dim
     * @returns {Float32Array}
     */
    similarity_matrix(vectors_a, vectors_b, vector_dim) {
        const ptr0 = passArrayF32ToWasm0(vectors_a, wasm.__wbindgen_malloc);
        const len0 = WASM_VECTOR_LEN;
        const ptr1 = passArrayF32ToWasm0(vectors_b, wasm.__wbindgen_malloc);
        const len1 = WASM_VECTOR_LEN;
        const ret = wasm.simdmath_similarity_matrix(this.__wbg_ptr, ptr0, len0, ptr1, len1, vector_dim);
        var v3 = getArrayF32FromWasm0(ret[0], ret[1]).slice();
        wasm.__wbindgen_free(ret[0], ret[1] * 4, 4);
        return v3;
    }
}

async function __wbg_load(module, imports) {
    if (typeof Response === 'function' && module instanceof Response) {
        if (typeof WebAssembly.instantiateStreaming === 'function') {
            try {
                return await WebAssembly.instantiateStreaming(module, imports);

            } catch (e) {
                if (module.headers.get('Content-Type') != 'application/wasm') {
                    console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve Wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e);

                } else {
                    throw e;
                }
            }
        }

        const bytes = await module.arrayBuffer();
        return await WebAssembly.instantiate(bytes, imports);

    } else {
        const instance = await WebAssembly.instantiate(module, imports);

        if (instance instanceof WebAssembly.Instance) {
            return { instance, module };

        } else {
            return instance;
        }
    }
}

function __wbg_get_imports() {
    const imports = {};
    imports.wbg = {};
    imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function(arg0, arg1) {
        let deferred0_0;
        let deferred0_1;
        try {
            deferred0_0 = arg0;
            deferred0_1 = arg1;
            console.error(getStringFromWasm0(arg0, arg1));
        } finally {
            wasm.__wbindgen_free(deferred0_0, deferred0_1, 1);
        }
    };
    imports.wbg.__wbg_new_8a6f238a6ece86ea = function() {
        const ret = new Error();
        return ret;
    };
    imports.wbg.__wbg_stack_0ed75d68575b0f3c = function(arg0, arg1) {
        const ret = arg1.stack;
        const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
        const len1 = WASM_VECTOR_LEN;
        getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
        getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
    };
    imports.wbg.__wbindgen_init_externref_table = function() {
        const table = wasm.__wbindgen_export_3;
        const offset = table.grow(4);
        table.set(0, undefined);
        table.set(offset + 0, undefined);
        table.set(offset + 1, null);
        table.set(offset + 2, true);
        table.set(offset + 3, false);
        ;
    };
    imports.wbg.__wbindgen_throw = function(arg0, arg1) {
        throw new Error(getStringFromWasm0(arg0, arg1));
    };

    return imports;
}

function __wbg_init_memory(imports, memory) {

}

function __wbg_finalize_init(instance, module) {
    wasm = instance.exports;
    __wbg_init.__wbindgen_wasm_module = module;
    cachedDataViewMemory0 = null;
    cachedFloat32ArrayMemory0 = null;
    cachedUint8ArrayMemory0 = null;


    wasm.__wbindgen_start();
    return wasm;
}

function initSync(module) {
    if (wasm !== undefined) return wasm;


    if (typeof module !== 'undefined') {
        if (Object.getPrototypeOf(module) === Object.prototype) {
            ({module} = module)
        } else {
            console.warn('using deprecated parameters for `initSync()`; pass a single object instead')
        }
    }

    const imports = __wbg_get_imports();

    __wbg_init_memory(imports);

    if (!(module instanceof WebAssembly.Module)) {
        module = new WebAssembly.Module(module);
    }

    const instance = new WebAssembly.Instance(module, imports);

    return __wbg_finalize_init(instance, module);
}

async function __wbg_init(module_or_path) {
    if (wasm !== undefined) return wasm;


    if (typeof module_or_path !== 'undefined') {
        if (Object.getPrototypeOf(module_or_path) === Object.prototype) {
            ({module_or_path} = module_or_path)
        } else {
            console.warn('using deprecated parameters for the initialization function; pass a single object instead')
        }
    }

    if (typeof module_or_path === 'undefined') {
        module_or_path = new URL('simd_math_bg.wasm', import.meta.url);
    }
    const imports = __wbg_get_imports();

    if (typeof module_or_path === 'string' || (typeof Request === 'function' && module_or_path instanceof Request) || (typeof URL === 'function' && module_or_path instanceof URL)) {
        module_or_path = fetch(module_or_path);
    }

    __wbg_init_memory(imports);

    const { instance, module } = await __wbg_load(await module_or_path, imports);

    return __wbg_finalize_init(instance, module);
}

export { initSync };
export default __wbg_init;

```

--------------------------------------------------------------------------------
/app/chrome-extension/entrypoints/background/tools/browser/console.ts:
--------------------------------------------------------------------------------

```typescript
import { createErrorResponse, ToolResult } from '@/common/tool-handler';
import { BaseBrowserToolExecutor } from '../base-browser';
import { TOOL_NAMES } from 'chrome-mcp-shared';

const DEBUGGER_PROTOCOL_VERSION = '1.3';
const DEFAULT_MAX_MESSAGES = 100;

interface ConsoleToolParams {
  url?: string;
  includeExceptions?: boolean;
  maxMessages?: number;
}

interface ConsoleMessage {
  timestamp: number;
  level: string;
  text: string;
  args?: any[];
  source?: string;
  url?: string;
  lineNumber?: number;
  stackTrace?: any;
}

interface ConsoleException {
  timestamp: number;
  text: string;
  url?: string;
  lineNumber?: number;
  columnNumber?: number;
  stackTrace?: any;
}

interface ConsoleResult {
  success: boolean;
  message: string;
  tabId: number;
  tabUrl: string;
  tabTitle: string;
  captureStartTime: number;
  captureEndTime: number;
  totalDurationMs: number;
  messages: ConsoleMessage[];
  exceptions: ConsoleException[];
  messageCount: number;
  exceptionCount: number;
  messageLimitReached: boolean;
}

/**
 * Tool for capturing console output from browser tabs
 */
class ConsoleTool extends BaseBrowserToolExecutor {
  name = TOOL_NAMES.BROWSER.CONSOLE;

  async execute(args: ConsoleToolParams): Promise<ToolResult> {
    const { url, includeExceptions = true, maxMessages = DEFAULT_MAX_MESSAGES } = args;

    let targetTab: chrome.tabs.Tab;

    try {
      if (url) {
        // Navigate to the specified URL
        targetTab = await this.navigateToUrl(url);
      } else {
        // Use current active tab
        const [activeTab] = await chrome.tabs.query({ active: true, currentWindow: true });
        if (!activeTab?.id) {
          return createErrorResponse('No active tab found and no URL provided.');
        }
        targetTab = activeTab;
      }

      if (!targetTab?.id) {
        return createErrorResponse('Failed to identify target tab.');
      }

      const tabId = targetTab.id;

      // Capture console messages (one-time capture)
      const result = await this.captureConsoleMessages(tabId, {
        includeExceptions,
        maxMessages,
      });

      return {
        content: [
          {
            type: 'text',
            text: JSON.stringify(result),
          },
        ],
        isError: false,
      };
    } catch (error: any) {
      console.error('ConsoleTool: Critical error during execute:', error);
      return createErrorResponse(`Error in ConsoleTool: ${error.message || String(error)}`);
    }
  }

  private async navigateToUrl(url: string): Promise<chrome.tabs.Tab> {
    // Check if URL is already open
    const existingTabs = await chrome.tabs.query({ url });

    if (existingTabs.length > 0 && existingTabs[0]?.id) {
      const tab = existingTabs[0];
      // Activate the existing tab
      await chrome.tabs.update(tab.id!, { active: true });
      await chrome.windows.update(tab.windowId, { focused: true });
      return tab;
    } else {
      // Create new tab with the URL
      const newTab = await chrome.tabs.create({ url, active: true });
      // Wait for tab to be ready
      await this.waitForTabReady(newTab.id!);
      return newTab;
    }
  }

  private async waitForTabReady(tabId: number): Promise<void> {
    return new Promise((resolve) => {
      const checkTab = async () => {
        try {
          const tab = await chrome.tabs.get(tabId);
          if (tab.status === 'complete') {
            resolve();
          } else {
            setTimeout(checkTab, 100);
          }
        } catch (error) {
          // Tab might be closed, resolve anyway
          resolve();
        }
      };
      checkTab();
    });
  }

  private formatConsoleArgs(args: any[]): string {
    if (!args || args.length === 0) return '';

    return args
      .map((arg) => {
        if (arg.type === 'string') {
          return arg.value || '';
        } else if (arg.type === 'number') {
          return String(arg.value || '');
        } else if (arg.type === 'boolean') {
          return String(arg.value || '');
        } else if (arg.type === 'object') {
          return arg.description || '[Object]';
        } else if (arg.type === 'undefined') {
          return 'undefined';
        } else if (arg.type === 'function') {
          return arg.description || '[Function]';
        } else {
          return arg.description || arg.value || String(arg);
        }
      })
      .join(' ');
  }

  private async captureConsoleMessages(
    tabId: number,
    options: {
      includeExceptions: boolean;
      maxMessages: number;
    },
  ): Promise<ConsoleResult> {
    const { includeExceptions, maxMessages } = options;
    const startTime = Date.now();
    const messages: ConsoleMessage[] = [];
    const exceptions: ConsoleException[] = [];
    let limitReached = false;

    try {
      // Get tab information
      const tab = await chrome.tabs.get(tabId);

      // Check if debugger is already attached
      const targets = await chrome.debugger.getTargets();
      const existingTarget = targets.find(
        (t) => t.tabId === tabId && t.attached && t.type === 'page',
      );
      if (existingTarget && !existingTarget.extensionId) {
        throw new Error(
          `Debugger is already attached to tab ${tabId} by another tool (e.g., DevTools).`,
        );
      }

      // Attach debugger
      try {
        await chrome.debugger.attach({ tabId }, DEBUGGER_PROTOCOL_VERSION);
      } catch (error: any) {
        if (error.message?.includes('Cannot attach to the target with an attached client')) {
          throw new Error(
            `Debugger is already attached to tab ${tabId}. This might be DevTools or another extension.`,
          );
        }
        throw error;
      }

      // Set up event listener to collect messages
      const collectedMessages: any[] = [];
      const collectedExceptions: any[] = [];

      const eventListener = (source: chrome.debugger.Debuggee, method: string, params?: any) => {
        if (source.tabId !== tabId) return;

        if (method === 'Log.entryAdded' && params?.entry) {
          collectedMessages.push(params.entry);
        } else if (method === 'Runtime.consoleAPICalled' && params) {
          // Convert Runtime.consoleAPICalled to Log.entryAdded format
          const logEntry = {
            timestamp: params.timestamp,
            level: params.type || 'log',
            text: this.formatConsoleArgs(params.args || []),
            source: 'console-api',
            url: params.stackTrace?.callFrames?.[0]?.url,
            lineNumber: params.stackTrace?.callFrames?.[0]?.lineNumber,
            stackTrace: params.stackTrace,
            args: params.args,
          };
          collectedMessages.push(logEntry);
        } else if (
          method === 'Runtime.exceptionThrown' &&
          includeExceptions &&
          params?.exceptionDetails
        ) {
          collectedExceptions.push(params.exceptionDetails);
        }
      };

      chrome.debugger.onEvent.addListener(eventListener);

      try {
        // Enable Runtime domain first to capture console API calls and exceptions
        await chrome.debugger.sendCommand({ tabId }, 'Runtime.enable');

        // Also enable Log domain to capture other log entries
        await chrome.debugger.sendCommand({ tabId }, 'Log.enable');

        // Wait for all messages to be flushed
        await new Promise((resolve) => setTimeout(resolve, 2000));

        // Process collected messages
        for (const entry of collectedMessages) {
          if (messages.length >= maxMessages) {
            limitReached = true;
            break;
          }

          const message: ConsoleMessage = {
            timestamp: entry.timestamp,
            level: entry.level || 'log',
            text: entry.text || '',
            source: entry.source,
            url: entry.url,
            lineNumber: entry.lineNumber,
          };

          if (entry.stackTrace) {
            message.stackTrace = entry.stackTrace;
          }

          if (entry.args && Array.isArray(entry.args)) {
            message.args = entry.args;
          }

          messages.push(message);
        }

        // Process collected exceptions
        for (const exceptionDetails of collectedExceptions) {
          const exception: ConsoleException = {
            timestamp: Date.now(),
            text:
              exceptionDetails.text ||
              exceptionDetails.exception?.description ||
              'Unknown exception',
            url: exceptionDetails.url,
            lineNumber: exceptionDetails.lineNumber,
            columnNumber: exceptionDetails.columnNumber,
          };

          if (exceptionDetails.stackTrace) {
            exception.stackTrace = exceptionDetails.stackTrace;
          }

          exceptions.push(exception);
        }
      } finally {
        // Clean up
        chrome.debugger.onEvent.removeListener(eventListener);

        try {
          await chrome.debugger.sendCommand({ tabId }, 'Runtime.disable');
        } catch (e) {
          console.warn(`ConsoleTool: Error disabling Runtime for tab ${tabId}:`, e);
        }

        try {
          await chrome.debugger.sendCommand({ tabId }, 'Log.disable');
        } catch (e) {
          console.warn(`ConsoleTool: Error disabling Log for tab ${tabId}:`, e);
        }

        try {
          await chrome.debugger.detach({ tabId });
        } catch (e) {
          console.warn(`ConsoleTool: Error detaching debugger for tab ${tabId}:`, e);
        }
      }

      const endTime = Date.now();

      // Sort messages by timestamp
      messages.sort((a, b) => a.timestamp - b.timestamp);
      exceptions.sort((a, b) => a.timestamp - b.timestamp);

      return {
        success: true,
        message: `Console capture completed for tab ${tabId}. ${messages.length} messages, ${exceptions.length} exceptions captured.`,
        tabId,
        tabUrl: tab.url || '',
        tabTitle: tab.title || '',
        captureStartTime: startTime,
        captureEndTime: endTime,
        totalDurationMs: endTime - startTime,
        messages,
        exceptions,
        messageCount: messages.length,
        exceptionCount: exceptions.length,
        messageLimitReached: limitReached,
      };
    } catch (error: any) {
      console.error(`ConsoleTool: Error capturing console messages for tab ${tabId}:`, error);
      throw error;
    }
  }
}

export const consoleTool = new ConsoleTool();

```

--------------------------------------------------------------------------------
/app/chrome-extension/utils/model-cache-manager.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Model Cache Manager
 */

const CACHE_NAME = 'onnx-model-cache-v1';
const CACHE_EXPIRY_DAYS = 30;
const MAX_CACHE_SIZE_MB = 500;

export interface CacheMetadata {
  timestamp: number;
  modelUrl: string;
  size: number;
  version: string;
}

export interface CacheEntry {
  url: string;
  size: number;
  sizeMB: number;
  timestamp: number;
  age: string;
  expired: boolean;
}

export interface CacheStats {
  totalSize: number;
  totalSizeMB: number;
  entryCount: number;
  entries: CacheEntry[];
}

interface CacheEntryDetails {
  url: string;
  timestamp: number;
  size: number;
}

export class ModelCacheManager {
  private static instance: ModelCacheManager | null = null;

  public static getInstance(): ModelCacheManager {
    if (!ModelCacheManager.instance) {
      ModelCacheManager.instance = new ModelCacheManager();
    }
    return ModelCacheManager.instance;
  }

  private constructor() {}

  private getCacheMetadataKey(modelUrl: string): string {
    const encodedUrl = encodeURIComponent(modelUrl);
    return `https://cache-metadata.local/${encodedUrl}`;
  }

  private isCacheExpired(metadata: CacheMetadata): boolean {
    const now = Date.now();
    const expiryTime = metadata.timestamp + CACHE_EXPIRY_DAYS * 24 * 60 * 60 * 1000;
    return now > expiryTime;
  }

  private isMetadataUrl(url: string): boolean {
    return url.startsWith('https://cache-metadata.local/');
  }

  private async collectCacheEntries(): Promise<{
    entries: CacheEntryDetails[];
    totalSize: number;
    entryCount: number;
  }> {
    const cache = await caches.open(CACHE_NAME);
    const keys = await cache.keys();
    const entries: CacheEntryDetails[] = [];
    let totalSize = 0;
    let entryCount = 0;

    for (const request of keys) {
      if (this.isMetadataUrl(request.url)) continue;

      const response = await cache.match(request);
      if (response) {
        const blob = await response.blob();
        const size = blob.size;
        totalSize += size;
        entryCount++;

        const metadataResponse = await cache.match(this.getCacheMetadataKey(request.url));
        let timestamp = 0;

        if (metadataResponse) {
          try {
            const metadata: CacheMetadata = await metadataResponse.json();
            timestamp = metadata.timestamp;
          } catch (error) {
            console.warn('Failed to parse cache metadata:', error);
          }
        }

        entries.push({
          url: request.url,
          timestamp,
          size,
        });
      }
    }

    return { entries, totalSize, entryCount };
  }

  public async cleanupCacheOnDemand(newDataSize: number = 0): Promise<void> {
    const cache = await caches.open(CACHE_NAME);
    const { entries, totalSize } = await this.collectCacheEntries();
    const maxSizeBytes = MAX_CACHE_SIZE_MB * 1024 * 1024;
    const projectedSize = totalSize + newDataSize;

    if (projectedSize <= maxSizeBytes) {
      return;
    }

    console.log(
      `Cache size (${(totalSize / 1024 / 1024).toFixed(2)}MB) + new data (${(newDataSize / 1024 / 1024).toFixed(2)}MB) exceeds limit (${MAX_CACHE_SIZE_MB}MB), cleaning up...`,
    );

    const expiredEntries: CacheEntryDetails[] = [];
    const validEntries: CacheEntryDetails[] = [];

    for (const entry of entries) {
      const metadataResponse = await cache.match(this.getCacheMetadataKey(entry.url));
      let isExpired = false;

      if (metadataResponse) {
        try {
          const metadata: CacheMetadata = await metadataResponse.json();
          isExpired = this.isCacheExpired(metadata);
        } catch (error) {
          isExpired = true;
        }
      } else {
        isExpired = true;
      }

      if (isExpired) {
        expiredEntries.push(entry);
      } else {
        validEntries.push(entry);
      }
    }

    let currentSize = totalSize;
    for (const entry of expiredEntries) {
      await cache.delete(entry.url);
      await cache.delete(this.getCacheMetadataKey(entry.url));
      currentSize -= entry.size;
      console.log(
        `Cleaned up expired cache entry: ${entry.url} (${(entry.size / 1024 / 1024).toFixed(2)}MB)`,
      );
    }

    if (currentSize + newDataSize > maxSizeBytes) {
      validEntries.sort((a, b) => a.timestamp - b.timestamp);

      for (const entry of validEntries) {
        if (currentSize + newDataSize <= maxSizeBytes) break;

        await cache.delete(entry.url);
        await cache.delete(this.getCacheMetadataKey(entry.url));
        currentSize -= entry.size;
        console.log(
          `Cleaned up old cache entry: ${entry.url} (${(entry.size / 1024 / 1024).toFixed(2)}MB)`,
        );
      }
    }

    console.log(`Cache cleanup complete. New size: ${(currentSize / 1024 / 1024).toFixed(2)}MB`);
  }

  public async storeCacheMetadata(modelUrl: string, size: number): Promise<void> {
    const cache = await caches.open(CACHE_NAME);
    const metadata: CacheMetadata = {
      timestamp: Date.now(),
      modelUrl,
      size,
      version: CACHE_NAME,
    };

    const metadataResponse = new Response(JSON.stringify(metadata), {
      headers: { 'Content-Type': 'application/json' },
    });

    await cache.put(this.getCacheMetadataKey(modelUrl), metadataResponse);
  }

  public async getCachedModelData(modelUrl: string): Promise<ArrayBuffer | null> {
    const cache = await caches.open(CACHE_NAME);
    const cachedResponse = await cache.match(modelUrl);

    if (!cachedResponse) {
      return null;
    }

    const metadataResponse = await cache.match(this.getCacheMetadataKey(modelUrl));
    if (metadataResponse) {
      try {
        const metadata: CacheMetadata = await metadataResponse.json();
        if (!this.isCacheExpired(metadata)) {
          console.log('Model found in cache and not expired. Loading from cache.');
          return cachedResponse.arrayBuffer();
        } else {
          console.log('Cached model is expired, removing...');
          await this.deleteCacheEntry(modelUrl);
          return null;
        }
      } catch (error) {
        console.warn('Failed to parse cache metadata, treating as expired:', error);
        await this.deleteCacheEntry(modelUrl);
        return null;
      }
    } else {
      console.log('Cached model has no metadata, treating as expired...');
      await this.deleteCacheEntry(modelUrl);
      return null;
    }
  }

  public async storeModelData(modelUrl: string, data: ArrayBuffer): Promise<void> {
    await this.cleanupCacheOnDemand(data.byteLength);

    const cache = await caches.open(CACHE_NAME);
    const response = new Response(data);

    await cache.put(modelUrl, response);
    await this.storeCacheMetadata(modelUrl, data.byteLength);

    console.log(
      `Model cached successfully (${(data.byteLength / 1024 / 1024).toFixed(2)}MB): ${modelUrl}`,
    );
  }

  public async deleteCacheEntry(modelUrl: string): Promise<void> {
    const cache = await caches.open(CACHE_NAME);
    await cache.delete(modelUrl);
    await cache.delete(this.getCacheMetadataKey(modelUrl));
  }

  public async clearAllCache(): Promise<void> {
    const cache = await caches.open(CACHE_NAME);
    const keys = await cache.keys();

    for (const request of keys) {
      await cache.delete(request);
    }

    console.log('All model cache entries cleared');
  }

  public async getCacheStats(): Promise<CacheStats> {
    const { entries, totalSize, entryCount } = await this.collectCacheEntries();
    const cache = await caches.open(CACHE_NAME);

    const cacheEntries: CacheEntry[] = [];

    for (const entry of entries) {
      const metadataResponse = await cache.match(this.getCacheMetadataKey(entry.url));
      let expired = false;

      if (metadataResponse) {
        try {
          const metadata: CacheMetadata = await metadataResponse.json();
          expired = this.isCacheExpired(metadata);
        } catch (error) {
          expired = true;
        }
      } else {
        expired = true;
      }

      const age =
        entry.timestamp > 0
          ? `${Math.round((Date.now() - entry.timestamp) / (1000 * 60 * 60 * 24))} days`
          : 'unknown';

      cacheEntries.push({
        url: entry.url,
        size: entry.size,
        sizeMB: Number((entry.size / 1024 / 1024).toFixed(2)),
        timestamp: entry.timestamp,
        age,
        expired,
      });
    }

    return {
      totalSize,
      totalSizeMB: Number((totalSize / 1024 / 1024).toFixed(2)),
      entryCount,
      entries: cacheEntries.sort((a, b) => b.timestamp - a.timestamp),
    };
  }

  public async manualCleanup(): Promise<void> {
    await this.cleanupCacheOnDemand(0);
    console.log('Manual cache cleanup completed');
  }

  /**
   * Check if a specific model is cached and not expired
   * @param modelUrl The model URL to check
   * @returns Promise<boolean> True if model is cached and valid
   */
  public async isModelCached(modelUrl: string): Promise<boolean> {
    try {
      const cache = await caches.open(CACHE_NAME);
      const cachedResponse = await cache.match(modelUrl);

      if (!cachedResponse) {
        return false;
      }

      const metadataResponse = await cache.match(this.getCacheMetadataKey(modelUrl));
      if (metadataResponse) {
        try {
          const metadata: CacheMetadata = await metadataResponse.json();
          return !this.isCacheExpired(metadata);
        } catch (error) {
          console.warn('Failed to parse cache metadata for cache check:', error);
          return false;
        }
      } else {
        // No metadata means expired
        return false;
      }
    } catch (error) {
      console.error('Error checking model cache:', error);
      return false;
    }
  }

  /**
   * Check if any valid (non-expired) model cache exists
   * @returns Promise<boolean> True if at least one valid model cache exists
   */
  public async hasAnyValidCache(): Promise<boolean> {
    try {
      const cache = await caches.open(CACHE_NAME);
      const keys = await cache.keys();

      for (const request of keys) {
        if (this.isMetadataUrl(request.url)) continue;

        const metadataResponse = await cache.match(this.getCacheMetadataKey(request.url));
        if (metadataResponse) {
          try {
            const metadata: CacheMetadata = await metadataResponse.json();
            if (!this.isCacheExpired(metadata)) {
              return true; // Found at least one valid cache
            }
          } catch (error) {
            // Skip invalid metadata
            continue;
          }
        }
      }

      return false;
    } catch (error) {
      console.error('Error checking for valid cache:', error);
      return false;
    }
  }
}

```

--------------------------------------------------------------------------------
/app/chrome-extension/inject-scripts/keyboard-helper.js:
--------------------------------------------------------------------------------

```javascript
/* eslint-disable */
// keyboard-helper.js
// This script is injected into the page to handle keyboard event simulation

if (window.__KEYBOARD_HELPER_INITIALIZED__) {
  // Already initialized, skip
} else {
  window.__KEYBOARD_HELPER_INITIALIZED__ = true;

  // A map for special keys to their KeyboardEvent properties
  // Key names should be lowercase for matching
  const SPECIAL_KEY_MAP = {
    enter: { key: 'Enter', code: 'Enter', keyCode: 13 },
    tab: { key: 'Tab', code: 'Tab', keyCode: 9 },
    esc: { key: 'Escape', code: 'Escape', keyCode: 27 },
    escape: { key: 'Escape', code: 'Escape', keyCode: 27 },
    space: { key: ' ', code: 'Space', keyCode: 32 },
    backspace: { key: 'Backspace', code: 'Backspace', keyCode: 8 },
    delete: { key: 'Delete', code: 'Delete', keyCode: 46 },
    del: { key: 'Delete', code: 'Delete', keyCode: 46 },
    up: { key: 'ArrowUp', code: 'ArrowUp', keyCode: 38 },
    arrowup: { key: 'ArrowUp', code: 'ArrowUp', keyCode: 38 },
    down: { key: 'ArrowDown', code: 'ArrowDown', keyCode: 40 },
    arrowdown: { key: 'ArrowDown', code: 'ArrowDown', keyCode: 40 },
    left: { key: 'ArrowLeft', code: 'ArrowLeft', keyCode: 37 },
    arrowleft: { key: 'ArrowLeft', code: 'ArrowLeft', keyCode: 37 },
    right: { key: 'ArrowRight', code: 'ArrowRight', keyCode: 39 },
    arrowright: { key: 'ArrowRight', code: 'ArrowRight', keyCode: 39 },
    home: { key: 'Home', code: 'Home', keyCode: 36 },
    end: { key: 'End', code: 'End', keyCode: 35 },
    pageup: { key: 'PageUp', code: 'PageUp', keyCode: 33 },
    pagedown: { key: 'PageDown', code: 'PageDown', keyCode: 34 },
    insert: { key: 'Insert', code: 'Insert', keyCode: 45 },
    // Function keys
    ...Object.fromEntries(
      Array.from({ length: 12 }, (_, i) => [
        `f${i + 1}`,
        { key: `F${i + 1}`, code: `F${i + 1}`, keyCode: 112 + i },
      ]),
    ),
  };

  const MODIFIER_KEYS = {
    ctrl: 'ctrlKey',
    control: 'ctrlKey',
    alt: 'altKey',
    shift: 'shiftKey',
    meta: 'metaKey',
    command: 'metaKey',
    cmd: 'metaKey',
  };

  /**
   * Parses a key string (e.g., "Ctrl+Shift+A", "Enter") into a main key and modifiers.
   * @param {string} keyString - String representation of a single key press (can include modifiers).
   * @returns { {key: string, code: string, keyCode: number, charCode?: number, modifiers: {ctrlKey:boolean, altKey:boolean, shiftKey:boolean, metaKey:boolean}} | null }
   *          Returns null if the keyString is invalid or represents only modifiers.
   */
  function parseSingleKeyCombination(keyString) {
    const parts = keyString.split('+').map((part) => part.trim().toLowerCase());
    const modifiers = {
      ctrlKey: false,
      altKey: false,
      shiftKey: false,
      metaKey: false,
    };
    let mainKeyPart = null;

    for (const part of parts) {
      if (MODIFIER_KEYS[part]) {
        modifiers[MODIFIER_KEYS[part]] = true;
      } else if (mainKeyPart === null) {
        // First non-modifier is the main key
        mainKeyPart = part;
      } else {
        // Invalid format: multiple main keys in a single combination (e.g., "Ctrl+A+B")
        console.error(`Invalid key combination string: ${keyString}. Multiple main keys found.`);
        return null;
      }
    }

    if (!mainKeyPart) {
      // This case could happen if the keyString is something like "Ctrl+" or just "Ctrl"
      // If the intent was to press JUST 'Control', the input should be 'Control' not 'Control+'
      // Let's check if mainKeyPart is actually a modifier name used as a main key
      if (Object.keys(MODIFIER_KEYS).includes(parts[parts.length - 1]) && parts.length === 1) {
        mainKeyPart = parts[parts.length - 1]; // e.g. user wants to press "Control" key itself
        // For "Control" key itself, key: "Control", code: "ControlLeft" (or Right)
        if (mainKeyPart === 'ctrl' || mainKeyPart === 'control')
          return { key: 'Control', code: 'ControlLeft', keyCode: 17, modifiers };
        if (mainKeyPart === 'alt') return { key: 'Alt', code: 'AltLeft', keyCode: 18, modifiers };
        if (mainKeyPart === 'shift')
          return { key: 'Shift', code: 'ShiftLeft', keyCode: 16, modifiers };
        if (mainKeyPart === 'meta' || mainKeyPart === 'command' || mainKeyPart === 'cmd')
          return { key: 'Meta', code: 'MetaLeft', keyCode: 91, modifiers };
      } else {
        console.error(`Invalid key combination string: ${keyString}. No main key specified.`);
        return null;
      }
    }

    const specialKey = SPECIAL_KEY_MAP[mainKeyPart];
    if (specialKey) {
      return { ...specialKey, modifiers };
    }

    // For single characters or other unmapped keys
    if (mainKeyPart.length === 1) {
      const charCode = mainKeyPart.charCodeAt(0);
      // If Shift is active and it's a letter, use the uppercase version for 'key'
      // This mimics more closely how keyboards behave.
      let keyChar = mainKeyPart;
      if (modifiers.shiftKey && mainKeyPart.match(/^[a-z]$/i)) {
        keyChar = mainKeyPart.toUpperCase();
      }

      return {
        key: keyChar,
        code: `Key${mainKeyPart.toUpperCase()}`, // 'a' -> KeyA, 'A' -> KeyA
        keyCode: charCode,
        charCode: charCode, // charCode is legacy, but some old systems might use it
        modifiers,
      };
    }

    console.error(`Unknown key: ${mainKeyPart} in string "${keyString}"`);
    return null; // Or handle as an error
  }

  /**
   * Simulates a single key press (keydown, (keypress), keyup) for a parsed key.
   * @param { {key: string, code: string, keyCode: number, charCode?: number, modifiers: object} } parsedKeyInfo
   * @param {Element} element - Target element.
   * @returns {{success: boolean, error?: string}}
   */
  function dispatchKeyEvents(parsedKeyInfo, element) {
    if (!parsedKeyInfo) return { success: false, error: 'Invalid key info provided for dispatch.' };

    const { key, code, keyCode, charCode, modifiers } = parsedKeyInfo;

    const eventOptions = {
      key: key,
      code: code,
      bubbles: true,
      cancelable: true,
      composed: true, // Important for shadow DOM
      view: window,
      ...modifiers, // ctrlKey, altKey, shiftKey, metaKey
      // keyCode/which are deprecated but often set for compatibility
      keyCode: keyCode || (key.length === 1 ? key.charCodeAt(0) : 0),
      which: keyCode || (key.length === 1 ? key.charCodeAt(0) : 0),
    };

    try {
      const kdRes = element.dispatchEvent(new KeyboardEvent('keydown', eventOptions));

      // keypress is deprecated, but simulate if it's a character key or Enter
      // Only dispatch if keydown was not cancelled and it's a character producing key
      if (kdRes && (key.length === 1 || key === 'Enter' || key === ' ')) {
        const keypressOptions = { ...eventOptions };
        if (charCode) keypressOptions.charCode = charCode;
        element.dispatchEvent(new KeyboardEvent('keypress', keypressOptions));
      }

      element.dispatchEvent(new KeyboardEvent('keyup', eventOptions));
      return { success: true };
    } catch (error) {
      console.error(`Error dispatching key events for "${key}":`, error);
      return {
        success: false,
        error: `Error dispatching key events for "${key}": ${error.message}`,
      };
    }
  }

  /**
   * Simulate keyboard events on an element or document
   * @param {string} keysSequenceString - String representation of key(s) (e.g., "Enter", "Ctrl+C, A, B")
   * @param {Element} targetElement - Element to dispatch events on (optional)
   * @param {number} delay - Delay between key sequences in milliseconds (optional)
   * @returns {Promise<Object>} - Result of the keyboard operation
   */
  async function simulateKeyboard(keysSequenceString, targetElement = null, delay = 0) {
    try {
      const element = targetElement || document.activeElement || document.body;

      if (element !== document.activeElement && typeof element.focus === 'function') {
        element.focus();
        await new Promise((resolve) => setTimeout(resolve, 50)); // Small delay for focus
      }

      const keyCombinations = keysSequenceString
        .split(',')
        .map((k) => k.trim())
        .filter((k) => k.length > 0);
      const operationResults = [];

      for (let i = 0; i < keyCombinations.length; i++) {
        const comboString = keyCombinations[i];
        const parsedKeyInfo = parseSingleKeyCombination(comboString);

        if (!parsedKeyInfo) {
          operationResults.push({
            keyCombination: comboString,
            success: false,
            error: `Invalid key string or combination: ${comboString}`,
          });
          continue; // Skip to next combination in sequence
        }

        const dispatchResult = dispatchKeyEvents(parsedKeyInfo, element);
        operationResults.push({
          keyCombination: comboString,
          ...dispatchResult,
        });

        if (dispatchResult.error) {
          // Optionally, decide if sequence should stop on first error
          // For now, we continue but log the error in results
          console.warn(
            `Failed to simulate key combination "${comboString}": ${dispatchResult.error}`,
          );
        }

        if (delay > 0 && i < keyCombinations.length - 1) {
          await new Promise((resolve) => setTimeout(resolve, delay));
        }
      }

      // Check if all individual operations were successful
      const overallSuccess = operationResults.every((r) => r.success);

      return {
        success: overallSuccess,
        message: overallSuccess
          ? `Keyboard events simulated successfully: ${keysSequenceString}`
          : `Some keyboard events failed for: ${keysSequenceString}`,
        results: operationResults, // Detailed results for each key combination
        targetElement: {
          tagName: element.tagName,
          id: element.id,
          className: element.className,
          type: element.type, // if applicable e.g. for input
        },
      };
    } catch (error) {
      console.error('Error in simulateKeyboard:', error);
      return {
        success: false,
        error: `Error simulating keyboard events: ${error.message}`,
        results: [],
      };
    }
  }

  // Listener for messages from the extension
  chrome.runtime.onMessage.addListener((request, _sender, sendResponse) => {
    if (request.action === 'simulateKeyboard') {
      let targetEl = null;
      if (request.selector) {
        targetEl = document.querySelector(request.selector);
        if (!targetEl) {
          sendResponse({
            success: false,
            error: `Element with selector "${request.selector}" not found`,
            results: [],
          });
          return true; // Keep channel open for async response
        }
      }

      simulateKeyboard(request.keys, targetEl, request.delay)
        .then(sendResponse)
        .catch((error) => {
          // This catch is for unexpected errors in simulateKeyboard promise chain itself
          console.error('Unexpected error in simulateKeyboard promise chain:', error);
          sendResponse({
            success: false,
            error: `Unexpected error during keyboard simulation: ${error.message}`,
            results: [],
          });
        });
      return true; // Indicates async response is expected
    } else if (request.action === 'chrome_keyboard_ping') {
      sendResponse({ status: 'pong', initialized: true }); // Respond that it's initialized
      return false; // Synchronous response
    }
    // Not our message, or no async response needed
    return false;
  });
}

```

--------------------------------------------------------------------------------
/app/chrome-extension/entrypoints/background/semantic-similarity.ts:
--------------------------------------------------------------------------------

```typescript
import type { ModelPreset } from '@/utils/semantic-similarity-engine';
import { OffscreenManager } from '@/utils/offscreen-manager';
import { BACKGROUND_MESSAGE_TYPES, OFFSCREEN_MESSAGE_TYPES } from '@/common/message-types';
import { STORAGE_KEYS, ERROR_MESSAGES } from '@/common/constants';
import { hasAnyModelCache } from '@/utils/semantic-similarity-engine';

/**
 * Model configuration state management interface
 */
interface ModelConfig {
  modelPreset: ModelPreset;
  modelVersion: 'full' | 'quantized' | 'compressed';
  modelDimension: number;
}

let currentBackgroundModelConfig: ModelConfig | null = null;

/**
 * Initialize semantic engine only if model cache exists
 * This is called during plugin startup to avoid downloading models unnecessarily
 */
export async function initializeSemanticEngineIfCached(): Promise<boolean> {
  try {
    console.log('Background: Checking if semantic engine should be initialized from cache...');

    const hasCachedModel = await hasAnyModelCache();
    if (!hasCachedModel) {
      console.log('Background: No cached models found, skipping semantic engine initialization');
      return false;
    }

    console.log('Background: Found cached models, initializing semantic engine...');
    await initializeDefaultSemanticEngine();
    return true;
  } catch (error) {
    console.error('Background: Error during conditional semantic engine initialization:', error);
    return false;
  }
}

/**
 * Initialize default semantic engine model
 */
export async function initializeDefaultSemanticEngine(): Promise<void> {
  try {
    console.log('Background: Initializing default semantic engine...');

    // Update status to initializing
    await updateModelStatus('initializing', 0);

    const result = await chrome.storage.local.get([STORAGE_KEYS.SEMANTIC_MODEL, 'selectedVersion']);
    const defaultModel =
      (result[STORAGE_KEYS.SEMANTIC_MODEL] as ModelPreset) || 'multilingual-e5-small';
    const defaultVersion =
      (result.selectedVersion as 'full' | 'quantized' | 'compressed') || 'quantized';

    const { PREDEFINED_MODELS } = await import('@/utils/semantic-similarity-engine');
    const modelInfo = PREDEFINED_MODELS[defaultModel];

    await OffscreenManager.getInstance().ensureOffscreenDocument();

    const response = await chrome.runtime.sendMessage({
      target: 'offscreen',
      type: OFFSCREEN_MESSAGE_TYPES.SIMILARITY_ENGINE_INIT,
      config: {
        useLocalFiles: false,
        modelPreset: defaultModel,
        modelVersion: defaultVersion,
        modelDimension: modelInfo.dimension,
        forceOffscreen: true,
      },
    });

    if (response && response.success) {
      currentBackgroundModelConfig = {
        modelPreset: defaultModel,
        modelVersion: defaultVersion,
        modelDimension: modelInfo.dimension,
      };
      console.log('Semantic engine initialized successfully:', currentBackgroundModelConfig);

      // Update status to ready
      await updateModelStatus('ready', 100);

      // Also initialize ContentIndexer now that semantic engine is ready
      try {
        const { getGlobalContentIndexer } = await import('@/utils/content-indexer');
        const contentIndexer = getGlobalContentIndexer();
        contentIndexer.startSemanticEngineInitialization();
        console.log('ContentIndexer initialization triggered after semantic engine initialization');
      } catch (indexerError) {
        console.warn(
          'Failed to initialize ContentIndexer after semantic engine initialization:',
          indexerError,
        );
      }
    } else {
      const errorMessage = response?.error || ERROR_MESSAGES.TOOL_EXECUTION_FAILED;
      await updateModelStatus('error', 0, errorMessage, 'unknown');
      throw new Error(errorMessage);
    }
  } catch (error: any) {
    console.error('Background: Failed to initialize default semantic engine:', error);
    const errorMessage = error?.message || 'Unknown error during semantic engine initialization';
    await updateModelStatus('error', 0, errorMessage, 'unknown');
    // Don't throw error, let the extension continue running
  }
}

/**
 * Check if model switch is needed
 */
function needsModelSwitch(
  modelPreset: ModelPreset,
  modelVersion: 'full' | 'quantized' | 'compressed',
  modelDimension?: number,
): boolean {
  if (!currentBackgroundModelConfig) {
    return true;
  }

  const keyFields = ['modelPreset', 'modelVersion', 'modelDimension'];
  for (const field of keyFields) {
    const newValue =
      field === 'modelPreset'
        ? modelPreset
        : field === 'modelVersion'
          ? modelVersion
          : modelDimension;
    if (newValue !== currentBackgroundModelConfig[field as keyof ModelConfig]) {
      return true;
    }
  }

  return false;
}

/**
 * Handle model switching
 */
export async function handleModelSwitch(
  modelPreset: ModelPreset,
  modelVersion: 'full' | 'quantized' | 'compressed' = 'quantized',
  modelDimension?: number,
  previousDimension?: number,
): Promise<{ success: boolean; error?: string }> {
  try {
    const needsSwitch = needsModelSwitch(modelPreset, modelVersion, modelDimension);
    if (!needsSwitch) {
      await updateModelStatus('ready', 100);
      return { success: true };
    }

    await updateModelStatus('downloading', 0);

    try {
      await OffscreenManager.getInstance().ensureOffscreenDocument();
    } catch (offscreenError) {
      console.error('Background: Failed to create offscreen document:', offscreenError);
      const errorMessage = `Failed to create offscreen document: ${offscreenError}`;
      await updateModelStatus('error', 0, errorMessage, 'unknown');
      return { success: false, error: errorMessage };
    }

    const response = await chrome.runtime.sendMessage({
      target: 'offscreen',
      type: OFFSCREEN_MESSAGE_TYPES.SIMILARITY_ENGINE_INIT,
      config: {
        useLocalFiles: false,
        modelPreset: modelPreset,
        modelVersion: modelVersion,
        modelDimension: modelDimension,
        forceOffscreen: true,
      },
    });

    if (response && response.success) {
      currentBackgroundModelConfig = {
        modelPreset: modelPreset,
        modelVersion: modelVersion,
        modelDimension: modelDimension!,
      };

      // Only reinitialize ContentIndexer when dimension changes
      try {
        if (modelDimension && previousDimension && modelDimension !== previousDimension) {
          const { getGlobalContentIndexer } = await import('@/utils/content-indexer');
          const contentIndexer = getGlobalContentIndexer();
          await contentIndexer.reinitialize();
        }
      } catch (indexerError) {
        console.warn('Background: Failed to reinitialize ContentIndexer:', indexerError);
      }

      await updateModelStatus('ready', 100);
      return { success: true };
    } else {
      const errorMessage = response?.error || 'Failed to switch model';
      const errorType = analyzeErrorType(errorMessage);
      await updateModelStatus('error', 0, errorMessage, errorType);
      throw new Error(errorMessage);
    }
  } catch (error: any) {
    console.error('Model switch failed:', error);
    const errorMessage = error.message || 'Unknown error';
    const errorType = analyzeErrorType(errorMessage);
    await updateModelStatus('error', 0, errorMessage, errorType);
    return { success: false, error: errorMessage };
  }
}

/**
 * Get model status
 */
export async function handleGetModelStatus(): Promise<{
  success: boolean;
  status?: any;
  error?: string;
}> {
  try {
    if (typeof chrome === 'undefined' || !chrome.storage || !chrome.storage.local) {
      console.error('Background: chrome.storage.local is not available for status query');
      return {
        success: true,
        status: {
          initializationStatus: 'idle',
          downloadProgress: 0,
          isDownloading: false,
          lastUpdated: Date.now(),
        },
      };
    }

    const result = await chrome.storage.local.get(['modelState']);
    const modelState = result.modelState || {
      status: 'idle',
      downloadProgress: 0,
      isDownloading: false,
      lastUpdated: Date.now(),
    };

    return {
      success: true,
      status: {
        initializationStatus: modelState.status,
        downloadProgress: modelState.downloadProgress,
        isDownloading: modelState.isDownloading,
        lastUpdated: modelState.lastUpdated,
        errorMessage: modelState.errorMessage,
        errorType: modelState.errorType,
      },
    };
  } catch (error: any) {
    console.error('Failed to get model status:', error);
    return { success: false, error: error.message };
  }
}

/**
 * Update model status
 */
export async function updateModelStatus(
  status: string,
  progress: number,
  errorMessage?: string,
  errorType?: string,
): Promise<void> {
  try {
    // Check if chrome.storage is available
    if (typeof chrome === 'undefined' || !chrome.storage || !chrome.storage.local) {
      console.error('Background: chrome.storage.local is not available for status update');
      return;
    }

    const modelState = {
      status,
      downloadProgress: progress,
      isDownloading: status === 'downloading' || status === 'initializing',
      lastUpdated: Date.now(),
      errorMessage: errorMessage || '',
      errorType: errorType || '',
    };
    await chrome.storage.local.set({ modelState });
  } catch (error) {
    console.error('Failed to update model status:', error);
  }
}

/**
 * Handle model status updates from offscreen document
 */
export async function handleUpdateModelStatus(
  modelState: any,
): Promise<{ success: boolean; error?: string }> {
  try {
    // Check if chrome.storage is available
    if (typeof chrome === 'undefined' || !chrome.storage || !chrome.storage.local) {
      console.error('Background: chrome.storage.local is not available');
      return { success: false, error: 'chrome.storage.local is not available' };
    }

    await chrome.storage.local.set({ modelState });
    return { success: true };
  } catch (error: any) {
    console.error('Background: Failed to update model status:', error);
    return { success: false, error: error.message };
  }
}

/**
 * Analyze error type based on error message
 */
function analyzeErrorType(errorMessage: string): 'network' | 'file' | 'unknown' {
  const message = errorMessage.toLowerCase();

  if (
    message.includes('network') ||
    message.includes('fetch') ||
    message.includes('timeout') ||
    message.includes('connection') ||
    message.includes('cors') ||
    message.includes('failed to fetch')
  ) {
    return 'network';
  }

  if (
    message.includes('corrupt') ||
    message.includes('invalid') ||
    message.includes('format') ||
    message.includes('parse') ||
    message.includes('decode') ||
    message.includes('onnx')
  ) {
    return 'file';
  }

  return 'unknown';
}

/**
 * Initialize semantic similarity module message listeners
 */
export const initSemanticSimilarityListener = () => {
  chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
    if (message.type === BACKGROUND_MESSAGE_TYPES.SWITCH_SEMANTIC_MODEL) {
      handleModelSwitch(
        message.modelPreset,
        message.modelVersion,
        message.modelDimension,
        message.previousDimension,
      )
        .then((result: { success: boolean; error?: string }) => sendResponse(result))
        .catch((error: any) => sendResponse({ success: false, error: error.message }));
      return true;
    } else if (message.type === BACKGROUND_MESSAGE_TYPES.GET_MODEL_STATUS) {
      handleGetModelStatus()
        .then((result: { success: boolean; status?: any; error?: string }) => sendResponse(result))
        .catch((error: any) => sendResponse({ success: false, error: error.message }));
      return true;
    } else if (message.type === BACKGROUND_MESSAGE_TYPES.UPDATE_MODEL_STATUS) {
      handleUpdateModelStatus(message.modelState)
        .then((result: { success: boolean; error?: string }) => sendResponse(result))
        .catch((error: any) => sendResponse({ success: false, error: error.message }));
      return true;
    } else if (message.type === BACKGROUND_MESSAGE_TYPES.INITIALIZE_SEMANTIC_ENGINE) {
      initializeDefaultSemanticEngine()
        .then(() => sendResponse({ success: true }))
        .catch((error: any) => sendResponse({ success: false, error: error.message }));
      return true;
    }
  });
};

```

--------------------------------------------------------------------------------
/app/chrome-extension/inject-scripts/interactive-elements-helper.js:
--------------------------------------------------------------------------------

```javascript
/* eslint-disable */
// interactive-elements-helper.js
// This script is injected into the page to find interactive elements.
// Final version by Calvin, featuring a multi-layered fallback strategy
// and comprehensive element support, built on a performant and reliable core.

(function () {
  // Prevent re-initialization
  if (window.__INTERACTIVE_ELEMENTS_HELPER_INITIALIZED__) {
    return;
  }
  window.__INTERACTIVE_ELEMENTS_HELPER_INITIALIZED__ = true;

  /**
   * @typedef {Object} ElementInfo
   * @property {string} type - The type of the element (e.g., 'button', 'link').
   * @property {string} selector - A CSS selector to uniquely identify the element.
   * @property {string} text - The visible text or accessible name of the element.
   * @property {boolean} isInteractive - Whether the element is currently interactive.
   * @property {Object} [coordinates] - The coordinates of the element if requested.
   * @property {boolean} [disabled] - For elements that can be disabled.
   * @property {string} [href] - For links.
   * @property {boolean} [checked] - for checkboxes and radio buttons.
   */

  /**
   * Configuration for element types and their corresponding selectors.
   * Now more comprehensive with common ARIA roles.
   */
  const ELEMENT_CONFIG = {
    button: 'button, input[type="button"], input[type="submit"], [role="button"]',
    link: 'a[href], [role="link"]',
    input:
      'input:not([type="button"]):not([type="submit"]):not([type="checkbox"]):not([type="radio"])',
    checkbox: 'input[type="checkbox"], [role="checkbox"]',
    radio: 'input[type="radio"], [role="radio"]',
    textarea: 'textarea',
    select: 'select',
    tab: '[role="tab"]',
    // Generic interactive elements: combines tabindex, common roles, and explicit handlers.
    // This is the key to finding custom-built interactive components.
    interactive: `[onclick], [tabindex]:not([tabindex^="-"]), [role="menuitem"], [role="slider"], [role="option"], [role="treeitem"]`,
  };

  // A combined selector for ANY interactive element, used in the fallback logic.
  const ANY_INTERACTIVE_SELECTOR = Object.values(ELEMENT_CONFIG).join(', ');

  // --- Core Helper Functions ---

  /**
   * Checks if an element is genuinely visible on the page.
   * "Visible" means it's not styled with display:none, visibility:hidden, etc.
   * This check intentionally IGNORES whether the element is within the current viewport.
   * @param {Element} el The element to check.
   * @returns {boolean} True if the element is visible.
   */
  function isElementVisible(el) {
    if (!el || !el.isConnected) return false;

    const style = window.getComputedStyle(el);
    if (
      style.display === 'none' ||
      style.visibility === 'hidden' ||
      parseFloat(style.opacity) === 0
    ) {
      return false;
    }

    const rect = el.getBoundingClientRect();
    return rect.width > 0 || rect.height > 0 || el.tagName === 'A'; // Allow zero-size anchors as they can still be navigated
  }

  /**
   * Checks if an element is considered interactive (not disabled or hidden from accessibility).
   * @param {Element} el The element to check.
   * @returns {boolean} True if the element is interactive.
   */
  function isElementInteractive(el) {
    if (el.hasAttribute('disabled') || el.getAttribute('aria-disabled') === 'true') {
      return false;
    }
    if (el.closest('[aria-hidden="true"]')) {
      return false;
    }
    return true;
  }

  /**
   * Generates a reasonably stable CSS selector for a given element.
   * @param {Element} el The element.
   * @returns {string} A CSS selector.
   */
  function generateSelector(el) {
    if (!(el instanceof Element)) return '';

    if (el.id) {
      const idSelector = `#${CSS.escape(el.id)}`;
      if (document.querySelectorAll(idSelector).length === 1) return idSelector;
    }

    for (const attr of ['data-testid', 'data-cy', 'name']) {
      const attrValue = el.getAttribute(attr);
      if (attrValue) {
        const attrSelector = `[${attr}="${CSS.escape(attrValue)}"]`;
        if (document.querySelectorAll(attrSelector).length === 1) return attrSelector;
      }
    }

    let path = '';
    let current = el;
    while (current && current.nodeType === Node.ELEMENT_NODE && current.tagName !== 'BODY') {
      let selector = current.tagName.toLowerCase();
      const parent = current.parentElement;
      if (parent) {
        const siblings = Array.from(parent.children).filter(
          (child) => child.tagName === current.tagName,
        );
        if (siblings.length > 1) {
          const index = siblings.indexOf(current) + 1;
          selector += `:nth-of-type(${index})`;
        }
      }
      path = path ? `${selector} > ${path}` : selector;
      current = parent;
    }
    return path ? `body > ${path}` : 'body';
  }

  /**
   * Finds the accessible name for an element (label, aria-label, etc.).
   * @param {Element} el The element.
   * @returns {string} The accessible name.
   */
  function getAccessibleName(el) {
    const labelledby = el.getAttribute('aria-labelledby');
    if (labelledby) {
      const labelElement = document.getElementById(labelledby);
      if (labelElement) return labelElement.textContent?.trim() || '';
    }
    const ariaLabel = el.getAttribute('aria-label');
    if (ariaLabel) return ariaLabel.trim();
    if (el.id) {
      const label = document.querySelector(`label[for="${el.id}"]`);
      if (label) return label.textContent?.trim() || '';
    }
    const parentLabel = el.closest('label');
    if (parentLabel) return parentLabel.textContent?.trim() || '';
    return (
      el.getAttribute('placeholder') ||
      el.getAttribute('value') ||
      el.textContent?.trim() ||
      el.getAttribute('title') ||
      ''
    );
  }

  /**
   * Simple subsequence matching for fuzzy search.
   * @param {string} text The text to search within.
   * @param {string} query The query subsequence.
   * @returns {boolean}
   */
  function fuzzyMatch(text, query) {
    if (!text || !query) return false;
    const lowerText = text.toLowerCase();
    const lowerQuery = query.toLowerCase();
    let textIndex = 0;
    let queryIndex = 0;
    while (textIndex < lowerText.length && queryIndex < lowerQuery.length) {
      if (lowerText[textIndex] === lowerQuery[queryIndex]) {
        queryIndex++;
      }
      textIndex++;
    }
    return queryIndex === lowerQuery.length;
  }

  /**
   * Creates the standardized info object for an element.
   * Modified to handle the new 'text' type from the final fallback.
   */
  function createElementInfo(el, type, includeCoordinates, isInteractiveOverride = null) {
    const isActuallyInteractive = isElementInteractive(el);
    const info = {
      type,
      selector: generateSelector(el),
      text: getAccessibleName(el) || el.textContent?.trim(),
      isInteractive: isInteractiveOverride !== null ? isInteractiveOverride : isActuallyInteractive,
      disabled: el.hasAttribute('disabled') || el.getAttribute('aria-disabled') === 'true',
    };
    if (includeCoordinates) {
      const rect = el.getBoundingClientRect();
      info.coordinates = {
        x: rect.left + rect.width / 2,
        y: rect.top + rect.height / 2,
        rect: {
          x: rect.x,
          y: rect.y,
          width: rect.width,
          height: rect.height,
          top: rect.top,
          right: rect.right,
          bottom: rect.bottom,
          left: rect.left,
        },
      };
    }
    return info;
  }

  /**
   * [CORE UTILITY] Finds interactive elements based on a set of types.
   * This is our high-performance Layer 1 search function.
   */
  function findInteractiveElements(options = {}) {
    const { textQuery, includeCoordinates = true, types = Object.keys(ELEMENT_CONFIG) } = options;

    const selectorsToFind = types
      .map((type) => ELEMENT_CONFIG[type])
      .filter(Boolean)
      .join(', ');
    if (!selectorsToFind) return [];

    const targetElements = Array.from(document.querySelectorAll(selectorsToFind));
    const uniqueElements = new Set(targetElements);
    const results = [];

    for (const el of uniqueElements) {
      if (!isElementVisible(el) || !isElementInteractive(el)) continue;

      const accessibleName = getAccessibleName(el);
      if (textQuery && !fuzzyMatch(accessibleName, textQuery)) continue;

      let elementType = 'unknown';
      for (const [type, typeSelector] of Object.entries(ELEMENT_CONFIG)) {
        if (el.matches(typeSelector)) {
          elementType = type;
          break;
        }
      }
      results.push(createElementInfo(el, elementType, includeCoordinates));
    }
    return results;
  }

  /**
   * [ORCHESTRATOR] The main entry point that implements the 3-layer fallback logic.
   * @param {object} options - The main search options.
   * @returns {ElementInfo[]}
   */
  function findElementsByTextWithFallback(options = {}) {
    const { textQuery, includeCoordinates = true } = options;

    if (!textQuery) {
      return findInteractiveElements({ ...options, types: Object.keys(ELEMENT_CONFIG) });
    }

    // --- Layer 1: High-reliability search for interactive elements matching text ---
    let results = findInteractiveElements({ ...options, types: Object.keys(ELEMENT_CONFIG) });
    if (results.length > 0) {
      return results;
    }

    // --- Layer 2: Find text, then find its interactive ancestor ---
    const lowerCaseText = textQuery.toLowerCase();
    const xPath = `//text()[contains(translate(., 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'), '${lowerCaseText}')]`;
    const textNodes = document.evaluate(
      xPath,
      document,
      null,
      XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
      null,
    );

    const interactiveElements = new Set();
    if (textNodes.snapshotLength > 0) {
      for (let i = 0; i < textNodes.snapshotLength; i++) {
        const parentElement = textNodes.snapshotItem(i).parentElement;
        if (parentElement) {
          const interactiveAncestor = parentElement.closest(ANY_INTERACTIVE_SELECTOR);
          if (
            interactiveAncestor &&
            isElementVisible(interactiveAncestor) &&
            isElementInteractive(interactiveAncestor)
          ) {
            interactiveElements.add(interactiveAncestor);
          }
        }
      }

      if (interactiveElements.size > 0) {
        return Array.from(interactiveElements).map((el) => {
          let elementType = 'interactive';
          for (const [type, typeSelector] of Object.entries(ELEMENT_CONFIG)) {
            if (el.matches(typeSelector)) {
              elementType = type;
              break;
            }
          }
          return createElementInfo(el, elementType, includeCoordinates);
        });
      }
    }

    // --- Layer 3: Final fallback, return any element containing the text ---
    const leafElements = new Set();
    for (let i = 0; i < textNodes.snapshotLength; i++) {
      const parentElement = textNodes.snapshotItem(i).parentElement;
      if (parentElement && isElementVisible(parentElement)) {
        leafElements.add(parentElement);
      }
    }

    const finalElements = Array.from(leafElements).filter((el) => {
      return ![...leafElements].some((otherEl) => el !== otherEl && el.contains(otherEl));
    });

    return finalElements.map((el) => createElementInfo(el, 'text', includeCoordinates, true));
  }

  // --- Chrome Message Listener ---
  chrome.runtime.onMessage.addListener((request, _sender, sendResponse) => {
    if (request.action === 'getInteractiveElements') {
      try {
        let elements;
        if (request.selector) {
          // If a selector is provided, bypass the text-based logic and use a direct query.
          const foundEls = Array.from(document.querySelectorAll(request.selector));
          elements = foundEls.map((el) =>
            createElementInfo(
              el,
              'selected',
              request.includeCoordinates !== false,
              isElementInteractive(el),
            ),
          );
        } else {
          // Otherwise, use our powerful multi-layered text search
          elements = findElementsByTextWithFallback(request);
        }
        sendResponse({ success: true, elements });
      } catch (error) {
        console.error('Error in getInteractiveElements:', error);
        sendResponse({ success: false, error: error.message });
      }
      return true; // Async response
    } else if (request.action === 'chrome_get_interactive_elements_ping') {
      sendResponse({ status: 'pong' });
      return false;
    }
  });

  console.log('Interactive elements helper script loaded');
})();

```

--------------------------------------------------------------------------------
/app/chrome-extension/_locales/en/messages.json:
--------------------------------------------------------------------------------

```json
{
  "extensionName": {
    "message": "chrome-mcp-server",
    "description": "Extension name"
  },
  "extensionDescription": {
    "message": "Exposes browser capabilities with your own chrome",
    "description": "Extension description"
  },
  "nativeServerConfigLabel": {
    "message": "Native Server Configuration",
    "description": "Main section header for native server settings"
  },
  "semanticEngineLabel": {
    "message": "Semantic Engine",
    "description": "Main section header for semantic engine"
  },
  "embeddingModelLabel": {
    "message": "Embedding Model",
    "description": "Main section header for model selection"
  },
  "indexDataManagementLabel": {
    "message": "Index Data Management",
    "description": "Main section header for data management"
  },
  "modelCacheManagementLabel": {
    "message": "Model Cache Management",
    "description": "Main section header for cache management"
  },
  "statusLabel": {
    "message": "Status",
    "description": "Generic status label"
  },
  "runningStatusLabel": {
    "message": "Running Status",
    "description": "Server running status label"
  },
  "connectionStatusLabel": {
    "message": "Connection Status",
    "description": "Connection status label"
  },
  "lastUpdatedLabel": {
    "message": "Last Updated:",
    "description": "Last updated timestamp label"
  },
  "connectButton": {
    "message": "Connect",
    "description": "Connect button text"
  },
  "disconnectButton": {
    "message": "Disconnect",
    "description": "Disconnect button text"
  },
  "connectingStatus": {
    "message": "Connecting...",
    "description": "Connecting status message"
  },
  "connectedStatus": {
    "message": "Connected",
    "description": "Connected status message"
  },
  "disconnectedStatus": {
    "message": "Disconnected",
    "description": "Disconnected status message"
  },
  "detectingStatus": {
    "message": "Detecting...",
    "description": "Detecting status message"
  },
  "serviceRunningStatus": {
    "message": "Service Running (Port: $PORT$)",
    "description": "Service running with port number",
    "placeholders": {
      "port": {
        "content": "$1",
        "example": "12306"
      }
    }
  },
  "serviceNotConnectedStatus": {
    "message": "Service Not Connected",
    "description": "Service not connected status"
  },
  "connectedServiceNotStartedStatus": {
    "message": "Connected, Service Not Started",
    "description": "Connected but service not started status"
  },
  "mcpServerConfigLabel": {
    "message": "MCP Server Configuration",
    "description": "MCP server configuration section label"
  },
  "connectionPortLabel": {
    "message": "Connection Port",
    "description": "Connection port input label"
  },
  "refreshStatusButton": {
    "message": "Refresh Status",
    "description": "Refresh status button tooltip"
  },
  "copyConfigButton": {
    "message": "Copy Configuration",
    "description": "Copy configuration button text"
  },
  "retryButton": {
    "message": "Retry",
    "description": "Retry button text"
  },
  "cancelButton": {
    "message": "Cancel",
    "description": "Cancel button text"
  },
  "confirmButton": {
    "message": "Confirm",
    "description": "Confirm button text"
  },
  "saveButton": {
    "message": "Save",
    "description": "Save button text"
  },
  "closeButton": {
    "message": "Close",
    "description": "Close button text"
  },
  "resetButton": {
    "message": "Reset",
    "description": "Reset button text"
  },
  "initializingStatus": {
    "message": "Initializing...",
    "description": "Initializing progress message"
  },
  "processingStatus": {
    "message": "Processing...",
    "description": "Processing progress message"
  },
  "loadingStatus": {
    "message": "Loading...",
    "description": "Loading progress message"
  },
  "clearingStatus": {
    "message": "Clearing...",
    "description": "Clearing progress message"
  },
  "cleaningStatus": {
    "message": "Cleaning...",
    "description": "Cleaning progress message"
  },
  "downloadingStatus": {
    "message": "Downloading...",
    "description": "Downloading progress message"
  },
  "semanticEngineReadyStatus": {
    "message": "Semantic Engine Ready",
    "description": "Semantic engine ready status"
  },
  "semanticEngineInitializingStatus": {
    "message": "Semantic Engine Initializing...",
    "description": "Semantic engine initializing status"
  },
  "semanticEngineInitFailedStatus": {
    "message": "Semantic Engine Initialization Failed",
    "description": "Semantic engine initialization failed status"
  },
  "semanticEngineNotInitStatus": {
    "message": "Semantic Engine Not Initialized",
    "description": "Semantic engine not initialized status"
  },
  "initSemanticEngineButton": {
    "message": "Initialize Semantic Engine",
    "description": "Initialize semantic engine button text"
  },
  "reinitializeButton": {
    "message": "Reinitialize",
    "description": "Reinitialize button text"
  },
  "downloadingModelStatus": {
    "message": "Downloading Model... $PROGRESS$%",
    "description": "Model download progress with percentage",
    "placeholders": {
      "progress": {
        "content": "$1",
        "example": "50"
      }
    }
  },
  "switchingModelStatus": {
    "message": "Switching Model...",
    "description": "Model switching progress message"
  },
  "modelLoadedStatus": {
    "message": "Model Loaded",
    "description": "Model successfully loaded status"
  },
  "modelFailedStatus": {
    "message": "Model Failed to Load",
    "description": "Model failed to load status"
  },
  "lightweightModelDescription": {
    "message": "Lightweight Multilingual Model",
    "description": "Description for lightweight model option"
  },
  "betterThanSmallDescription": {
    "message": "Slightly larger than e5-small, but better performance",
    "description": "Description for medium model option"
  },
  "multilingualModelDescription": {
    "message": "Multilingual Semantic Model",
    "description": "Description for multilingual model option"
  },
  "fastPerformance": {
    "message": "Fast",
    "description": "Fast performance indicator"
  },
  "balancedPerformance": {
    "message": "Balanced",
    "description": "Balanced performance indicator"
  },
  "accuratePerformance": {
    "message": "Accurate",
    "description": "Accurate performance indicator"
  },
  "networkErrorMessage": {
    "message": "Network connection error, please check network and retry",
    "description": "Network connection error message"
  },
  "modelCorruptedErrorMessage": {
    "message": "Model file corrupted or incomplete, please retry download",
    "description": "Model corruption error message"
  },
  "unknownErrorMessage": {
    "message": "Unknown error, please check if your network can access HuggingFace",
    "description": "Unknown error fallback message"
  },
  "permissionDeniedErrorMessage": {
    "message": "Permission denied",
    "description": "Permission denied error message"
  },
  "timeoutErrorMessage": {
    "message": "Operation timed out",
    "description": "Timeout error message"
  },
  "indexedPagesLabel": {
    "message": "Indexed Pages",
    "description": "Number of indexed pages label"
  },
  "indexSizeLabel": {
    "message": "Index Size",
    "description": "Index size label"
  },
  "activeTabsLabel": {
    "message": "Active Tabs",
    "description": "Number of active tabs label"
  },
  "vectorDocumentsLabel": {
    "message": "Vector Documents",
    "description": "Number of vector documents label"
  },
  "cacheSizeLabel": {
    "message": "Cache Size",
    "description": "Cache size label"
  },
  "cacheEntriesLabel": {
    "message": "Cache Entries",
    "description": "Number of cache entries label"
  },
  "clearAllDataButton": {
    "message": "Clear All Data",
    "description": "Clear all data button text"
  },
  "clearAllCacheButton": {
    "message": "Clear All Cache",
    "description": "Clear all cache button text"
  },
  "cleanExpiredCacheButton": {
    "message": "Clean Expired Cache",
    "description": "Clean expired cache button text"
  },
  "exportDataButton": {
    "message": "Export Data",
    "description": "Export data button text"
  },
  "importDataButton": {
    "message": "Import Data",
    "description": "Import data button text"
  },
  "confirmClearDataTitle": {
    "message": "Confirm Clear Data",
    "description": "Clear data confirmation dialog title"
  },
  "settingsTitle": {
    "message": "Settings",
    "description": "Settings dialog title"
  },
  "aboutTitle": {
    "message": "About",
    "description": "About dialog title"
  },
  "helpTitle": {
    "message": "Help",
    "description": "Help dialog title"
  },
  "clearDataWarningMessage": {
    "message": "This operation will clear all indexed webpage content and vector data, including:",
    "description": "Clear data warning message"
  },
  "clearDataList1": {
    "message": "All webpage text content index",
    "description": "First item in clear data list"
  },
  "clearDataList2": {
    "message": "Vector embedding data",
    "description": "Second item in clear data list"
  },
  "clearDataList3": {
    "message": "Search history and cache",
    "description": "Third item in clear data list"
  },
  "clearDataIrreversibleWarning": {
    "message": "This operation is irreversible! After clearing, you need to browse webpages again to rebuild the index.",
    "description": "Irreversible operation warning"
  },
  "confirmClearButton": {
    "message": "Confirm Clear",
    "description": "Confirm clear action button"
  },
  "cacheDetailsLabel": {
    "message": "Cache Details",
    "description": "Cache details section label"
  },
  "noCacheDataMessage": {
    "message": "No cache data",
    "description": "No cache data available message"
  },
  "loadingCacheInfoStatus": {
    "message": "Loading cache information...",
    "description": "Loading cache information status"
  },
  "processingCacheStatus": {
    "message": "Processing cache...",
    "description": "Processing cache status"
  },
  "expiredLabel": {
    "message": "Expired",
    "description": "Expired item label"
  },
  "bookmarksBarLabel": {
    "message": "Bookmarks Bar",
    "description": "Bookmarks bar folder name"
  },
  "newTabLabel": {
    "message": "New Tab",
    "description": "New tab label"
  },
  "currentPageLabel": {
    "message": "Current Page",
    "description": "Current page label"
  },
  "menuLabel": {
    "message": "Menu",
    "description": "Menu accessibility label"
  },
  "navigationLabel": {
    "message": "Navigation",
    "description": "Navigation accessibility label"
  },
  "mainContentLabel": {
    "message": "Main Content",
    "description": "Main content accessibility label"
  },
  "languageSelectorLabel": {
    "message": "Language",
    "description": "Language selector label"
  },
  "themeLabel": {
    "message": "Theme",
    "description": "Theme selector label"
  },
  "lightTheme": {
    "message": "Light",
    "description": "Light theme option"
  },
  "darkTheme": {
    "message": "Dark",
    "description": "Dark theme option"
  },
  "autoTheme": {
    "message": "Auto",
    "description": "Auto theme option"
  },
  "advancedSettingsLabel": {
    "message": "Advanced Settings",
    "description": "Advanced settings section label"
  },
  "debugModeLabel": {
    "message": "Debug Mode",
    "description": "Debug mode toggle label"
  },
  "verboseLoggingLabel": {
    "message": "Verbose Logging",
    "description": "Verbose logging toggle label"
  },
  "successNotification": {
    "message": "Operation completed successfully",
    "description": "Generic success notification"
  },
  "warningNotification": {
    "message": "Warning: Please review before proceeding",
    "description": "Generic warning notification"
  },
  "infoNotification": {
    "message": "Information",
    "description": "Generic info notification"
  },
  "configCopiedNotification": {
    "message": "Configuration copied to clipboard",
    "description": "Configuration copied success message"
  },
  "dataClearedNotification": {
    "message": "Data cleared successfully",
    "description": "Data cleared success message"
  },
  "bytesUnit": {
    "message": "bytes",
    "description": "Bytes unit"
  },
  "kilobytesUnit": {
    "message": "KB",
    "description": "Kilobytes unit"
  },
  "megabytesUnit": {
    "message": "MB",
    "description": "Megabytes unit"
  },
  "gigabytesUnit": {
    "message": "GB",
    "description": "Gigabytes unit"
  },
  "itemsUnit": {
    "message": "items",
    "description": "Items count unit"
  },
  "pagesUnit": {
    "message": "pages",
    "description": "Pages count unit"
  }
}

```
Page 2/8FirstPrevNextLast