Skip to main content

Overview

ContextFort automatically detects when browser agents are active and provides comprehensive monitoring capabilities. Every agent interaction is tracked, captured, and stored locally for review and audit.
Privacy First: All screenshots and session data are stored locally on your machine. Nothing is transmitted to external servers.

How Agent Detection Works

ContextFort uses Chrome’s Tab Groups API to detect agent activity:
  1. Tab Group Monitoring: When an agent creates or updates a tab group with the ⌛ emoji, ContextFort detects this as an active agent session
  2. Automatic Session Creation: A new session is created and tracking begins immediately
  3. Event Listeners: Content scripts listen for all agent interactions (clicks, inputs, navigation)
  4. Screenshot Capture: Screenshots are automatically captured at key moments

Detection Implementation

chrome-extension/background.js
chrome.tabGroups.onUpdated.addListener(async (group) => {
  const previousTitle = groupTitles.get(group.id);
  const currentTitle = group.title;

  const hadSandTimer = previousTitle && previousTitle.includes('⌛');
  const hasSandTimer = currentTitle && currentTitle.includes('⌛');

  if (!hadSandTimer && hasSandTimer) {
    // Agent started - begin tracking
    chrome.tabs.query({ groupId: group.id }, async (tabs) => {
      if (tabs.length === 0) return;
      const tab = tabs[0];
      onMessageAgentDetected(tab, group.id);
      await chrome.tabs.sendMessage(tab.id, { type: 'START_AGENT_LISTENING' });
    });
  }
});
ContextFort specifically monitors for the ⌛ (hourglass) emoji used by Claude’s Computer Use agent mode.

Session Tracking

What Gets Tracked

Each agent session captures:

Session Metadata

  • Unique session ID
  • Start and end timestamps
  • Session duration
  • Tab information

Navigation History

  • All URLs visited
  • Page titles
  • Domain transitions
  • Navigation timestamps

Agent Actions

  • Click events with coordinates
  • Input field interactions
  • Right-click actions
  • Double-click events

Screenshots

  • Before/after action pairs
  • Page reads
  • Screenshot count per session
  • Full page captures

Session Lifecycle

chrome-extension/background.js
async function getOrCreateSession(groupId, firstTabId, firstTabUrl, firstTabTitle) {
  if (sessions.has(groupId)) {
    return sessions.get(groupId);
  }

  const sessionId = Date.now();
  const session = {
    id: sessionId,
    groupId: groupId,
    startTime: new Date().toISOString(),
    endTime: null,
    duration: null,
    tabId: firstTabId,
    tabTitle: firstTabTitle || 'Unknown',
    tabUrl: firstTabUrl || 'Unknown',
    screenshotCount: 0,
    status: 'active',
    visitedUrls: []
  };

  sessions.set(groupId, session);
  // Store in Chrome local storage
  const result = await chrome.storage.local.get(['sessions']);
  const allSessions = result.sessions || [];
  allSessions.unshift(session);
  await chrome.storage.local.set({ sessions: allSessions });

  return session;
}

Screenshot Capture

Automatic Screenshot Triggers

Screenshots are captured automatically when agents:
Before Screenshot: Captured immediately when agent clicks After Screenshot: Captured 300ms after click to show the result
chrome-extension/background.js
if (message.action === 'click') {
  chrome.tabs.captureVisibleTab(tab.windowId, { format: 'png' }, (dataUrl1) => {
    saveEventData(dataUrl1, false, currentTab.url, currentTab.title, null).then(actionId => {
      setTimeout(() => {
        chrome.tabs.captureVisibleTab(tab.windowId, { format: 'png' }, (dataUrl2) => {
          saveEventData(dataUrl2, true, resultTab.url, resultTab.title, actionId);
        });
      }, 300);
    });
  });
}
Debounced Capture: Input events are debounced for 1 second to avoid excessive screenshots Final Screenshot: Captured 500ms after the last input to show complete text
chrome-extension/background.js
const INPUT_DEBOUNCE_MS = 1000;

function onMessageScreenshotTrigger(message, tab) {
  if (message.action === 'input') {
    let debounceState = inputDebounceTimers.get(tab.id);
    if (debounceState.timer) clearTimeout(debounceState.timer);

    debounceState.inputs.push({
      element: message.element,
      inputValue: message.inputValue,
      timestamp: new Date().toISOString()
    });

    debounceState.timer = setTimeout(() => {
      // Capture screenshot with all input values
      const inputValues = debounceState.inputs.map(i => i.inputValue);
      chrome.tabs.captureVisibleTab(tab.windowId, { format: 'png' }, (dataUrl) => {
        saveEventData(dataUrl, inputValues);
      });
    }, INPUT_DEBOUNCE_MS);
  }
}

Screenshot Storage

{
  id: 1709567890123.456,
  sessionId: 1709567800000,
  tabId: 1234,
  url: "https://example.com/page",
  title: "Example Page",
  reason: "agent_event",
  timestamp: "2025-02-28T12:34:50.123Z",
  dataUrl: "data:image/png;base64,...",
  eventType: "click",
  eventDetails: {
    element: {
      tag: "BUTTON",
      id: "submit-btn",
      className: "primary-button",
      text: "Submit",
      type: null,
      name: null
    },
    coordinates: { x: 450, y: 300 },
    inputValue: null,
    actionType: "click"
  }
}

Event Listeners

Content Script Implementation

The content script (injected into every page) captures all agent interactions:
chrome-extension/content.js
let agentModeActive = false;

function onClickCapture(e) {
  if (agentModeActive) {
    safeSendMessage({
      type: 'SCREENSHOT_TRIGGER',
      action: 'click',
      eventType: 'click',
      element: captureElement(e.target),
      coordinates: {
        x: e.clientX * window.devicePixelRatio,
        y: e.clientY * window.devicePixelRatio
      },
      url: window.location.href,
      title: document.title
    });
  }
}

function startListening() {
  if (agentModeActive) return;
  agentModeActive = true;

  document.addEventListener('click', onClickCapture, true);
  document.addEventListener('dblclick', onDblClickCapture, true);
  document.addEventListener('contextmenu', onContextMenuCapture, true);
  document.addEventListener('input', onInputCapture, true);
}

Element Capture

All element metadata is captured for audit purposes:
chrome-extension/content.js
function captureElement(target) {
  if (!target) return null;

  let className = null;
  if (target.className) {
    if (typeof target.className === 'string') {
      className = target.className;
    } else if (target.className.baseVal !== undefined) {
      // Handle SVG elements
      className = target.className.baseVal;
    }
  }

  return {
    tag: target.tagName,
    id: target.id || null,
    className: className,
    text: target.textContent?.substring(0, 50) || null,
    type: target.type || null,
    name: target.name || null
  };
}

Viewing Session History

Access the Visibility dashboard to:
  1. View All Sessions: See complete list of all agent sessions
  2. Session Details: Click into any session to see:
    • Full timeline of events
    • All screenshots with timestamps
    • Navigation history
    • Session duration and metadata
  3. Export Data: Download sessions and screenshots for offline review

Open Visibility Dashboard

Click the ContextFort icon in Chrome to open the dashboard

Data Deletion

Delete All Data

  1. Go to chrome://extensions/
  2. Find ContextFort
  3. Click “Remove” to uninstall (deletes all data)

Delete Specific Sessions

Per-session deletion is not yet available in the dashboard. You can delete all sessions using chrome.storage.local.remove(['sessions']).

Clear Storage

// Manual deletion via console
chrome.storage.local.remove(['sessions', 'screenshots'], () => {
  console.log('All session data deleted');
});

Performance Optimization

Queued Storage Writes

To prevent storage bottlenecks, screenshot writes are queued:
chrome-extension/background.js
const storageWriteQueue = [];
let isProcessingQueue = false;

async function queuedStorageWrite(screenshotData, activation) {
  return new Promise((resolve) => {
    storageWriteQueue.push({ screenshotData, activation, resolve });
    processStorageQueue();
  });
}

async function processStorageQueue() {
  if (isProcessingQueue || storageWriteQueue.length === 0) return;
  isProcessingQueue = true;

  while (storageWriteQueue.length > 0) {
    const { screenshotData, activation, resolve } = storageWriteQueue.shift();
    try {
      const result = await chrome.storage.local.get(['screenshots', 'sessions']);
      const screenshots = result.screenshots || [];
      screenshots.push(screenshotData);
      await chrome.storage.local.set({ screenshots: screenshots });
      resolve(screenshotData.id);
    } catch (error) {
      console.error('Storage write failed:', error);
      resolve(null);
    }
  }
  isProcessingQueue = false;
}

Next Steps

Session Isolation

Learn how ContextFort swaps cookies to isolate agent sessions

Blocking Rules

Configure URL and domain blocking to prevent context mixing

Screenshots

Deep dive into screenshot capture and storage

Dashboard

Explore the Visibility dashboard UI

Build docs developers (and LLMs) love