(() => {
  var __create = Object.create;
  var __defProp = Object.defineProperty;
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
  var __getOwnPropNames = Object.getOwnPropertyNames;
  var __getProtoOf = Object.getPrototypeOf;
  var __hasOwnProp = Object.prototype.hasOwnProperty;
  var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
    get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
  }) : x)(function(x) {
    if (typeof require !== "undefined") return require.apply(this, arguments);
    throw Error('Dynamic require of "' + x + '" is not supported');
  });
  var __commonJS = (cb, mod) => function __require2() {
    return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
  };
  var __copyProps = (to, from, except, desc) => {
    if (from && typeof from === "object" || typeof from === "function") {
      for (let key of __getOwnPropNames(from))
        if (!__hasOwnProp.call(to, key) && key !== except)
          __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
    }
    return to;
  };
  var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
    // If the importer is in node compatibility mode or this is not an ESM
    // file that has been converted to a CommonJS file using a Babel-
    // compatible transform (i.e. "__esModule" has not been set), then set
    // "default" to the CommonJS "module.exports" for node compatibility.
    isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
    mod
  ));

  // src/shared/version.js
  var require_version = __commonJS({
    "src/shared/version.js"(exports, module) {
      (function attachBuildIdentity(root, factory) {
        const api = factory();
        if (typeof module === "object" && module.exports) module.exports = api;
        root.TfaBuildIdentity = api;
      })(typeof globalThis === "object" ? globalThis : exports, function createBuildIdentity() {
        "use strict";
        const EXTENSION_VERSION = "1.25.34";
        const injectedFingerprint = true ? "ad6e691b8aca11ed33afdee80003f6267e631e774c668aa45122b2917e7c7890" : "";
        const fingerprint = injectedFingerprint || `source:${EXTENSION_VERSION}`;
        return Object.freeze({ version: EXTENSION_VERSION, fingerprint });
      });
    }
  });

  // src/shared/license.js
  var require_license = __commonJS({
    "src/shared/license.js"(exports, module) {
      (function initTFALicense(global) {
        "use strict";
        const ALPHABET = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
        const VERSION = 1;
        const PAYLOAD_BYTES = 7;
        const MAC_BYTES = 5;
        const RAW_CODE_BYTES = PAYLOAD_BYTES + MAC_BYTES;
        const KEY_HEX_LENGTH = 32;
        const GROUP_SIZE = 4;
        const LICENSE_STORAGE_KEY = "license.record.v1";
        const LICENSE_KEYS = Object.freeze([
          Object.freeze({
            id: "key-2026-07",
            codePrefix: "AK",
            verificationKey: "e28c3927cda8946ef2a59de7b1053962"
          })
        ]);
        function hexToBytes(value) {
          const normalized = String(value || "").trim().toLowerCase();
          if (!/^[0-9a-f]+$/.test(normalized) || normalized.length !== KEY_HEX_LENGTH) {
            throw new Error("invalid verification key");
          }
          const bytes = new Uint8Array(normalized.length / 2);
          for (let index = 0; index < normalized.length; index += 2) {
            bytes[index / 2] = Number.parseInt(normalized.slice(index, index + 2), 16);
          }
          return bytes;
        }
        function normalizeCode(value) {
          const compact = String(value || "").trim().toUpperCase().replace(/[^A-Z0-9]/g, "");
          if (compact.length <= 2) return { prefix: null, body: compact };
          return { prefix: compact.slice(0, 2), body: compact.slice(2) };
        }
        function normalizeUserCode(value) {
          const normalized = normalizeCode(value);
          if (!normalized.prefix || !normalized.body) return "";
          const groups = normalized.body.match(new RegExp(`.{1,${GROUP_SIZE}}`, "g")) || [];
          return `${normalized.prefix}-${groups.join("-")}`;
        }
        function decodeBase32(value) {
          let bits = 0;
          let buffer = 0;
          const output = [];
          for (const character of value) {
            const alphabetIndex = ALPHABET.indexOf(character);
            if (alphabetIndex === -1) throw new Error("invalid code body");
            buffer = buffer << 5 | alphabetIndex;
            bits += 5;
            if (bits >= 8) {
              output.push(buffer >>> bits - 8 & 255);
              bits -= 8;
            }
          }
          return Uint8Array.from(output.slice(0, RAW_CODE_BYTES));
        }
        function bytesEqual(left, right) {
          if (left.length !== right.length) return false;
          let difference = 0;
          for (let index = 0; index < left.length; index += 1) {
            difference |= left[index] ^ right[index];
          }
          return difference === 0;
        }
        async function makeMac(payload, verificationKey) {
          const cryptoApi = global.crypto || globalThis.crypto;
          if (!cryptoApi || !cryptoApi.subtle) throw new Error("web crypto unavailable");
          const key = await cryptoApi.subtle.importKey(
            "raw",
            hexToBytes(verificationKey),
            { name: "HMAC", hash: "SHA-256" },
            false,
            ["sign"]
          );
          const signature = await cryptoApi.subtle.sign("HMAC", key, payload);
          return new Uint8Array(signature).slice(0, MAC_BYTES);
        }
        function epochMs() {
          return Date.UTC(2025, 0, 1, 0, 0, 0, 0);
        }
        async function verifyActivationCode(options) {
          let verificationKey;
          try {
            verificationKey = String(options && options.verificationKey || "").trim().toLowerCase();
            hexToBytes(verificationKey);
          } catch {
            return { valid: false, reason: "invalid verification key" };
          }
          const normalized = normalizeCode(options && options.code);
          const expectedPrefix = String(options && options.expectedPrefix || "").trim().toUpperCase();
          if (expectedPrefix && normalized.prefix !== expectedPrefix) {
            return { valid: false, reason: "prefix mismatch" };
          }
          if (normalized.body.length !== 20) {
            return { valid: false, reason: "code length mismatch" };
          }
          let raw;
          try {
            raw = decodeBase32(normalized.body);
          } catch {
            return { valid: false, reason: "invalid code body" };
          }
          if (raw.length !== RAW_CODE_BYTES) {
            return { valid: false, reason: "invalid code bytes" };
          }
          const payload = raw.slice(0, PAYLOAD_BYTES);
          const actualMac = raw.slice(PAYLOAD_BYTES);
          const expectedMac = await makeMac(payload, verificationKey);
          if (!bytesEqual(actualMac, expectedMac)) {
            return { valid: false, reason: "signature mismatch" };
          }
          const version = payload[0] || 0;
          if (version !== VERSION) {
            return { valid: false, reason: "unsupported version" };
          }
          const dayOffset = (payload[1] || 0) << 8 | (payload[2] || 0);
          const nonce = ((payload[3] || 0) << 24 | (payload[4] || 0) << 16 | (payload[5] || 0) << 8 | (payload[6] || 0)) >>> 0;
          return {
            valid: true,
            decoded: {
              version,
              dayOffset,
              nonce,
              issuedAt: new Date(epochMs() + dayOffset * 864e5).toISOString()
            }
          };
        }
        function mapLicenseError(reason) {
          const messages = {
            "empty code": "\u8BF7\u8F93\u5165\u6FC0\u6D3B\u7801",
            "prefix mismatch": "\u6FC0\u6D3B\u7801\u4E0D\u5C5E\u4E8E\u5F53\u524D\u4EA7\u54C1",
            "code length mismatch": "\u6FC0\u6D3B\u7801\u957F\u5EA6\u4E0D\u6B63\u786E",
            "invalid code body": "\u6FC0\u6D3B\u7801\u5305\u542B\u65E0\u6548\u5B57\u7B26",
            "invalid code bytes": "\u6FC0\u6D3B\u7801\u65E0\u6548\uFF0C\u8BF7\u68C0\u67E5\u540E\u91CD\u65B0\u8F93\u5165",
            "signature mismatch": "\u6FC0\u6D3B\u7801\u65E0\u6548\uFF0C\u8BF7\u68C0\u67E5\u540E\u91CD\u65B0\u8F93\u5165",
            "unsupported version": "\u6FC0\u6D3B\u7801\u7248\u672C\u4E0D\u53D7\u652F\u6301\uFF0C\u8BF7\u8054\u7CFB\u5356\u5BB6",
            "invalid verification key": "\u63D2\u4EF6\u6388\u6743\u914D\u7F6E\u5F02\u5E38\uFF0C\u8BF7\u8054\u7CFB\u5F00\u53D1\u8005",
            "invalid stored record": "\u672C\u5730\u6FC0\u6D3B\u8BB0\u5F55\u5F02\u5E38\uFF0C\u8BF7\u91CD\u65B0\u8F93\u5165\u6FC0\u6D3B\u7801"
          };
          return messages[reason] || "\u6FC0\u6D3B\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5";
        }
        async function verifyWithKeyRing(code) {
          let lastReason = "signature mismatch";
          for (const keyConfig of LICENSE_KEYS) {
            const result = await verifyActivationCode({
              code,
              verificationKey: keyConfig.verificationKey,
              expectedPrefix: keyConfig.codePrefix
            });
            if (result.valid) return { keyConfig, decoded: result.decoded };
            lastReason = result.reason;
          }
          return { error: lastReason };
        }
        function getStorage() {
          const chromeApi = global.chrome || globalThis.chrome;
          if (!chromeApi || !chromeApi.storage || !chromeApi.storage.local) {
            throw new Error("chrome.storage.local unavailable");
          }
          return chromeApi.storage.local;
        }
        async function activateLicense(input) {
          const code = normalizeUserCode(input);
          if (!code) {
            const reason = "empty code";
            return { status: "invalid", reason, message: mapLicenseError(reason) };
          }
          const verification = await verifyWithKeyRing(code);
          if (verification.error) {
            return {
              status: "invalid",
              reason: verification.error,
              message: mapLicenseError(verification.error)
            };
          }
          const record = {
            schemaVersion: 1,
            status: "active",
            code,
            keyId: verification.keyConfig.id,
            codePrefix: verification.keyConfig.codePrefix,
            issuedAt: verification.decoded.issuedAt,
            activatedAt: (/* @__PURE__ */ new Date()).toISOString()
          };
          await getStorage().set({ [LICENSE_STORAGE_KEY]: record });
          return { status: "active", record };
        }
        async function deactivateLicense() {
          await getStorage().remove(LICENSE_STORAGE_KEY);
          return { status: "inactive" };
        }
        function isStoredRecord(value) {
          return Boolean(
            value && value.schemaVersion === 1 && value.status === "active" && typeof value.code === "string" && typeof value.activatedAt === "string"
          );
        }
        async function getLicenseState() {
          const stored = await getStorage().get(LICENSE_STORAGE_KEY);
          const record = stored && stored[LICENSE_STORAGE_KEY];
          if (!record) return { status: "inactive" };
          if (!isStoredRecord(record)) {
            const reason = "invalid stored record";
            return { status: "invalid", reason, message: mapLicenseError(reason) };
          }
          const verification = await verifyWithKeyRing(record.code);
          if (verification.error) {
            return {
              status: "invalid",
              reason: verification.error,
              message: mapLicenseError(verification.error)
            };
          }
          return {
            status: "active",
            record: {
              ...record,
              keyId: verification.keyConfig.id,
              codePrefix: verification.keyConfig.codePrefix,
              issuedAt: verification.decoded.issuedAt
            }
          };
        }
        function maskActivationCode(code) {
          const normalized = normalizeUserCode(code);
          const parts = normalized.split("-");
          if (parts.length < 2) return "\u2022\u2022\u2022\u2022";
          return [parts[0], ...parts.slice(1, -1).map(() => "\u2022\u2022\u2022\u2022"), parts.at(-1)].join("-");
        }
        function resolveEntitlements(state) {
          return {
            premium: Boolean(state && state.status === "active"),
            removeAds: false,
            customThemes: false
          };
        }
        const api = {
          LICENSE_STORAGE_KEY,
          verifyActivationCode,
          activateLicense,
          deactivateLicense,
          getLicenseState,
          maskActivationCode,
          mapLicenseError,
          resolveEntitlements
        };
        global.TFALicense = api;
        if (typeof module !== "undefined" && module.exports) {
          module.exports = api;
        }
      })(typeof self !== "undefined" ? self : globalThis);
    }
  });

  // src/shared/dock-logic.js
  var require_dock_logic = __commonJS({
    "src/shared/dock-logic.js"(exports, module) {
      (function exposeDockLogic(root, factory) {
        "use strict";
        const api = factory();
        if (typeof module === "object" && module.exports) {
          module.exports = api;
        }
        root.TfaDockLogic = api;
      })(typeof globalThis === "object" ? globalThis : exports, function createDockLogic() {
        "use strict";
        const DOCK_STORAGE_KEY = "tfa_web_dock";
        const DOCK_TEXT_ICON_COLORS = Object.freeze([
          Object.freeze({ id: "violet", hex: "#6d5bd0" }),
          Object.freeze({ id: "blue", hex: "#2563eb" }),
          Object.freeze({ id: "cyan", hex: "#0e7490" }),
          Object.freeze({ id: "teal", hex: "#0f766e" }),
          Object.freeze({ id: "green", hex: "#15803d" }),
          Object.freeze({ id: "amber", hex: "#a16207" }),
          Object.freeze({ id: "orange", hex: "#c2410c" }),
          Object.freeze({ id: "red", hex: "#b91c1c" }),
          Object.freeze({ id: "rose", hex: "#be123c" }),
          Object.freeze({ id: "slate", hex: "#475569" })
        ]);
        const MOTION_LIMITS = Object.freeze({
          iconSize: Object.freeze([36, 72]),
          magnification: Object.freeze([1.2, 2.4]),
          influenceRadius: Object.freeze([80, 260]),
          response: Object.freeze([0.08, 0.32]),
          damping: Object.freeze([0.45, 0.92]),
          hideDelay: Object.freeze([0, 2e3])
        });
        const APPEARANCE_LIMITS = Object.freeze({
          tileSize: Object.freeze([44, 76]),
          trayPadding: Object.freeze([4, 24]),
          tileOpacity: Object.freeze([0, 100])
        });
        const FAVICON_SIZE_BUCKETS = Object.freeze([64, 128, 256, 512]);
        const DOCK_ICON_SOURCE_ORDER = Object.freeze({
          "page-svg": 0,
          "page-touch": 1,
          "page-icon": 2,
          saved: 3,
          tab: 4,
          chrome: 5
        });
        const DEFAULT_DOCK_CONFIG = Object.freeze({
          enabled: true,
          enabledUserSet: false,
          autoHide: false,
          sites: Object.freeze([]),
          motion: Object.freeze({
            iconSize: 48,
            magnification: 1.85,
            influenceRadius: 150,
            response: 0.18,
            damping: 0.72,
            hideDelay: 650
          }),
          appearance: Object.freeze({
            tileSize: 58,
            trayPadding: 10,
            tileColor: "#f8f8fa",
            tileOpacity: 100
          })
        });
        function clamp(value, limits) {
          return Math.min(limits[1], Math.max(limits[0], value));
        }
        function normalizeMotion(value) {
          const input = value && typeof value === "object" ? value : {};
          const motion = {};
          for (const key of Object.keys(MOTION_LIMITS)) {
            const candidate = input[key];
            motion[key] = Number.isFinite(candidate) ? clamp(candidate, MOTION_LIMITS[key]) : DEFAULT_DOCK_CONFIG.motion[key];
          }
          return motion;
        }
        function normalizeDockColor(value, fallback = DEFAULT_DOCK_CONFIG.appearance.tileColor) {
          const text = value == null ? "" : String(value).trim();
          if (/^#[\da-f]{6}$/i.test(text)) return text.toLowerCase();
          if (/^#[\da-f]{3}$/i.test(text)) {
            return `#${text.slice(1).split("").map((part) => part + part).join("")}`.toLowerCase();
          }
          return fallback;
        }
        function normalizeAppearance(value, legacyIconSize) {
          const input = value && typeof value === "object" ? value : null;
          const legacy = Number.isFinite(legacyIconSize) ? legacyIconSize : DEFAULT_DOCK_CONFIG.motion.iconSize;
          const migratedTile = clamp(
            Math.max(DEFAULT_DOCK_CONFIG.appearance.tileSize, legacy + 6),
            APPEARANCE_LIMITS.tileSize
          );
          return {
            tileSize: input && Number.isFinite(input.tileSize) ? clamp(input.tileSize, APPEARANCE_LIMITS.tileSize) : input ? DEFAULT_DOCK_CONFIG.appearance.tileSize : migratedTile,
            trayPadding: input && Number.isFinite(input.trayPadding) ? clamp(input.trayPadding, APPEARANCE_LIMITS.trayPadding) : DEFAULT_DOCK_CONFIG.appearance.trayPadding,
            tileColor: normalizeDockColor(
              input && input.tileColor,
              DEFAULT_DOCK_CONFIG.appearance.tileColor
            ),
            tileOpacity: input && Number.isFinite(input.tileOpacity) ? clamp(input.tileOpacity, APPEARANCE_LIMITS.tileOpacity) : DEFAULT_DOCK_CONFIG.appearance.tileOpacity
          };
        }
        function normalizeDockUrl(input) {
          let value = input == null ? "" : String(input).trim();
          if (!value) {
            throw new TypeError("\u8BF7\u8F93\u5165\u7F51\u9875\u5730\u5740");
          }
          const hasScheme = /^[a-z][a-z\d+.-]*:/i.test(value);
          if (!hasScheme) {
            value = `https://${value}`;
          }
          const parsed = new URL(value);
          if (!["http:", "https:", "file:"].includes(parsed.protocol)) {
            throw new TypeError("\u4E0D\u652F\u6301\u7684\u7F51\u5740\u534F\u8BAE");
          }
          return parsed.href;
        }
        function normalizeDockIconUrl(value) {
          const text = value == null ? "" : String(value).trim();
          if (!text || text.length > 8192) return "";
          try {
            const parsed = new URL(text);
            return parsed.protocol === "http:" || parsed.protocol === "https:" ? parsed.href : "";
          } catch (_error) {
            return "";
          }
        }
        function normalizeDockIconText(value) {
          const text = value == null ? "" : String(value).trim();
          return /^[A-Za-z\u3400-\u4DBF\u4E00-\u9FFF]{1,10}$/.test(text) ? text : "";
        }
        function normalizeDockTextIconColor(value, fallback = "") {
          const text = value == null ? "" : String(value).trim().toLowerCase();
          const match = DOCK_TEXT_ICON_COLORS.find((color) => color.id === text || color.hex === text);
          if (match) return match.hex;
          const fallbackText = fallback == null ? "" : String(fallback).trim().toLowerCase();
          const fallbackMatch = DOCK_TEXT_ICON_COLORS.find((color) => color.id === fallbackText || color.hex === fallbackText);
          return fallbackMatch ? fallbackMatch.hex : "";
        }
        function safeEphemeralDockIconUrl(value, baseUrl) {
          const text = value == null ? "" : String(value).trim();
          if (!text || text.length > 65536) return "";
          if (/^data:image\/(?:png|gif|jpe?g|webp|svg\+xml)(?:;[^,]*)?,/i.test(text)) return text;
          try {
            const parsed = new URL(text, baseUrl);
            return parsed.protocol === "http:" || parsed.protocol === "https:" ? parsed.href : "";
          } catch (_error) {
            return "";
          }
        }
        function compareDockIconCandidates(a, b) {
          const sourceDelta = (DOCK_ICON_SOURCE_ORDER[a && a.source] ?? 99) - (DOCK_ICON_SOURCE_ORDER[b && b.source] ?? 99);
          if (sourceDelta) return sourceDelta;
          const vectorDelta = Number(Boolean(b && b.vector)) - Number(Boolean(a && a.vector));
          if (vectorDelta) return vectorDelta;
          const sizeDelta = (Number(b && b.declaredSize) || 0) - (Number(a && a.declaredSize) || 0);
          if (sizeDelta) return sizeDelta;
          return String(a && a.url || "").localeCompare(String(b && b.url || ""));
        }
        function isFinalDockFaviconCandidate(candidate, naturalWidth, naturalHeight, requiredPixels) {
          if (!candidate || candidate.verified !== true) return false;
          if (candidate.vector === true) return true;
          const shortSide = Math.min(Number(naturalWidth) || 0, Number(naturalHeight) || 0);
          const threshold = Math.max(1, Math.ceil(Number(requiredPixels) || 1));
          return shortSide >= threshold;
        }
        function normalizeSite(site) {
          if (!site || typeof site !== "object") return null;
          const id = site.id == null ? "" : String(site.id).trim();
          if (!id) return null;
          try {
            const url = normalizeDockUrl(site.url);
            const suppliedTitle = site.title == null ? "" : String(site.title).trim();
            const title = suppliedTitle || new URL(url).hostname || url;
            const normalized = { id, title, url };
            const iconText = normalizeDockIconText(site.iconText);
            if (site.iconMode === "text" && iconText) {
              normalized.iconMode = "text";
              normalized.iconText = iconText;
              const iconColor = normalizeDockTextIconColor(site.iconColor);
              if (iconColor) normalized.iconColor = iconColor;
            } else {
              const iconUrl = normalizeDockIconUrl(site.iconUrl);
              if (iconUrl) {
                normalized.iconUrl = iconUrl;
                if (site.iconMode === "custom") normalized.iconMode = "custom";
              }
            }
            return normalized;
          } catch (_error) {
            return null;
          }
        }
        function normalizeDockConfig(value) {
          const input = value && typeof value === "object" ? value : {};
          const sites = [];
          const seenUrls = /* @__PURE__ */ new Set();
          const motion = normalizeMotion(input.motion);
          const appearance = normalizeAppearance(input.appearance, motion.iconSize);
          motion.iconSize = Math.min(motion.iconSize, appearance.tileSize - 6);
          if (Array.isArray(input.sites)) {
            for (const candidate of input.sites) {
              const site = normalizeSite(candidate);
              if (!site || seenUrls.has(site.url)) continue;
              seenUrls.add(site.url);
              sites.push(site);
            }
          }
          return {
            enabled: input.enabled !== false,
            enabledUserSet: input.enabledUserSet === true,
            autoHide: input.autoHide === true,
            sites,
            motion,
            appearance
          };
        }
        function isDockSiteFixed(config, url) {
          try {
            const normalizedUrl = normalizeDockUrl(url);
            return normalizeDockConfig(config).sites.some((site) => site.url === normalizedUrl);
          } catch (_error) {
            return false;
          }
        }
        function createSite(site) {
          const input = site && typeof site === "object" ? site : {};
          const url = normalizeDockUrl(input.url);
          const suppliedTitle = input.title == null ? "" : String(input.title).trim();
          const title = suppliedTitle || new URL(url).hostname || url;
          const suppliedId = input.id == null ? "" : String(input.id).trim();
          const randomSuffix = Math.random().toString(36).slice(2, 8) || "0";
          const id = suppliedId || `dock-${Date.now()}-${randomSuffix}`;
          const normalized = { id, title, url };
          const iconText = normalizeDockIconText(input.iconText);
          if (input.iconMode === "text" && iconText) {
            normalized.iconMode = "text";
            normalized.iconText = iconText;
            const iconColor = normalizeDockTextIconColor(input.iconColor);
            if (iconColor) normalized.iconColor = iconColor;
          } else {
            const iconUrl = normalizeDockIconUrl(input.iconUrl);
            if (iconUrl) {
              normalized.iconUrl = iconUrl;
              if (input.iconMode === "custom") normalized.iconMode = "custom";
            }
          }
          return normalized;
        }
        function upsertDockSite(config, site) {
          const normalized = normalizeDockConfig(config);
          const nextSite = createSite(site);
          const matchingIndex = normalized.sites.findIndex((candidate) => candidate.id === nextSite.id);
          const duplicate = normalized.sites.some((candidate, index) => candidate.url === nextSite.url && index !== matchingIndex);
          if (duplicate) {
            const error = new Error("\u8BE5\u7F51\u9875\u5DF2\u56FA\u5B9A");
            error.code = "DUPLICATE_URL";
            throw error;
          }
          if (matchingIndex === -1) {
            normalized.sites.push(nextSite);
          } else {
            normalized.sites[matchingIndex] = nextSite;
          }
          return normalized;
        }
        function removeDockSite(config, siteId) {
          const normalized = normalizeDockConfig(config);
          const id = siteId == null ? "" : String(siteId).trim();
          normalized.sites = normalized.sites.filter((site) => site.id !== id);
          return normalized;
        }
        function reorderDockSites(config, orderedIds) {
          const normalized = normalizeDockConfig(config);
          const remaining = normalized.sites.slice();
          const sites = [];
          if (Array.isArray(orderedIds)) {
            for (const rawId of orderedIds) {
              const id = rawId == null ? "" : String(rawId).trim();
              const index = remaining.findIndex((site) => site.id === id);
              if (index === -1) continue;
              sites.push(remaining.splice(index, 1)[0]);
            }
          }
          sites.push(...remaining);
          normalized.sites = sites;
          return normalized;
        }
        function moveDockSiteUrls(sites, fromIndex, insertionIndex) {
          const copy = Array.isArray(sites) ? sites.slice() : [];
          if (!Number.isInteger(fromIndex) || fromIndex < 0 || fromIndex >= copy.length) {
            return copy.map((site) => site.url);
          }
          const [moved] = copy.splice(fromIndex, 1);
          const target = Math.max(0, Math.min(copy.length, Number(insertionIndex) || 0));
          copy.splice(target, 0, moved);
          return copy.map((site) => site.url);
        }
        function reorderDockSitesByUrls(config, orderedUrls) {
          const normalized = normalizeDockConfig(config);
          const remaining = normalized.sites.slice();
          const sites = [];
          for (const rawUrl of Array.isArray(orderedUrls) ? orderedUrls : []) {
            let url;
            try {
              url = normalizeDockUrl(rawUrl);
            } catch (_error) {
              continue;
            }
            const index = remaining.findIndex((site) => site.url === url);
            if (index !== -1) sites.push(remaining.splice(index, 1)[0]);
          }
          normalized.sites = sites.concat(remaining);
          return normalized;
        }
        function toggleDockSite(config, site) {
          const normalized = normalizeDockConfig(config);
          const url = normalizeDockUrl(site && site.url);
          const fixedIndex = normalized.sites.findIndex((candidate) => candidate.url === url);
          if (fixedIndex !== -1) {
            normalized.sites.splice(fixedIndex, 1);
            return { config: normalized, fixed: false };
          }
          return { config: upsertDockSite(normalized, site), fixed: true };
        }
        function computeDockTargets(centers, pointerX, motion, appearance) {
          const positions = Array.isArray(centers) ? centers : [];
          if (!Number.isFinite(pointerX)) {
            return positions.map(() => ({ scale: 1, x: 0, y: 0 }));
          }
          const safeMotion = normalizeMotion(motion);
          const safeAppearance = normalizeAppearance(appearance, safeMotion.iconSize);
          const scales = positions.map((center) => {
            if (!Number.isFinite(center)) return 1;
            const distance = Math.abs(center - pointerX);
            if (distance >= safeMotion.influenceRadius) return 1;
            const phase = distance / safeMotion.influenceRadius * Math.PI / 2;
            const influence = Math.cos(phase) ** 2;
            return 1 + (safeMotion.magnification - 1) * influence;
          });
          const extraWidths = scales.map((scale) => (scale - 1) * safeAppearance.tileSize);
          return scales.map((scale, index) => {
            let leftExtra = 0;
            let rightExtra = 0;
            for (let other = 0; other < extraWidths.length; other += 1) {
              if (other < index) leftExtra += extraWidths[other];
              if (other > index) rightExtra += extraWidths[other];
            }
            return {
              scale,
              x: (leftExtra - rightExtra) / 2,
              y: -(scale - 1) * safeAppearance.tileSize * 0.58
            };
          });
        }
        function computeDockTooltipPlacement({
          viewportWidth,
          itemCenter,
          tooltipWidth,
          gutter = 8
        } = {}) {
          const safeViewportWidth = Number.isFinite(viewportWidth) && viewportWidth > 0 ? viewportWidth : 0;
          const requestedGutter = Number.isFinite(gutter) && gutter > 0 ? gutter : 0;
          const safeGutter = Math.min(requestedGutter, safeViewportWidth / 2);
          const maxTooltipWidth = Math.max(0, safeViewportWidth - safeGutter * 2);
          const safeTooltipWidth = Math.min(
            Number.isFinite(tooltipWidth) && tooltipWidth > 0 ? tooltipWidth : 0,
            maxTooltipWidth
          );
          const halfWidth = safeTooltipWidth / 2;
          const safeItemCenter = Number.isFinite(itemCenter) ? itemCenter : safeViewportWidth / 2;
          const minCenter = safeGutter + halfWidth;
          const maxCenter = safeViewportWidth - safeGutter - halfWidth;
          const bubbleCenter = minCenter <= maxCenter ? clamp(safeItemCenter, [minCenter, maxCenter]) : safeViewportWidth / 2;
          const arrowRange = Math.max(0, halfWidth - 11);
          const arrowShift = clamp(safeItemCenter - bubbleCenter, [-arrowRange, arrowRange]);
          return {
            bubbleCenter,
            labelShift: bubbleCenter - safeItemCenter,
            arrowShift
          };
        }
        function projectDockPointerX(baseCenter, pointerX, visualRect, iconSize) {
          if (!Number.isFinite(pointerX)) return pointerX;
          if (!Number.isFinite(baseCenter) || !visualRect || !Number.isFinite(visualRect.left) || !Number.isFinite(visualRect.width) || visualRect.width <= 0) {
            return pointerX;
          }
          const safeIconSize = Number.isFinite(iconSize) && iconSize > 0 ? iconSize : DEFAULT_DOCK_CONFIG.motion.iconSize;
          const visualLeft = visualRect.left;
          const visualRight = visualLeft + visualRect.width;
          const clampedPointer = clamp(pointerX, [visualLeft, visualRight]);
          const visualCenter = visualLeft + visualRect.width / 2;
          return baseCenter + (clampedPointer - visualCenter) * (safeIconSize / visualRect.width);
        }
        function requiredDockFaviconPixels(motion, devicePixelRatio) {
          const safeMotion = normalizeMotion(motion);
          const dpr = Number.isFinite(devicePixelRatio) && devicePixelRatio > 0 ? Math.max(1, devicePixelRatio) : 2;
          return Math.ceil(safeMotion.iconSize * safeMotion.magnification * dpr);
        }
        function selectDockFaviconSize(motion, devicePixelRatio) {
          const required = requiredDockFaviconPixels(motion, devicePixelRatio);
          return FAVICON_SIZE_BUCKETS.find((size) => size >= required) || 512;
        }
        function stepDockSpring(state, target, dt, motion) {
          const input = state && typeof state === "object" ? state : {};
          const value = Number.isFinite(input.value) ? input.value : 0;
          const oldVelocity = Number.isFinite(input.velocity) ? input.velocity : 0;
          const targetValue = Number.isFinite(target) ? target : value;
          const frameInput = Number.isFinite(dt) ? dt * 60 : 1;
          const frame = clamp(frameInput, [0.25, 2]);
          const safeMotion = normalizeMotion(motion);
          const acceleration = (targetValue - value) * safeMotion.response * frame;
          const velocity = (oldVelocity + acceleration) * safeMotion.damping ** frame;
          return { value: value + velocity, velocity };
        }
        return {
          DOCK_STORAGE_KEY,
          DOCK_TEXT_ICON_COLORS,
          MOTION_LIMITS,
          APPEARANCE_LIMITS,
          FAVICON_SIZE_BUCKETS,
          DEFAULT_DOCK_CONFIG,
          normalizeDockUrl,
          normalizeDockIconUrl,
          normalizeDockIconText,
          normalizeDockTextIconColor,
          normalizeDockColor,
          safeEphemeralDockIconUrl,
          compareDockIconCandidates,
          isFinalDockFaviconCandidate,
          normalizeAppearance,
          normalizeDockConfig,
          isDockSiteFixed,
          upsertDockSite,
          removeDockSite,
          reorderDockSites,
          reorderDockSitesByUrls,
          moveDockSiteUrls,
          toggleDockSite,
          computeDockTargets,
          computeDockTooltipPlacement,
          projectDockPointerX,
          requiredDockFaviconPixels,
          selectDockFaviconSize,
          stepDockSpring
        };
      });
    }
  });

  // src/shared/web-dock-recently-closed-logic.js
  var require_web_dock_recently_closed_logic = __commonJS({
    "src/shared/web-dock-recently-closed-logic.js"(exports, module) {
      (function attachWebDockRecentlyClosedLogic(root, factory) {
        const api = factory();
        if (typeof module === "object" && module.exports) module.exports = api;
        root.TfaWebDockRecentlyClosedLogic = api;
      })(typeof globalThis === "object" ? globalThis : exports, function createModule() {
        "use strict";
        const MAX_ITEMS = 25;
        function webUrl(value) {
          try {
            const parsed = new URL(String(value || "").trim());
            return parsed.protocol === "http:" || parsed.protocol === "https:" ? parsed.href : "";
          } catch (_error) {
            return "";
          }
        }
        function faviconUrl(value) {
          const raw = String(value || "").trim();
          if (/^data:image\//i.test(raw)) return raw;
          return webUrl(raw);
        }
        function collectRecentlyClosed(sessions) {
          const candidates = [];
          (Array.isArray(sessions) ? sessions : []).forEach((session, sessionIndex) => {
            const lastModified = Number(session?.lastModified) || 0;
            if (session?.url && !session?.tab && !session?.window) {
              const directSessionId = String(session.sessionId || "");
              const directSessionType = ["tab", "window", "url"].includes(session.sessionType) ? session.sessionType : directSessionId ? "tab" : "url";
              candidates.push({
                tab: session,
                sessionId: directSessionId,
                sessionType: directSessionType,
                itemId: String(session.id || ""),
                lastModified,
                order: sessionIndex * 1e3
              });
            }
            if (session?.tab) candidates.push({
              tab: session.tab,
              sessionId: String(session.sessionId || session.tab.sessionId || ""),
              sessionType: session.sessionId || session.tab.sessionId ? "tab" : "url",
              lastModified,
              order: sessionIndex * 1e3
            });
            if (Array.isArray(session?.window?.tabs)) {
              const windowSessionId = String(session.sessionId || session.window.sessionId || "");
              session.window.tabs.forEach((tab, tabIndex) => candidates.push({
                tab,
                // A card represents one website, not the whole closed window. Chrome's
                // window session id would restore every tab, so window children reopen
                // their own URL while retaining a stable per-child identity.
                sessionId: "",
                sessionType: "url",
                itemId: windowSessionId ? `session:${windowSessionId}:window:${tabIndex}` : tab?.sessionId ? `session:${tab.sessionId}` : "",
                windowTabIndex: tabIndex,
                lastModified,
                order: sessionIndex * 1e3 + tabIndex + 1
              }));
            }
          });
          candidates.sort((left, right) => right.lastModified - left.lastModified || left.order - right.order);
          const seen = /* @__PURE__ */ new Set();
          const result = [];
          for (const candidate of candidates) {
            const url = webUrl(candidate.tab?.url);
            if (!url) continue;
            const keyUrl = new URL(url);
            keyUrl.hash = "";
            if (seen.has(keyUrl.href)) continue;
            seen.add(keyUrl.href);
            const title = String(candidate.tab?.title || "").trim() || keyUrl.hostname;
            const sessionType = candidate.sessionId ? candidate.sessionType : "url";
            const id = candidate.itemId || (sessionType === "window" ? `session:${candidate.sessionId}:window:${candidate.windowTabIndex}` : candidate.sessionId ? `session:${candidate.sessionId}` : `url:${keyUrl.href}`);
            result.push(Object.freeze({
              id,
              sessionId: candidate.sessionId,
              sessionType,
              title,
              url,
              favIconUrl: faviconUrl(candidate.tab?.favIconUrl),
              lastModified: candidate.lastModified
            }));
          }
          return result;
        }
        function normalizeRecentlyClosed(sessions, limit = MAX_ITEMS) {
          const maximum = Math.max(0, Math.min(MAX_ITEMS, Number(limit) || MAX_ITEMS));
          return collectRecentlyClosed(sessions).slice(0, maximum);
        }
        function recentConsumptionKey(item = {}) {
          const id = String(item.id || "").trim();
          const url = webUrl(item.url);
          if (!id || !url) return "";
          const canonical = new URL(url);
          canonical.hash = "";
          return `${id}
${canonical.href}`;
        }
        return Object.freeze({ MAX_ITEMS, webUrl, collectRecentlyClosed, normalizeRecentlyClosed, recentConsumptionKey });
      });
    }
  });

  // src/shared/runtime-contract.js
  var require_runtime_contract = __commonJS({
    "src/shared/runtime-contract.js"(exports, module) {
      (function(global) {
        const FRAME_BRIDGE_FILES = ["frame_bridge.js"];
        const TOP_FRAME_BOOTSTRAP_FILES = ["content_bootstrap.js"];
        const TOP_FRAME_FEATURE_FILES = Object.freeze({
          web_dock: Object.freeze(["web_dock.js"]),
          workspace: Object.freeze(["workspace.js"]),
          guidance: Object.freeze(["guidance.js"])
        });
        const TOP_FRAME_FILES = TOP_FRAME_BOOTSTRAP_FILES;
        const CONTENT_SCRIPT_FILES = TOP_FRAME_BOOTSTRAP_FILES;
        const MANIFEST_LOAD_PHASES = Object.freeze({
          frameBridge: "document_start",
          topFrame: "document_start",
          pageWorld: "document_start"
        });
        const api = {
          FRAME_BRIDGE_FILES,
          TOP_FRAME_BOOTSTRAP_FILES,
          TOP_FRAME_FEATURE_FILES,
          TOP_FRAME_FILES,
          CONTENT_SCRIPT_FILES,
          MANIFEST_LOAD_PHASES
        };
        if (typeof module !== "undefined" && module.exports) module.exports = api;
        global.TfaRuntimeContract = api;
      })(typeof globalThis !== "undefined" ? globalThis : exports);
    }
  });

  // src/shared/active-trigger-logic.js
  var require_active_trigger_logic = __commonJS({
    "src/shared/active-trigger-logic.js"(exports, module) {
      (function attachActiveTriggerLogic(root, factory) {
        const api = factory();
        if (typeof module === "object" && module.exports) module.exports = api;
        root.TfaActiveTriggerLogic = api;
      })(typeof globalThis === "object" ? globalThis : exports, function createActiveTriggerLogic() {
        "use strict";
        const PROMPT_STORAGE_KEY = "tfa_active_guidance_prompt";
        const STATE_STORAGE_KEY = "tfa_active_trigger_state";
        const DISMISS_MESSAGE = "ACTIVE_GUIDANCE_DISMISS";
        const MUTE_TODAY_MESSAGE = "ACTIVE_GUIDANCE_MUTE_TODAY";
        const CLAIM_MESSAGE = "ACTIVE_GUIDANCE_CLAIM";
        const CONFIRM_MESSAGE = "ACTIVE_GUIDANCE_CONFIRM";
        const RELEASE_MESSAGE = "ACTIVE_GUIDANCE_RELEASE";
        const TEST_PROMPT_MESSAGE = "ACTIVE_GUIDANCE_TEST_PROMPT";
        const TEST_WINDOW_MESSAGE = "ACTIVE_GUIDANCE_TEST_WINDOW";
        const SWITCH_WINDOW_MS = 5e3;
        const SWITCH_THRESHOLD = 4;
        const SWITCH_COOLDOWN_MS = 5 * 6e4;
        const OVERLOAD_COOLDOWN_MS = 2 * 60 * 6e4;
        const DAILY_GUIDANCE_LIMIT = 2;
        const LOCAL_WORK_WINDOWS = Object.freeze([
          Object.freeze({ startMinute: 8 * 60, endMinute: 12 * 60 }),
          Object.freeze({ startMinute: 13 * 60, endMinute: 19 * 60 })
        ]);
        const PROMPT_PRIORITY = Object.freeze({
          "frequent-switch": 10,
          startup: 20,
          "overload-20": 30,
          "overload-30": 40
        });
        const PROMPT_TTL_MS = Object.freeze({
          "frequent-switch": 10 * 6e4,
          startup: 12 * 60 * 6e4,
          "overload-20": 24 * 60 * 6e4,
          "overload-30": 24 * 60 * 6e4
        });
        function finiteNonNegative(value, fallback = 0) {
          const number = Number(value);
          return Number.isFinite(number) && number >= 0 ? number : fallback;
        }
        function normalizeState(value, fallbackTabCount = null) {
          const source = value && typeof value === "object" ? value : {};
          const windowTabCounts = {};
          if (source.windowTabCounts && typeof source.windowTabCounts === "object" && !Array.isArray(source.windowTabCounts)) {
            for (const [windowId, countValue] of Object.entries(source.windowTabCounts)) {
              const count = Number(countValue);
              if (/^\d+$/.test(windowId) && Number.isInteger(count) && count >= 0) windowTabCounts[windowId] = count;
            }
          }
          const storedCount = source.previousTabCount === null || source.previousTabCount === void 0 ? NaN : Number(source.previousTabCount);
          const fallbackCount = Number(fallbackTabCount);
          return {
            version: 4,
            previousTabCount: Number.isInteger(storedCount) && storedCount >= 0 ? storedCount : Number.isInteger(fallbackCount) && fallbackCount >= 0 ? fallbackCount : null,
            windowTabCounts,
            windowSessionId: typeof source.windowSessionId === "string" ? source.windowSessionId : "",
            lastFrequentPromptAt: finiteNonNegative(source.lastFrequentPromptAt),
            lastAlertAt20: finiteNonNegative(source.lastAlertAt20),
            lastAlertAt30: finiteNonNegative(source.lastAlertAt30),
            lastFrequentPromptId: typeof source.lastFrequentPromptId === "string" ? source.lastFrequentPromptId : "",
            lastAlertPromptId20: typeof source.lastAlertPromptId20 === "string" ? source.lastAlertPromptId20 : "",
            lastAlertPromptId30: typeof source.lastAlertPromptId30 === "string" ? source.lastAlertPromptId30 : "",
            guidanceDay: typeof source.guidanceDay === "string" ? source.guidanceDay : "",
            guidanceCount: Math.min(
              DAILY_GUIDANCE_LIMIT,
              Math.floor(finiteNonNegative(source.guidanceCount))
            ),
            guidancePromptIds: Array.isArray(source.guidancePromptIds) ? [...new Set(source.guidancePromptIds.filter((id) => typeof id === "string" && id).slice(-DAILY_GUIDANCE_LIMIT))] : [],
            mutedGuidanceDay: typeof source.mutedGuidanceDay === "string" ? source.mutedGuidanceDay : typeof source.mutedOverloadDay === "string" ? source.mutedOverloadDay : "",
            // Retained for one schema generation so pre-V2 readers do not lose a mute.
            mutedOverloadDay: typeof source.mutedOverloadDay === "string" ? source.mutedOverloadDay : typeof source.mutedGuidanceDay === "string" ? source.mutedGuidanceDay : ""
          };
        }
        function localDateParts(timestamp, localTimeOptions = {}) {
          const date = new Date(finiteNonNegative(timestamp, Date.now()));
          const offsetValue = localTimeOptions?.utcOffsetMinutes;
          const offset = typeof offsetValue === "number" ? offsetValue : Number.NaN;
          if (Number.isFinite(offset) && offset >= -24 * 60 && offset <= 24 * 60) {
            const shifted = new Date(date.getTime() + offset * 6e4);
            return {
              year: shifted.getUTCFullYear(),
              month: shifted.getUTCMonth() + 1,
              day: shifted.getUTCDate(),
              hour: shifted.getUTCHours(),
              minute: shifted.getUTCMinutes()
            };
          }
          return {
            year: date.getFullYear(),
            month: date.getMonth() + 1,
            day: date.getDate(),
            hour: date.getHours(),
            minute: date.getMinutes()
          };
        }
        function localDayKey(timestamp, localTimeOptions) {
          const parts = localDateParts(timestamp, localTimeOptions);
          const year = parts.year;
          const month = String(parts.month).padStart(2, "0");
          const day = String(parts.day).padStart(2, "0");
          return `${year}-${month}-${day}`;
        }
        function isWithinLocalWorkHours(timestamp, localTimeOptions) {
          const parts = localDateParts(timestamp, localTimeOptions);
          const minute = parts.hour * 60 + parts.minute;
          return LOCAL_WORK_WINDOWS.some((window2) => minute >= window2.startMinute && minute < window2.endMinute);
        }
        function guidanceCountForDay(state, timestamp, localTimeOptions) {
          const normalized = normalizeState(state);
          return normalized.guidanceDay === localDayKey(timestamp, localTimeOptions) ? normalized.guidanceCount : 0;
        }
        function canShowAutomatedPrompt(state, timestamp, localTimeOptions) {
          const normalized = normalizeState(state);
          const now = finiteNonNegative(timestamp, Date.now());
          return !isMutedToday(normalized, now, localTimeOptions) && isWithinLocalWorkHours(now, localTimeOptions) && guidanceCountForDay(normalized, now, localTimeOptions) < DAILY_GUIDANCE_LIMIT;
        }
        function markAutomatedPromptShown(state, timestamp, promptId = "", localTimeOptions) {
          const normalized = normalizeState(state);
          const now = finiteNonNegative(timestamp, Date.now());
          const day = localDayKey(now, localTimeOptions);
          const count = normalized.guidanceDay === day ? normalized.guidanceCount : 0;
          const ids = normalized.guidanceDay === day ? normalized.guidancePromptIds : [];
          const id = typeof promptId === "string" ? promptId : "";
          if (id && ids.includes(id)) return normalized;
          return {
            ...normalized,
            guidanceDay: day,
            guidanceCount: Math.min(DAILY_GUIDANCE_LIMIT, count + 1),
            guidancePromptIds: id ? [...ids, id].slice(-DAILY_GUIDANCE_LIMIT) : ids
          };
        }
        function rollbackAutomatedPromptShown(state, timestamp, promptId, localTimeOptions) {
          const normalized = normalizeState(state);
          const id = typeof promptId === "string" ? promptId : "";
          const day = localDayKey(timestamp, localTimeOptions);
          if (!id || normalized.guidanceDay !== day || !normalized.guidancePromptIds.includes(id)) return normalized;
          return {
            ...normalized,
            guidanceCount: Math.max(0, normalized.guidanceCount - 1),
            guidancePromptIds: normalized.guidancePromptIds.filter((value) => value !== id)
          };
        }
        function recordActivation(timestamps, timestamp) {
          const now = finiteNonNegative(timestamp, Date.now());
          const recent = (Array.isArray(timestamps) ? timestamps : []).map((value) => finiteNonNegative(value, -1)).filter((value) => value >= 0 && value >= now - SWITCH_WINDOW_MS && value <= now);
          recent.push(now);
          return { timestamps: recent, shouldPrompt: recent.length > SWITCH_THRESHOLD };
        }
        function crossedOverloadThreshold(previousCount, currentCount) {
          if (previousCount === null || previousCount === void 0 || previousCount === "") return null;
          const previous = Number(previousCount);
          const current = Number(currentCount);
          if (!Number.isInteger(previous) || previous < 0 || !Number.isInteger(current) || current < 0) return null;
          if (previous === 29 && current === 30) return 30;
          if (previous === 19 && current === 20) return 20;
          return null;
        }
        function currentOverloadThreshold(tabCount) {
          const count = Number(tabCount);
          if (!Number.isInteger(count) || count < 20) return null;
          return count >= 30 ? 30 : 20;
        }
        function canShowFrequentPrompt(state, timestamp, localTimeOptions) {
          const normalized = normalizeState(state);
          const now = finiteNonNegative(timestamp, Date.now());
          if (!canShowAutomatedPrompt(normalized, now, localTimeOptions)) return false;
          return !normalized.lastFrequentPromptAt || now - normalized.lastFrequentPromptAt >= SWITCH_COOLDOWN_MS;
        }
        function canShowOverloadPrompt(state, threshold, timestamp, localTimeOptions) {
          const normalized = normalizeState(state);
          const now = finiteNonNegative(timestamp, Date.now());
          if (!canShowAutomatedPrompt(normalized, now, localTimeOptions)) return false;
          const lastAlertAt = threshold === 30 ? normalized.lastAlertAt30 : normalized.lastAlertAt20;
          return !lastAlertAt || now - lastAlertAt >= OVERLOAD_COOLDOWN_MS;
        }
        function markFrequentPrompt(state, timestamp, promptId = "") {
          return {
            ...normalizeState(state),
            lastFrequentPromptAt: finiteNonNegative(timestamp, Date.now()),
            lastFrequentPromptId: typeof promptId === "string" ? promptId : ""
          };
        }
        function markOverloadPrompt(state, threshold, timestamp, promptId = "") {
          const next = normalizeState(state);
          const field = threshold === 30 ? "lastAlertAt30" : "lastAlertAt20";
          const idField = threshold === 30 ? "lastAlertPromptId30" : "lastAlertPromptId20";
          next[field] = finiteNonNegative(timestamp, Date.now());
          next[idField] = typeof promptId === "string" ? promptId : "";
          return next;
        }
        function rollbackPromptCooldown(state, prompt, rollback = {}) {
          const normalized = normalizeState(state);
          const id = typeof prompt?.id === "string" ? prompt.id : "";
          if (!id) return normalized;
          if (prompt.type === "frequent-switch" && normalized.lastFrequentPromptId === id) {
            normalized.lastFrequentPromptAt = finiteNonNegative(rollback.lastFrequentPromptAt);
            normalized.lastFrequentPromptId = typeof rollback.lastFrequentPromptId === "string" ? rollback.lastFrequentPromptId : "";
          }
          if (prompt.type === "overload-20" && normalized.lastAlertPromptId20 === id) {
            normalized.lastAlertAt20 = finiteNonNegative(rollback.lastAlertAt20);
            normalized.lastAlertPromptId20 = typeof rollback.lastAlertPromptId20 === "string" ? rollback.lastAlertPromptId20 : "";
          }
          if (prompt.type === "overload-30" && normalized.lastAlertPromptId30 === id) {
            normalized.lastAlertAt30 = finiteNonNegative(rollback.lastAlertAt30);
            normalized.lastAlertPromptId30 = typeof rollback.lastAlertPromptId30 === "string" ? rollback.lastAlertPromptId30 : "";
          }
          return normalized;
        }
        function isMutedToday(state, timestamp, localTimeOptions) {
          const normalized = normalizeState(state);
          return normalized.mutedGuidanceDay === localDayKey(timestamp, localTimeOptions);
        }
        function muteToday(state, timestamp, localTimeOptions) {
          const day = localDayKey(timestamp, localTimeOptions);
          return { ...normalizeState(state), mutedGuidanceDay: day, mutedOverloadDay: day };
        }
        function muteOverloadToday(state, timestamp, localTimeOptions) {
          return muteToday(state, timestamp, localTimeOptions);
        }
        function createPrompt(type, timestamp, options = {}) {
          const now = finiteNonNegative(timestamp, Date.now());
          const normalizedType = type === "overload" ? Number(options.threshold) >= 30 ? "overload-30" : "overload-20" : type;
          const priority = PROMPT_PRIORITY[normalizedType];
          if (!priority) return null;
          return Object.freeze({
            id: `${normalizedType}:${now}`,
            type: normalizedType,
            threshold: normalizedType.startsWith("overload-") ? Number(normalizedType.slice(-2)) : null,
            priority,
            createdAt: now,
            expiresAt: now + PROMPT_TTL_MS[normalizedType],
            allowMute: true,
            automated: options.automated !== false,
            claim: null
          });
        }
        function normalizePrompt(value, timestamp = Date.now()) {
          if (!value || typeof value !== "object" || !PROMPT_PRIORITY[value.type]) return null;
          const createdAt = finiteNonNegative(value.createdAt, 0);
          const expiresAt = finiteNonNegative(value.expiresAt, 0);
          if (!createdAt || !expiresAt || expiresAt <= finiteNonNegative(timestamp, Date.now())) return null;
          const claimTabId = Number(value.claim?.tabId);
          const claimedAt = finiteNonNegative(value.claim?.claimedAt);
          const documentId = typeof value.claim?.documentId === "string" && value.claim.documentId ? value.claim.documentId : "";
          const sourceUrl = typeof value.claim?.sourceUrl === "string" && value.claim.sourceUrl ? value.claim.sourceUrl : "";
          const rollback = value.claim?.rollback && typeof value.claim.rollback === "object" ? { ...value.claim.rollback } : null;
          const claim = Number.isInteger(claimTabId) && claimTabId >= 0 && claimedAt > 0 ? {
            tabId: claimTabId,
            claimedAt,
            counted: value.claim?.counted === true,
            visible: value.claim?.visible === true,
            ...documentId ? { documentId } : {},
            ...sourceUrl ? { sourceUrl } : {},
            ...rollback ? { rollback } : {}
          } : null;
          return {
            id: typeof value.id === "string" && value.id ? value.id : `${value.type}:${createdAt}`,
            type: value.type,
            threshold: value.type.startsWith("overload-") ? Number(value.type.slice(-2)) : null,
            priority: PROMPT_PRIORITY[value.type],
            createdAt,
            expiresAt,
            allowMute: true,
            // Production prompts from the previous schema did not persist this flag.
            // Only an explicit false denotes a manual/debug prompt that bypasses gates.
            automated: value.automated !== false,
            claim
          };
        }
        function shouldReplacePrompt(current, next, timestamp = Date.now()) {
          const currentPrompt = normalizePrompt(current, timestamp);
          const nextPrompt = normalizePrompt(next, timestamp);
          if (!nextPrompt) return false;
          if (!currentPrompt) return true;
          return nextPrompt.priority > currentPrompt.priority;
        }
        return Object.freeze({
          PROMPT_STORAGE_KEY,
          STATE_STORAGE_KEY,
          DISMISS_MESSAGE,
          MUTE_TODAY_MESSAGE,
          CLAIM_MESSAGE,
          CONFIRM_MESSAGE,
          RELEASE_MESSAGE,
          TEST_PROMPT_MESSAGE,
          TEST_WINDOW_MESSAGE,
          SWITCH_WINDOW_MS,
          SWITCH_THRESHOLD,
          SWITCH_COOLDOWN_MS,
          OVERLOAD_COOLDOWN_MS,
          DAILY_GUIDANCE_LIMIT,
          LOCAL_WORK_WINDOWS,
          PROMPT_PRIORITY,
          PROMPT_TTL_MS,
          normalizeState,
          localDateParts,
          localDayKey,
          recordActivation,
          isWithinLocalWorkHours,
          guidanceCountForDay,
          canShowAutomatedPrompt,
          markAutomatedPromptShown,
          rollbackAutomatedPromptShown,
          crossedOverloadThreshold,
          currentOverloadThreshold,
          canShowFrequentPrompt,
          canShowOverloadPrompt,
          markFrequentPrompt,
          markOverloadPrompt,
          rollbackPromptCooldown,
          isMutedToday,
          muteToday,
          muteOverloadToday,
          createPrompt,
          normalizePrompt,
          shouldReplacePrompt
        });
      });
    }
  });

  // src/shared/web-dock-catalog.js
  var require_web_dock_catalog = __commonJS({
    "src/shared/web-dock-catalog.js"(exports, module) {
      (() => {
        const rows = [
          ["chatgpt", "ChatGPT", "https://chatgpt.com/", "AI"],
          ["claude", "Claude", "https://claude.ai/", "AI"],
          ["gemini", "Gemini", "https://gemini.google.com/", "AI"],
          ["copilot", "Microsoft Copilot", "https://copilot.microsoft.com/", "AI"],
          ["perplexity", "Perplexity", "https://www.perplexity.ai/", "AI"],
          ["grok", "Grok", "https://grok.com/", "AI"],
          ["deepseek", "DeepSeek", "https://chat.deepseek.com/", "AI"],
          ["qwen", "\u901A\u4E49\u5343\u95EE", "https://www.qianwen.com/", "AI"],
          ["kimi", "Kimi", "https://www.kimi.com/", "AI"],
          ["doubao", "\u8C46\u5305", "https://www.doubao.com/", "AI"],
          ["wenxin", "\u6587\u5FC3\u4E00\u8A00", "https://yiyan.baidu.com/", "AI"],
          ["yuanbao", "\u817E\u8BAF\u5143\u5B9D", "https://yuanbao.tencent.com/", "AI"],
          ["zhipu", "\u667A\u8C31\u6E05\u8A00", "https://chatglm.cn/", "AI"],
          ["mistral", "Mistral AI", "https://chat.mistral.ai/", "AI"],
          ["huggingface", "Hugging Face", "https://huggingface.co/", "AI"],
          ["poe", "Poe", "https://poe.com/", "AI"],
          ["cohere", "Cohere", "https://cohere.com/", "AI"],
          ["characterai", "Character.AI", "https://character.ai/", "AI"],
          ["midjourney", "Midjourney", "https://www.midjourney.com/", "AI"],
          ["suno", "Suno", "https://suno.com/", "AI"],
          ["google", "Google", "https://www.google.com/", "\u5DE5\u5177"],
          ["bing", "Bing", "https://www.bing.com/", "\u5DE5\u5177"],
          ["baidu", "\u767E\u5EA6", "https://www.baidu.com/", "\u5DE5\u5177"],
          ["yahoo", "Yahoo", "https://www.yahoo.com/", "\u5DE5\u5177"],
          ["duckduckgo", "DuckDuckGo", "https://duckduckgo.com/", "\u5DE5\u5177"],
          ["yandex", "Yandex", "https://yandex.com/", "\u5DE5\u5177"],
          ["naver", "Naver", "https://www.naver.com/", "\u5DE5\u5177"],
          ["notion", "Notion", "https://www.notion.so/", "\u5DE5\u5177"],
          ["googledrive", "Google Drive", "https://drive.google.com/", "\u5DE5\u5177"],
          ["gmail", "Gmail", "https://mail.google.com/", "\u5DE5\u5177"],
          ["outlook", "Outlook", "https://outlook.live.com/", "\u5DE5\u5177"],
          ["dropbox", "Dropbox", "https://www.dropbox.com/", "\u5DE5\u5177"],
          ["onedrive", "OneDrive", "https://onedrive.live.com/", "\u5DE5\u5177"],
          ["box", "Box", "https://www.box.com/", "\u5DE5\u5177"],
          ["slack", "Slack", "https://slack.com/", "\u5DE5\u5177"],
          ["trello", "Trello", "https://trello.com/", "\u5DE5\u5177"],
          ["asana", "Asana", "https://asana.com/", "\u5DE5\u5177"],
          ["airtable", "Airtable", "https://www.airtable.com/", "\u5DE5\u5177"],
          ["evernote", "Evernote", "https://evernote.com/", "\u5DE5\u5177"],
          ["zoom", "Zoom", "https://zoom.us/", "\u5DE5\u5177"],
          ["facebook", "Facebook", "https://www.facebook.com/", "\u793E\u4EA4"],
          ["instagram", "Instagram", "https://www.instagram.com/", "\u793E\u4EA4"],
          ["x", "X", "https://x.com/", "\u793E\u4EA4"],
          ["threads", "Threads", "https://www.threads.net/", "\u793E\u4EA4"],
          ["tiktok", "TikTok", "https://www.tiktok.com/", "\u793E\u4EA4"],
          ["linkedin", "LinkedIn", "https://www.linkedin.com/", "\u793E\u4EA4"],
          ["reddit", "Reddit", "https://www.reddit.com/", "\u793E\u4EA4"],
          ["pinterest", "Pinterest", "https://www.pinterest.com/", "\u793E\u4EA4"],
          ["discord", "Discord", "https://discord.com/", "\u793E\u4EA4"],
          ["telegram", "Telegram", "https://telegram.org/", "\u793E\u4EA4"],
          ["whatsapp", "WhatsApp", "https://www.whatsapp.com/", "\u793E\u4EA4"],
          ["wechat", "\u5FAE\u4FE1", "https://weixin.qq.com/", "\u793E\u4EA4"],
          ["qq", "QQ", "https://im.qq.com/", "\u793E\u4EA4"],
          ["weibo", "\u5FAE\u535A", "https://weibo.com/", "\u793E\u4EA4"],
          ["zhihu", "\u77E5\u4E4E", "https://www.zhihu.com/", "\u793E\u4EA4"],
          ["douban", "\u8C46\u74E3", "https://www.douban.com/", "\u793E\u4EA4"],
          ["xiaohongshu", "\u5C0F\u7EA2\u4E66", "https://www.xiaohongshu.com/", "\u793E\u4EA4"],
          ["kuaishou", "\u5FEB\u624B", "https://www.kuaishou.com/", "\u793E\u4EA4"],
          ["bilibili", "\u54D4\u54E9\u54D4\u54E9", "https://www.bilibili.com/", "\u793E\u4EA4"],
          ["douyin", "\u6296\u97F3", "https://www.douyin.com/", "\u793E\u4EA4"],
          ["youtube", "YouTube", "https://www.youtube.com/", "\u5F71\u97F3"],
          ["netflix", "Netflix", "https://www.netflix.com/", "\u5F71\u97F3"],
          ["spotify", "Spotify", "https://open.spotify.com/", "\u5F71\u97F3"],
          ["applemusic", "Apple Music", "https://music.apple.com/", "\u5F71\u97F3"],
          ["soundcloud", "SoundCloud", "https://soundcloud.com/", "\u5F71\u97F3"],
          ["twitch", "Twitch", "https://www.twitch.tv/", "\u5F71\u97F3"],
          ["vimeo", "Vimeo", "https://vimeo.com/", "\u5F71\u97F3"],
          ["disneyplus", "Disney+", "https://www.disneyplus.com/", "\u5F71\u97F3"],
          ["primevideo", "Prime Video", "https://www.primevideo.com/", "\u5F71\u97F3"],
          ["max", "Max", "https://www.max.com/", "\u5F71\u97F3"],
          ["hulu", "Hulu", "https://www.hulu.com/", "\u5F71\u97F3"],
          ["iqiyi", "\u7231\u5947\u827A", "https://www.iqiyi.com/", "\u5F71\u97F3"],
          ["tencentvideo", "\u817E\u8BAF\u89C6\u9891", "https://v.qq.com/", "\u5F71\u97F3"],
          ["youku", "\u4F18\u9177", "https://www.youku.com/", "\u5F71\u97F3"],
          ["mangotv", "\u8292\u679CTV", "https://www.mgtv.com/", "\u5F71\u97F3"],
          ["neteasemusic", "\u7F51\u6613\u4E91\u97F3\u4E50", "https://music.163.com/", "\u5F71\u97F3"],
          ["qqmusic", "QQ\u97F3\u4E50", "https://y.qq.com/", "\u5F71\u97F3"],
          ["kugou", "\u9177\u72D7\u97F3\u4E50", "https://www.kugou.com/", "\u5F71\u97F3"],
          ["ximalaya", "\u559C\u9A6C\u62C9\u96C5", "https://www.ximalaya.com/", "\u5F71\u97F3"],
          ["bandcamp", "Bandcamp", "https://bandcamp.com/", "\u5F71\u97F3"],
          ["github", "GitHub", "https://github.com/", "\u5F00\u53D1"],
          ["gitlab", "GitLab", "https://gitlab.com/", "\u5F00\u53D1"],
          ["bitbucket", "Bitbucket", "https://bitbucket.org/", "\u5F00\u53D1"],
          ["stackoverflow", "Stack Overflow", "https://stackoverflow.com/", "\u5F00\u53D1"],
          ["npm", "npm", "https://www.npmjs.com/", "\u5F00\u53D1"],
          ["mdn", "MDN", "https://developer.mozilla.org/", "\u5F00\u53D1"],
          ["docker", "Docker", "https://www.docker.com/", "\u5F00\u53D1"],
          ["kubernetes", "Kubernetes", "https://kubernetes.io/", "\u5F00\u53D1"],
          ["vercel", "Vercel", "https://vercel.com/", "\u5F00\u53D1"],
          ["netlify", "Netlify", "https://www.netlify.com/", "\u5F00\u53D1"],
          ["cloudflare", "Cloudflare", "https://www.cloudflare.com/", "\u5F00\u53D1"],
          ["aws", "AWS", "https://aws.amazon.com/", "\u5F00\u53D1"],
          ["azure", "Microsoft Azure", "https://azure.microsoft.com/", "\u5F00\u53D1"],
          ["googlecloud", "Google Cloud", "https://cloud.google.com/", "\u5F00\u53D1"],
          ["firebase", "Firebase", "https://firebase.google.com/", "\u5F00\u53D1"],
          ["supabase", "Supabase", "https://supabase.com/", "\u5F00\u53D1"],
          ["codepen", "CodePen", "https://codepen.io/", "\u5F00\u53D1"],
          ["replit", "Replit", "https://replit.com/", "\u5F00\u53D1"],
          ["leetcode", "LeetCode", "https://leetcode.com/", "\u5F00\u53D1"],
          ["hackerrank", "HackerRank", "https://www.hackerrank.com/", "\u5F00\u53D1"],
          ["jetbrains", "JetBrains", "https://www.jetbrains.com/", "\u5F00\u53D1"],
          ["vscode", "Visual Studio Code", "https://code.visualstudio.com/", "\u5F00\u53D1"],
          ["pypi", "PyPI", "https://pypi.org/", "\u5F00\u53D1"],
          ["rust", "Rust", "https://www.rust-lang.org/", "\u5F00\u53D1"],
          ["golang", "Go", "https://go.dev/", "\u5F00\u53D1"],
          ["figma", "Figma", "https://www.figma.com/", "\u8BBE\u8BA1"],
          ["canva", "Canva", "https://www.canva.com/", "\u8BBE\u8BA1"],
          ["dribbble", "Dribbble", "https://dribbble.com/", "\u8BBE\u8BA1"],
          ["behance", "Behance", "https://www.behance.net/", "\u8BBE\u8BA1"],
          ["adobe", "Adobe", "https://www.adobe.com/", "\u8BBE\u8BA1"],
          ["photoshop", "Photoshop Web", "https://photoshop.adobe.com/", "\u8BBE\u8BA1"],
          ["pixiv", "Pixiv", "https://www.pixiv.net/", "\u8BBE\u8BA1"],
          ["pixabay", "Pixabay", "https://pixabay.com/", "\u8BBE\u8BA1"],
          ["unsplash", "Unsplash", "https://unsplash.com/", "\u8BBE\u8BA1"],
          ["pexels", "Pexels", "https://www.pexels.com/", "\u8BBE\u8BA1"],
          ["freepik", "Freepik", "https://www.freepik.com/", "\u8BBE\u8BA1"],
          ["sketch", "Sketch", "https://www.sketch.com/", "\u8BBE\u8BA1"],
          ["framer", "Framer", "https://www.framer.com/", "\u8BBE\u8BA1"],
          ["miro", "Miro", "https://miro.com/", "\u8BBE\u8BA1"],
          ["excalidraw", "Excalidraw", "https://excalidraw.com/", "\u8BBE\u8BA1"],
          ["amazon", "Amazon", "https://www.amazon.com/", "\u8D2D\u7269"],
          ["ebay", "eBay", "https://www.ebay.com/", "\u8D2D\u7269"],
          ["aliexpress", "AliExpress", "https://www.aliexpress.com/", "\u8D2D\u7269"],
          ["walmart", "Walmart", "https://www.walmart.com/", "\u8D2D\u7269"],
          ["etsy", "Etsy", "https://www.etsy.com/", "\u8D2D\u7269"],
          ["shopify", "Shopify", "https://www.shopify.com/", "\u8D2D\u7269"],
          ["temu", "Temu", "https://www.temu.com/", "\u8D2D\u7269"],
          ["taobao", "\u6DD8\u5B9D", "https://www.taobao.com/", "\u8D2D\u7269"],
          ["tmall", "\u5929\u732B", "https://www.tmall.com/", "\u8D2D\u7269"],
          ["jd", "\u4EAC\u4E1C", "https://www.jd.com/", "\u8D2D\u7269"],
          ["pinduoduo", "\u62FC\u591A\u591A", "https://www.pinduoduo.com/", "\u8D2D\u7269"],
          ["suning", "\u82CF\u5B81\u6613\u8D2D", "https://www.suning.com/", "\u8D2D\u7269"],
          ["vipshop", "\u552F\u54C1\u4F1A", "https://www.vip.com/", "\u8D2D\u7269"],
          ["dangdang", "\u5F53\u5F53", "https://www.dangdang.com/", "\u8D2D\u7269"],
          ["xianyu", "\u95F2\u9C7C", "https://www.goofish.com/", "\u8D2D\u7269"],
          ["meituan", "\u7F8E\u56E2", "https://www.meituan.com/", "\u8D2D\u7269"],
          ["dianping", "\u5927\u4F17\u70B9\u8BC4", "https://www.dianping.com/", "\u8D2D\u7269"],
          ["ctrip", "\u643A\u7A0B", "https://www.ctrip.com/", "\u8D2D\u7269"],
          ["qunar", "\u53BB\u54EA\u513F", "https://www.qunar.com/", "\u8D2D\u7269"],
          ["airbnb", "Airbnb", "https://www.airbnb.com/", "\u8D2D\u7269"],
          ["360", "360\u641C\u7D22", "https://www.so.com/", "\u56FD\u5185"],
          ["sogou", "\u641C\u72D7", "https://www.sogou.com/", "\u56FD\u5185"],
          ["shenma", "\u795E\u9A6C\u641C\u7D22", "https://m.sm.cn/", "\u56FD\u5185"],
          ["netease", "\u7F51\u6613", "https://www.163.com/", "\u56FD\u5185"],
          ["sina", "\u65B0\u6D6A", "https://www.sina.com.cn/", "\u56FD\u5185"],
          ["sohu", "\u641C\u72D0", "https://www.sohu.com/", "\u56FD\u5185"],
          ["toutiao", "\u4ECA\u65E5\u5934\u6761", "https://www.toutiao.com/", "\u56FD\u5185"],
          ["thepaper", "\u6F8E\u6E43\u65B0\u95FB", "https://www.thepaper.cn/", "\u56FD\u5185"],
          ["caixin", "\u8D22\u65B0", "https://www.caixin.com/", "\u56FD\u5185"],
          ["huxiu", "\u864E\u55C5", "https://www.huxiu.com/", "\u56FD\u5185"],
          ["36kr", "36\u6C2A", "https://36kr.com/", "\u56FD\u5185"],
          ["eastmoney", "\u4E1C\u65B9\u8D22\u5BCC", "https://www.eastmoney.com/", "\u56FD\u5185"],
          ["xueqiu", "\u96EA\u7403", "https://xueqiu.com/", "\u56FD\u5185"],
          ["10jqka", "\u540C\u82B1\u987A", "https://www.10jqka.com.cn/", "\u56FD\u5185"],
          ["boss", "BOSS\u76F4\u8058", "https://www.zhipin.com/", "\u56FD\u5185"],
          ["zhaopin", "\u667A\u8054\u62DB\u8058", "https://www.zhaopin.com/", "\u56FD\u5185"],
          ["51job", "\u524D\u7A0B\u65E0\u5FE7", "https://www.51job.com/", "\u56FD\u5185"],
          ["liepin", "\u730E\u8058", "https://www.liepin.com/", "\u56FD\u5185"],
          ["dingtalk", "\u9489\u9489", "https://www.dingtalk.com/", "\u56FD\u5185"],
          ["feishu", "\u98DE\u4E66", "https://www.feishu.cn/", "\u56FD\u5185"],
          ["tencentdocs", "\u817E\u8BAF\u6587\u6863", "https://docs.qq.com/", "\u56FD\u5185"],
          ["wps", "WPS", "https://www.wps.cn/", "\u56FD\u5185"],
          ["youdao", "\u6709\u9053", "https://www.youdao.com/", "\u56FD\u5185"],
          ["baidupan", "\u767E\u5EA6\u7F51\u76D8", "https://pan.baidu.com/", "\u56FD\u5185"],
          ["aliyun", "\u963F\u91CC\u4E91", "https://www.aliyun.com/", "\u56FD\u5185"],
          ["tencentcloud", "\u817E\u8BAF\u4E91", "https://cloud.tencent.com/", "\u56FD\u5185"],
          ["huaweicloud", "\u534E\u4E3A\u4E91", "https://www.huaweicloud.com/", "\u56FD\u5185"],
          ["gitee", "Gitee", "https://gitee.com/", "\u56FD\u5185"],
          ["csdn", "CSDN", "https://www.csdn.net/", "\u56FD\u5185"],
          ["juejin", "\u7A00\u571F\u6398\u91D1", "https://juejin.cn/", "\u56FD\u5185"],
          ["cnblogs", "\u535A\u5BA2\u56ED", "https://www.cnblogs.com/", "\u56FD\u5185"],
          ["segmentfault", "SegmentFault", "https://segmentfault.com/", "\u56FD\u5185"],
          ["oschina", "\u5F00\u6E90\u4E2D\u56FD", "https://www.oschina.net/", "\u56FD\u5185"],
          ["lanzou", "\u84DD\u594F\u4E91", "https://www.lanzou.com/", "\u56FD\u5185"],
          ["quark", "\u5938\u514B", "https://www.quark.cn/", "\u56FD\u5185"],
          ["uc", "UC\u6D4F\u89C8\u5668", "https://www.uc.cn/", "\u56FD\u5185"],
          ["amap", "\u9AD8\u5FB7\u5730\u56FE", "https://www.amap.com/", "\u56FD\u5185"],
          ["baidumaps", "\u767E\u5EA6\u5730\u56FE", "https://map.baidu.com/", "\u56FD\u5185"],
          ["eleme", "\u997F\u4E86\u4E48", "https://www.ele.me/", "\u56FD\u5185"],
          ["cainiao", "\u83DC\u9E1F", "https://www.cainiao.com/", "\u56FD\u5185"],
          ["wikipedia", "Wikipedia", "https://www.wikipedia.org/", "\u77E5\u8BC6"],
          ["britannica", "Britannica", "https://www.britannica.com/", "\u77E5\u8BC6"],
          ["medium", "Medium", "https://medium.com/", "\u77E5\u8BC6"],
          ["quora", "Quora", "https://www.quora.com/", "\u77E5\u8BC6"],
          ["substack", "Substack", "https://substack.com/", "\u77E5\u8BC6"],
          ["nytimes", "The New York Times", "https://www.nytimes.com/", "\u77E5\u8BC6"],
          ["bbc", "BBC", "https://www.bbc.com/", "\u77E5\u8BC6"],
          ["cnn", "CNN", "https://www.cnn.com/", "\u77E5\u8BC6"],
          ["reuters", "Reuters", "https://www.reuters.com/", "\u77E5\u8BC6"],
          ["bloomberg", "Bloomberg", "https://www.bloomberg.com/", "\u77E5\u8BC6"],
          ["guardian", "The Guardian", "https://www.theguardian.com/", "\u77E5\u8BC6"],
          ["wsj", "The Wall Street Journal", "https://www.wsj.com/", "\u77E5\u8BC6"],
          ["ft", "Financial Times", "https://www.ft.com/", "\u77E5\u8BC6"],
          ["npr", "NPR", "https://www.npr.org/", "\u77E5\u8BC6"],
          ["apnews", "AP News", "https://apnews.com/", "\u77E5\u8BC6"],
          ["economist", "The Economist", "https://www.economist.com/", "\u77E5\u8BC6"],
          ["gitbook", "GitBook", "https://www.gitbook.com/", "\u77E5\u8BC6"],
          ["archive", "Internet Archive", "https://archive.org/", "\u77E5\u8BC6"],
          ["coursera", "Coursera", "https://www.coursera.org/", "\u77E5\u8BC6"],
          ["edx", "edX", "https://www.edx.org/", "\u77E5\u8BC6"]
        ];
        const svgIds = /* @__PURE__ */ new Set([
          "airbnb",
          "applemusic",
          "baidu",
          "bandcamp",
          "bbc",
          "copilot",
          "github",
          "golang",
          "google",
          "juejin",
          "lanzou",
          "rust",
          "shenma",
          "threads",
          "vimeo",
          "whatsapp",
          "x"
        ]);
        const sites = rows.map(([id, title, url, category]) => Object.freeze({
          id,
          title,
          url,
          category,
          iconFile: `web-dock-icons/${id}.${svgIds.has(id) ? "svg" : "png"}`
        }));
        const byHost = /* @__PURE__ */ new Map();
        for (const site of sites) {
          try {
            const host = new URL(site.url).hostname.replace(/^www\./, "");
            byHost.set(host, site);
          } catch (_) {
          }
        }
        const api = Object.freeze({
          sites: Object.freeze(sites),
          findByUrl(value) {
            try {
              const host = new URL(value).hostname.replace(/^www\./, "");
              return byHost.get(host) || null;
            } catch (_) {
              return null;
            }
          }
        });
        globalThis.TfaWebDockCatalog = api;
        if (typeof module === "object" && module.exports) module.exports = api;
      })();
    }
  });

  // src/shared/newtab-site-space-model.js
  var require_newtab_site_space_model = __commonJS({
    "src/shared/newtab-site-space-model.js"(exports, module) {
      (function attachNewTabSiteSpaceModel(root, factory) {
        const api = factory(
          root.TfaDockLogic || (typeof __require === "function" ? require_dock_logic() : null),
          root.TfaWebDockCatalog || (typeof __require === "function" ? require_web_dock_catalog() : null)
        );
        if (typeof module === "object" && module.exports) module.exports = api;
        root.TfaNewTabSiteSpaceModel = api;
      })(typeof globalThis === "object" ? globalThis : exports, function createNewTabSiteSpaceModel(DockLogic, WebDockCatalog) {
        "use strict";
        const STORAGE_KEY = "tfa_newtab_site_space_v1";
        const SCHEMA_VERSION = 3;
        const DISPLAY_DEFAULTS = Object.freeze({ iconSize: 52, titleSize: 12 });
        const DISPLAY_LIMITS = Object.freeze({
          iconSize: Object.freeze({ min: 34, max: 64 }),
          titleSize: Object.freeze({ min: 10, max: 16 })
        });
        const TEXT_ICON_COLORS = DockLogic?.DOCK_TEXT_ICON_COLORS || Object.freeze([
          Object.freeze({ id: "violet", hex: "#7568dc" })
        ]);
        const COLOR_IDS = new Set(TEXT_ICON_COLORS.map((item) => item.id));
        const SAFE_ICON_KINDS = /* @__PURE__ */ new Set(["catalog", "favicon", "letter"]);
        const isChinese = (language) => String(language || "").toLowerCase().startsWith("zh");
        const safeText = (value, limit = 80) => String(value || "").trim().slice(0, limit);
        const safeId = (value) => safeText(value, 96).replace(/[^a-zA-Z0-9_-]/g, "");
        const safeTimestamp = (value, fallback) => Number.isFinite(Number(value)) && Number(value) > 0 ? Number(value) : fallback;
        function safeUrl(value) {
          const input = String(value || "").trim();
          if (!input) return "";
          const explicitScheme = /^[a-z][a-z\d+.-]*:/i.test(input);
          const hasHttpScheme = /^https?:\/\//i.test(input);
          if (explicitScheme && !hasHttpScheme) {
            const bareHostPort = /^(?:localhost|(?:[a-z\d-]+\.)+[a-z\d-]+|\[[\da-f:]+\]):\d+(?:[/?#]|$)/i.test(input);
            if (!bareHostPort) return "";
          }
          const candidate = hasHttpScheme ? input : `https://${input.replace(/^\/\//, "")}`;
          try {
            const url = new URL(candidate);
            if (!["http:", "https:"].includes(url.protocol)) return "";
            const hostname = url.hostname;
            if (!hostname || !hostname.includes(".") && hostname !== "localhost" && !/^\[[\da-f:]+\]$/i.test(hostname)) return "";
            url.hash = "";
            return url.href;
          } catch (_error) {
            return "";
          }
        }
        function clampDisplayValue(value, key) {
          const limits = DISPLAY_LIMITS[key];
          const fallback = DISPLAY_DEFAULTS[key];
          const number = Number(value);
          if (!limits || !Number.isFinite(number)) return fallback;
          return Math.round(Math.min(limits.max, Math.max(limits.min, number)));
        }
        function normalizeDisplay(value = {}) {
          return {
            iconSize: clampDisplayValue(value?.iconSize, "iconSize"),
            titleSize: clampDisplayValue(value?.titleSize, "titleSize")
          };
        }
        const LEGACY_DEFAULT_DEFINITION = Object.freeze([
          Object.freeze({ id: "work", titles: Object.freeze(["\u5DE5\u4F5C\u4E0E\u534F\u4F5C", "Work and collaboration"]), siteIds: Object.freeze(["gmail", "notion", "feishu", "tencentdocs", "googledrive"]) }),
          Object.freeze({ id: "ai-create", titles: Object.freeze(["AI \u4E0E\u521B\u4F5C", "AI and creation"]), siteIds: Object.freeze(["chatgpt", "claude", "gemini", "deepseek", "qwen", "figma", "canva"]) }),
          Object.freeze({ id: "knowledge", titles: Object.freeze(["\u77E5\u8BC6\u4E0E\u8D44\u8BAF", "Knowledge and news"]), siteIds: Object.freeze(["zhihu", "wikipedia", "bilibili", "youtube", "36kr", "github"]) }),
          Object.freeze({ id: "social", titles: Object.freeze(["\u793E\u4EA4\u4E0E\u793E\u533A", "Social and community"]), siteIds: Object.freeze(["wechat", "xiaohongshu", "weibo", "x", "reddit"]) }),
          Object.freeze({ id: "life", titles: Object.freeze(["\u751F\u6D3B\u4E0E\u5A31\u4E50", "Life and entertainment"]), siteIds: Object.freeze(["taobao", "jd", "meituan", "ctrip", "amap", "neteasemusic"]) })
        ]);
        const LEGACY_SITE_DEFINITION = Object.freeze({
          gmail: ["Gmail", "https://mail.google.com/"],
          notion: ["Notion", "https://www.notion.so/"],
          feishu: ["\u98DE\u4E66", "https://www.feishu.cn/"],
          tencentdocs: ["\u817E\u8BAF\u6587\u6863", "https://docs.qq.com/"],
          googledrive: ["Google Drive", "https://drive.google.com/"],
          chatgpt: ["ChatGPT", "https://chatgpt.com/"],
          claude: ["Claude", "https://claude.ai/"],
          gemini: ["Gemini", "https://gemini.google.com/"],
          deepseek: ["DeepSeek", "https://chat.deepseek.com/"],
          qwen: ["\u901A\u4E49\u5343\u95EE", "https://www.qianwen.com/"],
          figma: ["Figma", "https://www.figma.com/"],
          canva: ["Canva", "https://www.canva.com/"],
          zhihu: ["\u77E5\u4E4E", "https://www.zhihu.com/"],
          wikipedia: ["Wikipedia", "https://www.wikipedia.org/"],
          bilibili: ["\u54D4\u54E9\u54D4\u54E9", "https://www.bilibili.com/"],
          youtube: ["YouTube", "https://www.youtube.com/"],
          "36kr": ["36\u6C2A", "https://36kr.com/"],
          github: ["GitHub", "https://github.com/"],
          wechat: ["\u5FAE\u4FE1", "https://weixin.qq.com/"],
          xiaohongshu: ["\u5C0F\u7EA2\u4E66", "https://www.xiaohongshu.com/"],
          weibo: ["\u5FAE\u535A", "https://weibo.com/"],
          x: ["X", "https://x.com/"],
          reddit: ["Reddit", "https://www.reddit.com/"],
          taobao: ["\u6DD8\u5B9D", "https://www.taobao.com/"],
          jd: ["\u4EAC\u4E1C", "https://www.jd.com/"],
          meituan: ["\u7F8E\u56E2", "https://www.meituan.com/"],
          ctrip: ["\u643A\u7A0B", "https://www.ctrip.com/"],
          amap: ["\u9AD8\u5FB7\u5730\u56FE", "https://www.amap.com/"],
          neteasemusic: ["\u7F51\u6613\u4E91\u97F3\u4E50", "https://music.163.com/"]
        });
        const DEFAULT_DEFINITION = Object.freeze([
          Object.freeze({ id: "work", zh: "\u5DE5\u4F5C\u4E0E\u534F\u4F5C", en: "Work and collaboration", siteIds: Object.freeze([
            "gmail",
            "outlook",
            "notion",
            "googledrive",
            "onedrive",
            "dropbox",
            "box",
            "slack",
            "zoom",
            "trello",
            "asana",
            "airtable",
            "evernote",
            "feishu",
            "dingtalk",
            "tencentdocs",
            "wps",
            "baidupan",
            "google",
            "bing"
          ]) }),
          Object.freeze({ id: "ai-create", zh: "AI \u4E0E\u521B\u4F5C", en: "AI and creation", siteIds: Object.freeze([
            "chatgpt",
            "claude",
            "gemini",
            "copilot",
            "perplexity",
            "grok",
            "deepseek",
            "qwen",
            "kimi",
            "doubao",
            "wenxin",
            "yuanbao",
            "zhipu",
            "mistral",
            "huggingface",
            "poe",
            "cohere",
            "characterai",
            "midjourney",
            "suno"
          ]) }),
          Object.freeze({ id: "knowledge", zh: "\u77E5\u8BC6\u4E0E\u8D44\u8BAF", en: "Knowledge and news", siteIds: Object.freeze([
            "zhihu",
            "wikipedia",
            "medium",
            "quora",
            "substack",
            "gitbook",
            "archive",
            "coursera",
            "edx",
            "36kr",
            "huxiu",
            "thepaper",
            "caixin",
            "nytimes",
            "bbc",
            "reuters",
            "bloomberg",
            "guardian",
            "wsj",
            "ft",
            "npr",
            "apnews",
            "economist"
          ]) }),
          Object.freeze({ id: "design-dev", zh: "\u8BBE\u8BA1\u4E0E\u5F00\u53D1", en: "Design and development", siteIds: Object.freeze([
            "figma",
            "canva",
            "miro",
            "excalidraw",
            "adobe",
            "photoshop",
            "dribbble",
            "behance",
            "unsplash",
            "pexels",
            "freepik",
            "github",
            "gitlab",
            "stackoverflow",
            "mdn",
            "vercel",
            "docker",
            "leetcode",
            "gitee",
            "juejin",
            "csdn",
            "cnblogs",
            "segmentfault",
            "vscode"
          ]) }),
          Object.freeze({ id: "social-media", zh: "\u793E\u4EA4\u4E0E\u5F71\u97F3", en: "Social and media", siteIds: Object.freeze([
            "wechat",
            "qq",
            "weibo",
            "x",
            "reddit",
            "linkedin",
            "telegram",
            "discord",
            "xiaohongshu",
            "bilibili",
            "youtube",
            "douyin",
            "tiktok",
            "spotify",
            "neteasemusic",
            "qqmusic",
            "ximalaya",
            "iqiyi",
            "tencentvideo",
            "douban"
          ]) }),
          Object.freeze({ id: "life", zh: "\u751F\u6D3B\u4E0E\u6D88\u8D39", en: "Life and shopping", siteIds: Object.freeze([
            "taobao",
            "tmall",
            "jd",
            "pinduoduo",
            "amazon",
            "meituan",
            "dianping",
            "ctrip",
            "airbnb",
            "amap",
            "baidumaps",
            "eleme",
            "xianyu",
            "suning",
            "dangdang",
            "vipshop",
            "qunar"
          ]) })
        ]);
        const CATALOG_BY_ID = new Map((WebDockCatalog?.sites || []).map((site) => [site.id, site]));
        function isUntouchedLegacySpace(value) {
          if (Number(value?.schemaVersion) >= SCHEMA_VERSION) return false;
          if (!Array.isArray(value?.categories) || !Array.isArray(value?.sites)) return false;
          if (Number(value.revision) !== 1) return false;
          if (value.categories.length !== LEGACY_DEFAULT_DEFINITION.length) return false;
          const categories = value.categories.slice().sort((a, b) => Number(a?.order) - Number(b?.order));
          if (!LEGACY_DEFAULT_DEFINITION.every((group, order) => {
            const item = categories[order];
            return item && safeId(item.id) === group.id && Number(item.order) === order && group.titles.includes(safeText(item.title, 40));
          })) return false;
          if (value.sites.length !== 29) return false;
          const sites = value.sites.slice().sort((a, b) => {
            const categoryOrder = (id) => LEGACY_DEFAULT_DEFINITION.findIndex((group) => group.id === safeId(id));
            return categoryOrder(a?.categoryId) - categoryOrder(b?.categoryId) || Number(a?.order) - Number(b?.order);
          });
          let offset = 0;
          return LEGACY_DEFAULT_DEFINITION.every((group) => group.siteIds.every((id, order) => {
            const item = sites[offset++];
            const definition = LEGACY_SITE_DEFINITION[id];
            return item && definition && safeId(item.id) === id && safeId(item.categoryId) === group.id && Number(item.order) === order && safeText(item.title, 80) === definition[0] && safeUrl(item.url) === definition[1] && item.iconKind === "catalog" && safeText(item.iconValue, 160).replace(/[^a-zA-Z0-9_./-]/g, "") === `web-dock-icons/${id}.png` && item.iconColor === (TEXT_ICON_COLORS[0]?.id || "violet");
          }));
        }
        function createDefaultSpace({ now = Date.now(), language = "zh" } = {}) {
          const timestamp = Number(now) || Date.now();
          const categories = DEFAULT_DEFINITION.map((group, order) => ({
            id: group.id,
            title: isChinese(language) ? group.zh : group.en,
            order,
            createdAt: timestamp,
            updatedAt: timestamp
          }));
          const sites = DEFAULT_DEFINITION.flatMap((group) => group.siteIds.map((siteId, order) => {
            const site = CATALOG_BY_ID.get(siteId);
            if (!site) return null;
            return {
              id: site.id,
              categoryId: group.id,
              title: site.title,
              url: site.url,
              iconKind: "catalog",
              iconValue: site.iconFile,
              iconColor: TEXT_ICON_COLORS[0]?.id || "violet",
              order,
              createdAt: timestamp,
              updatedAt: timestamp
            };
          }).filter(Boolean));
          return {
            schemaVersion: SCHEMA_VERSION,
            revision: 1,
            categories,
            sites,
            display: normalizeDisplay(),
            updatedAt: timestamp
          };
        }
        function normalizeSpace(value, options = {}) {
          const now = Number(options.now) || Date.now();
          if (!value || typeof value !== "object" || !Array.isArray(value.categories) || !Array.isArray(value.sites)) {
            return createDefaultSpace({ now, language: options.language });
          }
          if (isUntouchedLegacySpace(value)) return createDefaultSpace({ now, language: options.language });
          const seenCategories = /* @__PURE__ */ new Set();
          const categories = value.categories.map((item, index) => {
            const id = safeId(item?.id);
            const title = safeText(item?.title, 40);
            if (!id || !title || seenCategories.has(id)) return null;
            seenCategories.add(id);
            return {
              id,
              title,
              order: Number.isFinite(Number(item.order)) ? Number(item.order) : index,
              createdAt: safeTimestamp(item.createdAt, now),
              updatedAt: safeTimestamp(item.updatedAt, now)
            };
          }).filter(Boolean).sort((a, b) => a.order - b.order).map((item, order) => ({ ...item, order }));
          const categoryIds = new Set(categories.map((item) => item.id));
          const seenSites = /* @__PURE__ */ new Set();
          const sites = value.sites.map((item, index) => {
            const id = safeId(item?.id);
            const categoryId = safeId(item?.categoryId);
            const title = safeText(item?.title, 80);
            const url = safeUrl(item?.url);
            if (!id || !categoryIds.has(categoryId) || !title || !url || seenSites.has(id)) return null;
            seenSites.add(id);
            const iconKind = SAFE_ICON_KINDS.has(item?.iconKind) ? item.iconKind : "favicon";
            const iconValue = iconKind === "catalog" ? safeText(item?.iconValue, 160).replace(/[^a-zA-Z0-9_./-]/g, "") : safeText(item?.iconValue, 240);
            return {
              id,
              categoryId,
              title,
              url,
              iconKind,
              iconValue,
              iconColor: COLOR_IDS.has(item?.iconColor) ? item.iconColor : TEXT_ICON_COLORS[0]?.id || "violet",
              order: Number.isFinite(Number(item.order)) ? Number(item.order) : index,
              createdAt: safeTimestamp(item.createdAt, now),
              updatedAt: safeTimestamp(item.updatedAt, now)
            };
          }).filter(Boolean);
          const orderedSites = categories.flatMap((category) => sites.filter((site) => site.categoryId === category.id).sort((a, b) => a.order - b.order).map((site, order) => ({ ...site, order })));
          return {
            schemaVersion: SCHEMA_VERSION,
            revision: Math.max(1, Number(value.revision) || 1),
            categories,
            sites: orderedSites,
            display: normalizeDisplay(value.display),
            updatedAt: safeTimestamp(value.updatedAt, now)
          };
        }
        return Object.freeze({
          STORAGE_KEY,
          SCHEMA_VERSION,
          DISPLAY_DEFAULTS,
          DISPLAY_LIMITS,
          TEXT_ICON_COLORS,
          DEFAULT_DEFINITION,
          safeText,
          safeId,
          safeUrl,
          normalizeDisplay,
          createDefaultSpace,
          normalizeSpace
        });
      });
    }
  });

  // src/shared/v2-data-schema.js
  var require_v2_data_schema = __commonJS({
    "src/shared/v2-data-schema.js"(exports, module) {
      (function attachV2DataSchema(root, factory) {
        const api = factory();
        if (typeof module === "object" && module.exports) module.exports = api;
        root.TfaV2DataSchema = api;
      })(typeof globalThis === "object" ? globalThis : exports, function createV2DataSchemaModule() {
        "use strict";
        const DB_NAME = "tfa_v2";
        const DB_VERSION = 4;
        const STORES = Object.freeze({
          PAGES: "pages",
          TASKS: "tasks",
          RELATIONS: "relations",
          CHANGES: "changes",
          TAB_INSTANCES: "tabInstances",
          BEHAVIOR_EVENTS: "behaviorEvents",
          SUGGESTIONS: "suggestions"
        });
        const MAX_CHANGES = 100;
        const TASK_DOMAINS = Object.freeze(["work", "study", "life", "interest", "admin", "other"]);
        const TASK_STATUSES = Object.freeze(["active", "paused", "done", "pending"]);
        const TASK_SOURCES = Object.freeze(["manual", "ai"]);
        const RELATION_ROLES = Object.freeze(["core", "related", "shared"]);
        const RELATION_SOURCES = Object.freeze(["manual", "rule", "ai"]);
        const SUGGESTION_KINDS = Object.freeze(["relation.add"]);
        const SUGGESTION_ORIGINS = Object.freeze(["rule", "ai"]);
        const SUGGESTION_STATUSES = Object.freeze(["pending", "accepted", "rejected", "expired", "superseded", "undone"]);
        const SUGGESTION_RISK_FLAGS = Object.freeze([
          "possible_misclick",
          "quick_pass",
          "redirect_or_ad",
          "temporary_query",
          "object_conflict",
          "initial_snapshot",
          "manual_rejected",
          "manual_conflict",
          "cooldown",
          "shared_tool",
          "intent_drift",
          "sensitive_site",
          "single_signal",
          "low_margin",
          "ambiguous",
          "unknown_risk",
          "noise_risk"
        ]);
        const SUGGESTION_EVIDENCE_CODES = Object.freeze([
          "opener_link",
          "stable_object",
          "qualified_return",
          "task_snapshot_restore",
          "native_group_restore",
          "title_overlap",
          "same_native_group",
          "same_window",
          "same_domain",
          "risk_suppressed",
          "ambiguous"
        ]);
        const SUGGESTION_FIELDS = /* @__PURE__ */ new Set([
          "id",
          "kind",
          "pageId",
          "taskId",
          "role",
          "isPrimary",
          "origin",
          "confidence",
          "riskFlags",
          "evidenceCodes",
          "reasonText",
          "reasonLocale",
          "status",
          "analysisRunId",
          "rank",
          "inputVersion",
          "ruleVersion",
          "modelVersion",
          "dedupeKey",
          "createdAt",
          "updatedAt",
          "expiresAt",
          "decidedAt",
          "cooldownUntil",
          "appliedRelationId",
          "appliedChangeId"
        ]);
        const CHANGE_ACTIONS = Object.freeze([
          "task.create",
          "task.update",
          "task.delete",
          "task.reorder",
          "relation.add",
          "relation.update",
          "relation.remove",
          "relation.reorder",
          "suggestion.accept",
          "suggestion.reject"
        ]);
        const BEHAVIOR_EVENT_TYPES = Object.freeze([
          "tab.opened",
          "tab.activated",
          "tab.navigated",
          "tab.closed",
          "tab.moved",
          "tab.replaced",
          "tab.group_changed",
          "group.updated",
          "window.focus_changed",
          "window.created",
          "window.removed",
          "tab.reconciled_missing",
          "restore.explicit"
        ]);
        const MAX_BEHAVIOR_CONTEXT_BYTES = 2048;
        const FORBIDDEN_CONTEXT_PARTS = /* @__PURE__ */ new Set([
          "url",
          "uri",
          "href",
          "body",
          "content",
          "html",
          "cookie",
          "cookies",
          "form",
          "formdata",
          "screenshot",
          "pagetext",
          "documenttext",
          "pagebody",
          "documentbody"
        ]);
        const TRACKING_KEYS = /* @__PURE__ */ new Set([
          "fbclid",
          "gclid",
          "dclid",
          "msclkid",
          "yclid",
          "twclid",
          "ttclid",
          "mc_cid",
          "mc_eid",
          "_ga",
          "_gl"
        ]);
        const SENSITIVE_KEYS = /* @__PURE__ */ new Set([
          "access_token",
          "refresh_token",
          "id_token",
          "token",
          "auth",
          "authorization",
          "api_key",
          "apikey",
          "password",
          "passwd",
          "session",
          "sessionid",
          "session_id"
        ]);
        const SAFE_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,159}$/;
        const TASK_PROVENANCE_TEXT = /^[a-z0-9][a-z0-9._:-]{0,159}$/;
        function normalizeSafeUrl(value) {
          try {
            const parsed = new URL(String(value || "").trim());
            if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return "";
            parsed.username = "";
            parsed.password = "";
            parsed.hash = "";
            Array.from(parsed.searchParams.keys()).forEach((key) => {
              const lower = key.toLowerCase();
              if (lower.startsWith("utm_") || TRACKING_KEYS.has(lower) || SENSITIVE_KEYS.has(lower)) {
                parsed.searchParams.delete(key);
              }
            });
            return parsed.href;
          } catch (_error) {
            return "";
          }
        }
        function normalizeFaviconUrl(value) {
          const raw = String(value || "").trim();
          if (/^data:image\/[a-z0-9.+-]+(?:;[a-z0-9=.+-]+)*,/i.test(raw)) return raw;
          return normalizeSafeUrl(raw);
        }
        function defaultUuid() {
          if (typeof crypto === "object" && typeof crypto.randomUUID === "function") return crypto.randomUUID();
          return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 12)}`;
        }
        function validId(value) {
          return SAFE_ID.test(String(value || ""));
        }
        function makeEntityId(prefix, uuidFactory = defaultUuid) {
          const safePrefix = String(prefix || "").trim();
          if (!/^(page|task|change|event|suggestion)$/.test(safePrefix)) throw new TypeError("Invalid entity id prefix");
          const token = String(uuidFactory()).trim();
          if (!SAFE_ID.test(token)) throw new TypeError("Invalid entity id token");
          return `${safePrefix}-${token}`;
        }
        function makeRelationId(taskId, pageId) {
          if (!validId(taskId) || !validId(pageId)) throw new TypeError("Invalid relation id parts");
          return `${taskId}::${pageId}`;
        }
        function makeTabInstanceId(browserSessionId, tabId) {
          const sessionId = String(browserSessionId || "").trim();
          const numericTabId = Number(tabId);
          if (!validId(sessionId) || !Number.isInteger(numericTabId) || numericTabId < 0) {
            throw new TypeError("Invalid tab instance id parts");
          }
          return `${sessionId}:${numericTabId}`;
        }
        function timestamp(value, fallback) {
          const number = Number(value);
          return Number.isFinite(number) && number >= 0 ? Math.trunc(number) : fallback;
        }
        function currentTime(options) {
          const value = typeof options?.now === "function" ? options.now() : options?.now;
          return timestamp(value, Date.now());
        }
        function idFor(prefix, supplied, options) {
          const raw = String(supplied || "").trim();
          if (raw) {
            if (!validId(raw) || prefix !== "task" && !raw.startsWith(`${prefix}-`)) {
              throw new TypeError(`Invalid ${prefix} id`);
            }
            return raw;
          }
          if (typeof options?.createId === "function") {
            const generated = String(options.createId(prefix) || "").trim();
            if (!validId(generated) || !generated.startsWith(`${prefix}-`)) throw new TypeError(`Invalid generated ${prefix} id`);
            return generated;
          }
          return makeEntityId(prefix, options?.uuidFactory);
        }
        function normalizePageEntity(input = {}, options = {}) {
          const normalizedUrl = normalizeSafeUrl(input.normalizedUrl || input.restoreUrl || input.url);
          const restoreUrl = normalizeSafeUrl(input.restoreUrl || input.url || normalizedUrl);
          if (!normalizedUrl || !restoreUrl) throw new TypeError("Page requires a safe http(s) URL");
          const now = currentTime(options);
          const createdAt = timestamp(input.createdAt, now);
          const updatedAt = Math.max(createdAt, timestamp(input.updatedAt, createdAt));
          const domain = new URL(normalizedUrl).hostname;
          const title = String(input.title || "").trim() || domain;
          return Object.freeze({
            id: idFor("page", input.id, options),
            normalizedUrl,
            restoreUrl,
            title,
            domain,
            faviconUrl: normalizeFaviconUrl(input.faviconUrl || input.favIconUrl || input.favicon),
            createdAt,
            updatedAt
          });
        }
        function normalizeTask(input = {}, options = {}) {
          const title = String(input.title || "").trim();
          if (!title) throw new TypeError("Task title is required");
          const domain = input.domain == null || input.domain === "" ? "other" : String(input.domain);
          const status = input.status == null || input.status === "" ? "active" : String(input.status);
          if (!TASK_DOMAINS.includes(domain)) throw new TypeError("Invalid task domain");
          if (!TASK_STATUSES.includes(status)) throw new TypeError("Invalid task status");
          const sortOrder = input.sortOrder == null ? 0 : Number(input.sortOrder);
          if (!Number.isFinite(sortOrder)) throw new TypeError("Invalid task sortOrder");
          const source = input.source == null || input.source === "" ? "manual" : String(input.source);
          if (!TASK_SOURCES.includes(source)) throw new TypeError("Invalid task source");
          const confidence = source === "manual" ? 1 : Number(input.confidence);
          if (!Number.isFinite(confidence) || confidence < 0 || confidence > 1) throw new TypeError("Invalid task confidence");
          const evidence = source === "manual" ? "manual" : String(input.evidence || "").trim();
          if (!TASK_PROVENANCE_TEXT.test(evidence)) throw new TypeError("Invalid task evidence");
          const modelVersion = source === "manual" ? "" : String(input.modelVersion || "").trim();
          if (source === "ai" && !TASK_PROVENANCE_TEXT.test(modelVersion)) throw new TypeError("Invalid task modelVersion");
          const now = currentTime(options);
          const createdAt = timestamp(input.createdAt, now);
          return Object.freeze({
            id: idFor("task", input.id, options),
            title,
            domain,
            status,
            sortOrder,
            source,
            manualLocked: source === "manual" ? true : input.manualLocked === true,
            confidence,
            evidence,
            modelVersion,
            createdAt,
            updatedAt: Math.max(createdAt, timestamp(input.updatedAt, createdAt))
          });
        }
        function normalizeRelation(input = {}, options = {}) {
          const taskId = String(input.taskId || "").trim();
          const pageId = String(input.pageId || "").trim();
          if (!pageId.startsWith("page-")) throw new TypeError("Invalid relation pageId");
          const id = makeRelationId(taskId, pageId);
          if (input.id != null && input.id !== "" && String(input.id) !== id) throw new TypeError("Relation id does not match taskId and pageId");
          const role = input.role == null || input.role === "" ? "related" : String(input.role);
          const source = input.source == null || input.source === "" ? "manual" : String(input.source);
          if (!RELATION_ROLES.includes(role)) throw new TypeError("Invalid relation role");
          if (!RELATION_SOURCES.includes(source)) throw new TypeError("Invalid relation source");
          const sortOrder = input.sortOrder == null ? 0 : Number(input.sortOrder);
          if (!Number.isFinite(sortOrder)) throw new TypeError("Invalid relation sortOrder");
          const confidence = source === "manual" ? 1 : Number(input.confidence);
          if (!Number.isFinite(confidence) || confidence < 0 || confidence > 1) throw new TypeError("Invalid relation confidence");
          const now = currentTime(options);
          const createdAt = timestamp(input.createdAt, now);
          return Object.freeze({
            id,
            taskId,
            pageId,
            isPrimary: input.isPrimary === true,
            role,
            source,
            manualLocked: source === "manual" ? true : input.manualLocked === true,
            confidence,
            evidence: source === "manual" ? "manual" : String(input.evidence || "").trim().slice(0, 160),
            sortOrder,
            createdAt,
            updatedAt: Math.max(createdAt, timestamp(input.updatedAt, createdAt))
          });
        }
        function cloneSnapshot(value) {
          if (value == null) return null;
          try {
            const serialized = JSON.stringify(value);
            if (serialized === void 0) throw new Error("empty");
            return JSON.parse(serialized);
          } catch (_error) {
            throw new TypeError("Change snapshot must be JSON-serializable");
          }
        }
        function normalizeChangeRecord(input = {}, options = {}) {
          const action = String(input.action || "");
          if (!CHANGE_ACTIONS.includes(action)) throw new TypeError("Invalid change action");
          const before = cloneSnapshot(input.before);
          const after = cloneSnapshot(input.after);
          if (before == null && after == null) throw new TypeError("Change requires before or after snapshot");
          const createdAt = timestamp(input.createdAt, currentTime(options));
          const undoneAt = input.undoneAt == null ? null : timestamp(input.undoneAt, NaN);
          if (undoneAt !== null && (!Number.isFinite(undoneAt) || undoneAt < createdAt)) throw new TypeError("Invalid undoneAt");
          return Object.freeze({
            id: idFor("change", input.id, options),
            action,
            before,
            after,
            createdAt,
            undoneAt
          });
        }
        function optionalPageId(value, fieldName) {
          if (value == null || value === "") return null;
          const pageId = String(value).trim();
          if (!validId(pageId) || !pageId.startsWith("page-")) throw new TypeError(`Invalid ${fieldName}`);
          return pageId;
        }
        function integer(value, fieldName, minimum = Number.MIN_SAFE_INTEGER) {
          const number = Number(value);
          if (!Number.isSafeInteger(number) || number < minimum) throw new TypeError(`Invalid ${fieldName}`);
          return number;
        }
        function optionalInteger(value, fieldName, minimum = Number.MIN_SAFE_INTEGER) {
          return value == null ? null : integer(value, fieldName, minimum);
        }
        function suggestionToken(value, fieldName, { required = true } = {}) {
          const token = String(value || "").trim();
          if (!token && !required) return "";
          if (!SAFE_ID.test(token)) throw new TypeError(`Invalid suggestion ${fieldName}`);
          return token;
        }
        function suggestionCodes(value, fieldName, allowed, { required = false } = {}) {
          if (!Array.isArray(value) || value.length > 8 || required && value.length === 0) {
            throw new TypeError(`Invalid suggestion ${fieldName}`);
          }
          const codes = value.map((item) => String(item || "").trim());
          if (new Set(codes).size !== codes.length || codes.some((code) => !allowed.includes(code))) {
            throw new TypeError(`Invalid suggestion ${fieldName}`);
          }
          return Object.freeze(codes);
        }
        function suggestionTimestamp(value, fieldName, fallback) {
          if (value == null) return fallback;
          if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
            throw new TypeError(`Invalid suggestion ${fieldName}`);
          }
          return Math.trunc(value);
        }
        function normalizeSuggestion(input = {}, options = {}) {
          if (!input || typeof input !== "object" || Array.isArray(input)) throw new TypeError("Suggestion must be an object");
          const unknown = Object.keys(input).filter((field) => !SUGGESTION_FIELDS.has(field));
          if (unknown.length) throw new TypeError(`Unknown suggestion field: ${unknown[0]}`);
          const kind = String(input.kind || "relation.add");
          const pageId = String(input.pageId || "").trim();
          const taskId = String(input.taskId || "").trim();
          const role = String(input.role || "related");
          const origin = String(input.origin || "rule");
          const status = String(input.status || "pending");
          const reasonText = String(input.reasonText || "").trim();
          const reasonLocale = String(input.reasonLocale || "").trim();
          if (!SUGGESTION_KINDS.includes(kind)) throw new TypeError("Invalid suggestion kind");
          if (!pageId.startsWith("page-") || !validId(pageId)) throw new TypeError("Invalid suggestion pageId");
          if (!validId(taskId)) throw new TypeError("Invalid suggestion taskId");
          if (!RELATION_ROLES.includes(role)) throw new TypeError("Invalid suggestion role");
          if (typeof input.isPrimary !== "boolean") throw new TypeError("Invalid suggestion isPrimary");
          if (!SUGGESTION_ORIGINS.includes(origin)) throw new TypeError("Invalid suggestion origin");
          if (typeof input.confidence !== "number" || !Number.isFinite(input.confidence) || input.confidence < 0 || input.confidence > 1) {
            throw new TypeError("Invalid suggestion confidence");
          }
          if (!SUGGESTION_STATUSES.includes(status)) throw new TypeError("Invalid suggestion status");
          if (!reasonText || reasonText.length > 240 || /https?:\/\/|[?&][A-Za-z0-9_.~-]+=|(?:access[_-]?token|token|api[_-]?key|secret[_-]?key|private[_-]?key|authorization|bearer|cookie)\s*[:=]|(?:^|\s)sk-[A-Za-z0-9_-]{8,}/i.test(reasonText)) {
            throw new TypeError("Invalid or sensitive suggestion reasonText");
          }
          if (!["zh", "en"].includes(reasonLocale)) throw new TypeError("Invalid suggestion reasonLocale");
          const riskFlags = suggestionCodes(input.riskFlags, "riskFlags", SUGGESTION_RISK_FLAGS);
          const evidenceCodes = suggestionCodes(input.evidenceCodes, "evidenceCodes", SUGGESTION_EVIDENCE_CODES, { required: true });
          const analysisRunId = suggestionToken(input.analysisRunId, "analysisRunId");
          const inputVersion = suggestionToken(input.inputVersion, "inputVersion");
          const ruleVersion = suggestionToken(input.ruleVersion, "ruleVersion");
          const modelVersion = suggestionToken(input.modelVersion, "modelVersion", { required: origin === "ai" });
          if (origin === "rule" && modelVersion) throw new TypeError("Rule suggestion cannot have modelVersion");
          const dedupeKey = suggestionToken(input.dedupeKey, "dedupeKey");
          if (!Number.isSafeInteger(input.rank) || input.rank < 1 || input.rank > 8) throw new TypeError("Invalid suggestion rank");
          const rank = input.rank;
          const now = currentTime(options);
          const createdAt = suggestionTimestamp(input.createdAt, "createdAt", now);
          const updatedAt = suggestionTimestamp(input.updatedAt, "updatedAt", createdAt);
          const expiresAt = suggestionTimestamp(input.expiresAt, "expiresAt", NaN);
          const decidedAt = input.decidedAt == null ? null : suggestionTimestamp(input.decidedAt, "decidedAt", NaN);
          if (updatedAt < createdAt || !Number.isFinite(expiresAt) || expiresAt < createdAt) throw new TypeError("Invalid suggestion timestamps");
          const pending = status === "pending";
          if (pending && decidedAt !== null || !pending && (!Number.isFinite(decidedAt) || decidedAt < createdAt || decidedAt > updatedAt)) {
            throw new TypeError("Invalid suggestion decidedAt");
          }
          if (pending && expiresAt < updatedAt) throw new TypeError("Pending suggestion has expired");
          if (status === "expired" && decidedAt < expiresAt) throw new TypeError("Expired suggestion was decided before expiry");
          if (["accepted", "rejected"].includes(status) && decidedAt > expiresAt) throw new TypeError("Suggestion decision occurred after expiry");
          const cooldownUntil = input.cooldownUntil == null ? null : suggestionTimestamp(input.cooldownUntil, "cooldownUntil", NaN);
          if (["rejected", "undone"].includes(status)) {
            if (!Number.isFinite(cooldownUntil) || cooldownUntil < decidedAt) throw new TypeError("Rejected or undone suggestion requires cooldown");
          } else if (cooldownUntil !== null) throw new TypeError("Suggestion status cannot have cooldown");
          const appliedRelationId = input.appliedRelationId == null ? null : String(input.appliedRelationId);
          const appliedChangeId = input.appliedChangeId == null ? null : String(input.appliedChangeId);
          const applied = status === "accepted" || status === "undone";
          if (applied) {
            if (appliedRelationId !== makeRelationId(taskId, pageId) || !validId(appliedChangeId) || !appliedChangeId.startsWith("change-")) {
              throw new TypeError("Accepted or undone suggestion requires applied relation and change ids");
            }
          } else if (appliedRelationId !== null || appliedChangeId !== null) {
            throw new TypeError("Only accepted or undone suggestions can reference applied records");
          }
          return Object.freeze({
            id: idFor("suggestion", input.id, options),
            kind,
            pageId,
            taskId,
            role,
            isPrimary: input.isPrimary,
            origin,
            confidence: input.confidence,
            riskFlags,
            evidenceCodes,
            reasonText,
            reasonLocale,
            status,
            analysisRunId,
            rank,
            inputVersion,
            ruleVersion,
            modelVersion,
            dedupeKey,
            createdAt,
            updatedAt,
            expiresAt,
            decidedAt,
            cooldownUntil,
            appliedRelationId,
            appliedChangeId
          });
        }
        function normalizeTabInstance(input = {}, options = {}) {
          const browserSessionId = String(input.browserSessionId || "").trim();
          const tabId = integer(input.tabId, "tabId", 0);
          const id = makeTabInstanceId(browserSessionId, tabId);
          if (input.id != null && input.id !== "" && String(input.id) !== id) {
            throw new TypeError("Tab instance id does not match browserSessionId and tabId");
          }
          const observedFrom = String(input.observedFrom || "").trim();
          if (!observedFrom || observedFrom.length > 40) throw new TypeError("Invalid observedFrom");
          const now = currentTime(options);
          const firstObservedAt = timestamp(input.firstObservedAt, now);
          const lastSeenAt = Math.max(firstObservedAt, timestamp(input.lastSeenAt, firstObservedAt));
          const lastActivatedAt = input.lastActivatedAt == null ? null : timestamp(input.lastActivatedAt, NaN);
          if (lastActivatedAt !== null && (!Number.isFinite(lastActivatedAt) || lastActivatedAt < firstObservedAt || lastActivatedAt > lastSeenAt)) {
            throw new TypeError("Invalid lastActivatedAt");
          }
          return Object.freeze({
            id,
            browserSessionId,
            tabId,
            windowId: integer(input.windowId, "windowId"),
            index: integer(input.index, "index", 0),
            groupId: integer(input.groupId == null ? -1 : input.groupId, "groupId"),
            pinned: input.pinned === true,
            active: input.active === true,
            pageId: optionalPageId(input.pageId, "pageId"),
            observedFrom,
            preInstallHistoryUnknown: input.preInstallHistoryUnknown === true,
            firstObservedAt,
            lastSeenAt,
            lastActivatedAt
          });
        }
        function cloneBehaviorContext(value) {
          const input = value == null ? {} : value;
          if (!input || typeof input !== "object" || Array.isArray(input) || Object.getPrototypeOf(input) !== Object.prototype) {
            throw new TypeError("Behavior event context must be a plain JSON object");
          }
          const seen = /* @__PURE__ */ new Set();
          function isForbiddenKey(key) {
            const raw = String(key || "");
            const camelAware = raw.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase();
            const parts = camelAware.split(/[^a-z0-9]+/).filter(Boolean);
            const collapsed = parts.join("");
            return parts.some((part) => FORBIDDEN_CONTEXT_PARTS.has(part)) || FORBIDDEN_CONTEXT_PARTS.has(collapsed);
          }
          function visit(item, depth, key) {
            if (depth > 4) throw new TypeError("Behavior event context is too deeply nested");
            if (key && isForbiddenKey(key)) throw new TypeError("Behavior event context contains forbidden data");
            if (item == null || typeof item === "boolean") return item;
            if (typeof item === "number") {
              if (!Number.isFinite(item)) throw new TypeError("Behavior event context must be JSON-safe");
              return item;
            }
            if (typeof item === "string") {
              if (item.length > 256 || /^https?:\/\//i.test(item.trim())) {
                throw new TypeError("Behavior event context contains oversized or URL data");
              }
              return item;
            }
            if (typeof item !== "object" || seen.has(item)) throw new TypeError("Behavior event context must be JSON-safe");
            seen.add(item);
            let cloned2;
            if (Array.isArray(item)) {
              if (item.length > 32) throw new TypeError("Behavior event context array is too large");
              cloned2 = item.map((entry) => visit(entry, depth + 1, ""));
            } else {
              if (Object.getPrototypeOf(item) !== Object.prototype) {
                throw new TypeError("Behavior event context must be a plain JSON object");
              }
              const entries = Object.entries(item);
              if (entries.length > 32) throw new TypeError("Behavior event context has too many fields");
              cloned2 = Object.fromEntries(entries.map(([entryKey, entry]) => [
                entryKey,
                visit(entry, depth + 1, entryKey)
              ]));
            }
            seen.delete(item);
            return cloned2;
          }
          const cloned = visit(input, 0, "");
          const serialized = JSON.stringify(cloned);
          if (new TextEncoder().encode(serialized).byteLength > MAX_BEHAVIOR_CONTEXT_BYTES) {
            throw new TypeError("Behavior event context is too large");
          }
          return Object.freeze(cloned);
        }
        function normalizeBehaviorEvent(input = {}, options = {}) {
          const type = String(input.type || "").trim();
          if (!BEHAVIOR_EVENT_TYPES.includes(type)) throw new TypeError("Invalid behavior event type");
          const browserSessionId = input.browserSessionId == null ? null : String(input.browserSessionId).trim();
          if (browserSessionId === null && options.allowLegacyMissingSession !== true) {
            throw new TypeError("Behavior event browserSessionId is required");
          }
          if (browserSessionId !== null && !validId(browserSessionId)) throw new TypeError("Invalid behavior event browserSessionId");
          return Object.freeze({
            id: idFor("event", input.id, options),
            type,
            occurredAt: timestamp(input.occurredAt, currentTime(options)),
            browserSessionId,
            pageId: optionalPageId(input.pageId, "pageId"),
            tabId: optionalInteger(input.tabId, "tabId", 0),
            windowId: optionalInteger(input.windowId, "windowId"),
            sourcePageId: optionalPageId(input.sourcePageId, "sourcePageId"),
            context: cloneBehaviorContext(input.context)
          });
        }
        function result(errors) {
          return Object.freeze({ valid: errors.length === 0, errors: Object.freeze(errors) });
        }
        function validatePageEntity(value) {
          const errors = [];
          if (!value || typeof value !== "object") return result(["Page must be an object"]);
          if (!validId(value.id) || !String(value.id).startsWith("page-")) errors.push("Invalid page id");
          const normalized = normalizeSafeUrl(value.normalizedUrl);
          if (!normalized || normalized !== value.normalizedUrl) errors.push("Invalid normalizedUrl");
          if (!normalizeSafeUrl(value.restoreUrl) || normalizeSafeUrl(value.restoreUrl) !== value.restoreUrl) errors.push("Invalid restoreUrl");
          if (!String(value.title || "").trim()) errors.push("Page title is required");
          if (normalized && new URL(normalized).hostname !== value.domain) errors.push("Invalid page domain");
          if (!Number.isFinite(value.createdAt) || value.createdAt < 0 || !Number.isFinite(value.updatedAt) || value.updatedAt < value.createdAt) errors.push("Invalid page timestamps");
          return result(errors);
        }
        function validateTask(value) {
          const errors = [];
          if (!value || typeof value !== "object") return result(["Task must be an object"]);
          if (!validId(value.id)) errors.push("Invalid task id");
          if (!String(value.title || "").trim()) errors.push("Task title is required");
          if (!TASK_DOMAINS.includes(value.domain)) errors.push("Invalid task domain");
          if (!TASK_STATUSES.includes(value.status)) errors.push("Invalid task status");
          if (!Number.isFinite(value.sortOrder)) errors.push("Invalid task sortOrder");
          if (!TASK_SOURCES.includes(value.source)) errors.push("Invalid task source");
          if (typeof value.manualLocked !== "boolean") errors.push("Invalid task manualLocked");
          if (!Number.isFinite(value.confidence) || value.confidence < 0 || value.confidence > 1) errors.push("Invalid task confidence");
          if (!TASK_PROVENANCE_TEXT.test(String(value.evidence || ""))) errors.push("Invalid task evidence");
          if (value.source === "manual" && (value.manualLocked !== true || value.confidence !== 1 || value.evidence !== "manual" || value.modelVersion !== "")) {
            errors.push("Invalid manual task provenance");
          }
          if (value.source === "ai" && !TASK_PROVENANCE_TEXT.test(String(value.modelVersion || ""))) errors.push("Invalid task modelVersion");
          if (!Number.isFinite(value.createdAt) || value.createdAt < 0 || !Number.isFinite(value.updatedAt) || value.updatedAt < value.createdAt) errors.push("Invalid task timestamps");
          return result(errors);
        }
        function validateRelation(value) {
          const errors = [];
          if (!value || typeof value !== "object") return result(["Relation must be an object"]);
          let expectedId = "";
          try {
            expectedId = makeRelationId(value.taskId, value.pageId);
            if (!String(value.pageId).startsWith("page-")) errors.push("Invalid relation pageId");
          } catch (_error) {
            errors.push("Invalid relation id parts");
          }
          if (!expectedId || value.id !== expectedId) errors.push("Invalid relation id");
          if (typeof value.isPrimary !== "boolean") errors.push("Invalid isPrimary");
          if (!RELATION_ROLES.includes(value.role)) errors.push("Invalid relation role");
          if (!RELATION_SOURCES.includes(value.source)) errors.push("Invalid relation source");
          if (value.source === "manual" && (value.manualLocked !== true || value.confidence !== 1 || value.evidence !== "manual")) errors.push("Invalid manual relation provenance");
          if (!Number.isFinite(value.confidence) || value.confidence < 0 || value.confidence > 1) errors.push("Invalid relation confidence");
          if (!Number.isFinite(value.sortOrder)) errors.push("Invalid relation sortOrder");
          if (!Number.isFinite(value.createdAt) || value.createdAt < 0 || !Number.isFinite(value.updatedAt) || value.updatedAt < value.createdAt) errors.push("Invalid relation timestamps");
          return result(errors);
        }
        function validateChangeRecord(value) {
          const errors = [];
          if (!value || typeof value !== "object") return result(["Change must be an object"]);
          if (!validId(value.id) || !String(value.id).startsWith("change-")) errors.push("Invalid change id");
          if (!CHANGE_ACTIONS.includes(value.action)) errors.push("Invalid change action");
          if (value.before == null && value.after == null) errors.push("Change requires before or after snapshot");
          if (!Number.isFinite(value.createdAt) || value.createdAt < 0) errors.push("Invalid change createdAt");
          if (value.undoneAt !== null && (!Number.isFinite(value.undoneAt) || value.undoneAt < value.createdAt)) errors.push("Invalid change undoneAt");
          return result(errors);
        }
        function validateTabInstance(value) {
          const errors = [];
          if (!value || typeof value !== "object") return result(["Tab instance must be an object"]);
          try {
            if (value.id !== makeTabInstanceId(value.browserSessionId, value.tabId)) errors.push("Invalid tab instance id");
          } catch (_error) {
            errors.push("Invalid tab instance id parts");
          }
          if (!Number.isSafeInteger(value.windowId)) errors.push("Invalid windowId");
          if (!Number.isSafeInteger(value.index) || value.index < 0) errors.push("Invalid tab index");
          if (!Number.isSafeInteger(value.groupId)) errors.push("Invalid groupId");
          if (typeof value.pinned !== "boolean" || typeof value.active !== "boolean") errors.push("Invalid tab flags");
          if (value.pageId !== null && (!validId(value.pageId) || !String(value.pageId).startsWith("page-"))) errors.push("Invalid pageId");
          if (!String(value.observedFrom || "").trim() || String(value.observedFrom).length > 40) errors.push("Invalid observedFrom");
          if (typeof value.preInstallHistoryUnknown !== "boolean") errors.push("Invalid preInstallHistoryUnknown");
          if (!Number.isFinite(value.firstObservedAt) || value.firstObservedAt < 0 || !Number.isFinite(value.lastSeenAt) || value.lastSeenAt < value.firstObservedAt) errors.push("Invalid tab instance timestamps");
          if (value.lastActivatedAt !== null && (!Number.isFinite(value.lastActivatedAt) || value.lastActivatedAt < value.firstObservedAt || value.lastActivatedAt > value.lastSeenAt)) errors.push("Invalid lastActivatedAt");
          return result(errors);
        }
        function validateBehaviorEvent(value) {
          const errors = [];
          if (!value || typeof value !== "object") return result(["Behavior event must be an object"]);
          if (!validId(value.id) || !String(value.id).startsWith("event-")) errors.push("Invalid behavior event id");
          if (!BEHAVIOR_EVENT_TYPES.includes(value.type)) errors.push("Invalid behavior event type");
          if (value.browserSessionId != null && !validId(value.browserSessionId)) errors.push("Invalid browserSessionId");
          if (!Number.isFinite(value.occurredAt) || value.occurredAt < 0) errors.push("Invalid occurredAt");
          ["pageId", "sourcePageId"].forEach((field) => {
            if (value[field] !== null && (!validId(value[field]) || !String(value[field]).startsWith("page-"))) errors.push(`Invalid ${field}`);
          });
          if (value.tabId !== null && (!Number.isSafeInteger(value.tabId) || value.tabId < 0)) errors.push("Invalid tabId");
          if (value.windowId !== null && !Number.isSafeInteger(value.windowId)) errors.push("Invalid windowId");
          try {
            cloneBehaviorContext(value.context);
          } catch (error) {
            errors.push(error.message);
          }
          return result(errors);
        }
        function validateSuggestion(value) {
          if (!value || typeof value !== "object" || Array.isArray(value)) return result(["Suggestion must be an object"]);
          try {
            const normalized = normalizeSuggestion(value, { now: 0, createId: (prefix) => `${prefix}-validation` });
            const expectedKeys = Object.keys(normalized).sort();
            const actualKeys = Object.keys(value).sort();
            if (JSON.stringify(expectedKeys) !== JSON.stringify(actualKeys) || expectedKeys.some((key) => JSON.stringify(value[key]) !== JSON.stringify(normalized[key]))) {
              return result(["Suggestion is not normalized"]);
            }
            return result([]);
          } catch (error) {
            return result([error.message]);
          }
        }
        function validatePrimaryRelations(relations) {
          const errors = [];
          const byPage = /* @__PURE__ */ new Map();
          const ids = /* @__PURE__ */ new Set();
          (Array.isArray(relations) ? relations : []).forEach((relation) => {
            const validation = validateRelation(relation);
            validation.errors.forEach((error) => errors.push(`${String(relation?.id || "relation")}: ${error}`));
            if (relation?.id && ids.has(relation.id)) errors.push(`Duplicate relation id: ${relation.id}`);
            if (relation?.id) ids.add(relation.id);
            const pageId = String(relation?.pageId || "");
            if (!pageId) return;
            const group = byPage.get(pageId) || [];
            group.push(relation);
            byPage.set(pageId, group);
          });
          byPage.forEach((group, pageId) => {
            const primaries = group.filter((relation) => relation.isPrimary === true).length;
            if (primaries !== 1) errors.push(`Page ${pageId} must have exactly one primary relation`);
          });
          return result(errors);
        }
        return Object.freeze({
          DB_NAME,
          DB_VERSION,
          STORES,
          MAX_CHANGES,
          MAX_BEHAVIOR_CONTEXT_BYTES,
          TASK_DOMAINS,
          TASK_STATUSES,
          TASK_SOURCES,
          RELATION_ROLES,
          RELATION_SOURCES,
          CHANGE_ACTIONS,
          SUGGESTION_KINDS,
          SUGGESTION_ORIGINS,
          SUGGESTION_STATUSES,
          SUGGESTION_RISK_FLAGS,
          SUGGESTION_EVIDENCE_CODES,
          BEHAVIOR_EVENT_TYPES,
          normalizeSafeUrl,
          makeEntityId,
          makeRelationId,
          makeTabInstanceId,
          normalizePageEntity,
          normalizeTask,
          normalizeRelation,
          normalizeChangeRecord,
          normalizeTabInstance,
          normalizeBehaviorEvent,
          normalizeSuggestion,
          validatePageEntity,
          validateTask,
          validateRelation,
          validateChangeRecord,
          validateTabInstance,
          validateBehaviorEvent,
          validateSuggestion,
          validatePrimaryRelations
        });
      });
    }
  });

  // src/shared/v2-behavior-logic.js
  var require_v2_behavior_logic = __commonJS({
    "src/shared/v2-behavior-logic.js"(exports, module) {
      (function attachV2BehaviorLogic(rootScope, factory) {
        const api = factory();
        if (typeof module === "object" && module.exports) module.exports = api;
        rootScope.TfaV2BehaviorLogic = api;
      })(typeof globalThis === "object" ? globalThis : exports, function createV2BehaviorLogic() {
        "use strict";
        const VERY_SHORT_ACTIVATION_MS = 2e3;
        const QUICK_CLOSE_MS = 5e3;
        const RISK_RULE_VERSION = 1;
        const SAFE_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,159}$/;
        const EVENT_TYPES = Object.freeze([
          "tab.opened",
          "tab.activated",
          "tab.navigated",
          "tab.closed",
          "tab.moved",
          "tab.replaced",
          "tab.group_changed",
          "group.updated",
          "window.focus_changed",
          "window.created",
          "window.removed",
          "tab.reconciled_missing",
          "restore.explicit"
        ]);
        const EVENT_TYPE_SET = new Set(EVENT_TYPES);
        const RISK_FLAGS = Object.freeze([
          "quick_pass",
          "possible_misclick",
          "redirect_or_ad_candidate",
          "temporary_query"
        ]);
        const RISK_FLAG_SET = new Set(RISK_FLAGS);
        const REAL_TAB_CHANGE_FIELDS = Object.freeze([
          "windowId",
          "index",
          "groupId",
          "pinned",
          "active",
          "pageId",
          "preInstallHistoryUnknown",
          "lastActivatedAt"
        ]);
        const CONTEXT_FIELDS = Object.freeze({
          "tab.opened": Object.freeze({ openerTabId: "tabId", index: "index" }),
          "tab.activated": Object.freeze({ previousTabId: "tabId", activationDurationMs: "duration", riskFlags: "riskFlags" }),
          "tab.navigated": Object.freeze({ navigationDurationMs: "duration", riskFlags: "riskFlags" }),
          "tab.closed": Object.freeze({ index: "index", groupId: "integer", activationDurationMs: "duration", riskFlags: "riskFlags" }),
          "tab.moved": Object.freeze({ previousWindowId: "integer", previousIndex: "index", nextWindowId: "integer", nextIndex: "index" }),
          "tab.replaced": Object.freeze({ addedTabId: "tabId", removedTabId: "tabId" }),
          "tab.group_changed": Object.freeze({ previousGroupId: "integer", groupId: "integer" }),
          "group.updated": Object.freeze({ groupId: "integer", title: "text", color: "token", collapsed: "boolean" }),
          "window.focus_changed": Object.freeze({
            previousWindowId: "integer",
            previousTabId: "tabId",
            activationDurationMs: "duration"
          }),
          "window.created": Object.freeze({}),
          "window.removed": Object.freeze({}),
          "tab.reconciled_missing": Object.freeze({ reason: "token" }),
          "restore.explicit": Object.freeze({ episodeId: "token", restoreKind: "token", pageIds: "pageIds" })
        });
        function defaultToken() {
          if (typeof crypto === "object" && typeof crypto.randomUUID === "function") return crypto.randomUUID();
          return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 12)}`;
        }
        function asSafeInteger(value, fieldName, minimum = Number.MIN_SAFE_INTEGER) {
          const number = Number(value);
          if (!Number.isSafeInteger(number) || number < minimum) throw new TypeError(`Invalid ${fieldName}`);
          return number;
        }
        function optionalSafeInteger(value, fieldName, minimum = Number.MIN_SAFE_INTEGER) {
          return value == null ? null : asSafeInteger(value, fieldName, minimum);
        }
        function asTimestamp(value, fallback) {
          const number = Number(value);
          return Number.isFinite(number) && number >= 0 ? Math.trunc(number) : fallback;
        }
        function currentTime(now) {
          const supplied = typeof now === "function" ? now() : now;
          return asTimestamp(supplied, Date.now());
        }
        function optionalPageId(value, fieldName) {
          if (value == null || value === "") return null;
          const pageId = String(value).trim();
          if (!SAFE_ID.test(pageId) || !pageId.startsWith("page-")) throw new TypeError(`Invalid ${fieldName}`);
          return pageId;
        }
        function isTrackableUrl(value) {
          try {
            const parsed = new URL(String(value || "").trim());
            return parsed.protocol === "http:" || parsed.protocol === "https:";
          } catch (_error) {
            return false;
          }
        }
        function makeBrowserSessionId(tokenFactory = defaultToken) {
          if (typeof tokenFactory !== "function") throw new TypeError("Browser session token factory must be a function");
          const token = String(tokenFactory() || "").trim();
          if (!SAFE_ID.test(token)) throw new TypeError("Invalid browser session token");
          const sessionId = `session-${token}`;
          if (!SAFE_ID.test(sessionId)) throw new TypeError("Browser session id is too long");
          return sessionId;
        }
        function makeTabInstanceId(browserSessionId, tabId) {
          const sessionId = String(browserSessionId || "").trim();
          const numericTabId = asSafeInteger(tabId, "tabId", 0);
          if (!SAFE_ID.test(sessionId)) throw new TypeError("Invalid browserSessionId");
          return `${sessionId}:${numericTabId}`;
        }
        function normalizeObservedTab(tab = {}, options = {}) {
          const browserSessionId = String(options.browserSessionId || "").trim();
          const tabId = asSafeInteger(tab.id == null ? tab.tabId : tab.id, "tabId", 0);
          const now = currentTime(options.now);
          const firstObservedAt = Math.min(now, asTimestamp(tab.firstObservedAt, now));
          const active = tab.active === true;
          const priorActivatedAt = tab.lastActivatedAt == null ? null : asTimestamp(tab.lastActivatedAt, null);
          const lastActivatedAt = active ? now : priorActivatedAt !== null && priorActivatedAt >= firstObservedAt && priorActivatedAt <= now ? priorActivatedAt : null;
          const observedFrom = String(options.observedFrom || "").trim();
          if (!observedFrom || observedFrom.length > 40) throw new TypeError("Invalid observedFrom");
          return Object.freeze({
            id: makeTabInstanceId(browserSessionId, tabId),
            browserSessionId,
            tabId,
            windowId: asSafeInteger(tab.windowId, "windowId"),
            index: asSafeInteger(tab.index, "index", 0),
            groupId: asSafeInteger(tab.groupId == null ? -1 : tab.groupId, "groupId"),
            pinned: tab.pinned === true,
            active,
            pageId: optionalPageId(options.pageId, "pageId"),
            observedFrom,
            preInstallHistoryUnknown: options.preInstallHistoryUnknown === true || tab.preInstallHistoryUnknown === true || observedFrom === "initial_snapshot",
            firstObservedAt,
            lastSeenAt: now,
            lastActivatedAt
          });
        }
        function diffTabInstance(previous, next) {
          if (!previous || !next) throw new TypeError("Both tab instances are required");
          const changes = {};
          REAL_TAB_CHANGE_FIELDS.forEach((field) => {
            if (!Object.is(previous[field], next[field])) changes[field] = next[field];
          });
          return Object.freeze(changes);
        }
        function normalizeRiskFlags(value) {
          const seen = /* @__PURE__ */ new Set();
          const flags = [];
          (Array.isArray(value) ? value : []).forEach((flag) => {
            if (RISK_FLAG_SET.has(flag) && !seen.has(flag)) {
              seen.add(flag);
              flags.push(flag);
            }
          });
          return Object.freeze(flags);
        }
        function contextValue(kind, value, fieldName) {
          if (value == null) return void 0;
          if (kind === "tabId") return asSafeInteger(value, fieldName, 0);
          if (kind === "index") return asSafeInteger(value, fieldName, 0);
          if (kind === "integer") return asSafeInteger(value, fieldName);
          if (kind === "duration") return asSafeInteger(value, fieldName, 0);
          if (kind === "boolean") return value === true;
          if (kind === "riskFlags") {
            const flags = normalizeRiskFlags(value);
            return flags.length ? flags : void 0;
          }
          if (kind === "pageIds") {
            if (!Array.isArray(value)) throw new TypeError(`Invalid ${fieldName}`);
            const ids = [...new Set(value.map((item) => optionalPageId(item, fieldName)).filter(Boolean))];
            if (!ids.length || ids.length > 32) throw new TypeError(`Invalid ${fieldName}`);
            return Object.freeze(ids);
          }
          const text = String(value).trim();
          if (!text) return void 0;
          if (kind === "token" && !/^[A-Za-z0-9._-]{1,80}$/.test(text)) throw new TypeError(`Invalid ${fieldName}`);
          if (kind === "text" && text.length > 120) throw new TypeError(`Invalid ${fieldName}`);
          return text;
        }
        function makeBehaviorEvent(type, input = {}, options = {}) {
          const eventType = String(type || "").trim();
          if (!EVENT_TYPE_SET.has(eventType)) throw new TypeError("Invalid behavior event type");
          const browserSessionId = String(input.browserSessionId || options.browserSessionId || "").trim();
          makeTabInstanceId(browserSessionId, 0);
          const occurredAt = asTimestamp(input.occurredAt, currentTime(options.now));
          const context = {};
          Object.entries(CONTEXT_FIELDS[eventType]).forEach(([field, kind]) => {
            const value = contextValue(kind, input[field], field);
            if (value !== void 0) context[field] = value;
          });
          return Object.freeze({
            type: eventType,
            occurredAt,
            browserSessionId,
            pageId: optionalPageId(input.pageId, "pageId"),
            tabId: optionalSafeInteger(input.tabId, "tabId", 0),
            windowId: optionalSafeInteger(input.windowId, "windowId"),
            sourcePageId: optionalPageId(input.sourcePageId, "sourcePageId"),
            context: Object.freeze(context)
          });
        }
        function eventForOpen(input, options) {
          return makeBehaviorEvent("tab.opened", input, options);
        }
        function eventForActivation(input = {}, options) {
          return makeBehaviorEvent("tab.activated", {
            ...input,
            sourcePageId: input.sourcePageId == null ? input.previousPageId : input.sourcePageId
          }, options);
        }
        function eventForNavigation(input = {}, options) {
          return makeBehaviorEvent("tab.navigated", {
            ...input,
            sourcePageId: input.sourcePageId == null ? input.previousPageId : input.sourcePageId,
            pageId: input.pageId == null ? input.nextPageId : input.pageId
          }, options);
        }
        function eventForClose(input, options) {
          return makeBehaviorEvent("tab.closed", input, options);
        }
        function eventForMove(input, options) {
          return makeBehaviorEvent("tab.moved", input, options);
        }
        function eventForGroup(input = {}, options) {
          const type = input.type === "group.updated" ? "group.updated" : "tab.group_changed";
          return makeBehaviorEvent(type, input, options);
        }
        function eventForWindow(input = {}, options) {
          const type = ["window.focus_changed", "window.created", "window.removed"].includes(input.type) ? input.type : "window.focus_changed";
          return makeBehaviorEvent(type, input, options);
        }
        function eventForReplaced(input, options) {
          return makeBehaviorEvent("tab.replaced", input, options);
        }
        function eventForReconciledMissing(input, options) {
          return makeBehaviorEvent("tab.reconciled_missing", input, options);
        }
        function eventForExplicitRestore(input, options) {
          return makeBehaviorEvent("restore.explicit", input, options);
        }
        function deriveRiskFlags(input = {}) {
          const flags = [];
          const openedAt = Number(input.openedAt);
          const closedAt = Number(input.closedAt);
          const lifetime = closedAt - openedAt;
          if (Number.isFinite(openedAt) && openedAt >= 0 && Number.isFinite(closedAt) && lifetime >= 0 && lifetime <= QUICK_CLOSE_MS && input.continuedFromPage !== true) {
            flags.push("quick_pass");
          }
          const activationDurationMs = Number(input.activationDurationMs);
          if (Number.isFinite(activationDurationMs) && activationDurationMs >= 0 && activationDurationMs <= VERY_SHORT_ACTIVATION_MS && input.returnedToSource === true) {
            flags.push("possible_misclick");
          }
          if (input.isRedirectCandidate === true && input.continuedFromPage !== true) {
            flags.push("redirect_or_ad_candidate");
          }
          if (input.isTemporaryQuery === true) flags.push("temporary_query");
          return Object.freeze(flags);
        }
        return Object.freeze({
          VERY_SHORT_ACTIVATION_MS,
          QUICK_CLOSE_MS,
          RISK_RULE_VERSION,
          EVENT_TYPES,
          RISK_FLAGS,
          REAL_TAB_CHANGE_FIELDS,
          isTrackableUrl,
          makeBrowserSessionId,
          makeTabInstanceId,
          normalizeObservedTab,
          diffTabInstance,
          makeBehaviorEvent,
          eventForOpen,
          eventForNavigation,
          eventForActivation,
          eventForClose,
          eventForMove,
          eventForGroup,
          eventForWindow,
          eventForReplaced,
          eventForReconciledMissing,
          eventForExplicitRestore,
          deriveRiskFlags
        });
      });
    }
  });

  // src/shared/v2-page-context-logic.js
  var require_v2_page_context_logic = __commonJS({
    "src/shared/v2-page-context-logic.js"(exports, module) {
      (function attachV2PageContextLogic(rootScope, factory) {
        const api = factory();
        if (typeof module === "object" && module.exports) module.exports = api;
        rootScope.TfaV2PageContextLogic = api;
      })(typeof globalThis === "object" ? globalThis : exports, function createV2PageContextLogic() {
        "use strict";
        const CONTEXT_VERSION = "page-context-v1";
        const PATH_TYPES = Object.freeze([
          "root",
          "workspace",
          "project",
          "document",
          "search",
          "media",
          "settings",
          "other"
        ]);
        const RISK_ORDER = Object.freeze([
          "sensitive_site",
          "redirect_or_ad",
          "temporary_query",
          "shared_tool",
          "intent_drift"
        ]);
        const AD_HOSTS = Object.freeze([
          "2mdn.net",
          "adnxs.com",
          "adservice.google.com",
          "doubleclick.net",
          "googleadservices.com",
          "googlesyndication.com",
          "outbrain.com",
          "taboola.com"
        ]);
        const WEATHER_HOSTS = Object.freeze([
          "accuweather.com",
          "nmc.cn",
          "qweather.com",
          "weather.com",
          "weather.gov",
          "weathercn.com",
          "wunderground.com"
        ]);
        const AUTH_HOSTS = Object.freeze([
          "accounts.google.com",
          "account.microsoft.com",
          "login.live.com",
          "login.microsoftonline.com",
          "payments.google.com"
        ]);
        const SAFE_OBJECT_TOKEN = /^[A-Za-z0-9_-]{6,180}$/;
        const NOTION_ID = /(?:^|[-/])([0-9a-f]{32}|[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(?:$|[/?])/i;
        const SENSITIVE_PATH = /(?:^|\/)(?:account|accounts|auth|billing|checkout|login|oauth|password|payment|payments|security|signin|wallet)(?:\/|$)/i;
        const array = (value) => Array.isArray(value) ? value : [];
        function hostMatches(hostname, suffix) {
          return hostname === suffix || hostname.endsWith(`.${suffix}`);
        }
        function listedHost(hostname, hosts) {
          return hosts.some((host) => hostMatches(hostname, host));
        }
        function objectToken(value) {
          const token = String(value || "").trim();
          return SAFE_OBJECT_TOKEN.test(token) ? token : "";
        }
        function parsedPage(page = {}) {
          try {
            const parsed = new URL(String(page.normalizedUrl || page.restoreUrl || page.url || "").trim());
            if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return null;
            return parsed;
          } catch (_error) {
            return null;
          }
        }
        function segments(parsed) {
          return parsed.pathname.split("/").filter(Boolean).map((segment) => {
            try {
              return decodeURIComponent(segment);
            } catch (_error) {
              return segment;
            }
          });
        }
        function stableObject(parsed, parts) {
          const host = parsed.hostname.toLowerCase();
          if (host === "docs.google.com" && parts.length >= 3 && parts[1] === "d") {
            const kind = { document: "document", spreadsheets: "sheet", presentation: "slide" }[parts[0]];
            const token = objectToken(parts[2]);
            if (kind && token) return { key: `google:${kind}:${token}`, family: `google:${kind}`, pathType: "document" };
          }
          if (hostMatches(host, "figma.com") && ["file", "design"].includes(parts[0])) {
            const token = objectToken(parts[1]);
            if (token) return { key: `figma:file:${token}`, family: "figma:file", pathType: "document" };
          }
          if (hostMatches(host, "notion.so") || hostMatches(host, "notion.site")) {
            const match = `${parsed.pathname}/`.match(NOTION_ID);
            if (match) return { key: `notion:page:${match[1].replace(/-/g, "").toLowerCase()}`, family: "notion:page", pathType: "document" };
          }
          if (host === "github.com" && parts.length >= 2 && !parts[0].startsWith(".") && !parts[1].startsWith(".")) {
            const owner = parts[0].toLowerCase();
            const repo = parts[1].replace(/\.git$/i, "").toLowerCase();
            if (/^[a-z0-9_.-]{1,100}$/.test(owner) && /^[a-z0-9_.-]{1,100}$/.test(repo)) {
              if (parts[2] === "issues" && /^\d+$/.test(parts[3] || "")) {
                return { key: `github:issue:${owner}/${repo}#${parts[3]}`, family: `github:repo:${owner}/${repo}`, pathType: "document" };
              }
              if (parts[2] === "pull" && /^\d+$/.test(parts[3] || "")) {
                return { key: `github:pull:${owner}/${repo}#${parts[3]}`, family: `github:repo:${owner}/${repo}`, pathType: "document" };
              }
              if (parts.length === 2) return { key: `github:repo:${owner}/${repo}`, family: `github:repo:${owner}/${repo}`, pathType: "project" };
            }
          }
          if (hostMatches(host, "feishu.cn") || hostMatches(host, "larksuite.com")) {
            const kind = { docx: "doc", docs: "doc", sheets: "sheet", wiki: "wiki", base: "base" }[parts[0]];
            const token = objectToken(parts[1]);
            if (kind && token) return { key: `feishu:${kind}:${token}`, family: `feishu:${kind}`, pathType: kind === "base" ? "project" : "document" };
          }
          if (hostMatches(host, "yuque.com") && parts.length === 3 && parts.every((part) => /^[A-Za-z0-9._~-]{1,120}$/.test(part))) {
            return { key: `yuque:doc:${parts.join("/")}`, family: `yuque:repo:${parts[0]}/${parts[1]}`, pathType: "document" };
          }
          if (hostMatches(host, "coze.com") || hostMatches(host, "coze.cn")) {
            for (let index = 0; index < parts.length - 1; index += 1) {
              const kind = ["bot", "workflow", "project", "project-ide", "database", "knowledge"].includes(parts[index]) ? parts[index] : "";
              const token = objectToken(parts[index + 1]);
              if (kind && token) return { key: `coze:${kind}:${token}`, family: `coze:${kind}`, pathType: "project" };
            }
            const spaceIndex = parts.indexOf("space");
            const spaceId = spaceIndex >= 0 ? objectToken(parts[spaceIndex + 1]) : "";
            if (spaceId) return { key: `coze:space:${spaceId}`, family: "coze:space", pathType: "workspace" };
          }
          return { key: "", family: "", pathType: "" };
        }
        function rootSharedTool(parsed, parts) {
          const host = parsed.hostname.toLowerCase();
          const path = parsed.pathname.replace(/\/+$/, "") || "/";
          if (host === "mail.google.com" && /^\/mail\/u\/\d+$/.test(path)) return true;
          if ((host === "outlook.live.com" || host === "outlook.office.com") && /^\/mail(?:\/0)?$/.test(path)) return true;
          if (host === "calendar.google.com" && (/^\/calendar\/u\/\d+\/r$/.test(path) || path === "/")) return true;
          if ((hostMatches(host, "google.com") || hostMatches(host, "bing.com")) && path === "/") return true;
          if (host === "chatgpt.com" && (path === "/" || path === "/new")) return true;
          if (host === "claude.ai" && (path === "/" || path === "/new")) return true;
          if (host === "gemini.google.com" && (path === "/" || path === "/app")) return true;
          if (["perplexity.ai", "chat.deepseek.com", "kimi.com"].includes(host) && parts.length === 0) return true;
          return false;
        }
        function mediaPage(parsed, parts) {
          const host = parsed.hostname.toLowerCase();
          if (hostMatches(host, "youtube.com") && parts[0] === "watch") return true;
          if (host === "youtu.be" && Boolean(objectToken(parts[0]))) return true;
          if (hostMatches(host, "bilibili.com") && parts[0] === "video" && /^(?:BV|av)[A-Za-z0-9]+$/i.test(parts[1] || "")) return true;
          if (hostMatches(host, "vimeo.com") && /^\d+$/.test(parts[0] || "")) return true;
          if (hostMatches(host, "douyin.com") && parts[0] === "video" && /^\d+$/.test(parts[1] || "")) return true;
          if (hostMatches(host, "twitch.tv") && parts[0] === "videos" && /^\d+$/.test(parts[1] || "")) return true;
          return false;
        }
        function searchPage(parsed) {
          const host = parsed.hostname.toLowerCase();
          return hostMatches(host, "google.com") && parsed.pathname === "/search" || hostMatches(host, "bing.com") && parsed.pathname === "/search" || hostMatches(host, "baidu.com") && parsed.pathname === "/s";
        }
        function analyzePageContext(page = {}, options = {}) {
          const parsed = parsedPage(page);
          if (!parsed) return Object.freeze({
            version: CONTEXT_VERSION,
            stableObjectKey: "",
            stableObjectFamily: "",
            pathType: "other",
            riskFlags: Object.freeze([]),
            knownAdDomain: false,
            media: false
          });
          const host = parsed.hostname.toLowerCase();
          const parts = segments(parsed);
          const stable = stableObject(parsed, parts);
          const knownAdDomain = listedHost(host, AD_HOSTS);
          const temporary = listedHost(host, WEATHER_HOSTS);
          const shared = rootSharedTool(parsed, parts);
          const media = mediaPage(parsed, parts);
          const sensitive = listedHost(host, AUTH_HOSTS) || SENSITIVE_PATH.test(parsed.pathname);
          const titleOverlap = Number(options.titleOverlap);
          const referenceCount = Math.max(0, Math.trunc(Number(options.referenceCount) || 0));
          const risks = /* @__PURE__ */ new Set();
          if (sensitive) risks.add("sensitive_site");
          if (knownAdDomain && options.quickPass === true) risks.add("redirect_or_ad");
          if (temporary) risks.add("temporary_query");
          if (shared) risks.add("shared_tool");
          if (media && referenceCount > 0 && Number.isFinite(titleOverlap) && titleOverlap <= 0.04) risks.add("intent_drift");
          let pathType = stable.pathType || "other";
          if (shared || parts.length === 0) pathType = "root";
          else if (media) pathType = "media";
          else if (searchPage(parsed)) pathType = "search";
          else if (sensitive) pathType = "settings";
          return Object.freeze({
            version: CONTEXT_VERSION,
            stableObjectKey: stable.key,
            stableObjectFamily: stable.family,
            pathType: PATH_TYPES.includes(pathType) ? pathType : "other",
            riskFlags: Object.freeze(RISK_ORDER.filter((risk) => risks.has(risk))),
            knownAdDomain,
            media
          });
        }
        return Object.freeze({ CONTEXT_VERSION, PATH_TYPES, RISK_ORDER, analyzePageContext });
      });
    }
  });

  // src/shared/v2-page-observation-logic.js
  var require_v2_page_observation_logic = __commonJS({
    "src/shared/v2-page-observation-logic.js"(exports, module) {
      (function attachV2PageObservationLogic(rootScope, factory) {
        const pageContextLogic = typeof module === "object" && module.exports ? require_v2_page_context_logic() : rootScope.TfaV2PageContextLogic;
        const api = factory(pageContextLogic);
        if (typeof module === "object" && module.exports) module.exports = api;
        rootScope.TfaV2PageObservationLogic = api;
      })(typeof globalThis === "object" ? globalThis : exports, function createV2PageObservationLogic(pageContextLogic) {
        "use strict";
        if (!pageContextLogic || typeof pageContextLogic.analyzePageContext !== "function") {
          throw new TypeError("Page observation logic requires page context logic");
        }
        const VERSION = "page-observation-v1";
        const OBSERVATION_STATES = Object.freeze([
          "observing",
          "temporary_query",
          "possible_misclick",
          "redirect_or_ad",
          "intent_drift",
          "eligible"
        ]);
        const OBSERVATION_WINDOWS_MS = Object.freeze({
          temporary_query: 3e4,
          search: 45e3,
          redirect_or_ad: 3e4,
          media: 12e4
        });
        const array = (value) => Array.isArray(value) ? value : [];
        const timestamp = (value, fallback = 0) => Number.isFinite(Number(value)) && Number(value) >= 0 ? Math.trunc(Number(value)) : fallback;
        const eventTime = (event) => timestamp(event?.occurredAt);
        const pageIdOf = (page) => String(page?.id || page?.pageId || "").trim();
        const risksOf = (context) => new Set(array(context?.riskFlags).map(String));
        const isManualRelation = (relation, pageId) => relation?.pageId === pageId && relation.source === "manual" && relation.manualLocked === true;
        function titleTokens(value) {
          const text = String(value || "").normalize("NFKC").toLowerCase();
          const tokens = new Set(text.match(/[a-z0-9]{2,}/g) || []);
          for (const segment of text.match(new RegExp("\\p{Script=Han}+", "gu")) || []) {
            if (segment.length === 1) tokens.add(segment);
            else for (let index = 0; index < segment.length - 1; index += 1) tokens.add(segment.slice(index, index + 2));
          }
          return tokens;
        }
        function titleOverlap(left, right) {
          const leftTokens = titleTokens(left), rightTokens = titleTokens(right);
          if (!leftTokens.size || !rightTokens.size) return 0;
          let matches = 0;
          leftTokens.forEach((token) => {
            if (rightTokens.has(token)) matches += 1;
          });
          return matches / (/* @__PURE__ */ new Set([...leftTokens, ...rightTokens])).size;
        }
        function scopedEvents(input, pageId) {
          const sessionId = String(input.browserSessionId || "").trim();
          return array(input.events).filter((event) => event && (!sessionId || event.browserSessionId === sessionId) && (event.pageId === pageId || event.sourcePageId === pageId)).sort((left, right) => eventTime(left) - eventTime(right) || String(left.id || "").localeCompare(String(right.id || "")));
        }
        function hasPossibleMisclick(events, pageId) {
          const entries = events.map((event, index) => ({ event, index })).filter(({ event }) => event.type === "tab.activated" && event.pageId === pageId && event.sourcePageId && event.sourcePageId !== pageId);
          for (let cursor = entries.length - 1; cursor >= 0; cursor -= 1) {
            const { event: entry, index } = entries[cursor];
            const nextIndex = events.findIndex((event, candidateIndex) => candidateIndex > index && event.type === "tab.activated" && event.windowId === entry.windowId);
            if (nextIndex < 0) continue;
            const returned = events[nextIndex];
            const interveningAction = events.slice(index + 1, nextIndex).some((event) => event.type !== "tab.activated" && (event.pageId === pageId || event.sourcePageId === pageId));
            if (!interveningAction && returned.pageId === entry.sourcePageId && returned.sourcePageId === pageId && timestamp(returned.context?.activationDurationMs, Number.POSITIVE_INFINITY) <= 1500) return true;
          }
          return false;
        }
        function hasQuickPass(events) {
          return events.some((event) => event.type === "tab.closed" && array(event.context?.riskFlags).includes("quick_pass"));
        }
        function hasShortRedirect(events, pageId) {
          return events.some((event) => event.type === "tab.navigated" && event.sourcePageId === pageId && timestamp(event.context?.navigationDurationMs, Number.POSITIVE_INFINITY) <= 5e3);
        }
        function currentInstanceObservedAt(input, pageId) {
          const sessionId = String(input.browserSessionId || "").trim();
          const observed = array(input.currentInstances).filter((instance) => instance?.pageId === pageId && (!sessionId || instance.browserSessionId === sessionId)).map((instance) => timestamp(instance.firstObservedAt, -1)).filter((value) => value >= 0);
          return observed.length ? Math.max(...observed) : -1;
        }
        function observedAt(input, page, events, pageId, now) {
          const instanceTime = currentInstanceObservedAt(input, pageId);
          if (instanceTime >= 0) return instanceTime;
          const supplied = timestamp(input.firstObservedAt, -1);
          if (supplied >= 0) return supplied;
          const opens = events.filter((event) => event.type === "tab.opened" && event.pageId === pageId);
          if (opens.length) return eventTime(opens.at(-1));
          return timestamp(page?.createdAt, timestamp(page?.updatedAt, now));
        }
        function reevaluationEvidence(events, relations, pageId) {
          const opens = events.filter((event) => event.type === "tab.opened" && event.pageId === pageId);
          if (opens.length >= 2) return "repeated_visit";
          const latest = opens.at(-1);
          if (latest?.sourcePageId && relations.some((relation) => isManualRelation(relation, latest.sourcePageId))) {
            return "strong_opener";
          }
          return "";
        }
        function confirmedOpenerReference(events, relations, pages, pageId) {
          const latest = events.filter((event) => event.type === "tab.opened" && event.pageId === pageId && event.sourcePageId).at(-1);
          if (!latest || !relations.some((relation) => isManualRelation(relation, latest.sourcePageId))) return null;
          return pages.find((page) => pageIdOf(page) === latest.sourcePageId) || null;
        }
        function result(observationState, reasonCode, observeUntil = null, hardRisk = false) {
          return Object.freeze({
            version: VERSION,
            observationState,
            reasonCode,
            observeUntil,
            hardRisk,
            suppressSuggestions: observationState === "observing" || hardRisk
          });
        }
        function classifyPageObservation(input = {}) {
          const page = input.page || {};
          const pageId = pageIdOf(page);
          const now = timestamp(input.now, Date.now());
          if (array(input.relations).some((relation) => isManualRelation(relation, pageId))) {
            return result("eligible", "manual_relation");
          }
          const events = scopedEvents(input, pageId);
          if (hasPossibleMisclick(events, pageId)) return result("possible_misclick", "possible_misclick", null, true);
          const quickPass = hasQuickPass(events), shortRedirect = hasShortRedirect(events, pageId);
          const openerReference = confirmedOpenerReference(events, array(input.relations), array(input.pages), pageId);
          const pageContext = input.pageContext || pageContextLogic.analyzePageContext(page, {
            quickPass: quickPass || shortRedirect,
            ...openerReference ? { referenceCount: 1, titleOverlap: titleOverlap(page.title, openerReference.title) } : {}
          });
          const risks = risksOf(pageContext);
          if ((risks.has("redirect_or_ad") || pageContext.knownAdDomain === true) && (quickPass || shortRedirect)) {
            return result("redirect_or_ad", "redirect_or_ad", null, true);
          }
          let observationKind = "";
          if (risks.has("temporary_query")) observationKind = "temporary_query";
          else if (pageContext.pathType === "search") observationKind = "search";
          else if (risks.has("redirect_or_ad") || pageContext.knownAdDomain === true) observationKind = "redirect_or_ad";
          else if (pageContext.media === true || pageContext.pathType === "media") observationKind = "media";
          if (!observationKind) return result("eligible", "ordinary_page");
          const observeUntil = observedAt(input, page, events, pageId, now) + OBSERVATION_WINDOWS_MS[observationKind];
          if (now < observeUntil) return result("observing", `observing_${observationKind}`, observeUntil);
          if (observationKind === "temporary_query" || observationKind === "search") {
            const reconsidered = reevaluationEvidence(events, array(input.relations), pageId);
            if (reconsidered) return result("eligible", reconsidered, observeUntil);
            return result("temporary_query", "temporary_query", observeUntil, true);
          }
          if (observationKind === "redirect_or_ad") return result("redirect_or_ad", "redirect_or_ad", observeUntil, true);
          if (risks.has("intent_drift")) return result("intent_drift", "intent_drift", observeUntil, true);
          return result("eligible", "media_observed", observeUntil);
        }
        return Object.freeze({ VERSION, OBSERVATION_STATES, OBSERVATION_WINDOWS_MS, classifyPageObservation });
      });
    }
  });

  // src/shared/v2-new-task-cluster-logic.js
  var require_v2_new_task_cluster_logic = __commonJS({
    "src/shared/v2-new-task-cluster-logic.js"(exports, module) {
      (function attachV2NewTaskClusterLogic(rootScope, factory) {
        const pageContextLogic = typeof module === "object" && module.exports ? require_v2_page_context_logic() : rootScope.TfaV2PageContextLogic;
        const api = factory(pageContextLogic);
        if (typeof module === "object" && module.exports) module.exports = api;
        rootScope.TfaV2NewTaskClusterLogic = api;
      })(typeof globalThis === "object" ? globalThis : exports, function createV2NewTaskClusterLogic(pageContextLogic) {
        "use strict";
        if (!pageContextLogic || typeof pageContextLogic.analyzePageContext !== "function") {
          throw new TypeError("New task clustering requires page context logic");
        }
        const MECHANISM_VERSION = "new-task-cluster-v1";
        const SAFE_PAGE_ID = /^page-[A-Za-z0-9._:-]{1,154}$/;
        const AUTO_THRESHOLD = 0.86;
        const SUGGEST_THRESHOLD = 0.68;
        const EXCLUDED_RISKS = /* @__PURE__ */ new Set([
          "possible_misclick",
          "redirect_or_ad",
          "temporary_query",
          "shared_tool",
          "intent_drift",
          "sensitive_site",
          "object_conflict",
          "noise_risk",
          "manual_rejected",
          "cooldown"
        ]);
        const FAMILY_SUPPORT = Object.freeze({ stable_object: 0.74, opener_chain: 0.6, co_restore: 0.68 });
        const EVIDENCE_CODE = Object.freeze({
          stable_object: "stable_object",
          opener_chain: "opener_link",
          co_restore: "task_snapshot_restore"
        });
        const array = (value) => Array.isArray(value) ? value : [];
        const number = (value, fallback = 0) => Number.isFinite(Number(value)) ? Number(value) : fallback;
        function hashToken(value) {
          let hash = 2166136261;
          const text = JSON.stringify(value);
          for (let index = 0; index < text.length; index += 1) {
            hash = Math.imul(hash ^ text.charCodeAt(index), 16777619) >>> 0;
          }
          return hash.toString(36);
        }
        function pairKey(left, right) {
          return [left, right].sort().join("::");
        }
        function rounded(value) {
          return Math.round(value * 1e6) / 1e6;
        }
        function combinedSupport(values) {
          return rounded(1 - values.reduce((remaining, value) => remaining * (1 - value), 1));
        }
        function titleTokens(value) {
          const text = String(value || "").normalize("NFKC").toLowerCase();
          const result = new Set(text.match(/[a-z0-9]{2,}/g) || []);
          for (const segment of text.match(new RegExp("\\p{Script=Han}+", "gu")) || []) {
            for (let index = 0; index < segment.length - 1; index += 1) result.add(segment.slice(index, index + 2));
          }
          return result;
        }
        function titleOverlap(left, right) {
          const a = titleTokens(left), b = titleTokens(right);
          if (!a.size || !b.size) return 0;
          let hits = 0;
          a.forEach((token) => {
            if (b.has(token)) hits += 1;
          });
          return hits / (/* @__PURE__ */ new Set([...a, ...b])).size;
        }
        function eventKey(event) {
          return String(event?.id || `${event?.browserSessionId || ""}:${event?.tabId ?? ""}:${event?.occurredAt || 0}`);
        }
        function pageEventRisks(pageId, events) {
          const risks = /* @__PURE__ */ new Set();
          const activations = events.filter((event) => event?.type === "tab.activated");
          const latestEntryIndex = activations.reduce((latest2, event, index) => event.pageId === pageId && event.sourcePageId && event.sourcePageId !== pageId ? index : latest2, -1);
          if (latestEntryIndex >= 0) {
            const entry = activations[latestEntryIndex];
            const returned = activations.slice(latestEntryIndex + 1).find((event) => event.windowId === entry.windowId);
            if (returned && returned.pageId === entry.sourcePageId && returned.sourcePageId === pageId && number(returned.context?.activationDurationMs, Infinity) <= 1500) risks.add("possible_misclick");
          }
          const targetEvents = events.filter((event) => event?.pageId === pageId || event?.sourcePageId === pageId);
          const latest = targetEvents.at(-1);
          if (latest?.type === "tab.closed" && array(latest.context?.riskFlags).includes("quick_pass")) risks.add("quick_pass");
          return risks;
        }
        function buildNewTaskClusters(input = {}) {
          const browserSessionId = String(input.browserSessionId || "").trim();
          const relations = array(input.relations);
          const related = new Set(relations.map((relation) => String(relation?.pageId || "")).filter(Boolean));
          const instances = array(input.tabInstances).filter((instance) => !browserSessionId || instance?.browserSessionId === browserSessionId);
          const open = new Set(instances.map((instance) => String(instance?.pageId || "")).filter(Boolean));
          const requested = new Set(array(input.pageIds).map(String));
          const pages = array(input.pages).filter((page) => SAFE_PAGE_ID.test(String(page?.id || "")) && (!requested.size || requested.has(page.id)) && !related.has(page.id) && open.has(page.id));
          const pageById = new Map(pages.map((page) => [page.id, page]));
          const events = array(input.events).filter((event) => !browserSessionId || event?.browserSessionId === browserSessionId).sort((left, right) => number(left?.occurredAt) - number(right?.occurredAt) || eventKey(left).localeCompare(eventKey(right)));
          const contextByPage = /* @__PURE__ */ new Map();
          const risksByPage = /* @__PURE__ */ new Map();
          pages.forEach((page) => {
            const shortAdPass = events.some((event) => event?.type === "tab.navigated" && event.sourcePageId === page.id && number(event.context?.navigationDurationMs, Infinity) <= 5e3);
            const context = pageContextLogic.analyzePageContext(page, { quickPass: shortAdPass });
            contextByPage.set(page.id, context);
            const risks = /* @__PURE__ */ new Set([...context.riskFlags, ...pageEventRisks(page.id, events)]);
            if (instances.some((instance) => instance.pageId === page.id && (instance.preInstallHistoryUnknown === true || instance.observedFrom === "initial_snapshot"))) {
              risks.add("initial_snapshot");
            }
            risksByPage.set(page.id, risks);
          });
          const edgeByPair = /* @__PURE__ */ new Map();
          function edge(left, right) {
            if (left === right || !pageById.has(left) || !pageById.has(right)) return null;
            const key = pairKey(left, right);
            if (!edgeByPair.has(key)) edgeByPair.set(key, { pageIds: key.split("::"), families: /* @__PURE__ */ new Map() });
            return edgeByPair.get(key);
          }
          for (let leftIndex = 0; leftIndex < pages.length; leftIndex += 1) {
            for (let rightIndex = leftIndex + 1; rightIndex < pages.length; rightIndex += 1) {
              const left = contextByPage.get(pages[leftIndex].id), right = contextByPage.get(pages[rightIndex].id);
              if (left.stableObjectKey && left.stableObjectKey === right.stableObjectKey) {
                edge(pages[leftIndex].id, pages[rightIndex].id).families.set("stable_object", FAMILY_SUPPORT.stable_object);
              }
            }
          }
          const seenOpenEvents = /* @__PURE__ */ new Set();
          const openerCounts = /* @__PURE__ */ new Map();
          events.forEach((event) => {
            if (event?.type !== "tab.opened" || !pageById.has(event.pageId) || !pageById.has(event.sourcePageId)) return;
            const key = eventKey(event);
            if (seenOpenEvents.has(key)) return;
            seenOpenEvents.add(key);
            const pair = pairKey(event.pageId, event.sourcePageId);
            openerCounts.set(pair, (openerCounts.get(pair) || 0) + 1);
          });
          openerCounts.forEach((count, key) => {
            const target = edge(...key.split("::"));
            target.families.set("opener_chain", count >= 2 ? 0.68 : FAMILY_SUPPORT.opener_chain);
          });
          const restoreCounts = /* @__PURE__ */ new Map();
          const seenEpisodes = /* @__PURE__ */ new Set();
          array(input.restoreEpisodes).forEach((episode) => {
            const episodeId = String(episode?.context?.episodeId || episode?.id || "").trim();
            if (!episodeId || seenEpisodes.has(episodeId) || !(episode?.explicit === true || episode?.type === "explicit_restore" || episode?.type === "restore.explicit")) return;
            seenEpisodes.add(episodeId);
            const ids = [...new Set(array(episode.pageIds || episode?.context?.pageIds).map(String).filter((id) => pageById.has(id)))].sort();
            for (let left = 0; left < ids.length; left += 1) for (let right = left + 1; right < ids.length; right += 1) {
              const key = pairKey(ids[left], ids[right]);
              restoreCounts.set(key, (restoreCounts.get(key) || 0) + 1);
            }
          });
          restoreCounts.forEach((count, key) => {
            if (count < 2) return;
            edge(...key.split("::")).families.set("co_restore", count >= 3 ? 0.72 : FAMILY_SUPPORT.co_restore);
          });
          const proposedSets = [];
          const stableGroups = /* @__PURE__ */ new Map();
          pages.forEach((page) => {
            const key = contextByPage.get(page.id).stableObjectKey;
            if (!key) return;
            const group = stableGroups.get(key) || [];
            group.push(page.id);
            stableGroups.set(key, group);
          });
          stableGroups.forEach((pageIds) => {
            if (pageIds.length >= 2) proposedSets.push(pageIds.sort());
          });
          edgeByPair.forEach((value) => {
            if (![...stableGroups.values()].some((group) => value.pageIds.every((id) => group.includes(id)))) {
              proposedSets.push([...value.pageIds]);
            }
          });
          const uniqueSets = new Map(proposedSets.map((ids) => [ids.join("|"), ids]));
          const candidates = [];
          uniqueSets.forEach((pageIds) => {
            const clusterRisks = new Set(pageIds.flatMap((id) => [...risksByPage.get(id)]));
            if ([...clusterRisks].some((risk) => EXCLUDED_RISKS.has(risk))) return;
            for (const id of pageIds) {
              const context = contextByPage.get(id);
              if (!context.media) continue;
              const bestOverlap = Math.max(0, ...pageIds.filter((peer) => peer !== id).map((peer) => titleOverlap(pageById.get(id).title, pageById.get(peer).title)));
              if (bestOverlap <= 0.04) clusterRisks.add("intent_drift");
            }
            if ([...clusterRisks].some((risk) => EXCLUDED_RISKS.has(risk))) return;
            const nodeSupports = pageIds.map((id) => {
              const families = /* @__PURE__ */ new Map();
              pageIds.filter((peer) => peer !== id).forEach((peer) => {
                edgeByPair.get(pairKey(id, peer))?.families.forEach((support, family) => {
                  families.set(family, Math.max(families.get(family) || 0, support));
                });
              });
              return { id, families, confidence: combinedSupport([...families.values()]) };
            });
            const confidence = Math.min(...nodeSupports.map((item) => item.confidence));
            if (confidence < SUGGEST_THRESHOLD) return;
            const signalFamilies = [...new Set(nodeSupports.flatMap((item) => [...item.families.keys()]))].sort();
            const evidenceCodes = signalFamilies.map((family) => EVIDENCE_CODE[family]).filter(Boolean);
            const fingerprint = `cluster-${hashToken([pageIds, signalFamilies])}`;
            const cooled = array(input.cooldowns).some((item) => item?.fingerprint === fingerprint && number(item.expiresAt) > number(input.now, Date.now()));
            if (cooled) clusterRisks.add("cooldown");
            const disposition = pageIds.length <= 8 && confidence >= AUTO_THRESHOLD && signalFamilies.length >= 2 && clusterRisks.size === 0 ? "auto_candidate" : "suggest_only";
            candidates.push({ pageIds, confidence, signalFamilies, evidenceCodes, riskFlags: [...clusterRisks].sort(), fingerprint, disposition });
          });
          const membership = /* @__PURE__ */ new Map();
          candidates.forEach((cluster) => cluster.pageIds.forEach((id) => membership.set(id, (membership.get(id) || 0) + 1)));
          candidates.forEach((cluster) => {
            if (cluster.pageIds.some((id) => membership.get(id) > 1)) {
              cluster.riskFlags = [.../* @__PURE__ */ new Set([...cluster.riskFlags, "ambiguous"])].sort();
              cluster.disposition = "suggest_only";
            }
          });
          candidates.sort((left, right) => right.confidence - left.confidence || left.pageIds.join("|").localeCompare(right.pageIds.join("|")));
          const inputVersion = `cluster-input-v1:${hashToken([
            MECHANISM_VERSION,
            pageContextLogic.CONTEXT_VERSION,
            pages.map((page) => [page.id, number(page.updatedAt), [...risksByPage.get(page.id)].sort()]).sort(),
            relations.map((item) => [item.pageId, item.taskId, number(item.updatedAt)]).sort(),
            [...edgeByPair].map(([key, value]) => [key, [...value.families]]).sort(),
            array(input.cooldowns).map((item) => [String(item?.fingerprint || ""), number(item?.expiresAt)]).sort()
          ])}`;
          return Object.freeze({
            mechanismVersion: MECHANISM_VERSION,
            inputVersion,
            clusters: Object.freeze(candidates.map((cluster) => Object.freeze({
              clusterId: `new-task-${hashToken([inputVersion, cluster.pageIds])}`,
              fingerprint: cluster.fingerprint,
              pageIds: Object.freeze([...cluster.pageIds]),
              confidence: cluster.confidence,
              independentSignalFamilies: cluster.signalFamilies.length,
              evidenceCodes: Object.freeze([...cluster.evidenceCodes]),
              riskFlags: Object.freeze([...cluster.riskFlags]),
              disposition: cluster.disposition
            })))
          });
        }
        return Object.freeze({
          MECHANISM_VERSION,
          AUTO_THRESHOLD,
          SUGGEST_THRESHOLD,
          buildNewTaskClusters
        });
      });
    }
  });

  // src/shared/v2-signal-aggregation-logic.js
  var require_v2_signal_aggregation_logic = __commonJS({
    "src/shared/v2-signal-aggregation-logic.js"(exports, module) {
      (function attachV2SignalAggregationLogic(rootScope, factory) {
        const pageContextLogic = typeof module === "object" && module.exports ? require_v2_page_context_logic() : rootScope.TfaV2PageContextLogic;
        const api = factory(pageContextLogic);
        if (typeof module === "object" && module.exports) module.exports = api;
        rootScope.TfaV2SignalAggregationLogic = api;
      })(typeof globalThis === "object" ? globalThis : exports, function createV2SignalAggregationLogic(pageContextLogic) {
        "use strict";
        if (!pageContextLogic || typeof pageContextLogic.analyzePageContext !== "function") {
          throw new TypeError("V2 signal aggregation requires page context logic");
        }
        const SIGNAL_VERSION = "safe-signals-v3";
        const DEFAULT_LOOKBACK_MS = 30 * 24 * 60 * 60 * 1e3;
        const DEFAULT_DWELL_MS = 1e4;
        const DEFAULT_DEBOUNCE_MS = 5 * 60 * 1e3;
        const DEFAULT_MAX_EPISODE_MS = 30 * 60 * 1e3;
        const LIMITATIONS = Object.freeze([
          "stable_object_high_confidence_routes_only",
          "task_snapshot_restore_unavailable",
          "native_group_restore_initial_snapshot_only",
          "events_without_session_ignored"
        ]);
        const SAFE_ID = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,159}$/;
        const STOP_WORDS = /* @__PURE__ */ new Set([
          "the",
          "and",
          "for",
          "with",
          "from",
          "home",
          "page",
          "website",
          "app",
          "web",
          "\u9996\u9875",
          "\u9875\u9762",
          "\u7F51\u7AD9",
          "\u7CFB\u7EDF",
          "\u5E73\u53F0",
          "\u5DE5\u5177",
          "\u767B\u5F55",
          "\u6D4F\u89C8\u5668"
        ]);
        const array = (value) => Array.isArray(value) ? value : [];
        const number = (value, fallback) => Number.isFinite(Number(value)) ? Number(value) : fallback;
        function safeId(value, field) {
          const result = String(value || "").trim();
          if (!SAFE_ID.test(result)) throw new TypeError(`${field} is required`);
          return result;
        }
        function eventTime(event) {
          return number(event && event.occurredAt, 0);
        }
        function eventKey(event) {
          return String(event && event.id || `${event?.browserSessionId || ""}:${event?.tabId ?? ""}:${eventTime(event)}:${event?.sourcePageId || ""}`);
        }
        function manualRelation(relation) {
          return relation && relation.source === "manual" && relation.manualLocked === true;
        }
        function leaveDuration(event) {
          if (event.type === "tab.activated") return number(event.context && event.context.activationDurationMs, -1);
          if (event.type === "tab.navigated") return number(event.context && event.context.navigationDurationMs, -1);
          if (event.type === "tab.closed") return number(event.context && event.context.activationDurationMs, -1);
          return -1;
        }
        function isLeaveEvent(event, pageId, tabId) {
          if (event.tabId !== tabId) return false;
          if (event.type === "tab.closed") return event.pageId === pageId;
          return (event.type === "tab.activated" || event.type === "tab.navigated") && event.sourcePageId === pageId;
        }
        function titleTokens(value, ignoredTokens) {
          const text = String(value || "").normalize("NFKC").toLowerCase();
          const tokens = /* @__PURE__ */ new Set();
          for (const word of text.match(/[a-z0-9]{2,}/g) || []) tokens.add(word);
          for (const segment of text.match(new RegExp("\\p{Script=Han}+", "gu")) || []) {
            if (segment.length === 1) tokens.add(segment);
            else for (let index = 0; index < segment.length - 1; index += 1) tokens.add(segment.slice(index, index + 2));
          }
          for (const token of [...tokens]) {
            if (STOP_WORDS.has(token) || ignoredTokens.has(token)) tokens.delete(token);
          }
          return tokens;
        }
        function ignoredDomainTokens(domains) {
          return new Set(array(domains).flatMap((domain) => String(domain || "").toLowerCase().split(/[^a-z0-9]+/)).filter((token) => token.length >= 2));
        }
        function overlap(left, right, ignoredTokens) {
          const leftTokens = titleTokens(left, ignoredTokens);
          const rightTokens = titleTokens(right, ignoredTokens);
          if (!leftTokens.size || !rightTokens.size) return 0;
          let matches = 0;
          leftTokens.forEach((token) => {
            if (rightTokens.has(token)) matches += 1;
          });
          return matches / (/* @__PURE__ */ new Set([...leftTokens, ...rightTokens])).size;
        }
        function aggregateSuggestionSignals(input = {}) {
          const page = input.page || {};
          const pageId = safeId(page.id || input.pageId, "pageId");
          const browserSessionId = safeId(input.browserSessionId, "browserSessionId");
          const now = number(input.now, Date.now());
          const lookbackMs = Math.max(0, number(input.lookbackMs, DEFAULT_LOOKBACK_MS));
          const minDwellMs = Math.max(0, number(input.minDwellMs, DEFAULT_DWELL_MS));
          const debounceMs = Math.max(0, number(input.debounceMs, DEFAULT_DEBOUNCE_MS));
          const maxEpisodeMs = Math.max(minDwellMs, number(input.maxEpisodeMs, DEFAULT_MAX_EPISODE_MS));
          const maxCandidates = Math.min(8, Math.max(1, Math.trunc(number(input.maxCandidates, 8))));
          const pages = array(input.pages);
          const tasks = array(input.tasks);
          const relations = array(input.relations);
          const suggestions = array(input.suggestions);
          const pageById = new Map(pages.concat(page).filter((item) => item && item.id).map((item) => [item.id, item]));
          const pageContextById = new Map([...pageById].map(([id, item]) => [id, pageContextLogic.analyzePageContext(item)]));
          const targetContext = pageContextLogic.analyzePageContext(page);
          const taskById = new Map(tasks.filter((task) => task && task.id && task.status !== "done").map((task) => [task.id, task]));
          const existingTaskIds = new Set(relations.filter((relation) => relation && relation.pageId === pageId).map((relation) => relation.taskId));
          const manual = relations.filter((relation) => manualRelation(relation) && taskById.has(relation.taskId));
          const anchorRelationsByPage = /* @__PURE__ */ new Map();
          const anchorRelationsByTask = /* @__PURE__ */ new Map();
          manual.forEach((relation) => {
            if (!anchorRelationsByPage.has(relation.pageId)) anchorRelationsByPage.set(relation.pageId, []);
            anchorRelationsByPage.get(relation.pageId).push(relation);
            if (!anchorRelationsByTask.has(relation.taskId)) anchorRelationsByTask.set(relation.taskId, []);
            anchorRelationsByTask.get(relation.taskId).push(relation);
          });
          const instances = array(input.tabInstances).filter((instance) => instance && instance.browserSessionId === browserSessionId);
          const events = array(input.events).filter((event) => event && event.browserSessionId === browserSessionId && eventTime(event) >= now - lookbackMs && eventTime(event) <= now).sort((left, right) => eventTime(left) - eventTime(right) || eventKey(left).localeCompare(eventKey(right)));
          const targetInstances = instances.filter((instance) => instance.pageId === pageId);
          const stats = /* @__PURE__ */ new Map();
          function candidate(taskId) {
            if (!taskById.has(taskId) || existingTaskIds.has(taskId)) return null;
            if (!stats.has(taskId)) stats.set(taskId, {
              taskId,
              openerHits: 0,
              stableObjectMatch: false,
              qualifiedReturnCount: 0,
              sameNativeGroup: false,
              sameWindow: false,
              sameDomain: false,
              priority: 0,
              strong: false
            });
            return stats.get(taskId);
          }
          const seenOpenEvents = /* @__PURE__ */ new Set();
          events.filter((event) => event.type === "tab.opened" && event.pageId === pageId).forEach((event) => {
            const key = eventKey(event);
            if (seenOpenEvents.has(key)) return;
            seenOpenEvents.add(key);
            const knownAnchors = (anchorRelationsByPage.get(event.sourcePageId) || []).filter((relation) => Number.isFinite(Number(relation.createdAt)) && Number(relation.createdAt) <= eventTime(event));
            for (const taskId of new Set(knownAnchors.map((relation) => relation.taskId))) {
              const item = candidate(taskId);
              if (!item) continue;
              item.openerHits = Math.min(3, item.openerHits + 1);
              item.priority = Math.max(item.priority, 5);
              item.strong = true;
            }
          });
          const lastQualifiedAt = /* @__PURE__ */ new Map();
          const usedQualifiedLeaves = /* @__PURE__ */ new Set();
          events.forEach((entry, entryIndex) => {
            if (entry.type !== "tab.activated" || entry.pageId !== pageId || entry.sourcePageId === pageId) return;
            const anchorTaskIds = new Set((anchorRelationsByPage.get(entry.sourcePageId) || []).filter((relation) => Number.isFinite(Number(relation.createdAt)) && Number(relation.createdAt) <= eventTime(entry)).map((relation) => relation.taskId));
            if (!anchorTaskIds.size) return;
            const leave = events.slice(entryIndex + 1).find((event) => isLeaveEvent(event, pageId, entry.tabId) && event.windowId === entry.windowId && eventTime(event) - eventTime(entry) <= maxEpisodeMs);
            if (!leave || leaveDuration(leave) < minDwellMs) return;
            for (const taskId of anchorTaskIds) {
              const episodeKey = `${taskId}:${eventKey(leave)}`;
              if (usedQualifiedLeaves.has(episodeKey)) continue;
              const previous = lastQualifiedAt.get(taskId);
              if (previous != null && eventTime(entry) - previous < debounceMs) continue;
              const item = candidate(taskId);
              if (!item) continue;
              item.qualifiedReturnCount = Math.min(4, item.qualifiedReturnCount + 1);
              item.priority = Math.max(item.priority, 4);
              item.strong = true;
              lastQualifiedAt.set(taskId, eventTime(entry));
              usedQualifiedLeaves.add(episodeKey);
            }
          });
          taskById.forEach((task, taskId) => {
            if (existingTaskIds.has(taskId)) return;
            const anchors = anchorRelationsByTask.get(taskId) || [];
            if (!anchors.length) return;
            const anchorPageIds = new Set(anchors.map((relation) => relation.pageId));
            const anchorInstances = instances.filter((instance) => anchorPageIds.has(instance.pageId));
            const item = candidate(taskId);
            item.stableObjectMatch = Boolean(targetContext.stableObjectKey) && anchors.some((relation) => pageContextById.get(relation.pageId)?.stableObjectKey === targetContext.stableObjectKey);
            item.sameNativeGroup = targetInstances.some((target) => target.groupId >= 0 && anchorInstances.some((anchor) => anchor.windowId === target.windowId && anchor.groupId === target.groupId && anchor.groupId >= 0));
            item.sameWindow = targetInstances.some((target) => anchorInstances.some((anchor) => anchor.windowId === target.windowId));
            item.sameDomain = Boolean(page.domain) && anchors.some((relation) => pageById.get(relation.pageId)?.domain === page.domain);
            if (item.stableObjectMatch) {
              item.priority = Math.max(item.priority, 6);
              item.strong = true;
            }
            if (item.sameNativeGroup) item.priority = Math.max(item.priority, 3);
            if (item.sameWindow) item.priority = Math.max(item.priority, 2);
            if (item.sameDomain) item.priority = Math.max(item.priority, 1);
            if (!item.priority) stats.delete(taskId);
          });
          const hasRuntimeOpen = events.some((event) => event.type === "tab.opened" && event.pageId === pageId);
          const initialSnapshot = !hasRuntimeOpen && targetInstances.some((instance) => instance.preInstallHistoryUnknown === true || instance.observedFrom === "initial_snapshot");
          const targetEvents = events.filter((event) => event.pageId === pageId || event.sourcePageId === pageId);
          const latestTargetEvent = targetEvents[targetEvents.length - 1];
          const quickPass = latestTargetEvent?.type === "tab.closed" && array(latestTargetEvent.context?.riskFlags).includes("quick_pass");
          const shortRedirect = latestTargetEvent?.type === "tab.navigated" && number(latestTargetEvent.context?.navigationDurationMs, Number.POSITIVE_INFINITY) <= 5e3;
          const latestTargetActivationIndex = events.reduce((latest, event, index) => event.type === "tab.activated" && event.pageId === pageId && event.sourcePageId && event.sourcePageId !== pageId ? index : latest, -1);
          let possibleMisclick = false;
          if (latestTargetActivationIndex >= 0) {
            const entry = events[latestTargetActivationIndex];
            const nextActivationIndex = events.findIndex((event, index) => index > latestTargetActivationIndex && event.type === "tab.activated" && event.windowId === entry.windowId);
            if (nextActivationIndex > latestTargetActivationIndex) {
              const returned = events[nextActivationIndex];
              const interveningTargetAction = events.slice(latestTargetActivationIndex + 1, nextActivationIndex).some((event) => event.type !== "tab.activated" && (event.pageId === pageId || event.sourcePageId === pageId));
              possibleMisclick = !interveningTargetAction && returned.pageId === entry.sourcePageId && returned.sourcePageId === pageId && number(returned.context?.activationDurationMs, Number.POSITIVE_INFINITY) <= 1500;
            }
          }
          const candidates = [...stats.values()].filter((item) => taskById.get(item.taskId)?.status !== "paused" || item.strong).sort((left, right) => right.priority - left.priority || right.openerHits - left.openerHits || right.qualifiedReturnCount - left.qualifiedReturnCount || number(taskById.get(left.taskId)?.sortOrder, 0) - number(taskById.get(right.taskId)?.sortOrder, 0) || left.taskId.localeCompare(right.taskId)).slice(0, maxCandidates).map((item) => {
            const task = taskById.get(item.taskId);
            const anchors = anchorRelationsByTask.get(item.taskId) || [];
            const preferredAnchors = anchors.filter((relation) => relation.isPrimary === true || relation.role === "core");
            const titleSources = [task.title, ...(preferredAnchors.length ? preferredAnchors : anchors).map((relation) => pageById.get(relation.pageId)?.title).filter(Boolean)];
            const ignoredTokens = ignoredDomainTokens([page.domain, ...anchors.map((relation) => pageById.get(relation.pageId)?.domain)]);
            const titleOverlap = titleSources.reduce((best, title) => Math.max(best, overlap(page.title, title, ignoredTokens)), 0);
            const candidateContext = pageContextLogic.analyzePageContext(page, {
              quickPass: quickPass || shortRedirect,
              titleOverlap,
              referenceCount: titleSources.length
            });
            const latestSuggestion = suggestions.filter((suggestion) => suggestion && suggestion.pageId === pageId && suggestion.taskId === item.taskId).sort((left, right) => number(right.updatedAt ?? right.decidedAt ?? right.createdAt, 0) - number(left.updatedAt ?? left.decidedAt ?? left.createdAt, 0))[0];
            const riskFlags = [...candidateContext.riskFlags];
            if (possibleMisclick) riskFlags.push("possible_misclick");
            if (initialSnapshot) riskFlags.push("initial_snapshot");
            if (quickPass) riskFlags.push("quick_pass");
            if (latestSuggestion && (latestSuggestion.status === "rejected" || latestSuggestion.status === "undone")) {
              riskFlags.push("manual_rejected");
              if (number(latestSuggestion.cooldownUntil, 0) > now) riskFlags.push("cooldown");
            }
            return {
              taskId: item.taskId,
              openerHits: item.openerHits,
              stableObjectMatch: item.stableObjectMatch,
              qualifiedReturnCount: item.qualifiedReturnCount,
              taskSnapshotRestoreHits: 0,
              nativeGroupRestoreHits: initialSnapshot && item.sameNativeGroup && anchors.some((relation) => (relation.isPrimary === true || relation.role === "core") && instances.some((instance) => instance.pageId === relation.pageId && targetInstances.some((target) => target.groupId >= 0 && target.windowId === instance.windowId && target.groupId === instance.groupId))) ? 1 : 0,
              titleOverlap,
              targetPathType: targetContext.pathType,
              sameNativeGroup: item.sameNativeGroup,
              sameWindow: item.sameWindow,
              sameDomain: item.sameDomain,
              riskFlags
            };
          });
          return { pageId, candidates, signalVersion: SIGNAL_VERSION, limitations: [...LIMITATIONS] };
        }
        return Object.freeze({
          SIGNAL_VERSION,
          LIMITATIONS,
          DEFAULT_LOOKBACK_MS,
          DEFAULT_DWELL_MS,
          DEFAULT_DEBOUNCE_MS,
          DEFAULT_MAX_EPISODE_MS,
          aggregateSuggestionSignals
        });
      });
    }
  });

  // src/shared/v2-suggestion-rules.js
  var require_v2_suggestion_rules = __commonJS({
    "src/shared/v2-suggestion-rules.js"(exports, module) {
      (function attachV2SuggestionRules(rootScope, factory) {
        const api = factory();
        if (typeof module === "object" && module.exports) module.exports = api;
        rootScope.TfaV2SuggestionRules = api;
      })(typeof globalThis === "object" ? globalThis : exports, function createV2SuggestionRules() {
        "use strict";
        const RULE_VERSION = "local-candidate-v2";
        const WEIGHTS = Object.freeze({
          openerHit: 30,
          stableObjectMatch: 38,
          qualifiedReturn: 11,
          taskSnapshotRestore: 28,
          nativeGroupRestore: 8,
          titleOverlap: 10,
          sameNativeGroup: 8,
          sameWindow: 2,
          sameDomain: 3
        });
        const CAPS = Object.freeze({ openerHits: 3, qualifiedReturnCount: 4, taskSnapshotRestoreHits: 3, nativeGroupRestoreHits: 3 });
        const THRESHOLDS = Object.freeze({ medium: 28, high: 60, hardRiskScoreCap: 12, ambiguityDelta: 5, maxCandidates: 8 });
        const CANONICAL_RISK_FLAGS = Object.freeze([
          "possible_misclick",
          "quick_pass",
          "redirect_or_ad",
          "temporary_query",
          "object_conflict",
          "initial_snapshot",
          "manual_rejected",
          "manual_conflict",
          "cooldown",
          "shared_tool",
          "intent_drift",
          "sensitive_site",
          "single_signal",
          "low_margin",
          "ambiguous",
          "unknown_risk",
          "noise_risk"
        ]);
        const RISK_SET = new Set(CANONICAL_RISK_FLAGS);
        const HARD_RISKS = /* @__PURE__ */ new Set([
          "possible_misclick",
          "redirect_or_ad",
          "temporary_query",
          "object_conflict",
          "manual_rejected",
          "manual_conflict",
          "cooldown",
          "intent_drift",
          "sensitive_site",
          "unknown_risk",
          "noise_risk"
        ]);
        const MEDIUM_CAP_RISKS = /* @__PURE__ */ new Set(["initial_snapshot", "single_signal", "low_margin", "ambiguous"]);
        const EVIDENCE_PRIORITY = Object.freeze([
          "risk_suppressed",
          "ambiguous",
          "stable_object",
          "opener_link",
          "qualified_return",
          "task_snapshot_restore",
          "native_group_restore",
          "title_overlap",
          "same_native_group",
          "same_domain",
          "same_window"
        ]);
        const SAFE_ID = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,159}$/;
        const count = (value, maximum) => Math.min(maximum, Math.max(0, Number.isFinite(Number(value)) ? Math.trunc(Number(value)) : 0));
        const overlap = (value) => Math.min(1, Math.max(0, Number.isFinite(Number(value)) ? Number(value) : 0));
        function id(value, field) {
          const normalized = String(value || "").trim();
          if (!SAFE_ID.test(normalized)) throw new TypeError(`${field} is required`);
          return normalized;
        }
        function risks(value) {
          const seen = /* @__PURE__ */ new Set();
          return (Array.isArray(value) ? value : []).reduce((result, item) => {
            const flag = String(item || "");
            if (!RISK_SET.has(flag)) throw new TypeError(`Unknown risk flag: ${flag || "(empty)"}`);
            if (!seen.has(flag)) {
              seen.add(flag);
              result.push(flag);
            }
            return result;
          }, []);
        }
        function addUnique(items, value) {
          if (!items.includes(value)) items.push(value);
        }
        function confidenceLevel(score) {
          if (score >= THRESHOLDS.high) return "high";
          if (score >= THRESHOLDS.medium) return "medium";
          return "low";
        }
        function capAtMedium(candidate, riskFlag) {
          if (riskFlag) addUnique(candidate.riskFlags, riskFlag);
          if (candidate.score >= THRESHOLDS.high) {
            candidate.score = THRESHOLDS.high - 1;
            addUnique(candidate.reasonCodes, "risk_suppressed");
          }
        }
        function finalize(candidate) {
          candidate.reasonCodes = EVIDENCE_PRIORITY.filter((code) => candidate.reasonCodes.includes(code)).slice(0, 8);
          candidate.confidenceLevel = confidenceLevel(candidate.score);
          candidate.confidence = candidate.score / 100;
          candidate.eligible = !candidate._suppressed && candidate.confidenceLevel !== "low";
          return candidate;
        }
        function scoreCandidate(candidate = {}, index = 0) {
          const openerHits = count(candidate.openerHits, CAPS.openerHits);
          const qualifiedReturnCount = count(candidate.qualifiedReturnCount, CAPS.qualifiedReturnCount);
          const taskSnapshotRestoreHits = count(candidate.taskSnapshotRestoreHits, CAPS.taskSnapshotRestoreHits);
          const nativeGroupRestoreHits = count(candidate.nativeGroupRestoreHits, CAPS.nativeGroupRestoreHits);
          const titleOverlap = overlap(candidate.titleOverlap);
          const stableObjectMatch = candidate.stableObjectMatch === true;
          const reasonCodes = [];
          let score = 0;
          if (openerHits) {
            score += openerHits * WEIGHTS.openerHit;
            reasonCodes.push("opener_link");
          }
          if (stableObjectMatch) {
            score += WEIGHTS.stableObjectMatch;
            reasonCodes.push("stable_object");
          }
          if (qualifiedReturnCount) {
            score += qualifiedReturnCount * WEIGHTS.qualifiedReturn;
            reasonCodes.push("qualified_return");
          }
          if (taskSnapshotRestoreHits) {
            score += taskSnapshotRestoreHits * WEIGHTS.taskSnapshotRestore;
            reasonCodes.push("task_snapshot_restore");
          }
          if (nativeGroupRestoreHits) {
            score += nativeGroupRestoreHits * WEIGHTS.nativeGroupRestore;
            reasonCodes.push("native_group_restore");
          }
          if (titleOverlap) {
            score += titleOverlap * WEIGHTS.titleOverlap;
            reasonCodes.push("title_overlap");
          }
          if (candidate.sameNativeGroup === true) {
            score += WEIGHTS.sameNativeGroup;
            reasonCodes.push("same_native_group");
          }
          if (candidate.sameWindow === true) {
            score += WEIGHTS.sameWindow;
            reasonCodes.push("same_window");
          }
          if (candidate.sameDomain === true) {
            score += WEIGHTS.sameDomain;
            reasonCodes.push("same_domain");
          }
          const riskFlags = risks(candidate.riskFlags);
          const strongFamilyCount = [openerHits > 0, stableObjectMatch, qualifiedReturnCount > 0, taskSnapshotRestoreHits > 0].filter(Boolean).length;
          const quickPassCorroborated = stableObjectMatch || qualifiedReturnCount > 0 || taskSnapshotRestoreHits > 0;
          const hardSuppressed = riskFlags.some((flag) => HARD_RISKS.has(flag));
          const conditionalSuppressed = riskFlags.includes("quick_pass") && !quickPassCorroborated || riskFlags.includes("shared_tool") && !stableObjectMatch;
          const result = { taskId: id(candidate.taskId, "taskId"), score: Math.min(100, Math.max(0, Math.round(score))), confidence: 0, confidenceLevel: "low", reasonCodes, riskFlags, ruleVersion: RULE_VERSION, eligible: false, _index: index, _suppressed: hardSuppressed || conditionalSuppressed };
          if (result._suppressed) {
            result.score = Math.min(result.score, THRESHOLDS.hardRiskScoreCap);
            addUnique(result.reasonCodes, "risk_suppressed");
          } else {
            if (result.score >= THRESHOLDS.high && strongFamilyCount < 2) capAtMedium(result, "single_signal");
            if (result.riskFlags.some((flag) => MEDIUM_CAP_RISKS.has(flag))) capAtMedium(result);
            if (result.riskFlags.includes("ambiguous")) addUnique(result.reasonCodes, "ambiguous");
          }
          return finalize(result);
        }
        function scoreSuggestionCandidates(input = {}) {
          const pageId = id(input.pageId, "pageId");
          const sourceCandidates = Array.isArray(input.candidates) ? input.candidates : [];
          if (sourceCandidates.length > THRESHOLDS.maxCandidates) throw new TypeError(`At most ${THRESHOLDS.maxCandidates} candidates are allowed`);
          const taskIds = sourceCandidates.map((candidate) => id(candidate?.taskId, "taskId"));
          if (new Set(taskIds).size !== taskIds.length) throw new TypeError("Candidate taskId values must be unique");
          const candidates = sourceCandidates.map(scoreCandidate).sort((left, right) => right.score - left.score || left.taskId.localeCompare(right.taskId) || left._index - right._index);
          const leader = candidates[0];
          const ambiguous = leader ? candidates.filter((candidate) => leader.score - candidate.score <= THRESHOLDS.ambiguityDelta) : [];
          if (ambiguous.length >= 2) ambiguous.forEach((candidate) => {
            addUnique(candidate.riskFlags, "low_margin");
            addUnique(candidate.riskFlags, "ambiguous");
            addUnique(candidate.reasonCodes, "ambiguous");
            capAtMedium(candidate);
            finalize(candidate);
          });
          candidates.sort((left, right) => right.score - left.score || left.taskId.localeCompare(right.taskId) || left._index - right._index);
          return { pageId, candidates: candidates.map(({ _index, _suppressed, ...candidate }) => candidate) };
        }
        return Object.freeze({ RULE_VERSION, WEIGHTS, THRESHOLDS, CANONICAL_RISK_FLAGS, scoreSuggestionCandidates });
      });
    }
  });

  // src/shared/v2-ai-settings.js
  var require_v2_ai_settings = __commonJS({
    "src/shared/v2-ai-settings.js"(exports, module) {
      (function attachV2AiSettings(root, factory) {
        const api = factory();
        if (typeof module === "object" && module.exports) module.exports = api;
        if (root) root.TfaV2AiSettings = api;
      })(typeof globalThis === "object" ? globalThis : exports, function createV2AiSettingsModule() {
        "use strict";
        const AI_SETTINGS_STORAGE_KEY = "tfa_v2_ai_settings";
        const AI_API_KEY_STORAGE_KEY = "tfa_v2_ai_api_key";
        const DEFAULT_BASE_URL = "https://fast.sbbbbbbbbb.xyz/v1";
        const DEFAULT_MODEL = "grok-4.5";
        const DEFAULT_TIMEOUT_MS = 9e4;
        const PROVIDER = "openai-compatible";
        const DEFAULT_AI_SETTINGS = Object.freeze({
          enabled: false,
          autoOrganize: true,
          provider: PROVIDER,
          baseUrl: DEFAULT_BASE_URL,
          model: DEFAULT_MODEL,
          timeoutMs: DEFAULT_TIMEOUT_MS,
          excludeSensitiveSites: true
        });
        const SENSITIVE_FIELD = /^(?:api[-_]?key|authorization|access[-_]?token|secret)$/i;
        function cleanText(value, fallback, maxLength, label, strict) {
          const supplied = value !== void 0 && value !== null;
          const text = typeof value === "string" ? value.trim() : "";
          if (!text || text.length > maxLength || /[\u0000-\u001f\u007f]/.test(text)) {
            if (strict && supplied) throw new TypeError(`Invalid AI ${label}`);
            return fallback;
          }
          return text;
        }
        function normalizeBaseUrl(value, { strict = false } = {}) {
          const raw = cleanText(value, DEFAULT_BASE_URL, 2048, "base URL", strict);
          try {
            const parsed = new URL(raw);
            if (parsed.protocol !== "https:" || parsed.username || parsed.password || parsed.search || parsed.hash) {
              throw new TypeError("Invalid AI base URL");
            }
            const pathname = parsed.pathname.replace(/\/+$/, "") || "";
            return `${parsed.origin}${pathname}`;
          } catch (_error) {
            if (strict) throw new TypeError("AI base URL must use HTTPS");
            return DEFAULT_BASE_URL;
          }
        }
        function normalizeAiSettings(value = {}, { strict = false } = {}) {
          const input = value && typeof value === "object" && !Array.isArray(value) ? value : {};
          return Object.freeze({
            enabled: typeof input.enabled === "boolean" ? input.enabled : DEFAULT_AI_SETTINGS.enabled,
            autoOrganize: typeof input.autoOrganize === "boolean" ? input.autoOrganize : DEFAULT_AI_SETTINGS.autoOrganize,
            provider: PROVIDER,
            baseUrl: normalizeBaseUrl(input.baseUrl, { strict }),
            model: cleanText(input.model, DEFAULT_MODEL, 200, "model", strict),
            timeoutMs: DEFAULT_TIMEOUT_MS,
            excludeSensitiveSites: typeof input.excludeSensitiveSites === "boolean" ? input.excludeSensitiveSites : DEFAULT_AI_SETTINGS.excludeSensitiveSites
          });
        }
        function normalizeApiKey(value, { required = false } = {}) {
          const key = typeof value === "string" ? value.trim() : "";
          if (!key && required || key.length > 4096 || /[\u0000-\u001f\u007f]/.test(key)) {
            throw new TypeError("Invalid AI API key");
          }
          return key;
        }
        function maskApiKey(value) {
          const key = normalizeApiKey(value);
          if (!key) return "";
          return key.length <= 4 ? "\u2022\u2022\u2022\u2022" : `\u2022\u2022\u2022\u2022${key.slice(-4)}`;
        }
        function publicAiSettings(settings, apiKey = "") {
          const normalized = normalizeAiSettings(settings);
          const key = normalizeApiKey(apiKey);
          return Object.freeze({
            ...normalized,
            hasApiKey: Boolean(key),
            maskedApiKey: maskApiKey(key)
          });
        }
        function redactAiData(value, { secrets = [] } = {}) {
          const secretValues = (Array.isArray(secrets) ? secrets : [secrets]).map((secret) => String(secret || "")).filter(Boolean);
          const seen = /* @__PURE__ */ new WeakSet();
          function visit(current) {
            if (typeof current === "string") {
              let result = current.replace(/Bearer\s+[^\s,;]+/gi, "Bearer [REDACTED]");
              secretValues.forEach((secret) => {
                result = result.split(secret).join("[REDACTED]");
              });
              return result;
            }
            if (!current || typeof current !== "object") return current;
            if (seen.has(current)) return "[Circular]";
            seen.add(current);
            if (Array.isArray(current)) return current.map(visit);
            return Object.fromEntries(Object.entries(current).filter(([key]) => !SENSITIVE_FIELD.test(key)).map(([key, item]) => [key, visit(item)]));
          }
          return visit(value);
        }
        return Object.freeze({
          AI_SETTINGS_STORAGE_KEY,
          AI_API_KEY_STORAGE_KEY,
          DEFAULT_BASE_URL,
          DEFAULT_MODEL,
          DEFAULT_TIMEOUT_MS,
          PROVIDER,
          DEFAULT_AI_SETTINGS,
          normalizeBaseUrl,
          normalizeAiSettings,
          normalizeApiKey,
          maskApiKey,
          publicAiSettings,
          redactAiData
        });
      });
    }
  });

  // src/shared/v2-ai-contract.js
  var require_v2_ai_contract = __commonJS({
    "src/shared/v2-ai-contract.js"(exports, module) {
      (function attachV2AiContract(rootScope, factory) {
        const api = factory();
        if (typeof module === "object" && module.exports) module.exports = api;
        rootScope.TfaV2AiContract = api;
      })(typeof globalThis === "object" ? globalThis : exports, function createV2AiContract() {
        "use strict";
        const VERSION = 1;
        const MAX_PAGES = 20;
        const MAX_TASKS = 50;
        const MAX_ACTIONS = 50;
        const SAFE_PAGE_ID = /^page-[A-Za-z0-9._:-]{1,154}$/;
        const SAFE_TASK_ID = /^task-[A-Za-z0-9._:-]{1,154}$/;
        const LOCALES = Object.freeze(["zh-CN", "en"]);
        const TASK_DOMAINS = Object.freeze(["work", "study", "life", "interest", "admin", "other"]);
        const PATH_TYPES = Object.freeze(["root", "workspace", "project", "document", "search", "media", "settings", "other"]);
        const EVIDENCE_CODES = Object.freeze([
          "opener_link",
          "stable_object",
          "qualified_return",
          "task_snapshot_restore",
          "native_group_restore",
          "title_overlap",
          "same_native_group",
          "same_window",
          "same_domain",
          "risk_suppressed",
          "ambiguous"
        ]);
        const RISK_CODES = Object.freeze([
          "possible_misclick",
          "quick_pass",
          "redirect_or_ad",
          "temporary_query",
          "object_conflict",
          "initial_snapshot",
          "manual_rejected",
          "manual_conflict",
          "cooldown",
          "shared_tool",
          "intent_drift",
          "sensitive_site",
          "single_signal",
          "low_margin",
          "ambiguous",
          "unknown_risk",
          "noise_risk"
        ]);
        const REASON_CODES = Object.freeze([
          "semantic_match",
          "task_name_match",
          "domain_match",
          "core_page_similarity",
          "behavior_support",
          "stable_object_match",
          "related_pages",
          "clearer_task_name",
          "virtual_order",
          "ambiguous",
          "no_suitable_task",
          "insufficient_evidence"
        ]);
        const PLAN_ACTIONS = Object.freeze([
          "create_ai_task",
          "rename_ai_task",
          "assign_ai_relation",
          "reorder_ai_tasks",
          "no_change"
        ]);
        function fail(message) {
          const error = new TypeError(message);
          error.code = "INVALID_AI_CONTRACT";
          throw error;
        }
        function record(value, field) {
          if (!value || typeof value !== "object" || Array.isArray(value)) fail(`${field} must be an object`);
          return value;
        }
        function exact(value, field, allowed, required = allowed) {
          const source = record(value, field);
          const keys = Object.keys(source);
          const unknown = keys.find((key) => !allowed.includes(key));
          if (unknown) fail(`${field} contains an unknown field`);
          const missing = required.find((key) => !Object.prototype.hasOwnProperty.call(source, key));
          if (missing) fail(`${field} is missing a required field`);
          return source;
        }
        function text(value, field, maximum, { empty = false } = {}) {
          if (typeof value !== "string") fail(`${field} must be a string`);
          const normalized = value.trim();
          if (!empty && !normalized || normalized.length > maximum) fail(`${field} is invalid`);
          if (/\b(?:https?|file|chrome|edge):\/\//i.test(normalized)) fail(`${field} must not contain a URL`);
          return normalized;
        }
        function id(value, field, pattern) {
          const normalized = String(value || "").trim();
          if (!pattern.test(normalized)) fail(`${field} is invalid`);
          return normalized;
        }
        function enumeration(value, field, values) {
          if (!values.includes(value)) fail(`${field} is invalid`);
          return value;
        }
        function boundedArray(value, field, minimum, maximum) {
          if (!Array.isArray(value) || value.length < minimum || value.length > maximum) {
            fail(`${field} must contain ${minimum} to ${maximum} items`);
          }
          return value;
        }
        function unique(items, field) {
          if (new Set(items).size !== items.length) fail(`${field} must be unique`);
          return items;
        }
        function confidence(value, field) {
          if (typeof value !== "number" || !Number.isFinite(value) || value < 0 || value > 1) {
            fail(`${field} must be between 0 and 1`);
          }
          return value;
        }
        function reasonCodes(value, field) {
          const items = boundedArray(value, field, 1, 3).map((code) => enumeration(code, field, REASON_CODES));
          return unique(items, field);
        }
        function countedCodes(value, field, codes) {
          const items = boundedArray(value || [], field, 0, 8).map((item, index) => {
            const source = exact(item, `${field}[${index}]`, ["code", "count"]);
            if (!Number.isInteger(source.count) || source.count < 1 || source.count > 99) fail(`${field}[${index}].count is invalid`);
            return { code: enumeration(source.code, `${field}[${index}].code`, codes), count: source.count };
          });
          unique(items.map((item) => item.code), field);
          return items;
        }
        function registeredDomain(value, field) {
          const normalized = text(value, field, 253);
          if (!/^[A-Za-z0-9](?:[A-Za-z0-9.-]{0,251}[A-Za-z0-9])?$/.test(normalized) || normalized.includes("..")) fail(`${field} is invalid`);
          return normalized.toLowerCase();
        }
        function corePageTitles(value, field) {
          return boundedArray(value || [], field, 0, 5).map((item, index) => text(item, `${field}[${index}]`, 160));
        }
        function normalizePageBase(value, field) {
          const source = exact(value, field, [
            "pageId",
            "title",
            "registeredDomain",
            "pathType",
            "evidence",
            "risks"
          ]);
          return {
            pageId: id(source.pageId, `${field}.pageId`, SAFE_PAGE_ID),
            title: text(source.title, `${field}.title`, 200),
            registeredDomain: registeredDomain(source.registeredDomain, `${field}.registeredDomain`),
            pathType: enumeration(source.pathType, `${field}.pathType`, PATH_TYPES),
            evidence: countedCodes(source.evidence, `${field}.evidence`, EVIDENCE_CODES),
            risks: countedCodes(source.risks, `${field}.risks`, RISK_CODES)
          };
        }
        function normalizeCandidate(value, field) {
          const source = exact(value, field, ["taskId", "name", "domain", "corePageTitles"]);
          return {
            taskId: id(source.taskId, `${field}.taskId`, SAFE_TASK_ID),
            name: text(source.name, `${field}.name`, 120),
            domain: enumeration(source.domain, `${field}.domain`, TASK_DOMAINS),
            corePageTitles: corePageTitles(source.corePageTitles, `${field}.corePageTitles`)
          };
        }
        function normalizeRankInput(value) {
          const source = exact(value, "rankInput", ["version", "locale", "pages"]);
          if (source.version !== VERSION) fail("rankInput.version is unsupported");
          const pages = boundedArray(source.pages, "rankInput.pages", 1, MAX_PAGES).map((page, pageIndex) => {
            const item = exact(page, `rankInput.pages[${pageIndex}]`, [
              "pageId",
              "title",
              "registeredDomain",
              "pathType",
              "evidence",
              "risks",
              "candidates"
            ]);
            const base = normalizePageBase({
              pageId: item.pageId,
              title: item.title,
              registeredDomain: item.registeredDomain,
              pathType: item.pathType,
              evidence: item.evidence,
              risks: item.risks
            }, `rankInput.pages[${pageIndex}]`);
            const candidates = boundedArray(item.candidates, `rankInput.pages[${pageIndex}].candidates`, 3, 8).map((candidate, candidateIndex) => normalizeCandidate(candidate, `rankInput.pages[${pageIndex}].candidates[${candidateIndex}]`));
            unique(candidates.map((candidate) => candidate.taskId), `rankInput.pages[${pageIndex}].candidates.taskId`);
            return { ...base, candidates };
          });
          unique(pages.map((page) => page.pageId), "rankInput.pages.pageId");
          return { version: VERSION, locale: enumeration(source.locale, "rankInput.locale", LOCALES), pages };
        }
        function normalizeTask(value, field) {
          const source = exact(value, field, [
            "taskId",
            "name",
            "domain",
            "source",
            "canRename",
            "canReceiveRelations",
            "corePageTitles"
          ]);
          if (!["manual", "ai"].includes(source.source)) fail(`${field}.source is invalid`);
          if (typeof source.canRename !== "boolean" || typeof source.canReceiveRelations !== "boolean") {
            fail(`${field} capabilities are invalid`);
          }
          return {
            taskId: id(source.taskId, `${field}.taskId`, SAFE_TASK_ID),
            name: text(source.name, `${field}.name`, 120),
            domain: enumeration(source.domain, `${field}.domain`, TASK_DOMAINS),
            source: source.source,
            canRename: source.canRename,
            canReceiveRelations: source.canReceiveRelations,
            corePageTitles: corePageTitles(source.corePageTitles, `${field}.corePageTitles`)
          };
        }
        function normalizeWorkspaceInput(value) {
          const source = exact(value, "workspaceInput", ["version", "locale", "pages", "tasks"]);
          if (source.version !== VERSION) fail("workspaceInput.version is unsupported");
          const pages = boundedArray(source.pages, "workspaceInput.pages", 1, MAX_PAGES).map((page, index) => normalizePageBase(page, `workspaceInput.pages[${index}]`));
          const tasks = boundedArray(source.tasks, "workspaceInput.tasks", 0, MAX_TASKS).map((task, index) => normalizeTask(task, `workspaceInput.tasks[${index}]`));
          unique(pages.map((page) => page.pageId), "workspaceInput.pages.pageId");
          unique(tasks.map((task) => task.taskId), "workspaceInput.tasks.taskId");
          return {
            version: VERSION,
            locale: enumeration(source.locale, "workspaceInput.locale", LOCALES),
            pages,
            tasks
          };
        }
        function normalizeRankResult(value, input) {
          const normalizedInput = normalizeRankInput(input);
          const source = exact(value, "rankResult", ["version", "items"]);
          if (source.version !== VERSION) fail("rankResult.version is unsupported");
          const pages = new Map(normalizedInput.pages.map((page) => [page.pageId, page]));
          const items = boundedArray(source.items, "rankResult.items", 0, normalizedInput.pages.length).map((item, index) => {
            const candidate = exact(item, `rankResult.items[${index}]`, [
              "pageId",
              "decision",
              "taskId",
              "confidence",
              "reasonCodes"
            ]);
            const pageId = id(candidate.pageId, `rankResult.items[${index}].pageId`, SAFE_PAGE_ID);
            const page = pages.get(pageId);
            if (!page) fail(`rankResult.items[${index}].pageId is outside the input set`);
            if (!["assign", "no_match"].includes(candidate.decision)) fail(`rankResult.items[${index}].decision is invalid`);
            const taskId = candidate.taskId == null ? null : id(candidate.taskId, `rankResult.items[${index}].taskId`, SAFE_TASK_ID);
            if (candidate.decision === "assign" && !page.candidates.some((task) => task.taskId === taskId)) {
              fail(`rankResult.items[${index}].taskId is outside the page candidate set`);
            }
            if (candidate.decision === "no_match" && taskId !== null) fail(`rankResult.items[${index}].taskId must be null`);
            return {
              pageId,
              decision: candidate.decision,
              taskId,
              confidence: confidence(candidate.confidence, `rankResult.items[${index}].confidence`),
              reasonCodes: reasonCodes(candidate.reasonCodes, `rankResult.items[${index}].reasonCodes`)
            };
          });
          unique(items.map((item) => item.pageId), "rankResult.items.pageId");
          return { version: VERSION, items };
        }
        function normalizePlanAction(value, index, context) {
          const field = `workspacePlan.actions[${index}]`;
          const source = record(value, field);
          enumeration(source.type, `${field}.type`, PLAN_ACTIONS);
          let action;
          if (source.type === "create_ai_task") {
            exact(source, field, ["type", "name", "domain", "pageIds", "confidence", "reasonCodes"]);
            const pageIds = unique(boundedArray(source.pageIds, `${field}.pageIds`, 2, MAX_PAGES).map((pageId) => id(pageId, `${field}.pageIds`, SAFE_PAGE_ID)), `${field}.pageIds`);
            if (pageIds.some((pageId) => !context.pages.has(pageId))) fail(`${field}.pageIds contains an unknown page`);
            action = { type: source.type, name: text(source.name, `${field}.name`, 120), domain: enumeration(source.domain, `${field}.domain`, TASK_DOMAINS), pageIds };
          } else if (source.type === "rename_ai_task") {
            exact(source, field, ["type", "taskId", "name", "confidence", "reasonCodes"]);
            const taskId = id(source.taskId, `${field}.taskId`, SAFE_TASK_ID);
            const task = context.tasks.get(taskId);
            if (!task || task.source !== "ai" || task.canRename !== true) fail(`${field}.taskId cannot be renamed`);
            action = { type: source.type, taskId, name: text(source.name, `${field}.name`, 120) };
          } else if (source.type === "assign_ai_relation") {
            exact(source, field, ["type", "pageId", "taskId", "role", "isPrimary", "confidence", "reasonCodes"]);
            const pageId = id(source.pageId, `${field}.pageId`, SAFE_PAGE_ID);
            const taskId = id(source.taskId, `${field}.taskId`, SAFE_TASK_ID);
            if (!context.pages.has(pageId)) fail(`${field}.pageId is unknown`);
            if (!context.tasks.get(taskId)?.canReceiveRelations) fail(`${field}.taskId cannot receive relations`);
            if (!["core", "related", "shared"].includes(source.role) || typeof source.isPrimary !== "boolean") fail(`${field} relation fields are invalid`);
            action = { type: source.type, pageId, taskId, role: source.role, isPrimary: source.isPrimary };
          } else if (source.type === "reorder_ai_tasks") {
            exact(source, field, ["type", "taskIds", "confidence", "reasonCodes"]);
            const taskIds = unique(boundedArray(source.taskIds, `${field}.taskIds`, 2, MAX_TASKS).map((taskId) => id(taskId, `${field}.taskIds`, SAFE_TASK_ID)), `${field}.taskIds`);
            if (taskIds.some((taskId) => !context.tasks.has(taskId))) fail(`${field}.taskIds contains an unknown task`);
            action = { type: source.type, taskIds };
          } else {
            exact(source, field, ["type", "reasonCodes"]);
            action = { type: source.type };
          }
          return {
            ...action,
            ...source.type === "no_change" ? {} : { confidence: confidence(source.confidence, `${field}.confidence`) },
            reasonCodes: reasonCodes(source.reasonCodes, `${field}.reasonCodes`)
          };
        }
        function normalizeWorkspacePlan(value, input) {
          const normalizedInput = normalizeWorkspaceInput(input);
          const source = exact(value, "workspacePlan", ["version", "actions"]);
          if (source.version !== VERSION) fail("workspacePlan.version is unsupported");
          const context = {
            pages: new Set(normalizedInput.pages.map((page) => page.pageId)),
            tasks: new Map(normalizedInput.tasks.map((task) => [task.taskId, task]))
          };
          const actions = boundedArray(source.actions, "workspacePlan.actions", 1, MAX_ACTIONS).map((action, index) => normalizePlanAction(action, index, context));
          if (actions.some((action) => action.type === "no_change") && actions.length !== 1) fail("no_change must be the only action");
          const keys = actions.map((action) => {
            if (action.type === "rename_ai_task") return `rename:${action.taskId}`;
            if (action.type === "assign_ai_relation") return `assign:${action.pageId}:${action.taskId}`;
            if (action.type === "reorder_ai_tasks") return "reorder";
            if (action.type === "create_ai_task") return `create:${[...action.pageIds].sort().join(",")}`;
            return "no_change";
          });
          unique(keys, "workspacePlan.actions");
          return { version: VERSION, actions };
        }
        return Object.freeze({
          VERSION,
          MAX_PAGES,
          MAX_TASKS,
          MAX_ACTIONS,
          LOCALES,
          TASK_DOMAINS,
          PATH_TYPES,
          EVIDENCE_CODES,
          RISK_CODES,
          REASON_CODES,
          PLAN_ACTIONS,
          normalizeRankInput,
          normalizeRankResult,
          normalizeWorkspaceInput,
          normalizeWorkspacePlan
        });
      });
    }
  });

  // src/shared/v2-safe-data-backup.js
  var require_v2_safe_data_backup = __commonJS({
    "src/shared/v2-safe-data-backup.js"(exports, module) {
      (function attachV2SafeDataBackup(root, factory) {
        const api = factory(
          root.TfaV2DataSchema || (typeof __require === "function" ? require_v2_data_schema() : null)
        );
        if (typeof module === "object" && module.exports) module.exports = api;
        root.TfaV2SafeDataBackup = api;
      })(typeof globalThis === "object" ? globalThis : exports, function createV2SafeDataBackupModule(DataSchema) {
        "use strict";
        const FORMAT = "tabflow-v2-safe-backup";
        const LEGACY_FORMAT = "tabflow-v2-safe-export";
        const FORMAT_VERSION = 2;
        const MAX_BYTES = 5 * 1024 * 1024;
        const LIMITS = Object.freeze({
          tasks: 2e3,
          pages: 1e4,
          relations: 3e4,
          categories: 100,
          siteSpaceSites: 2e3,
          dockSites: 500
        });
        const SAFE_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,159}$/;
        const SAFE_EXTERNAL_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,95}$/;
        const SENSITIVE_KEY = /(?:api.?key|secret|password|passwd|authorization|bearer|cookie|activation.?key|license.?key|screenshot|page.?text|document.?text|behavior|history|change)/i;
        const SECRET_VALUE_PATTERNS = Object.freeze([
          /(?:^|\s)sk-[A-Za-z0-9_-]{8,}/i,
          /\bbearer\s+[A-Za-z0-9._~-]{8,}/i,
          /\bgh[pousr]_[A-Za-z0-9]{20,}\b/i,
          /\bgithub_pat_[A-Za-z0-9_]{20,}\b/i,
          /\bglpat-[A-Za-z0-9_-]{20,}\b/i,
          /\bxox[baprs]-[A-Za-z0-9-]{10,}\b/i,
          /\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/,
          /\bAIza[0-9A-Za-z_-]{30,}\b/,
          /\b(?:sk|rk)_(?:live|test)_[0-9A-Za-z]{16,}\b/i,
          /\bnpm_[A-Za-z0-9]{20,}\b/i,
          /\bAK-[A-Za-z0-9][A-Za-z0-9-]{7,}\b/,
          /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/,
          /-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----/i,
          /\b(?:api.?key|activation.?key|license.?key|secret|password|passwd)\s*[:=]\s*["']?[A-Za-z0-9._~+/-]{8,}/i
        ]);
        const SAFE_PROVENANCE = /^[a-z0-9][a-z0-9._:-]{0,159}$/;
        const REDACTED_COLLISION_PATH = ".tabflow-query-collision";
        const TASK_FIELDS = Object.freeze([
          "id",
          "title",
          "domain",
          "status",
          "sortOrder",
          "source",
          "manualLocked",
          "confidence",
          "evidence",
          "modelVersion",
          "createdAt",
          "updatedAt"
        ]);
        const PAGE_FIELDS = Object.freeze([
          "id",
          "normalizedUrl",
          "restoreUrl",
          "title",
          "domain",
          "faviconUrl",
          "createdAt",
          "updatedAt"
        ]);
        const RELATION_FIELDS = Object.freeze([
          "id",
          "taskId",
          "pageId",
          "isPrimary",
          "role",
          "source",
          "manualLocked",
          "confidence",
          "evidence",
          "sortOrder",
          "createdAt",
          "updatedAt"
        ]);
        function failure(code, message, details) {
          const error = new Error(message);
          error.code = code;
          if (details) error.details = details;
          return error;
        }
        function plain(value) {
          return Boolean(value && typeof value === "object" && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype);
        }
        function byteLength(value) {
          const text2 = typeof value === "string" ? value : JSON.stringify(value);
          if (typeof TextEncoder === "function") return new TextEncoder().encode(text2).byteLength;
          return unescape(encodeURIComponent(text2)).length;
        }
        function exactFields(value, fields, label, optional = []) {
          if (!plain(value)) throw failure("BACKUP_INVALID_RECORD", `${label} must be a plain object`);
          const allowed = /* @__PURE__ */ new Set([...fields, ...optional]);
          const unexpected = Object.keys(value).filter((key) => !allowed.has(key));
          if (unexpected.length) throw failure("BACKUP_UNKNOWN_FIELD", `${label} contains unsupported fields`, unexpected);
        }
        function scanSensitive(value, path = "$", seen = /* @__PURE__ */ new Set()) {
          if (typeof value === "string") {
            if (SECRET_VALUE_PATTERNS.some((pattern) => pattern.test(value))) {
              throw failure("BACKUP_SENSITIVE_DATA", `Sensitive value found at ${path}`);
            }
            return;
          }
          if (!value || typeof value !== "object") return;
          if (seen.has(value)) throw failure("BACKUP_INVALID_JSON", "Backup contains a circular reference");
          seen.add(value);
          Object.entries(value).forEach(([key, entry]) => {
            if (SENSITIVE_KEY.test(key)) throw failure("BACKUP_SENSITIVE_DATA", `Sensitive field found at ${path}.${key}`);
            scanSensitive(entry, `${path}.${key}`, seen);
          });
          seen.delete(value);
        }
        function safeUrl(value, { allowEmpty = false } = {}) {
          try {
            const url = new URL(String(value || "").trim());
            if (!["http:", "https:"].includes(url.protocol)) throw new Error("protocol");
            url.username = "";
            url.password = "";
            url.search = "";
            url.hash = "";
            return url.href;
          } catch (_error) {
            if (allowEmpty && !String(value || "").trim()) return "";
            throw failure("BACKUP_INVALID_URL", "Backup URLs must be http(s) URLs");
          }
        }
        function text(value, field, max, { required = false } = {}) {
          const result = String(value == null ? "" : value).trim();
          if (required && !result || result.length > max) {
            throw failure("BACKUP_INVALID_FIELD", `Invalid ${field}`);
          }
          return result;
        }
        function number(value, field, { integer = false, min = 0, max = Number.MAX_SAFE_INTEGER } = {}) {
          const result = Number(value);
          if (!Number.isFinite(result) || integer && !Number.isSafeInteger(result) || result < min || result > max) {
            throw failure("BACKUP_INVALID_FIELD", `Invalid ${field}`);
          }
          return result;
        }
        function unique(records, key, label) {
          const seen = /* @__PURE__ */ new Set();
          records.forEach((record) => {
            const id = record[key];
            if (seen.has(id)) throw failure("BACKUP_DUPLICATE_ID", `Duplicate ${label}: ${id}`);
            seen.add(id);
          });
          return seen;
        }
        function requireLimit(records, limit, label) {
          if (!Array.isArray(records)) throw failure("BACKUP_INVALID_SECTION", `${label} must be an array`);
          if (records.length > limit) throw failure("BACKUP_TOO_LARGE", `${label} exceeds ${limit} records`);
        }
        function safeTask(raw, { legacy = false } = {}) {
          exactFields(raw, legacy ? TASK_FIELDS.filter((field) => !["confidence", "evidence", "modelVersion"].includes(field)) : TASK_FIELDS, "task");
          const input = legacy ? {
            ...raw,
            domain: raw.domain || "other",
            status: raw.status || "active",
            sortOrder: Number.isFinite(Number(raw.sortOrder)) ? Number(raw.sortOrder) : 0,
            source: "manual",
            manualLocked: true,
            confidence: 1,
            evidence: "manual",
            modelVersion: "",
            createdAt: Number.isFinite(Number(raw.createdAt)) ? Number(raw.createdAt) : 0,
            updatedAt: Number.isFinite(Number(raw.updatedAt)) ? Number(raw.updatedAt) : Number.isFinite(Number(raw.createdAt)) ? Number(raw.createdAt) : 0
          } : raw;
          let normalized;
          try {
            normalized = DataSchema.normalizeTask(input, { now: Number(input.updatedAt) || 0 });
          } catch (error) {
            throw failure("BACKUP_INVALID_TASK", error.message);
          }
          if (!DataSchema.validateTask(normalized).valid || TASK_FIELDS.some((field) => normalized[field] !== input[field])) {
            throw failure("BACKUP_INVALID_TASK", `Task ${String(input.id || "")} is not normalized`);
          }
          if (!SAFE_PROVENANCE.test(normalized.evidence) || normalized.modelVersion && !SAFE_PROVENANCE.test(normalized.modelVersion)) {
            throw failure("BACKUP_INVALID_TASK", "Task provenance must use canonical codes");
          }
          return { ...normalized };
        }
        function safePage(raw, { legacy = false } = {}) {
          const legacyFields = PAGE_FIELDS.filter((field) => field !== "faviconUrl");
          exactFields(raw, legacy ? legacyFields : PAGE_FIELDS, "page");
          const normalizedUrl = safeUrl(raw.normalizedUrl);
          const restoreUrl = safeUrl(raw.restoreUrl);
          const faviconUrl = legacy ? "" : safeUrl(raw.faviconUrl, { allowEmpty: true });
          if (!legacy && (raw.normalizedUrl !== normalizedUrl || raw.restoreUrl !== restoreUrl || raw.faviconUrl !== faviconUrl)) {
            throw failure("BACKUP_INVALID_URL", "Page URLs must already be safely normalized");
          }
          const hostname = new URL(normalizedUrl).hostname;
          const input = legacy ? {
            ...raw,
            normalizedUrl,
            restoreUrl,
            title: raw.title || hostname,
            domain: raw.domain || hostname,
            faviconUrl,
            createdAt: Number.isFinite(Number(raw.createdAt)) ? Number(raw.createdAt) : 0,
            updatedAt: Number.isFinite(Number(raw.updatedAt)) ? Number(raw.updatedAt) : Number.isFinite(Number(raw.createdAt)) ? Number(raw.createdAt) : 0
          } : { ...raw, normalizedUrl, restoreUrl, faviconUrl };
          let normalized;
          try {
            normalized = DataSchema.normalizePageEntity(input, { now: Number(input.updatedAt) || 0 });
          } catch (error) {
            throw failure("BACKUP_INVALID_PAGE", error.message);
          }
          if (!DataSchema.validatePageEntity(normalized).valid || PAGE_FIELDS.some((field) => normalized[field] !== input[field])) {
            throw failure("BACKUP_INVALID_PAGE", `Page ${String(input.id || "")} is not normalized`);
          }
          return { ...normalized };
        }
        function safeRelation(raw, { legacy = false } = {}) {
          exactFields(raw, legacy ? RELATION_FIELDS.filter((field) => !["confidence", "evidence"].includes(field)) : RELATION_FIELDS, "relation");
          const input = legacy ? {
            ...raw,
            isPrimary: raw.isPrimary === true,
            role: raw.role || "related",
            source: "manual",
            manualLocked: true,
            confidence: 1,
            evidence: "manual",
            sortOrder: Number.isFinite(Number(raw.sortOrder)) ? Number(raw.sortOrder) : 0,
            createdAt: Number.isFinite(Number(raw.createdAt)) ? Number(raw.createdAt) : 0,
            updatedAt: Number.isFinite(Number(raw.updatedAt)) ? Number(raw.updatedAt) : Number.isFinite(Number(raw.createdAt)) ? Number(raw.createdAt) : 0
          } : raw;
          let normalized;
          try {
            normalized = DataSchema.normalizeRelation(input, { now: Number(input.updatedAt) || 0 });
          } catch (error) {
            throw failure("BACKUP_INVALID_RELATION", error.message);
          }
          if (!DataSchema.validateRelation(normalized).valid || RELATION_FIELDS.some((field) => normalized[field] !== input[field])) {
            throw failure("BACKUP_INVALID_RELATION", `Relation ${String(input.id || "")} is not normalized`);
          }
          if (!SAFE_PROVENANCE.test(normalized.evidence)) {
            throw failure("BACKUP_INVALID_RELATION", "Relation provenance must use canonical codes");
          }
          return { ...normalized };
        }
        function safeTaskGraph(raw, options = {}) {
          if (!plain(raw)) throw failure("BACKUP_INVALID_SECTION", "taskGraph must be an object");
          exactFields(raw, ["tasks", "pages", "relations"], "taskGraph");
          requireLimit(raw.tasks, LIMITS.tasks, "tasks");
          requireLimit(raw.pages, LIMITS.pages, "pages");
          requireLimit(raw.relations, LIMITS.relations, "relations");
          const tasks = raw.tasks.map((item) => safeTask(item, options));
          const pages = raw.pages.map((item) => safePage(item, options));
          const relations = raw.relations.map((item) => safeRelation(item, options));
          const taskIds = unique(tasks, "id", "task id");
          const pageIds = unique(pages, "id", "page id");
          unique(pages, "normalizedUrl", "page URL");
          unique(relations, "id", "relation id");
          relations.forEach((relation) => {
            if (!taskIds.has(relation.taskId) || !pageIds.has(relation.pageId)) {
              throw failure("BACKUP_BROKEN_REFERENCE", `Relation ${relation.id} references a missing record`);
            }
          });
          const primary = DataSchema.validatePrimaryRelations(relations);
          if (!primary.valid) throw failure("BACKUP_PRIMARY_CONFLICT", primary.errors.join("; "));
          return { tasks, pages, relations };
        }
        function safeCategory(raw) {
          exactFields(raw, ["id", "title", "order", "createdAt", "updatedAt"], "siteSpace category");
          const id = text(raw.id, "category id", 96, { required: true });
          if (!SAFE_EXTERNAL_ID.test(id)) throw failure("BACKUP_INVALID_CATEGORY", "Invalid category id");
          const createdAt = number(raw.createdAt, "category createdAt");
          const updatedAt = number(raw.updatedAt, "category updatedAt", { min: createdAt });
          return {
            id,
            title: text(raw.title, "category title", 40, { required: true }),
            order: number(raw.order, "category order", { integer: true }),
            createdAt,
            updatedAt
          };
        }
        function safeSite(raw, categoryIds) {
          exactFields(raw, [
            "id",
            "categoryId",
            "title",
            "url",
            "iconKind",
            "iconValue",
            "iconColor",
            "order",
            "createdAt",
            "updatedAt"
          ], "siteSpace site");
          const id = text(raw.id, "site id", 96, { required: true });
          const categoryId = text(raw.categoryId, "site categoryId", 96, { required: true });
          if (!SAFE_EXTERNAL_ID.test(id) || !categoryIds.has(categoryId)) {
            throw failure("BACKUP_INVALID_SITE", "Invalid site id or category");
          }
          const iconKind = text(raw.iconKind, "site iconKind", 16, { required: true });
          if (!["catalog", "favicon", "letter"].includes(iconKind)) throw failure("BACKUP_INVALID_SITE", "Invalid site icon kind");
          let iconValue = text(raw.iconValue, "site iconValue", 240);
          if (iconKind === "catalog" && !/^[A-Za-z0-9_./-]{0,160}$/.test(iconValue)) {
            throw failure("BACKUP_INVALID_SITE", "Invalid catalog icon");
          }
          if (iconKind === "favicon") {
            const safeIconValue = iconValue ? safeUrl(iconValue) : "";
            if (iconValue !== safeIconValue) throw failure("BACKUP_INVALID_URL", "Site icon URL must already be safely normalized");
            iconValue = safeIconValue;
          }
          const url = safeUrl(raw.url);
          if (raw.url !== url) throw failure("BACKUP_INVALID_URL", "Site URL must already be safely normalized");
          const createdAt = number(raw.createdAt, "site createdAt");
          const updatedAt = number(raw.updatedAt, "site updatedAt", { min: createdAt });
          return {
            id,
            categoryId,
            title: text(raw.title, "site title", 80, { required: true }),
            url,
            iconKind,
            iconValue,
            iconColor: text(raw.iconColor, "site iconColor", 16),
            order: number(raw.order, "site order", { integer: true }),
            createdAt,
            updatedAt
          };
        }
        function safeSiteSpace(raw) {
          if (raw == null) return null;
          exactFields(raw, ["schemaVersion", "revision", "categories", "sites", "display", "updatedAt"], "siteSpace");
          requireLimit(raw.categories, LIMITS.categories, "siteSpace categories");
          requireLimit(raw.sites, LIMITS.siteSpaceSites, "siteSpace sites");
          const categories = raw.categories.map(safeCategory);
          const categoryIds = unique(categories, "id", "category id");
          const sites = raw.sites.map((site) => safeSite(site, categoryIds));
          unique(sites, "id", "site id");
          exactFields(raw.display, ["iconSize", "titleSize"], "siteSpace display");
          return {
            schemaVersion: number(raw.schemaVersion, "siteSpace schemaVersion", { integer: true, min: 1 }),
            revision: number(raw.revision, "siteSpace revision", { integer: true, min: 1 }),
            categories,
            sites,
            display: {
              iconSize: number(raw.display.iconSize, "siteSpace iconSize", { min: 20, max: 100 }),
              titleSize: number(raw.display.titleSize, "siteSpace titleSize", { min: 8, max: 24 })
            },
            updatedAt: number(raw.updatedAt, "siteSpace updatedAt")
          };
        }
        function safeDockSite(raw) {
          exactFields(raw, ["id", "title", "url", "iconMode", "iconText", "iconColor", "iconUrl"], "dock site", [
            "iconMode",
            "iconText",
            "iconColor",
            "iconUrl"
          ]);
          const id = text(raw.id, "dock site id", 96, { required: true });
          if (!SAFE_EXTERNAL_ID.test(id)) throw failure("BACKUP_INVALID_DOCK", "Invalid dock site id");
          const url = safeUrl(raw.url);
          if (raw.url !== url) throw failure("BACKUP_INVALID_URL", "Dock URL must already be safely normalized");
          const site = { id, title: text(raw.title, "dock title", 80, { required: true }), url };
          if (raw.iconMode != null) site.iconMode = text(raw.iconMode, "dock iconMode", 16);
          if (raw.iconText != null) site.iconText = text(raw.iconText, "dock iconText", 10);
          if (raw.iconColor != null) site.iconColor = text(raw.iconColor, "dock iconColor", 16);
          if (raw.iconUrl != null) {
            site.iconUrl = safeUrl(raw.iconUrl, { allowEmpty: true });
            if (raw.iconUrl !== site.iconUrl) throw failure("BACKUP_INVALID_URL", "Dock icon URL must already be safely normalized");
          }
          return site;
        }
        function safeDock(raw) {
          if (raw == null) return null;
          exactFields(raw, ["enabled", "enabledUserSet", "autoHide", "sites", "motion", "appearance"], "dock");
          requireLimit(raw.sites, LIMITS.dockSites, "dock sites");
          const sites = raw.sites.map(safeDockSite);
          unique(sites, "id", "dock site id");
          unique(sites, "url", "dock site URL");
          exactFields(raw.motion, ["iconSize", "magnification", "influenceRadius", "response", "damping", "hideDelay"], "dock motion");
          exactFields(raw.appearance, ["tileSize", "trayPadding", "tileColor", "tileOpacity"], "dock appearance");
          if (typeof raw.enabled !== "boolean" || typeof raw.enabledUserSet !== "boolean" || typeof raw.autoHide !== "boolean") {
            throw failure("BACKUP_INVALID_DOCK", "Invalid dock flags");
          }
          return {
            enabled: raw.enabled,
            enabledUserSet: raw.enabledUserSet,
            autoHide: raw.autoHide,
            sites,
            motion: Object.fromEntries(Object.entries(raw.motion).map(([key, value]) => [
              key,
              number(value, `dock motion ${key}`, { min: 0, max: 1e4 })
            ])),
            appearance: {
              tileSize: number(raw.appearance.tileSize, "dock tileSize", { min: 20, max: 100 }),
              trayPadding: number(raw.appearance.trayPadding, "dock trayPadding", { min: 0, max: 40 }),
              tileColor: /^#[0-9a-f]{6}$/i.test(raw.appearance.tileColor) ? raw.appearance.tileColor.toLowerCase() : (() => {
                throw failure("BACKUP_INVALID_DOCK", "Invalid dock tileColor");
              })(),
              tileOpacity: number(raw.appearance.tileOpacity, "dock tileOpacity", { min: 0, max: 100 })
            }
          };
        }
        function deepFreeze(value) {
          if (!value || typeof value !== "object" || Object.isFrozen(value)) return value;
          Object.values(value).forEach(deepFreeze);
          return Object.freeze(value);
        }
        function safeSummary(taskGraph, siteSpace, dock) {
          return {
            tasks: taskGraph.tasks.length,
            pages: taskGraph.pages.length,
            relations: taskGraph.relations.length,
            categories: siteSpace?.categories.length || 0,
            sites: siteSpace?.sites.length || 0,
            dockSites: dock?.sites.length || 0
          };
        }
        function validateSummary(value, fields, label) {
          exactFields(value, fields, label);
          fields.forEach((field) => number(value[field], `${label} ${field}`, { integer: true, min: 0 }));
        }
        const pick = (value, fields) => Object.fromEntries(fields.filter((field) => value?.[field] !== void 0).map((field) => [field, value[field]]));
        function projectedUrl(value) {
          try {
            return safeUrl(value, { allowEmpty: true });
          } catch (_error) {
            return "";
          }
        }
        function collisionSafePages(items, projectPage) {
          const used = /* @__PURE__ */ new Set();
          return items.map((item, index) => {
            const page = projectPage(item);
            if (!page.normalizedUrl) return page;
            if (!used.has(page.normalizedUrl)) {
              used.add(page.normalizedUrl);
              return page;
            }
            let serial = index + 1;
            let normalizedUrl = "";
            do {
              const candidate = new URL(page.normalizedUrl);
              const basePath = candidate.pathname.endsWith("/") ? candidate.pathname : `${candidate.pathname}/`;
              candidate.pathname = `${basePath}${REDACTED_COLLISION_PATH}/${serial}`;
              normalizedUrl = candidate.href;
              serial += 1;
            } while (used.has(normalizedUrl));
            used.add(normalizedUrl);
            return {
              ...page,
              // The query-free restore target remains useful, while an opaque
              // same-origin identity keeps distinct local pages structurally valid.
              normalizedUrl
            };
          });
        }
        function projectSiteSpace(value) {
          if (!plain(value)) return null;
          return {
            ...pick(value, ["schemaVersion", "revision", "display", "updatedAt"]),
            categories: (Array.isArray(value.categories) ? value.categories : []).map((item) => pick(item, ["id", "title", "order", "createdAt", "updatedAt"])),
            sites: (Array.isArray(value.sites) ? value.sites : []).map((item) => ({
              ...pick(item, [
                "id",
                "categoryId",
                "title",
                "iconKind",
                "iconValue",
                "iconColor",
                "order",
                "createdAt",
                "updatedAt"
              ]),
              url: projectedUrl(item?.url),
              ...item?.iconKind === "favicon" ? { iconValue: projectedUrl(item.iconValue) } : {}
            }))
          };
        }
        function projectDock(value) {
          if (!plain(value)) return null;
          return {
            ...pick(value, ["enabled", "enabledUserSet", "autoHide", "motion", "appearance"]),
            sites: (Array.isArray(value.sites) ? value.sites : []).map((item) => ({
              ...pick(item, ["id", "title", "iconMode", "iconText", "iconColor"]),
              url: projectedUrl(item?.url),
              ...item?.iconUrl !== void 0 ? { iconUrl: projectedUrl(item.iconUrl) } : {}
            }))
          };
        }
        function buildSafeDataBackup(snapshot = {}, options = {}) {
          const timestamp = number(options.now?.() ?? Date.now(), "exportedAt");
          const projectTask = (item, index) => {
            const source = item?.source === "ai" && item?.manualLocked !== void 0 && item?.confidence !== void 0 && item?.evidence && item?.modelVersion ? "ai" : "manual";
            return {
              ...pick(item, ["id", "title", "domain", "status"]),
              sortOrder: Number.isFinite(Number(item?.sortOrder)) ? Number(item.sortOrder) : index,
              source,
              manualLocked: source === "manual" ? true : item.manualLocked === true,
              confidence: source === "manual" ? 1 : item.confidence,
              evidence: source === "manual" ? "manual" : item.evidence,
              modelVersion: source === "manual" ? "" : item.modelVersion,
              createdAt: Number.isFinite(Number(item?.createdAt)) ? Number(item.createdAt) : timestamp,
              updatedAt: Number.isFinite(Number(item?.updatedAt)) ? Number(item.updatedAt) : timestamp
            };
          };
          const projectPage = (item) => ({
            ...pick(item, ["id", "title", "domain"]),
            normalizedUrl: projectedUrl(item?.normalizedUrl),
            restoreUrl: projectedUrl(item?.restoreUrl),
            faviconUrl: projectedUrl(item?.faviconUrl),
            createdAt: Number.isFinite(Number(item?.createdAt)) ? Number(item.createdAt) : timestamp,
            updatedAt: Number.isFinite(Number(item?.updatedAt)) ? Number(item.updatedAt) : timestamp
          });
          const projectRelation = (item, index) => {
            const source = item?.source === "ai" || item?.source === "rule" ? item.source : "manual";
            const safeSource = source !== "manual" && item?.confidence !== void 0 && item?.evidence ? source : "manual";
            return {
              ...pick(item, ["id", "taskId", "pageId", "isPrimary", "role"]),
              source: safeSource,
              manualLocked: safeSource === "manual" ? true : item.manualLocked === true,
              confidence: safeSource === "manual" ? 1 : item.confidence,
              evidence: safeSource === "manual" ? "manual" : item.evidence,
              sortOrder: Number.isFinite(Number(item?.sortOrder)) ? Number(item.sortOrder) : index,
              createdAt: Number.isFinite(Number(item?.createdAt)) ? Number(item.createdAt) : timestamp,
              updatedAt: Number.isFinite(Number(item?.updatedAt)) ? Number(item.updatedAt) : timestamp
            };
          };
          const projectedTaskGraph = {
            tasks: (Array.isArray(snapshot.tasks) ? snapshot.tasks : snapshot.taskGraph?.tasks || []).map(projectTask),
            pages: collisionSafePages(
              Array.isArray(snapshot.pages) ? snapshot.pages : snapshot.taskGraph?.pages || [],
              projectPage
            ),
            relations: (Array.isArray(snapshot.relations) ? snapshot.relations : snapshot.taskGraph?.relations || []).map(projectRelation)
          };
          scanSensitive(projectedTaskGraph);
          const taskGraph = safeTaskGraph(projectedTaskGraph);
          const siteSpace = safeSiteSpace(projectSiteSpace(snapshot.siteSpace));
          const dock = safeDock(projectDock(snapshot.dock));
          const output = {
            format: FORMAT,
            formatVersion: FORMAT_VERSION,
            schemaVersion: number(options.schemaVersion || DataSchema.DB_VERSION, "schemaVersion", { integer: true, min: 1 }),
            extensionVersion: text(options.extensionVersion, "extensionVersion", 40),
            exportedAt: timestamp,
            summary: safeSummary(taskGraph, siteSpace, dock),
            taskGraph,
            siteSpace,
            dock
          };
          scanSensitive(output);
          if (byteLength(output) > MAX_BYTES) throw failure("BACKUP_TOO_LARGE", "Backup exceeds the maximum size");
          return deepFreeze(output);
        }
        function parseAndValidateBackup(input) {
          if (typeof input === "string" && byteLength(input) > MAX_BYTES) {
            throw failure("BACKUP_TOO_LARGE", "Backup exceeds the maximum size");
          }
          let raw;
          try {
            raw = typeof input === "string" ? JSON.parse(input) : input;
          } catch (_error) {
            throw failure("BACKUP_INVALID_JSON", "Backup is not valid JSON");
          }
          if (!plain(raw)) throw failure("BACKUP_INVALID_FORMAT", "Backup root must be an object");
          if (byteLength(raw) > MAX_BYTES) throw failure("BACKUP_TOO_LARGE", "Backup exceeds the maximum size");
          scanSensitive(raw);
          if (raw.format === LEGACY_FORMAT && Number(raw.formatVersion) === 1) {
            exactFields(raw, [
              "format",
              "formatVersion",
              "schemaVersion",
              "extensionVersion",
              "exportedAt",
              "summary",
              "tasks",
              "pages",
              "relations"
            ], "legacy backup");
            validateSummary(raw.summary, ["tasks", "pages", "relations"], "legacy summary");
            const taskGraph2 = safeTaskGraph({
              tasks: raw.tasks,
              pages: raw.pages,
              relations: raw.relations
            }, { legacy: true });
            return deepFreeze({
              format: FORMAT,
              formatVersion: FORMAT_VERSION,
              schemaVersion: DataSchema.DB_VERSION,
              extensionVersion: text(raw.extensionVersion, "extensionVersion", 40),
              exportedAt: number(raw.exportedAt, "exportedAt"),
              summary: safeSummary(taskGraph2, null, null),
              taskGraph: taskGraph2,
              siteSpace: null,
              dock: null,
              migratedFrom: 1
            });
          }
          if (raw.format !== FORMAT || Number(raw.formatVersion) !== FORMAT_VERSION) {
            throw failure("BACKUP_UNSUPPORTED_VERSION", "Unsupported TabFlow backup version");
          }
          exactFields(raw, [
            "format",
            "formatVersion",
            "schemaVersion",
            "extensionVersion",
            "exportedAt",
            "summary",
            "taskGraph",
            "siteSpace",
            "dock"
          ], "backup");
          validateSummary(raw.summary, ["tasks", "pages", "relations", "categories", "sites", "dockSites"], "summary");
          const taskGraph = safeTaskGraph(raw.taskGraph);
          const siteSpace = safeSiteSpace(raw.siteSpace);
          const dock = safeDock(raw.dock);
          const result = {
            format: FORMAT,
            formatVersion: FORMAT_VERSION,
            schemaVersion: number(raw.schemaVersion, "schemaVersion", { integer: true, min: 1 }),
            extensionVersion: text(raw.extensionVersion, "extensionVersion", 40),
            exportedAt: number(raw.exportedAt, "exportedAt"),
            summary: safeSummary(taskGraph, siteSpace, dock),
            taskGraph,
            siteSpace,
            dock
          };
          return deepFreeze(result);
        }
        return Object.freeze({
          FORMAT,
          LEGACY_FORMAT,
          FORMAT_VERSION,
          MAX_BYTES,
          LIMITS,
          buildSafeDataBackup,
          parseAndValidateBackup,
          safeUrl
        });
      });
    }
  });

  // src/shared/v2-safe-data-export.js
  var require_v2_safe_data_export = __commonJS({
    "src/shared/v2-safe-data-export.js"(exports, module) {
      (function attachV2SafeDataExport(root, factory) {
        const api = factory(
          root.TfaV2SafeDataBackup || (typeof __require === "function" ? require_v2_safe_data_backup() : null)
        );
        if (typeof module === "object" && module.exports) module.exports = api;
        root.TfaV2SafeDataExport = api;
      })(typeof globalThis === "object" ? globalThis : exports, function createModule(SafeBackup) {
        "use strict";
        const pick = (value, fields) => Object.fromEntries(fields.filter((field) => value?.[field] !== void 0).map((field) => [field, value[field]]));
        function safeUrl(value) {
          try {
            const url = new URL(String(value || ""));
            if (!["http:", "https:"].includes(url.protocol)) return "";
            url.username = "";
            url.password = "";
            url.search = "";
            url.hash = "";
            return url.href;
          } catch (_error) {
            return "";
          }
        }
        function buildLegacySafeDataExport(snapshot = {}, options = {}) {
          const tasks = (Array.isArray(snapshot.tasks) ? snapshot.tasks : []).map((task) => pick(task, [
            "id",
            "title",
            "domain",
            "status",
            "sortOrder",
            "source",
            "manualLocked",
            "createdAt",
            "updatedAt"
          ]));
          const pages = (Array.isArray(snapshot.pages) ? snapshot.pages : []).map((page) => ({
            ...pick(page, ["id", "title", "domain", "createdAt", "updatedAt"]),
            normalizedUrl: safeUrl(page.normalizedUrl),
            restoreUrl: safeUrl(page.restoreUrl)
          }));
          const relations = (Array.isArray(snapshot.relations) ? snapshot.relations : []).map((relation) => pick(relation, [
            "id",
            "taskId",
            "pageId",
            "isPrimary",
            "role",
            "source",
            "manualLocked",
            "sortOrder",
            "createdAt",
            "updatedAt"
          ]));
          return Object.freeze({
            format: "tabflow-v2-safe-export",
            formatVersion: 1,
            schemaVersion: 4,
            extensionVersion: String(options.extensionVersion || ""),
            exportedAt: Number(options.now?.() ?? Date.now()),
            summary: { tasks: tasks.length, pages: pages.length, relations: relations.length },
            tasks,
            pages,
            relations
          });
        }
        function buildSafeDataExport(snapshot = {}, options = {}) {
          if (!SafeBackup || typeof SafeBackup.buildSafeDataBackup !== "function") {
            throw new TypeError("Safe backup module is unavailable");
          }
          return SafeBackup.buildSafeDataBackup(snapshot, options);
        }
        return Object.freeze({ buildSafeDataExport, buildLegacySafeDataExport });
      });
    }
  });

  // src/background/license-service.js
  var require_license_service = __commonJS({
    "src/background/license-service.js"(exports, module) {
      (function attachBackgroundLicenseService(root, factory) {
        const api = factory();
        const commonJs = typeof module === "object" && module.exports;
        if (commonJs) module.exports = api;
        if (root) root.TfaBackgroundLicenseService = api;
      })(typeof globalThis === "object" ? globalThis : exports, function createModule() {
        "use strict";
        function createLicenseService(deps) {
          const license = deps.license;
          async function getState() {
            try {
              const state = await license.getLicenseState();
              return { ok: true, state };
            } catch (_) {
              return { ok: false, message: "\u8BFB\u53D6\u6FC0\u6D3B\u72B6\u6001\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5" };
            }
          }
          async function activate(msg = {}) {
            try {
              const state = await license.activateLicense(msg.code || "");
              return { ok: true, state };
            } catch (_) {
              return { ok: false, message: "\u6FC0\u6D3B\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5" };
            }
          }
          async function deactivate() {
            try {
              const state = await license.deactivateLicense();
              return { ok: true, state };
            } catch (_) {
              return { ok: false, message: "\u6CE8\u9500\u6FC0\u6D3B\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5" };
            }
          }
          return Object.freeze({
            handlers: Object.freeze({
              LICENSE_GET_STATE: getState,
              LICENSE_ACTIVATE: activate,
              LICENSE_DEACTIVATE: deactivate
            })
          });
        }
        return Object.freeze({ createLicenseService });
      });
    }
  });

  // src/background/screenshot-service.js
  var require_screenshot_service = __commonJS({
    "src/background/screenshot-service.js"(exports, module) {
      (function attachBackgroundScreenshotService(root, factory) {
        const api = factory();
        const commonJs = typeof module === "object" && module.exports;
        if (commonJs) module.exports = api;
        if (root) root.TfaBackgroundScreenshotService = api;
      })(typeof globalThis === "object" ? globalThis : exports, function createModule() {
        "use strict";
        const CACHE_KEY = "tfa_screenshot_cache_v2";
        const CACHE_URL_KEY = "tfa_screenshot_cache_urls_v2";
        const LEGACY_KEYS = ["tfa_screenshot_cache", "tfa_screenshot_cache_urls"];
        const MAX_ITEMS = 40;
        const CAPTURE_QUALITY = 55;
        const OUTPUT_QUALITY = 0.62;
        const MAX_WIDTH = 640;
        const MAX_HEIGHT = 400;
        const THROTTLE_MS = 1200;
        const DELAYS = Object.freeze({ activated: 500, loaded: 1400, switched: 450 });
        const CAPTURE_RETRY_DELAYS = Object.freeze([0, 120, 280]);
        function createScreenshotService(deps) {
          const {
            chrome: chrome2,
            isBrowserInternalUrl,
            fetchFn = typeof fetch === "function" ? fetch : null,
            createImageBitmapFn = typeof createImageBitmap === "function" ? createImageBitmap : null,
            OffscreenCanvasClass = typeof OffscreenCanvas === "function" ? OffscreenCanvas : null,
            btoaFn = typeof btoa === "function" ? btoa : null,
            setTimer = setTimeout,
            clearTimer = clearTimeout,
            delay = (milliseconds) => new Promise((resolve) => setTimer(resolve, milliseconds)),
            now = Date.now,
            warn = () => {
            }
          } = deps;
          const screenshotCache = /* @__PURE__ */ new Map();
          const screenshotCacheUrls = /* @__PURE__ */ new Map();
          const lastCaptureTime = /* @__PURE__ */ new Map();
          const pendingCaptureTimers = /* @__PURE__ */ new Map();
          const captureInFlight = /* @__PURE__ */ new Map();
          const tabGenerations = /* @__PURE__ */ new Map();
          const windowActivationEpoch = /* @__PURE__ */ new Map();
          const removedTabTombstones = /* @__PURE__ */ new Set();
          let restorePromise = null;
          let persistChain = Promise.resolve();
          let captureLeaseSequence = 0;
          function currentGeneration(tabId) {
            return tabGenerations.get(tabId) || 0;
          }
          function forgetTab(tabId) {
            if (!Number.isInteger(tabId) || tabId <= 0) return;
            clearScheduled(tabId);
            tabGenerations.set(tabId, currentGeneration(tabId) + 1);
            screenshotCache.delete(tabId);
            screenshotCacheUrls.delete(tabId);
            lastCaptureTime.delete(tabId);
            const inFlight = captureInFlight.get(tabId);
            if (inFlight) removedTabTombstones.add(tabId);
            else tabGenerations.delete(tabId);
            persist().catch(() => {
            });
          }
          function forgetWindow(windowId) {
            if (!Number.isInteger(windowId) || windowId <= 0) return;
            windowActivationEpoch.delete(windowId);
          }
          async function restoreFromSession() {
            try {
              const storageArea = chrome2.storage && chrome2.storage.session;
              if (!storageArea) return;
              const { [CACHE_KEY]: stored, [CACHE_URL_KEY]: storedUrls } = await storageArea.get([CACHE_KEY, CACHE_URL_KEY]);
              if (!stored || typeof stored !== "object") return;
              Object.entries(stored).forEach(([tabId, dataUrl]) => {
                if (!dataUrl) return;
                const numericId = Number(tabId);
                if (!Number.isFinite(numericId) || currentGeneration(numericId) !== 0 || screenshotCache.has(numericId)) return;
                screenshotCache.set(numericId, dataUrl);
                const cachedUrl = storedUrls && typeof storedUrls === "object" ? storedUrls[tabId] : "";
                if (cachedUrl) screenshotCacheUrls.set(numericId, cachedUrl);
              });
            } catch (_) {
            }
          }
          function ensureRestored() {
            if (!restorePromise) {
              restorePromise = (async () => {
                try {
                  const storageArea = chrome2.storage && chrome2.storage.session;
                  if (storageArea && typeof storageArea.remove === "function") await storageArea.remove(LEGACY_KEYS);
                } catch (_) {
                }
                await restoreFromSession();
              })();
            }
            return restorePromise;
          }
          function persist() {
            persistChain = persistChain.then(async () => {
              try {
                const storageArea = chrome2.storage && chrome2.storage.session;
                if (!storageArea) return;
                const payload = {};
                const urlPayload = {};
                for (const [tabId, dataUrl] of screenshotCache) payload[tabId] = dataUrl;
                for (const [tabId, url] of screenshotCacheUrls) if (url) urlPayload[tabId] = url;
                await storageArea.set({ [CACHE_KEY]: payload, [CACHE_URL_KEY]: urlPayload });
              } catch (_) {
              }
            });
            return persistChain;
          }
          function remember(tabId, dataUrl, url = "", generation = currentGeneration(tabId)) {
            if (!tabId || !dataUrl || currentGeneration(tabId) !== generation) return false;
            screenshotCache.delete(tabId);
            screenshotCacheUrls.delete(tabId);
            screenshotCache.set(tabId, dataUrl);
            if (url) screenshotCacheUrls.set(tabId, url);
            while (screenshotCache.size > MAX_ITEMS) {
              const firstKey = screenshotCache.keys().next().value;
              screenshotCache.delete(firstKey);
              screenshotCacheUrls.delete(firstKey);
            }
            persist().catch(() => {
            });
            return true;
          }
          function invalidate(tabId) {
            if (!tabId) return;
            tabGenerations.set(tabId, currentGeneration(tabId) + 1);
            screenshotCache.delete(tabId);
            screenshotCacheUrls.delete(tabId);
            lastCaptureTime.delete(tabId);
            persist().catch(() => {
            });
          }
          function getCached(tabId, currentUrl = "") {
            if (!screenshotCache.has(tabId)) return "";
            const cachedUrl = screenshotCacheUrls.get(tabId) || "";
            if (currentUrl && !cachedUrl) return "";
            if (cachedUrl && currentUrl && cachedUrl !== currentUrl) {
              invalidate(tabId);
              return "";
            }
            return screenshotCache.get(tabId) || "";
          }
          function clearScheduled(tabId) {
            const timerSet = pendingCaptureTimers.get(tabId);
            if (!timerSet) return;
            for (const timerId of timerSet) clearTimer(timerId);
            pendingCaptureTimers.delete(tabId);
          }
          async function getActiveTabInWindow(windowId) {
            const [tab] = await chrome2.tabs.query({ active: true, windowId });
            return tab || null;
          }
          async function hasVisibleWorkspace(tabId) {
            try {
              const response = await chrome2.tabs.sendMessage(tabId, { type: "TFA_QUERY_OVERLAY_VISIBILITY" });
              return response?.visible === true;
            } catch (_error) {
              return false;
            }
          }
          function createCaptureLeaseId(tabId) {
            captureLeaseSequence += 1;
            return `capture:${tabId}:${now()}:${captureLeaseSequence}`;
          }
          async function setCaptureSurfacesHidden(tabId, hidden, leaseId) {
            if (typeof chrome2.tabs.sendMessage !== "function") return true;
            try {
              const response = await chrome2.tabs.sendMessage(tabId, {
                type: "TFA_SET_CAPTURE_SURFACES_HIDDEN",
                hidden: hidden === true,
                leaseId
              });
              if (response?.ok !== true || typeof response.hidden !== "boolean") return false;
              return hidden === true ? true : response.hidden === false;
            } catch (error) {
              return /receiving end does not exist|could not establish connection/i.test(String(error?.message || error));
            }
          }
          async function downscale(dataUrl) {
            if (!/^data:image\//i.test(dataUrl || "")) return dataUrl || "";
            if (!fetchFn || !createImageBitmapFn || !OffscreenCanvasClass || !btoaFn) return dataUrl;
            let bitmap = null;
            try {
              const response = await fetchFn(dataUrl);
              bitmap = await createImageBitmapFn(await response.blob());
              const scale = Math.min(1, MAX_WIDTH / bitmap.width, MAX_HEIGHT / bitmap.height);
              if (scale >= 1) return dataUrl;
              const width = Math.max(1, Math.round(bitmap.width * scale));
              const height = Math.max(1, Math.round(bitmap.height * scale));
              const canvas = new OffscreenCanvasClass(width, height);
              const context = canvas.getContext("2d", { alpha: false });
              if (!context) return dataUrl;
              context.drawImage(bitmap, 0, 0, width, height);
              const blob = await canvas.convertToBlob({ type: "image/jpeg", quality: OUTPUT_QUALITY });
              const bytes = new Uint8Array(await blob.arrayBuffer());
              let binary = "";
              for (let offset = 0; offset < bytes.length; offset += 32768) {
                binary += String.fromCharCode(...bytes.subarray(offset, offset + 32768));
              }
              return `data:image/jpeg;base64,${btoaFn(binary)}`;
            } catch (_) {
              return dataUrl;
            } finally {
              if (bitmap && typeof bitmap.close === "function") bitmap.close();
            }
          }
          async function capture(tabId, windowId, options = {}) {
            const { ignoreThrottle = false } = options;
            if (!tabId || !windowId) return "";
            if (removedTabTombstones.has(tabId)) {
              removedTabTombstones.delete(tabId);
              tabGenerations.set(tabId, currentGeneration(tabId) + 1);
            }
            const generation = currentGeneration(tabId);
            const inFlight = captureInFlight.get(tabId);
            if (inFlight?.generation === generation) return inFlight.promise;
            const isCurrentGeneration = () => currentGeneration(tabId) === generation;
            const capturePromise = (async () => {
              await ensureRestored();
              if (!isCurrentGeneration()) return "";
              let lastError = null;
              for (let attempt = 0; attempt < CAPTURE_RETRY_DELAYS.length; attempt += 1) {
                if (CAPTURE_RETRY_DELAYS[attempt] > 0) await delay(CAPTURE_RETRY_DELAYS[attempt]);
                if (!isCurrentGeneration()) return "";
                try {
                  const tab = await chrome2.tabs.get(tabId);
                  if (!isCurrentGeneration()) return "";
                  const currentUrl = tab && tab.url ? tab.url : "";
                  if (!tab || !tab.active || isBrowserInternalUrl(currentUrl)) return "";
                  if (tab.status !== "complete") continue;
                  await hasVisibleWorkspace(tabId);
                  if (!isCurrentGeneration()) return "";
                  const last = lastCaptureTime.get(tabId) || 0;
                  if (!ignoreThrottle && now() - last < THROTTLE_MS) return getCached(tabId, currentUrl);
                  const before = await getActiveTabInWindow(windowId);
                  if (!isCurrentGeneration()) return "";
                  if (!before || before.id !== tabId || (before.url || "") !== currentUrl) continue;
                  const epoch = windowActivationEpoch.get(windowId) || 0;
                  const captureLeaseId = createCaptureLeaseId(tabId);
                  let hideAttempted = false;
                  let captured;
                  try {
                    hideAttempted = true;
                    const surfacesHidden = await setCaptureSurfacesHidden(tabId, true, captureLeaseId);
                    if (!isCurrentGeneration()) return "";
                    if (!surfacesHidden) return getCached(tabId, currentUrl);
                    const stillActive = await getActiveTabInWindow(windowId);
                    if (!isCurrentGeneration()) return "";
                    if (!stillActive || stillActive.id !== tabId || (stillActive.url || "") !== currentUrl) return "";
                    captured = await chrome2.tabs.captureVisibleTab(windowId, { format: "jpeg", quality: CAPTURE_QUALITY });
                  } finally {
                    if (hideAttempted) await setCaptureSurfacesHidden(tabId, false, captureLeaseId);
                  }
                  const after = await getActiveTabInWindow(windowId);
                  if (!isCurrentGeneration()) return "";
                  if (!after || after.id !== tabId || (after.url || "") !== currentUrl) return "";
                  if ((windowActivationEpoch.get(windowId) || 0) !== epoch) return "";
                  const dataUrl = await downscale(captured);
                  if (!isCurrentGeneration()) return "";
                  if (!dataUrl) continue;
                  if (!remember(tabId, dataUrl, currentUrl, generation)) return "";
                  lastCaptureTime.set(tabId, now());
                  return dataUrl;
                } catch (error) {
                  lastError = error;
                  if (!isCurrentGeneration()) return "";
                }
              }
              if (lastError) {
                warn("[TFA preview] capture failed", { tabId, windowId, message: lastError && lastError.message ? lastError.message : String(lastError) });
              }
              return "";
            })();
            const captureEntry = { generation, windowId, promise: capturePromise };
            captureInFlight.set(tabId, captureEntry);
            try {
              return await capturePromise;
            } finally {
              if (captureInFlight.get(tabId) === captureEntry) {
                captureInFlight.delete(tabId);
                if (removedTabTombstones.has(tabId)) {
                  removedTabTombstones.delete(tabId);
                  tabGenerations.delete(tabId);
                }
              }
            }
          }
          function schedule(tabId, windowId, delay2, options = {}) {
            if (!tabId || !windowId) return;
            clearScheduled(tabId);
            const timerId = setTimer(async () => {
              const timerSet2 = pendingCaptureTimers.get(tabId);
              if (timerSet2) {
                timerSet2.delete(timerId);
                if (!timerSet2.size) pendingCaptureTimers.delete(tabId);
              }
              await capture(tabId, windowId, options);
            }, delay2);
            const timerSet = pendingCaptureTimers.get(tabId) || /* @__PURE__ */ new Set();
            timerSet.add(timerId);
            pendingCaptureTimers.set(tabId, timerSet);
          }
          function noteActivation(windowId) {
            windowActivationEpoch.set(windowId, (windowActivationEpoch.get(windowId) || 0) + 1);
          }
          async function resolveSwitchSource(msg = {}, sender = {}) {
            let sourceTabId = msg.fromTabId || sender.tab && sender.tab.id || null;
            let sourceWindowId = msg.fromWindowId || sender.tab && sender.tab.windowId || null;
            if (!sourceTabId || !sourceWindowId) {
              try {
                const [activeTab] = await chrome2.tabs.query({ active: true, currentWindow: true });
                if (activeTab) {
                  sourceTabId = sourceTabId || activeTab.id;
                  sourceWindowId = sourceWindowId || activeTab.windowId;
                }
              } catch (_) {
              }
            }
            return { sourceTabId, sourceWindowId };
          }
          async function captureCurrent(msg, sender = {}) {
            try {
              const senderTab = sender && sender.tab;
              if (senderTab && senderTab.id && senderTab.windowId) {
                return { tabId: senderTab.id, url: senderTab.url || "", screenshot: await capture(senderTab.id, senderTab.windowId, { ignoreThrottle: true }) };
              }
              const win = await chrome2.windows.getCurrent();
              const [activeTab] = await chrome2.tabs.query({ active: true, windowId: win.id });
              if (activeTab) return { tabId: activeTab.id, url: activeTab.url || "", screenshot: await capture(activeTab.id, win.id, { ignoreThrottle: true }) };
            } catch (_) {
            }
            return { error: "capture failed" };
          }
          async function getAll() {
            await ensureRestored();
            const tabs = await chrome2.tabs.query({}).catch(() => []);
            const tabUrlById = new Map(tabs.map((tab) => [tab.id, tab.url || ""]));
            const screenshots = {};
            const previewUrls = {};
            for (const [tabId] of screenshotCache) {
              const dataUrl = getCached(tabId, tabUrlById.get(tabId) || "");
              if (dataUrl) {
                screenshots[tabId] = dataUrl;
                previewUrls[tabId] = tabUrlById.get(tabId) || screenshotCacheUrls.get(tabId) || "";
              }
            }
            return { screenshots, previewUrls };
          }
          async function getPreview(msg = {}) {
            try {
              const tab = await chrome2.tabs.get(msg.tabId);
              if (!tab) return { error: "tab not found" };
              await ensureRestored();
              const currentUrl = tab.url || "";
              let screenshot = getCached(msg.tabId, currentUrl);
              if (!screenshot && tab.active && tab.status === "complete" && !isBrowserInternalUrl(currentUrl)) {
                screenshot = await capture(tab.id, tab.windowId, { ignoreThrottle: true });
              }
              return {
                tabId: tab.id,
                url: currentUrl,
                preview: {
                  screenshot,
                  unavailableReason: screenshot ? "" : isBrowserInternalUrl(currentUrl) ? "restricted" : !tab.active ? "inactive" : tab.status !== "complete" ? "loading" : "capture-failed"
                }
              };
            } catch (error) {
              return { error: error.message };
            }
          }
          return Object.freeze({
            cache: screenshotCache,
            cacheUrls: screenshotCacheUrls,
            lastCaptureTime,
            delays: DELAYS,
            capture,
            schedule,
            clearScheduled,
            invalidate,
            forgetTab,
            forgetWindow,
            noteActivation,
            // Exposed for deterministic lifecycle diagnostics and tests.
            tabGenerations,
            windowActivationEpoch,
            resolveSwitchSource,
            handlers: Object.freeze({
              CAPTURE_CURRENT_TAB: captureCurrent,
              GET_ALL_SCREENSHOTS: getAll,
              GET_TAB_PREVIEW: getPreview
            })
          });
        }
        return Object.freeze({ createScreenshotService, DELAYS, MAX_ITEMS, CAPTURE_RETRY_DELAYS });
      });
    }
  });

  // src/background/trash-service.js
  var require_trash_service = __commonJS({
    "src/background/trash-service.js"(exports, module) {
      (function attachBackgroundTrashService(root, factory) {
        const api = factory();
        const commonJs = typeof module === "object" && module.exports;
        if (commonJs) module.exports = api;
        if (root) root.TfaBackgroundTrashService = api;
      })(typeof globalThis === "object" ? globalThis : exports, function createModule() {
        "use strict";
        const TRASH_KEY = "tfa_trash";
        const TRASH_MAX = 50;
        const TASK_OPERATION_STALE_MS = 10 * 60 * 1e3;
        const TASK_DOMAINS = /* @__PURE__ */ new Set(["work", "study", "life", "interest", "admin", "other"]);
        const TASK_STATUSES = /* @__PURE__ */ new Set(["active", "paused", "done", "pending"]);
        const TASK_RELATION_ROLES = /* @__PURE__ */ new Set(["core", "related", "shared"]);
        const TASK_RELATION_SOURCES = /* @__PURE__ */ new Set(["manual", "rule", "ai", "suggested"]);
        function boundedString(value, name, { required = false, max = 8192 } = {}) {
          if (typeof value !== "string") throw new Error(`Invalid task ${name}`);
          const result = value.trim();
          if (required && !result || result.length > max || result.includes("\0")) throw new Error(`Invalid task ${name}`);
          return result;
        }
        function finiteTimestamp(value, name) {
          if (!Number.isFinite(value) || value < 0) throw new Error(`Invalid task ${name}`);
          return value;
        }
        function validateTaskSnapshot(value) {
          if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("Invalid task snapshot");
          if (!Array.isArray(value.pages) || value.pages.length > 500) throw new Error("Invalid task pages");
          const domain = boundedString(value.domain, "domain", { required: true, max: 32 });
          const status = boundedString(value.status, "status", { required: true, max: 32 });
          if (!TASK_DOMAINS.has(domain)) throw new Error("Invalid task domain");
          if (!TASK_STATUSES.has(status)) throw new Error("Invalid task status");
          const taskSortOrder = value.sortOrder == null ? 0 : Number(value.sortOrder);
          if (!Number.isFinite(taskSortOrder)) throw new Error("Invalid task sortOrder");
          const task = {
            id: boundedString(value.id, "id", { required: true, max: 256 }),
            title: boundedString(value.title, "title", { required: true, max: 500 }),
            domain,
            status,
            sortOrder: taskSortOrder,
            createdAt: finiteTimestamp(value.createdAt, "createdAt"),
            updatedAt: finiteTimestamp(value.updatedAt, "updatedAt"),
            pages: value.pages.map((page) => {
              if (!page || typeof page !== "object" || Array.isArray(page)) throw new Error("Invalid task page");
              const tabId = page.tabId == null ? null : page.tabId;
              if (tabId !== null && !Number.isInteger(tabId)) throw new Error("Invalid task page tabId");
              const source = boundedString(page.source, "page source", { required: true, max: 32 });
              const role = boundedString(page.role || "related", "page role", { required: true, max: 32 });
              if (!TASK_RELATION_SOURCES.has(source)) throw new Error("Invalid task page source");
              if (!TASK_RELATION_ROLES.has(role)) throw new Error("Invalid task page role");
              const sortOrder = Number(page.sortOrder == null ? 0 : page.sortOrder);
              if (!Number.isFinite(sortOrder)) throw new Error("Invalid task page sortOrder");
              const relatedTaskIds = Array.isArray(page.relatedTaskIds) ? [...new Set(page.relatedTaskIds.map((id) => boundedString(String(id), "related task id", { required: true, max: 256 })))] : [];
              const confidence = source === "manual" ? 1 : Number(page.confidence == null ? 0.5 : page.confidence);
              if (!Number.isFinite(confidence) || confidence < 0 || confidence > 1) throw new Error("Invalid task page confidence");
              return {
                id: boundedString(page.id, "page id", { required: true, max: 1e4 }),
                tabId,
                title: boundedString(page.title, "page title", { required: true, max: 2e3 }),
                url: boundedString(page.url, "page url", { required: true, max: 8192 }),
                favicon: boundedString(page.favicon || "", "page favicon", { max: 8192 }),
                source,
                role,
                sortOrder,
                isPrimary: page.isPrimary === true,
                manualLocked: source === "manual" ? true : page.manualLocked === true,
                confidence,
                evidence: source === "manual" ? "manual" : boundedString(page.evidence || "trash-restore", "page evidence", { max: 160 }),
                relatedTaskIds
              };
            })
          };
          if (value.recoveredFromTrashId != null) {
            task.recoveredFromTrashId = boundedString(value.recoveredFromTrashId, "restore token", { required: true, max: 256 });
          }
          return task;
        }
        function createTrashService(deps) {
          const { chrome: chrome2, now = Date.now, random = Math.random, logError = () => {
          } } = deps;
          async function getTrash() {
            const { [TRASH_KEY]: trash } = await chrome2.storage.local.get(TRASH_KEY);
            return trash || [];
          }
          async function saveTrash(trash) {
            await chrome2.storage.local.set({ [TRASH_KEY]: trash });
          }
          async function addToTrash(items) {
            const trash = await getTrash();
            trash.unshift(...items);
            if (trash.length > TRASH_MAX) trash.length = TRASH_MAX;
            await saveTrash(trash);
          }
          async function addTask(msg = {}) {
            let task;
            try {
              task = validateTaskSnapshot(msg.task);
            } catch (error) {
              return { ok: false, error: error?.message || "Invalid task snapshot" };
            }
            const operationKey = typeof (msg.operationToken || msg.operationKey) === "string" ? String(msg.operationToken || msg.operationKey).trim().slice(0, 512) : "";
            const trash = await getTrash();
            if (operationKey) {
              const existing = trash.find((entry) => entry.type === "task" && entry.operationKey === operationKey);
              if (existing && existing.task?.id !== task.id) {
                return { ok: false, code: "OPERATION_TOKEN_CONFLICT", error: "Task trash operation token belongs to another task" };
              }
              if (existing) return { ok: true, id: existing.id, phase: existing.operationPhase || "ready", existing: true };
            }
            const closedAt = now();
            const idPrefix = `${closedAt}-task-${task.id}-${random().toString(36).slice(2, 6)}`;
            let id = idPrefix;
            for (let suffix = 2; trash.some((entry) => entry.id === id); suffix += 1) id = `${idPrefix}-${suffix}`;
            const item = {
              type: "task",
              id,
              task,
              closedAt,
              ...operationKey ? { operationKey } : {},
              ...operationKey ? {
                operationPhase: msg.prepared === true ? "prepared" : "ready",
                operationUpdatedAt: closedAt
              } : {}
            };
            await addToTrash([item]);
            return { ok: true, id: item.id, phase: item.operationPhase || "ready" };
          }
          async function updateTaskOperation(msg = {}) {
            const operationToken = typeof (msg.operationToken || msg.operationKey) === "string" ? String(msg.operationToken || msg.operationKey).trim().slice(0, 512) : "";
            const phase = String(msg.phase || "");
            if (!operationToken || !["ready", "restoring", "remove"].includes(phase)) {
              return { ok: false, code: "INVALID_TASK_TRASH_OPERATION", error: "Invalid task trash operation" };
            }
            const trash = await getTrash();
            const index = trash.findIndex((entry) => entry.type === "task" && entry.operationKey === operationToken && (!msg.id || String(entry.id) === String(msg.id)));
            if (index < 0) return phase === "remove" ? { ok: true, existing: true, removed: true } : { ok: false, code: "TASK_TRASH_OPERATION_NOT_FOUND", error: "Task trash operation not found" };
            if (phase === "remove") {
              await saveTrash(trash.filter((_, itemIndex) => itemIndex !== index));
              return { ok: true, removed: true };
            }
            if (trash[index].operationPhase === phase) return { ok: true, id: trash[index].id, phase, existing: true };
            trash[index] = { ...trash[index], operationPhase: phase, operationUpdatedAt: now() };
            await saveTrash(trash);
            return { ok: true, id: trash[index].id, phase };
          }
          async function settleStaleTaskOperations(trash) {
            const currentTime = now();
            let changed = false;
            const next = trash.reduce((items, entry) => {
              const stale = entry.type === "task" && ["prepared", "restoring"].includes(entry.operationPhase) && currentTime - Number(entry.operationUpdatedAt || entry.closedAt || 0) >= TASK_OPERATION_STALE_MS;
              if (!stale) {
                items.push(entry);
                return items;
              }
              changed = true;
              items.push({ ...entry, operationPhase: "ready", operationUpdatedAt: currentTime, recoveredOperation: true });
              return items;
            }, []);
            if (changed) await saveTrash(next);
            return next;
          }
          async function closeTabs(msg = {}) {
            const ids = Array.isArray(msg.tabIds) ? msg.tabIds : [msg.tabIds];
            const closedAt = now();
            const tabsInfo = await Promise.all(ids.map(async (id) => {
              try {
                const tab = await chrome2.tabs.get(id);
                return {
                  type: "tab",
                  id: `${closedAt}-${id}-${random().toString(36).slice(2, 6)}`,
                  tabId: id,
                  title: tab.title || "",
                  url: tab.url || "",
                  favIconUrl: tab.favIconUrl || "",
                  windowId: tab.windowId,
                  closedAt
                };
              } catch (_) {
                return null;
              }
            }));
            const items = tabsInfo.filter(Boolean);
            if (items.length) {
              try {
                await addToTrash(items);
              } catch (error) {
                logError("[TFA] trash save failed:", error);
                return { ok: false, error: error?.message || String(error) };
              }
            }
            try {
              await chrome2.tabs.remove(ids);
            } catch (error) {
              if (items.length) {
                const itemIds = new Set(items.map((item) => item.id));
                try {
                  await saveTrash((await getTrash()).filter((entry) => !itemIds.has(entry.id)));
                } catch (_) {
                }
              }
              return { ok: false, error: error?.message || String(error) };
            }
            return { ok: true };
          }
          async function closeGroup(msg = {}) {
            const groupId = msg.groupId;
            const allTabs = await chrome2.tabs.query({});
            const groupTabs = allTabs.filter((tab) => tab.groupId === groupId);
            let group = null;
            try {
              group = await chrome2.tabGroups.get(groupId);
            } catch (_) {
            }
            const groupInfo = {
              type: "group",
              id: `${now()}-${groupId}-${random().toString(36).slice(2, 6)}`,
              groupId,
              title: group ? group.title : "",
              color: group ? group.color : "grey",
              tabs: groupTabs.map((tab) => ({
                title: tab.title || "",
                url: tab.url || "",
                favIconUrl: tab.favIconUrl || ""
              })),
              closedAt: now()
            };
            try {
              await addToTrash([groupInfo]);
            } catch (error) {
              logError("[TFA] trash save failed:", error);
              return { ok: false, error: error?.message || String(error) };
            }
            if (groupTabs.length) {
              try {
                await chrome2.tabs.remove(groupTabs.map((tab) => tab.id));
              } catch (error) {
                try {
                  await saveTrash((await getTrash()).filter((entry) => entry.id !== groupInfo.id));
                } catch (_) {
                }
                return { ok: false, error: error?.message || String(error) };
              }
            }
            return { ok: true };
          }
          async function list(msg = {}) {
            const trash = await settleStaleTaskOperations(await getTrash());
            return { trash: msg.includeTasks === true ? trash.filter((entry) => entry.type !== "task" || !entry.operationPhase || entry.operationPhase === "ready") : trash.filter((entry) => entry.type !== "task") };
          }
          async function listTaskOperations() {
            const trash = await getTrash();
            return { ok: true, operations: trash.filter((entry) => entry.type === "task" && entry.operationKey && ["prepared", "restoring", "ready"].includes(entry.operationPhase || "ready")) };
          }
          async function restore(msg = {}) {
            const trash = await getTrash();
            const item = trash.find((entry) => entry.id === msg.id);
            if (!item) return { error: "not found" };
            if (item.type === "task") {
              return { ok: false, code: "TASK_RESTORE_REQUIRES_V2", error: "Task trash items must be restored by V2" };
            }
            if (item.type === "tab") {
              await chrome2.tabs.create({ url: item.url, active: false });
            } else if (item.type === "group") {
              const tabIds = [];
              for (const tabInfo of item.tabs) {
                const newTab = await chrome2.tabs.create({ url: tabInfo.url, active: false });
                tabIds.push(newTab.id);
              }
              if (tabIds.length) {
                const newGroupId = await chrome2.tabs.group({ tabIds });
                await chrome2.tabGroups.update(newGroupId, {
                  title: item.title || "",
                  color: item.color || "grey"
                });
              }
            }
            await saveTrash(trash.filter((entry) => entry.id !== msg.id));
            return { ok: true };
          }
          async function remove(msg = {}) {
            const trash = await getTrash();
            const item = trash.find((entry) => entry.id === msg.id);
            if (!item) return { ok: false, error: "not found" };
            if (!["tab", "group", "task"].includes(item.type)) return { ok: false, code: "UNSUPPORTED_TRASH_TYPE", error: "unsupported trash type" };
            if (item.type === "task" && msg.itemType !== "task") {
              return { ok: false, code: "TASK_DELETE_REQUIRES_V2", error: "Task trash items must be deleted by V2" };
            }
            await saveTrash(trash.filter((entry) => entry.id !== msg.id));
            return { ok: true };
          }
          async function clear() {
            const trash = await getTrash();
            await saveTrash(trash.filter((entry) => entry.type === "task"));
            return { ok: true };
          }
          return Object.freeze({
            getTrash,
            saveTrash,
            addToTrash,
            handlers: Object.freeze({
              ADD_TASK_TO_TRASH: addTask,
              UPDATE_TASK_TRASH_OPERATION: updateTaskOperation,
              GET_TASK_TRASH_OPERATIONS: listTaskOperations,
              CLOSE_TABS: closeTabs,
              CLOSE_GROUP: closeGroup,
              GET_TRASH: list,
              RESTORE_TRASH_ITEM: restore,
              DELETE_TRASH_ITEM: remove,
              CLEAR_TRASH: clear
            })
          });
        }
        return Object.freeze({ createTrashService, TRASH_KEY, TRASH_MAX, TASK_OPERATION_STALE_MS });
      });
    }
  });

  // src/background/tab-service.js
  var require_tab_service = __commonJS({
    "src/background/tab-service.js"(exports, module) {
      (function attachBackgroundTabService(root, factory) {
        const recentlyClosedLogic = typeof module === "object" && module.exports ? require_web_dock_recently_closed_logic() : root.TfaWebDockRecentlyClosedLogic;
        const api = factory(recentlyClosedLogic);
        const commonJs = typeof module === "object" && module.exports;
        if (commonJs) module.exports = api;
        if (root) root.TfaBackgroundTabService = api;
      })(typeof globalThis === "object" ? globalThis : exports, function createModule(recentlyClosedLogic) {
        "use strict";
        const DEFAULT_HOT_BOOKMARKS = Object.freeze([
          { title: "Google", url: "https://www.google.com", emoji: "\u{1F50D}" },
          { title: "YouTube", url: "https://www.youtube.com", emoji: "\u25B6\uFE0F" },
          { title: "GitHub", url: "https://github.com", emoji: "\u{1F419}" },
          { title: "Gmail", url: "https://mail.google.com", emoji: "\u2709\uFE0F" },
          { title: "Google Translate", url: "https://translate.google.com", emoji: "\u{1F310}" },
          { title: "Maps", url: "https://maps.google.com", emoji: "\u{1F5FA}\uFE0F" },
          { title: "ChatGPT", url: "https://chat.openai.com", emoji: "\u{1F916}" },
          { title: "Wikipedia", url: "https://zh.wikipedia.org", emoji: "\u{1F4DA}" }
        ]);
        const GROUP_COLORS = ["grey", "blue", "red", "yellow", "green", "pink", "purple", "cyan", "orange"];
        const RECENT_CONSUMPTION_STORAGE_KEY = "tfa_web_dock_recent_consumed_v1";
        const RECENT_CONSUMPTION_TTL_MS = 24 * 60 * 60 * 1e3;
        const RECENT_CONSUMPTION_LIMIT = 100;
        function hostOfUrl(url) {
          try {
            return new URL(url).hostname.replace(/^www\./, "");
          } catch (_) {
            return "";
          }
        }
        function createTabService(deps) {
          const {
            chrome: chrome2,
            screenshot,
            ensureContentScriptReady = async () => false,
            syncDockConfigToTab = async () => false,
            random = Math.random
          } = deps;
          const recentStorage = chrome2.storage?.session || null;
          let recentConsumptionQueue = Promise.resolve();
          function recentStorageError(code) {
            const error = new Error("Recently closed consumption storage is unavailable");
            error.code = code;
            return error;
          }
          function withRecentConsumptionLock(operation) {
            const work = recentConsumptionQueue.catch(() => void 0).then(operation);
            recentConsumptionQueue = work.then(() => void 0, () => void 0);
            return work;
          }
          async function readRecentConsumptionRecords(now = Date.now()) {
            if (typeof recentStorage?.get !== "function") throw recentStorageError("RECENT_STORAGE_UNAVAILABLE");
            let stored;
            try {
              stored = await recentStorage.get(RECENT_CONSUMPTION_STORAGE_KEY);
            } catch (_error) {
              throw recentStorageError("RECENT_STORAGE_READ_FAILED");
            }
            if (!stored || typeof stored !== "object") throw recentStorageError("RECENT_STORAGE_READ_FAILED");
            const raw = stored[RECENT_CONSUMPTION_STORAGE_KEY];
            const source = Array.isArray(raw) ? raw : [];
            const records = source.filter((record) => record && typeof record.key === "string" && Number.isFinite(Number(record.consumedAt)) && now - Number(record.consumedAt) >= 0 && now - Number(record.consumedAt) < RECENT_CONSUMPTION_TTL_MS).sort((left, right) => Number(right.consumedAt) - Number(left.consumedAt)).slice(0, RECENT_CONSUMPTION_LIMIT);
            return {
              records,
              dirty: raw !== void 0 && !Array.isArray(raw) || JSON.stringify(records) !== JSON.stringify(source)
            };
          }
          async function writeRecentConsumptionRecords(records) {
            const next = records.slice(0, RECENT_CONSUMPTION_LIMIT);
            try {
              if (!next.length) {
                if (typeof recentStorage?.remove !== "function") throw recentStorageError("RECENT_STORAGE_UNAVAILABLE");
                await recentStorage.remove(RECENT_CONSUMPTION_STORAGE_KEY);
              } else {
                if (typeof recentStorage?.set !== "function") throw recentStorageError("RECENT_STORAGE_UNAVAILABLE");
                await recentStorage.set({ [RECENT_CONSUMPTION_STORAGE_KEY]: next });
              }
            } catch (error) {
              if (error?.code) throw error;
              throw recentStorageError("RECENT_STORAGE_WRITE_FAILED");
            }
            return next;
          }
          async function readNativeRecentlyClosed() {
            const sessions = await chrome2.sessions.getRecentlyClosed({ maxResults: recentlyClosedLogic.MAX_ITEMS });
            return recentlyClosedLogic.collectRecentlyClosed(sessions);
          }
          async function reconcileRecentConsumption(items, snapshot = null, now = Date.now()) {
            const currentSnapshot = snapshot || await readRecentConsumptionRecords(now);
            const current = currentSnapshot.records;
            const activeKeys = new Set(items.map(recentlyClosedLogic.recentConsumptionKey).filter(Boolean));
            const reconciled = current.filter((record) => activeKeys.has(record.key));
            if (currentSnapshot.dirty || JSON.stringify(reconciled) !== JSON.stringify(current)) {
              await writeRecentConsumptionRecords(reconciled);
            }
            return reconciled;
          }
          async function getAllTabs() {
            const [tabs, windows, groups] = await Promise.all([
              chrome2.tabs.query({}),
              chrome2.windows.getAll({}),
              chrome2.tabGroups.query({}).catch(() => [])
            ]);
            const groupMap = new Map(groups.map((group2) => [group2.id, group2]));
            tabs.forEach((tab) => {
              if (Number.isInteger(tab.groupId) && tab.groupId >= 0) {
                const group2 = groupMap.get(tab.groupId);
                if (group2) {
                  tab._groupTitle = group2.title || "";
                  tab._groupColor = group2.color || "grey";
                  tab._groupCollapsed = group2.collapsed === true;
                }
              }
            });
            const currentWin = await chrome2.windows.getCurrent().catch(() => null);
            return { tabs, windows, currentWindowId: currentWin && currentWin.id };
          }
          async function switchTab(msg = {}, sender = {}) {
            const { sourceTabId, sourceWindowId } = await screenshot.resolveSwitchSource(msg, sender);
            if (sourceTabId && sourceWindowId && sourceTabId !== msg.tabId) {
              await screenshot.capture(sourceTabId, sourceWindowId, { ignoreThrottle: true });
            }
            const contentReady = await ensureContentScriptReady(msg.tabId).catch(() => false);
            if (contentReady) await syncDockConfigToTab(msg.tabId).catch(() => false);
            const tab = await chrome2.tabs.update(msg.tabId, { active: true });
            await chrome2.windows.update(tab.windowId, { focused: true }).catch(() => {
            });
            screenshot.schedule(tab.id, tab.windowId, screenshot.delays.switched, { ignoreThrottle: true });
            return { ok: true };
          }
          async function duplicate(msg = {}) {
            await chrome2.tabs.duplicate(msg.tabId);
            return { ok: true };
          }
          async function pin(msg = {}) {
            await chrome2.tabs.update(msg.tabId, { pinned: !!msg.pinned });
            return { ok: true };
          }
          async function recentlyClosed() {
            try {
              return await withRecentConsumptionLock(async () => {
                const items = await readNativeRecentlyClosed();
                const records = await reconcileRecentConsumption(items);
                const consumedKeys = new Set(records.map((record) => record.key));
                return {
                  dockItems: items.filter((item) => !consumedKeys.has(recentlyClosedLogic.recentConsumptionKey(item))).slice(0, recentlyClosedLogic.MAX_ITEMS)
                };
              });
            } catch (error) {
              return { ok: false, code: error?.code || "RECENTLY_CLOSED_UNAVAILABLE", error: "Recently closed list unavailable" };
            }
          }
          async function consumeRecentlyClosedItem(msg = {}) {
            try {
              return await withRecentConsumptionLock(async () => {
                const requested = {
                  id: String(msg.itemId || ""),
                  url: recentlyClosedLogic.webUrl(msg.url)
                };
                const requestedKey = recentlyClosedLogic.recentConsumptionKey(requested);
                if (!requestedKey) return { ok: false, code: "INVALID_RECENT_ITEM" };
                const items = await readNativeRecentlyClosed();
                const item = items.find((candidate) => recentlyClosedLogic.recentConsumptionKey(candidate) === requestedKey);
                if (!item || item.sessionType !== "url") return { ok: false, code: "RECENT_ITEM_STALE" };
                const now = Date.now();
                const snapshot = await readRecentConsumptionRecords(now);
                const records = await reconcileRecentConsumption(items, snapshot, now);
                const next = [
                  { key: requestedKey, itemId: item.id, url: item.url, consumedAt: now },
                  ...records.filter((record) => record.key !== requestedKey)
                ].slice(0, RECENT_CONSUMPTION_LIMIT);
                await writeRecentConsumptionRecords(next);
                return { ok: true, consumed: true, itemId: item.id };
              });
            } catch (error) {
              return { ok: false, code: error?.code || "RECENT_STORAGE_UNAVAILABLE" };
            }
          }
          async function reconcileRecentlyClosedConsumption() {
            try {
              return await withRecentConsumptionLock(async () => {
                const items = await readNativeRecentlyClosed();
                const records = await reconcileRecentConsumption(items);
                return { ok: true, retained: records.length };
              });
            } catch (error) {
              return { ok: false, code: error?.code || "RECENT_STORAGE_UNAVAILABLE" };
            }
          }
          async function restoreSession(msg = {}) {
            const restored = msg.sessionId ? await chrome2.sessions.restore(msg.sessionId) : await chrome2.sessions.restore();
            const restoredWindowId = Number.isInteger(restored?.tab?.windowId) ? restored.tab.windowId : restored?.window?.id;
            if (Number.isInteger(restoredWindowId)) {
              await chrome2.windows.update(restoredWindowId, { focused: true }).catch(() => {
              });
            }
            return { ok: true, tab: restored && restored.tab, window: restored && restored.window };
          }
          async function move(msg = {}) {
            const target = { index: msg.index };
            if (msg.windowId) target.windowId = msg.windowId;
            await chrome2.tabs.move(msg.tabId, target);
            return { ok: true };
          }
          async function moveRelative(msg = {}) {
            const tabId = Number(msg.tabId);
            if (!Number.isInteger(tabId)) return { ok: false, error: "Invalid tab id" };
            const requestedWindowId = Number(msg.windowId);
            const requestedGroupId = Number.isInteger(Number(msg.groupId)) ? Number(msg.groupId) : -1;
            const beforeTabId = Number.isInteger(Number(msg.beforeTabId)) ? Number(msg.beforeTabId) : null;
            const afterTabId = Number.isInteger(Number(msg.afterTabId)) ? Number(msg.afterTabId) : null;
            if (beforeTabId === tabId || afterTabId === tabId) return { ok: true, unchanged: true };
            let anchor = null;
            const anchorId = beforeTabId || afterTabId;
            if (anchorId) {
              try {
                anchor = await chrome2.tabs.get(anchorId);
              } catch (_) {
                return { ok: false, error: "Target tab no longer exists" };
              }
            }
            const windowId = Number.isInteger(anchor?.windowId) ? anchor.windowId : requestedWindowId;
            if (!Number.isInteger(windowId)) return { ok: false, error: "Invalid target window" };
            const groupId = Number.isInteger(anchor?.groupId) ? anchor.groupId : requestedGroupId;
            let source;
            try {
              source = await chrome2.tabs.get(tabId);
            } catch (_) {
              return { ok: false, error: "Source tab no longer exists" };
            }
            async function orderedTabs() {
              return (await chrome2.tabs.query({ windowId })).slice().sort((left, right) => Number(left.index) - Number(right.index));
            }
            function requestedIndex(tabs) {
              const current = tabs.find((tab) => tab.id === tabId);
              let target = anchorId ? tabs.find((tab) => tab.id === anchorId) : null;
              if (!target && msg.placement === "end") {
                const groupTabs = tabs.filter((tab) => Number(tab.groupId) === groupId && tab.id !== tabId);
                target = groupTabs.at(-1) || null;
              }
              if (!target) return Math.max(0, tabs.length - (current ? 1 : 0));
              const after = Boolean(afterTabId) || msg.placement === "end";
              const rawIndex = Number(target.index) + (after ? 1 : 0);
              return Math.max(0, current && Number(current.index) < rawIndex ? rawIndex - 1 : rawIndex);
            }
            async function moveUsingFreshOrder() {
              const tabs = await orderedTabs();
              await chrome2.tabs.move(tabId, { windowId, index: requestedIndex(tabs) });
            }
            async function isVerified() {
              const tabs = await orderedTabs();
              const movingIndex = tabs.findIndex((tab) => tab.id === tabId);
              if (movingIndex < 0) return false;
              if (beforeTabId) return tabs[movingIndex + 1]?.id === beforeTabId;
              if (afterTabId) return tabs[movingIndex - 1]?.id === afterTabId;
              if (msg.placement === "end") {
                const groupTabs = tabs.filter((tab) => Number(tab.groupId) === groupId);
                return groupTabs.at(-1)?.id === tabId;
              }
              return true;
            }
            if (source.windowId !== windowId) await moveUsingFreshOrder();
            const moved = await chrome2.tabs.get(tabId);
            if (Number(moved.groupId) !== groupId) {
              if (groupId >= 0) await chrome2.tabs.group({ groupId, tabIds: [tabId] });
              else if (Number(moved.groupId) >= 0) await chrome2.tabs.ungroup([tabId]);
            }
            for (let attempt = 0; attempt < 2; attempt += 1) {
              await moveUsingFreshOrder();
              if (await isVerified()) {
                const tabs = await orderedTabs();
                return { ok: true, windowId, groupId, order: tabs.map((tab) => tab.id) };
              }
            }
            return { ok: false, error: "Tab order changed before the move could be verified" };
          }
          async function group(msg = {}) {
            const groupId = await chrome2.tabs.group({ tabIds: msg.tabIds });
            const colorIdx = Math.floor(random() * GROUP_COLORS.length);
            await chrome2.tabGroups.update(groupId, { title: msg.title || "", color: msg.color || GROUP_COLORS[colorIdx] });
            return { ok: true, groupId };
          }
          async function groupByDomain(msg = {}) {
            const tabs = await chrome2.tabs.query(Number.isInteger(Number(msg.windowId)) ? { windowId: Number(msg.windowId) } : { currentWindow: true });
            const ungrouped = tabs.filter((tab) => !tab.pinned && (!tab.groupId || tab.groupId === -1) && /^(https?:|file:)/i.test(tab.url || ""));
            const domainMap = {};
            for (const tab of ungrouped) {
              const host = hostOfUrl(tab.url);
              if (host) (domainMap[host] = domainMap[host] || []).push(tab.id);
            }
            const colors = ["blue", "red", "yellow", "green", "pink", "purple", "cyan", "orange"];
            let colorIdx = 0;
            let groupedCount = 0;
            for (const domain of Object.keys(domainMap)) {
              const tabIds = domainMap[domain];
              if (tabIds.length < 2) continue;
              try {
                const groupId = await chrome2.tabs.group({ tabIds });
                await chrome2.tabGroups.update(groupId, { title: domain, color: colors[colorIdx % colors.length] });
                colorIdx += 1;
                groupedCount += 1;
              } catch (_) {
              }
            }
            return { ok: true, groups: groupedCount };
          }
          async function ungroupAll(msg = {}) {
            const tabs = await chrome2.tabs.query(Number.isInteger(Number(msg.windowId)) ? { windowId: Number(msg.windowId) } : { currentWindow: true });
            const ids = tabs.filter((tab) => tab.groupId && tab.groupId !== -1).map((tab) => tab.id);
            if (ids.length) await chrome2.tabs.ungroup(ids);
            return { ok: true, count: ids.length };
          }
          async function ungroup(msg = {}) {
            await chrome2.tabs.ungroup(msg.tabIds);
            return { ok: true };
          }
          async function addToGroup(msg = {}) {
            await chrome2.tabs.group({ groupId: msg.groupId, tabIds: msg.tabIds });
            return { ok: true };
          }
          async function updateGroup(msg = {}) {
            await chrome2.tabGroups.update(msg.groupId, { title: msg.title || "", color: msg.color || "grey" });
            return { ok: true };
          }
          async function newTab(msg = {}) {
            await chrome2.tabs.create({ url: msg.url || "about:newtab", active: msg.active !== false });
            return { ok: true };
          }
          async function getHotBookmarks() {
            const { hotBookmarks } = await chrome2.storage.local.get("hotBookmarks");
            return { bookmarks: hotBookmarks || DEFAULT_HOT_BOOKMARKS };
          }
          async function addHotBookmark(msg = {}) {
            const { hotBookmarks = [] } = await chrome2.storage.local.get("hotBookmarks");
            if (!hotBookmarks.find((bookmark) => bookmark.url === msg.bookmark.url)) {
              hotBookmarks.push(msg.bookmark);
              await chrome2.storage.local.set({ hotBookmarks });
            }
            return { ok: true, bookmarks: hotBookmarks };
          }
          async function removeHotBookmark(msg = {}) {
            const { hotBookmarks = [] } = await chrome2.storage.local.get("hotBookmarks");
            const filtered = hotBookmarks.filter((bookmark) => bookmark.url !== msg.url);
            await chrome2.storage.local.set({ hotBookmarks: filtered });
            return { ok: true, bookmarks: filtered };
          }
          async function pinAsBookmark(msg = {}) {
            const tab = await chrome2.tabs.get(msg.tabId);
            const { hotBookmarks = [] } = await chrome2.storage.local.get("hotBookmarks");
            if (!hotBookmarks.find((bookmark) => bookmark.url === tab.url)) {
              hotBookmarks.push({ title: tab.title || tab.url, url: tab.url, emoji: "\u2B50" });
              await chrome2.storage.local.set({ hotBookmarks });
            }
            return { ok: true, bookmarks: hotBookmarks };
          }
          async function openDockSite(msg = {}, sender = {}) {
            const request = msg.url && typeof msg.url === "object" ? msg.url : msg;
            const openInBackground = request.active === false;
            let url;
            try {
              url = deps.DockLogic.normalizeDockUrl(request.url);
            } catch (error) {
              return { ok: false, code: "INVALID_URL", message: error.message };
            }
            if (openInBackground) {
              const createInfo = { url, active: false };
              const senderWindowId = sender && sender.tab && sender.tab.windowId;
              if (Number.isInteger(senderWindowId)) createInfo.windowId = senderWindowId;
              try {
                const created = await chrome2.tabs.create(createInfo);
                return { ok: true, action: "created-background", tabId: created.id };
              } catch (_) {
                try {
                  const created = await chrome2.tabs.create({ url, active: false });
                  return { ok: true, action: "created-background", tabId: created.id };
                } catch (_error) {
                  return { ok: false, code: "OPEN_FAILED", message: "\u6253\u5F00\u7F51\u9875\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5" };
                }
              }
            }
            let matchingTab = null;
            try {
              const tabs = await chrome2.tabs.query({});
              matchingTab = tabs.find((tab) => {
                try {
                  return deps.DockLogic.normalizeDockUrl(tab.url) === url;
                } catch (_) {
                  return false;
                }
              }) || null;
            } catch (_) {
            }
            if (matchingTab) {
              try {
                const contentReady = await ensureContentScriptReady(matchingTab.id).catch(() => false);
                if (contentReady) await syncDockConfigToTab(matchingTab.id).catch(() => false);
                const activated = await chrome2.tabs.update(matchingTab.id, { active: true });
                const windowId = activated && Number.isInteger(activated.windowId) ? activated.windowId : matchingTab.windowId;
                if (Number.isInteger(windowId)) await chrome2.windows.update(windowId, { focused: true }).catch(() => void 0);
                return { ok: true, action: "switched", tabId: matchingTab.id };
              } catch (_) {
              }
            }
            let createError = null;
            try {
              const createInfo = { url, active: true };
              const senderWindowId = sender && sender.tab && sender.tab.windowId;
              if (Number.isInteger(senderWindowId)) createInfo.windowId = senderWindowId;
              const created = await chrome2.tabs.create(createInfo);
              return { ok: true, action: "created", tabId: created.id };
            } catch (error) {
              createError = error;
            }
            try {
              const created = await chrome2.tabs.create({ url, active: true });
              return { ok: true, action: "created", tabId: created.id };
            } catch (error) {
              createError = error;
            }
            const senderTabId = sender && sender.tab && sender.tab.id;
            if (Number.isInteger(senderTabId)) {
              try {
                const updated = await chrome2.tabs.update(senderTabId, { url, active: true });
                return { ok: true, action: "navigated", tabId: updated && updated.id };
              } catch (error) {
                createError = error;
              }
            }
            void createError;
            return { ok: false, code: "OPEN_FAILED", message: "\u6253\u5F00\u7F51\u9875\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5" };
          }
          return Object.freeze({
            defaultHotBookmarks: DEFAULT_HOT_BOOKMARKS,
            handlers: Object.freeze({
              GET_ALL_TABS: getAllTabs,
              SWITCH_TAB: switchTab,
              DUPLICATE_TAB: duplicate,
              PIN_TAB: pin,
              GET_RECENTLY_CLOSED: recentlyClosed,
              CONSUME_RECENTLY_CLOSED_ITEM: consumeRecentlyClosedItem,
              RESTORE_SESSION: restoreSession,
              MOVE_TAB: move,
              MOVE_TAB_RELATIVE: moveRelative,
              GROUP_TABS: group,
              GROUP_BY_DOMAIN: groupByDomain,
              UNGROUP_ALL: ungroupAll,
              UNGROUP_TABS: ungroup,
              ADD_TO_GROUP: addToGroup,
              UPDATE_GROUP: updateGroup,
              NEW_TAB: newTab,
              GET_HOT_BOOKMARKS: getHotBookmarks,
              ADD_HOT_BOOKMARK: addHotBookmark,
              REMOVE_HOT_BOOKMARK: removeHotBookmark,
              PIN_AS_BOOKMARK: pinAsBookmark,
              DOCK_OPEN_SITE: openDockSite
            }),
            reconcileRecentlyClosedConsumption
          });
        }
        return Object.freeze({ createTabService, DEFAULT_HOT_BOOKMARKS });
      });
    }
  });

  // src/background/dock-favicon-service.js
  var require_dock_favicon_service = __commonJS({
    "src/background/dock-favicon-service.js"(exports, module) {
      (function attachBackgroundDockFaviconService(root, factory) {
        const api = factory();
        const commonJs = typeof module === "object" && module.exports;
        if (commonJs) module.exports = api;
        if (root) root.TfaBackgroundDockFaviconService = api;
      })(typeof globalThis === "object" ? globalThis : exports, function createModule() {
        "use strict";
        const CONCURRENCY = 4;
        const GENERIC_PAGE_URL = "https://tfa-favicon-missing.invalid/";
        const MAX_RESPONSE_BYTES = 1024 * 1024;
        const FETCH_TIMEOUT_MS = 2500;
        function createDockFaviconService(deps) {
          const {
            chrome: chrome2,
            DockLogic,
            readDockConfig,
            fetchFn = fetch,
            AbortControllerClass = AbortController,
            setTimer = setTimeout,
            clearTimer = clearTimeout
          } = deps;
          let metadataActive = 0;
          const metadataWaiters = [];
          async function mapWithConcurrency(items, limit, worker) {
            const list = Array.isArray(items) ? items : [];
            const results = new Array(list.length);
            let nextIndex = 0;
            const workerCount = Math.min(list.length, Math.max(1, Number(limit) || 1));
            async function runWorker() {
              while (nextIndex < list.length) {
                const index = nextIndex;
                nextIndex += 1;
                results[index] = await worker(list[index], index);
              }
            }
            await Promise.all(Array.from({ length: workerCount }, () => runWorker()));
            return results;
          }
          async function withConcurrency(worker) {
            await new Promise((resolve2) => {
              const acquire = () => {
                metadataActive += 1;
                resolve2();
              };
              if (metadataActive < CONCURRENCY) acquire();
              else metadataWaiters.push(acquire);
            });
            try {
              return await worker();
            } finally {
              metadataActive -= 1;
              const next = metadataWaiters.shift();
              if (next) next();
            }
          }
          function requestSize(value) {
            const size = Number(value);
            return DockLogic.FAVICON_SIZE_BUCKETS.includes(size) ? size : 0;
          }
          function chromeUrl(pageUrl, size) {
            const faviconUrl = new URL(chrome2.runtime.getURL("/_favicon/"));
            faviconUrl.searchParams.set("pageUrl", pageUrl);
            faviconUrl.searchParams.set("size", String(size));
            return faviconUrl.href;
          }
          async function readBytes(pageUrl, size) {
            try {
              return await withConcurrency(async () => {
                const controller = new AbortControllerClass();
                const timeout = setTimer(() => controller.abort(), FETCH_TIMEOUT_MS);
                try {
                  const response = await fetchFn(chromeUrl(pageUrl, size), { cache: "no-store", signal: controller.signal });
                  if (!response || response.ok !== true) return null;
                  const buffer = await response.arrayBuffer();
                  if (!buffer || buffer.byteLength < 1 || buffer.byteLength > MAX_RESPONSE_BYTES) return null;
                  return new Uint8Array(buffer);
                } finally {
                  clearTimer(timeout);
                }
              });
            } catch (_) {
              return null;
            }
          }
          function genericBytes(size, bytesBySize) {
            let pending = bytesBySize.get(size);
            if (pending) return pending;
            pending = readBytes(GENERIC_PAGE_URL, size);
            bytesBySize.set(size, pending);
            pending.then((bytes) => {
              if (!bytes && bytesBySize.get(size) === pending) bytesBySize.delete(size);
            });
            return pending;
          }
          function sameBytes(first, second) {
            if (!first || !second || first.byteLength !== second.byteLength) return false;
            for (let index = 0; index < first.byteLength; index += 1) if (first[index] !== second[index]) return false;
            return true;
          }
          async function hasDistinct(pageUrl, size, genericBytesBySize) {
            const [candidate, generic] = await Promise.all([readBytes(pageUrl, size), genericBytes(size, genericBytesBySize)]);
            return Boolean(candidate && generic && !sameBytes(candidate, generic));
          }
          function pageOrigin(value) {
            try {
              const parsed = new URL(value);
              return parsed.protocol === "http:" || parsed.protocol === "https:" ? parsed.origin : "";
            } catch (_) {
              return "";
            }
          }
          function isVector(value) {
            return /^data:image\/svg\+xml(?:;[^,]*)?,/i.test(value) || /\.svg(?:$|[?#])/i.test(value);
          }
          function pageCandidates(tab, response) {
            if (!tab || !response || response.ok !== true || !Array.isArray(response.icons)) return [];
            let pageUrl = "";
            try {
              pageUrl = DockLogic.normalizeDockUrl(response.pageUrl);
            } catch (_) {
              return [];
            }
            let tabLocation;
            let pageLocation;
            try {
              tabLocation = new URL(tab.normalizedUrl);
              pageLocation = new URL(pageUrl);
            } catch (_) {
              return [];
            }
            const responseOrigin = String(response.origin || "");
            const sameWebOrigin = ["http:", "https:"].includes(tabLocation.protocol) && pageLocation.origin === tabLocation.origin && responseOrigin === pageLocation.origin;
            const exactFilePage = tabLocation.protocol === "file:" && pageLocation.protocol === "file:" && pageUrl === tab.normalizedUrl && responseOrigin === "null";
            if (!sameWebOrigin && !exactFilePage) return [];
            const allowedSources = /* @__PURE__ */ new Set(["page-svg", "page-touch", "page-icon"]);
            const sanitized = [];
            for (const rawCandidate of response.icons.slice(0, 12)) {
              if (!rawCandidate || !allowedSources.has(rawCandidate.source)) continue;
              const url = DockLogic.safeEphemeralDockIconUrl(rawCandidate.url, pageUrl);
              if (!url) continue;
              const numericSize = Number(rawCandidate.declaredSize);
              sanitized.push({
                url,
                source: rawCandidate.source,
                vector: rawCandidate.source === "page-svg",
                declaredSize: Number.isFinite(numericSize) ? Math.max(0, numericSize) : 0,
                verified: true
              });
            }
            sanitized.sort(DockLogic.compareDockIconCandidates);
            return ["page-svg", "page-touch", "page-icon"].map((source) => sanitized.find((candidate) => candidate.source === source)).filter(Boolean);
          }
          async function resolve(msg = {}) {
            const requested = Array.isArray(msg.urls) ? msg.urls.slice(0, 64) : [];
            const [queriedTabs, storedConfig] = await Promise.all([
              chrome2.tabs.query({}).catch(() => []),
              readDockConfig().catch(() => DockLogic.normalizeDockConfig())
            ]);
            const tabsByUrl = /* @__PURE__ */ new Map();
            const tabsByOrigin = /* @__PURE__ */ new Map();
            for (const rawTab of queriedTabs) {
              try {
                const normalizedUrl = DockLogic.normalizeDockUrl(rawTab && rawTab.url);
                const tab = { ...rawTab, normalizedUrl };
                if (!tabsByUrl.has(normalizedUrl)) tabsByUrl.set(normalizedUrl, tab);
                const origin = pageOrigin(normalizedUrl);
                if (origin && !tabsByOrigin.has(origin)) tabsByOrigin.set(origin, tab);
              } catch (_) {
              }
            }
            const savedIconsByUrl = /* @__PURE__ */ new Map();
            for (const site of storedConfig.sites) if (site.iconUrl) savedIconsByUrl.set(site.url, site.iconUrl);
            const resolutions = requested.map((rawUrl) => {
              let url = "";
              try {
                url = DockLogic.normalizeDockUrl(rawUrl);
              } catch (_) {
                return { url: "", match: "none", tabStatus: "", candidates: [], matchedTab: null };
              }
              const exactTab = tabsByUrl.get(url) || null;
              const sameOriginTab = exactTab ? null : tabsByOrigin.get(pageOrigin(url)) || null;
              const matchedTab = exactTab || sameOriginTab;
              return {
                url,
                match: exactTab ? "exact" : sameOriginTab ? "same-origin" : "none",
                tabStatus: matchedTab && (matchedTab.status === "loading" || matchedTab.status === "complete") ? matchedTab.status : "",
                candidates: [],
                matchedTab
              };
            });
            const metadataTabs = [];
            const seenTabIds = /* @__PURE__ */ new Set();
            for (const resolution of resolutions) {
              const tab = resolution.matchedTab;
              if (!tab || !Number.isInteger(tab.id) || seenTabIds.has(tab.id)) continue;
              seenTabIds.add(tab.id);
              metadataTabs.push(tab);
              if (metadataTabs.length === 16) break;
            }
            const faviconSize = requestSize(msg.faviconSize);
            const genericBySize = /* @__PURE__ */ new Map();
            const [metadataResults, verifications] = await Promise.all([
              mapWithConcurrency(metadataTabs, CONCURRENCY, (tab) => withConcurrency(async () => {
                try {
                  const response = await chrome2.tabs.sendMessage(tab.id, { type: "DOCK_GET_PAGE_ICONS" }, { frameId: 0 });
                  return pageCandidates(tab, response);
                } catch (_) {
                  return [];
                }
              })),
              faviconSize ? mapWithConcurrency(resolutions, CONCURRENCY, (resolution) => resolution.url ? hasDistinct(resolution.url, faviconSize, genericBySize) : false) : Promise.resolve([])
            ]);
            const pageCandidatesByTabId = new Map(metadataTabs.map((tab, index) => [tab.id, metadataResults[index] || []]));
            const icons = resolutions.map((resolution, index) => {
              const candidates = [];
              if (resolution.matchedTab) candidates.push(...pageCandidatesByTabId.get(resolution.matchedTab.id) || []);
              const savedUrl = savedIconsByUrl.get(resolution.url) || "";
              if (savedUrl) candidates.push({ url: savedUrl, source: "saved", vector: isVector(savedUrl), declaredSize: 0, verified: true });
              if (resolution.matchedTab) {
                const tabUrl = DockLogic.safeEphemeralDockIconUrl(resolution.matchedTab.favIconUrl, resolution.matchedTab.normalizedUrl);
                if (tabUrl) candidates.push({ url: tabUrl, source: "tab", vector: isVector(tabUrl), declaredSize: 0, verified: true });
              }
              const seenUrls = /* @__PURE__ */ new Set();
              const uniqueCandidates = candidates.sort(DockLogic.compareDockIconCandidates).filter((candidate) => {
                if (seenUrls.has(candidate.url)) return false;
                seenUrls.add(candidate.url);
                return true;
              });
              const icon = { url: resolution.url, match: resolution.match, tabStatus: resolution.tabStatus, candidates: uniqueCandidates };
              if (faviconSize) icon.chromeFaviconVerified = verifications[index] === true;
              return icon;
            });
            return { ok: true, icons };
          }
          return Object.freeze({
            handlers: Object.freeze({ DOCK_RESOLVE_FAVICONS: resolve }),
            get activeCount() {
              return metadataActive;
            }
          });
        }
        return Object.freeze({ createDockFaviconService, CONCURRENCY, FETCH_TIMEOUT_MS, MAX_RESPONSE_BYTES });
      });
    }
  });

  // src/background/dock-search-service.js
  var require_dock_search_service = __commonJS({
    "src/background/dock-search-service.js"(exports, module) {
      (function attachBackgroundDockSearchService(root, factory) {
        const api = factory();
        const commonJs = typeof module === "object" && module.exports;
        if (commonJs) module.exports = api;
        if (root) root.TfaBackgroundDockSearchService = api;
      })(typeof globalThis === "object" ? globalThis : exports, function createModule() {
        "use strict";
        const RECENTS_KEY = "tfa_web_dock_search_recents_v1";
        const ENGINES = Object.freeze({
          google: { base: "https://www.google.com/search", param: "q" },
          bing: { base: "https://www.bing.com/search", param: "q" },
          baidu: { base: "https://www.baidu.com/s", param: "wd" },
          yandex: { base: "https://yandex.com/search/", param: "text" }
        });
        const OPTIONAL_PERMISSIONS = Object.freeze(["bookmarks", "history"]);
        const PERMISSION_DECISION_KEY = "tfa_optional_permission_decisions_v1";
        const PERMISSION_DECISION_VERSION = 1;
        const PERMISSION_COPY = Object.freeze({
          BOOKMARKS_PERMISSION_REQUIRED: Object.freeze({ zh: "\u9700\u8981\u4E66\u7B7E\u6743\u9650\u624D\u80FD\u4ECE\u4E66\u7B7E\u5BFC\u5165\uFF1B\u53EF\u7A0D\u540E\u91CD\u8BD5\u3002", en: "Bookmark access is required to import bookmarks. You can try again later." }),
          BOOKMARKS_PERMISSION_DENIED: Object.freeze({ zh: "\u672A\u83B7\u5F97\u4E66\u7B7E\u6743\u9650\uFF1B\u672A\u5BFC\u5165\u4EFB\u4F55\u4E66\u7B7E\uFF0C\u53EF\u7A0D\u540E\u91CD\u8BD5\u3002", en: "Bookmark access was not granted. Nothing was imported, and you can try again later." }),
          HISTORY_PERMISSION_REQUIRED: Object.freeze({ zh: "\u9700\u8981\u5386\u53F2\u8BB0\u5F55\u6743\u9650\u624D\u80FD\u663E\u793A\u5386\u53F2\u7ED3\u679C\uFF1B\u5F53\u524D\u9875\u7B7E\u641C\u7D22\u4ECD\u53EF\u7528\u3002", en: "History access is required to show history results. Current-tab search still works." }),
          HISTORY_PERMISSION_DENIED: Object.freeze({ zh: "\u672A\u83B7\u5F97\u5386\u53F2\u8BB0\u5F55\u6743\u9650\uFF1B\u4ECD\u53EF\u641C\u7D22\u5F53\u524D\u9875\u7B7E\uFF0C\u53EF\u7A0D\u540E\u91CD\u8BD5\u3002", en: "History access was not granted. Current-tab search still works, and you can try again later." }),
          OPTIONAL_PERMISSION_UNSUPPORTED: Object.freeze({ zh: "\u5F53\u524D\u6D4F\u89C8\u5668\u4E0D\u652F\u6301\u6309\u9700\u7533\u8BF7\u6B64\u6743\u9650\u3002", en: "This browser does not support requesting this permission on demand." })
        });
        function createOptionalPermissionGate(chromeApi) {
          const attempted = /* @__PURE__ */ new Set();
          let deniedPermissions = null;
          let deniedPermissionsRead = null;
          let decisionWork = Promise.resolve();
          const decisionGenerations = new Map(OPTIONAL_PERMISSIONS.map((permission) => [permission, 0]));
          function assertPermission(permission) {
            if (!OPTIONAL_PERMISSIONS.includes(permission)) {
              throw new TypeError(`Unsupported optional permission: ${permission}`);
            }
          }
          async function readDeniedPermissions() {
            if (deniedPermissions) return deniedPermissions;
            if (!deniedPermissionsRead) {
              deniedPermissionsRead = (async () => {
                let stored = null;
                try {
                  const snapshot = await chromeApi?.storage?.local?.get?.(PERMISSION_DECISION_KEY);
                  stored = snapshot?.[PERMISSION_DECISION_KEY];
                } catch (_) {
                }
                const values = stored?.version === PERMISSION_DECISION_VERSION && Array.isArray(stored.denied) ? stored.denied.filter((permission) => OPTIONAL_PERMISSIONS.includes(permission)) : [];
                deniedPermissions = new Set(values);
                return deniedPermissions;
              })();
            }
            return deniedPermissionsRead;
          }
          function decisionGeneration(permission) {
            return decisionGenerations.get(permission) || 0;
          }
          function advanceDecisionGenerations(permissions) {
            for (const permission of permissions) {
              decisionGenerations.set(permission, decisionGeneration(permission) + 1);
            }
          }
          function updateDeniedPermissions(updates, expectedGenerations = null) {
            const operation = decisionWork.then(async () => {
              const denied = await readDeniedPermissions();
              let changed = false;
              let applied = true;
              for (const [permission, value] of updates) {
                assertPermission(permission);
                if (expectedGenerations && decisionGeneration(permission) !== expectedGenerations.get(permission)) {
                  applied = false;
                  continue;
                }
                if (value && !denied.has(permission)) {
                  denied.add(permission);
                  changed = true;
                } else if (!value && denied.delete(permission)) {
                  changed = true;
                }
              }
              if (!changed) return applied;
              const record = {
                version: PERMISSION_DECISION_VERSION,
                denied: OPTIONAL_PERMISSIONS.filter((permission) => denied.has(permission))
              };
              try {
                await chromeApi?.storage?.local?.set?.({ [PERMISSION_DECISION_KEY]: record });
              } catch (_) {
              }
              return applied;
            });
            decisionWork = operation.catch(() => void 0);
            return operation;
          }
          async function wasDenied(permission) {
            assertPermission(permission);
            await decisionWork;
            return (await readDeniedPermissions()).has(permission);
          }
          function response(permission, code, granted = false) {
            const copy = PERMISSION_COPY[code] || PERMISSION_COPY.OPTIONAL_PERMISSION_UNSUPPORTED;
            return Object.freeze({
              ok: granted,
              granted,
              permission,
              code: granted ? "" : code,
              recoverable: code !== "OPTIONAL_PERMISSION_UNSUPPORTED",
              messageKey: granted ? "" : `permission.${permission}.${code === "OPTIONAL_PERMISSION_UNSUPPORTED" ? "unsupported" : code.endsWith("_DENIED") ? "denied" : "required"}`,
              message: granted ? "" : copy.zh,
              messageEn: granted ? "" : copy.en
            });
          }
          async function ensure(permission, { request: request2 = false, once = false } = {}) {
            assertPermission(permission);
            const api = chromeApi?.permissions;
            if (!api || typeof api.contains !== "function" || typeof api.request !== "function") {
              return response(permission, "OPTIONAL_PERMISSION_UNSUPPORTED");
            }
            const containsGeneration = decisionGeneration(permission);
            try {
              const granted = await api.contains({ permissions: [permission] });
              if (containsGeneration !== decisionGeneration(permission)) {
                return ensure(permission);
              }
              if (granted) {
                const applied = await updateDeniedPermissions(
                  [[permission, false]],
                  /* @__PURE__ */ new Map([[permission, containsGeneration]])
                );
                if (!applied) return ensure(permission);
                return response(permission, "", true);
              }
            } catch (_) {
              return response(permission, "OPTIONAL_PERMISSION_UNSUPPORTED");
            }
            const deniedCode = `${permission.toUpperCase()}_PERMISSION_DENIED`;
            const requiredCode = `${permission.toUpperCase()}_PERMISSION_REQUIRED`;
            if (!request2 || once && attempted.has(permission)) {
              return response(permission, await wasDenied(permission) || once && attempted.has(permission) ? deniedCode : requiredCode);
            }
            attempted.add(permission);
            const expectedGeneration = decisionGeneration(permission);
            try {
              const granted = await api.request({ permissions: [permission] });
              const applied = await updateDeniedPermissions(
                [[permission, !granted]],
                /* @__PURE__ */ new Map([[permission, expectedGeneration]])
              );
              if (!applied) return ensure(permission);
              return granted ? response(permission, "", true) : response(permission, deniedCode);
            } catch (_) {
              const applied = await updateDeniedPermissions(
                [[permission, true]],
                /* @__PURE__ */ new Map([[permission, expectedGeneration]])
              );
              if (!applied) return ensure(permission);
              return response(permission, deniedCode);
            }
          }
          async function request(permission) {
            assertPermission(permission);
            const api = chromeApi?.permissions;
            if (!api || typeof api.request !== "function") {
              return response(permission, "OPTIONAL_PERMISSION_UNSUPPORTED");
            }
            attempted.add(permission);
            const expectedGeneration = decisionGeneration(permission);
            const deniedCode = `${permission.toUpperCase()}_PERMISSION_DENIED`;
            try {
              const decision = api.request({ permissions: [permission] });
              const granted = await decision;
              const applied = await updateDeniedPermissions(
                [[permission, !granted]],
                /* @__PURE__ */ new Map([[permission, expectedGeneration]])
              );
              if (!applied) return ensure(permission);
              return granted ? response(permission, "", true) : response(permission, deniedCode);
            } catch (_) {
              const applied = await updateDeniedPermissions(
                [[permission, true]],
                /* @__PURE__ */ new Map([[permission, expectedGeneration]])
              );
              if (!applied) return ensure(permission);
              return response(permission, deniedCode);
            }
          }
          async function syncDecision(permission) {
            assertPermission(permission);
            const api = chromeApi?.permissions;
            const expectedGeneration = decisionGeneration(permission);
            let granted = false;
            try {
              granted = await api?.contains?.({ permissions: [permission] }) === true;
            } catch (_) {
            }
            if (granted) {
              const applied2 = await updateDeniedPermissions(
                [[permission, false]],
                /* @__PURE__ */ new Map([[permission, expectedGeneration]])
              );
              if (!applied2) return ensure(permission);
              return response(permission, "", true);
            }
            const applied = await updateDeniedPermissions(
              [[permission, true]],
              /* @__PURE__ */ new Map([[permission, expectedGeneration]])
            );
            if (!applied) return ensure(permission);
            return response(permission, `${permission.toUpperCase()}_PERMISSION_DENIED`);
          }
          async function clearDecisions(permissions) {
            const valid = [...new Set(Array.isArray(permissions) ? permissions : [])].filter((permission) => OPTIONAL_PERMISSIONS.includes(permission));
            if (!valid.length) return;
            advanceDecisionGenerations(valid);
            await updateDeniedPermissions(valid.map((permission) => [permission, false]));
          }
          return Object.freeze({ ensure, request, syncDecision, clearDecisions });
        }
        function createDockSearchService(deps) {
          const { chrome: chrome2, DockLogic, now = Date.now } = deps;
          const permissionGate = deps.permissionGate || createOptionalPermissionGate(chrome2);
          function flattenBookmarks(nodes, parentTitles = [], output = []) {
            for (const node of Array.isArray(nodes) ? nodes : []) {
              const title = node && node.title != null ? String(node.title).trim() : "";
              const path = title ? parentTitles.concat(title) : parentTitles;
              if (node && typeof node.url === "string" && /^https?:\/\//i.test(node.url)) {
                try {
                  output.push({
                    id: node.id == null ? "" : String(node.id),
                    title: title || new URL(node.url).hostname,
                    url: DockLogic.normalizeDockUrl(node.url),
                    path: parentTitles.filter(Boolean).join(" / ")
                  });
                } catch (_) {
                }
              }
              if (node && Array.isArray(node.children)) flattenBookmarks(node.children, path, output);
            }
            return output;
          }
          async function getBookmarks(msg = {}) {
            const permission = await permissionGate.ensure("bookmarks");
            if (!permission.granted) return { ...permission, bookmarks: [] };
            if (!chrome2.bookmarks || typeof chrome2.bookmarks.getTree !== "function") {
              return { ok: false, code: "BOOKMARKS_UNAVAILABLE", message: "\u5F53\u524D\u6D4F\u89C8\u5668\u65E0\u6CD5\u8BFB\u53D6\u4E66\u7B7E", bookmarks: [] };
            }
            try {
              const tree = await chrome2.bookmarks.getTree();
              const query = msg.query == null ? "" : String(msg.query).trim().toLocaleLowerCase();
              const requestedLimit = Number(msg.limit);
              const limit = Number.isFinite(requestedLimit) ? Math.min(1e3, Math.max(1, Math.trunc(requestedLimit))) : 500;
              const seenUrls = /* @__PURE__ */ new Set();
              const bookmarks = flattenBookmarks(tree).filter((bookmark) => {
                if (seenUrls.has(bookmark.url)) return false;
                seenUrls.add(bookmark.url);
                if (!query) return true;
                return `${bookmark.title} ${bookmark.url} ${bookmark.path}`.toLocaleLowerCase().includes(query);
              }).slice(0, limit);
              return { ok: true, bookmarks };
            } catch (_) {
              return { ok: false, code: "BOOKMARKS_READ_FAILED", message: "\u8BFB\u53D6 Chrome \u4E66\u7B7E\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5", bookmarks: [] };
            }
          }
          function normalizeQuery(value) {
            return String(value == null ? "" : value).trim().replace(/\s+/g, " ").slice(0, 500);
          }
          function buildSearchUrl(engine, query) {
            const normalized = normalizeQuery(query);
            const descriptor = ENGINES[engine];
            if (!descriptor || !normalized) return "";
            const url = new URL(descriptor.base);
            url.searchParams.set(descriptor.param, normalized);
            return url.href;
          }
          function normalizeUrl(value) {
            try {
              const parsed = new URL(String(value || ""));
              if (!["http:", "https:"].includes(parsed.protocol)) return "";
              parsed.hash = "";
              return parsed.href;
            } catch (_) {
              return "";
            }
          }
          function normalizeMatchText(value) {
            return String(value == null ? "" : value).normalize("NFKC").toLocaleLowerCase().replace(/\s+/g, " ").trim();
          }
          function readableUrlText(value) {
            try {
              const parsed = new URL(String(value || ""));
              const pathParts = parsed.pathname.split("/").map((part) => {
                try {
                  return decodeURIComponent(part.replace(/\+/g, " "));
                } catch (_) {
                  return part;
                }
              }).filter((part) => part && !/^\d{7,}$/.test(part) && !/^[a-f\d-]{12,}$/i.test(part) && !/^[a-z\d_-]{24,}$/i.test(part));
              const searchKeys = /* @__PURE__ */ new Set(["q", "query", "wd", "text", "search", "keyword", "keywords", "search_query"]);
              const searchTerms = [];
              parsed.searchParams.forEach((parameterValue, key) => {
                if (searchKeys.has(key.toLocaleLowerCase()) && parameterValue.length <= 240) searchTerms.push(parameterValue);
              });
              return `${parsed.hostname.replace(/^www\./i, "")} ${pathParts.join(" ")} ${searchTerms.join(" ")}`;
            } catch (_) {
              return "";
            }
          }
          function candidateText(candidate) {
            return normalizeMatchText(`${candidate && candidate.title || ""} ${readableUrlText(candidate && candidate.url)}`);
          }
          function fuzzyScore(candidate, query) {
            const text = candidateText(candidate);
            const needle = normalizeMatchText(query);
            if (!text || !needle) return 0;
            const directIndex = text.indexOf(needle);
            if (directIndex >= 0) return 1e3 - Math.min(300, directIndex);
            const compactText = text.replace(/[^\p{L}\p{N}]+/gu, "");
            const compactNeedle = needle.replace(/[^\p{L}\p{N}]+/gu, "");
            if (!compactNeedle) return 0;
            const compactIndex = compactText.indexOf(compactNeedle);
            if (compactIndex >= 0) return 850 - Math.min(250, compactIndex);
            if (/^\d+$/.test(compactNeedle) || Array.from(compactNeedle).length <= 2) return 0;
            const tokens = needle.split(/[^\p{L}\p{N}]+/u).filter(Boolean);
            if (tokens.length > 1 && tokens.every((token) => text.includes(token))) return 700 + tokens.length;
            let cursor = 0;
            let firstIndex = -1;
            for (const character of compactNeedle) {
              const foundAt = compactText.indexOf(character, cursor);
              if (foundAt < 0) return 0;
              if (firstIndex < 0) firstIndex = foundAt;
              cursor = foundAt + 1;
            }
            const span = cursor - Math.max(0, firstIndex);
            const maximumSpan = Math.max(compactNeedle.length + 6, Math.ceil(compactNeedle.length * 2.5));
            if (span > maximumSpan) return 0;
            return Math.max(1, 520 - Math.min(360, span - compactNeedle.length));
          }
          function rank(items, query, compareTies) {
            return (Array.isArray(items) ? items : []).map((item) => ({ item, score: fuzzyScore(item, query) })).filter((entry) => entry.score > 0 && normalizeUrl(entry.item && entry.item.url)).sort((a, b) => b.score - a.score || (compareTies ? compareTies(a.item, b.item) : 0)).map((entry) => entry.item);
          }
          async function getSearchPermissions() {
            const [bookmarks, history] = await Promise.all([
              permissionGate.ensure("bookmarks"),
              permissionGate.ensure("history")
            ]);
            return {
              ok: true,
              permissions: Object.freeze({ bookmarks, history })
            };
          }
          async function suggest(msg = {}, sender = {}) {
            const query = normalizeQuery(msg.query);
            const requestId = Number(msg.requestId) || 0;
            const requestedLimit = Number(msg.limit);
            const limit = Number.isFinite(requestedLimit) ? Math.min(16, Math.max(1, Math.trunc(requestedLimit))) : 10;
            const permissionState = await getSearchPermissions();
            const { bookmarks: bookmarksPermission, history: historyPermission } = permissionState.permissions;
            if (!query) {
              return {
                ok: true,
                requestId,
                suggestions: [],
                permissions: permissionState.permissions
              };
            }
            const [tabsResult, bookmarksResult, historyResult] = await Promise.allSettled([
              chrome2.tabs.query({}),
              bookmarksPermission.granted && chrome2.bookmarks && chrome2.bookmarks.getTree ? chrome2.bookmarks.getTree() : [],
              historyPermission.granted && chrome2.history && chrome2.history.search ? chrome2.history.search({ text: "", startTime: now() - 7 * 24 * 60 * 60 * 1e3, maxResults: 500 }) : []
            ]);
            const senderWindowId = sender && sender.tab && sender.tab.windowId;
            const tabs = tabsResult.status === "fulfilled" ? tabsResult.value : [];
            const bookmarks = bookmarksResult.status === "fulfilled" ? flattenBookmarks(bookmarksResult.value) : [];
            const historyItems = historyResult.status === "fulfilled" ? historyResult.value : [];
            const seen = /* @__PURE__ */ new Set();
            const sourceNames = ["tab", "bookmark", "browser"];
            const sourceBuckets = Object.fromEntries(sourceNames.map((source) => [source, []]));
            const add = (source, item) => {
              const url = normalizeUrl(item && item.url);
              if (!url || seen.has(url)) return;
              seen.add(url);
              sourceBuckets[source].push({
                source,
                title: String(item.title || new URL(url).hostname || url).trim(),
                url,
                tabId: source === "tab" && Number.isInteger(item.id) ? item.id : void 0,
                score: fuzzyScore(item, query),
                lastVisitTime: Number(item && item.lastVisitTime) || 0
              });
            };
            rank(tabs, query, (a, b) => Number(b.windowId === senderWindowId) - Number(a.windowId === senderWindowId) || Number(Boolean(b.active)) - Number(Boolean(a.active))).slice(0, 8).forEach((tab) => add("tab", tab));
            rank(bookmarks, query).slice(0, 8).forEach((bookmark) => add("bookmark", bookmark));
            rank(historyItems, query, (a, b) => (Number(b && b.lastVisitTime) || 0) - (Number(a && a.lastVisitTime) || 0)).slice(0, 12).forEach((item) => add("browser", item));
            const sourceCounts = Object.fromEntries(sourceNames.map((source) => [source, 0]));
            let remaining = limit;
            while (remaining > 0) {
              let allocated = false;
              for (const source of sourceNames) {
                if (remaining < 1) break;
                if (sourceCounts[source] >= sourceBuckets[source].length) continue;
                sourceCounts[source] += 1;
                remaining -= 1;
                allocated = true;
              }
              if (!allocated) break;
            }
            const ranked = sourceNames.flatMap((source) => sourceBuckets[source].slice(0, sourceCounts[source]).sort((a, b) => b.score - a.score || b.lastVisitTime - a.lastVisitTime)).map(({ score, lastVisitTime, ...suggestion }) => suggestion);
            return {
              ok: true,
              requestId,
              suggestions: ranked,
              permissions: Object.freeze({
                history: historyPermission,
                bookmarks: bookmarksPermission
              }),
              ...!historyPermission.granted ? {
                code: historyPermission.code,
                recoverable: historyPermission.recoverable,
                messageKey: historyPermission.messageKey,
                message: historyPermission.message,
                messageEn: historyPermission.messageEn
              } : {}
            };
          }
          async function requestHistoryPermission() {
            return typeof permissionGate.request === "function" ? permissionGate.request("history") : permissionGate.ensure("history", { request: true });
          }
          async function requestBookmarksPermission() {
            return typeof permissionGate.request === "function" ? permissionGate.request("bookmarks") : permissionGate.ensure("bookmarks", { request: true });
          }
          async function syncPermissionDecision(permission) {
            if (!OPTIONAL_PERMISSIONS.includes(permission)) {
              throw new TypeError(`Unsupported optional permission: ${permission}`);
            }
            if (typeof permissionGate.syncDecision === "function") {
              return permissionGate.syncDecision(permission);
            }
            return permissionGate.ensure(permission);
          }
          async function clearPermissionDecisions(permissions) {
            if (typeof permissionGate.clearDecisions === "function") {
              await permissionGate.clearDecisions(permissions);
            }
          }
          async function getRecents() {
            try {
              const stored = await chrome2.storage.local.get(RECENTS_KEY);
              const recents = Array.isArray(stored[RECENTS_KEY]) ? stored[RECENTS_KEY].filter((item) => normalizeQuery(item && item.query)).slice(0, 10) : [];
              return { ok: true, recents };
            } catch (_) {
              return { ok: true, recents: [] };
            }
          }
          async function open(msg = {}, sender = {}) {
            const query = normalizeQuery(msg.query);
            const engine = Object.prototype.hasOwnProperty.call(ENGINES, msg.engine) ? msg.engine : "google";
            const url = buildSearchUrl(engine, query);
            if (!url) return { ok: false, code: "EMPTY_QUERY", message: "\u8BF7\u8F93\u5165\u641C\u7D22\u5185\u5BB9" };
            try {
              const stored = await chrome2.storage.local.get(RECENTS_KEY);
              const previous = Array.isArray(stored[RECENTS_KEY]) ? stored[RECENTS_KEY] : [];
              const key = query.toLocaleLowerCase();
              const recents = [{ query, engine, searchedAt: now() }].concat(previous.filter((item) => normalizeQuery(item && item.query).toLocaleLowerCase() !== key)).slice(0, 10);
              await chrome2.storage.local.set({ [RECENTS_KEY]: recents });
            } catch (_) {
            }
            const createInfo = { url, active: true };
            const senderWindowId = sender && sender.tab && sender.tab.windowId;
            if (Number.isInteger(senderWindowId)) createInfo.windowId = senderWindowId;
            try {
              const tab = await chrome2.tabs.create(createInfo);
              return { ok: true, tabId: tab && tab.id, url };
            } catch (_) {
              try {
                const tab = await chrome2.tabs.create({ url, active: true });
                return { ok: true, tabId: tab && tab.id, url };
              } catch (error) {
                return { ok: false, code: "OPEN_FAILED", message: error && error.message ? error.message : "\u6253\u5F00\u641C\u7D22\u9875\u5931\u8D25" };
              }
            }
          }
          return Object.freeze({
            flattenBookmarks,
            normalizeQuery,
            fuzzyScore,
            syncPermissionDecision,
            clearPermissionDecisions,
            handlers: Object.freeze({
              DOCK_GET_BOOKMARKS: getBookmarks,
              DOCK_REQUEST_BOOKMARKS_PERMISSION: requestBookmarksPermission,
              DOCK_REQUEST_HISTORY_PERMISSION: requestHistoryPermission,
              DOCK_SEARCH_GET_PERMISSIONS: getSearchPermissions,
              DOCK_SEARCH_SUGGEST: suggest,
              DOCK_SEARCH_GET_RECENTS: getRecents,
              DOCK_SEARCH_OPEN: open
            })
          });
        }
        return Object.freeze({
          createDockSearchService,
          createOptionalPermissionGate,
          OPTIONAL_PERMISSIONS,
          PERMISSION_COPY,
          PERMISSION_DECISION_KEY,
          PERMISSION_DECISION_VERSION,
          RECENTS_KEY,
          ENGINES
        });
      });
    }
  });

  // src/background/dock-service.js
  var require_dock_service = __commonJS({
    "src/background/dock-service.js"(exports, module) {
      (function attachBackgroundDockService(root, factory) {
        const api = factory();
        const commonJs = typeof module === "object" && module.exports;
        if (commonJs) module.exports = api;
        if (root) root.TfaBackgroundDockService = api;
      })(typeof globalThis === "object" ? globalThis : exports, function createModule() {
        "use strict";
        function createDockService(deps) {
          const { chrome: chrome2, DockLogic, readDockConfig, queueDockMutation, runDockInput, inputErrorSymbol } = deps;
          function mutationFailure(error) {
            const inputError = error && error[inputErrorSymbol] ? error.cause : null;
            if (inputError && inputError.code === "DUPLICATE_URL") {
              return { ok: false, code: "DUPLICATE_URL", message: "\u8BE5\u7F51\u9875\u5DF2\u56FA\u5B9A" };
            }
            if (inputError && inputError.name === "TypeError") {
              return { ok: false, code: "INVALID_URL", message: inputError.message };
            }
            return { ok: false, code: "STORAGE_ERROR", message: "\u4FDD\u5B58\u7F51\u9875\u62D3\u5C55\u575E\u8BBE\u7F6E\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5" };
          }
          async function getConfig() {
            try {
              return { ok: true, config: await readDockConfig() };
            } catch (_) {
              return { ok: false, code: "STORAGE_ERROR", message: "\u8BFB\u53D6\u7F51\u9875\u62D3\u5C55\u575E\u8BBE\u7F6E\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5" };
            }
          }
          async function getTabZoom(msg = {}, sender = {}) {
            void msg;
            const tabId = sender && sender.tab && sender.tab.id;
            if (!Number.isInteger(tabId) || tabId < 0) return { ok: true, zoom: 1 };
            try {
              const zoom = await chrome2.tabs.getZoom(tabId);
              return { ok: true, zoom: Number.isFinite(zoom) && zoom > 0 ? zoom : 1 };
            } catch (_) {
              return { ok: true, zoom: 1 };
            }
          }
          async function updateSettings(msg = {}) {
            try {
              const patch = msg.patch && typeof msg.patch === "object" ? msg.patch : {};
              const result = await queueDockMutation((current) => {
                const next = { ...current, motion: { ...current.motion }, appearance: { ...current.appearance } };
                if (Object.prototype.hasOwnProperty.call(patch, "enabled")) {
                  next.enabled = patch.enabled;
                  next.enabledUserSet = true;
                }
                if (Object.prototype.hasOwnProperty.call(patch, "autoHide")) next.autoHide = patch.autoHide;
                if (patch.motion && typeof patch.motion === "object") next.motion = { ...next.motion, ...patch.motion };
                if (patch.appearance && typeof patch.appearance === "object") {
                  for (const key of Object.keys(DockLogic.APPEARANCE_LIMITS)) {
                    if (Object.prototype.hasOwnProperty.call(patch.appearance, key)) next.appearance[key] = patch.appearance[key];
                  }
                  if (Object.prototype.hasOwnProperty.call(patch.appearance, "tileColor")) next.appearance.tileColor = patch.appearance.tileColor;
                }
                return { config: next };
              });
              return { ok: true, config: result.config };
            } catch (error) {
              return mutationFailure(error);
            }
          }
          async function upsertSite(msg = {}) {
            try {
              const result = await queueDockMutation((current) => {
                const submitted = msg.site && typeof msg.site === "object" ? msg.site : {};
                const submittedUrl = runDockInput(() => DockLogic.normalizeDockUrl(submitted.url));
                const matchingUrlSite = current.sites.find((site) => site.url === submittedUrl) || null;
                const matchingIdSite = submitted.id == null ? null : current.sites.find((site) => site.id === String(submitted.id).trim()) || null;
                if (matchingUrlSite && matchingUrlSite.id !== (matchingIdSite && matchingIdSite.id)) {
                  if (!matchingIdSite) {
                    const replacement = { ...matchingUrlSite, ...submitted, id: matchingUrlSite.id, url: submittedUrl };
                    return {
                      config: runDockInput(() => DockLogic.upsertDockSite(current, replacement)),
                      action: "updated-existing",
                      existing: true,
                      siteId: matchingUrlSite.id
                    };
                  }
                  return { config: current, action: "already-exists", existing: true, siteId: matchingUrlSite.id };
                }
                return {
                  config: runDockInput(() => DockLogic.upsertDockSite(current, submitted)),
                  action: matchingIdSite ? "updated" : "created",
                  existing: false
                };
              });
              const duplicate = result.existing === true;
              return {
                ok: true,
                ...duplicate ? { code: "ALREADY_EXISTS", message: result.action === "updated-existing" ? "\u8BE5\u7F51\u9875\u5DF2\u5728\u62D3\u5C55\u575E\u4E2D\uFF0C\u5DF2\u66F4\u65B0\u73B0\u6709\u9879\u76EE" : "\u8BE5\u7F51\u9875\u5DF2\u5728\u62D3\u5C55\u575E\u4E2D" } : {},
                action: result.action,
                existing: duplicate,
                ...result.siteId ? { siteId: result.siteId } : {},
                config: result.config
              };
            } catch (error) {
              return mutationFailure(error);
            }
          }
          async function removeSite(msg = {}) {
            try {
              const result = await queueDockMutation((current) => ({ config: DockLogic.removeDockSite(current, msg.siteId) }));
              return { ok: true, config: result.config };
            } catch (error) {
              return mutationFailure(error);
            }
          }
          async function reorderSites(msg = {}) {
            try {
              const result = await queueDockMutation((current) => ({
                config: Array.isArray(msg.orderedUrls) ? DockLogic.reorderDockSitesByUrls(current, msg.orderedUrls) : DockLogic.reorderDockSites(current, msg.orderedIds)
              }));
              return { ok: true, config: result.config };
            } catch (error) {
              return mutationFailure(error);
            }
          }
          async function toggleSite(msg = {}) {
            try {
              const result = await queueDockMutation((current) => runDockInput(() => DockLogic.toggleDockSite(current, msg.site)));
              return { ok: true, config: result.config, fixed: result.fixed };
            } catch (error) {
              return mutationFailure(error);
            }
          }
          return Object.freeze({
            handlers: Object.freeze({
              DOCK_GET_CONFIG: getConfig,
              DOCK_GET_TAB_ZOOM: getTabZoom,
              DOCK_UPDATE_SETTINGS: updateSettings,
              DOCK_UPSERT_SITE: upsertSite,
              DOCK_REMOVE_SITE: removeSite,
              DOCK_REORDER_SITES: reorderSites,
              DOCK_TOGGLE_SITE: toggleSite
            })
          });
        }
        return Object.freeze({ createDockService });
      });
    }
  });

  // src/background/active-trigger-service.js
  var require_active_trigger_service = __commonJS({
    "src/background/active-trigger-service.js"(exports, module) {
      (function attachActiveTriggerService(root, factory) {
        const api = factory(root);
        if (typeof module === "object" && module.exports) module.exports = api;
        root.TfaBackgroundActiveTriggerService = api;
      })(typeof globalThis === "object" ? globalThis : exports, function createActiveTriggerServiceModule(root) {
        "use strict";
        const SESSION_STORAGE_KEY = "tfa_active_guidance_browser_session";
        const COMPENSATION_STORAGE_KEY = "tfa_active_guidance_pending_compensations";
        function createActiveTriggerService(options = {}) {
          const chromeApi = options.chrome || root.chrome;
          const logic = options.logic || root.TfaActiveTriggerLogic;
          const now = typeof options.now === "function" ? options.now : () => Date.now();
          const configuredLocalTimeOptions = options.localTimeOptions;
          if (!chromeApi?.storage?.local || !chromeApi?.tabs || !logic) {
            throw new Error("Active trigger service dependencies are unavailable");
          }
          let activationTimestamps = [];
          let operationQueue = Promise.resolve();
          const requestedCompensationIds = /* @__PURE__ */ new Set();
          const fallbackSessionId = `memory:${now()}:${Math.random().toString(36).slice(2)}`;
          let sessionIdPromise = null;
          function localTimeOptionsAt(timestamp) {
            const value = typeof configuredLocalTimeOptions === "function" ? configuredLocalTimeOptions(timestamp) : configuredLocalTimeOptions;
            return value && typeof value === "object" ? value : void 0;
          }
          function enqueue(operation) {
            const current = operationQueue.catch(() => void 0).then(operation);
            operationQueue = current.then(() => void 0, () => void 0);
            return current;
          }
          async function browserSessionId() {
            if (sessionIdPromise) return sessionIdPromise;
            sessionIdPromise = (async () => {
              const sessionStorage = chromeApi.storage?.session;
              if (!sessionStorage?.get || !sessionStorage?.set) return fallbackSessionId;
              const stored = await sessionStorage.get(SESSION_STORAGE_KEY);
              const existing = stored?.[SESSION_STORAGE_KEY];
              if (typeof existing === "string" && existing) return existing;
              const created = `browser:${now()}:${Math.random().toString(36).slice(2)}`;
              await sessionStorage.set({ [SESSION_STORAGE_KEY]: created });
              return created;
            })().catch(() => fallbackSessionId);
            return sessionIdPromise;
          }
          async function readState(fallbackCount = null) {
            const stored = await chromeApi.storage.local.get(logic.STATE_STORAGE_KEY);
            const state = logic.normalizeState(stored?.[logic.STATE_STORAGE_KEY], fallbackCount);
            const sessionId = await browserSessionId();
            if (state.windowSessionId !== sessionId) {
              state.windowSessionId = sessionId;
              state.windowTabCounts = {};
              state.previousTabCount = null;
            }
            return state;
          }
          async function writeState(state) {
            const normalized = logic.normalizeState(state);
            normalized.windowSessionId = await browserSessionId();
            await chromeApi.storage.local.set({ [logic.STATE_STORAGE_KEY]: normalized });
            return normalized;
          }
          async function writePromptAndState(prompt, state) {
            const normalizedPrompt = logic.normalizePrompt(prompt, now());
            const normalizedState = logic.normalizeState(state);
            normalizedState.windowSessionId = await browserSessionId();
            await chromeApi.storage.local.set({
              [logic.PROMPT_STORAGE_KEY]: normalizedPrompt,
              [logic.STATE_STORAGE_KEY]: normalizedState
            });
            return { prompt: normalizedPrompt, state: normalizedState };
          }
          function normalizeCompensations(value) {
            const source = value && typeof value === "object" && !Array.isArray(value) ? value : {};
            const normalized = {};
            for (const [promptId, entry] of Object.entries(source)) {
              if (!promptId || !entry || typeof entry !== "object" || entry.promptId !== promptId) continue;
              const tabId = Number(entry.tabId);
              const claimedAt = Number(entry.claimedAt);
              if (!Number.isInteger(tabId) || tabId < 0 || !Number.isFinite(claimedAt) || claimedAt <= 0) continue;
              const prompt = entry.prompt && typeof entry.prompt === "object" ? {
                id: promptId,
                type: entry.prompt.type,
                threshold: entry.prompt.threshold ?? null
              } : null;
              if (!prompt || !["startup", "frequent-switch", "overload-20", "overload-30"].includes(prompt.type)) continue;
              normalized[promptId] = {
                promptId,
                tabId,
                claimedAt,
                prompt,
                rollback: entry.rollback && typeof entry.rollback === "object" ? { ...entry.rollback } : {},
                documentId: typeof entry.documentId === "string" ? entry.documentId : "",
                sourceUrl: typeof entry.sourceUrl === "string" ? entry.sourceUrl : "",
                requested: entry.requested === true,
                discard: entry.discard === true
              };
            }
            return normalized;
          }
          async function readCompensations() {
            const stored = await chromeApi.storage.local.get(COMPENSATION_STORAGE_KEY);
            return normalizeCompensations(stored?.[COMPENSATION_STORAGE_KEY]);
          }
          function compensationFor(prompt) {
            return {
              promptId: prompt.id,
              tabId: prompt.claim.tabId,
              claimedAt: prompt.claim.claimedAt,
              prompt: {
                id: prompt.id,
                type: prompt.type,
                threshold: prompt.threshold
              },
              rollback: prompt.claim.rollback && typeof prompt.claim.rollback === "object" ? { ...prompt.claim.rollback } : {},
              documentId: prompt.claim.documentId || "",
              sourceUrl: prompt.claim.sourceUrl || "",
              requested: false,
              // If the worker disappears before the claimant confirms visibility,
              // fail closed: the exact unpublished prompt is discarded.  An
              // explicit ordinary release changes this to false before processing.
              discard: true
            };
          }
          async function writePromptStateAndCompensations(prompt, state, compensations) {
            const normalizedPrompt = logic.normalizePrompt(prompt, now());
            const normalizedState = logic.normalizeState(state);
            normalizedState.windowSessionId = await browserSessionId();
            const normalizedCompensations = normalizeCompensations(compensations);
            await chromeApi.storage.local.set({
              [logic.PROMPT_STORAGE_KEY]: normalizedPrompt,
              [logic.STATE_STORAGE_KEY]: normalizedState,
              [COMPENSATION_STORAGE_KEY]: normalizedCompensations
            });
            return {
              prompt: normalizedPrompt,
              state: normalizedState,
              compensations: normalizedCompensations
            };
          }
          async function writePromptAndCompensations(prompt, compensations) {
            const normalizedPrompt = logic.normalizePrompt(prompt, now());
            const normalizedCompensations = normalizeCompensations(compensations);
            await chromeApi.storage.local.set({
              [logic.PROMPT_STORAGE_KEY]: normalizedPrompt,
              [COMPENSATION_STORAGE_KEY]: normalizedCompensations
            });
            return { prompt: normalizedPrompt, compensations: normalizedCompensations };
          }
          function normalizeWindowId(value) {
            if (value === null || value === void 0 || value === "") return null;
            const windowId = Number(value);
            return Number.isInteger(windowId) && windowId >= 0 ? windowId : null;
          }
          async function windowTabSnapshot(windowId = null) {
            const scopedWindowId = normalizeWindowId(windowId);
            let tabs;
            try {
              tabs = await chromeApi.tabs.query(scopedWindowId == null ? { currentWindow: true } : { windowId: scopedWindowId });
            } catch (error) {
              if (scopedWindowId == null) throw error;
              tabs = [];
            }
            const safeTabs = Array.isArray(tabs) ? tabs : [];
            return {
              count: safeTabs.length,
              windowId: scopedWindowId ?? (Number.isInteger(Number(safeTabs[0]?.windowId)) ? Number(safeTabs[0].windowId) : null)
            };
          }
          function previousWindowCount(state, windowId) {
            const normalizedWindowId = normalizeWindowId(windowId);
            const key = normalizedWindowId == null ? "" : String(normalizedWindowId);
            if (key && Object.hasOwn(state.windowTabCounts || {}, key)) return state.windowTabCounts[key];
            return null;
          }
          function markWindowCount(state, windowId, count) {
            const normalizedWindowId = normalizeWindowId(windowId);
            const key = normalizedWindowId == null ? "" : String(normalizedWindowId);
            state.previousTabCount = count;
            if (key) state.windowTabCounts = { ...state.windowTabCounts || {}, [key]: count };
            return state;
          }
          async function publishPrompt(prompt, stateToCommit = null) {
            if (!prompt) return false;
            const stored = await chromeApi.storage.local.get(logic.PROMPT_STORAGE_KEY);
            const current = logic.normalizePrompt(stored?.[logic.PROMPT_STORAGE_KEY], now());
            let nextState = stateToCommit ? logic.normalizeState(stateToCommit) : null;
            if (!logic.shouldReplacePrompt(current, prompt, now())) {
              if (nextState) await writeState(nextState);
              return false;
            }
            const nextPrompt = { ...prompt, claim: null };
            if (current?.claim?.counted && !current.claim.visible) {
              nextState = logic.rollbackAutomatedPromptShown(
                nextState || await readState(),
                current.claim.claimedAt,
                current.id,
                localTimeOptionsAt(current.claim.claimedAt)
              );
              nextState = logic.rollbackPromptCooldown(nextState, current, current.claim.rollback);
              const compensations = await readCompensations();
              delete compensations[current.id];
              requestedCompensationIds.delete(current.id);
              await writePromptStateAndCompensations(nextPrompt, nextState, compensations);
            } else if (nextState) {
              await writePromptAndState(nextPrompt, nextState);
            } else {
              await chromeApi.storage.local.set({
                [logic.PROMPT_STORAGE_KEY]: nextPrompt
              });
            }
            return true;
          }
          function testPromptType(value) {
            if (value === "startup" || value === "frequent-switch") return value;
            if (value === "overload" || value === "overload-20") return "overload-20";
            if (value === "overload-30") return "overload-30";
            return null;
          }
          function showTestPrompt(type) {
            return enqueue(async () => {
              const normalizedType = testPromptType(type);
              if (!normalizedType) return { ok: false, error: "Unsupported guidance test type" };
              const prompt = normalizedType.startsWith("overload-") ? logic.createPrompt("overload", now(), {
                threshold: Number(normalizedType.slice(-2)),
                automated: false
              }) : logic.createPrompt(normalizedType, now(), { automated: false });
              await chromeApi.storage.local.set({ [logic.PROMPT_STORAGE_KEY]: prompt });
              return { ok: true, prompt };
            });
          }
          async function openTestWindow(count) {
            const total = Number(count);
            if (![19, 20].includes(total)) return { ok: false, error: "Only 19 or 20 test tabs are supported" };
            if (typeof chromeApi.windows?.create !== "function") return { ok: false, error: "Browser window API unavailable" };
            const urls = Array.from({ length: total }, () => "https://www.baidu.com/");
            const created = await chromeApi.windows.create({ url: urls, focused: true });
            return { ok: true, count: total, windowId: created?.id ?? null };
          }
          async function removePrompt(promptId, overloadOnly = false) {
            const stored = await chromeApi.storage.local.get(logic.PROMPT_STORAGE_KEY);
            const current = logic.normalizePrompt(stored?.[logic.PROMPT_STORAGE_KEY], now());
            if (!current) {
              await chromeApi.storage.local.remove(logic.PROMPT_STORAGE_KEY);
              return true;
            }
            if (promptId && current.id !== promptId) return false;
            if (overloadOnly && !current.type.startsWith("overload-")) return false;
            await chromeApi.storage.local.remove(logic.PROMPT_STORAGE_KEY);
            return true;
          }
          function rollbackSnapshot(state, prompt) {
            if (prompt.type === "frequent-switch") {
              return {
                lastFrequentPromptAt: state.lastFrequentPromptAt,
                lastFrequentPromptId: state.lastFrequentPromptId
              };
            }
            if (prompt.type === "overload-20") {
              return {
                lastAlertAt20: state.lastAlertAt20,
                lastAlertPromptId20: state.lastAlertPromptId20
              };
            }
            if (prompt.type === "overload-30") {
              return {
                lastAlertAt30: state.lastAlertAt30,
                lastAlertPromptId30: state.lastAlertPromptId30
              };
            }
            return {};
          }
          function markPromptClaimedState(state, prompt, timestamp) {
            let next = logic.normalizeState(state);
            if (prompt.type === "frequent-switch") {
              next = logic.markFrequentPrompt(next, timestamp, prompt.id);
            } else if (prompt.type === "overload-20" || prompt.type === "overload-30") {
              next = logic.markOverloadPrompt(next, prompt.threshold, timestamp, prompt.id);
            }
            return logic.markAutomatedPromptShown(next, timestamp, prompt.id, localTimeOptionsAt(timestamp));
          }
          function normalizeClaimSource(source) {
            if (source && typeof source === "object") {
              return {
                tabId: Number(source?.tab?.id ?? source?.tabId),
                documentId: typeof source.documentId === "string" ? source.documentId : "",
                sourceUrl: typeof source.url === "string" ? source.url : typeof source?.tab?.url === "string" ? source.tab.url : "",
                internal: source.internal === true
              };
            }
            return { tabId: Number(source), documentId: "", sourceUrl: "", internal: false };
          }
          function claimOwnedBy(claim, source) {
            if (!claim || claim.tabId !== source.tabId) return false;
            if (source.internal) return true;
            if (claim.documentId) return Boolean(source.documentId) && claim.documentId === source.documentId;
            return true;
          }
          async function drainRequestedCompensationBeforeUse(promptId) {
            const drained = await retryCompensationsNow();
            if (!drained.ok && requestedCompensationIds.has(promptId)) return false;
            try {
              const compensations = await readCompensations();
              return !requestedCompensationIds.has(promptId) && compensations[promptId]?.requested !== true;
            } catch (_error) {
              return false;
            }
          }
          function claimPrompt(promptId, source) {
            return enqueue(async () => {
              const claimSource = normalizeClaimSource(source);
              const claimantTabId = claimSource.tabId;
              if (!Number.isInteger(claimantTabId) || claimantTabId < 0) {
                return { ok: false, claimed: false, code: "INVALID_GUIDANCE_CLAIM_SOURCE" };
              }
              if (!await drainRequestedCompensationBeforeUse(promptId)) {
                return { ok: true, claimed: false, compensationPending: true };
              }
              const stored = await chromeApi.storage.local.get(logic.PROMPT_STORAGE_KEY);
              const current = logic.normalizePrompt(stored?.[logic.PROMPT_STORAGE_KEY], now());
              if (!current || current.id !== promptId) return { ok: true, claimed: false };
              if (claimOwnedBy(current.claim, claimSource)) {
                return { ok: true, claimed: true, prompt: current };
              }
              if (current.claim) return { ok: true, claimed: false };
              const claimedAt = now();
              let state = await readState();
              if (current.automated && !logic.canShowAutomatedPrompt(state, claimedAt, localTimeOptionsAt(claimedAt))) {
                await removePrompt(current.id);
                return { ok: true, claimed: false, exhausted: true };
              }
              const rollback = current.automated ? rollbackSnapshot(state, current) : {};
              if (current.automated) state = markPromptClaimedState(state, current, claimedAt);
              const claimedPrompt = {
                ...current,
                claim: {
                  tabId: claimantTabId,
                  claimedAt,
                  counted: current.automated,
                  visible: false,
                  ...claimSource.documentId ? { documentId: claimSource.documentId } : {},
                  ...claimSource.sourceUrl ? { sourceUrl: claimSource.sourceUrl } : {},
                  ...current.automated ? { rollback } : {}
                }
              };
              if (current.automated) {
                const compensations = await readCompensations();
                compensations[current.id] = compensationFor(claimedPrompt);
                const committed = await writePromptStateAndCompensations(claimedPrompt, state, compensations);
                return { ok: true, claimed: true, ...committed };
              }
              await chromeApi.storage.local.set({ [logic.PROMPT_STORAGE_KEY]: claimedPrompt });
              return { ok: true, claimed: true, prompt: logic.normalizePrompt(claimedPrompt, claimedAt) };
            });
          }
          async function confirmPromptNow(promptId, source) {
            const claimSource = normalizeClaimSource(source);
            const claimantTabId = claimSource.tabId;
            const confirmedAt = now();
            if (!await drainRequestedCompensationBeforeUse(promptId)) {
              return { ok: true, confirmed: false, compensationPending: true };
            }
            const stored = await chromeApi.storage.local.get(logic.PROMPT_STORAGE_KEY);
            const current = logic.normalizePrompt(stored?.[logic.PROMPT_STORAGE_KEY], confirmedAt);
            if (!current || current.id !== promptId || !claimOwnedBy(current.claim, claimSource)) {
              return { ok: true, confirmed: false };
            }
            if (current.claim.visible) return { ok: true, confirmed: true, prompt: current };
            const compensations = await readCompensations();
            delete compensations[current.id];
            requestedCompensationIds.delete(current.id);
            if (current.claim.counted && logic.localDayKey(current.claim.claimedAt, localTimeOptionsAt(current.claim.claimedAt)) !== logic.localDayKey(confirmedAt, localTimeOptionsAt(confirmedAt))) {
              let state = await readState();
              state = logic.rollbackAutomatedPromptShown(
                state,
                current.claim.claimedAt,
                current.id,
                localTimeOptionsAt(current.claim.claimedAt)
              );
              state = logic.rollbackPromptCooldown(state, current, current.claim.rollback);
              if (!logic.canShowAutomatedPrompt(state, confirmedAt, localTimeOptionsAt(confirmedAt))) {
                const released = { ...current, claim: null };
                const committed2 = await writePromptStateAndCompensations(released, state, compensations);
                return { ok: true, confirmed: false, exhausted: true, ...committed2 };
              }
              const rollback = rollbackSnapshot(state, current);
              state = markPromptClaimedState(state, current, confirmedAt);
              const migrated = {
                ...current,
                claim: {
                  ...current.claim,
                  claimedAt: confirmedAt,
                  rollback,
                  visible: true
                }
              };
              const committed = await writePromptStateAndCompensations(migrated, state, compensations);
              return { ok: true, confirmed: true, ...committed };
            }
            const confirmedPrompt = {
              ...current,
              claim: { ...current.claim, visible: true }
            };
            await writePromptAndCompensations(confirmedPrompt, compensations);
            return {
              ok: true,
              confirmed: true,
              prompt: logic.normalizePrompt(confirmedPrompt, now())
            };
          }
          function confirmPrompt(promptId, source) {
            return enqueue(() => confirmPromptNow(promptId, source));
          }
          async function processCompensationNow(promptId) {
            const stored = await chromeApi.storage.local.get([
              logic.PROMPT_STORAGE_KEY,
              logic.STATE_STORAGE_KEY,
              COMPENSATION_STORAGE_KEY
            ]);
            const compensations = normalizeCompensations(stored?.[COMPENSATION_STORAGE_KEY]);
            const record = compensations[promptId];
            if (!record) {
              requestedCompensationIds.delete(promptId);
              return { ok: true, released: false, compensated: false };
            }
            const rawCurrent = stored?.[logic.PROMPT_STORAGE_KEY];
            const current = logic.normalizePrompt(rawCurrent, now());
            const exactCurrent = rawCurrent && typeof rawCurrent === "object" && rawCurrent.id === promptId;
            if (exactCurrent && current?.claim?.visible) {
              delete compensations[promptId];
              requestedCompensationIds.delete(promptId);
              const committed2 = await writePromptAndCompensations(rawCurrent, compensations);
              return { ok: true, released: false, compensated: false, visible: true, ...committed2 };
            }
            let state = logic.normalizeState(stored?.[logic.STATE_STORAGE_KEY]);
            state = logic.rollbackAutomatedPromptShown(
              state,
              record.claimedAt,
              record.promptId,
              localTimeOptionsAt(record.claimedAt)
            );
            state = logic.rollbackPromptCooldown(state, record.prompt, record.rollback);
            delete compensations[promptId];
            let committed;
            if (exactCurrent) {
              const nextPrompt = record.discard ? null : current ? { ...current, claim: null } : null;
              committed = await writePromptStateAndCompensations(nextPrompt, state, compensations);
            } else {
              const normalizedState = logic.normalizeState(state);
              normalizedState.windowSessionId = await browserSessionId();
              const normalizedCompensations = normalizeCompensations(compensations);
              await chromeApi.storage.local.set({
                [logic.STATE_STORAGE_KEY]: normalizedState,
                [COMPENSATION_STORAGE_KEY]: normalizedCompensations
              });
              committed = {
                prompt: logic.normalizePrompt(rawCurrent, now()),
                state: normalizedState,
                compensations: normalizedCompensations
              };
            }
            requestedCompensationIds.delete(promptId);
            return {
              ok: true,
              released: exactCurrent,
              discarded: exactCurrent && record.discard,
              compensated: true,
              ...committed
            };
          }
          async function markCompensationRequestedNow(promptId, discard) {
            requestedCompensationIds.add(promptId);
            const compensations = await readCompensations();
            const record = compensations[promptId];
            if (!record) return false;
            record.requested = true;
            record.discard = discard === true;
            await chromeApi.storage.local.set({
              [COMPENSATION_STORAGE_KEY]: normalizeCompensations(compensations)
            });
            return true;
          }
          async function releasePromptNow(promptId, source, options2 = {}) {
            const claimSource = normalizeClaimSource(source);
            const claimantTabId = claimSource.tabId;
            const discard = options2.discard === true;
            const stored = await chromeApi.storage.local.get(logic.PROMPT_STORAGE_KEY);
            const current = logic.normalizePrompt(stored?.[logic.PROMPT_STORAGE_KEY], now());
            if (!current || current.id !== promptId || !claimOwnedBy(current.claim, claimSource)) {
              return { ok: true, released: false };
            }
            const releasedPrompt = discard ? null : { ...current, claim: null };
            if (current.claim.counted) {
              const recorded = await markCompensationRequestedNow(current.id, discard);
              if (!recorded) {
                const compensations = await readCompensations();
                compensations[current.id] = {
                  ...compensationFor(current),
                  requested: true,
                  discard
                };
                await chromeApi.storage.local.set({
                  [COMPENSATION_STORAGE_KEY]: normalizeCompensations(compensations)
                });
              }
              return processCompensationNow(current.id);
            }
            await chromeApi.storage.local.set({ [logic.PROMPT_STORAGE_KEY]: releasedPrompt });
            return {
              ok: true,
              released: true,
              discarded: discard,
              prompt: logic.normalizePrompt(releasedPrompt, now())
            };
          }
          async function retryCompensationsNow({ all = false } = {}) {
            const compensations = await readCompensations();
            const ids = Object.keys(compensations).filter((promptId) => all || compensations[promptId].requested || requestedCompensationIds.has(promptId));
            const results = [];
            for (const promptId of ids) {
              try {
                results.push(await processCompensationNow(promptId));
              } catch (error) {
                requestedCompensationIds.add(promptId);
                results.push({ ok: false, promptId, error });
              }
            }
            return { ok: results.every((result) => result.ok), results };
          }
          function retryCompensations(options2 = {}) {
            return enqueue(() => retryCompensationsNow(options2));
          }
          function releasePrompt(promptId, source, options2 = {}) {
            return enqueue(() => releasePromptNow(promptId, source, options2));
          }
          async function handleTabActivated() {
            return enqueue(async () => {
              await retryCompensationsNow();
              const timestamp = now();
              const activation = logic.recordActivation(activationTimestamps, timestamp);
              activationTimestamps = activation.timestamps;
              if (!activation.shouldPrompt) return { prompted: false };
              activationTimestamps = [];
              let state = await readState();
              if (!logic.canShowFrequentPrompt(state, timestamp, localTimeOptionsAt(timestamp))) {
                return { prompted: false, cooldown: true };
              }
              const prompted = await publishPrompt(logic.createPrompt("frequent-switch", timestamp));
              return { prompted, state };
            });
          }
          async function checkTabCount({ forceCurrent = false, windowId = null } = {}) {
            const timestamp = now();
            const snapshot = await windowTabSnapshot(windowId);
            const { count } = snapshot;
            let state = await readState(count);
            const threshold = forceCurrent ? logic.currentOverloadThreshold(count) : logic.crossedOverloadThreshold(previousWindowCount(state, snapshot.windowId), count);
            markWindowCount(state, snapshot.windowId, count);
            let prompted = false;
            if (threshold && logic.canShowOverloadPrompt(state, threshold, timestamp, localTimeOptionsAt(timestamp))) {
              prompted = await publishPrompt(logic.createPrompt("overload", timestamp, { threshold }), state);
              state = await readState();
            } else {
              state = await writeState(state);
            }
            return { count, threshold, prompted, state };
          }
          function handleTabCreated(tab = {}) {
            return enqueue(async () => {
              await retryCompensationsNow();
              return checkTabCount({ windowId: tab?.windowId });
            });
          }
          function handleTabRemoved(_tabId, removeInfo = {}) {
            return enqueue(async () => {
              const stored = await chromeApi.storage.local.get(logic.PROMPT_STORAGE_KEY);
              const current = logic.normalizePrompt(stored?.[logic.PROMPT_STORAGE_KEY], now());
              let claimResult = null;
              if (current?.claim?.tabId === Number(_tabId)) {
                claimResult = current.claim.visible ? { ok: await removePrompt(current.id), dismissed: true } : await releasePromptNow(current.id, { tabId: _tabId, internal: true });
              }
              await retryCompensationsNow();
              const snapshot = await windowTabSnapshot(removeInfo?.windowId);
              const { count } = snapshot;
              const state = await readState(count);
              markWindowCount(state, snapshot.windowId, count);
              await writeState(state);
              return { count, claimResult };
            });
          }
          function handleTabUpdated(tabId, changeInfo = {}) {
            if (!changeInfo.url && changeInfo.status !== "loading") return Promise.resolve({ changed: false });
            return enqueue(async () => {
              const stored = await chromeApi.storage.local.get(logic.PROMPT_STORAGE_KEY);
              const current = logic.normalizePrompt(stored?.[logic.PROMPT_STORAGE_KEY], now());
              if (current?.claim?.tabId !== Number(tabId)) return { changed: false };
              const claimResult = current.claim.visible ? { ok: await removePrompt(current.id), dismissed: true } : await releasePromptNow(current.id, { tabId, internal: true }, { discard: true });
              await retryCompensationsNow();
              return { changed: true, claimResult };
            });
          }
          async function rebuildWindowBaselinesNow() {
            const tabs = await chromeApi.tabs.query({});
            const windowTabCounts = {};
            for (const tab of Array.isArray(tabs) ? tabs : []) {
              const windowId = normalizeWindowId(tab?.windowId);
              if (windowId == null) continue;
              const key = String(windowId);
              windowTabCounts[key] = (windowTabCounts[key] || 0) + 1;
            }
            const state = await readState();
            state.windowTabCounts = windowTabCounts;
            state.previousTabCount = null;
            return { state: await writeState(state), windowTabCounts };
          }
          function rebuildWindowBaselines() {
            return enqueue(rebuildWindowBaselinesNow);
          }
          function handleWindowRemoved(windowId) {
            return enqueue(async () => {
              const state = await readState();
              const key = String(normalizeWindowId(windowId));
              if (key !== "null") delete state.windowTabCounts[key];
              return { state: await writeState(state), windowId: normalizeWindowId(windowId) };
            });
          }
          function handleStartup() {
            return enqueue(async () => {
              await retryCompensationsNow({ all: true });
              const timestamp = now();
              const rebuilt = await rebuildWindowBaselinesNow();
              let { state } = rebuilt;
              let prompted = false;
              if (logic.canShowAutomatedPrompt(state, timestamp, localTimeOptionsAt(timestamp))) {
                prompted = await publishPrompt(logic.createPrompt("startup", timestamp));
              }
              return { count: null, threshold: null, prompted, state };
            });
          }
          function handleInstalled(details = {}) {
            if (details.reason !== "install") return Promise.resolve({ prompted: false });
            return handleStartup();
          }
          function dismiss(promptId) {
            return enqueue(() => removePrompt(promptId));
          }
          function muteToday(promptId) {
            return enqueue(async () => {
              const stored = await chromeApi.storage.local.get(logic.PROMPT_STORAGE_KEY);
              const current = logic.normalizePrompt(stored?.[logic.PROMPT_STORAGE_KEY], now());
              if (!current || current.id !== promptId) {
                return { ok: false, prompt: current };
              }
              const timestamp = now();
              const state = (logic.muteToday || logic.muteOverloadToday)(
                await readState(),
                timestamp,
                localTimeOptionsAt(timestamp)
              );
              const committed = await writePromptAndState(null, state);
              return { ok: true, ...committed };
            });
          }
          const handlers = Object.freeze({
            [logic.DISMISS_MESSAGE]: (message) => dismiss(message?.promptId).then((ok) => ({ ok })),
            [logic.MUTE_TODAY_MESSAGE]: (message) => muteToday(message?.promptId),
            [logic.CLAIM_MESSAGE]: (message, sender) => claimPrompt(message?.promptId, sender),
            [logic.CONFIRM_MESSAGE]: (message, sender) => confirmPrompt(message?.promptId, sender),
            [logic.RELEASE_MESSAGE]: (message, sender) => releasePrompt(
              message?.promptId,
              sender,
              { discard: message?.discard === true }
            ),
            [logic.TEST_PROMPT_MESSAGE]: (message) => showTestPrompt(message?.promptType),
            [logic.TEST_WINDOW_MESSAGE]: (message) => openTestWindow(message?.count)
          });
          return Object.freeze({
            handlers,
            handleTabActivated,
            handleTabCreated,
            handleTabRemoved,
            handleTabUpdated,
            handleStartup,
            handleInstalled,
            handleWindowRemoved,
            rebuildWindowBaselines,
            checkTabCount,
            dismiss,
            muteToday,
            claimPrompt,
            confirmPrompt,
            releasePrompt,
            retryCompensations,
            showTestPrompt,
            openTestWindow,
            inspect: () => ({
              activationTimestamps: [...activationTimestamps],
              requestedCompensationIds: [...requestedCompensationIds]
            })
          });
        }
        return Object.freeze({ SESSION_STORAGE_KEY, COMPENSATION_STORAGE_KEY, createActiveTriggerService });
      });
    }
  });

  // src/background/newtab-site-space-service.js
  var require_newtab_site_space_service = __commonJS({
    "src/background/newtab-site-space-service.js"(exports, module) {
      (function attachNewTabSiteSpaceService(root, factory) {
        const api = factory(root);
        if (typeof module === "object" && module.exports) module.exports = api;
        root.TfaBackgroundNewTabSiteSpaceService = api;
      })(typeof globalThis === "object" ? globalThis : exports, function createNewTabSiteSpaceServiceModule(root) {
        "use strict";
        function createNewTabSiteSpaceService(options = {}) {
          const chromeApi = options.chrome || root.chrome;
          const model = options.model || root.TfaNewTabSiteSpaceModel;
          const now = typeof options.now === "function" ? options.now : () => Date.now();
          if (!chromeApi?.storage?.local || !model?.normalizeSpace) throw new Error("new tab site space dependencies unavailable");
          let queue = Promise.resolve();
          const enqueue = (operation) => {
            const current = queue.catch(() => void 0).then(operation);
            queue = current.then(() => void 0, () => void 0);
            return current;
          };
          async function read() {
            const stored = await chromeApi.storage.local.get(model.STORAGE_KEY);
            const existing = stored?.[model.STORAGE_KEY];
            const space = model.normalizeSpace(existing, { now: now(), language: chromeApi.i18n?.getUILanguage?.() });
            if (!existing || Number(existing.schemaVersion) !== Number(space.schemaVersion)) {
              await chromeApi.storage.local.set({ [model.STORAGE_KEY]: space });
            }
            return space;
          }
          function get() {
            return enqueue(async () => ({ ok: true, space: await read() }));
          }
          function save(value, expectedRevision = null) {
            return enqueue(async () => {
              const current = await read();
              const expected = expectedRevision == null ? NaN : Number(expectedRevision);
              if (Number.isInteger(expected) && expected >= 0 && expected !== Number(current.revision)) {
                return {
                  ok: false,
                  code: "REVISION_CONFLICT",
                  message: "New tab site space changed in another page",
                  space: current
                };
              }
              const space = model.normalizeSpace({
                ...value,
                revision: current.revision + 1,
                updatedAt: now()
              }, { now: now(), language: chromeApi.i18n?.getUILanguage?.() });
              await chromeApi.storage.local.set({ [model.STORAGE_KEY]: space });
              return { ok: true, space };
            });
          }
          function reset() {
            return enqueue(async () => {
              const space = model.createDefaultSpace({ now: now(), language: chromeApi.i18n?.getUILanguage?.() });
              await chromeApi.storage.local.set({ [model.STORAGE_KEY]: space });
              return { ok: true, space };
            });
          }
          const handlers = Object.freeze({
            NEWTAB_SITE_SPACE_GET: get,
            NEWTAB_SITE_SPACE_SAVE: (message) => save(message?.space, message?.expectedRevision),
            NEWTAB_SITE_SPACE_RESET: reset
          });
          return Object.freeze({ handlers, get, save, reset });
        }
        return Object.freeze({ createNewTabSiteSpaceService });
      });
    }
  });

  // src/background/v2-data-store.js
  var require_v2_data_store = __commonJS({
    "src/background/v2-data-store.js"(exports, module) {
      (function attachV2DataStore(root, factory) {
        const api = factory();
        if (typeof module === "object" && module.exports) module.exports = api;
        root.TfaV2DataStore = api;
      })(typeof globalThis === "object" ? globalThis : exports, function createV2DataStoreModule() {
        "use strict";
        const LEGACY_TASKS_KEY = "tfa_v2_tasks";
        const SCHEMA_VERSION_KEY = "tfa_v2_schema_version";
        const TASK_PROVENANCE_MIGRATION_KEY = "tfa_v2_task_provenance_version";
        function requestAsPromise(request) {
          return new Promise((resolve, reject) => {
            request.onsuccess = () => resolve(request.result);
            request.onerror = () => reject(request.error || new Error("IndexedDB request failed"));
          });
        }
        function transactionAsPromise(transaction) {
          return new Promise((resolve, reject) => {
            const fail = () => reject(transaction.error || new Error("IndexedDB transaction failed"));
            transaction.oncomplete = () => resolve();
            transaction.onerror = fail;
            transaction.onabort = fail;
          });
        }
        function hasName(list, name) {
          return Boolean(list && (typeof list.contains === "function" ? list.contains(name) : Array.from(list).includes(name)));
        }
        function ensureIndex(store, name, keyPath, options) {
          if (!hasName(store.indexNames, name)) store.createIndex(name, keyPath, options);
        }
        function installV1Schema(database, stores) {
          const pages = hasName(database.objectStoreNames, stores.PAGES) ? null : database.createObjectStore(stores.PAGES, { keyPath: "id" });
          if (pages) {
            ensureIndex(pages, "normalizedUrl", "normalizedUrl", { unique: true });
            ensureIndex(pages, "updatedAt", "updatedAt", { unique: false });
          }
          const tasks = hasName(database.objectStoreNames, stores.TASKS) ? null : database.createObjectStore(stores.TASKS, { keyPath: "id" });
          if (tasks) {
            ensureIndex(tasks, "status", "status", { unique: false });
            ensureIndex(tasks, "domain", "domain", { unique: false });
            ensureIndex(tasks, "sortOrder", "sortOrder", { unique: false });
          }
          const relations = hasName(database.objectStoreNames, stores.RELATIONS) ? null : database.createObjectStore(stores.RELATIONS, { keyPath: "id" });
          if (relations) {
            ensureIndex(relations, "taskId", "taskId", { unique: false });
            ensureIndex(relations, "pageId", "pageId", { unique: false });
          }
          const changes = hasName(database.objectStoreNames, stores.CHANGES) ? null : database.createObjectStore(stores.CHANGES, { keyPath: "id" });
          if (changes) ensureIndex(changes, "createdAt", "createdAt", { unique: false });
        }
        function installV2Schema(database, stores) {
          installV1Schema(database, stores);
          const tabInstances = hasName(database.objectStoreNames, stores.TAB_INSTANCES) ? null : database.createObjectStore(stores.TAB_INSTANCES, { keyPath: "id" });
          if (tabInstances) {
            ensureIndex(tabInstances, "browserSessionId", "browserSessionId", { unique: false });
            ensureIndex(tabInstances, "tabId", "tabId", { unique: false });
            ensureIndex(tabInstances, "pageId", "pageId", { unique: false });
            ensureIndex(tabInstances, "windowId", "windowId", { unique: false });
            ensureIndex(tabInstances, "lastSeenAt", "lastSeenAt", { unique: false });
          }
          const behaviorEvents = hasName(database.objectStoreNames, stores.BEHAVIOR_EVENTS) ? null : database.createObjectStore(stores.BEHAVIOR_EVENTS, { keyPath: "id" });
          if (behaviorEvents) {
            ensureIndex(behaviorEvents, "occurredAt", "occurredAt", { unique: false });
            ensureIndex(behaviorEvents, "pageId", "pageId", { unique: false });
            ensureIndex(behaviorEvents, "tabId", "tabId", { unique: false });
            ensureIndex(behaviorEvents, "type", "type", { unique: false });
          }
        }
        function installV3Schema(database, stores, upgradeTransaction) {
          installV2Schema(database, stores);
          const suggestions = hasName(database.objectStoreNames, stores.SUGGESTIONS) ? null : database.createObjectStore(stores.SUGGESTIONS, { keyPath: "id" });
          if (suggestions) {
            ensureIndex(suggestions, "status", "status", { unique: false });
            ensureIndex(suggestions, "pageId", "pageId", { unique: false });
            ensureIndex(suggestions, "taskId", "taskId", { unique: false });
            ensureIndex(suggestions, "expiresAt", "expiresAt", { unique: false });
            ensureIndex(suggestions, "dedupeKey", "dedupeKey", { unique: true });
          }
          const behaviorEvents = upgradeTransaction && hasName(database.objectStoreNames, stores.BEHAVIOR_EVENTS) ? upgradeTransaction.objectStore(stores.BEHAVIOR_EVENTS) : null;
          if (behaviorEvents) ensureIndex(behaviorEvents, "browserSessionId", "browserSessionId", { unique: false });
        }
        function installV4Schema(database, stores, upgradeTransaction) {
          installV3Schema(database, stores, upgradeTransaction);
        }
        function stablePageId(normalizedUrl) {
          let hashA = 2166136261;
          let hashB = 2654435769;
          for (let index = 0; index < normalizedUrl.length; index += 1) {
            const code = normalizedUrl.charCodeAt(index);
            hashA = Math.imul(hashA ^ code, 16777619) >>> 0;
            hashB = Math.imul(hashB ^ code, 2246822507) >>> 0;
          }
          return `page-${hashA.toString(36)}${hashB.toString(36)}`;
        }
        function createV2DataStore({
          indexedDB: indexedDBApi,
          storage,
          schema,
          now = () => Date.now(),
          createId
        } = {}) {
          const dataSchema = schema || typeof globalThis === "object" && globalThis.TfaV2DataSchema;
          const idb = indexedDBApi || typeof globalThis === "object" && globalThis.indexedDB;
          if (!idb || typeof idb.open !== "function" || typeof idb.deleteDatabase !== "function") {
            throw new Error("V2 data store requires IndexedDB");
          }
          if (!storage || typeof storage.get !== "function" || typeof storage.set !== "function" || typeof storage.remove !== "function") {
            throw new Error("V2 data store requires chrome.storage.local compatible storage");
          }
          if (!dataSchema || !dataSchema.DB_NAME || !dataSchema.STORES) {
            throw new Error("V2 data store requires TfaV2DataSchema");
          }
          const stores = dataSchema.STORES;
          let databasePromise = null;
          function openDatabase(version, installSchema) {
            const opening = new Promise((resolve, reject) => {
              const request = version == null ? idb.open(dataSchema.DB_NAME) : idb.open(dataSchema.DB_NAME, version);
              request.onupgradeneeded = () => installSchema(request.result, stores, request.transaction);
              request.onsuccess = () => {
                const database = request.result;
                database.onversionchange = () => {
                  database.close();
                  if (databasePromise === opening) databasePromise = null;
                };
                resolve(database);
              };
              request.onerror = () => reject(request.error || new Error("Unable to open V2 task database"));
              request.onblocked = () => reject(new Error("V2 task database upgrade is blocked"));
            });
            databasePromise = opening;
            opening.catch(() => {
              if (databasePromise === opening) databasePromise = null;
            });
            return opening;
          }
          async function migrateTaskProvenanceV4(database) {
            const stored = await storage.get(TASK_PROVENANCE_MIGRATION_KEY);
            if (Number(stored?.[TASK_PROVENANCE_MIGRATION_KEY]) >= 4) return Object.freeze({ migrated: false, tasks: 0 });
            const transaction = database.transaction([stores.TASKS], "readwrite");
            const completion = transactionAsPromise(transaction);
            try {
              const taskStore = transaction.objectStore(stores.TASKS);
              const tasks = await requestAsPromise(taskStore.getAll());
              let migrated = 0;
              for (const task of tasks) {
                const normalized = dataSchema.normalizeTask(task, { now, createId });
                if (JSON.stringify(task) === JSON.stringify(normalized)) continue;
                await requestAsPromise(taskStore.put(normalized));
                migrated += 1;
              }
              await completion;
              await storage.set({ [TASK_PROVENANCE_MIGRATION_KEY]: 4 });
              return Object.freeze({ migrated: migrated > 0, tasks: migrated });
            } catch (cause) {
              try {
                transaction.abort();
              } catch (_abortError) {
              }
              await completion.catch(() => void 0);
              const error = new Error(cause?.message || "Unable to migrate task provenance");
              error.code = "V2_TASK_PROVENANCE_MIGRATION_FAILED";
              error.cause = cause;
              throw error;
            }
          }
          function open() {
            if (databasePromise) return databasePromise;
            const opening = openDatabase(dataSchema.DB_VERSION, installV4Schema);
            const ready = opening.then(async (database) => {
              try {
                await migrateTaskProvenanceV4(database);
                database.onversionchange = () => {
                  database.close();
                  if (databasePromise === ready) databasePromise = null;
                };
                return database;
              } catch (error) {
                database.close();
                throw error;
              }
            });
            databasePromise = ready;
            ready.catch(() => {
              if (databasePromise === ready) databasePromise = null;
            });
            return ready;
          }
          async function openCurrentVersion() {
            if (databasePromise) {
              const current = await databasePromise.catch(() => null);
              if (current) return current;
            }
            return openDatabase(null, installV1Schema);
          }
          function upgradeError(cause) {
            const error = new Error(cause?.message || "Unable to upgrade V2 task database");
            error.code = "V2_DB_UPGRADE_FAILED";
            error.cause = cause;
            return error;
          }
          async function hasBusinessData() {
            const businessStores = [stores.PAGES, stores.TASKS, stores.RELATIONS, stores.CHANGES];
            return runTransaction(businessStores, "readonly", async (tx) => {
              const counts = await Promise.all(businessStores.map((storeName) => tx.count(storeName)));
              return counts.some((count) => count > 0);
            });
          }
          function currentDatabaseVersion(database) {
            const version = Number(database && database.version);
            return Number.isInteger(version) && version > 0 ? version : dataSchema.DB_VERSION;
          }
          async function close() {
            if (!databasePromise) return;
            const database = await databasePromise.catch(() => null);
            if (database) database.close();
            databasePromise = null;
          }
          function createTransactionContext(transaction) {
            const objectStore = (storeName) => transaction.objectStore(storeName);
            return Object.freeze({
              get: (storeName, key) => requestAsPromise(objectStore(storeName).get(key)),
              getAll: (storeName) => requestAsPromise(objectStore(storeName).getAll()),
              getByIndex: (storeName, indexName, query) => requestAsPromise(
                objectStore(storeName).index(indexName).get(query)
              ),
              getAllByIndex: (storeName, indexName, query) => requestAsPromise(
                query === void 0 ? objectStore(storeName).index(indexName).getAll() : objectStore(storeName).index(indexName).getAll(query)
              ),
              put: (storeName, value) => requestAsPromise(objectStore(storeName).put(value)),
              delete: (storeName, key) => requestAsPromise(objectStore(storeName).delete(key)),
              clear: (storeName) => requestAsPromise(objectStore(storeName).clear()),
              count: (storeName) => requestAsPromise(objectStore(storeName).count())
            });
          }
          async function trimChanges(context) {
            const changes = await context.getAll(stores.CHANGES);
            const excess = changes.length - dataSchema.MAX_CHANGES;
            if (excess <= 0) return;
            changes.sort((left, right) => Number(left.createdAt) - Number(right.createdAt) || String(left.id).localeCompare(String(right.id)));
            await Promise.all(changes.slice(0, excess).map((change) => context.delete(stores.CHANGES, change.id)));
          }
          async function runTransaction(storeNames, mode, worker) {
            const names = Array.from(new Set(Array.isArray(storeNames) ? storeNames : [storeNames]));
            if (!names.length || typeof worker !== "function") throw new Error("Invalid V2 data transaction");
            const database = await open();
            const transaction = database.transaction(names, mode || "readonly");
            const completion = transactionAsPromise(transaction);
            const context = createTransactionContext(transaction);
            try {
              const result = await worker(context);
              if ((mode || "readonly") === "readwrite" && names.includes(stores.CHANGES)) {
                await trimChanges(context);
              }
              await completion;
              return result;
            } catch (error) {
              try {
                transaction.abort();
              } catch (_abortError) {
              }
              await completion.catch(() => void 0);
              throw error;
            }
          }
          const getAll = (storeName) => runTransaction([storeName], "readonly", (tx) => tx.getAll(storeName));
          async function readTaskGraph() {
            return runTransaction(
              [stores.PAGES, stores.TASKS, stores.RELATIONS],
              "readonly",
              async (tx) => {
                const [tasks, pages, relations] = await Promise.all([
                  tx.getAll(stores.TASKS),
                  tx.getAll(stores.PAGES),
                  tx.getAll(stores.RELATIONS)
                ]);
                return Object.freeze({ tasks, pages, relations });
              }
            );
          }
          async function replaceTaskGraph(snapshot = {}) {
            const tasks = Array.isArray(snapshot.tasks) ? snapshot.tasks : [];
            const pages = Array.isArray(snapshot.pages) ? snapshot.pages : [];
            const relations = Array.isArray(snapshot.relations) ? snapshot.relations : [];
            return runTransaction(
              [stores.PAGES, stores.TASKS, stores.RELATIONS, stores.CHANGES],
              "readwrite",
              async (tx) => {
                await tx.clear(stores.RELATIONS);
                await tx.clear(stores.PAGES);
                await tx.clear(stores.TASKS);
                await tx.clear(stores.CHANGES);
                for (const task of tasks) await tx.put(stores.TASKS, task);
                for (const page of pages) await tx.put(stores.PAGES, page);
                for (const relation of relations) await tx.put(stores.RELATIONS, relation);
                return Object.freeze({
                  tasks: tasks.length,
                  pages: pages.length,
                  relations: relations.length
                });
              }
            );
          }
          function normalizeLegacyTask(rawTask, sortOrder, timestamp) {
            if (!rawTask || !String(rawTask.id || "").trim() || !String(rawTask.title || "").trim()) return null;
            try {
              const task = dataSchema.normalizeTask({
                ...rawTask,
                id: String(rawTask.id).trim(),
                title: String(rawTask.title).trim(),
                sortOrder: Number.isFinite(rawTask.sortOrder) ? rawTask.sortOrder : sortOrder,
                createdAt: Number(rawTask.createdAt) || timestamp,
                updatedAt: Number(rawTask.updatedAt) || timestamp
              }, { now, createId });
              return dataSchema.validateTask(task).valid ? task : null;
            } catch (_error) {
              return null;
            }
          }
          function normalizeLegacyPage(rawPage, timestamp) {
            const rawUrl = rawPage && (rawPage.restoreUrl || rawPage.url || rawPage.normalizedUrl);
            const normalizedUrl = dataSchema.normalizeSafeUrl(rawUrl);
            if (!normalizedUrl) return null;
            try {
              const page = dataSchema.normalizePageEntity({
                ...rawPage,
                id: stablePageId(normalizedUrl),
                normalizedUrl,
                restoreUrl: normalizedUrl,
                faviconUrl: String(rawPage.faviconUrl || rawPage.favicon || rawPage.favIconUrl || ""),
                createdAt: Number(rawPage.createdAt) || timestamp,
                updatedAt: Number(rawPage.updatedAt) || timestamp
              }, { now, createId });
              return dataSchema.validatePageEntity(page).valid ? page : null;
            } catch (_error) {
              return null;
            }
          }
          async function migrateLegacyTasks() {
            const stored = await storage.get([SCHEMA_VERSION_KEY, LEGACY_TASKS_KEY]);
            const migrationVersion = Number(stored && stored[SCHEMA_VERSION_KEY]) || 0;
            const legacyTasks = Array.isArray(stored && stored[LEGACY_TASKS_KEY]) ? stored[LEGACY_TASKS_KEY] : [];
            let database;
            try {
              database = await open();
            } catch (error) {
              throw upgradeError(error);
            }
            const actualVersion = currentDatabaseVersion(database);
            const businessDataExists = await hasBusinessData();
            if (businessDataExists) {
              if (migrationVersion !== actualVersion) await storage.set({ [SCHEMA_VERSION_KEY]: actualVersion });
              return Object.freeze({ migrated: false, tasks: 0, pages: 0, relations: 0, skipped: 0 });
            }
            if (migrationVersion >= 1 && legacyTasks.length === 0) {
              if (migrationVersion !== actualVersion) await storage.set({ [SCHEMA_VERSION_KEY]: actualVersion });
              return Object.freeze({ migrated: false, tasks: 0, pages: 0, relations: 0, skipped: 0 });
            }
            const timestamp = now();
            const taskRecords = [];
            const pagesByUrl = /* @__PURE__ */ new Map();
            const relationCandidatesById = /* @__PURE__ */ new Map();
            let skipped = 0;
            legacyTasks.forEach((rawTask, taskIndex) => {
              const task = normalizeLegacyTask(rawTask, taskIndex, timestamp);
              if (!task) {
                skipped += 1;
                return;
              }
              taskRecords.push(task);
              const rawPages = Array.isArray(rawTask.pages) ? rawTask.pages : [];
              rawPages.forEach((rawPage, relationIndex) => {
                const page = normalizeLegacyPage(rawPage, timestamp);
                if (!page) {
                  skipped += 1;
                  return;
                }
                const canonicalPage = pagesByUrl.get(page.normalizedUrl) || page;
                pagesByUrl.set(page.normalizedUrl, canonicalPage);
                const relationId = dataSchema.makeRelationId(task.id, canonicalPage.id);
                if (relationCandidatesById.has(relationId)) return;
                relationCandidatesById.set(relationId, {
                  task,
                  page: canonicalPage,
                  relationIndex
                });
              });
            });
            const relationCandidates = Array.from(relationCandidatesById.values());
            relationCandidates.sort((left, right) => Number(left.task.sortOrder) - Number(right.task.sortOrder) || left.relationIndex - right.relationIndex || String(left.task.id).localeCompare(String(right.task.id)));
            const primaryPages = /* @__PURE__ */ new Set();
            const relationsById = /* @__PURE__ */ new Map();
            relationCandidates.forEach(({ task, page, relationIndex }) => {
              const id = dataSchema.makeRelationId(task.id, page.id);
              const relation = dataSchema.normalizeRelation({
                id,
                taskId: task.id,
                pageId: page.id,
                isPrimary: !primaryPages.has(page.id),
                role: "related",
                source: "manual",
                manualLocked: true,
                confidence: 1,
                evidence: "manual",
                sortOrder: relationIndex,
                createdAt: timestamp,
                updatedAt: timestamp
              }, { now, createId });
              if (dataSchema.validateRelation(relation).valid) {
                relationsById.set(id, relation);
                primaryPages.add(page.id);
              } else {
                skipped += 1;
              }
            });
            await runTransaction([stores.PAGES, stores.TASKS, stores.RELATIONS], "readwrite", async (tx) => {
              for (const task of taskRecords) await tx.put(stores.TASKS, task);
              for (const page of pagesByUrl.values()) await tx.put(stores.PAGES, page);
              for (const relation of relationsById.values()) await tx.put(stores.RELATIONS, relation);
            });
            await storage.set({ [SCHEMA_VERSION_KEY]: actualVersion });
            return Object.freeze({
              migrated: true,
              tasks: taskRecords.length,
              pages: pagesByUrl.size,
              relations: relationsById.size,
              skipped
            });
          }
          async function clearAllData() {
            await close();
            await new Promise((resolve, reject) => {
              const request = idb.deleteDatabase(dataSchema.DB_NAME);
              request.onsuccess = () => resolve();
              request.onerror = () => reject(request.error || new Error("Unable to clear V2 task database"));
              request.onblocked = () => reject(new Error("Clearing V2 task database is blocked"));
            });
            await storage.remove([SCHEMA_VERSION_KEY, LEGACY_TASKS_KEY, TASK_PROVENANCE_MIGRATION_KEY]);
          }
          return Object.freeze({
            open,
            openCurrentVersion,
            close,
            runTransaction,
            getAll,
            readTaskGraph,
            replaceTaskGraph,
            migrateLegacyTasks,
            clearAllData
          });
        }
        return Object.freeze({
          LEGACY_TASKS_KEY,
          SCHEMA_VERSION_KEY,
          TASK_PROVENANCE_MIGRATION_KEY,
          installV1Schema,
          installV2Schema,
          installV3Schema,
          installV4Schema,
          stablePageId,
          createV2DataStore
        });
      });
    }
  });

  // src/background/v2-task-service.js
  var require_v2_task_service = __commonJS({
    "src/background/v2-task-service.js"(exports, module) {
      (function attachV2TaskService(root, factory) {
        const api = factory();
        if (typeof module === "object" && module.exports) module.exports = api;
        if (root) root.TfaV2TaskService = api;
      })(typeof globalThis === "object" ? globalThis : exports, function createV2TaskServiceModule() {
        "use strict";
        const STORE_KEYS = Object.freeze({ pages: "PAGES", tasks: "TASKS", relations: "RELATIONS", changes: "CHANGES" });
        const BUSINESS_STORES = Object.freeze(["pages", "tasks", "relations"]);
        function clone(value) {
          return value == null ? value : JSON.parse(JSON.stringify(value));
        }
        function same(left, right) {
          return JSON.stringify(left) === JSON.stringify(right);
        }
        function createV2TaskService({ store, schema, now = Date.now, createId } = {}) {
          if (!store || typeof store.runTransaction !== "function" || typeof store.clearAllData !== "function") {
            throw new TypeError("V2 task service requires a data store");
          }
          if (!schema || !schema.STORES || typeof schema.normalizeTask !== "function") {
            throw new TypeError("V2 task service requires the V2 data schema");
          }
          const names = Object.fromEntries(Object.entries(STORE_KEYS).map(([key, constant]) => [key, schema.STORES[constant]]));
          const schemaOptions = () => ({ now, ...createId ? { createId } : {} });
          const txStores = Object.values(names);
          function error(code, message) {
            const result = new Error(message);
            result.code = code;
            return result;
          }
          function requireId(value, field) {
            const id = String(value || "").trim();
            if (!id || id.length > 160) throw error("INVALID_INPUT", `${field} is required`);
            return id;
          }
          function requireOperationToken(value) {
            const token = String(value || "").trim();
            if (!token || token.length > 512) throw error("INVALID_INPUT", "operationToken is required");
            return token;
          }
          function snapshot(records = {}) {
            return {
              ...Object.fromEntries(BUSINESS_STORES.map((key) => [key, clone(records[key] || [])])),
              ...records.restoreToken ? { restoreToken: String(records.restoreToken) } : {},
              ...records.operationToken ? { operationToken: String(records.operationToken) } : {}
            };
          }
          function change(action, before, after) {
            return schema.normalizeChangeRecord({ action, before: snapshot(before), after: snapshot(after) }, schemaOptions());
          }
          async function recordsForPage(tx, pageId) {
            return tx.getAllByIndex(names.relations, "pageId", pageId);
          }
          async function assertPrimaryInvariant(tx, pageIds) {
            const relations = [];
            for (const pageId of new Set(pageIds)) relations.push(...await recordsForPage(tx, pageId));
            const validation = schema.validatePrimaryRelations(relations);
            if (!validation.valid) throw error("PRIMARY_CONFLICT", validation.errors.join("; "));
          }
          async function putChange(tx, record) {
            const existing = await tx.getAll(names.changes);
            const latestTime = existing.reduce((maximum, item) => Math.max(maximum, Number(item.createdAt) || 0), -1);
            const stored = record.createdAt > latestTime ? record : schema.normalizeChangeRecord({
              ...record,
              createdAt: latestTime + 1
            }, schemaOptions());
            await tx.put(names.changes, stored);
            return stored;
          }
          function getLatestChange(changes) {
            return [...changes].sort((a, b) => a.createdAt - b.createdAt || String(a.id).localeCompare(String(b.id))).at(-1);
          }
          async function exportTaskGraph() {
            if (typeof store.readTaskGraph === "function") return clone(await store.readTaskGraph());
            return store.runTransaction([names.tasks, names.pages, names.relations], "readonly", async (tx) => {
              const [tasks, pages, relations] = await Promise.all([
                tx.getAll(names.tasks),
                tx.getAll(names.pages),
                tx.getAll(names.relations)
              ]);
              return clone({ tasks, pages, relations });
            });
          }
          function validateReplacementGraph(message = {}) {
            const graph = message.taskGraph && typeof message.taskGraph === "object" ? message.taskGraph : message;
            const tasks = Array.isArray(graph.tasks) ? graph.tasks.map(clone) : null;
            const pages = Array.isArray(graph.pages) ? graph.pages.map(clone) : null;
            const relations = Array.isArray(graph.relations) ? graph.relations.map(clone) : null;
            if (!tasks || !pages || !relations) throw error("INVALID_BACKUP_GRAPH", "Task graph arrays are required");
            const taskIds = /* @__PURE__ */ new Set();
            const pageIds = /* @__PURE__ */ new Set();
            const relationIds = /* @__PURE__ */ new Set();
            for (const task of tasks) {
              const validation = schema.validateTask(task);
              if (!validation.valid || taskIds.has(task.id)) throw error("INVALID_BACKUP_GRAPH", "Invalid or duplicate task");
              taskIds.add(task.id);
            }
            for (const page of pages) {
              const validation = schema.validatePageEntity(page);
              if (!validation.valid || pageIds.has(page.id)) throw error("INVALID_BACKUP_GRAPH", "Invalid or duplicate page");
              pageIds.add(page.id);
            }
            for (const relation of relations) {
              const validation = schema.validateRelation(relation);
              if (!validation.valid || relationIds.has(relation.id) || !taskIds.has(relation.taskId) || !pageIds.has(relation.pageId)) {
                throw error("INVALID_BACKUP_GRAPH", "Invalid, duplicate, or disconnected relation");
              }
              relationIds.add(relation.id);
            }
            const primary = schema.validatePrimaryRelations(relations);
            if (!primary.valid) throw error("PRIMARY_CONFLICT", primary.errors.join("; "));
            return { tasks, pages, relations };
          }
          async function replaceTaskGraph(message = {}) {
            const graph = validateReplacementGraph(message);
            if (typeof store.replaceTaskGraph === "function") {
              const summary2 = await store.replaceTaskGraph(graph);
              return { ok: true, summary: clone(summary2) };
            }
            const summary = await store.runTransaction(txStores, "readwrite", async (tx) => {
              await tx.clear(names.relations);
              await tx.clear(names.pages);
              await tx.clear(names.tasks);
              await tx.clear(names.changes);
              for (const task of graph.tasks) await tx.put(names.tasks, task);
              for (const page of graph.pages) await tx.put(names.pages, page);
              for (const relation of graph.relations) await tx.put(names.relations, relation);
              return {
                tasks: graph.tasks.length,
                pages: graph.pages.length,
                relations: graph.relations.length
              };
            });
            return { ok: true, summary: clone(summary) };
          }
          async function list() {
            return store.runTransaction([names.tasks, names.pages, names.relations, names.changes], "readonly", async (tx) => {
              const [tasks, pages, relations, changes] = await Promise.all([
                tx.getAll(names.tasks),
                tx.getAll(names.pages),
                tx.getAll(names.relations),
                tx.getAll(names.changes)
              ]);
              const latest = getLatestChange(changes);
              return {
                ok: true,
                tasks: clone(tasks),
                pages: clone(pages),
                relations: clone(relations),
                canUndo: Boolean(latest && latest.undoneAt == null)
              };
            });
          }
          async function createTask(message = {}) {
            return store.runTransaction([names.tasks, names.changes], "readwrite", async (tx) => {
              const tasks = await tx.getAll(names.tasks);
              const input = message.task && typeof message.task === "object" ? message.task : message;
              const nextSortOrder = tasks.length ? tasks.reduce((minimum, task2) => Math.min(minimum, Number.isFinite(Number(task2.sortOrder)) ? Number(task2.sortOrder) : 0), Number.POSITIVE_INFINITY) - 1 : 0;
              const task = schema.normalizeTask({
                ...input,
                sortOrder: input.sortOrder ?? nextSortOrder,
                source: "manual",
                manualLocked: true,
                confidence: 1,
                evidence: "manual",
                modelVersion: ""
              }, schemaOptions());
              if (await tx.get(names.tasks, task.id)) throw error("TASK_EXISTS", "Task already exists");
              await tx.put(names.tasks, task);
              await putChange(tx, change("task.create", {}, { tasks: [task] }));
              return { ok: true, task: clone(task) };
            });
          }
          async function updateTask(message = {}) {
            const taskId = requireId(message.taskId || message.id, "taskId");
            return store.runTransaction([names.tasks, names.changes], "readwrite", async (tx) => {
              const before = await tx.get(names.tasks, taskId);
              if (!before) throw error("TASK_NOT_FOUND", "Task not found");
              const patch = message.patch && typeof message.patch === "object" ? message.patch : message;
              const allowed = ["title", "domain", "status", "sortOrder"];
              const nextInput = { ...before, updatedAt: before.updatedAt };
              allowed.forEach((key) => {
                if (Object.prototype.hasOwnProperty.call(patch, key)) nextInput[key] = patch[key];
              });
              const candidate = schema.normalizeTask(nextInput, schemaOptions());
              const unchanged = allowed.every((key) => before[key] === candidate[key]);
              if (unchanged) return { ok: true, task: clone(before), existing: true, unchanged: true };
              const userFieldChanged = ["title", "domain", "status"].some((key) => before[key] !== candidate[key]);
              const after = schema.normalizeTask({
                ...candidate,
                ...userFieldChanged ? { manualLocked: true } : {},
                updatedAt: now()
              }, schemaOptions());
              await tx.put(names.tasks, after);
              const action = before.sortOrder !== after.sortOrder && allowed.filter((key) => key !== "sortOrder").every((key) => before[key] === after[key]) ? "task.reorder" : "task.update";
              await putChange(tx, change(action, { tasks: [before] }, { tasks: [after] }));
              return { ok: true, task: clone(after) };
            });
          }
          async function reorderTasks(message = {}) {
            if (!Array.isArray(message.orderedTaskIds)) {
              throw error("INVALID_TASK_ORDER", "orderedTaskIds must be an array");
            }
            const orderedTaskIds = message.orderedTaskIds.map((value) => requireId(value, "taskId"));
            return store.runTransaction([names.tasks, names.changes], "readwrite", async (tx) => {
              const tasks = await tx.getAll(names.tasks);
              const existingIds = new Set(tasks.map((task) => task.id));
              const orderedIds = new Set(orderedTaskIds);
              if (orderedTaskIds.length !== tasks.length || orderedIds.size !== orderedTaskIds.length || orderedTaskIds.some((taskId) => !existingIds.has(taskId))) {
                throw error("INVALID_TASK_ORDER", "orderedTaskIds must cover every task exactly once");
              }
              const tasksById = new Map(tasks.map((task) => [task.id, task]));
              const changedBefore = [];
              const changedAfter = [];
              const updatedAt = now();
              orderedTaskIds.forEach((taskId, sortOrder) => {
                const before = tasksById.get(taskId);
                if (before.sortOrder === sortOrder) return;
                changedBefore.push(before);
                changedAfter.push(schema.normalizeTask({ ...before, sortOrder, updatedAt }, schemaOptions()));
              });
              if (!changedAfter.length) {
                return {
                  ok: true,
                  tasks: orderedTaskIds.map((taskId) => clone(tasksById.get(taskId))),
                  existing: true,
                  unchanged: true
                };
              }
              for (const task of changedAfter) await tx.put(names.tasks, task);
              await putChange(tx, change(
                "task.reorder",
                { tasks: changedBefore },
                { tasks: changedAfter }
              ));
              const changedById = new Map(changedAfter.map((task) => [task.id, task]));
              return {
                ok: true,
                tasks: orderedTaskIds.map((taskId) => clone(changedById.get(taskId) || tasksById.get(taskId)))
              };
            });
          }
          async function deleteTask(message = {}) {
            const taskId = requireId(message.taskId || message.id, "taskId");
            const operationToken = message.operationToken ? requireOperationToken(message.operationToken) : "";
            const replacements = message.replacementTaskIdsByPage && typeof message.replacementTaskIdsByPage === "object" ? message.replacementTaskIdsByPage : {};
            return store.runTransaction([names.tasks, names.relations, names.changes], "readwrite", async (tx) => {
              if (operationToken) {
                const previous = (await tx.getAll(names.changes)).find((item) => item.after?.operationToken === operationToken);
                if (previous && previous.before?.tasks?.[0]?.id !== taskId) {
                  throw error("OPERATION_TOKEN_CONFLICT", "operationToken belongs to another task");
                }
                if (previous) return {
                  ok: true,
                  taskId,
                  operationToken,
                  existing: true,
                  undone: previous.undoneAt != null
                };
              }
              const task = await tx.get(names.tasks, taskId);
              if (!task) throw error("TASK_NOT_FOUND", "Task not found");
              const removed = await tx.getAllByIndex(names.relations, "taskId", taskId);
              const changedBefore = [...removed];
              const changedAfter = [];
              for (const relation of removed) {
                if (!relation.isPrimary) continue;
                const others = (await recordsForPage(tx, relation.pageId)).filter((item) => item.taskId !== taskId);
                if (!others.length) continue;
                const replacementTaskId = replacements[relation.pageId] || (removed.length === 1 ? message.replacementTaskId : "");
                const replacement = others.find((item) => item.taskId === replacementTaskId);
                if (!replacement) throw error("REPLACEMENT_REQUIRED", `replacementTaskId is required for page ${relation.pageId}`);
                changedBefore.push(replacement);
                const promoted = schema.normalizeRelation({ ...replacement, isPrimary: true, updatedAt: now() }, schemaOptions());
                changedAfter.push(promoted);
                await tx.put(names.relations, promoted);
              }
              for (const relation of removed) await tx.delete(names.relations, relation.id);
              await tx.delete(names.tasks, taskId);
              await assertPrimaryInvariant(tx, removed.map((item) => item.pageId));
              await putChange(tx, change(
                "task.delete",
                { tasks: [task], relations: changedBefore },
                { relations: changedAfter, operationToken }
              ));
              return { ok: true, taskId, ...operationToken ? { operationToken } : {} };
            });
          }
          async function restoreTask(message = {}) {
            const restoreToken = requireId(message.restoreToken, "restoreToken");
            const rawTask = message.task && typeof message.task === "object" ? message.task : message.snapshot;
            if (!rawTask || typeof rawTask !== "object") throw error("INVALID_INPUT", "Task snapshot is required");
            return store.runTransaction(txStores, "readwrite", async (tx) => {
              const previousChanges = await tx.getAll(names.changes);
              const previousRestore = previousChanges.find((item) => item.after?.restoreToken === restoreToken);
              if (previousRestore) {
                const restoredId = previousRestore.after?.tasks?.[0]?.id;
                const restoredTask = restoredId ? await tx.get(names.tasks, restoredId) : null;
                if (restoredTask && same(restoredTask, previousRestore.after?.tasks?.[0])) {
                  return { ok: true, task: clone(restoredTask), existing: true };
                }
                return { ok: true, task: null, existing: true, consumed: true, undone: previousRestore.undoneAt != null };
              }
              let task = schema.normalizeTask(rawTask, schemaOptions());
              if (await tx.get(names.tasks, task.id)) {
                let attempts = 0;
                do {
                  task = schema.normalizeTask({ ...rawTask, id: void 0 }, schemaOptions());
                  attempts += 1;
                  if (attempts > 100) throw error("ID_CONFLICT", "Unable to allocate restored task id");
                } while (await tx.get(names.tasks, task.id));
              }
              const createdPages = [];
              const relations = [];
              const seenUrls = /* @__PURE__ */ new Set();
              for (const [index, rawPage] of (Array.isArray(rawTask.pages) ? rawTask.pages : []).entries()) {
                let normalizedPage;
                try {
                  normalizedPage = schema.normalizePageEntity(rawPage, schemaOptions());
                } catch (_) {
                  continue;
                }
                if (seenUrls.has(normalizedPage.normalizedUrl)) continue;
                seenUrls.add(normalizedPage.normalizedUrl);
                let page = await tx.getByIndex(names.pages, "normalizedUrl", normalizedPage.normalizedUrl);
                if (!page) {
                  page = normalizedPage;
                  createdPages.push(page);
                  await tx.put(names.pages, page);
                }
                const currentRelations = await recordsForPage(tx, page.id);
                const source = ["manual", "rule", "ai"].includes(rawPage.source) ? rawPage.source : rawPage.source === "suggested" ? "ai" : "manual";
                const relation = schema.normalizeRelation({
                  taskId: task.id,
                  pageId: page.id,
                  isPrimary: currentRelations.length === 0,
                  role: rawPage.role || "related",
                  source,
                  manualLocked: rawPage.manualLocked === true,
                  confidence: source === "manual" ? 1 : rawPage.confidence ?? 0.5,
                  evidence: source === "manual" ? "manual" : rawPage.evidence || "trash-restore",
                  sortOrder: rawPage.sortOrder == null ? index : rawPage.sortOrder
                }, schemaOptions());
                relations.push(relation);
                await tx.put(names.relations, relation);
              }
              await tx.put(names.tasks, task);
              await assertPrimaryInvariant(tx, relations.map((item) => item.pageId));
              await putChange(tx, change("task.create", {}, {
                tasks: [task],
                pages: createdPages,
                relations,
                restoreToken
              }));
              return { ok: true, task: clone(task), relations: clone(relations), existing: false };
            });
          }
          async function addRelation(message = {}) {
            const taskId = requireId(message.taskId, "taskId");
            return store.runTransaction([names.pages, names.tasks, names.relations, names.changes], "readwrite", async (tx) => {
              if (!await tx.get(names.tasks, taskId)) throw error("TASK_NOT_FOUND", "Task not found");
              let page = null;
              if (message.pageId) {
                page = await tx.get(names.pages, requireId(message.pageId, "pageId"));
                if (!page) throw error("PAGE_NOT_FOUND", "Page not found");
              } else if (message.page && typeof message.page === "object") {
                const normalized = schema.normalizePageEntity(message.page, schemaOptions());
                const existingPage = await tx.getByIndex(names.pages, "normalizedUrl", normalized.normalizedUrl);
                page = existingPage ? schema.normalizePageEntity({
                  ...existingPage,
                  ...message.page,
                  id: existingPage.id,
                  normalizedUrl: normalized.normalizedUrl,
                  restoreUrl: normalized.restoreUrl,
                  createdAt: existingPage.createdAt,
                  updatedAt: now()
                }, schemaOptions()) : normalized;
              }
              if (!page) throw error("PAGE_NOT_FOUND", "Page not found");
              const beforePage = await tx.get(names.pages, page.id);
              const beforeRelations = await recordsForPage(tx, page.id);
              const relationId = schema.makeRelationId(taskId, page.id);
              const existing = beforeRelations.find((item) => item.id === relationId);
              const shouldBePrimary = message.isPrimary === true || beforeRelations.length === 0;
              const afterRelations = beforeRelations.map((item) => shouldBePrimary && item.id !== relationId && item.isPrimary ? schema.normalizeRelation({ ...item, isPrimary: false, updatedAt: now() }, schemaOptions()) : item);
              const relation = schema.normalizeRelation({
                ...existing || {},
                taskId,
                pageId: page.id,
                role: message.role ?? existing?.role,
                source: "manual",
                sortOrder: message.sortOrder ?? existing?.sortOrder,
                isPrimary: shouldBePrimary || existing?.isPrimary === true,
                createdAt: existing?.createdAt,
                updatedAt: now()
              }, schemaOptions());
              const relationIndex = afterRelations.findIndex((item) => item.id === relation.id);
              if (relationIndex >= 0) afterRelations[relationIndex] = relation;
              else afterRelations.push(relation);
              await tx.put(names.pages, page);
              for (const item of afterRelations) await tx.put(names.relations, item);
              await assertPrimaryInvariant(tx, [page.id]);
              const before = { pages: beforePage ? [beforePage] : [], relations: beforeRelations };
              const after = { pages: [page], relations: afterRelations };
              await putChange(tx, change(existing ? "relation.update" : "relation.add", before, after));
              return { ok: true, page: clone(page), relation: clone(relation) };
            });
          }
          async function removeRelation(message = {}) {
            const taskId = requireId(message.taskId, "taskId");
            const pageId = requireId(message.pageId, "pageId");
            return store.runTransaction([names.relations, names.changes], "readwrite", async (tx) => {
              const relationId = schema.makeRelationId(taskId, pageId);
              const relation = await tx.get(names.relations, relationId);
              if (!relation) throw error("RELATION_NOT_FOUND", "Relation not found");
              const before = await recordsForPage(tx, pageId);
              const after = before.filter((item) => item.id !== relationId);
              if (relation.isPrimary && after.length) {
                if (!message.replacementTaskId) throw error("REPLACEMENT_REQUIRED", "replacementTaskId is required");
                const replacementId = requireId(message.replacementTaskId, "replacementTaskId");
                const index = after.findIndex((item) => item.taskId === replacementId);
                if (index < 0) throw error("INVALID_REPLACEMENT", "Replacement relation not found");
                after[index] = schema.normalizeRelation({ ...after[index], isPrimary: true, updatedAt: now() }, schemaOptions());
              }
              await tx.delete(names.relations, relationId);
              for (const item of after) await tx.put(names.relations, item);
              await assertPrimaryInvariant(tx, [pageId]);
              await putChange(tx, change("relation.remove", { relations: before }, { relations: after }));
              return { ok: true, relationId };
            });
          }
          async function moveRelation(message = {}) {
            const sourceTaskId = requireId(message.sourceTaskId, "sourceTaskId");
            const targetTaskId = requireId(message.targetTaskId, "targetTaskId");
            const pageId = requireId(message.pageId, "pageId");
            if (sourceTaskId === targetTaskId) throw error("INVALID_INPUT", "sourceTaskId and targetTaskId must differ");
            return store.runTransaction([names.tasks, names.relations, names.changes], "readwrite", async (tx) => {
              const [sourceTask, targetTask] = await Promise.all([
                tx.get(names.tasks, sourceTaskId),
                tx.get(names.tasks, targetTaskId)
              ]);
              if (!sourceTask || !targetTask) throw error("TASK_NOT_FOUND", "Source or target task not found");
              const before = await recordsForPage(tx, pageId);
              const source = before.find((item) => item.taskId === sourceTaskId);
              if (!source) throw error("RELATION_NOT_FOUND", "Source relation not found");
              const existingTarget = before.find((item) => item.taskId === targetTaskId);
              const targetRelations = await tx.getAllByIndex(names.relations, "taskId", targetTaskId);
              const expectedTargetPageIds = /* @__PURE__ */ new Set([
                ...targetRelations.map((relation) => relation.pageId),
                pageId
              ]);
              let orderedTargetPageIds = null;
              if (message.orderedTargetPageIds != null) {
                if (!Array.isArray(message.orderedTargetPageIds)) {
                  throw error("INVALID_RELATION_ORDER", "orderedTargetPageIds must be an array");
                }
                orderedTargetPageIds = message.orderedTargetPageIds.map((value) => requireId(value, "pageId"));
                const orderedIds = new Set(orderedTargetPageIds);
                if (orderedTargetPageIds.length !== expectedTargetPageIds.size || orderedIds.size !== orderedTargetPageIds.length || orderedTargetPageIds.some((id) => !expectedTargetPageIds.has(id))) {
                  throw error("INVALID_RELATION_ORDER", "orderedTargetPageIds must cover the target task after the move exactly once");
                }
              }
              const targetRelation = schema.normalizeRelation({
                ...existingTarget || {},
                taskId: targetTaskId,
                pageId,
                role: existingTarget?.role || "core",
                source: "manual",
                manualLocked: true,
                isPrimary: true,
                confidence: 1,
                evidence: "manual",
                modelVersion: "",
                sortOrder: existingTarget?.sortOrder ?? targetRelations.reduce((maximum, relation) => Math.max(maximum, Number(relation.sortOrder) || 0), -1) + 1,
                createdAt: existingTarget?.createdAt,
                updatedAt: now()
              }, schemaOptions());
              let after = before.filter((item) => item.taskId !== sourceTaskId && item.taskId !== targetTaskId).map((item) => item.isPrimary ? schema.normalizeRelation({ ...item, isPrimary: false, updatedAt: now() }, schemaOptions()) : item);
              after.push(targetRelation);
              const targetByPageId = new Map(targetRelations.map((relation) => [relation.pageId, relation]));
              targetByPageId.set(pageId, targetRelation);
              const finalOrder = orderedTargetPageIds || [...targetRelations].filter((relation) => relation.pageId !== pageId).sort((left, right) => Number(left.sortOrder) - Number(right.sortOrder) || String(left.id).localeCompare(String(right.id))).map((relation) => relation.pageId).concat(pageId);
              const orderedTargetRelations = finalOrder.map((id, sortOrder) => {
                const relation = targetByPageId.get(id);
                return schema.normalizeRelation({
                  ...relation,
                  sortOrder,
                  updatedAt: relation.sortOrder === sortOrder ? relation.updatedAt : now()
                }, schemaOptions());
              });
              const movedTarget = orderedTargetRelations.find((relation) => relation.pageId === pageId);
              after = after.map((relation) => relation.taskId === targetTaskId ? movedTarget : relation);
              const beforeById = new Map([...before, ...targetRelations].map((relation) => [relation.id, relation]));
              const afterById = new Map([...after, ...orderedTargetRelations].map((relation) => [relation.id, relation]));
              await tx.delete(names.relations, source.id);
              for (const item of afterById.values()) await tx.put(names.relations, item);
              await assertPrimaryInvariant(tx, [pageId]);
              await putChange(tx, change(
                "relation.update",
                { relations: [...beforeById.values()] },
                { relations: [...afterById.values()] }
              ));
              return {
                ok: true,
                relation: clone(movedTarget),
                relations: clone(orderedTargetRelations),
                removedRelationId: source.id
              };
            });
          }
          async function reorderRelations(message = {}) {
            const taskId = requireId(message.taskId, "taskId");
            if (!Array.isArray(message.orderedPageIds)) throw error("INVALID_RELATION_ORDER", "orderedPageIds must be an array");
            const orderedPageIds = message.orderedPageIds.map((value) => requireId(value, "pageId"));
            return store.runTransaction([names.tasks, names.relations, names.changes], "readwrite", async (tx) => {
              if (!await tx.get(names.tasks, taskId)) throw error("TASK_NOT_FOUND", "Task not found");
              const relations = await tx.getAllByIndex(names.relations, "taskId", taskId);
              const existingIds = new Set(relations.map((relation) => relation.pageId));
              const orderedIds = new Set(orderedPageIds);
              if (orderedPageIds.length !== relations.length || orderedIds.size !== orderedPageIds.length || orderedPageIds.some((pageId) => !existingIds.has(pageId))) {
                throw error("INVALID_RELATION_ORDER", "orderedPageIds must cover every task relation exactly once");
              }
              const relationsByPageId = new Map(relations.map((relation) => [relation.pageId, relation]));
              const changedBefore = [], changedAfter = [], updatedAt = now();
              orderedPageIds.forEach((pageId, sortOrder) => {
                const before = relationsByPageId.get(pageId);
                if (before.sortOrder === sortOrder) return;
                changedBefore.push(before);
                changedAfter.push(schema.normalizeRelation({ ...before, sortOrder, updatedAt }, schemaOptions()));
              });
              if (!changedAfter.length) return {
                ok: true,
                relations: orderedPageIds.map((pageId) => clone(relationsByPageId.get(pageId))),
                existing: true,
                unchanged: true
              };
              for (const relation of changedAfter) await tx.put(names.relations, relation);
              await putChange(tx, change("relation.reorder", { relations: changedBefore }, { relations: changedAfter }));
              const changedByPageId = new Map(changedAfter.map((relation) => [relation.pageId, relation]));
              return { ok: true, relations: orderedPageIds.map((pageId) => clone(changedByPageId.get(pageId) || relationsByPageId.get(pageId))) };
            });
          }
          async function undoLast() {
            return store.runTransaction(txStores, "readwrite", async (tx) => {
              const changes = await tx.getAll(names.changes);
              const latest = getLatestChange(changes);
              if (!latest || latest.undoneAt != null) throw error("NOTHING_TO_UNDO", "Nothing to undo");
              if (String(latest.action || "").startsWith("suggestion.")) {
                throw error("SUGGESTION_UNDO_REQUIRED", "Use the suggestion undo action");
              }
              for (const key of BUSINESS_STORES) {
                const before = new Map((latest.before?.[key] || []).map((item) => [item.id, item]));
                const after = new Map((latest.after?.[key] || []).map((item) => [item.id, item]));
                for (const id of /* @__PURE__ */ new Set([...before.keys(), ...after.keys()])) {
                  const current = await tx.get(names[key], id);
                  if (!same(current || null, after.get(id) || null)) throw error("UNDO_CONFLICT", "Current state has changed");
                }
              }
              for (const key of BUSINESS_STORES) {
                const before = new Map((latest.before?.[key] || []).map((item) => [item.id, item]));
                const after = new Map((latest.after?.[key] || []).map((item) => [item.id, item]));
                for (const id of after.keys()) if (!before.has(id)) await tx.delete(names[key], id);
                for (const item of before.values()) await tx.put(names[key], item);
              }
              await tx.put(names.changes, { ...latest, undoneAt: Math.max(now(), latest.createdAt) });
              const operationToken = latest.action === "task.delete" ? latest.after?.operationToken : "";
              return {
                ok: true,
                changeId: latest.id,
                action: latest.action,
                ...operationToken ? { trashCompensation: { action: "remove", operationToken } } : {}
              };
            });
          }
          async function clearData() {
            await store.clearAllData();
            return { ok: true };
          }
          function safe(handler) {
            return async (message = {}) => {
              try {
                return await handler(message);
              } catch (failure) {
                return { ok: false, code: failure?.code || "V2_DATA_ERROR", error: failure?.message || String(failure) };
              }
            };
          }
          return Object.freeze({
            list,
            exportTaskGraph,
            replaceTaskGraph,
            createTask,
            updateTask,
            reorderTasks,
            deleteTask,
            restoreTask,
            addRelation,
            removeRelation,
            moveRelation,
            reorderRelations,
            undoLast,
            clearData,
            handlers: Object.freeze({
              V2_TASK_LIST: safe(list),
              V2_TASK_CREATE: safe(createTask),
              V2_TASK_UPDATE: safe(updateTask),
              V2_TASK_REORDER: safe(reorderTasks),
              V2_TASK_DELETE: safe(deleteTask),
              V2_TASK_RESTORE: safe(restoreTask),
              V2_RELATION_ADD: safe(addRelation),
              V2_RELATION_REMOVE: safe(removeRelation),
              V2_RELATION_MOVE: safe(moveRelation),
              V2_RELATION_REORDER: safe(reorderRelations),
              V2_TASK_UNDO_LAST: safe(undoLast),
              V2_TASK_DATA_CLEAR: safe(clearData)
            })
          });
        }
        return Object.freeze({ createV2TaskService });
      });
    }
  });

  // src/background/v2-backup-service.js
  var require_v2_backup_service = __commonJS({
    "src/background/v2-backup-service.js"(exports, module) {
      (function attachV2BackupService(root, factory) {
        const api = factory(
          root.TfaV2SafeDataBackup || (typeof __require === "function" ? require_v2_safe_data_backup() : null)
        );
        if (typeof module === "object" && module.exports) module.exports = api;
        if (root) root.TfaV2BackupService = api;
      })(typeof globalThis === "object" ? globalThis : exports, function createV2BackupServiceModule(SafeBackup) {
        "use strict";
        function clone(value) {
          return value == null ? value : JSON.parse(JSON.stringify(value));
        }
        function failure(code, message, cause, details) {
          const error = new Error(message);
          error.code = code;
          if (cause) error.cause = cause;
          if (details) error.details = details;
          return error;
        }
        function createV2BackupService({
          taskService,
          siteSpaceAdapter = null,
          dockAdapter = null,
          safeBackup = SafeBackup,
          extensionVersion = "",
          now = Date.now
        } = {}) {
          if (!taskService || typeof taskService.exportTaskGraph !== "function" || typeof taskService.replaceTaskGraph !== "function") {
            throw new TypeError("V2 backup service requires a task service with atomic graph replacement");
          }
          if (!safeBackup || typeof safeBackup.buildSafeDataBackup !== "function" || typeof safeBackup.parseAndValidateBackup !== "function") {
            throw new TypeError("V2 backup service requires the safe backup module");
          }
          const adapters = Object.freeze({ siteSpace: siteSpaceAdapter, dock: dockAdapter });
          let importInFlight = false;
          function usableAdapter(adapter) {
            return Boolean(adapter && typeof adapter.read === "function" && typeof adapter.replace === "function");
          }
          async function readOptionalSection(name, explicit) {
            if (explicit !== void 0) return clone(explicit);
            const adapter = adapters[name];
            return usableAdapter(adapter) ? clone(await adapter.read()) : null;
          }
          async function exportBackup(options = {}) {
            const [taskGraph, siteSpace, dock] = await Promise.all([
              taskService.exportTaskGraph(),
              readOptionalSection("siteSpace", options.siteSpace),
              readOptionalSection("dock", options.dock)
            ]);
            return safeBackup.buildSafeDataBackup(
              { taskGraph, siteSpace, dock },
              { extensionVersion, now }
            );
          }
          function previewImport(input) {
            const backup = safeBackup.parseAndValidateBackup(input);
            return Object.freeze({
              ok: true,
              formatVersion: backup.formatVersion,
              migratedFrom: backup.migratedFrom || null,
              summary: clone(backup.summary),
              sections: Object.freeze({
                taskGraph: true,
                siteSpace: backup.siteSpace !== null,
                dock: backup.dock !== null
              })
            });
          }
          async function importBackup(input, options = {}) {
            if (importInFlight) {
              throw failure("BACKUP_IMPORT_IN_PROGRESS", "Another backup import is already in progress");
            }
            importInFlight = true;
            try {
              const backup = safeBackup.parseAndValidateBackup(input);
              const sections = ["siteSpace", "dock"].filter((name) => backup[name] !== null);
              for (const name of sections) {
                if (!usableAdapter(adapters[name])) {
                  throw failure("BACKUP_SECTION_UNAVAILABLE", `Cannot restore ${name} in this runtime`);
                }
              }
              const previousSnapshots = options.previousSnapshots && typeof options.previousSnapshots === "object" ? options.previousSnapshots : {};
              const previous = {
                taskGraph: previousSnapshots.taskGraph ? clone(previousSnapshots.taskGraph) : await taskService.exportTaskGraph()
              };
              for (const name of sections) {
                previous[name] = Object.hasOwn(previousSnapshots, name) ? clone(previousSnapshots[name]) : clone(await adapters[name].read());
              }
              const appliedExternal = [];
              let taskGraphApplied = false;
              try {
                await taskService.replaceTaskGraph({ taskGraph: backup.taskGraph });
                taskGraphApplied = true;
                for (const name of sections) {
                  appliedExternal.push(name);
                  await adapters[name].replace(clone(backup[name]));
                }
                return Object.freeze({
                  ok: true,
                  formatVersion: backup.formatVersion,
                  migratedFrom: backup.migratedFrom || null,
                  summary: clone(backup.summary),
                  consistency: "task-graph-transactional-external-compensated"
                });
              } catch (cause) {
                const failedSections = [];
                for (const name of [...appliedExternal].reverse()) {
                  try {
                    await adapters[name].replace(clone(previous[name]));
                  } catch (_error) {
                    failedSections.push(name);
                  }
                }
                if (taskGraphApplied) {
                  try {
                    await taskService.replaceTaskGraph({ taskGraph: previous.taskGraph });
                  } catch (_error) {
                    failedSections.push("taskGraph");
                  }
                }
                if (failedSections.length) {
                  throw failure(
                    "BACKUP_IMPORT_PARTIAL_ROLLBACK",
                    "Backup import failed and one or more previous snapshots could not be restored",
                    cause,
                    { failedSections: Object.freeze([...new Set(failedSections)]) }
                  );
                }
                throw failure("BACKUP_IMPORT_ROLLED_BACK", "Backup import failed; previous data was restored", cause);
              }
            } finally {
              importInFlight = false;
            }
          }
          return Object.freeze({ exportBackup, previewImport, importBackup });
        }
        return Object.freeze({ createV2BackupService });
      });
    }
  });

  // src/background/v2-behavior-store.js
  var require_v2_behavior_store = __commonJS({
    "src/background/v2-behavior-store.js"(exports, module) {
      (function attachV2BehaviorStore(root, factory) {
        const api = factory();
        if (typeof module === "object" && module.exports) module.exports = api;
        root.TfaV2BehaviorStore = api;
      })(typeof globalThis === "object" ? globalThis : exports, function createV2BehaviorStoreModule() {
        "use strict";
        const DEFAULT_RETENTION_MS = 30 * 24 * 60 * 60 * 1e3;
        const DEFAULT_PURGE_LIMIT = 500;
        const EXPLICIT_RESTORE_KINDS = /* @__PURE__ */ new Set([
          "task_restore",
          "trash_tab_restore",
          "trash_group_restore",
          "trash_batch_restore",
          "session_restore"
        ]);
        function createV2BehaviorStore({ dataStore, schema, now = () => Date.now(), createId } = {}) {
          const dataSchema = schema || typeof globalThis === "object" && globalThis.TfaV2DataSchema;
          if (!dataStore || typeof dataStore.runTransaction !== "function") {
            throw new Error("V2 behavior store requires a V2 data store");
          }
          if (!dataSchema || !dataSchema.STORES || typeof dataSchema.normalizeTabInstance !== "function" || typeof dataSchema.normalizeBehaviorEvent !== "function") {
            throw new Error("V2 behavior store requires the V2 behavior schema");
          }
          const stores = dataSchema.STORES;
          const instanceStore = stores.TAB_INSTANCES;
          const eventStore = stores.BEHAVIOR_EVENTS;
          if (!instanceStore || !eventStore) throw new Error("V2 behavior stores are unavailable");
          const normalizeInstance = (value) => dataSchema.normalizeTabInstance(value, { now, createId });
          const normalizeEvent = (value) => dataSchema.normalizeBehaviorEvent(value, { now, createId });
          const instanceId = (browserSessionId, tabId) => typeof dataSchema.makeTabInstanceId === "function" ? dataSchema.makeTabInstanceId(browserSessionId, tabId) : `${String(browserSessionId || "").trim()}:${Number(tabId)}`;
          async function getTabInstance(browserSessionId, tabId) {
            const id = tabId === void 0 ? String(browserSessionId || "") : instanceId(browserSessionId, tabId);
            if (!id) return null;
            const value = await dataStore.runTransaction(
              [instanceStore],
              "readonly",
              (tx) => tx.get(instanceStore, id)
            );
            return value || null;
          }
          async function listTabInstances(browserSessionId) {
            const values = await dataStore.runTransaction(
              [instanceStore],
              "readonly",
              (tx) => tx.getAll(instanceStore)
            );
            const sessionId = browserSessionId == null ? "" : String(browserSessionId).trim();
            return (sessionId ? values.filter((value) => value.browserSessionId === sessionId) : values).sort((left, right) => Number(left.windowId) - Number(right.windowId) || Number(left.index) - Number(right.index) || Number(left.tabId) - Number(right.tabId));
          }
          async function upsertTabInstance(value) {
            const normalized = normalizeInstance(value);
            await dataStore.runTransaction([instanceStore], "readwrite", (tx) => tx.put(instanceStore, normalized));
            return normalized;
          }
          async function deleteTabInstance(browserSessionId, tabId) {
            const id = tabId === void 0 ? String(browserSessionId || "") : instanceId(browserSessionId, tabId);
            if (!id) return false;
            return dataStore.runTransaction([instanceStore], "readwrite", async (tx) => {
              const existing = await tx.get(instanceStore, id);
              if (!existing) return false;
              await tx.delete(instanceStore, id);
              return true;
            });
          }
          async function replaceTabId(input, oldTabId, newTabId) {
            const options = input && typeof input === "object" ? input : { browserSessionId: input, oldTabId, newTabId };
            const browserSessionId = String(options.browserSessionId || "").trim();
            const previousTabId = Number(options.oldTabId ?? options.removedTabId);
            const replacementTabId = Number(options.newTabId ?? options.addedTabId);
            const previousId = instanceId(browserSessionId, previousTabId);
            return dataStore.runTransaction([instanceStore], "readwrite", async (tx) => {
              const previous = await tx.get(instanceStore, previousId);
              if (!previous) return null;
              const replacement = normalizeInstance({
                ...previous,
                ...options.patch,
                id: instanceId(browserSessionId, replacementTabId),
                browserSessionId,
                tabId: replacementTabId
              });
              await tx.delete(instanceStore, previousId);
              await tx.put(instanceStore, replacement);
              return replacement;
            });
          }
          async function recordBehaviorEvent(value) {
            const normalized = normalizeEvent(value);
            await dataStore.runTransaction([eventStore], "readwrite", (tx) => tx.put(eventStore, normalized));
            return normalized;
          }
          async function recordExplicitRestoreEpisode(value = {}) {
            const episodeId = String(value.episodeId || "").trim();
            const restoreKind = String(value.restoreKind || "").trim();
            const pageIds = [...new Set((Array.isArray(value.pageIds) ? value.pageIds : []).map((item) => String(item || "").trim()).filter(Boolean))].slice(0, 32);
            if (!episodeId || !EXPLICIT_RESTORE_KINDS.has(restoreKind) || !pageIds.length) {
              throw new TypeError("Invalid explicit restore episode");
            }
            const id = String(value.id || `event-${episodeId}`).trim();
            return dataStore.runTransaction([eventStore], "readwrite", async (tx) => {
              const existing = await tx.get(eventStore, id);
              if (existing && existing.type !== "restore.explicit") throw new TypeError("Restore episode id conflict");
              if (existing?.context?.restoreKind && existing.context.restoreKind !== restoreKind) {
                throw new TypeError("Restore episode kind conflict");
              }
              const mergedPageIds = [.../* @__PURE__ */ new Set([
                ...Array.isArray(existing?.context?.pageIds) ? existing.context.pageIds : [],
                ...pageIds
              ])].slice(0, 32);
              const normalized = normalizeEvent({
                ...existing || {},
                id,
                type: "restore.explicit",
                browserSessionId: value.browserSessionId || existing?.browserSessionId,
                occurredAt: existing?.occurredAt ?? value.occurredAt,
                pageId: mergedPageIds[0],
                context: { episodeId, restoreKind, pageIds: mergedPageIds }
              });
              await tx.put(eventStore, normalized);
              return normalized;
            });
          }
          async function purgeExpiredEvents(options = {}) {
            const currentTime = Number.isFinite(options.now) ? options.now : now();
            const retentionMs = Number.isFinite(options.retentionMs) && options.retentionMs >= 0 ? options.retentionMs : DEFAULT_RETENTION_MS;
            const limit = Number.isInteger(options.limit) && options.limit > 0 ? options.limit : DEFAULT_PURGE_LIMIT;
            const cutoff = currentTime - retentionMs;
            return dataStore.runTransaction([eventStore], "readwrite", async (tx) => {
              const events = await tx.getAll(eventStore);
              const expired = events.filter((event) => Number(event.occurredAt) < cutoff).sort((left, right) => Number(left.occurredAt) - Number(right.occurredAt) || String(left.id).localeCompare(String(right.id))).slice(0, limit);
              for (const event of expired) await tx.delete(eventStore, event.id);
              return expired.length;
            });
          }
          async function clearBehaviorEvents() {
            return dataStore.runTransaction([eventStore], "readwrite", async (tx) => {
              const count = await tx.count(eventStore);
              await tx.clear(eventStore);
              return count;
            });
          }
          async function reconcileSnapshot(instances, suppliedBrowserSessionId = "") {
            const normalized = (Array.isArray(instances) ? instances : []).map(normalizeInstance);
            const sessionIds = new Set(normalized.map((item) => item.browserSessionId));
            if (sessionIds.size > 1) throw new TypeError("Snapshot must belong to one browser session");
            const suppliedSessionId = String(suppliedBrowserSessionId || "").trim();
            const snapshotSessionId = sessionIds.size ? sessionIds.values().next().value : "";
            if (suppliedSessionId && snapshotSessionId && suppliedSessionId !== snapshotSessionId) {
              throw new TypeError("Snapshot browser session does not match");
            }
            const browserSessionId = snapshotSessionId || suppliedSessionId;
            if (!browserSessionId) {
              return Object.freeze({ added: 0, updated: 0, removed: 0 });
            }
            instanceId(browserSessionId, 0);
            return dataStore.runTransaction([instanceStore], "readwrite", async (tx) => {
              const existing = (await tx.getAll(instanceStore)).filter((item) => item.browserSessionId === browserSessionId);
              const existingById = new Map(existing.map((item) => [item.id, item]));
              const nextIds = /* @__PURE__ */ new Set();
              let added = 0;
              let updated = 0;
              for (const item of normalized) {
                if (nextIds.has(item.id)) continue;
                nextIds.add(item.id);
                const previous = existingById.get(item.id);
                if (!previous) added += 1;
                else if (JSON.stringify(previous) !== JSON.stringify(item)) updated += 1;
                if (!previous || JSON.stringify(previous) !== JSON.stringify(item)) {
                  await tx.put(instanceStore, item);
                }
              }
              const removedItems = existing.filter((item) => !nextIds.has(item.id));
              for (const item of removedItems) await tx.delete(instanceStore, item.id);
              return Object.freeze({ added, updated, removed: removedItems.length });
            });
          }
          return Object.freeze({
            getTabInstance,
            listTabInstances,
            upsertTabInstance,
            deleteTabInstance,
            replaceTabId,
            recordBehaviorEvent,
            recordExplicitRestoreEpisode,
            purgeExpiredEvents,
            clearBehaviorEvents,
            reconcileSnapshot
          });
        }
        return Object.freeze({ DEFAULT_RETENTION_MS, DEFAULT_PURGE_LIMIT, createV2BehaviorStore });
      });
    }
  });

  // src/background/v2-inbox-service.js
  var require_v2_inbox_service = __commonJS({
    "src/background/v2-inbox-service.js"(exports, module) {
      (function attachV2InboxService(root, factory) {
        const api = factory();
        if (typeof module === "object" && module.exports) module.exports = api;
        if (root) root.TfaV2InboxService = api;
      })(typeof globalThis === "object" ? globalThis : exports, function createV2InboxServiceModule() {
        "use strict";
        function createV2InboxService({
          dataStore,
          schema,
          stablePageId,
          preferenceStore,
          pageObservationLogic,
          now = Date.now
        } = {}) {
          if (!dataStore || typeof dataStore.runTransaction !== "function") {
            throw new TypeError("V2 inbox service requires a data store");
          }
          if (!schema || !schema.STORES || typeof schema.normalizeSafeUrl !== "function" || typeof schema.normalizePageEntity !== "function") {
            throw new TypeError("V2 inbox service requires the V2 data schema");
          }
          if (typeof stablePageId !== "function") throw new TypeError("V2 inbox service requires stablePageId");
          const stores = schema.STORES;
          const clock = () => {
            const value = Number(typeof now === "function" ? now() : now);
            return Number.isFinite(value) && value >= 0 ? Math.trunc(value) : Date.now();
          };
          function observationFor(page, pages, instances, events, relations, browserSessionId, timestamp) {
            if (typeof pageObservationLogic?.classifyPageObservation !== "function") {
              return { observationState: "eligible", reasonCode: "eligible", observeUntil: null, hardRisk: false, suppressSuggestions: false };
            }
            const firstObservedAt = Math.max(...instances.map((instance) => Number(instance.firstObservedAt)).filter((value) => Number.isFinite(value) && value >= 0));
            return pageObservationLogic.classifyPageObservation({
              page,
              currentInstances: instances,
              firstObservedAt: Number.isFinite(firstObservedAt) ? firstObservedAt : void 0,
              pages,
              events,
              relations,
              now: timestamp,
              browserSessionId
            });
          }
          async function observePage(tab = {}) {
            if (await preferenceStore?.isExcludedUrl?.(tab.url || tab.pendingUrl)) return null;
            const normalizedUrl = schema.normalizeSafeUrl(tab.url) || schema.normalizeSafeUrl(tab.pendingUrl);
            if (!normalizedUrl) return null;
            const timestamp = clock();
            return dataStore.runTransaction([stores.PAGES], "readwrite", async (tx) => {
              const existing = await tx.getByIndex(stores.PAGES, "normalizedUrl", normalizedUrl);
              const page = schema.normalizePageEntity({
                ...existing || {},
                id: existing?.id || stablePageId(normalizedUrl),
                normalizedUrl,
                restoreUrl: normalizedUrl,
                title: String(tab.title || "").trim() || existing?.title,
                faviconUrl: String(tab.favIconUrl || tab.faviconUrl || tab.favicon || "").trim() || existing?.faviconUrl,
                createdAt: existing?.createdAt ?? timestamp,
                updatedAt: timestamp
              }, { now: timestamp });
              await tx.put(stores.PAGES, page);
              return { ...page };
            });
          }
          function compareInstances(left, right) {
            const leftActivated = Number.isFinite(left.lastActivatedAt) ? left.lastActivatedAt : -1;
            const rightActivated = Number.isFinite(right.lastActivatedAt) ? right.lastActivatedAt : -1;
            return leftActivated - rightActivated || (Number(left.lastSeenAt) || 0) - (Number(right.lastSeenAt) || 0) || (Number(left.tabId) || 0) - (Number(right.tabId) || 0);
          }
          async function list(browserSessionId) {
            const sessionId = String(browserSessionId || "").trim();
            if (!sessionId) return { ok: true, pages: [] };
            const preferences = await preferenceStore?.load?.() || {};
            const ignoredPageIds = new Set(preferences.ignoredPageIds || []), sharedPageIds = new Set(preferences.sharedPageIds || []), excludedHosts = new Set(preferences.excludedHosts || []);
            return dataStore.runTransaction(
              [stores.PAGES, stores.RELATIONS, stores.TAB_INSTANCES, stores.BEHAVIOR_EVENTS],
              "readonly",
              async (tx) => {
                const [pages, relations, instances, events] = await Promise.all([
                  tx.getAll(stores.PAGES),
                  tx.getAll(stores.RELATIONS),
                  tx.getAll(stores.TAB_INSTANCES),
                  tx.getAllByIndex(stores.BEHAVIOR_EVENTS, "browserSessionId", sessionId)
                ]);
                const pagesById = new Map(pages.map((page) => [page.id, page]));
                const relatedPageIds = new Set(relations.map((relation) => relation.pageId).filter(Boolean));
                const grouped = /* @__PURE__ */ new Map();
                instances.forEach((instance) => {
                  if (instance.browserSessionId !== sessionId || !instance.pageId || relatedPageIds.has(instance.pageId) || !pagesById.has(instance.pageId)) return;
                  const group = grouped.get(instance.pageId) || [];
                  group.push(instance);
                  grouped.set(instance.pageId, group);
                });
                const result = Array.from(grouped, ([pageId, currentInstances]) => {
                  const preferred = [...currentInstances].sort(compareInstances).at(-1);
                  const page = pagesById.get(pageId);
                  let host = "";
                  try {
                    host = new URL(page.restoreUrl).hostname.toLowerCase();
                  } catch (_error) {
                  }
                  if (ignoredPageIds.has(pageId) || host && excludedHosts.has(host)) return null;
                  const observation = observationFor(page, pages, currentInstances, events, relations, sessionId, clock());
                  return {
                    id: page.id,
                    pageId: page.id,
                    title: page.title,
                    url: page.restoreUrl,
                    favicon: page.faviconUrl,
                    tabId: preferred.tabId,
                    windowId: preferred.windowId,
                    index: preferred.index,
                    lastSeenAt: preferred.lastSeenAt,
                    lastActivatedAt: preferred.lastActivatedAt,
                    observedFrom: preferred.observedFrom,
                    preInstallHistoryUnknown: preferred.preInstallHistoryUnknown === true,
                    instanceCount: currentInstances.length,
                    observationState: observation.observationState,
                    reasonCode: observation.reasonCode,
                    observeUntil: observation.observeUntil,
                    hardRisk: observation.hardRisk === true,
                    suppressSuggestions: observation.suppressSuggestions === true,
                    ...sharedPageIds.has(pageId) ? { sharedTool: true } : {}
                  };
                }).filter(Boolean);
                result.sort((left, right) => compareInstances(left, right) * -1 || String(left.pageId).localeCompare(String(right.pageId)));
                return { ok: true, pages: result };
              }
            );
          }
          return Object.freeze({ observePage, list });
        }
        return Object.freeze({ createV2InboxService });
      });
    }
  });

  // src/background/v2-inbox-preference-store.js
  var require_v2_inbox_preference_store = __commonJS({
    "src/background/v2-inbox-preference-store.js"(exports, module) {
      (function attachV2InboxPreferenceStore(root, factory) {
        const api = factory();
        if (typeof module === "object" && module.exports) module.exports = api;
        root.TfaV2InboxPreferenceStore = api;
      })(typeof globalThis === "object" ? globalThis : exports, function createModule() {
        "use strict";
        const STORAGE_KEY = "tfa_v2_inbox_preferences";
        const ACTIONS = /* @__PURE__ */ new Set(["ignore-page", "mark-shared", "exclude-site"]);
        const safeIds = (value) => [...new Set((Array.isArray(value) ? value : []).map(String).filter((id) => /^page-[A-Za-z0-9._-]{1,155}$/.test(id)))].slice(0, 2e3);
        const safeHosts = (value) => [...new Set((Array.isArray(value) ? value : []).map((host) => String(host).toLowerCase()).filter((host) => /^[a-z0-9.-]{1,253}$/.test(host)))].slice(0, 500);
        const hostOf = (url) => {
          try {
            return new URL(String(url || "")).hostname.toLowerCase();
          } catch (_error) {
            return "";
          }
        };
        function createV2InboxPreferenceStore({ storage, key = STORAGE_KEY } = {}) {
          if (!storage?.get || !storage?.set) throw new TypeError("Inbox preferences require local storage");
          async function load() {
            const raw = (await storage.get(key))?.[key] || {};
            return { ignoredPageIds: safeIds(raw.ignoredPageIds), sharedPageIds: safeIds(raw.sharedPageIds), excludedHosts: safeHosts(raw.excludedHosts) };
          }
          async function apply({ action, pageId, url } = {}) {
            if (!ACTIONS.has(action)) throw new TypeError("Invalid inbox preference action");
            const current = await load();
            if (action === "exclude-site") {
              const host = hostOf(url);
              if (!host) throw new TypeError("Invalid site URL");
              current.excludedHosts = safeHosts([...current.excludedHosts, host]);
            } else {
              const id = safeIds([pageId])[0];
              if (!id) throw new TypeError("Invalid page id");
              const field = action === "ignore-page" ? "ignoredPageIds" : "sharedPageIds";
              current[field] = safeIds([...current[field], id]);
            }
            await storage.set({ [key]: current });
            return current;
          }
          async function decision(pageId, url) {
            const current = await load(), host = hostOf(url);
            return { ignored: current.ignoredPageIds.includes(String(pageId)), shared: current.sharedPageIds.includes(String(pageId)), excluded: Boolean(host && current.excludedHosts.includes(host)) };
          }
          async function isExcludedUrl(url) {
            return (await decision("", url)).excluded;
          }
          async function clear() {
            await storage.remove?.(key);
            return true;
          }
          return Object.freeze({ load, apply, decision, isExcludedUrl, clear });
        }
        return Object.freeze({ STORAGE_KEY, createV2InboxPreferenceStore });
      });
    }
  });

  // src/background/v2-behavior-observer.js
  var require_v2_behavior_observer = __commonJS({
    "src/background/v2-behavior-observer.js"(exports, module) {
      (function attachV2BehaviorObserver(root, factory) {
        const api = factory();
        if (typeof module === "object" && module.exports) module.exports = api;
        root.TfaV2BehaviorObserver = api;
      })(typeof globalThis === "object" ? globalThis : exports, function createV2BehaviorObserverModule() {
        "use strict";
        const BROWSER_SESSION_ID_KEY = "tfa_v2_browser_session_id";
        const LAST_PURGE_AT_KEY = "tfa_v2_behavior_last_purge_at";
        const PURGE_INTERVAL_MS = 24 * 60 * 60 * 1e3;
        function createV2BehaviorObserver({
          chrome: chrome2,
          getStore,
          logic,
          sessionStorage = chrome2 && chrome2.storage && chrome2.storage.session,
          resolvePage,
          now = () => Date.now(),
          onError = () => {
          }
        } = {}) {
          if (!chrome2 || !chrome2.tabs || typeof chrome2.tabs.query !== "function") {
            throw new Error("V2 behavior observer requires the browser tabs API");
          }
          if (typeof getStore !== "function" || typeof resolvePage !== "function") {
            throw new Error("V2 behavior observer requires storage and page resolvers");
          }
          if (!logic || typeof logic.normalizeObservedTab !== "function") {
            throw new Error("V2 behavior observer requires behavior logic");
          }
          let started = false;
          let queue = Promise.resolve();
          let storePromise = null;
          let sessionIdPromise = null;
          let lastPurgeAtLoaded = false;
          let lastPurgeAt = null;
          let focusedWindowId = null;
          let lifecycleGeneration = 0;
          const listeners = [];
          const detachedTabs = /* @__PURE__ */ new Map();
          function reportError(error, phase) {
            try {
              onError(error, { phase });
            } catch (_onErrorError) {
            }
          }
          function enqueue(phase, worker) {
            const result = queue.then(worker);
            queue = result.catch((error) => {
              reportError(error, phase);
            });
            return queue;
          }
          function getBehaviorStore() {
            if (!storePromise) {
              storePromise = Promise.resolve().then(getStore);
              storePromise.catch(() => {
                storePromise = null;
              });
            }
            return storePromise;
          }
          function validSessionId(value) {
            try {
              logic.makeTabInstanceId(value, 0);
              return true;
            } catch (_error) {
              return false;
            }
          }
          function ensureBrowserSessionId() {
            if (sessionIdPromise) return sessionIdPromise;
            sessionIdPromise = (async () => {
              if (sessionStorage && typeof sessionStorage.get === "function") {
                const stored = await sessionStorage.get(BROWSER_SESSION_ID_KEY);
                const value = stored && stored[BROWSER_SESSION_ID_KEY];
                if (validSessionId(value)) return value;
              }
              const generated = logic.makeBrowserSessionId();
              if (sessionStorage && typeof sessionStorage.set === "function") {
                await sessionStorage.set({ [BROWSER_SESSION_ID_KEY]: generated });
              }
              return generated;
            })();
            sessionIdPromise.catch(() => {
              sessionIdPromise = null;
            });
            return sessionIdPromise;
          }
          async function safeResolvePage(tab, fallbackPageId = null) {
            if (!tab || !tab.url) return fallbackPageId;
            if (!logic.isTrackableUrl(tab.url)) return null;
            try {
              const resolved = await resolvePage(tab);
              if (typeof resolved === "string") return resolved || null;
              if (resolved && typeof resolved === "object") return resolved.pageId || resolved.id || null;
              return null;
            } catch (error) {
              reportError(error, "resolve-page");
              return fallbackPageId;
            }
          }
          function browserTab(tab, previous, tabId) {
            const source = tab || {};
            return {
              ...source,
              id: source.id == null ? tabId == null ? previous && previous.tabId : tabId : source.id,
              windowId: source.windowId == null ? previous && previous.windowId : source.windowId,
              index: source.index == null ? previous && previous.index : source.index,
              groupId: source.groupId == null ? previous ? previous.groupId : -1 : source.groupId,
              pinned: source.pinned == null ? Boolean(previous && previous.pinned) : source.pinned,
              active: source.active == null ? Boolean(previous && previous.active) : source.active,
              firstObservedAt: previous && previous.firstObservedAt,
              lastActivatedAt: previous && previous.lastActivatedAt,
              preInstallHistoryUnknown: Boolean(previous && previous.preInstallHistoryUnknown)
            };
          }
          function normalizeTab(tab, previous, browserSessionId, pageId, observedFrom, preInstallHistoryUnknown, observedAt = now()) {
            const normalized = logic.normalizeObservedTab(browserTab(tab, previous), {
              browserSessionId,
              observedFrom,
              pageId,
              preInstallHistoryUnknown,
              now: observedAt
            });
            if (previous && previous.active && normalized.active && previous.lastActivatedAt != null) {
              return Object.freeze({ ...normalized, lastActivatedAt: previous.lastActivatedAt });
            }
            return normalized;
          }
          async function getTab(tabId, supplied) {
            if (supplied && supplied.windowId != null && supplied.index != null) return supplied;
            if (typeof chrome2.tabs.get !== "function") return supplied || null;
            return chrome2.tabs.get(tabId);
          }
          function listen(event, handler, phase) {
            if (!event || typeof event.addListener !== "function") return;
            const queuedHandler = (...args) => {
              enqueue(phase, () => handler(...args));
            };
            event.addListener(queuedHandler);
            listeners.push({ event, handler: queuedHandler });
          }
          async function loadLastPurgeAt() {
            if (lastPurgeAtLoaded) return lastPurgeAt;
            lastPurgeAtLoaded = true;
            if (!sessionStorage || typeof sessionStorage.get !== "function") return null;
            const stored = await sessionStorage.get(LAST_PURGE_AT_KEY);
            const value = Number(stored && stored[LAST_PURGE_AT_KEY]);
            lastPurgeAt = Number.isFinite(value) && value >= 0 ? value : null;
            return lastPurgeAt;
          }
          async function maybePurge(store) {
            const currentTime = now();
            const previousPurgeAt = await loadLastPurgeAt();
            if (previousPurgeAt !== null && currentTime - previousPurgeAt < PURGE_INTERVAL_MS) return 0;
            const removed = await store.purgeExpiredEvents({ now: currentTime });
            lastPurgeAt = currentTime;
            lastPurgeAtLoaded = true;
            if (sessionStorage && typeof sessionStorage.set === "function") {
              try {
                await sessionStorage.set({ [LAST_PURGE_AT_KEY]: currentTime });
              } catch (error) {
                reportError(error, "persist-purge-time");
              }
            }
            return removed;
          }
          async function recordEvent(store, event, { skipPurge = false } = {}) {
            if (!skipPurge) {
              try {
                await maybePurge(store);
              } catch (error) {
                reportError(error, "purge-events");
              }
            }
            return store.recordBehaviorEvent(event);
          }
          async function snapshot(expectedGeneration) {
            if (!started || expectedGeneration !== lifecycleGeneration) return false;
            const [store, browserSessionId, tabs] = await Promise.all([
              getBehaviorStore(),
              ensureBrowserSessionId(),
              chrome2.tabs.query({})
            ]);
            if (!started || expectedGeneration !== lifecycleGeneration) return false;
            const existing = await store.listTabInstances(browserSessionId);
            const existingByTabId = new Map(existing.map((item) => [item.tabId, item]));
            const instances = [];
            for (const tab of Array.isArray(tabs) ? tabs : []) {
              try {
                const previous = existingByTabId.get(tab.id) || null;
                const pageId = await safeResolvePage(tab, previous && previous.pageId);
                instances.push(normalizeTab(
                  tab,
                  previous,
                  browserSessionId,
                  pageId,
                  previous ? previous.observedFrom : "initial_snapshot",
                  previous ? previous.preInstallHistoryUnknown : true
                ));
              } catch (error) {
                reportError(error, "snapshot-tab");
              }
            }
            if (!started || expectedGeneration !== lifecycleGeneration) return false;
            await store.reconcileSnapshot(instances, browserSessionId);
            try {
              await maybePurge(store);
            } catch (error) {
              reportError(error, "purge-events");
            }
            const currentIds = new Set(instances.map((item) => item.id));
            const missing = existing.filter((item) => !currentIds.has(item.id));
            for (const item of missing) {
              try {
                await recordEvent(store, logic.eventForReconciledMissing({
                  tabId: item.tabId,
                  windowId: item.windowId,
                  pageId: item.pageId,
                  reason: "service_worker_reconcile"
                }, { now: now(), browserSessionId }), { skipPurge: true });
              } catch (error) {
                reportError(error, "record-reconciled-missing");
              }
            }
            return true;
          }
          async function tabCreated(tab) {
            const [store, browserSessionId] = await Promise.all([getBehaviorStore(), ensureBrowserSessionId()]);
            const pageId = await safeResolvePage(tab, null);
            const instance = normalizeTab(tab, null, browserSessionId, pageId, "runtime_event", false);
            await store.upsertTabInstance(instance);
            let sourcePageId = null;
            if (Number.isInteger(tab && tab.openerTabId)) {
              sourcePageId = (await store.getTabInstance(browserSessionId, tab.openerTabId))?.pageId || null;
            }
            await recordEvent(store, logic.eventForOpen({
              tabId: instance.tabId,
              windowId: instance.windowId,
              pageId,
              sourcePageId,
              openerTabId: tab && tab.openerTabId,
              index: instance.index
            }, { now: now(), browserSessionId }));
          }
          async function tabActivated(activeInfo) {
            const [store, browserSessionId] = await Promise.all([getBehaviorStore(), ensureBrowserSessionId()]);
            const instances = await store.listTabInstances(browserSessionId);
            const previous = instances.find((item) => item.windowId === activeInfo.windowId && item.active && item.tabId !== activeInfo.tabId) || null;
            const existing = instances.find((item) => item.tabId === activeInfo.tabId) || null;
            const tab = await getTab(activeInfo.tabId, null);
            if (!tab) return;
            const pageId = await safeResolvePage(tab, existing && existing.pageId);
            const currentTime = now();
            if (previous) {
              const inactive = normalizeTab(
                { ...browserTab(null, previous), active: false },
                previous,
                browserSessionId,
                previous.pageId,
                "runtime_event",
                previous.preInstallHistoryUnknown
              );
              await store.upsertTabInstance(inactive);
            }
            const active = normalizeTab(
              { ...tab, active: true },
              existing,
              browserSessionId,
              pageId,
              "runtime_event",
              false
            );
            await store.upsertTabInstance(active);
            await recordEvent(store, logic.eventForActivation({
              tabId: active.tabId,
              windowId: active.windowId,
              pageId: active.pageId,
              previousTabId: previous && previous.tabId,
              previousPageId: previous && previous.pageId,
              activationDurationMs: previous && previous.lastActivatedAt != null ? Math.max(0, currentTime - previous.lastActivatedAt) : void 0
            }, { now: currentTime, browserSessionId }));
          }
          async function tabUpdated(tabId, _changeInfo, suppliedTab) {
            const [store, browserSessionId] = await Promise.all([getBehaviorStore(), ensureBrowserSessionId()]);
            const previous = await store.getTabInstance(browserSessionId, tabId);
            const tab = await getTab(tabId, suppliedTab);
            if (!tab) return;
            const pageId = await safeResolvePage(tab, previous && previous.pageId);
            const observedAt = now();
            let next = normalizeTab(
              tab,
              previous,
              browserSessionId,
              pageId,
              "runtime_event",
              false,
              observedAt
            );
            const pageChanged = Boolean(previous && previous.pageId !== next.pageId);
            const navigationDurationMs = pageChanged && previous.active && previous.lastActivatedAt != null ? Math.max(0, observedAt - previous.lastActivatedAt) : void 0;
            if (pageChanged && next.active) next = Object.freeze({ ...next, lastActivatedAt: observedAt });
            await store.upsertTabInstance(next);
            if (!previous) return;
            if (pageChanged) {
              await recordEvent(store, logic.eventForNavigation({
                tabId,
                windowId: next.windowId,
                previousPageId: previous.pageId,
                nextPageId: next.pageId,
                navigationDurationMs
              }, { now: observedAt, browserSessionId }));
            }
            if (previous.groupId !== next.groupId) {
              await recordEvent(store, logic.eventForGroup({
                tabId,
                windowId: next.windowId,
                pageId: next.pageId,
                previousGroupId: previous.groupId,
                groupId: next.groupId
              }, { now: now(), browserSessionId }));
            }
          }
          async function tabRemoved(tabId) {
            const [store, browserSessionId] = await Promise.all([getBehaviorStore(), ensureBrowserSessionId()]);
            const previous = await store.getTabInstance(browserSessionId, tabId);
            detachedTabs.delete(tabId);
            if (!previous) return;
            const currentTime = now();
            const activationDurationMs = previous.active && previous.lastActivatedAt != null ? Math.max(0, currentTime - previous.lastActivatedAt) : void 0;
            const riskFlags = previous.preInstallHistoryUnknown ? [] : logic.deriveRiskFlags({
              openedAt: previous.firstObservedAt,
              closedAt: currentTime,
              activationDurationMs
            });
            let captureError = null;
            try {
              await recordEvent(store, logic.eventForClose({
                tabId,
                windowId: previous.windowId,
                pageId: previous.pageId,
                index: previous.index,
                groupId: previous.groupId,
                activationDurationMs,
                riskFlags
              }, { now: currentTime, browserSessionId }));
            } catch (error) {
              captureError = error;
            }
            await store.deleteTabInstance(browserSessionId, tabId);
            if (captureError) throw captureError;
          }
          async function tabMoved(tabId, moveInfo, detachInfo) {
            const [store, browserSessionId] = await Promise.all([getBehaviorStore(), ensureBrowserSessionId()]);
            const previous = await store.getTabInstance(browserSessionId, tabId);
            if (!previous) return;
            const tab = await getTab(tabId, null);
            if (!tab) return;
            const pageId = await safeResolvePage(tab, previous.pageId);
            const next = normalizeTab(tab, previous, browserSessionId, pageId, "runtime_event", false);
            await store.upsertTabInstance(next);
            const prior = detachInfo || {};
            await recordEvent(store, logic.eventForMove({
              tabId,
              pageId: next.pageId,
              windowId: next.windowId,
              previousWindowId: prior.oldWindowId == null ? previous.windowId : prior.oldWindowId,
              previousIndex: prior.oldPosition == null ? moveInfo.fromIndex == null ? previous.index : moveInfo.fromIndex : prior.oldPosition,
              nextWindowId: next.windowId,
              nextIndex: moveInfo.toIndex == null ? next.index : moveInfo.toIndex
            }, { now: now(), browserSessionId }));
          }
          async function tabReplaced(addedTabId, removedTabId) {
            const [store, browserSessionId] = await Promise.all([getBehaviorStore(), ensureBrowserSessionId()]);
            const replacement = await store.replaceTabId({ browserSessionId, addedTabId, removedTabId });
            if (!replacement) return;
            const tab = await getTab(addedTabId, null);
            let current = replacement;
            if (tab) {
              const pageId = await safeResolvePage(tab, replacement.pageId);
              current = normalizeTab(
                tab,
                replacement,
                browserSessionId,
                pageId,
                "runtime_event",
                false
              );
              await store.upsertTabInstance(current);
            }
            await recordEvent(store, logic.eventForReplaced({
              tabId: addedTabId,
              windowId: (tab && tab.windowId) ?? replacement.windowId,
              pageId: current.pageId,
              addedTabId,
              removedTabId
            }, { now: now(), browserSessionId }));
          }
          async function groupUpdated(group) {
            const [store, browserSessionId] = await Promise.all([getBehaviorStore(), ensureBrowserSessionId()]);
            await recordEvent(store, logic.eventForGroup({
              type: "group.updated",
              windowId: group && group.windowId,
              groupId: group && group.id,
              title: group && group.title,
              color: group && group.color,
              collapsed: group && group.collapsed
            }, { now: now(), browserSessionId }));
          }
          async function windowEvent(type, window2) {
            const [store, browserSessionId] = await Promise.all([getBehaviorStore(), ensureBrowserSessionId()]);
            const windowId = typeof window2 === "object" ? window2.id : window2;
            await recordEvent(store, logic.eventForWindow({ type, windowId }, { now: now(), browserSessionId }));
          }
          async function windowFocusChanged(windowId) {
            const [store, browserSessionId] = await Promise.all([getBehaviorStore(), ensureBrowserSessionId()]);
            const previousWindowId = focusedWindowId;
            const currentTime = now();
            const instances = await store.listTabInstances(browserSessionId);
            const previous = instances.find((item) => item.windowId === previousWindowId && item.active) || null;
            const current = instances.find((item) => item.windowId === windowId && item.active) || null;
            const activationDurationMs = previous && previous.lastActivatedAt != null ? Math.max(0, currentTime - previous.lastActivatedAt) : void 0;
            if (previous) {
              await store.upsertTabInstance({
                ...previous,
                lastSeenAt: currentTime,
                lastActivatedAt: null
              });
            }
            if (current) {
              await store.upsertTabInstance({
                ...current,
                lastSeenAt: currentTime,
                lastActivatedAt: currentTime
              });
            }
            focusedWindowId = windowId;
            await recordEvent(store, logic.eventForWindow({
              type: "window.focus_changed",
              windowId,
              previousWindowId,
              previousTabId: previous && previous.tabId,
              activationDurationMs,
              sourcePageId: previous && previous.pageId,
              pageId: current && current.pageId
            }, { now: currentTime, browserSessionId }));
          }
          function registerListeners() {
            listen(chrome2.tabs.onCreated, tabCreated, "tab-created");
            listen(chrome2.tabs.onActivated, tabActivated, "tab-activated");
            listen(chrome2.tabs.onUpdated, tabUpdated, "tab-updated");
            listen(chrome2.tabs.onRemoved, tabRemoved, "tab-removed");
            listen(chrome2.tabs.onMoved, (tabId, info) => tabMoved(tabId, info, null), "tab-moved");
            listen(chrome2.tabs.onDetached, (tabId, info) => {
              detachedTabs.set(tabId, info);
            }, "tab-detached");
            listen(chrome2.tabs.onAttached, (tabId, info) => {
              const detached = detachedTabs.get(tabId) || null;
              detachedTabs.delete(tabId);
              return tabMoved(tabId, { toIndex: info.newPosition }, detached);
            }, "tab-attached");
            listen(chrome2.tabs.onReplaced, tabReplaced, "tab-replaced");
            if (chrome2.windows) {
              listen(chrome2.windows.onFocusChanged, windowFocusChanged, "window-focus");
              listen(chrome2.windows.onCreated, (window2) => windowEvent("window.created", window2), "window-created");
              listen(chrome2.windows.onRemoved, (windowId) => windowEvent("window.removed", windowId), "window-removed");
            }
            if (chrome2.tabGroups) {
              listen(chrome2.tabGroups.onCreated, groupUpdated, "group-created");
              listen(chrome2.tabGroups.onUpdated, groupUpdated, "group-updated");
              listen(chrome2.tabGroups.onMoved, groupUpdated, "group-moved");
              listen(chrome2.tabGroups.onRemoved, groupUpdated, "group-removed");
            }
          }
          function start() {
            if (started) return queue;
            started = true;
            lifecycleGeneration += 1;
            registerListeners();
            return reconcile("initial-snapshot");
          }
          function reconcile(phase = "reconcile") {
            if (!started) return Promise.resolve(false);
            const expectedGeneration = lifecycleGeneration;
            return enqueue(phase, () => snapshot(expectedGeneration)).then((result) => result === true);
          }
          function stop() {
            if (!started) return;
            started = false;
            lifecycleGeneration += 1;
            listeners.splice(0).forEach(({ event, handler }) => {
              if (typeof event.removeListener === "function") event.removeListener(handler);
            });
            detachedTabs.clear();
          }
          function whenIdle() {
            return queue;
          }
          return Object.freeze({ start, stop, reconcile, whenIdle, ensureBrowserSessionId });
        }
        return Object.freeze({
          BROWSER_SESSION_ID_KEY,
          LAST_PURGE_AT_KEY,
          PURGE_INTERVAL_MS,
          createV2BehaviorObserver
        });
      });
    }
  });

  // src/background/v2-suggestion-observation-gate.js
  var require_v2_suggestion_observation_gate = __commonJS({
    "src/background/v2-suggestion-observation-gate.js"(exports, module) {
      (function attachV2SuggestionObservationGate(root, factory) {
        const api = factory();
        if (typeof module === "object" && module.exports) module.exports = api;
        if (root) root.TfaV2SuggestionObservationGate = api;
      })(typeof globalThis === "object" ? globalThis : exports, function createModule() {
        "use strict";
        function createV2SuggestionObservationGate({ pageObservationLogic } = {}) {
          function classify(page, snapshot = {}, browserSessionId = "", now = Date.now()) {
            if (!page || typeof pageObservationLogic?.classifyPageObservation !== "function") {
              return { observationState: "eligible", reasonCode: "eligible", suppressSuggestions: false };
            }
            const instances = (snapshot.tabInstances || []).filter((instance) => instance.pageId === page.id && instance.browserSessionId === browserSessionId);
            const firstObservedAt = Math.max(...instances.map((instance) => Number(instance.firstObservedAt)).filter((value) => Number.isFinite(value) && value >= 0));
            return pageObservationLogic.classifyPageObservation({
              page,
              pages: snapshot.pages || [],
              currentInstances: instances,
              firstObservedAt: Number.isFinite(firstObservedAt) ? firstObservedAt : void 0,
              events: snapshot.events || [],
              relations: snapshot.relations || [],
              now,
              browserSessionId
            });
          }
          function eligiblePageIds(pageIds, snapshot, browserSessionId, now) {
            const pagesById = new Map((snapshot.pages || []).map((page) => [page.id, page]));
            return pageIds.filter((pageId) => classify(
              pagesById.get(pageId),
              snapshot,
              browserSessionId,
              now
            ).suppressSuggestions !== true);
          }
          return Object.freeze({ classify, eligiblePageIds });
        }
        return Object.freeze({ createV2SuggestionObservationGate });
      });
    }
  });

  // src/background/v2-suggestion-organization-input.js
  var require_v2_suggestion_organization_input = __commonJS({
    "src/background/v2-suggestion-organization-input.js"(exports, module) {
      (function attachV2SuggestionOrganizationInput(root, factory) {
        const api = factory();
        if (typeof module === "object" && module.exports) module.exports = api;
        if (root) root.TfaV2SuggestionOrganizationInput = api;
      })(typeof globalThis === "object" ? globalThis : exports, function createModule() {
        "use strict";
        const array = (value) => Array.isArray(value) ? value : [];
        function hashToken(value) {
          let hash = 2166136261;
          const text = JSON.stringify(value);
          for (let index = 0; index < text.length; index += 1) hash = Math.imul(hash ^ text.charCodeAt(index), 16777619) >>> 0;
          return hash.toString(36);
        }
        function buildOrganizationInput(snapshot, pending, clusterResult = null) {
          const sorted = [...pending].sort((left, right) => String(left.pageId).localeCompare(String(right.pageId)) || left.rank - right.rank || String(left.taskId).localeCompare(String(right.taskId)));
          const automaticClusters = array(clusterResult?.clusters).filter((cluster) => cluster?.disposition === "auto_candidate");
          const pageIds = [.../* @__PURE__ */ new Set([...sorted.map((item) => item.pageId), ...automaticClusters.flatMap((cluster) => cluster.pageIds)])];
          const candidateTaskIds = new Set(sorted.map((item) => item.taskId));
          const pagesById = new Map(snapshot.pages.map((item) => [item.id, item]));
          const suggestionsByPage = /* @__PURE__ */ new Map();
          sorted.forEach((item) => {
            const group = suggestionsByPage.get(item.pageId) || [];
            group.push(item);
            suggestionsByPage.set(item.pageId, group);
          });
          const clusterEvidenceByPage = /* @__PURE__ */ new Map();
          automaticClusters.forEach((cluster) => cluster.pageIds.forEach((pageId) => {
            const entry = clusterEvidenceByPage.get(pageId) || { evidenceCodes: [], riskFlags: [] };
            entry.evidenceCodes.push(...array(cluster.evidenceCodes));
            entry.riskFlags.push(...array(cluster.riskFlags));
            clusterEvidenceByPage.set(pageId, entry);
          }));
          const counted = (items, field) => {
            const counts = /* @__PURE__ */ new Map();
            items.forEach((item) => array(item[field]).forEach((code) => counts.set(code, Math.min(99, (counts.get(code) || 0) + 1))));
            return [...counts.entries()].map(([code, count]) => ({ code, count }));
          };
          const pages = pageIds.map((pageId) => {
            const page = pagesById.get(pageId), candidates2 = suggestionsByPage.get(pageId) || [];
            const clusterEvidence = clusterEvidenceByPage.get(pageId) || { evidenceCodes: [], riskFlags: [] };
            return page ? {
              id: page.id,
              title: page.title,
              normalizedUrl: page.normalizedUrl,
              restoreUrl: page.restoreUrl,
              evidence: counted([...candidates2, clusterEvidence], "evidenceCodes"),
              risks: counted([...candidates2, clusterEvidence], "riskFlags")
            } : null;
          }).filter(Boolean);
          const tasks = snapshot.tasks.filter((item) => candidateTaskIds.has(item.id)).map((item) => ({
            id: item.id,
            title: item.title,
            domain: item.domain,
            status: item.status,
            source: item.source,
            manualLocked: item.manualLocked,
            canReceiveRelations: item.status !== "done"
          }));
          const candidates = Object.fromEntries(pageIds.map((pageId) => [pageId, (suggestionsByPage.get(pageId) || []).map((item) => item.taskId)]));
          const localCandidates = sorted.map((item) => ({
            pageId: item.pageId,
            taskId: item.taskId,
            confidence: item.confidence,
            evidenceCodes: [...item.evidenceCodes],
            riskFlags: [...item.riskFlags],
            rank: item.rank
          }));
          const taskVersions = snapshot.tasks.filter((item) => candidateTaskIds.has(item.id)).map((item) => [item.id, item.updatedAt || 0, item.status]).sort();
          const pageVersions = pages.map((item) => [item.id, pagesById.get(item.id)?.updatedAt || 0]).sort();
          const relevantPageIds = new Set(pageIds);
          const relationVersions = array(snapshot.relations).filter((item) => relevantPageIds.has(item.pageId) || candidateTaskIds.has(item.taskId)).map((item) => [
            item.id,
            item.pageId,
            item.taskId,
            item.role,
            item.isPrimary === true,
            item.source,
            item.manualLocked === true,
            item.updatedAt || 0
          ]).sort((left, right) => left.join(":").localeCompare(right.join(":")));
          const suggestionVersions = array(snapshot.suggestions).filter((item) => relevantPageIds.has(item.pageId)).map((item) => [
            item.id,
            item.pageId,
            item.taskId,
            item.status,
            item.inputVersion,
            item.updatedAt || 0,
            item.decidedAt || 0,
            item.cooldownUntil || 0
          ]).sort((left, right) => left.join(":").localeCompare(right.join(":")));
          const inputVersion = `input-${hashToken([
            sorted.map((item) => [item.pageId, item.taskId, item.inputVersion]).sort((left, right) => left.join(":").localeCompare(right.join(":"))),
            taskVersions,
            pageVersions,
            relationVersions,
            suggestionVersions,
            clusterResult?.inputVersion || "",
            automaticClusters.map((cluster) => [
              cluster.clusterId,
              cluster.pageIds,
              cluster.confidence,
              cluster.independentSignalFamilies,
              cluster.evidenceCodes,
              cluster.riskFlags
            ]).sort()
          ])}`;
          const newTaskClusters = automaticClusters.map((cluster) => ({
            clusterId: cluster.clusterId,
            pageIds: [...cluster.pageIds],
            confidence: cluster.confidence,
            independentSignalFamilies: cluster.independentSignalFamilies,
            riskFlags: [...cluster.riskFlags],
            inputVersion
          }));
          return { inputVersion, pages, tasks, candidates, localCandidates, newTaskClusters };
        }
        return Object.freeze({ buildOrganizationInput });
      });
    }
  });

  // src/background/v2-suggestion-service.js
  var require_v2_suggestion_service = __commonJS({
    "src/background/v2-suggestion-service.js"(exports, module) {
      (function attachV2SuggestionService(root, factory) {
        const organizationInputModule = typeof module === "object" && module.exports ? require_v2_suggestion_organization_input() : root.TfaV2SuggestionOrganizationInput;
        const api = factory(organizationInputModule);
        if (typeof module === "object" && module.exports) module.exports = api;
        if (root) root.TfaV2SuggestionService = api;
      })(typeof globalThis === "object" ? globalThis : exports, function createV2SuggestionServiceModule(organizationInputModule) {
        "use strict";
        const PENDING_TTL_MS = 24 * 60 * 60 * 1e3;
        const REJECTION_COOLDOWN_MS = 7 * 24 * 60 * 60 * 1e3;
        const MAX_CANDIDATES = 8;
        const DEFAULT_RULE_VERSION = "local-candidate-v2";
        const SAFE_PAGE_ID = /^page-[A-Za-z0-9._:-]{1,154}$/;
        const SAFE_TASK_ID = /^task-[A-Za-z0-9._:-]{1,154}$/;
        const SAFE_MODEL_VERSION = /^[a-z0-9][a-z0-9._:-]{0,159}$/;
        const SAFE_CHANGE_ID = /^change-[A-Za-z0-9._:-]{1,153}$/;
        const AI_REASON_CODES = /* @__PURE__ */ new Set([
          "semantic_match",
          "task_name_match",
          "domain_match",
          "core_page_similarity",
          "behavior_support",
          "stable_object_match",
          "related_pages",
          "clearer_task_name",
          "virtual_order",
          "ambiguous",
          "no_suitable_task",
          "insufficient_evidence"
        ]);
        const SAFE_AI_FALLBACK_CODES = /* @__PURE__ */ new Set([
          "AI_DISABLED",
          "AI_API_KEY_REQUIRED",
          "INVALID_SETTINGS",
          "AI_STORAGE_ERROR",
          "ABORTED",
          "TIMEOUT",
          "NETWORK",
          "AUTH",
          "PAYMENT_REQUIRED",
          "FORBIDDEN",
          "NOT_FOUND",
          "RATE_LIMIT",
          "BAD_REQUEST",
          "SERVER",
          "HTTP",
          "NON_JSON",
          "EMPTY_RESPONSE",
          "OUTPUT_TOO_LARGE",
          "INVALID_OUTPUT",
          "AI_PROVIDER_UNAVAILABLE",
          "AI_REQUEST_FAILED",
          "INPUT_VERSION_MISMATCH",
          "PLAN_APPLICATION_FAILED"
        ]);
        const ADJUSTMENT_TYPES = /* @__PURE__ */ new Set(["create_ai_task", "rename_ai_task", "assign_ai_relation", "reorder_ai_tasks"]);
        const SAFE_ADJUSTMENT_TEXT = /^(?!.*(?:\b(?:https?|file|chrome|edge):\/\/|Bearer\s+|\bsk-[A-Za-z0-9_-]{8}|api[-_ ]?key|authorization|cookie|raw[_ ]?events?|behavior[_ ]?events?))[\s\S]*$/i;
        const REASON_TEXT = Object.freeze({
          opener_link: "\u7531\u8BE5\u4EFB\u52A1\u4E2D\u7684\u9875\u9762\u6253\u5F00",
          stable_object: "\u5339\u914D\u540C\u4E00\u4E2A\u7A33\u5B9A\u7684\u5DE5\u4F5C\u5BF9\u8C61",
          qualified_return: "\u4E0E\u8BE5\u4EFB\u52A1\u9875\u9762\u5F62\u6210\u591A\u6B21\u6709\u6548\u5F80\u8FD4",
          task_snapshot_restore: "\u4E0E\u8BE5\u4EFB\u52A1\u7684\u9875\u9762\u4E00\u8D77\u6062\u590D",
          native_group_restore: "\u6765\u81EA\u540C\u4E00\u6D4F\u89C8\u5668\u9875\u7B7E\u7EC4",
          title_overlap: "\u6807\u9898\u4E0E\u8BE5\u4EFB\u52A1\u7684\u6838\u5FC3\u9875\u9762\u76F8\u4F3C",
          same_native_group: "\u5F53\u524D\u4F4D\u4E8E\u540C\u4E00\u6D4F\u89C8\u5668\u9875\u7B7E\u7EC4",
          same_window: "\u5F53\u524D\u4F4D\u4E8E\u540C\u4E00\u6D4F\u89C8\u5668\u7A97\u53E3",
          same_domain: "\u4E0E\u8BE5\u4EFB\u52A1\u9875\u9762\u6765\u81EA\u540C\u4E00\u7AD9\u70B9",
          ambiguous: "\u5B58\u5728\u591A\u4E2A\u63A5\u8FD1\u7684\u4EFB\u52A1\u5019\u9009"
        });
        const clone = (value) => JSON.parse(JSON.stringify(value));
        const array = (value) => Array.isArray(value) ? value : [];
        function hashToken(value) {
          let hash = 2166136261;
          const text = JSON.stringify(value);
          for (let index = 0; index < text.length; index += 1) {
            hash = Math.imul(hash ^ text.charCodeAt(index), 16777619) >>> 0;
          }
          return hash.toString(36);
        }
        function pageIdsOf(message = {}) {
          if (message.pageIds == null) return [];
          if (!Array.isArray(message.pageIds)) throw new TypeError("pageIds must be an array");
          const result = [...new Set(message.pageIds.map((value) => String(value || "").trim()))];
          if (result.length > 100 || result.some((pageId) => !SAFE_PAGE_ID.test(pageId))) {
            throw new TypeError("Invalid pageIds");
          }
          return result;
        }
        function same(left, right) {
          return JSON.stringify(left ?? null) === JSON.stringify(right ?? null);
        }
        function safeRankDecisions(value) {
          const allowed = /* @__PURE__ */ new Set(["pageId", "taskId", "decision", "confidence", "reasonCodes", "modelVersion"]);
          const pages = /* @__PURE__ */ new Set();
          const decisions = [];
          for (const item of Array.isArray(value) ? value : []) {
            if (!item || typeof item !== "object" || Array.isArray(item) || Object.keys(item).some((field) => !allowed.has(field))) continue;
            const pageId = String(item.pageId || "").trim();
            const taskId = item.taskId == null ? null : String(item.taskId).trim();
            const decision = String(item.decision || "");
            const confidence = Number(item.confidence);
            const modelVersion = String(item.modelVersion || "").trim();
            if (!SAFE_PAGE_ID.test(pageId) || pages.has(pageId) || !["assign", "no_match"].includes(decision) || (decision === "assign" ? !SAFE_TASK_ID.test(taskId || "") : taskId !== null) || !Number.isFinite(confidence) || confidence < 0 || confidence > 1 || !SAFE_MODEL_VERSION.test(modelVersion) || !Array.isArray(item.reasonCodes) || item.reasonCodes.length < 1 || item.reasonCodes.length > 3 || new Set(item.reasonCodes).size !== item.reasonCodes.length || item.reasonCodes.some((code) => !AI_REASON_CODES.has(String(code)))) continue;
            pages.add(pageId);
            decisions.push({ pageId, taskId, decision, confidence, modelVersion });
          }
          return decisions;
        }
        function safeAdjustmentSummary(value, changeId) {
          if (!value || typeof value !== "object" || Array.isArray(value) || Object.keys(value).some((field) => !["changeId", "status", "items", "errorCode"].includes(field)) || value.changeId !== changeId || value.status !== "applied" || value.errorCode !== "" || !Array.isArray(value.items) || value.items.length < 1 || value.items.length > 50) return null;
          const safeText = (input, maximum, optional = false) => {
            if (typeof input !== "string") return null;
            const text = input.trim();
            if (!text && !optional || text.length > maximum || /[\u0000-\u001f\u007f]/.test(text) || !SAFE_ADJUSTMENT_TEXT.test(text)) return null;
            return text;
          };
          const items = [];
          for (const item of value.items) {
            if (!item || typeof item !== "object" || Array.isArray(item) || Object.keys(item).some((field) => !["type", "title", "before", "after", "reason"].includes(field)) || !ADJUSTMENT_TYPES.has(item.type)) return null;
            const title = safeText(item.title, 160);
            const before = safeText(item.before, 200, true);
            const after = safeText(item.after, 200, true);
            const reason = safeText(item.reason, 240, true);
            if (title == null || before == null || after == null || reason == null || !before && !after) return null;
            items.push({ type: item.type, title, before, after, reason });
          }
          return { changeId, status: "applied", items, errorCode: "" };
        }
        function createV2SuggestionService({
          store,
          schema,
          aggregateSuggestionSignals,
          scoreSuggestionCandidates,
          getBrowserSessionId,
          now = Date.now,
          createId,
          aiCoordinator = null,
          buildNewTaskClusters = null,
          observationGate = null
        } = {}) {
          if (!store || typeof store.runTransaction !== "function") throw new TypeError("Suggestion service requires a data store");
          if (!schema?.STORES || typeof schema.normalizeSuggestion !== "function") throw new TypeError("Suggestion service requires the V2 schema");
          if (typeof aggregateSuggestionSignals !== "function" || typeof scoreSuggestionCandidates !== "function") {
            throw new TypeError("Suggestion service requires aggregation and scoring functions");
          }
          if (typeof getBrowserSessionId !== "function" || typeof createId !== "function") {
            throw new TypeError("Suggestion service requires session and id providers");
          }
          if (aiCoordinator && typeof aiCoordinator.organize !== "function") {
            throw new TypeError("Suggestion service AI coordinator is invalid");
          }
          if (buildNewTaskClusters != null && typeof buildNewTaskClusters !== "function") {
            throw new TypeError("Suggestion service new-task clustering is invalid");
          }
          const stores = schema.STORES;
          const snapshotStores = [stores.TASKS, stores.RELATIONS, stores.TAB_INSTANCES, stores.BEHAVIOR_EVENTS, stores.SUGGESTIONS];
          const decisionStores = [
            stores.SUGGESTIONS,
            stores.PAGES,
            stores.TASKS,
            stores.RELATIONS,
            stores.CHANGES,
            stores.TAB_INSTANCES
          ];
          const clock = () => {
            const value = Number(typeof now === "function" ? now() : now);
            return Number.isFinite(value) && value >= 0 ? Math.trunc(value) : Date.now();
          };
          const schemaOptions = (timestamp) => ({ now: timestamp, createId });
          const ruleVersionFallback = String(scoreSuggestionCandidates.RULE_VERSION || scoreSuggestionCandidates.ruleVersion || DEFAULT_RULE_VERSION);
          function error(code, message) {
            const result = new Error(message);
            result.code = code;
            return result;
          }
          function suggestionIdOf(message = {}) {
            const suggestionId = String(message.suggestionId || "").trim();
            if (!/^suggestion-[A-Za-z0-9._:-]{1,149}$/.test(suggestionId)) throw error("INVALID_INPUT", "suggestionId is required");
            return suggestionId;
          }
          function change(action, id, before, after, timestamp) {
            return schema.normalizeChangeRecord({ id, action, before, after, createdAt: timestamp }, schemaOptions(timestamp));
          }
          function nextChangeTime(changes, timestamp) {
            return Math.max(timestamp, changes.reduce((maximum, item) => Math.max(maximum, Number(item.createdAt) || 0), -1) + 1);
          }
          function requirePending(suggestion, timestamp) {
            if (!suggestion) throw error("SUGGESTION_NOT_FOUND", "Suggestion not found");
            if (suggestion.status === "expired" || suggestion.status === "pending" && suggestion.expiresAt <= timestamp) {
              throw error("SUGGESTION_EXPIRED", "Suggestion has expired");
            }
            if (suggestion.status !== "pending") throw error("SUGGESTION_STALE", "Suggestion is no longer pending");
          }
          function eligiblePageIds(pageIds, snapshot, browserSessionId, timestamp) {
            return typeof observationGate?.eligiblePageIds === "function" ? observationGate.eligiblePageIds(pageIds, snapshot, browserSessionId, timestamp) : pageIds;
          }
          async function readSnapshot(pageIds, browserSessionId) {
            const records = await store.runTransaction(snapshotStores, "readonly", async (tx) => {
              const [tasks, relations, tabInstances, events, restoreEpisodes, suggestionsByPage] = await Promise.all([
                tx.getAll(stores.TASKS),
                tx.getAll(stores.RELATIONS),
                tx.getAllByIndex(stores.TAB_INSTANCES, "browserSessionId", browserSessionId),
                tx.getAllByIndex(stores.BEHAVIOR_EVENTS, "browserSessionId", browserSessionId),
                tx.getAllByIndex(stores.BEHAVIOR_EVENTS, "type", "restore.explicit"),
                Promise.all(pageIds.map((pageId) => tx.getAllByIndex(stores.SUGGESTIONS, "pageId", pageId)))
              ]);
              return { tasks, relations, tabInstances, events, restoreEpisodes, suggestions: suggestionsByPage.flat() };
            });
            const pageIdsToRead = new Set(pageIds);
            records.relations.forEach((relation) => {
              if (relation?.source === "manual" && relation.manualLocked === true && relation.pageId) {
                pageIdsToRead.add(relation.pageId);
              }
            });
            const pages = await store.runTransaction([stores.PAGES], "readonly", (tx) => Promise.all([...pageIdsToRead].map((pageId) => tx.get(stores.PAGES, pageId))));
            return { ...records, pages: pages.filter(Boolean) };
          }
          function relationSemantics(pageRelations = []) {
            const hasPrimary = pageRelations.some((relation) => relation?.isPrimary === true);
            return hasPrimary ? Object.freeze({ role: "shared", isPrimary: false }) : Object.freeze({ role: "core", isPrimary: true });
          }
          function inputVersion(page, task, aggregate, scored, rank, semantics) {
            return `input-${hashToken([
              aggregate.signalVersion,
              page.updatedAt || 0,
              task.updatedAt || 0,
              scored.ruleVersion,
              aggregate.openerHits,
              aggregate.stableObjectMatch,
              aggregate.qualifiedReturnCount,
              aggregate.taskSnapshotRestoreHits,
              aggregate.nativeGroupRestoreHits,
              aggregate.titleOverlap,
              aggregate.sameNativeGroup,
              aggregate.sameWindow,
              aggregate.sameDomain,
              aggregate.targetPathType,
              aggregate.riskFlags,
              scored.score,
              scored.confidence,
              scored.reasonCodes,
              scored.riskFlags,
              rank,
              semantics.role,
              semantics.isPrimary
            ])}`;
          }
          function reasonFor(evidenceCodes) {
            const reasons = evidenceCodes.map((code) => REASON_TEXT[code]).filter(Boolean).slice(0, 2);
            return reasons.join("\uFF1B") || "\u5B58\u5728\u591A\u9879\u53EF\u9A8C\u8BC1\u7684\u672C\u5730\u5173\u8054\u4FE1\u53F7";
          }
          function makeDesired(page, task, aggregate, scored, analysisRunId, rank, timestamp, pageRelations) {
            const semantics = relationSemantics(pageRelations);
            const version = inputVersion(page, task, aggregate, scored, rank, semantics);
            const allowedEvidence = new Set(schema.SUGGESTION_EVIDENCE_CODES || []);
            const evidenceCodes = scored.reasonCodes.filter((code) => allowedEvidence.has(code)).slice(0, 3);
            return {
              kind: "relation.add",
              pageId: page.id,
              taskId: task.id,
              role: semantics.role,
              isPrimary: semantics.isPrimary,
              origin: "rule",
              confidence: scored.confidence,
              riskFlags: [...scored.riskFlags],
              evidenceCodes,
              reasonText: reasonFor(evidenceCodes),
              reasonLocale: "zh",
              status: "pending",
              analysisRunId,
              rank,
              inputVersion: version,
              ruleVersion: scored.ruleVersion,
              modelVersion: "",
              dedupeKey: `dedupe-${hashToken([page.id, task.id, "relation.add", version])}`,
              createdAt: timestamp,
              updatedAt: timestamp,
              expiresAt: timestamp + PENDING_TTL_MS,
              decidedAt: null,
              cooldownUntil: null,
              appliedRelationId: null,
              appliedChangeId: null
            };
          }
          async function refresh(message = {}) {
            const pageIds = pageIdsOf(message);
            if (!pageIds.length) return { ok: true, created: 0, updated: 0, expired: 0, ruleVersion: ruleVersionFallback };
            const timestamp = clock();
            const browserSessionId = String(await getBrowserSessionId() || "").trim();
            if (!browserSessionId) throw new TypeError("Browser session is unavailable");
            const snapshot = await readSnapshot(pageIds, browserSessionId);
            const pagesById = new Map(snapshot.pages.map((page) => [page.id, page]));
            const tasksById = new Map(snapshot.tasks.map((task) => [task.id, task]));
            const openPageIds = new Set(snapshot.tabInstances.map((instance) => instance.pageId).filter(Boolean));
            const relationsByPage = /* @__PURE__ */ new Map();
            snapshot.relations.forEach((relation) => {
              if (!relation?.pageId) return;
              const group = relationsByPage.get(relation.pageId) || [];
              group.push(relation);
              relationsByPage.set(relation.pageId, group);
            });
            const desired = [];
            let reportedRuleVersion = ruleVersionFallback;
            for (const pageId of pageIds) {
              const page = pagesById.get(pageId);
              if (!page || !openPageIds.has(pageId) || !eligiblePageIds([pageId], snapshot, browserSessionId, timestamp).length) continue;
              const pageRelations = relationsByPage.get(pageId) || [];
              const existingTaskIds = new Set(pageRelations.map((relation) => relation.taskId));
              const aggregateResult = aggregateSuggestionSignals({
                page,
                pages: snapshot.pages,
                tasks: snapshot.tasks,
                relations: snapshot.relations,
                tabInstances: snapshot.tabInstances,
                events: snapshot.events,
                suggestions: snapshot.suggestions,
                browserSessionId,
                now: timestamp,
                maxCandidates: MAX_CANDIDATES
              });
              const recalled = aggregateResult.candidates.filter((candidate) => !existingTaskIds.has(candidate.taskId)).filter((candidate) => !pageRelations.length || candidate.stableObjectMatch === true && (candidate.targetPathType === "document" || Number(candidate.openerHits) > 0 || Number(candidate.qualifiedReturnCount) > 0 || Number(candidate.taskSnapshotRestoreHits) > 0)).slice(0, MAX_CANDIDATES);
              const scoredResult = scoreSuggestionCandidates({ pageId, candidates: recalled });
              const aggregateByTask = new Map(recalled.map((candidate) => [candidate.taskId, candidate]));
              const eligible = scoredResult.candidates.filter((candidate) => candidate.eligible === true).slice(0, MAX_CANDIDATES);
              const analysisRunId = String(createId("analysis"));
              eligible.forEach((scored, index) => {
                const aggregate = aggregateByTask.get(scored.taskId);
                const task = tasksById.get(scored.taskId);
                if (!aggregate || !task || task.status === "done") return;
                reportedRuleVersion = scored.ruleVersion || reportedRuleVersion;
                desired.push(makeDesired(
                  page,
                  task,
                  aggregate,
                  scored,
                  analysisRunId,
                  index + 1,
                  timestamp,
                  pageRelations
                ));
              });
            }
            return store.runTransaction([stores.SUGGESTIONS], "readwrite", async (tx) => {
              const existingByPage = /* @__PURE__ */ new Map();
              for (const pageId of pageIds) {
                existingByPage.set(pageId, await tx.getAllByIndex(stores.SUGGESTIONS, "pageId", pageId));
              }
              const desiredByPageTask = new Map(desired.map((item) => [`${item.pageId}:${item.taskId}`, item]));
              let created = 0;
              let updated = 0;
              let expired = 0;
              for (const suggestions of existingByPage.values()) {
                for (const suggestion of suggestions) {
                  if (suggestion.status !== "pending") continue;
                  if (suggestion.expiresAt <= timestamp) {
                    await tx.put(stores.SUGGESTIONS, schema.normalizeSuggestion({
                      ...suggestion,
                      status: "expired",
                      updatedAt: timestamp,
                      decidedAt: timestamp
                    }, schemaOptions(timestamp)));
                    expired += 1;
                    continue;
                  }
                  const next = desiredByPageTask.get(`${suggestion.pageId}:${suggestion.taskId}`);
                  if (!next || next.inputVersion !== suggestion.inputVersion) {
                    await tx.put(stores.SUGGESTIONS, schema.normalizeSuggestion({
                      ...suggestion,
                      status: "superseded",
                      updatedAt: timestamp,
                      decidedAt: timestamp
                    }, schemaOptions(timestamp)));
                    updated += 1;
                  }
                }
              }
              for (const candidate of desired) {
                const existing = existingByPage.get(candidate.pageId) || [];
                const activeCooldown = existing.some((suggestion) => suggestion.taskId === candidate.taskId && (suggestion.status === "rejected" || suggestion.status === "undone") && Number(suggestion.cooldownUntil) > timestamp);
                if (activeCooldown || existing.some((suggestion) => suggestion.dedupeKey === candidate.dedupeKey)) continue;
                await tx.put(stores.SUGGESTIONS, schema.normalizeSuggestion(candidate, schemaOptions(timestamp)));
                created += 1;
              }
              return { ok: true, created, updated, expired, ruleVersion: reportedRuleVersion };
            });
          }
          async function list(message = {}) {
            const pageIds = pageIdsOf(message);
            if (!pageIds.length) return { ok: true, suggestions: [] };
            const timestamp = clock();
            const browserSessionId = String(await getBrowserSessionId() || "").trim();
            if (!browserSessionId) return { ok: true, suggestions: [] };
            const snapshot = await readSnapshot(pageIds, browserSessionId);
            const allowed = new Set(eligiblePageIds(pageIds, snapshot, browserSessionId, timestamp));
            const records = snapshot.suggestions.filter((suggestion) => allowed.has(suggestion.pageId));
            const seen = /* @__PURE__ */ new Set();
            const perPage = /* @__PURE__ */ new Map();
            records.filter((suggestion) => suggestion.status === "pending" && suggestion.expiresAt > timestamp).sort((left, right) => String(left.pageId).localeCompare(String(right.pageId)) || left.rank - right.rank || String(left.taskId).localeCompare(String(right.taskId))).forEach((suggestion) => {
              const dedupe = `${suggestion.pageId}:${suggestion.taskId}`;
              const pageSuggestions = perPage.get(suggestion.pageId) || [];
              if (seen.has(dedupe) || pageSuggestions.length >= MAX_CANDIDATES) return;
              seen.add(dedupe);
              pageSuggestions.push(clone(schema.normalizeSuggestion(suggestion, schemaOptions(timestamp))));
              perPage.set(suggestion.pageId, pageSuggestions);
            });
            return { ok: true, suggestions: [...perPage.values()].flat() };
          }
          async function applyAiOrganization(decisions, snapshotPending, planApplied, timestamp) {
            const originalKeys = new Set(snapshotPending.map((item) => `${item.pageId}:${item.taskId}:${item.inputVersion}`));
            const targetPageIds = [...new Set(snapshotPending.map((item) => item.pageId))];
            return store.runTransaction([stores.SUGGESTIONS, stores.RELATIONS], "readwrite", async (tx) => {
              let updated = 0;
              const byPage = /* @__PURE__ */ new Map();
              for (const pageId of targetPageIds) {
                const current = (await tx.getAllByIndex(stores.SUGGESTIONS, "pageId", pageId)).filter((item) => item.status === "pending" && item.expiresAt > timestamp && originalKeys.has(`${item.pageId}:${item.taskId}:${item.inputVersion}`));
                byPage.set(pageId, current);
                if (planApplied && (await tx.getAllByIndex(stores.RELATIONS, "pageId", pageId)).length) {
                  for (const item of current) {
                    const next = schema.normalizeSuggestion({
                      ...item,
                      status: "superseded",
                      updatedAt: timestamp,
                      decidedAt: timestamp
                    }, schemaOptions(timestamp));
                    await tx.put(stores.SUGGESTIONS, next);
                    updated += 1;
                  }
                  byPage.set(pageId, []);
                }
              }
              for (const decision of decisions) {
                const current = byPage.get(decision.pageId) || [];
                if (!current.length) continue;
                if (decision.decision === "no_match") {
                  for (const item of current) {
                    const next = schema.normalizeSuggestion({
                      ...item,
                      status: "superseded",
                      updatedAt: timestamp,
                      decidedAt: timestamp
                    }, schemaOptions(timestamp));
                    await tx.put(stores.SUGGESTIONS, next);
                    updated += 1;
                  }
                  continue;
                }
                const selected = current.find((item) => item.taskId === decision.taskId);
                if (!selected) continue;
                const ordered = [selected, ...current.filter((item) => item.id !== selected.id).sort((left, right) => left.rank - right.rank || String(left.taskId).localeCompare(String(right.taskId)))];
                for (const [index, item] of ordered.entries()) {
                  const isSelected = item.id === selected.id;
                  const next = schema.normalizeSuggestion({
                    ...item,
                    rank: index + 1,
                    origin: isSelected ? "ai" : "rule",
                    confidence: isSelected ? decision.confidence : item.confidence,
                    modelVersion: isSelected ? decision.modelVersion : "",
                    updatedAt: timestamp
                  }, schemaOptions(timestamp));
                  if (!same(item, next)) {
                    await tx.put(stores.SUGGESTIONS, next);
                    updated += 1;
                  }
                }
              }
              return updated;
            });
          }
          async function organize(message = {}) {
            const pageIds = pageIdsOf(message);
            const empty = { ok: true, updated: 0, planApplied: false, rankApplied: false };
            if (!aiCoordinator || !pageIds.length) return empty;
            const timestamp = clock();
            const browserSessionId = String(await getBrowserSessionId() || "").trim();
            if (!browserSessionId) return empty;
            const snapshot = await readSnapshot(pageIds, browserSessionId);
            const eligibleIds = eligiblePageIds(pageIds, snapshot, browserSessionId, timestamp);
            if (!eligibleIds.length) return empty;
            const requested = new Set(eligibleIds);
            const pending = snapshot.suggestions.filter((item) => requested.has(item.pageId) && item.status === "pending" && item.expiresAt > timestamp);
            let clusterResult = null;
            if (buildNewTaskClusters) {
              try {
                clusterResult = buildNewTaskClusters({
                  pageIds: eligibleIds,
                  pages: snapshot.pages,
                  relations: snapshot.relations,
                  tabInstances: snapshot.tabInstances,
                  events: snapshot.events,
                  restoreEpisodes: snapshot.restoreEpisodes,
                  browserSessionId,
                  now: timestamp
                });
              } catch (_error) {
                clusterResult = null;
              }
            }
            const hasAutomaticCluster = array(clusterResult?.clusters).some((cluster) => cluster?.disposition === "auto_candidate");
            if (!pending.length && !hasAutomaticCluster) return empty;
            const input = organizationInputModule.buildOrganizationInput(snapshot, pending, clusterResult);
            let result;
            try {
              result = await aiCoordinator.organize({
                inputVersion: input.inputVersion,
                sourcePageIds: eligibleIds,
                browserSessionId,
                locale: message.locale === "zh-CN" ? "zh-CN" : "en",
                pages: input.pages,
                tasks: input.tasks,
                candidates: input.candidates,
                localCandidates: input.localCandidates,
                newTaskClusters: input.newTaskClusters,
                signal: message.signal
              });
            } catch (_error) {
              return empty;
            }
            if (!result || result.ok !== true) return empty;
            const fallbackCode = SAFE_AI_FALLBACK_CODES.has(result.fallbackCode) ? result.fallbackCode : "";
            const staleInput = fallbackCode === "INPUT_VERSION_MISMATCH";
            const decisions = staleInput ? [] : safeRankDecisions(result.rankDecisions);
            const planApplied = result.planApplied === true;
            let updated = 0;
            try {
              if (!staleInput) updated = await applyAiOrganization(decisions, pending, planApplied, timestamp);
            } catch (_error) {
              return empty;
            }
            const response = {
              ok: true,
              updated,
              planApplied,
              rankApplied: result.rankApplied === true && decisions.length > 0
            };
            if (fallbackCode) response.fallbackCode = fallbackCode;
            if (SAFE_CHANGE_ID.test(String(result.changeId || ""))) response.changeId = result.changeId;
            const adjustment = response.changeId ? safeAdjustmentSummary(result.adjustment, response.changeId) : null;
            if (adjustment) response.adjustment = adjustment;
            return Object.freeze(response);
          }
          async function currentOrganizationInputVersion(message = {}) {
            const sourcePageIds = Array.isArray(message.sourcePageIds) ? message.sourcePageIds : message.pageIds;
            const pageIds = pageIdsOf({ pageIds: sourcePageIds });
            const expectedSessionId = String(message.browserSessionId || "").trim();
            if (!pageIds.length || !expectedSessionId) return "";
            const currentSessionId = String(await getBrowserSessionId() || "").trim();
            if (!currentSessionId || currentSessionId !== expectedSessionId) return "";
            const timestamp = clock();
            const snapshot = await readSnapshot(pageIds, currentSessionId);
            const eligibleIds = eligiblePageIds(pageIds, snapshot, currentSessionId, timestamp);
            if (!eligibleIds.length) return "";
            const requested = new Set(eligibleIds);
            const pending = snapshot.suggestions.filter((item) => requested.has(item.pageId) && item.status === "pending" && item.expiresAt > timestamp);
            let clusterResult = null;
            if (buildNewTaskClusters) {
              try {
                clusterResult = buildNewTaskClusters({
                  pageIds: eligibleIds,
                  pages: snapshot.pages,
                  relations: snapshot.relations,
                  tabInstances: snapshot.tabInstances,
                  events: snapshot.events,
                  restoreEpisodes: snapshot.restoreEpisodes,
                  browserSessionId: currentSessionId,
                  now: timestamp
                });
              } catch (_error) {
                return "";
              }
            }
            const hasAutomaticCluster = array(clusterResult?.clusters).some((cluster) => cluster?.disposition === "auto_candidate");
            if (!pending.length && !hasAutomaticCluster) return "";
            return organizationInputModule.buildOrganizationInput(snapshot, pending, clusterResult).inputVersion;
          }
          async function invalidatePending() {
            const timestamp = clock();
            return store.runTransaction([stores.SUGGESTIONS], "readwrite", async (tx) => {
              const pending = await tx.getAllByIndex(stores.SUGGESTIONS, "status", "pending");
              for (const suggestion of pending) {
                await tx.put(stores.SUGGESTIONS, schema.normalizeSuggestion({
                  ...suggestion,
                  status: "superseded",
                  updatedAt: timestamp,
                  decidedAt: timestamp
                }, schemaOptions(timestamp)));
              }
              return { ok: true, invalidated: pending.length };
            });
          }
          async function resolve(message = {}) {
            const suggestionId = suggestionIdOf(message);
            const decision = String(message.decision || "");
            if (decision !== "accept" && decision !== "reject") throw error("INVALID_INPUT", "decision must be accept or reject");
            const initial = await store.runTransaction([stores.SUGGESTIONS], "readonly", (tx) => tx.get(stores.SUGGESTIONS, suggestionId));
            if (!initial) throw error("SUGGESTION_NOT_FOUND", "Suggestion not found");
            if (decision === "accept") {
              if (initial.status !== "pending") requirePending(initial, clock());
              await refresh({ pageIds: [initial.pageId] });
            } else requirePending(initial, clock());
            const timestamp = clock();
            const browserSessionId = String(await getBrowserSessionId() || "").trim();
            if (!browserSessionId) throw error("SUGGESTION_STALE", "Browser session is unavailable");
            return store.runTransaction(decisionStores, "readwrite", async (tx) => {
              const pending = await tx.get(stores.SUGGESTIONS, suggestionId);
              requirePending(pending, timestamp);
              const [page, task, pageRelations, currentInstances, changes] = await Promise.all([
                tx.get(stores.PAGES, pending.pageId),
                tx.get(stores.TASKS, pending.taskId),
                tx.getAllByIndex(stores.RELATIONS, "pageId", pending.pageId),
                tx.getAllByIndex(stores.TAB_INSTANCES, "browserSessionId", browserSessionId),
                tx.getAll(stores.CHANGES)
              ]);
              if (!page || !task || task.status === "done" || !currentInstances.some((instance) => instance.pageId === pending.pageId)) {
                throw error("SUGGESTION_STALE", "Suggestion input has changed");
              }
              const duplicate = pageRelations.some((relation2) => relation2.taskId === pending.taskId);
              const primaryCount = pageRelations.filter((relation2) => relation2.isPrimary === true).length;
              const invalidSemantics = pending.isPrimary === true ? pageRelations.length !== 0 || pending.role !== "core" : primaryCount !== 1 || !["shared", "related"].includes(pending.role);
              if (duplicate || invalidSemantics) throw error("SUGGESTION_STALE", "Suggestion relation semantics have changed");
              const changeTimestamp = nextChangeTime(changes, timestamp);
              if (decision === "reject") {
                const changeId2 = String(createId("change"));
                const rejected = schema.normalizeSuggestion({
                  ...pending,
                  status: "rejected",
                  updatedAt: timestamp,
                  decidedAt: timestamp,
                  cooldownUntil: timestamp + REJECTION_COOLDOWN_MS
                }, schemaOptions(timestamp));
                const record2 = change(
                  "suggestion.reject",
                  changeId2,
                  { suggestions: [pending] },
                  { suggestions: [rejected] },
                  changeTimestamp
                );
                await tx.put(stores.SUGGESTIONS, rejected);
                await tx.put(stores.CHANGES, record2);
                return { ok: true, suggestion: clone(rejected), changeId: record2.id };
              }
              const taskRelations = await tx.getAllByIndex(stores.RELATIONS, "taskId", pending.taskId);
              const evidenceCode = pending.evidenceCodes.find((code) => schema.SUGGESTION_EVIDENCE_CODES.includes(code));
              if (!evidenceCode) throw error("SUGGESTION_STALE", "Suggestion has no canonical evidence");
              const sortOrder = taskRelations.reduce((maximum, relation2) => Math.max(maximum, Number(relation2.sortOrder) || 0), -1) + 1;
              const relation = schema.normalizeRelation({
                taskId: pending.taskId,
                pageId: pending.pageId,
                isPrimary: pending.isPrimary,
                role: pending.role,
                source: pending.origin,
                manualLocked: true,
                confidence: pending.confidence,
                evidence: `accepted:${pending.id}:${evidenceCode}`,
                sortOrder,
                createdAt: timestamp,
                updatedAt: timestamp
              }, schemaOptions(timestamp));
              const changeId = String(createId("change"));
              const accepted = schema.normalizeSuggestion({
                ...pending,
                status: "accepted",
                updatedAt: timestamp,
                decidedAt: timestamp,
                appliedRelationId: relation.id,
                appliedChangeId: changeId
              }, schemaOptions(timestamp));
              const record = change(
                "suggestion.accept",
                changeId,
                { suggestions: [pending], relations: [] },
                { suggestions: [accepted], relations: [relation] },
                changeTimestamp
              );
              await tx.put(stores.RELATIONS, relation);
              await tx.put(stores.SUGGESTIONS, accepted);
              await tx.put(stores.CHANGES, record);
              return { ok: true, suggestion: clone(accepted), relation: clone(relation), changeId: record.id };
            });
          }
          async function undo(message = {}) {
            const suggestionId = suggestionIdOf(message);
            const timestamp = clock();
            const preliminary = await store.runTransaction(
              [stores.SUGGESTIONS],
              "readonly",
              (tx) => tx.get(stores.SUGGESTIONS, suggestionId)
            );
            const browserSessionId = preliminary?.status === "rejected" ? String(await getBrowserSessionId() || "").trim() : "";
            return store.runTransaction(decisionStores, "readwrite", async (tx) => {
              const current = await tx.get(stores.SUGGESTIONS, suggestionId);
              if (!current) throw error("SUGGESTION_NOT_FOUND", "Suggestion not found");
              if (current.status !== "accepted" && current.status !== "rejected") {
                throw error("UNDO_NOT_AVAILABLE", "Suggestion cannot be undone");
              }
              const changes = await tx.getAll(stores.CHANGES);
              const record = current.status === "accepted" ? await tx.get(stores.CHANGES, current.appliedChangeId) : [...changes].filter((item) => item.action === "suggestion.reject" && item.undoneAt == null && item.after?.suggestions?.some((suggestion) => suggestion.id === current.id)).sort((left, right) => right.createdAt - left.createdAt || String(right.id).localeCompare(String(left.id)))[0];
              const recordedAfter = record?.after?.suggestions?.find((suggestion) => suggestion.id === current.id);
              if (!record || record.undoneAt != null || !same(current, recordedAfter)) {
                throw error("UNDO_CONFLICT", "Suggestion state has changed");
              }
              if (current.status === "accepted") {
                const expectedRelation = record.after?.relations?.find((relation) => relation.id === current.appliedRelationId);
                const storedRelation = await tx.get(stores.RELATIONS, current.appliedRelationId);
                const pageRelations2 = await tx.getAllByIndex(stores.RELATIONS, "pageId", current.pageId);
                const remainingRelations = pageRelations2.filter((relation) => relation.id !== current.appliedRelationId);
                const remainingPrimaries = remainingRelations.filter((relation) => relation.isPrimary === true).length;
                if (!expectedRelation || !same(storedRelation, expectedRelation) || !pageRelations2.some((relation) => relation.id === current.appliedRelationId) || remainingRelations.length > 0 && remainingPrimaries !== 1) {
                  throw error("UNDO_CONFLICT", "Accepted relation has changed");
                }
                const undone = schema.normalizeSuggestion({
                  ...current,
                  status: "undone",
                  updatedAt: timestamp,
                  decidedAt: timestamp,
                  cooldownUntil: timestamp + REJECTION_COOLDOWN_MS
                }, schemaOptions(timestamp));
                await tx.delete(stores.RELATIONS, current.appliedRelationId);
                await tx.put(stores.SUGGESTIONS, undone);
                await tx.put(stores.CHANGES, schema.normalizeChangeRecord({
                  ...record,
                  undoneAt: Math.max(timestamp, record.createdAt)
                }, schemaOptions(timestamp)));
                return { ok: true, suggestion: clone(undone), relationRemoved: true, changeId: record.id };
              }
              const [page, task, pageRelations, currentInstances] = await Promise.all([
                tx.get(stores.PAGES, current.pageId),
                tx.get(stores.TASKS, current.taskId),
                tx.getAllByIndex(stores.RELATIONS, "pageId", current.pageId),
                browserSessionId ? tx.getAllByIndex(stores.TAB_INSTANCES, "browserSessionId", browserSessionId) : Promise.resolve([])
              ]);
              const duplicate = pageRelations.some((relation) => relation.taskId === current.taskId);
              const primaryCount = pageRelations.filter((relation) => relation.isPrimary === true).length;
              const validSemantics = current.isPrimary === true ? pageRelations.length === 0 && current.role === "core" : primaryCount === 1 && ["shared", "related"].includes(current.role);
              const canRestore = current.expiresAt > timestamp && page && task && task.status !== "done" && !duplicate && validSemantics && currentInstances.some((instance) => instance.pageId === current.pageId);
              const next = schema.normalizeSuggestion(canRestore ? {
                ...current,
                status: "pending",
                updatedAt: timestamp,
                decidedAt: null,
                cooldownUntil: null,
                appliedRelationId: null,
                appliedChangeId: null
              } : {
                ...current,
                status: "superseded",
                updatedAt: timestamp,
                decidedAt: timestamp,
                cooldownUntil: null,
                appliedRelationId: null,
                appliedChangeId: null
              }, schemaOptions(timestamp));
              await tx.put(stores.SUGGESTIONS, next);
              await tx.put(stores.CHANGES, schema.normalizeChangeRecord({
                ...record,
                undoneAt: Math.max(timestamp, record.createdAt)
              }, schemaOptions(timestamp)));
              return {
                ok: true,
                suggestion: clone(next),
                restored: canRestore,
                superseded: !canRestore,
                changeId: record.id
              };
            });
          }
          return Object.freeze({
            refresh,
            list,
            organize,
            currentOrganizationInputVersion,
            invalidatePending,
            resolve,
            undo
          });
        }
        return Object.freeze({ PENDING_TTL_MS, REJECTION_COOLDOWN_MS, MAX_CANDIDATES, createV2SuggestionService });
      });
    }
  });

  // src/background/v2-ai-settings-store.js
  var require_v2_ai_settings_store = __commonJS({
    "src/background/v2-ai-settings-store.js"(exports, module) {
      (function attachV2AiSettingsStore(root, factory) {
        const api = factory(root);
        if (typeof module === "object" && module.exports) module.exports = api;
        if (root) root.TfaV2AiSettingsStore = api;
      })(typeof globalThis === "object" ? globalThis : exports, function createV2AiSettingsStoreModule(rootScope) {
        "use strict";
        const API_KEY_ACTIONS = Object.freeze(["preserve", "replace", "clear"]);
        function storageError(operation) {
          const error = new Error(`AI settings ${operation} failed`);
          error.code = "AI_SETTINGS_STORAGE_ERROR";
          return error;
        }
        function createV2AiSettingsStore({ storage, settingsLogic } = {}) {
          const logic = settingsLogic || rootScope.TfaV2AiSettings;
          if (!storage || typeof storage.get !== "function" || typeof storage.set !== "function" || typeof storage.remove !== "function") {
            throw new Error("AI settings store requires chrome.storage.local compatible storage");
          }
          if (!logic?.AI_SETTINGS_STORAGE_KEY || !logic?.AI_API_KEY_STORAGE_KEY || typeof logic.normalizeAiSettings !== "function" || typeof logic.publicAiSettings !== "function") {
            throw new Error("AI settings model is unavailable");
          }
          const settingsKey = logic.AI_SETTINGS_STORAGE_KEY;
          const apiKeyKey = logic.AI_API_KEY_STORAGE_KEY;
          let mutationQueue = Promise.resolve();
          function enqueue(work) {
            const operation = mutationQueue.then(work);
            mutationQueue = operation.then(() => void 0, () => void 0);
            return operation;
          }
          function storedApiKey(value) {
            try {
              return logic.normalizeApiKey(value);
            } catch (_error) {
              return "";
            }
          }
          async function readStored() {
            let record;
            try {
              record = await storage.get([settingsKey, apiKeyKey]);
            } catch (_error) {
              throw storageError("read");
            }
            return {
              settings: logic.normalizeAiSettings(record?.[settingsKey]),
              apiKey: storedApiKey(record?.[apiKeyKey])
            };
          }
          async function load() {
            await mutationQueue;
            const record = await readStored();
            return logic.publicAiSettings(record.settings, record.apiKey);
          }
          async function readApiKey() {
            await mutationQueue;
            return (await readStored()).apiKey;
          }
          function save(input = {}) {
            const envelope = input && typeof input === "object" && !Array.isArray(input) ? input : {};
            const action = String(envelope.apiKeyAction || "preserve");
            if (!API_KEY_ACTIONS.includes(action)) throw new TypeError("Invalid AI API key action");
            const patch = envelope.settings && typeof envelope.settings === "object" && !Array.isArray(envelope.settings) ? envelope.settings : envelope;
            return enqueue(async () => {
              const previous = await readStored();
              const settings = logic.normalizeAiSettings({ ...previous.settings, ...patch }, { strict: true });
              let apiKey = previous.apiKey;
              try {
                if (action === "replace") {
                  apiKey = logic.normalizeApiKey(envelope.apiKey, { required: true });
                  await storage.set({ [settingsKey]: settings, [apiKeyKey]: apiKey });
                } else if (action === "clear") {
                  apiKey = "";
                  await storage.remove(apiKeyKey);
                  await storage.set({ [settingsKey]: settings });
                } else {
                  await storage.set({ [settingsKey]: settings });
                }
              } catch (error) {
                if (error instanceof TypeError) throw error;
                throw storageError("write");
              }
              return logic.publicAiSettings(settings, apiKey);
            });
          }
          function clear() {
            return enqueue(async () => {
              try {
                await storage.remove([settingsKey, apiKeyKey]);
              } catch (_error) {
                throw storageError("clear");
              }
              return true;
            });
          }
          async function exportSafe() {
            const publicSettings = await load();
            const { maskedApiKey: _maskedApiKey, ...safe } = publicSettings;
            return Object.freeze(logic.redactAiData(safe));
          }
          async function diagnostics() {
            const publicSettings = await load();
            return Object.freeze({
              enabled: publicSettings.enabled,
              autoOrganize: publicSettings.autoOrganize,
              provider: publicSettings.provider,
              baseUrl: publicSettings.baseUrl,
              model: publicSettings.model,
              excludeSensitiveSites: publicSettings.excludeSensitiveSites,
              hasApiKey: publicSettings.hasApiKey,
              storage: "local"
            });
          }
          return Object.freeze({ load, save, readApiKey, clear, exportSafe, diagnostics });
        }
        return Object.freeze({ API_KEY_ACTIONS, createV2AiSettingsStore });
      });
    }
  });

  // src/background/v2-openai-compatible-provider.js
  var require_v2_openai_compatible_provider = __commonJS({
    "src/background/v2-openai-compatible-provider.js"(exports, module) {
      (function attachV2OpenAiCompatibleProvider(rootScope, factory) {
        const contract = typeof module === "object" && module.exports ? require_v2_ai_contract() : rootScope.TfaV2AiContract;
        const api = factory(contract);
        if (typeof module === "object" && module.exports) module.exports = api;
        rootScope.TfaV2OpenAiCompatibleProvider = api;
      })(typeof globalThis === "object" ? globalThis : exports, function createV2OpenAiCompatibleProviderModule(contract) {
        "use strict";
        const PROVIDER = "openai-compatible";
        const DEFAULT_TIMEOUT_MS = 9e4;
        const MAX_TIMEOUT_MS = 12e4;
        const MAX_RESPONSE_BYTES = 256 * 1024;
        const MAX_CONTENT_BYTES = 64 * 1024;
        const MAX_UPSTREAM_REASON_LENGTH = 240;
        const ERROR_CODES = Object.freeze([
          "INVALID_SETTINGS",
          "ABORTED",
          "TIMEOUT",
          "NETWORK",
          "AUTH",
          "PAYMENT_REQUIRED",
          "FORBIDDEN",
          "NOT_FOUND",
          "RATE_LIMIT",
          "BAD_REQUEST",
          "SERVER",
          "HTTP",
          "NON_JSON",
          "EMPTY_RESPONSE",
          "OUTPUT_TOO_LARGE",
          "INVALID_OUTPUT"
        ]);
        const ERROR_MESSAGES = Object.freeze({
          INVALID_SETTINGS: "AI settings are invalid.",
          ABORTED: "AI request was cancelled.",
          TIMEOUT: "AI request timed out.",
          NETWORK: "AI service could not be reached.",
          AUTH: "AI service rejected the credentials.",
          PAYMENT_REQUIRED: "AI service requires account credit or model access.",
          FORBIDDEN: "AI service denied access to the provider or model.",
          NOT_FOUND: "AI service endpoint or model was not found.",
          RATE_LIMIT: "AI service rate limit was reached.",
          BAD_REQUEST: "AI service rejected the request format or model parameters.",
          SERVER: "AI service is temporarily unavailable.",
          HTTP: "AI service rejected the request.",
          NON_JSON: "AI service returned an unreadable response.",
          EMPTY_RESPONSE: "AI service returned no result.",
          OUTPUT_TOO_LARGE: "AI service returned too much data.",
          INVALID_OUTPUT: "AI service returned an invalid result."
        });
        class V2AiProviderError extends Error {
          constructor(code, status = null, upstreamReason = "") {
            super(ERROR_MESSAGES[code] || ERROR_MESSAGES.INVALID_OUTPUT);
            this.name = "V2AiProviderError";
            this.code = ERROR_CODES.includes(code) ? code : "INVALID_OUTPUT";
            if (Number.isInteger(status)) this.status = status;
            if (upstreamReason) this.upstreamReason = upstreamReason;
          }
        }
        function providerError(code, status, upstreamReason) {
          return new V2AiProviderError(code, status, upstreamReason);
        }
        function sanitizeUpstreamReason(value, secrets = []) {
          let text = typeof value === "string" ? value : "";
          if (!text) return "";
          for (const secret of secrets) {
            const raw = String(secret || "").trim();
            if (raw) text = text.split(raw).join("[redacted]");
          }
          return text.replace(/bearer\s+[^\s,;]+/gi, "Bearer [redacted]").replace(/\b(api[-_ ]?key|token|secret|authorization)\s*[:=]\s*[^\s,;]+/gi, "$1=[redacted]").replace(/[\u0000-\u001f\u007f]+/g, " ").replace(/\s+/g, " ").trim().slice(0, MAX_UPSTREAM_REASON_LENGTH);
        }
        function upstreamFailureFromRaw(raw, apiKey) {
          if (typeof raw !== "string" || !raw || raw.length > MAX_RESPONSE_BYTES) {
            return Object.freeze({ code: "", reason: "" });
          }
          let payload;
          try {
            payload = JSON.parse(raw);
          } catch (_) {
            return Object.freeze({ code: "", reason: "" });
          }
          const error = payload && typeof payload.error === "object" && !Array.isArray(payload.error) ? payload.error : null;
          const candidate = error?.message || error?.detail || (typeof payload?.error === "string" ? payload.error : "") || payload?.message || payload?.detail || "";
          const rawCode = error?.code || payload?.code || error?.type || payload?.type || "";
          const code = /^[A-Za-z0-9._:-]{1,80}$/.test(String(rawCode || "").trim()) ? String(rawCode).trim().toLowerCase() : "";
          return Object.freeze({ code, reason: sanitizeUpstreamReason(candidate, [apiKey]) });
        }
        function normalizeSettings(value = {}) {
          if (!value || typeof value !== "object" || Array.isArray(value)) throw providerError("INVALID_SETTINGS");
          let parsed;
          try {
            parsed = new URL(String(value.baseUrl || "").trim());
          } catch (_) {
            throw providerError("INVALID_SETTINGS");
          }
          if (parsed.protocol !== "https:" || parsed.username || parsed.password || parsed.search || parsed.hash) {
            throw providerError("INVALID_SETTINGS");
          }
          const model = String(value.model || "").trim();
          const apiKey = String(value.apiKey || "").trim();
          const timeoutMs = value.timeoutMs == null ? DEFAULT_TIMEOUT_MS : Number(value.timeoutMs);
          if (!model || model.length > 120 || !apiKey || apiKey.length > 4096 || !Number.isInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > MAX_TIMEOUT_MS) {
            throw providerError("INVALID_SETTINGS");
          }
          const baseUrl = parsed.toString().replace(/\/$/, "");
          return Object.freeze({ baseUrl, model, apiKey, timeoutMs });
        }
        function endpointOf(settings) {
          return `${settings.baseUrl}/chat/completions`;
        }
        function stripCodeFence(value) {
          const text = String(value || "").trim();
          const match = text.match(/^```(?:json)?\s*([\s\S]*?)\s*```$/i);
          return match ? match[1].trim() : text;
        }
        function parseStructuredContent(content) {
          const text = stripCodeFence(content);
          if (!text) throw providerError("EMPTY_RESPONSE");
          if (text.length > MAX_CONTENT_BYTES) throw providerError("OUTPUT_TOO_LARGE");
          let parsed;
          try {
            parsed = JSON.parse(text);
          } catch (_) {
            throw providerError("INVALID_OUTPUT");
          }
          if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw providerError("INVALID_OUTPUT");
          return parsed;
        }
        function errorCodeFromUpstream(status, failure = {}) {
          const code = String(failure.code || "").toLowerCase();
          const detail = `${code} ${String(failure.reason || "").toLowerCase()}`;
          if (/(?:invalid[_ .-]?api[_ .-]?key|unauthori[sz]ed|authentication[_ .-]?(?:failed|error)|auth[_ .-]?error)/.test(detail)) {
            return "AUTH";
          }
          if (/(?:insufficient[_ .-]?(?:user[_ .-]?)?quota|quota[_ .-]?(?:exceeded|exhausted)|billing[_ .-]?(?:required|error)|payment[_ .-]?required|余额不足|额度不足|配额不足|没有有效订阅|无可用订阅)/.test(detail)) {
            return "PAYMENT_REQUIRED";
          }
          if (/(?:model[_ .-]?not[_ .-]?found|endpoint[_ .-]?not[_ .-]?found|not[_ .-]?found)/.test(detail)) {
            return "NOT_FOUND";
          }
          if (/(?:rate[_ .-]?limit|too[_ .-]?many[_ .-]?requests)/.test(detail)) {
            return "RATE_LIMIT";
          }
          if (/(?:invalid[_ .-]?request|bad[_ .-]?request|validation[_ .-]?error|unprocessable)/.test(detail)) {
            return "BAD_REQUEST";
          }
          if (/(?:forbidden|access[_ .-]?denied|permission[_ .-]?denied|model[_ .-]?access)/.test(detail)) {
            return "FORBIDDEN";
          }
          if (status === 401) return "AUTH";
          if (status === 402) return "PAYMENT_REQUIRED";
          if (status === 403) return "FORBIDDEN";
          if (status === 404) return "NOT_FOUND";
          if (status === 429) return "RATE_LIMIT";
          if (status === 400 || status === 422) return "BAD_REQUEST";
          if (status >= 500) return "SERVER";
          return "HTTP";
        }
        function httpError(status, upstreamFailure = {}) {
          return providerError(
            errorCodeFromUpstream(status, upstreamFailure),
            status,
            upstreamFailure.reason || ""
          );
        }
        function responseContent(payload) {
          const choices = Array.isArray(payload?.choices) ? payload.choices : [];
          const content = choices[0]?.message?.content;
          if (typeof content === "string" && content.trim()) return content;
          if (Array.isArray(content)) {
            const text = content.map((part) => typeof part === "string" ? part : part?.text).filter((part) => typeof part === "string").join("").trim();
            if (text) return text;
          }
          const legacyText = choices[0]?.text;
          if (typeof legacyText === "string" && legacyText.trim()) return legacyText;
          throw providerError("EMPTY_RESPONSE");
        }
        function rankMessages(input) {
          return [
            {
              role: "system",
              content: [
                "Rank only the supplied page/task candidates. Output JSON only, without markdown or commentary.",
                'The root must be exactly {"version":1,"items":[...]} and every item must be exactly',
                '{"pageId":"an input pageId","decision":"assign|no_match","taskId":"an input candidate taskId or null","confidence":0.0,"reasonCodes":["semantic_match"]}.',
                "For no_match, taskId must be null. Never nest fields under input or output.",
                "Never invent ids, fields, tasks, pages, actions, reasons, or browsing facts."
              ].join(" ")
            },
            {
              role: "user",
              content: `Return {"version":1,"items":[{"pageId":"page-...","decision":"assign|no_match","taskId":"task-... or null","confidence":0,"reasonCodes":["semantic_match"]}]}. Input: ${JSON.stringify(input)}`
            }
          ];
        }
        function planMessages(input) {
          return [
            {
              role: "system",
              content: [
                "Plan virtual task changes only. Output JSON only, without markdown or commentary.",
                'The root must be exactly {"version":1,"actions":[...]}. Never nest action fields under input or output.',
                "Allowed exact action schemas:",
                '{"type":"create_ai_task","name":"...","domain":"work|study|life|interest|admin|other","pageIds":["input pageId","input pageId"],"confidence":0.0,"reasonCodes":["related_pages"]};',
                '{"type":"rename_ai_task","taskId":"input AI taskId","name":"...","confidence":0.0,"reasonCodes":["clearer_task_name"]};',
                '{"type":"assign_ai_relation","pageId":"input pageId","taskId":"input receivable taskId","role":"core|related|shared","isPrimary":false,"confidence":0.0,"reasonCodes":["semantic_match"]};',
                '{"type":"reorder_ai_tasks","taskIds":["input taskId","input taskId"],"confidence":0.0,"reasonCodes":["virtual_order"]};',
                '{"type":"no_change","reasonCodes":["insufficient_evidence"]}.',
                "create_ai_task needs at least two distinct input pageIds; reorder_ai_tasks needs at least two input taskIds.",
                "If no action satisfies every constraint, return only one no_change action.",
                "Never operate real browser tabs and never invent ids, fields, tasks, pages, actions, relations, or reason codes."
              ].join(" ")
            },
            {
              role: "user",
              content: `Return one version 1 workspacePlan JSON object using only the allowed action schemas and input closed sets. Input: ${JSON.stringify(input)}`
            }
          ];
        }
        function repairMessages(messages, invalidContent) {
          return [
            ...messages,
            { role: "assistant", content: String(invalidContent || "").slice(0, MAX_CONTENT_BYTES) },
            {
              role: "user",
              content: "Your previous response did not match the exact closed JSON schema. Return only one corrected JSON object. Do not add fields, markdown, commentary, or invented ids."
            }
          ];
        }
        function createOpenAiCompatibleProvider(options = {}) {
          if (!contract || typeof contract.normalizeRankResult !== "function" || typeof contract.normalizeWorkspacePlan !== "function") {
            throw new TypeError("AI contract is unavailable");
          }
          const defaultSettings = options.settings || null;
          const fetchImpl = options.fetch || options.fetchImpl || globalThis.fetch;
          const AbortControllerImpl = options.AbortController || options.AbortControllerImpl || globalThis.AbortController;
          const setTimer = options.setTimer || options.setTimeout || globalThis.setTimeout;
          const clearTimer = options.clearTimer || options.clearTimeout || globalThis.clearTimeout;
          if (typeof fetchImpl !== "function" || typeof AbortControllerImpl !== "function" || typeof setTimer !== "function" || typeof clearTimer !== "function") {
            throw new TypeError("AI provider dependencies are unavailable");
          }
          async function request(settingsValue, messages, maxTokens, externalSignal) {
            const settings = normalizeSettings(settingsValue);
            if (externalSignal?.aborted) throw providerError("ABORTED");
            const controller = new AbortControllerImpl();
            let timedOut = false;
            const onAbort = () => controller.abort();
            externalSignal?.addEventListener?.("abort", onAbort, { once: true });
            const timer = setTimer(() => {
              timedOut = true;
              controller.abort();
            }, settings.timeoutMs);
            try {
              let response;
              try {
                response = await fetchImpl(endpointOf(settings), {
                  method: "POST",
                  headers: {
                    Accept: "application/json",
                    "Content-Type": "application/json",
                    Authorization: `Bearer ${settings.apiKey}`
                  },
                  // Avoid optional sampling/structured-output fields here. Reasoning models and
                  // OpenAI-compatible gateways differ on those fields, while the core chat
                  // completions shape below is broadly supported.
                  body: JSON.stringify({ model: settings.model, messages, stream: false, max_tokens: maxTokens }),
                  signal: controller.signal
                });
              } catch (_) {
                if (timedOut) throw providerError("TIMEOUT");
                if (externalSignal?.aborted) throw providerError("ABORTED");
                throw providerError("NETWORK");
              }
              const status = Number(response?.status) || 0;
              if (!response?.ok) {
                let errorRaw = "";
                try {
                  if (typeof response.text === "function") errorRaw = await response.text();
                } catch (_) {
                }
                throw httpError(status, upstreamFailureFromRaw(errorRaw, settings.apiKey));
              }
              if (typeof response.text !== "function") throw providerError("NON_JSON");
              let raw;
              try {
                raw = await response.text();
              } catch (_) {
                throw providerError("NETWORK");
              }
              if (typeof raw !== "string" || raw.length > MAX_RESPONSE_BYTES) throw providerError("OUTPUT_TOO_LARGE");
              let payload;
              try {
                payload = JSON.parse(raw);
              } catch (_) {
                throw providerError("NON_JSON");
              }
              return { content: responseContent(payload), model: String(payload.model || settings.model).slice(0, 120) };
            } finally {
              clearTimer(timer);
              externalSignal?.removeEventListener?.("abort", onAbort);
            }
          }
          async function testConnection(settingsValue = defaultSettings, signal) {
            try {
              const result = await request(settingsValue, [
                { role: "system", content: "Reply with OK only." },
                { role: "user", content: "Connection test." }
              ], 8, signal);
              return { ok: true, provider: PROVIDER, model: result.model };
            } catch (error) {
              const safe = error instanceof V2AiProviderError ? error : providerError("NETWORK");
              return {
                ok: false,
                provider: PROVIDER,
                errorCode: safe.code,
                status: safe.status ?? null,
                ...safe.upstreamReason ? { upstreamReason: safe.upstreamReason } : {}
              };
            }
          }
          async function requestStructured(messages, maxTokens, normalizeResult, signal) {
            let response = await request(defaultSettings, messages, maxTokens, signal);
            for (let attempt = 0; attempt < 2; attempt += 1) {
              try {
                const parsed = parseStructuredContent(response.content);
                return normalizeResult(parsed);
              } catch (error) {
                const invalidOutput = error instanceof V2AiProviderError && error.code === "INVALID_OUTPUT";
                if (!invalidOutput || attempt > 0) throw invalidOutput ? error : providerError(error?.code || "INVALID_OUTPUT");
                response = await request(defaultSettings, repairMessages(messages, response.content), maxTokens, signal);
              }
            }
            throw providerError("INVALID_OUTPUT");
          }
          async function rankCandidates(input, signal) {
            let normalizedInput;
            try {
              normalizedInput = contract.normalizeRankInput(input);
            } catch (_) {
              throw providerError("INVALID_OUTPUT");
            }
            return requestStructured(rankMessages(normalizedInput), 1800, (parsed) => {
              try {
                return contract.normalizeRankResult(parsed, normalizedInput);
              } catch (_) {
                throw providerError("INVALID_OUTPUT");
              }
            }, signal);
          }
          async function planWorkspace(input, signal) {
            let normalizedInput;
            try {
              normalizedInput = contract.normalizeWorkspaceInput(input);
            } catch (_) {
              throw providerError("INVALID_OUTPUT");
            }
            return requestStructured(planMessages(normalizedInput), 2400, (parsed) => {
              try {
                return contract.normalizeWorkspacePlan(parsed, normalizedInput);
              } catch (_) {
                throw providerError("INVALID_OUTPUT");
              }
            }, signal);
          }
          return Object.freeze({ provider: PROVIDER, testConnection, rankCandidates, planWorkspace });
        }
        return Object.freeze({
          PROVIDER,
          DEFAULT_TIMEOUT_MS,
          MAX_TIMEOUT_MS,
          MAX_RESPONSE_BYTES,
          MAX_CONTENT_BYTES,
          MAX_UPSTREAM_REASON_LENGTH,
          ERROR_CODES,
          V2AiProviderError,
          normalizeSettings,
          stripCodeFence,
          createOpenAiCompatibleProvider,
          createOpenAICompatibleProvider: createOpenAiCompatibleProvider
        });
      });
    }
  });

  // src/background/v2-ai-service.js
  var require_v2_ai_service = __commonJS({
    "src/background/v2-ai-service.js"(exports, module) {
      (function attachV2AiService(rootScope, factory) {
        const api = factory(rootScope);
        if (typeof module === "object" && module.exports) module.exports = api;
        if (rootScope) rootScope.TfaV2AiService = api;
      })(typeof globalThis === "object" ? globalThis : exports, function createV2AiServiceModule(rootScope) {
        "use strict";
        const SERVICE_ERROR_CODES = Object.freeze([
          "AI_DISABLED",
          "AI_API_KEY_REQUIRED",
          "INVALID_SETTINGS",
          "AI_STORAGE_ERROR",
          "ABORTED",
          "TIMEOUT",
          "NETWORK",
          "AUTH",
          "PAYMENT_REQUIRED",
          "FORBIDDEN",
          "NOT_FOUND",
          "RATE_LIMIT",
          "BAD_REQUEST",
          "SERVER",
          "HTTP",
          "NON_JSON",
          "EMPTY_RESPONSE",
          "OUTPUT_TOO_LARGE",
          "INVALID_OUTPUT",
          "AI_PROVIDER_UNAVAILABLE",
          "AI_REQUEST_FAILED"
        ]);
        const PROVIDER_ERROR_CODES = /* @__PURE__ */ new Set([
          "INVALID_SETTINGS",
          "ABORTED",
          "TIMEOUT",
          "NETWORK",
          "AUTH",
          "PAYMENT_REQUIRED",
          "FORBIDDEN",
          "NOT_FOUND",
          "RATE_LIMIT",
          "BAD_REQUEST",
          "SERVER",
          "HTTP",
          "NON_JSON",
          "EMPTY_RESPONSE",
          "OUTPUT_TOO_LARGE",
          "INVALID_OUTPUT"
        ]);
        const PUBLIC_SETTING_FIELDS = Object.freeze([
          "enabled",
          "autoOrganize",
          "provider",
          "baseUrl",
          "model",
          "timeoutMs",
          "excludeSensitiveSites",
          "hasApiKey",
          "maskedApiKey"
        ]);
        function safeSettings(value = {}) {
          return Object.freeze(Object.fromEntries(PUBLIC_SETTING_FIELDS.filter((field) => Object.prototype.hasOwnProperty.call(value, field)).map((field) => [field, value[field]])));
        }
        function safeUpstreamReason(value, apiKey = "") {
          let text = typeof value === "string" ? value : "";
          const secret = String(apiKey || "").trim();
          if (secret) text = text.split(secret).join("[redacted]");
          return text.replace(/bearer\s+[^\s,;]+/gi, "Bearer [redacted]").replace(/\b(api[-_ ]?key|token|secret|authorization)\s*[:=]\s*[^\s,;]+/gi, "$1=[redacted]").replace(/[\u0000-\u001f\u007f]+/g, " ").replace(/\s+/g, " ").trim().slice(0, 240);
        }
        function failure(errorCode, upstreamReason = "", apiKey = "") {
          const code = SERVICE_ERROR_CODES.includes(errorCode) ? errorCode : "AI_REQUEST_FAILED";
          const reason = safeUpstreamReason(upstreamReason, apiKey);
          return Object.freeze({ ok: false, errorCode: code, ...reason ? { upstreamReason: reason } : {} });
        }
        function errorCodeOf(error) {
          if (error?.code === "AI_SETTINGS_STORAGE_ERROR") return "AI_STORAGE_ERROR";
          if (PROVIDER_ERROR_CODES.has(error?.code)) return error.code;
          if (error instanceof TypeError) return "INVALID_SETTINGS";
          return "AI_REQUEST_FAILED";
        }
        function createV2AiService({ settingsStore, settingsLogic, providerFactory } = {}) {
          const store = settingsStore;
          const logic = settingsLogic || rootScope.TfaV2AiSettings;
          const createProvider = providerFactory || rootScope.TfaV2OpenAiCompatibleProvider?.createOpenAiCompatibleProvider;
          if (!store || typeof store.load !== "function" || typeof store.save !== "function" || typeof store.readApiKey !== "function" || typeof store.clear !== "function") {
            throw new TypeError("AI service requires an AI settings store");
          }
          if (!logic || typeof logic.normalizeAiSettings !== "function" || typeof logic.normalizeApiKey !== "function") {
            throw new TypeError("AI service requires AI settings logic");
          }
          if (typeof createProvider !== "function") throw new TypeError("AI service requires an AI provider factory");
          async function getSettings() {
            try {
              return Object.freeze({ ok: true, settings: safeSettings(await store.load()) });
            } catch (error) {
              return failure(errorCodeOf(error));
            }
          }
          async function saveSettings(input = {}) {
            try {
              const saved = await store.save(input);
              return Object.freeze({ ok: true, settings: safeSettings(saved) });
            } catch (error) {
              return failure(errorCodeOf(error));
            }
          }
          function providerSettings(settings, apiKey) {
            return Object.freeze({
              baseUrl: settings.baseUrl,
              model: settings.model,
              timeoutMs: settings.timeoutMs,
              apiKey
            });
          }
          function providerFor(settings, apiKey) {
            return createProvider({ settings: providerSettings(settings, apiKey) });
          }
          async function readEnabledProvider() {
            const settings = await store.load();
            if (settings.enabled !== true) return { failure: failure("AI_DISABLED") };
            const apiKey = logic.normalizeApiKey(await store.readApiKey());
            if (!apiKey) return { failure: failure("AI_API_KEY_REQUIRED") };
            return { settings, apiKey, provider: providerFor(settings, apiKey) };
          }
          async function testConnection(input = {}, signal) {
            let activeApiKey = "";
            try {
              const stored = await store.load();
              const envelope = input && typeof input === "object" && !Array.isArray(input) ? input : {};
              const patch = envelope.settings && typeof envelope.settings === "object" && !Array.isArray(envelope.settings) ? envelope.settings : envelope;
              const settings = logic.normalizeAiSettings({ ...stored, ...patch }, { strict: true });
              const temporaryKey = Object.prototype.hasOwnProperty.call(envelope, "apiKey") ? logic.normalizeApiKey(envelope.apiKey) : "";
              const apiKey = temporaryKey || logic.normalizeApiKey(await store.readApiKey());
              if (!apiKey) return failure("AI_API_KEY_REQUIRED");
              activeApiKey = apiKey;
              const provider = providerFor(settings, apiKey);
              if (!provider || typeof provider.testConnection !== "function") return failure("AI_PROVIDER_UNAVAILABLE");
              const result = await provider.testConnection(providerSettings(settings, apiKey), signal);
              if (!result?.ok) {
                return failure(
                  PROVIDER_ERROR_CODES.has(result?.errorCode) ? result.errorCode : "AI_REQUEST_FAILED",
                  result?.upstreamReason,
                  apiKey
                );
              }
              return Object.freeze({ ok: true, model: settings.model });
            } catch (error) {
              return failure(errorCodeOf(error), error?.upstreamReason, activeApiKey);
            }
          }
          async function invoke(method, input, signal) {
            let apiKey = "";
            try {
              const context = await readEnabledProvider();
              if (context.failure) return context.failure;
              apiKey = context.apiKey;
              if (!context.provider || typeof context.provider[method] !== "function") return failure("AI_PROVIDER_UNAVAILABLE");
              const result = await context.provider[method](input, signal);
              return Object.freeze({ ok: true, result, model: context.settings.model });
            } catch (error) {
              return failure(errorCodeOf(error), error?.upstreamReason, apiKey);
            }
          }
          const rankCandidates = (input, signal) => invoke("rankCandidates", input, signal);
          const planWorkspace = (input, signal) => invoke("planWorkspace", input, signal);
          async function clearAll() {
            try {
              await store.clear();
              return Object.freeze({ ok: true });
            } catch (error) {
              return failure(errorCodeOf(error));
            }
          }
          return Object.freeze({
            getSettings,
            saveSettings,
            testConnection,
            rankCandidates,
            planWorkspace,
            clearAll
          });
        }
        return Object.freeze({ SERVICE_ERROR_CODES, createV2AiService });
      });
    }
  });

  // src/background/v2-ai-coordinator-state-store.js
  var require_v2_ai_coordinator_state_store = __commonJS({
    "src/background/v2-ai-coordinator-state-store.js"(exports, module) {
      (function attachV2AiCoordinatorStateStore(rootScope, factory) {
        const api = factory();
        if (typeof module === "object" && module.exports) module.exports = api;
        if (rootScope) rootScope.TfaV2AiCoordinatorStateStore = api;
      })(typeof globalThis === "object" ? globalThis : exports, function createV2AiCoordinatorStateStoreModule() {
        "use strict";
        const STORAGE_KEY = "tfa_v2_ai_coordinator_state";
        const FIELDS = Object.freeze([
          "version",
          "lastCompletedInputVersion",
          "lastAttemptAt",
          "failureCount",
          "retryAt"
        ]);
        function safeState(value = {}) {
          const source = value && typeof value === "object" && !Array.isArray(value) ? value : {};
          return Object.freeze(Object.fromEntries(FIELDS.filter((field) => Object.prototype.hasOwnProperty.call(source, field)).map((field) => [field, source[field]])));
        }
        function createV2AiCoordinatorStateStore({ storage } = {}) {
          if (!storage || typeof storage.get !== "function" || typeof storage.set !== "function") {
            throw new TypeError("AI coordinator state store requires chrome.storage.local");
          }
          async function load() {
            try {
              const record = await storage.get(STORAGE_KEY);
              return safeState(record?.[STORAGE_KEY]);
            } catch (_error) {
              const error = new Error("AI coordinator state is unavailable");
              error.code = "AI_STORAGE_ERROR";
              throw error;
            }
          }
          async function save(value) {
            const state = safeState(value);
            try {
              await storage.set({ [STORAGE_KEY]: state });
            } catch (_error) {
              const error = new Error("AI coordinator state could not be saved");
              error.code = "AI_STORAGE_ERROR";
              throw error;
            }
            return state;
          }
          async function clear() {
            if (typeof storage.remove !== "function") return false;
            try {
              await storage.remove(STORAGE_KEY);
              return true;
            } catch (_error) {
              return false;
            }
          }
          return Object.freeze({ load, save, clear });
        }
        return Object.freeze({ STORAGE_KEY, createV2AiCoordinatorStateStore });
      });
    }
  });

  // src/background/v2-ai-input-builder.js
  var require_v2_ai_input_builder = __commonJS({
    "src/background/v2-ai-input-builder.js"(exports, module) {
      (function attachV2AiInputBuilder(rootScope, factory) {
        const api = factory();
        if (typeof module === "object" && module.exports) module.exports = api;
        rootScope.TfaV2AiInputBuilder = api;
      })(typeof globalThis === "object" ? globalThis : exports, function createV2AiInputBuilderModule() {
        "use strict";
        const SAFE_PAGE_ID = /^page-[A-Za-z0-9._:-]{1,154}$/;
        const SAFE_TASK_ID = /^task-[A-Za-z0-9._:-]{1,154}$/;
        const MULTI_PART_SUFFIXES = /* @__PURE__ */ new Set([
          "co.uk",
          "org.uk",
          "com.cn",
          "net.cn",
          "org.cn",
          "com.au",
          "net.au",
          "co.jp",
          "co.kr",
          "co.nz",
          "com.br",
          "com.sg",
          "com.hk",
          "com.tw"
        ]);
        const DEFAULT_SENSITIVE_HOSTS = Object.freeze([
          "accounts.google.com",
          "mail.google.com",
          "outlook.live.com",
          "outlook.office.com",
          "login.live.com",
          "login.microsoftonline.com",
          "paypal.com"
        ]);
        const SENSITIVE_LABEL = /(?:^|[-.])(account|accounts|auth|bank|banking|billing|checkout|health|login|medical|patient|payment|signin|wallet)(?:[-.]|$)/i;
        const SENSITIVE_PATH = /\/(?:account|auth|banking|billing|checkout|health|login|medical|patient|payment|signin|wallet)(?:\/|$)/i;
        const URL_SCHEME = /\b(?:https?|file|chrome|edge):\/\/[^\s<>()]+/gi;
        const EMAIL = /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi;
        const SECRET_PAIR = /\b(?:api[_-]?key|authorization|bearer|password|passwd|secret|session|token)\s*[:=]\s*[^\s,;]+/gi;
        const LONG_SECRET = /\b(?:[A-F0-9]{32,}|[A-Za-z0-9_-]{40,})\b/g;
        const PAYMENT_NUMBER = /\b(?:\d[ -]*?){13,19}\b/g;
        function array(value) {
          return Array.isArray(value) ? value : [];
        }
        function safeId(value, pattern) {
          const id = String(value || "").trim();
          return pattern.test(id) ? id : "";
        }
        function redactText(value, maximum, fallback = "") {
          let text = String(value == null ? "" : value).replace(/[\u0000-\u001f\u007f]/g, " ").replace(URL_SCHEME, "[redacted-url]").replace(EMAIL, "[redacted-email]").replace(SECRET_PAIR, "[redacted-secret]").replace(LONG_SECRET, "[redacted-secret]").replace(PAYMENT_NUMBER, "[redacted-number]").replace(/\s+/g, " ").trim();
          if (!text) text = String(fallback || "").trim();
          if (text.length > maximum) text = text.slice(0, maximum).trim();
          return text;
        }
        function isPrivateHost(hostname) {
          const host = hostname.replace(/^\[|\]$/g, "").toLowerCase();
          if (!host || host === "localhost" || host === "::1" || host.endsWith(".local")) return true;
          const parts = host.split(".").map(Number);
          if (parts.length !== 4 || parts.some((part) => !Number.isInteger(part) || part < 0 || part > 255)) return !host.includes(".");
          return parts[0] === 10 || parts[0] === 127 || parts[0] === 169 && parts[1] === 254 || parts[0] === 172 && parts[1] >= 16 && parts[1] <= 31 || parts[0] === 192 && parts[1] === 168;
        }
        function registeredDomain(hostname) {
          const host = hostname.toLowerCase().replace(/^www\./, "").replace(/\.$/, "");
          if (isPrivateHost(host)) return "";
          const labels = host.split(".").filter(Boolean);
          if (labels.length < 2) return "";
          const suffix = labels.slice(-2).join(".");
          return MULTI_PART_SUFFIXES.has(suffix) && labels.length >= 3 ? labels.slice(-3).join(".") : suffix;
        }
        function classifyPath(pathname) {
          const path = String(pathname || "/").toLowerCase();
          if (path === "/" || path === "") return "root";
          if (/\/(?:search|find|results?)(?:\/|$)/.test(path)) return "search";
          if (/\/(?:settings?|preferences?|config)(?:\/|$)/.test(path)) return "settings";
          if (/\/(?:watch|videos?|media|live|audio)(?:\/|$)/.test(path)) return "media";
          if (/\/(?:docs?|documents?|files?|wiki|articles?)(?:\/|$)/.test(path)) return "document";
          if (/\/(?:projects?|repos?|repositories)(?:\/|$)/.test(path)) return "project";
          if (/\/(?:workspaces?|dashboard|app)(?:\/|$)/.test(path)) return "workspace";
          return "other";
        }
        function normalizeCounts(value, allowed, maximum = 8) {
          const counts = /* @__PURE__ */ new Map();
          const add = (code, count = 1) => {
            if (!allowed.has(code)) return;
            const safeCount = Math.max(1, Math.min(99, Math.floor(Number(count) || 1)));
            counts.set(code, Math.min(99, (counts.get(code) || 0) + safeCount));
          };
          if (Array.isArray(value)) {
            value.forEach((item) => {
              if (typeof item === "string") add(item);
              else if (item && typeof item === "object") add(item.code, item.count);
            });
          } else if (value && typeof value === "object") {
            Object.entries(value).forEach(([code, count]) => add(code, count));
          }
          return [...counts.entries()].slice(0, maximum).map(([code, count]) => ({ code, count }));
        }
        function createV2AiInputBuilder(options = {}) {
          const contract = options.contract || (typeof globalThis === "object" ? globalThis.TfaV2AiContract : null);
          if (!contract?.normalizeRankInput || !contract?.normalizeWorkspaceInput) {
            throw new TypeError("V2 AI contract is required");
          }
          const sensitiveHosts = new Set([
            ...DEFAULT_SENSITIVE_HOSTS,
            ...array(options.sensitiveHosts)
          ].map((host) => String(host).toLowerCase()));
          const customSensitive = typeof options.isSensitiveUrl === "function" ? options.isSensitiveUrl : () => false;
          const allowedEvidence = new Set(contract.EVIDENCE_CODES);
          const allowedRisks = new Set(contract.RISK_CODES);
          const allowedDomains = new Set(contract.TASK_DOMAINS);
          function sanitizePage(source) {
            const pageId = safeId(source?.pageId || source?.id, SAFE_PAGE_ID);
            if (!pageId) return { reason: "invalid_page" };
            const rawUrl = source?.url || source?.restoreUrl || source?.normalizedUrl;
            let parsed;
            try {
              parsed = new URL(String(rawUrl || ""));
            } catch (_error) {
              return { pageId, reason: "restricted_url" };
            }
            const hostname = parsed.hostname.toLowerCase();
            const domain = registeredDomain(hostname);
            const sensitiveRisk = array(source?.risks || source?.riskFlags).some((risk) => (typeof risk === "string" ? risk : risk?.code) === "sensitive_site");
            const isListed = [...sensitiveHosts].some((host) => hostname === host || hostname.endsWith(`.${host}`));
            if (!["http:", "https:"].includes(parsed.protocol) || !domain || sensitiveRisk || isListed || SENSITIVE_LABEL.test(hostname) || SENSITIVE_PATH.test(parsed.pathname) || customSensitive(parsed, source)) {
              return { pageId, reason: "sensitive_or_restricted_url" };
            }
            const title = redactText(source?.title, 200, domain);
            if (!title) return { pageId, reason: "invalid_title" };
            return {
              value: {
                pageId,
                title,
                registeredDomain: domain,
                pathType: contract.PATH_TYPES.includes(source?.pathType) ? source.pathType : classifyPath(parsed.pathname),
                evidence: normalizeCounts(source?.evidence || source?.evidenceCounts || source?.evidenceCodes, allowedEvidence),
                risks: normalizeCounts(source?.risks || source?.riskCounts || source?.riskFlags, allowedRisks)
              }
            };
          }
          function corePageTitles(task) {
            const raw = array(task?.corePageTitles).length ? task.corePageTitles : array(task?.pages || task?.tabs);
            const titles = [];
            for (const item of raw) {
              if (titles.length >= 5) break;
              if (item && typeof item === "object" && (item.url || item.restoreUrl || item.normalizedUrl)) {
                if (!sanitizePage({ ...item, pageId: item.pageId || item.id || "page-core" }).value) continue;
              }
              const title = redactText(typeof item === "string" ? item : item?.title, 160);
              if (title && !titles.includes(title)) titles.push(title);
            }
            return titles;
          }
          function sanitizeTask(source) {
            const taskId = safeId(source?.taskId || source?.id, SAFE_TASK_ID);
            const name = redactText(source?.name || source?.title, 120);
            if (!taskId || !name) return null;
            return {
              taskId,
              name,
              domain: allowedDomains.has(source?.domain) ? source.domain : "other",
              corePageTitles: corePageTitles(source)
            };
          }
          function candidateIdsFor(page, candidates) {
            const pageId = page?.pageId || page?.id;
            let raw = array(page?.candidateTaskIds).length ? page.candidateTaskIds : page?.candidates;
            if (!raw) {
              if (candidates instanceof Map) raw = candidates.get(pageId);
              else if (Array.isArray(candidates)) {
                raw = candidates.filter((entry) => entry?.pageId === pageId).flatMap((entry) => entry.taskIds || [entry.taskId]);
              } else if (candidates && typeof candidates === "object") raw = candidates[pageId];
            }
            return [...new Set(array(raw).flatMap((item) => {
              if (typeof item === "string") return [item];
              if (item?.taskIds) return item.taskIds;
              return [item?.taskId || item?.id];
            }).map((id) => safeId(id, SAFE_TASK_ID)).filter(Boolean))];
          }
          function buildRankCandidatesInput({ locale, pages, tasks, candidates } = {}) {
            const taskMap = new Map(array(tasks).map(sanitizeTask).filter(Boolean).map((task) => [task.taskId, task]));
            const rankPages = [];
            const skippedPages = [];
            for (const page of array(pages)) {
              const pageId = String(page?.pageId || page?.id || "");
              if (rankPages.length >= contract.MAX_PAGES) {
                skippedPages.push({ pageId, reason: "page_limit" });
                continue;
              }
              const safePage = sanitizePage(page);
              if (!safePage.value) {
                skippedPages.push({ pageId: safePage.pageId || pageId, reason: safePage.reason });
                continue;
              }
              const safeCandidates = candidateIdsFor(page, candidates).map((taskId) => taskMap.get(taskId)).filter(Boolean).slice(0, 8);
              if (safeCandidates.length < 3) {
                skippedPages.push({ pageId: safePage.value.pageId, reason: "insufficient_candidates" });
                continue;
              }
              rankPages.push({ ...safePage.value, candidates: safeCandidates });
            }
            const input = rankPages.length ? contract.normalizeRankInput({ version: contract.VERSION, locale: normalizeLocale(locale), pages: rankPages }) : null;
            return { input, skippedPages };
          }
          function buildPlanWorkspaceInput({ locale, pages, tasks } = {}) {
            const workspacePages = [];
            const skippedPages = [];
            for (const page of array(pages)) {
              const pageId = String(page?.pageId || page?.id || "");
              if (workspacePages.length >= contract.MAX_PAGES) {
                skippedPages.push({ pageId, reason: "page_limit" });
                continue;
              }
              const safePage = sanitizePage(page);
              if (safePage.value) workspacePages.push(safePage.value);
              else skippedPages.push({ pageId: safePage.pageId || pageId, reason: safePage.reason });
            }
            const workspaceTasks = [];
            const skippedTasks = [];
            for (const task of array(tasks)) {
              const taskId = String(task?.taskId || task?.id || "");
              if (workspaceTasks.length >= contract.MAX_TASKS) {
                skippedTasks.push({ taskId, reason: "task_limit" });
                continue;
              }
              const safeTask = sanitizeTask(task);
              if (!safeTask) {
                skippedTasks.push({ taskId, reason: "invalid_task" });
                continue;
              }
              const source = task?.source === "ai" ? "ai" : "manual";
              workspaceTasks.push({
                ...safeTask,
                source,
                canRename: source === "ai" && task?.manualLocked !== true && task?.canRename !== false,
                canReceiveRelations: task?.canReceiveRelations !== false && task?.status !== "done"
              });
            }
            const input = workspacePages.length ? contract.normalizeWorkspaceInput({
              version: contract.VERSION,
              locale: normalizeLocale(locale),
              pages: workspacePages,
              tasks: workspaceTasks
            }) : null;
            return { input, skippedPages, skippedTasks };
          }
          return Object.freeze({ buildRankCandidatesInput, buildPlanWorkspaceInput });
        }
        function normalizeLocale(locale) {
          return /^zh\b/i.test(String(locale || "")) ? "zh-CN" : "en";
        }
        return Object.freeze({ createV2AiInputBuilder, redactText, registeredDomain, classifyPath });
      });
    }
  });

  // src/background/v2-ai-suggestion-reranker.js
  var require_v2_ai_suggestion_reranker = __commonJS({
    "src/background/v2-ai-suggestion-reranker.js"(exports, module) {
      (function attachV2AiSuggestionReranker(rootScope, factory) {
        const contract = typeof module === "object" && module.exports ? require_v2_ai_contract() : rootScope.TfaV2AiContract;
        const api = factory(contract);
        if (typeof module === "object" && module.exports) module.exports = api;
        if (rootScope) rootScope.TfaV2AiSuggestionReranker = api;
      })(typeof globalThis === "object" ? globalThis : exports, function createV2AiSuggestionRerankerModule(defaultContract) {
        "use strict";
        const MIN_CANDIDATES = 3;
        const SAFE_FAILURE_CODES = /* @__PURE__ */ new Set([
          "AI_DISABLED",
          "AI_API_KEY_REQUIRED",
          "INVALID_SETTINGS",
          "AI_STORAGE_ERROR",
          "ABORTED",
          "TIMEOUT",
          "NETWORK",
          "AUTH",
          "PAYMENT_REQUIRED",
          "FORBIDDEN",
          "NOT_FOUND",
          "RATE_LIMIT",
          "BAD_REQUEST",
          "SERVER",
          "HTTP",
          "NON_JSON",
          "EMPTY_RESPONSE",
          "OUTPUT_TOO_LARGE",
          "INVALID_OUTPUT",
          "AI_PROVIDER_UNAVAILABLE",
          "AI_REQUEST_FAILED"
        ]);
        const own = (value, key) => Object.prototype.hasOwnProperty.call(value, key);
        function localFallback(localCandidates, fallbackCode) {
          return Object.freeze({
            ok: true,
            source: "local",
            applied: false,
            fallbackCode,
            candidates: localCandidates.slice(),
            decisions: Object.freeze([])
          });
        }
        function safeFailureCode(value) {
          return SAFE_FAILURE_CODES.has(value) ? value : "AI_REQUEST_FAILED";
        }
        function indexLocalCandidates(localCandidates) {
          if (!Array.isArray(localCandidates)) return null;
          const byPage = /* @__PURE__ */ new Map();
          const seen = /* @__PURE__ */ new Set();
          for (let index = 0; index < localCandidates.length; index += 1) {
            const candidate = localCandidates[index];
            const pageId = String(candidate?.pageId || "").trim();
            const taskId = String(candidate?.taskId || "").trim();
            const key = `${pageId}\0${taskId}`;
            if (!pageId || !taskId || seen.has(key)) return null;
            seen.add(key);
            const group = byPage.get(pageId) || [];
            group.push({ candidate, index, taskId });
            byPage.set(pageId, group);
          }
          return byPage;
        }
        function hasEnoughCandidates(rankInput, localByPage) {
          if (!rankInput || !Array.isArray(rankInput.pages) || !rankInput.pages.length) return false;
          return rankInput.pages.every((page) => (localByPage.get(String(page?.pageId || "")) || []).length >= MIN_CANDIDATES);
        }
        function explainableDecision(item, model) {
          return Object.freeze({
            pageId: item.pageId,
            decision: item.decision,
            taskId: item.taskId,
            confidence: item.confidence,
            reasonCodes: Object.freeze([...item.reasonCodes]),
            model: String(model || "").slice(0, 120)
          });
        }
        function applyRankResult({ localCandidates = [], rankInput, rankResult, model = "" } = {}, contract = defaultContract) {
          const localByPage = indexLocalCandidates(localCandidates);
          if (!localByPage) return localFallback(Array.isArray(localCandidates) ? localCandidates : [], "INVALID_LOCAL_CANDIDATES");
          if (!hasEnoughCandidates(rankInput, localByPage)) return localFallback(localCandidates, "TOO_FEW_CANDIDATES");
          if (!contract || typeof contract.normalizeRankResult !== "function") {
            return localFallback(localCandidates, "INVALID_OUTPUT");
          }
          let normalized;
          try {
            normalized = contract.normalizeRankResult(rankResult, rankInput);
          } catch (_) {
            return localFallback(localCandidates, "INVALID_OUTPUT");
          }
          const decisions = normalized.items.map((item) => explainableDecision(item, model));
          const output = localCandidates.slice();
          for (const decision of decisions) {
            if (decision.decision === "no_match") continue;
            const group = localByPage.get(decision.pageId) || [];
            const selectedIndex = group.findIndex((entry) => entry.taskId === decision.taskId);
            if (selectedIndex < 0) return localFallback(localCandidates, "INVALID_OUTPUT");
            const reordered = [group[selectedIndex], ...group.slice(0, selectedIndex), ...group.slice(selectedIndex + 1)];
            group.forEach((slot, index) => {
              const entry = reordered[index];
              output[slot.index] = entry.taskId === decision.taskId ? { ...entry.candidate, aiRerank: decision } : entry.candidate;
            });
          }
          return Object.freeze({
            ok: true,
            source: "ai",
            applied: decisions.some((item) => item.decision === "assign"),
            fallbackCode: null,
            candidates: output,
            decisions: Object.freeze(decisions)
          });
        }
        function createV2AiSuggestionReranker({ aiService, aiContract = defaultContract } = {}) {
          if (!aiService || typeof aiService.rankCandidates !== "function") {
            throw new TypeError("AI suggestion reranker requires an AI service");
          }
          async function rerank({ localCandidates = [], rankInput, signal } = {}) {
            const localByPage = indexLocalCandidates(localCandidates);
            if (!localByPage) return localFallback(Array.isArray(localCandidates) ? localCandidates : [], "INVALID_LOCAL_CANDIDATES");
            if (!hasEnoughCandidates(rankInput, localByPage)) return localFallback(localCandidates, "TOO_FEW_CANDIDATES");
            let response;
            try {
              response = await aiService.rankCandidates(rankInput, signal);
            } catch (error) {
              return localFallback(localCandidates, safeFailureCode(error?.code));
            }
            if (!response || response.ok !== true || !own(response, "result")) {
              return localFallback(localCandidates, safeFailureCode(response?.errorCode));
            }
            return applyRankResult({
              localCandidates,
              rankInput,
              rankResult: response.result,
              model: response.model
            }, aiContract);
          }
          return Object.freeze({ rerank });
        }
        return Object.freeze({ MIN_CANDIDATES, applyRankResult, createV2AiSuggestionReranker });
      });
    }
  });

  // src/background/v2-ai-plan-application-service.js
  var require_v2_ai_plan_application_service = __commonJS({
    "src/background/v2-ai-plan-application-service.js"(exports, module) {
      (function attachV2AiPlanApplicationService(rootScope, factory) {
        const api = factory();
        if (typeof module === "object" && module.exports) module.exports = api;
        if (rootScope) rootScope.TfaV2AiPlanApplicationService = api;
      })(typeof globalThis === "object" ? globalThis : exports, function createV2AiPlanApplicationServiceModule() {
        "use strict";
        const ALLOWED_ACTIONS = Object.freeze([
          "create_ai_task",
          "rename_ai_task",
          "assign_ai_relation",
          "reorder_ai_tasks",
          "no_change"
        ]);
        const THRESHOLDS = Object.freeze({
          create_ai_task: 0.86,
          rename_ai_task: 0.9,
          assign_ai_relation: 0.82,
          reorder_ai_tasks: 0.82
        });
        const ACTION_FIELDS = Object.freeze({
          create_ai_task: Object.freeze(["type", "name", "domain", "pageIds", "confidence", "reasonCodes"]),
          rename_ai_task: Object.freeze(["type", "taskId", "name", "confidence", "reasonCodes"]),
          assign_ai_relation: Object.freeze(["type", "pageId", "taskId", "role", "isPrimary", "confidence", "reasonCodes"]),
          reorder_ai_tasks: Object.freeze(["type", "taskIds", "confidence", "reasonCodes"]),
          no_change: Object.freeze(["type", "reasonCodes"])
        });
        const STORE_KEYS = Object.freeze({ pages: "PAGES", tasks: "TASKS", relations: "RELATIONS", changes: "CHANGES" });
        const BUSINESS_KEYS = Object.freeze(["pages", "tasks", "relations"]);
        const PROVENANCE = /^[a-z0-9][a-z0-9._:-]{0,159}$/;
        const AUTO_WRITE_RISKS = /* @__PURE__ */ new Set([
          "possible_misclick",
          "quick_pass",
          "redirect_or_ad",
          "temporary_query",
          "object_conflict",
          "initial_snapshot",
          "manual_rejected",
          "manual_conflict",
          "cooldown",
          "shared_tool",
          "intent_drift",
          "sensitive_site",
          "single_signal",
          "low_margin",
          "ambiguous",
          "unknown_risk",
          "noise_risk"
        ]);
        const ADJUSTMENT_TYPES = /* @__PURE__ */ new Set([
          "create_ai_task",
          "rename_ai_task",
          "assign_ai_relation",
          "reorder_ai_tasks"
        ]);
        const UNSAFE_ADJUSTMENT_TEXT = /(?:\b(?:https?|file|chrome|edge):\/\/|Bearer\s+|\bsk-[A-Za-z0-9_-]{8}|api[-_ ]?key|authorization|cookie|raw[_ ]?events?|behavior[_ ]?events?)/i;
        const ADJUSTMENT_REASONS = Object.freeze({
          related_pages: "Related pages",
          clearer_task_name: "Clearer task name",
          semantic_match: "Strong task match",
          virtual_order: "Task order"
        });
        const clone = (value) => value == null ? value : JSON.parse(JSON.stringify(value));
        const same = (left, right) => JSON.stringify(left ?? null) === JSON.stringify(right ?? null);
        function failure(code, message) {
          const error = new Error(message);
          error.code = code;
          return error;
        }
        function requirePlan(plan) {
          if (!plan || typeof plan !== "object" || Array.isArray(plan) || plan.version !== 1 || !Array.isArray(plan.actions) || plan.actions.length < 1 || plan.actions.length > 50) {
            throw failure("AI_PLAN_INVALID", "AI plan is invalid");
          }
          if (plan.actions.some((action) => !action || typeof action !== "object" || Array.isArray(action) || !ALLOWED_ACTIONS.includes(action.type))) {
            throw failure("AI_PLAN_ACTION_FORBIDDEN", "AI plan contains a forbidden action");
          }
          for (const action of plan.actions) {
            const fields = ACTION_FIELDS[action.type];
            if (Object.keys(action).some((field) => !fields.includes(field)) || fields.some((field) => !Object.prototype.hasOwnProperty.call(action, field))) {
              throw failure("AI_PLAN_INVALID", `${action.type} fields are invalid`);
            }
          }
          const noChange = plan.actions.filter((action) => action.type === "no_change");
          if (noChange.length && (noChange.length !== 1 || plan.actions.length !== 1)) {
            throw failure("AI_PLAN_INVALID", "no_change must be the only action");
          }
          return plan;
        }
        function requireId(value, field, prefix) {
          const id = String(value || "").trim();
          if (!id.startsWith(`${prefix}-`) || id.length > 160) throw failure("AI_PLAN_INVALID", `${field} is invalid`);
          return id;
        }
        function confidenceFor(action) {
          const value = Number(action.confidence);
          const threshold = THRESHOLDS[action.type];
          if (!Number.isFinite(value) || value < threshold || value > 1) {
            throw failure("AI_PLAN_CONFIDENCE_TOO_LOW", `${action.type} does not meet the automatic-write threshold`);
          }
          return value;
        }
        function reasonEvidence(type, modelVersion, reasonCodes) {
          const reasons = (Array.isArray(reasonCodes) ? reasonCodes : []).map((value) => String(value || "").trim()).filter((value) => /^[a-z0-9][a-z0-9_:-]{0,47}$/.test(value)).slice(0, 2);
          return [`ai`, modelVersion, type, ...reasons].join(":").slice(0, 160);
        }
        function snapshot(records = {}, metadata = null) {
          return {
            ...Object.fromEntries(BUSINESS_KEYS.map((key) => [key, clone(records[key] || [])])),
            ...metadata ? { aiPlan: clone(metadata) } : {}
          };
        }
        function exactEligibility(value, fields, label) {
          if (!value || typeof value !== "object" || Array.isArray(value) || Object.keys(value).some((field) => !fields.includes(field)) || fields.some((field) => !Object.prototype.hasOwnProperty.call(value, field))) {
            throw failure("AI_PLAN_NOT_ELIGIBLE", `${label} is invalid`);
          }
          return value;
        }
        function eligibilityRisks(value, label) {
          if (!Array.isArray(value) || value.length > 8 || value.some((risk) => typeof risk !== "string")) {
            throw failure("AI_PLAN_NOT_ELIGIBLE", `${label} risks are invalid`);
          }
          if (value.length || value.some((risk) => AUTO_WRITE_RISKS.has(risk) || !/^[a-z0-9_:-]{1,48}$/.test(risk))) {
            throw failure("AI_PLAN_HARD_RISK", `${label} contains a risk that forbids automatic writes`);
          }
        }
        function requireIndependentFamilies(value, label) {
          if (!Number.isInteger(value) || value < 2 || value > 8) {
            throw failure("AI_PLAN_NOT_ELIGIBLE", `${label} needs two independent signal families`);
          }
        }
        function pageSetKey(pageIds) {
          return [...pageIds].sort().join("|");
        }
        function adjustmentText(value, fallback, maximum) {
          const normalized = String(value == null ? "" : value).trim();
          if (!normalized || normalized.length > maximum || /[\u0000-\u001f\u007f]/.test(normalized) || UNSAFE_ADJUSTMENT_TEXT.test(normalized)) return fallback;
          return normalized;
        }
        function adjustmentReason(action) {
          const reason = (Array.isArray(action?.reasonCodes) ? action.reasonCodes : []).map((code) => ADJUSTMENT_REASONS[String(code || "").trim()]).find(Boolean);
          return reason || "AI organization";
        }
        function adjustmentItem(type, values = {}) {
          const safeType = ADJUSTMENT_TYPES.has(type) ? type : "reorder_ai_tasks";
          const title = adjustmentText(values.title, safeType === "reorder_ai_tasks" ? "Task order" : "AI task", 160);
          const before = adjustmentText(values.before, "Previous state", 200);
          const after = adjustmentText(values.after, "Updated state", 200);
          const reason = adjustmentText(values.reason, "AI organization", 240);
          return Object.freeze({ type: safeType, title, before, after, reason });
        }
        function adjustmentSummary(changeId, status, items) {
          const safeItems = (Array.isArray(items) ? items : []).slice(0, 50).map((item) => adjustmentItem(item?.type, item));
          return Object.freeze({ changeId, status, items: Object.freeze(safeItems), errorCode: "" });
        }
        function storedAdjustmentItems(metadata) {
          const stored = Array.isArray(metadata?.adjustmentItems) ? metadata.adjustmentItems : [];
          if (stored.length) return stored;
          return (Array.isArray(metadata?.actionTypes) ? metadata.actionTypes : []).filter((type) => ADJUSTMENT_TYPES.has(type)).slice(0, 50).map((type) => adjustmentItem(type));
        }
        function requireWriteContext(plan, context) {
          const validatedInputVersion = String(context?.validatedInputVersion || "").trim();
          const currentInputVersion = String(context?.currentInputVersion || "").trim();
          if (!PROVENANCE.test(validatedInputVersion) || !PROVENANCE.test(currentInputVersion)) {
            throw failure("AI_PLAN_INVALID", "validated and current input versions are required");
          }
          if (validatedInputVersion !== currentInputVersion) {
            throw failure("AI_INPUT_VERSION_MISMATCH", "AI plan input has changed");
          }
          const assignmentActions = plan.actions.filter((action) => action.type === "assign_ai_relation");
          const assignmentEntries = Array.isArray(context.eligibleAssignments) ? context.eligibleAssignments : [];
          const assignments = /* @__PURE__ */ new Map();
          for (const [index, entryValue] of assignmentEntries.entries()) {
            const entry = exactEligibility(
              entryValue,
              ["pageId", "taskId", "margin", "independentSignalFamilies", "riskFlags"],
              `eligibleAssignments[${index}]`
            );
            const pageId = requireId(entry.pageId, "pageId", "page");
            const taskId = requireId(entry.taskId, "taskId", "task");
            const key = `${pageId}::${taskId}`;
            if (assignments.has(key)) throw failure("AI_PLAN_NOT_ELIGIBLE", "eligibleAssignments must be unique");
            if (!Number.isFinite(Number(entry.margin)) || Number(entry.margin) < 0.18 || Number(entry.margin) > 1) {
              throw failure("AI_PLAN_NOT_ELIGIBLE", "AI assignment margin is below the automatic-write threshold");
            }
            requireIndependentFamilies(entry.independentSignalFamilies, `eligibleAssignments[${index}]`);
            eligibilityRisks(entry.riskFlags, `eligibleAssignments[${index}]`);
            assignments.set(key, entry);
          }
          if (assignmentActions.some((action) => !assignments.has(`${action.pageId}::${action.taskId}`))) {
            throw failure("AI_PLAN_NOT_ELIGIBLE", "AI assignment is outside the validated closed set");
          }
          const createActions = plan.actions.filter((action) => action.type === "create_ai_task");
          const createEntries = Array.isArray(context.eligibleCreatePageSets) ? context.eligibleCreatePageSets : [];
          const createSets = /* @__PURE__ */ new Set();
          for (const [index, entryValue] of createEntries.entries()) {
            const entry = exactEligibility(
              entryValue,
              ["pageIds", "independentSignalFamilies", "riskFlags"],
              `eligibleCreatePageSets[${index}]`
            );
            if (!Array.isArray(entry.pageIds) || entry.pageIds.length < 2 || new Set(entry.pageIds).size !== entry.pageIds.length) {
              throw failure("AI_PLAN_NOT_ELIGIBLE", `eligibleCreatePageSets[${index}] pages are invalid`);
            }
            const pageIds = entry.pageIds.map((pageId) => requireId(pageId, "pageId", "page"));
            requireIndependentFamilies(entry.independentSignalFamilies, `eligibleCreatePageSets[${index}]`);
            eligibilityRisks(entry.riskFlags, `eligibleCreatePageSets[${index}]`);
            const key = pageSetKey(pageIds);
            if (createSets.has(key)) throw failure("AI_PLAN_NOT_ELIGIBLE", "eligibleCreatePageSets must be unique");
            createSets.add(key);
          }
          if (createActions.some((action) => !Array.isArray(action.pageIds) || !createSets.has(pageSetKey(action.pageIds)))) {
            throw failure("AI_PLAN_NOT_ELIGIBLE", "AI task creation is outside the validated closed set");
          }
          return { validatedInputVersion, currentInputVersion };
        }
        function createV2AiPlanApplicationService({ store, schema, now = Date.now, createId } = {}) {
          if (!store || typeof store.runTransaction !== "function") {
            throw new TypeError("AI plan application service requires a V2 data store");
          }
          if (!schema?.STORES || typeof schema.normalizeTask !== "function" || typeof schema.normalizeRelation !== "function" || typeof schema.normalizeChangeRecord !== "function") {
            throw new TypeError("AI plan application service requires the V2 schema");
          }
          const names = Object.fromEntries(Object.entries(STORE_KEYS).map(([key, constant]) => [key, schema.STORES[constant]]));
          const transactionStores = [names.pages, names.tasks, names.relations, names.changes];
          const clock = () => {
            const value = Number(typeof now === "function" ? now() : now);
            return Number.isFinite(value) && value >= 0 ? Math.trunc(value) : Date.now();
          };
          const schemaOptions = (timestamp) => ({ now: timestamp, ...createId ? { createId } : {} });
          async function relationsForPage(tx, pageId) {
            return tx.getAllByIndex(names.relations, "pageId", pageId);
          }
          async function uniqueTask(tx, input, timestamp) {
            for (let attempt = 0; attempt < 20; attempt += 1) {
              const task = schema.normalizeTask(input, schemaOptions(timestamp));
              if (!await tx.get(names.tasks, task.id)) return task;
            }
            throw failure("AI_PLAN_ID_CONFLICT", "Unable to allocate an AI task id");
          }
          function requireModelVersion(value) {
            const modelVersion = String(value || "").trim();
            if (!PROVENANCE.test(modelVersion)) throw failure("AI_PLAN_INVALID", "modelVersion is invalid");
            return modelVersion;
          }
          function recordBefore(map, value) {
            if (value && !map.has(value.id)) map.set(value.id, clone(value));
          }
          function recordAfter(map, value) {
            map.set(value.id, clone(value));
          }
          function changeAction(actions, before) {
            if (actions.every((action) => action.type === "create_ai_task")) return "task.create";
            if (actions.every((action) => action.type === "reorder_ai_tasks")) return "task.reorder";
            if (before.tasks.length || actions.some((action) => action.type === "rename_ai_task")) return "task.update";
            return before.relations.length ? "relation.update" : "relation.add";
          }
          async function persistChange(tx, action, before, after, timestamp, metadata) {
            const existing = await tx.getAll(names.changes);
            const latest = existing.reduce((maximum, item) => Math.max(maximum, Number(item.createdAt) || 0), -1);
            const createdAt = Math.max(timestamp, latest + 1);
            const record = schema.normalizeChangeRecord({
              action: changeAction(action, before),
              before: snapshot(before),
              after: snapshot(after, metadata),
              createdAt
            }, schemaOptions(createdAt));
            await tx.put(names.changes, record);
            return record;
          }
          async function applyPlan(planValue, context = {}) {
            const plan = requirePlan(planValue);
            const writeContext = requireWriteContext(plan, context);
            if (plan.actions[0].type === "no_change") return { ok: true, unchanged: true, appliedActions: 0 };
            const modelVersion = requireModelVersion(context.modelVersion);
            const timestamp = clock();
            return store.runTransaction(transactionStores, "readwrite", async (tx) => {
              const [initialTasks, initialRelations] = await Promise.all([
                tx.getAll(names.tasks),
                tx.getAll(names.relations)
              ]);
              const initialTaskIds = new Set(initialTasks.map((task) => task.id));
              const touchedPages = /* @__PURE__ */ new Set();
              const beforeTasks = /* @__PURE__ */ new Map(), afterTasks = /* @__PURE__ */ new Map();
              const beforeRelations = /* @__PURE__ */ new Map(), afterRelations = /* @__PURE__ */ new Map();
              const adjustmentItems = [];
              let nextTaskOrder = initialTasks.reduce((maximum, task) => Math.max(maximum, Number(task.sortOrder) || 0), -1) + 1;
              for (const action of plan.actions) {
                const confidence = confidenceFor(action);
                if (action.type === "create_ai_task") {
                  if (!Array.isArray(action.pageIds) || action.pageIds.length < 2 || new Set(action.pageIds).size !== action.pageIds.length) {
                    throw failure("AI_PLAN_INVALID", "create_ai_task requires at least two unique pages");
                  }
                  const pages = [];
                  for (const pageIdValue of action.pageIds) {
                    const pageId = requireId(pageIdValue, "pageId", "page");
                    const page = await tx.get(names.pages, pageId);
                    if (!page) throw failure("AI_PLAN_STALE", "An AI task page no longer exists");
                    if ((await relationsForPage(tx, pageId)).length) {
                      throw failure("AI_PLAN_STALE", "An AI task page is no longer unassigned");
                    }
                    pages.push(page);
                  }
                  const evidence = reasonEvidence(action.type, modelVersion, action.reasonCodes);
                  const task = await uniqueTask(tx, {
                    title: action.name,
                    domain: action.domain,
                    status: "active",
                    sortOrder: nextTaskOrder++,
                    source: "ai",
                    manualLocked: false,
                    confidence,
                    evidence,
                    modelVersion,
                    createdAt: timestamp,
                    updatedAt: timestamp
                  }, timestamp);
                  await tx.put(names.tasks, task);
                  recordAfter(afterTasks, task);
                  for (const [index, page] of pages.entries()) {
                    const relation = schema.normalizeRelation({
                      taskId: task.id,
                      pageId: page.id,
                      role: "core",
                      isPrimary: true,
                      source: "ai",
                      manualLocked: false,
                      confidence,
                      evidence,
                      sortOrder: index,
                      createdAt: timestamp,
                      updatedAt: timestamp
                    }, schemaOptions(timestamp));
                    await tx.put(names.relations, relation);
                    recordAfter(afterRelations, relation);
                    touchedPages.add(page.id);
                  }
                  adjustmentItems.push(adjustmentItem(action.type, {
                    title: task.title,
                    before: "Not created",
                    after: `Created with ${pages.length} pages`,
                    reason: adjustmentReason(action)
                  }));
                  continue;
                }
                if (action.type === "rename_ai_task") {
                  const taskId = requireId(action.taskId, "taskId", "task");
                  const before2 = await tx.get(names.tasks, taskId);
                  if (!before2) throw failure("AI_PLAN_STALE", "AI rename target no longer exists");
                  if (before2.source !== "ai" || before2.manualLocked === true) {
                    throw failure("AI_MANUAL_LOCKED", "AI cannot rename a user-owned or locked task");
                  }
                  const evidence = reasonEvidence(action.type, modelVersion, action.reasonCodes);
                  const after2 = schema.normalizeTask({
                    ...before2,
                    title: action.name,
                    confidence,
                    evidence,
                    modelVersion,
                    updatedAt: timestamp
                  }, schemaOptions(timestamp));
                  if (!same(before2, after2)) {
                    recordBefore(beforeTasks, before2);
                    recordAfter(afterTasks, after2);
                    await tx.put(names.tasks, after2);
                    adjustmentItems.push(adjustmentItem(action.type, {
                      title: after2.title,
                      before: before2.title,
                      after: after2.title,
                      reason: adjustmentReason(action)
                    }));
                  }
                  continue;
                }
                if (action.type === "assign_ai_relation") {
                  const pageId = requireId(action.pageId, "pageId", "page");
                  const taskId = requireId(action.taskId, "taskId", "task");
                  const [page, task, current] = await Promise.all([
                    tx.get(names.pages, pageId),
                    tx.get(names.tasks, taskId),
                    relationsForPage(tx, pageId)
                  ]);
                  if (!page || !task) throw failure("AI_PLAN_STALE", "AI relation input has changed");
                  if (typeof action.isPrimary !== "boolean") throw failure("AI_PLAN_INVALID", "isPrimary is invalid");
                  const relationId = schema.makeRelationId(taskId, pageId);
                  const existing = current.find((relation2) => relation2.id === relationId);
                  if (existing && (existing.source !== "ai" || existing.manualLocked === true)) {
                    throw failure("AI_MANUAL_LOCKED", "AI cannot replace a user-confirmed relation");
                  }
                  if (!action.isPrimary && current.length === 0) {
                    throw failure("AI_PLAN_STALE", "A lone AI relation must be primary");
                  }
                  if (action.isPrimary) {
                    const protectedPrimary = current.find((relation2) => relation2.id !== relationId && relation2.isPrimary && (relation2.manualLocked === true || relation2.source !== "ai"));
                    if (protectedPrimary) throw failure("AI_MANUAL_LOCKED", "AI cannot replace a protected primary relation");
                    for (const relation2 of current) {
                      if (relation2.id === relationId || !relation2.isPrimary) continue;
                      const demoted = schema.normalizeRelation({ ...relation2, isPrimary: false, updatedAt: timestamp }, schemaOptions(timestamp));
                      recordBefore(beforeRelations, relation2);
                      recordAfter(afterRelations, demoted);
                      await tx.put(names.relations, demoted);
                    }
                  }
                  const evidence = reasonEvidence(action.type, modelVersion, action.reasonCodes);
                  const nextSortOrder = existing?.sortOrder ?? current.filter((relation2) => relation2.taskId === taskId).reduce((maximum, relation2) => Math.max(maximum, Number(relation2.sortOrder) || 0), -1) + 1;
                  const relation = schema.normalizeRelation({
                    ...existing || {},
                    taskId,
                    pageId,
                    role: action.role,
                    isPrimary: action.isPrimary === true,
                    source: "ai",
                    manualLocked: false,
                    confidence,
                    evidence,
                    sortOrder: nextSortOrder,
                    createdAt: existing?.createdAt ?? timestamp,
                    updatedAt: timestamp
                  }, schemaOptions(timestamp));
                  if (!same(existing, relation)) {
                    recordBefore(beforeRelations, existing);
                    recordAfter(afterRelations, relation);
                    await tx.put(names.relations, relation);
                    adjustmentItems.push(adjustmentItem(action.type, {
                      title: task.title,
                      before: existing ? existing.isPrimary ? "Primary relation" : "Secondary relation" : "Not linked",
                      after: relation.isPrimary ? "Primary relation" : "Secondary relation",
                      reason: adjustmentReason(action)
                    }));
                  }
                  touchedPages.add(pageId);
                  continue;
                }
                const orderedTaskIds = Array.isArray(action.taskIds) ? action.taskIds.map((taskId) => requireId(taskId, "taskId", "task")) : [];
                const reorderable = initialTasks.filter((task) => task.source === "ai" && task.manualLocked !== true);
                const reorderableIds = new Set(reorderable.map((task) => task.id));
                if (orderedTaskIds.length !== reorderable.length || new Set(orderedTaskIds).size !== orderedTaskIds.length || orderedTaskIds.some((taskId) => !initialTaskIds.has(taskId) || !reorderableIds.has(taskId))) {
                  throw failure("AI_MANUAL_LOCKED", "AI task order must cover only every unlocked AI task");
                }
                const slots = reorderable.map((task) => Number(task.sortOrder) || 0).sort((left, right) => left - right);
                for (const [index, taskId] of orderedTaskIds.entries()) {
                  const before2 = await tx.get(names.tasks, taskId);
                  if (!before2 || before2.source !== "ai" || before2.manualLocked === true) {
                    throw failure("AI_MANUAL_LOCKED", "AI cannot reorder a user-owned or locked task");
                  }
                  const after2 = schema.normalizeTask({ ...before2, sortOrder: slots[index], updatedAt: timestamp }, schemaOptions(timestamp));
                  if (!same(before2, after2)) {
                    recordBefore(beforeTasks, before2);
                    recordAfter(afterTasks, after2);
                    await tx.put(names.tasks, after2);
                  }
                }
                adjustmentItems.push(adjustmentItem(action.type, {
                  title: "Task order",
                  before: `Previous order of ${orderedTaskIds.length} tasks`,
                  after: `Updated order of ${orderedTaskIds.length} tasks`,
                  reason: adjustmentReason(action)
                }));
              }
              for (const pageId of touchedPages) {
                const validation = schema.validatePrimaryRelations(await relationsForPage(tx, pageId));
                if (!validation.valid) throw failure("PRIMARY_CONFLICT", validation.errors.join("; "));
              }
              const before = { tasks: [...beforeTasks.values()], relations: [...beforeRelations.values()] };
              const after = { tasks: [...afterTasks.values()], relations: [...afterRelations.values()] };
              if (!after.tasks.length && !after.relations.length) {
                return { ok: true, unchanged: true, appliedActions: 0 };
              }
              const analysisRunId = String(context.analysisRunId || "").trim();
              if (analysisRunId && !PROVENANCE.test(analysisRunId)) {
                throw failure("AI_PLAN_INVALID", "analysisRunId is invalid");
              }
              const metadata = {
                kind: "ai.workspace.plan",
                modelVersion,
                analysisRunId,
                actionTypes: plan.actions.map((action) => action.type),
                inputVersion: writeContext.validatedInputVersion,
                adjustmentItems
              };
              const change = await persistChange(tx, plan.actions, before, after, timestamp, metadata);
              const adjustment = adjustmentSummary(change.id, "applied", adjustmentItems);
              return {
                ok: true,
                changeId: change.id,
                appliedActions: plan.actions.length,
                adjustment,
                tasks: clone(after.tasks),
                relations: clone(after.relations)
              };
            });
          }
          async function undo(changeIdValue) {
            const changeId = requireId(changeIdValue, "changeId", "change");
            const timestamp = clock();
            return store.runTransaction(transactionStores, "readwrite", async (tx) => {
              const change = await tx.get(names.changes, changeId);
              if (!change || change.after?.aiPlan?.kind !== "ai.workspace.plan") {
                throw failure("AI_PLAN_NOT_FOUND", "AI plan change was not found");
              }
              if (change.undoneAt != null) throw failure("AI_PLAN_UNDO_UNAVAILABLE", "AI plan has already been undone");
              for (const key of BUSINESS_KEYS) {
                const after = new Map((change.after?.[key] || []).map((item) => [item.id, item]));
                for (const [id, expected] of after) {
                  const current = await tx.get(names[key], id);
                  if (!same(current, expected)) throw failure("AI_PLAN_UNDO_CONFLICT", "AI plan records have changed");
                }
              }
              for (const key of BUSINESS_KEYS) {
                const before = new Map((change.before?.[key] || []).map((item) => [item.id, item]));
                const after = new Map((change.after?.[key] || []).map((item) => [item.id, item]));
                for (const id of after.keys()) if (!before.has(id)) await tx.delete(names[key], id);
                for (const item of before.values()) await tx.put(names[key], item);
              }
              await tx.put(names.changes, schema.normalizeChangeRecord({
                ...change,
                undoneAt: Math.max(timestamp, change.createdAt)
              }, schemaOptions(timestamp)));
              return {
                ok: true,
                changeId,
                undone: true,
                adjustment: adjustmentSummary(changeId, "undone", storedAdjustmentItems(change.after?.aiPlan))
              };
            });
          }
          return Object.freeze({ applyPlan, undo });
        }
        return Object.freeze({ ALLOWED_ACTIONS, THRESHOLDS, createV2AiPlanApplicationService });
      });
    }
  });

  // src/background/v2-ai-organization-coordinator.js
  var require_v2_ai_organization_coordinator = __commonJS({
    "src/background/v2-ai-organization-coordinator.js"(exports, module) {
      (function attachV2AiOrganizationCoordinator(rootScope, factory) {
        const api = factory();
        if (typeof module === "object" && module.exports) module.exports = api;
        if (rootScope) rootScope.TfaV2AiOrganizationCoordinator = api;
      })(typeof globalThis === "object" ? globalThis : exports, function createV2AiOrganizationCoordinatorModule() {
        "use strict";
        const SAFE_INPUT_VERSION = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,95}$/;
        const SAFE_PAGE_ID = /^page-[A-Za-z0-9._:-]{1,154}$/;
        const SAFE_TASK_ID = /^task-[A-Za-z0-9._:-]{1,154}$/;
        const SAFE_MODEL_VERSION = /^[a-z0-9][a-z0-9._:-]{0,159}$/;
        const SAFE_CHANGE_ID = /^change-[A-Za-z0-9._:-]{1,153}$/;
        const ADJUSTMENT_TYPES = /* @__PURE__ */ new Set(["create_ai_task", "rename_ai_task", "assign_ai_relation", "reorder_ai_tasks"]);
        const SAFE_ADJUSTMENT_TEXT = /^(?!.*(?:\b(?:https?|file|chrome|edge):\/\/|Bearer\s+|\bsk-[A-Za-z0-9_-]{8}|api[-_ ]?key|authorization|cookie|raw[_ ]?events?|behavior[_ ]?events?))[\s\S]*$/i;
        const REASON_CODES = /* @__PURE__ */ new Set([
          "semantic_match",
          "task_name_match",
          "domain_match",
          "core_page_similarity",
          "behavior_support",
          "stable_object_match",
          "related_pages",
          "clearer_task_name",
          "virtual_order",
          "ambiguous",
          "no_suitable_task",
          "insufficient_evidence"
        ]);
        const STRONG_SIGNAL_FAMILIES = Object.freeze({
          opener_link: "opener",
          stable_object: "stable_object",
          qualified_return: "qualified_return",
          task_snapshot_restore: "task_restore"
        });
        const SAFE_FALLBACK_CODES = /* @__PURE__ */ new Set([
          "AI_DISABLED",
          "AI_API_KEY_REQUIRED",
          "INVALID_SETTINGS",
          "AI_STORAGE_ERROR",
          "ABORTED",
          "TIMEOUT",
          "NETWORK",
          "AUTH",
          "PAYMENT_REQUIRED",
          "FORBIDDEN",
          "NOT_FOUND",
          "RATE_LIMIT",
          "BAD_REQUEST",
          "SERVER",
          "HTTP",
          "NON_JSON",
          "EMPTY_RESPONSE",
          "OUTPUT_TOO_LARGE",
          "INVALID_OUTPUT",
          "AI_PROVIDER_UNAVAILABLE",
          "AI_REQUEST_FAILED",
          "INVALID_LOCAL_CANDIDATES",
          "TOO_FEW_CANDIDATES",
          "INPUT_UNAVAILABLE",
          "PLAN_APPLICATION_FAILED",
          "INPUT_VERSION_MISMATCH"
        ]);
        const DEFAULT_STATE = Object.freeze({
          version: 1,
          lastCompletedInputVersion: "",
          lastAttemptAt: 0,
          failureCount: 0,
          retryAt: 0
        });
        function integer(value, minimum = 0, maximum = Number.MAX_SAFE_INTEGER) {
          const number = Math.trunc(Number(value));
          return Number.isFinite(number) ? Math.max(minimum, Math.min(maximum, number)) : minimum;
        }
        function normalizeState(value) {
          const source = value && typeof value === "object" && !Array.isArray(value) ? value : {};
          const completed = String(source.lastCompletedInputVersion || "");
          return {
            version: 1,
            lastCompletedInputVersion: SAFE_INPUT_VERSION.test(completed) ? completed : "",
            lastAttemptAt: integer(source.lastAttemptAt),
            failureCount: integer(source.failureCount, 0, 20),
            retryAt: integer(source.retryAt)
          };
        }
        function safeFallbackCode(value, fallback = "AI_REQUEST_FAILED") {
          return SAFE_FALLBACK_CODES.has(value) ? value : fallback;
        }
        function count(value, maximum = 1e4) {
          return integer(Array.isArray(value) ? value.length : value, 0, maximum);
        }
        function normalizeRankDecisions(value) {
          const decisions = [];
          const seenPages = /* @__PURE__ */ new Set();
          const fields = /* @__PURE__ */ new Set(["pageId", "taskId", "decision", "confidence", "reasonCodes", "model", "modelVersion"]);
          for (const item of Array.isArray(value) ? value : []) {
            if (decisions.length >= 20 || !item || typeof item !== "object" || Array.isArray(item)) continue;
            if (Object.keys(item).some((field) => !fields.has(field)) || Object.prototype.hasOwnProperty.call(item, "model") && Object.prototype.hasOwnProperty.call(item, "modelVersion")) continue;
            const pageId = String(item.pageId || "").trim();
            const decision = String(item.decision || "");
            const taskId = item.taskId == null ? null : String(item.taskId).trim();
            const confidence = Number(item.confidence);
            const modelVersion = String(item.modelVersion || item.model || "").trim();
            const reasonCodes = Array.isArray(item.reasonCodes) ? item.reasonCodes.map(String) : [];
            if (!SAFE_PAGE_ID.test(pageId) || seenPages.has(pageId) || !["assign", "no_match"].includes(decision) || (decision === "assign" ? !SAFE_TASK_ID.test(taskId || "") : taskId !== null) || !Number.isFinite(confidence) || confidence < 0 || confidence > 1 || !SAFE_MODEL_VERSION.test(modelVersion) || reasonCodes.length < 1 || reasonCodes.length > 3 || new Set(reasonCodes).size !== reasonCodes.length || reasonCodes.some((code) => !REASON_CODES.has(code))) continue;
            seenPages.add(pageId);
            decisions.push(Object.freeze({
              pageId,
              taskId,
              decision,
              confidence,
              reasonCodes: Object.freeze([...reasonCodes]),
              modelVersion
            }));
          }
          return Object.freeze(decisions);
        }
        function eligibleAssignments(rankDecisions, localCandidates) {
          const candidates = Array.isArray(localCandidates) ? localCandidates : [];
          const byPage = /* @__PURE__ */ new Map();
          for (const candidate of candidates) {
            const pageId = String(candidate?.pageId || "").trim();
            const taskId = String(candidate?.taskId || "").trim();
            const confidence = Number(candidate?.confidence);
            if (!SAFE_PAGE_ID.test(pageId) || !SAFE_TASK_ID.test(taskId) || !Number.isFinite(confidence) || confidence < 0 || confidence > 1) continue;
            const group = byPage.get(pageId) || [];
            group.push({ candidate, taskId, confidence });
            byPage.set(pageId, group);
          }
          const eligible = [];
          for (const decision of rankDecisions) {
            if (decision.decision !== "assign") continue;
            const group = byPage.get(decision.pageId) || [];
            const selected = group.find((entry) => entry.taskId === decision.taskId);
            const competitors = group.filter((entry) => entry.taskId !== decision.taskId);
            if (!selected || !competitors.length) continue;
            const margin = selected.confidence - Math.max(...competitors.map((entry) => entry.confidence));
            const evidence = Array.isArray(selected.candidate.evidenceCodes) ? selected.candidate.evidenceCodes.map(String) : [];
            const independentFamilies = new Set(evidence.map((code) => STRONG_SIGNAL_FAMILIES[code]).filter(Boolean));
            const risks = Array.isArray(selected.candidate.riskFlags) ? selected.candidate.riskFlags.map(String) : null;
            if (margin < 0.18 || margin > 1 || independentFamilies.size < 2 || !risks || risks.length) continue;
            eligible.push(Object.freeze({
              pageId: decision.pageId,
              taskId: decision.taskId,
              margin: Math.round(margin * 1e6) / 1e6,
              independentSignalFamilies: independentFamilies.size,
              riskFlags: Object.freeze([])
            }));
          }
          return Object.freeze(eligible);
        }
        function eligibleCreatePageSets(clusters, inputVersion) {
          if (!SAFE_INPUT_VERSION.test(String(inputVersion || ""))) return Object.freeze([]);
          const prepared = [];
          for (const cluster of Array.isArray(clusters) ? clusters : []) {
            const pageIds = Array.isArray(cluster?.pageIds) ? cluster.pageIds.map(String) : [];
            const confidence = Number(cluster?.confidence);
            const families = Number(cluster?.independentSignalFamilies);
            const risks = Array.isArray(cluster?.riskFlags) ? cluster.riskFlags.map(String) : null;
            if (cluster?.disposition !== "auto_candidate" || cluster?.inputVersion !== inputVersion || pageIds.length < 2 || pageIds.length > 8 || new Set(pageIds).size !== pageIds.length || pageIds.some((pageId) => !SAFE_PAGE_ID.test(pageId)) || !Number.isFinite(confidence) || confidence < 0.86 || confidence > 1 || !Number.isSafeInteger(families) || families < 2 || !risks || risks.length) continue;
            prepared.push({ pageIds: [...pageIds].sort(), independentSignalFamilies: families, riskFlags: Object.freeze([]) });
          }
          const membership = /* @__PURE__ */ new Map();
          prepared.forEach((cluster) => cluster.pageIds.forEach((pageId) => membership.set(pageId, (membership.get(pageId) || 0) + 1)));
          return Object.freeze(prepared.filter((cluster) => cluster.pageIds.every((pageId) => membership.get(pageId) === 1)).sort((left, right) => left.pageIds.join("|").localeCompare(right.pageIds.join("|"))).map((cluster) => Object.freeze(cluster)));
        }
        function summary(status, patch = {}) {
          const source = patch.source === "ai" ? "ai" : "local";
          const changeId = SAFE_CHANGE_ID.test(String(patch.changeId || "")) ? String(patch.changeId) : "";
          const adjustment = normalizeAdjustment(patch.adjustment, changeId);
          const result = {
            ok: patch.ok !== false,
            status,
            source,
            rankRequested: patch.rankRequested === true,
            rankApplied: patch.rankApplied === true,
            rankCandidateCount: count(patch.rankCandidateCount),
            rankDecisionCount: count(patch.rankDecisionCount),
            rankSkippedPageCount: count(patch.rankSkippedPageCount),
            planRequested: patch.planRequested === true,
            planApplied: patch.planApplied === true,
            planActionCount: count(patch.planActionCount, 50),
            planSkippedPageCount: count(patch.planSkippedPageCount),
            rankDecisions: normalizeRankDecisions(patch.rankDecisions),
            ...patch.fallbackCode ? { fallbackCode: safeFallbackCode(patch.fallbackCode) } : {},
            ...changeId ? { changeId } : {},
            ...adjustment ? { adjustment } : {}
          };
          return Object.freeze(result);
        }
        function normalizeAdjustment(value, changeId) {
          if (!changeId || !value || typeof value !== "object" || Array.isArray(value) || Object.keys(value).some((field) => !["changeId", "status", "items", "errorCode"].includes(field)) || value.changeId !== changeId || value.status !== "applied" || value.errorCode !== "" || !Array.isArray(value.items) || value.items.length < 1 || value.items.length > 50) return null;
          const text = (input, maximum, optional = false) => {
            if (typeof input !== "string") return null;
            const result = input.trim();
            return !result && !optional || result.length > maximum || /[\u0000-\u001f\u007f]/.test(result) || !SAFE_ADJUSTMENT_TEXT.test(result) ? null : result;
          };
          const items = [];
          for (const item of value.items) {
            if (!item || typeof item !== "object" || Array.isArray(item) || Object.keys(item).some((field) => !["type", "title", "before", "after", "reason"].includes(field)) || !ADJUSTMENT_TYPES.has(item.type)) return null;
            const title = text(item.title, 160), before = text(item.before, 200, true);
            const after = text(item.after, 200, true), reason = text(item.reason, 240, true);
            if (title == null || before == null || after == null || reason == null || !before && !after) return null;
            items.push(Object.freeze({ type: item.type, title, before, after, reason }));
          }
          return Object.freeze({ changeId, status: "applied", items: Object.freeze(items), errorCode: "" });
        }
        function createV2AiOrganizationCoordinator({
          aiService,
          inputBuilder,
          reranker,
          planApplication = null,
          stateStore = null,
          now = Date.now,
          debounceMs = 1500,
          baseBackoffMs = 3e4,
          maxBackoffMs = 15 * 60 * 1e3,
          getCurrentInputVersion = null
        } = {}) {
          if (!aiService || typeof aiService.getSettings !== "function" || typeof aiService.planWorkspace !== "function") {
            throw new TypeError("AI organization coordinator requires an AI service");
          }
          if (!inputBuilder || typeof inputBuilder.buildRankCandidatesInput !== "function" || typeof inputBuilder.buildPlanWorkspaceInput !== "function") {
            throw new TypeError("AI organization coordinator requires an AI input builder");
          }
          if (!reranker || typeof reranker.rerank !== "function") {
            throw new TypeError("AI organization coordinator requires an AI reranker");
          }
          if (planApplication && typeof planApplication.applyPlan !== "function") {
            throw new TypeError("AI plan application dependency is invalid");
          }
          if (stateStore && (typeof stateStore.load !== "function" || typeof stateStore.save !== "function")) {
            throw new TypeError("AI coordinator state store requires load and save");
          }
          if (getCurrentInputVersion && typeof getCurrentInputVersion !== "function") {
            throw new TypeError("AI coordinator current input version reader is invalid");
          }
          if (planApplication && typeof getCurrentInputVersion !== "function") {
            throw new TypeError("AI plan application requires a current input version reader");
          }
          const debounce = integer(debounceMs, 0, 6e4);
          const backoffBase = integer(baseBackoffMs, 1, 60 * 60 * 1e3);
          const backoffMax = Math.max(backoffBase, integer(maxBackoffMs, backoffBase, 24 * 60 * 60 * 1e3));
          let memoryState = { ...DEFAULT_STATE };
          let inFlight = null;
          let inFlightVersion = "";
          function clock() {
            const value = Number(typeof now === "function" ? now() : now);
            return Number.isFinite(value) && value >= 0 ? Math.trunc(value) : Date.now();
          }
          async function loadState() {
            return normalizeState(stateStore ? await stateStore.load() : memoryState);
          }
          async function saveState(value) {
            const safe = normalizeState(value);
            if (stateStore) await stateStore.save(Object.freeze({ ...safe }));
            else memoryState = safe;
            return safe;
          }
          function backoffState(state, timestamp) {
            const failures = Math.min(20, state.failureCount + 1);
            const delay = Math.min(backoffMax, backoffBase * 2 ** Math.min(20, failures - 1));
            return { ...state, failureCount: failures, retryAt: timestamp + delay };
          }
          async function safeSettings() {
            try {
              const response = await aiService.getSettings();
              if (!response?.ok || !response.settings) {
                return { failureCode: safeFallbackCode(response?.errorCode, "AI_STORAGE_ERROR") };
              }
              if (response.settings.enabled !== true) return { failureCode: "AI_DISABLED" };
              if (response.settings.hasApiKey !== true) return { failureCode: "AI_API_KEY_REQUIRED" };
              return { settings: response.settings };
            } catch (_error) {
              return { failureCode: "AI_STORAGE_ERROR" };
            }
          }
          async function persistFailure(state, timestamp) {
            try {
              await saveState(backoffState(state, timestamp));
            } catch (_error) {
            }
          }
          async function execute(inputVersion, request) {
            const timestamp = clock();
            const settingsResult = await safeSettings();
            if (settingsResult.failureCode) {
              return summary("fallback", { fallbackCode: settingsResult.failureCode });
            }
            let state;
            try {
              state = await loadState();
            } catch (_error) {
              return summary("fallback", { fallbackCode: "AI_STORAGE_ERROR" });
            }
            if (state.lastCompletedInputVersion === inputVersion) return summary("duplicate");
            if (state.retryAt > timestamp) return summary("backoff");
            if (state.lastAttemptAt && timestamp - state.lastAttemptAt < debounce) return summary("debounced");
            try {
              state = await saveState({ ...state, lastAttemptAt: timestamp });
            } catch (_error) {
              return summary("fallback", { fallbackCode: "AI_STORAGE_ERROR" });
            }
            let rankBuild;
            try {
              rankBuild = inputBuilder.buildRankCandidatesInput({
                locale: request.locale,
                pages: request.pages,
                tasks: request.tasks,
                candidates: request.candidates
              });
            } catch (_error) {
              return summary("fallback", { fallbackCode: "INPUT_UNAVAILABLE" });
            }
            const rankSkippedPageCount = count(rankBuild?.skippedPages);
            const safeCreateSets = eligibleCreatePageSets(request.newTaskClusters, inputVersion);
            if (!rankBuild?.input && !safeCreateSets.length) {
              try {
                await saveState({ ...state, lastCompletedInputVersion: inputVersion, failureCount: 0, retryAt: 0 });
              } catch (_error) {
              }
              return summary("no_eligible_pages", { rankSkippedPageCount });
            }
            const rankSummary = {
              rankRequested: Boolean(rankBuild?.input),
              rankApplied: false,
              rankCandidateCount: 0,
              rankDecisionCount: 0,
              rankSkippedPageCount
            };
            let rankDecisions = Object.freeze([]);
            if (rankBuild?.input) {
              let rankResult;
              try {
                rankResult = await reranker.rerank({
                  localCandidates: Array.isArray(request.localCandidates) ? request.localCandidates : [],
                  rankInput: rankBuild.input,
                  signal: request.signal
                });
              } catch (_error) {
                rankResult = { ok: true, source: "local", fallbackCode: "AI_REQUEST_FAILED", candidates: [], decisions: [] };
              }
              rankSummary.rankCandidateCount = count(rankResult?.candidates);
              if (rankResult?.source !== "ai") {
                const fallbackCode = safeFallbackCode(rankResult?.fallbackCode);
                if (fallbackCode !== "ABORTED") await persistFailure(state, timestamp);
                return summary("rank_fallback", { ...rankSummary, fallbackCode });
              }
              rankDecisions = normalizeRankDecisions(rankResult.decisions);
              rankSummary.rankDecisions = rankDecisions;
              rankSummary.rankDecisionCount = rankDecisions.length;
              rankSummary.rankApplied = rankResult.applied === true && rankDecisions.length > 0;
            }
            if (settingsResult.settings.autoOrganize !== true || !planApplication) {
              try {
                await saveState({ ...state, lastCompletedInputVersion: inputVersion, failureCount: 0, retryAt: 0 });
              } catch (_error) {
              }
              return summary("completed", { ...rankSummary, source: "ai" });
            }
            let planBuild;
            try {
              planBuild = inputBuilder.buildPlanWorkspaceInput({
                locale: request.locale,
                pages: request.pages,
                tasks: request.tasks
              });
            } catch (_error) {
              return summary("plan_fallback", {
                ...rankSummary,
                source: "ai",
                fallbackCode: "INPUT_UNAVAILABLE"
              });
            }
            const planSkippedPageCount = count(planBuild?.skippedPages);
            if (!planBuild?.input) {
              try {
                await saveState({ ...state, lastCompletedInputVersion: inputVersion, failureCount: 0, retryAt: 0 });
              } catch (_error) {
              }
              return summary("completed", { ...rankSummary, source: "ai", planSkippedPageCount });
            }
            let planResponse;
            try {
              planResponse = await aiService.planWorkspace(planBuild.input, request.signal);
            } catch (_error) {
              planResponse = { ok: false, errorCode: "AI_REQUEST_FAILED" };
            }
            if (!planResponse?.ok || !planResponse.result) {
              const fallbackCode = safeFallbackCode(planResponse?.errorCode);
              if (fallbackCode !== "ABORTED") await persistFailure(state, timestamp);
              return summary("plan_fallback", {
                ...rankSummary,
                source: "ai",
                planRequested: true,
                planSkippedPageCount,
                fallbackCode
              });
            }
            let application;
            let currentInputVersion = "";
            try {
              const currentValue = await getCurrentInputVersion(request);
              currentInputVersion = String(currentValue || "").trim();
            } catch (_error) {
              currentInputVersion = "";
            }
            if (!SAFE_INPUT_VERSION.test(currentInputVersion) || currentInputVersion !== inputVersion) {
              return summary("plan_fallback", {
                ...rankSummary,
                source: "ai",
                planRequested: true,
                planSkippedPageCount,
                fallbackCode: "INPUT_VERSION_MISMATCH"
              });
            }
            const safeEligibleAssignments = eligibleAssignments(rankDecisions, request.localCandidates);
            try {
              application = await planApplication.applyPlan(planResponse.result, {
                modelVersion: String(planResponse.model || "").slice(0, 160),
                analysisRunId: inputVersion,
                inputVersion,
                validatedInputVersion: inputVersion,
                currentInputVersion,
                eligibleAssignments: safeEligibleAssignments,
                eligibleCreatePageSets: safeCreateSets
              });
            } catch (_error) {
              await persistFailure(state, timestamp);
              return summary("plan_fallback", {
                ...rankSummary,
                source: "ai",
                planRequested: true,
                planSkippedPageCount,
                fallbackCode: "PLAN_APPLICATION_FAILED"
              });
            }
            if (!application?.ok) {
              await persistFailure(state, timestamp);
              return summary("plan_fallback", {
                ...rankSummary,
                source: "ai",
                planRequested: true,
                planSkippedPageCount,
                fallbackCode: "PLAN_APPLICATION_FAILED"
              });
            }
            try {
              await saveState({ ...state, lastCompletedInputVersion: inputVersion, failureCount: 0, retryAt: 0 });
            } catch (_error) {
            }
            return summary("completed", {
              ...rankSummary,
              source: "ai",
              planRequested: true,
              planApplied: integer(application.appliedActions, 0, 50) > 0,
              planActionCount: application.appliedActions,
              planSkippedPageCount,
              changeId: application.changeId,
              adjustment: application.adjustment
            });
          }
          function organize(request = {}) {
            const inputVersion = String(request?.inputVersion || "").trim();
            if (!SAFE_INPUT_VERSION.test(inputVersion)) {
              return Promise.resolve(summary("invalid_input", { ok: false, fallbackCode: "INPUT_UNAVAILABLE" }));
            }
            if (inFlight) {
              return inFlightVersion === inputVersion ? inFlight : Promise.resolve(summary("busy"));
            }
            inFlightVersion = inputVersion;
            inFlight = execute(inputVersion, request).finally(() => {
              inFlight = null;
              inFlightVersion = "";
            });
            return inFlight;
          }
          return Object.freeze({ organize });
        }
        return Object.freeze({
          createV2AiOrganizationCoordinator,
          normalizeState,
          normalizeRankDecisions,
          eligibleAssignments,
          eligibleCreatePageSets
        });
      });
    }
  });

  // src/background/runtime.js
  var require_runtime = __commonJS({
    "src/background/runtime.js"(exports) {
      (function startBackground(root) {
        "use strict";
        const chromeApi = chrome;
        const BuildIdentity = root.TfaBuildIdentity;
        const DockLogic = root.TfaDockLogic;
        const RuntimeContract = root.TfaRuntimeContract;
        const LicenseModule = root.TfaBackgroundLicenseService;
        const ScreenshotModule = root.TfaBackgroundScreenshotService;
        const TrashModule = root.TfaBackgroundTrashService;
        const TabModule = root.TfaBackgroundTabService;
        const DockFaviconModule = root.TfaBackgroundDockFaviconService;
        const DockSearchModule = root.TfaBackgroundDockSearchService;
        const DockModule = root.TfaBackgroundDockService;
        const ActiveTriggerModule = root.TfaBackgroundActiveTriggerService;
        const NewTabSiteSpaceModule = root.TfaBackgroundNewTabSiteSpaceService;
        const V2DataSchema = root.TfaV2DataSchema;
        const V2BehaviorLogic = root.TfaV2BehaviorLogic;
        const V2PageObservationLogic = root.TfaV2PageObservationLogic;
        const V2SignalAggregationLogic = root.TfaV2SignalAggregationLogic;
        const V2NewTaskClusterLogic = root.TfaV2NewTaskClusterLogic;
        const V2SuggestionRules = root.TfaV2SuggestionRules;
        const V2DataStoreModule = root.TfaV2DataStore;
        const V2TaskServiceModule = root.TfaV2TaskService;
        const V2BackupServiceModule = root.TfaV2BackupService;
        const V2BehaviorStoreModule = root.TfaV2BehaviorStore;
        const V2InboxServiceModule = root.TfaV2InboxService;
        const V2InboxPreferenceStoreModule = root.TfaV2InboxPreferenceStore;
        const V2BehaviorObserverModule = root.TfaV2BehaviorObserver;
        const V2SuggestionObservationGateModule = root.TfaV2SuggestionObservationGate;
        const V2SuggestionServiceModule = root.TfaV2SuggestionService;
        const V2AiSettings = root.TfaV2AiSettings;
        const V2AiSettingsStoreModule = root.TfaV2AiSettingsStore;
        const V2OpenAiCompatibleProviderModule = root.TfaV2OpenAiCompatibleProvider;
        const V2AiServiceModule = root.TfaV2AiService;
        const V2AiContract = root.TfaV2AiContract;
        const V2AiCoordinatorStateStoreModule = root.TfaV2AiCoordinatorStateStore;
        const V2AiInputBuilderModule = root.TfaV2AiInputBuilder;
        const V2AiSuggestionRerankerModule = root.TfaV2AiSuggestionReranker;
        const V2AiPlanApplicationServiceModule = root.TfaV2AiPlanApplicationService;
        const V2AiOrganizationCoordinatorModule = root.TfaV2AiOrganizationCoordinator;
        if (!DockLogic) throw new Error("TfaDockLogic missing");
        if (!BuildIdentity || typeof BuildIdentity.version !== "string" || typeof BuildIdentity.fingerprint !== "string") {
          throw new Error("TfaBuildIdentity missing");
        }
        if (!RuntimeContract || !Array.isArray(RuntimeContract.FRAME_BRIDGE_FILES) || !Array.isArray(RuntimeContract.TOP_FRAME_BOOTSTRAP_FILES) || !RuntimeContract.TOP_FRAME_FEATURE_FILES) {
          throw new Error("TfaRuntimeContract frame injection files missing");
        }
        for (const [name, moduleApi] of Object.entries({
          LicenseModule,
          ScreenshotModule,
          TrashModule,
          TabModule,
          DockFaviconModule,
          DockSearchModule,
          DockModule,
          ActiveTriggerModule
        })) {
          if (!moduleApi) throw new Error(`${name} missing`);
        }
        const {
          FRAME_BRIDGE_FILES,
          TOP_FRAME_BOOTSTRAP_FILES,
          TOP_FRAME_FEATURE_FILES
        } = RuntimeContract;
        const CONTENT_FEATURES = Object.freeze(Object.keys(TOP_FRAME_FEATURE_FILES));
        const DESIRED_CONTENT_IDENTITY = Object.freeze({
          version: BuildIdentity.version,
          fingerprint: BuildIdentity.fingerprint
        });
        const DOCK_INPUT_ERROR = Symbol("dockInputError");
        const tabContentOperationQueue = /* @__PURE__ */ new Map();
        const tabFeatureInjectionQueue = /* @__PURE__ */ new Map();
        let dockMutationQueue = Promise.resolve();
        const dockBroadcastSessionToken = [Date.now().toString(36), Math.random().toString(36).slice(2)].join("-");
        let dockBroadcastRevision = 0;
        let recentlyClosedBroadcastRevision = 0;
        let searchPermissionBroadcastRevision = 0;
        let recentlyClosedBroadcastWork = null;
        let recentlyClosedBroadcastPending = false;
        let recentlyClosedBroadcastRetryCount = 0;
        let recentlyClosedBroadcastRetryTimer = null;
        const RECENTLY_CLOSED_BROADCAST_RETRY_DELAYS = Object.freeze([40, 160, 640]);
        let searchPermissionBroadcastWork = null;
        let searchPermissionBroadcastPending = false;
        let searchPermissionAuthoritySignature = "";
        let searchPermissionAuthoritySnapshot = null;
        let searchPermissionNewTabDeliveredRevision = 0;
        let searchPermissionAllDeliveredRevision = 0;
        let searchPermissionBroadcastRetryCount = 0;
        let searchPermissionBroadcastRetryTimer = null;
        const SEARCH_PERMISSION_BROADCAST_RETRY_DELAYS = Object.freeze([40, 160, 640]);
        let v2TaskBroadcastWork = null;
        let v2TaskBroadcastPending = false;
        let v2TaskBroadcastEpoch = Date.now();
        let v2TaskMutationRevision = 0;
        let v2TaskBroadcastRetryCount = 0;
        let v2TaskBroadcastRetryTimer = null;
        const V2_TASK_BROADCAST_RETRY_DELAYS = Object.freeze([40, 160, 640]);
        const pendingV2TaskMutationTypes = /* @__PURE__ */ new Set();
        let lastActiveTabId = null;
        function isBrowserInternalUrl(url = "") {
          if (/^(chrome-extension|edge|devtools|view-source):/i.test(url)) return true;
          if (/^chrome(?!-error)/i.test(url)) {
            if (/^chrome:\/\/newtab/i.test(url)) return false;
            return true;
          }
          return false;
        }
        function isInjectableUrl(url = "") {
          if (!url || /^(chrome-extension|edge|devtools|view-source):/i.test(url)) return false;
          if (/^chrome(?!-error)/i.test(url)) return /^chrome:\/\/(newtab|error)/i.test(url);
          return true;
        }
        function isPageUrl(url = "") {
          return /^(https?:|file:|data:|blob:)/i.test(url);
        }
        function isNewTabWorkspaceUrl(url = "") {
          const canonical = String(chromeApi.runtime.getURL("newtab.html") || "").split(/[?#]/, 1)[0];
          return Boolean(canonical) && String(url || "").split(/[?#]/, 1)[0] === canonical;
        }
        function isActiveGuidanceTestSender(sender = {}) {
          const sourceUrl = String(sender?.url || sender?.tab?.url || "");
          if (!sourceUrl) return false;
          let source;
          let extension;
          try {
            source = new URL(sourceUrl);
            extension = new URL(chromeApi.runtime.getURL(""));
          } catch (_error) {
            return false;
          }
          if (source.origin !== extension.origin) return false;
          if (chromeApi.runtime.id && sender?.id !== chromeApi.runtime.id) return false;
          return source.pathname === "/popup.html" || source.pathname === "/newtab.html";
        }
        async function toggleNewTabWorkspace(tabId) {
          try {
            const response = await chromeApi.runtime.sendMessage({ type: "TFA_NEWTAB_TOGGLE_OVERLAY", tabId });
            return { ok: Boolean(response?.ok), ready: true, newtab: true };
          } catch (_error) {
            return { ok: false, ready: true, newtab: true };
          }
        }
        async function readDockConfig() {
          const stored = await chromeApi.storage.sync.get(DockLogic.DOCK_STORAGE_KEY);
          const raw = stored && stored[DockLogic.DOCK_STORAGE_KEY];
          const config = DockLogic.normalizeDockConfig(raw);
          if (raw && raw.enabled === false && raw.enabledUserSet !== true) {
            const migrated = DockLogic.normalizeDockConfig({ ...raw, enabled: true, enabledUserSet: false });
            await chromeApi.storage.sync.set({ [DockLogic.DOCK_STORAGE_KEY]: migrated });
            return migrated;
          }
          return config;
        }
        function dockConfigMessage(value) {
          const config = DockLogic.normalizeDockConfig(value);
          const revision = ++dockBroadcastRevision;
          const token = `${dockBroadcastSessionToken}:${revision}`;
          return { type: "WEB_DOCK_CONFIG_CHANGED", config, revision, token };
        }
        async function syncDockConfigToTab(tabId) {
          if (!Number.isInteger(tabId)) return false;
          try {
            const message = dockConfigMessage(await readDockConfig());
            const response = await chromeApi.tabs.sendMessage(tabId, message);
            return Boolean(response && response.ok);
          } catch (_) {
            return false;
          }
        }
        async function broadcastDockConfigChanged(value) {
          const message = dockConfigMessage(value);
          let tabs;
          try {
            tabs = await chromeApi.tabs.query({});
          } catch (_) {
            return;
          }
          await Promise.all(tabs.filter((tab) => Number.isInteger(tab.id) && isInjectableUrl(tab.url || "")).map((tab) => Promise.resolve().then(() => chromeApi.tabs.sendMessage(tab.id, message)).catch(() => void 0)));
        }
        function scheduleRecentlyClosedBroadcastRetry() {
          if (recentlyClosedBroadcastRetryTimer != null || recentlyClosedBroadcastRetryCount >= RECENTLY_CLOSED_BROADCAST_RETRY_DELAYS.length) {
            return false;
          }
          const delay = RECENTLY_CLOSED_BROADCAST_RETRY_DELAYS[recentlyClosedBroadcastRetryCount++];
          recentlyClosedBroadcastRetryTimer = setTimeout(() => {
            recentlyClosedBroadcastRetryTimer = null;
            return broadcastRecentlyClosedChanged({ retry: true });
          }, delay);
          return true;
        }
        function broadcastRecentlyClosedChanged({ retry = false } = {}) {
          if (!retry) {
            recentlyClosedBroadcastRetryCount = 0;
            if (recentlyClosedBroadcastRetryTimer != null) {
              clearTimeout(recentlyClosedBroadcastRetryTimer);
              recentlyClosedBroadcastRetryTimer = null;
            }
          }
          recentlyClosedBroadcastPending = true;
          if (recentlyClosedBroadcastWork) return recentlyClosedBroadcastWork;
          recentlyClosedBroadcastWork = (async () => {
            while (recentlyClosedBroadcastPending) {
              recentlyClosedBroadcastPending = false;
              const revision = ++recentlyClosedBroadcastRevision;
              const message = {
                type: "WEB_DOCK_RECENTLY_CLOSED_CHANGED",
                revision,
                token: `${dockBroadcastSessionToken}:recent:${revision}`
              };
              let tabs;
              try {
                tabs = await chromeApi.tabs.query({});
              } catch (_) {
                scheduleRecentlyClosedBroadcastRetry();
                continue;
              }
              const deliveries = tabs.filter((tab) => Number.isInteger(tab.id) && isInjectableUrl(tab.url || "")).map((tab) => Promise.resolve().then(() => chromeApi.tabs.sendMessage(tab.id, message)));
              const settled = await Promise.allSettled(deliveries);
              if (settled.every((result) => result.status === "fulfilled" && result.value?.ok !== false)) {
                recentlyClosedBroadcastRetryCount = 0;
                if (recentlyClosedBroadcastRetryTimer != null) {
                  clearTimeout(recentlyClosedBroadcastRetryTimer);
                  recentlyClosedBroadcastRetryTimer = null;
                }
              } else {
                scheduleRecentlyClosedBroadcastRetry();
              }
            }
          })().finally(() => {
            recentlyClosedBroadcastWork = null;
          });
          return recentlyClosedBroadcastWork;
        }
        function queueDockMutation(mutator) {
          const operation = dockMutationQueue.catch(() => void 0).then(async () => {
            const current = await readDockConfig();
            const result = await mutator(current);
            const config = DockLogic.normalizeDockConfig(result.config || result);
            await chromeApi.storage.sync.set({ [DockLogic.DOCK_STORAGE_KEY]: config });
            await broadcastDockConfigChanged(config);
            return { ...result, config };
          });
          dockMutationQueue = operation.then(() => void 0, () => void 0);
          return operation;
        }
        function runDockInput(operation) {
          try {
            return operation();
          } catch (error) {
            throw { [DOCK_INPUT_ERROR]: true, cause: error };
          }
        }
        async function injectBootstrapScripts(tabId) {
          await chromeApi.scripting.executeScript({
            target: { tabId },
            world: "MAIN",
            files: ["page_world.js"]
          });
          const [probe] = await chromeApi.scripting.executeScript({
            target: { tabId },
            world: "MAIN",
            func: function probePageWorldIdentity(expectedFingerprint) {
              const record = window.__TFA_PAGE_WORLD_LOCK__;
              return {
                matches: Boolean(record && typeof record === "object" && record.fingerprint === expectedFingerprint && record.state === "active"),
                fingerprint: record && typeof record === "object" ? String(record.fingerprint || "") : "",
                legacy: Boolean(record && typeof record !== "object")
              };
            },
            args: [DESIRED_CONTENT_IDENTITY.fingerprint]
          });
          if (!probe || !probe.result || probe.result.matches !== true) return false;
          await chromeApi.scripting.executeScript({ target: { tabId, allFrames: true }, files: FRAME_BRIDGE_FILES });
          await chromeApi.scripting.executeScript({
            target: { tabId, frameIds: [0] },
            files: TOP_FRAME_BOOTSTRAP_FILES
          });
          return true;
        }
        function isContentFeature(value) {
          return CONTENT_FEATURES.includes(value) && Array.isArray(TOP_FRAME_FEATURE_FILES[value]) && TOP_FRAME_FEATURE_FILES[value].length > 0;
        }
        async function probeContentFeature(tabId, feature) {
          try {
            const [probe] = await chromeApi.scripting.executeScript({
              target: { tabId, frameIds: [0] },
              func: function probeRegisteredContentFeature(name) {
                const host = globalThis.__TFA_FEATURE_HOST__;
                return Boolean(host && typeof host.hasFeature === "function" && host.hasFeature(name));
              },
              args: [feature]
            });
            return Boolean(probe && probe.result === true);
          } catch (_) {
            return false;
          }
        }
        async function injectContentFeatureNow(tabId, feature) {
          if (!isContentFeature(feature)) return false;
          if (!await ensureContentScriptReadyNow(tabId)) return false;
          if (await probeContentFeature(tabId, feature)) return true;
          try {
            await chromeApi.scripting.executeScript({
              target: { tabId, frameIds: [0] },
              files: TOP_FRAME_FEATURE_FILES[feature]
            });
            return await probeContentFeature(tabId, feature);
          } catch (_) {
            return false;
          }
        }
        function ensureContentFeatureReady(tabId, feature) {
          if (!Number.isInteger(tabId) || !isContentFeature(feature)) return Promise.resolve(false);
          const key = `${tabId}:${feature}`;
          const active = tabFeatureInjectionQueue.get(key);
          if (active) return active;
          const operation = injectContentFeatureNow(tabId, feature);
          tabFeatureInjectionQueue.set(key, operation);
          return operation.finally(() => {
            if (tabFeatureInjectionQueue.get(key) === operation) tabFeatureInjectionQueue.delete(key);
          });
        }
        function enqueueTabContentOperation(tabId, operation) {
          const previous = tabContentOperationQueue.get(tabId) || Promise.resolve();
          const current = previous.catch(() => void 0).then(operation);
          tabContentOperationQueue.set(tabId, current);
          return current.finally(() => {
            if (tabContentOperationQueue.get(tabId) === current) tabContentOperationQueue.delete(tabId);
          });
        }
        function waitForDelay(ms) {
          return new Promise((resolve) => setTimeout(resolve, ms));
        }
        function isContentScriptPing(response) {
          return Boolean(response && response.ok);
        }
        function isCurrentContentScriptPing(response) {
          return Boolean(isContentScriptPing(response) && response.version === DESIRED_CONTENT_IDENTITY.version && response.fingerprint === DESIRED_CONTENT_IDENTITY.fingerprint);
        }
        async function pingContentScript(tabId) {
          try {
            const response = await chromeApi.tabs.sendMessage(tabId, { type: "PING" });
            return isContentScriptPing(response) ? response : null;
          } catch (_) {
            return null;
          }
        }
        function retryContentScriptPing(tabId, attempt = 0) {
          return chromeApi.tabs.sendMessage(tabId, { type: "PING" }).then(
            (response) => {
              if (isCurrentContentScriptPing(response)) return true;
              if (attempt >= 2) return false;
              return waitForDelay(100 * (attempt + 1)).then(() => retryContentScriptPing(tabId, attempt + 1));
            },
            () => {
              if (attempt >= 2) return false;
              return waitForDelay(100 * (attempt + 1)).then(() => retryContentScriptPing(tabId, attempt + 1));
            }
          );
        }
        async function injectAndConfirmContentScripts(tabId) {
          try {
            if (!await injectBootstrapScripts(tabId)) return false;
            return await retryContentScriptPing(tabId);
          } catch (_) {
            return false;
          }
        }
        async function ensureContentScriptReadyNow(tabId) {
          let observed = await pingContentScript(tabId);
          if (isCurrentContentScriptPing(observed)) return true;
          if (!observed) return injectAndConfirmContentScripts(tabId);
          for (let generation = 0; generation < 3; generation += 1) {
            try {
              await chromeApi.tabs.sendMessage(tabId, {
                type: "RELOAD",
                expectedInstanceId: typeof observed.instanceId === "string" ? observed.instanceId : ""
              });
            } catch (_) {
            }
            observed = await pingContentScript(tabId);
            if (isCurrentContentScriptPing(observed)) return true;
            if (!observed) return injectAndConfirmContentScripts(tabId);
          }
          return false;
        }
        function ensureContentScriptReady(tabId) {
          return enqueueTabContentOperation(tabId, () => ensureContentScriptReadyNow(tabId));
        }
        function toggleOverlayWhenReady(tabId) {
          return enqueueTabContentOperation(tabId, async () => {
            if (!await ensureContentScriptReadyNow(tabId)) {
              return { ok: false, ready: false, error: "\u9875\u9762\u811A\u672C\u7248\u672C\u51B2\u7A81\uFF0C\u8BF7\u5237\u65B0\u9875\u9762\u540E\u91CD\u8BD5" };
            }
            try {
              const response = await chromeApi.tabs.sendMessage(tabId, { type: "TOGGLE_OVERLAY" });
              return { ok: Boolean(response && response.ok), ready: true };
            } catch (_) {
              return { ok: false, ready: true };
            }
          });
        }
        async function reconcileActiveContentScripts() {
          let tabs;
          try {
            tabs = await chromeApi.tabs.query({ active: true });
          } catch (_) {
            return;
          }
          await Promise.all((Array.isArray(tabs) ? tabs : []).filter((tab) => Number.isInteger(tab.id) && isInjectableUrl(tab.url || "")).map((tab) => ensureContentScriptReady(tab.id).catch(() => false)));
        }
        const screenshotService = ScreenshotModule.createScreenshotService({
          chrome: chromeApi,
          isBrowserInternalUrl,
          warn: (...args) => console.warn(...args)
        });
        const trashService = TrashModule.createTrashService({ chrome: chromeApi, logError: (...args) => console.error(...args) });
        const tabService = TabModule.createTabService({
          chrome: chromeApi,
          screenshot: screenshotService,
          ensureContentScriptReady,
          syncDockConfigToTab,
          DockLogic
        });
        const licenseService = LicenseModule.createLicenseService({ license: root.TFALicense });
        const dockService = DockModule.createDockService({
          chrome: chromeApi,
          DockLogic,
          readDockConfig,
          queueDockMutation,
          runDockInput,
          inputErrorSymbol: DOCK_INPUT_ERROR
        });
        const dockFaviconService = DockFaviconModule.createDockFaviconService({
          chrome: chromeApi,
          DockLogic,
          readDockConfig
        });
        const dockSearchService = DockSearchModule.createDockSearchService({ chrome: chromeApi, DockLogic });
        function searchPermissionSignature(permissions = {}) {
          return ["bookmarks", "history"].map((permission) => {
            const state = permissions?.[permission] || {};
            return `${permission}:${state.granted === true ? "granted" : String(state.code || "required")}`;
          }).join("|");
        }
        function currentSearchPermissionAuthority() {
          const revision = searchPermissionBroadcastRevision;
          return {
            ok: true,
            synced: revision > 0 && searchPermissionNewTabDeliveredRevision === revision,
            delivered: revision > 0 && searchPermissionAllDeliveredRevision === revision,
            permissions: { ...searchPermissionAuthoritySnapshot || {} },
            revision,
            token: revision > 0 ? `${dockBroadcastSessionToken}:search-permissions:${revision}` : ""
          };
        }
        function scheduleSearchPermissionBroadcastRetry() {
          if (searchPermissionBroadcastRetryTimer != null || searchPermissionBroadcastRetryCount >= SEARCH_PERMISSION_BROADCAST_RETRY_DELAYS.length) {
            return false;
          }
          const delay = SEARCH_PERMISSION_BROADCAST_RETRY_DELAYS[searchPermissionBroadcastRetryCount++];
          searchPermissionBroadcastRetryTimer = setTimeout(() => {
            searchPermissionBroadcastRetryTimer = null;
            return broadcastSearchPermissionsChanged();
          }, delay);
          return true;
        }
        function broadcastSearchPermissionsChanged() {
          searchPermissionBroadcastPending = true;
          if (searchPermissionBroadcastWork) return searchPermissionBroadcastWork;
          searchPermissionBroadcastWork = (async () => {
            let authority = currentSearchPermissionAuthority();
            while (searchPermissionBroadcastPending) {
              searchPermissionBroadcastPending = false;
              let snapshot;
              try {
                snapshot = await dockSearchService.handlers.DOCK_SEARCH_GET_PERMISSIONS();
              } catch (_) {
                scheduleSearchPermissionBroadcastRetry();
                authority = { ...currentSearchPermissionAuthority(), synced: false, delivered: false };
                continue;
              }
              const permissions = { ...snapshot?.permissions || {} };
              const signature = searchPermissionSignature(permissions);
              if (signature !== searchPermissionAuthoritySignature) {
                searchPermissionAuthoritySignature = signature;
                searchPermissionAuthoritySnapshot = permissions;
                searchPermissionBroadcastRevision += 1;
                searchPermissionBroadcastRetryCount = 0;
              } else {
                searchPermissionAuthoritySnapshot = permissions;
              }
              const revision = searchPermissionBroadcastRevision;
              authority = currentSearchPermissionAuthority();
              if (searchPermissionAllDeliveredRevision === revision) continue;
              const message = {
                type: "WEB_DOCK_SEARCH_PERMISSIONS_CHANGED",
                permissions,
                revision,
                token: `${dockBroadcastSessionToken}:search-permissions:${revision}`
              };
              let tabs;
              try {
                tabs = await chromeApi.tabs.query({});
              } catch (_) {
                scheduleSearchPermissionBroadcastRetry();
                authority = { ...currentSearchPermissionAuthority(), synced: false, delivered: false };
                continue;
              }
              const webTabs = tabs.filter((tab) => Number.isInteger(tab.id) && isInjectableUrl(tab.url || ""));
              const hasNewTab = tabs.some((tab) => Number.isInteger(tab.id) && isNewTabWorkspaceUrl(tab.url || ""));
              const deliveries = [
                ...webTabs.map((tab) => ({
                  kind: "web",
                  promise: Promise.resolve().then(() => chromeApi.tabs.sendMessage(tab.id, message))
                })),
                ...hasNewTab ? [{
                  kind: "newtab",
                  promise: Promise.resolve().then(() => chromeApi.runtime.sendMessage(message))
                }] : []
              ];
              const settled = await Promise.allSettled(deliveries.map((delivery) => delivery.promise));
              const deliverySucceeded = (result) => result?.status === "fulfilled" && result.value?.ok !== false;
              const newTabIndex = deliveries.findIndex((delivery) => delivery.kind === "newtab");
              const newTabDelivered = newTabIndex >= 0 && deliverySucceeded(settled[newTabIndex]);
              if (newTabDelivered) {
                searchPermissionNewTabDeliveredRevision = revision;
              }
              const allDelivered = settled.every(deliverySucceeded);
              if (allDelivered) {
                searchPermissionAllDeliveredRevision = revision;
                searchPermissionBroadcastRetryCount = 0;
                if (searchPermissionBroadcastRetryTimer != null) {
                  clearTimeout(searchPermissionBroadcastRetryTimer);
                  searchPermissionBroadcastRetryTimer = null;
                }
              } else {
                scheduleSearchPermissionBroadcastRetry();
              }
              authority = {
                ...currentSearchPermissionAuthority(),
                synced: newTabDelivered,
                delivered: allDelivered
              };
            }
            return authority;
          })().finally(() => {
            searchPermissionBroadcastWork = null;
          });
          return searchPermissionBroadcastWork;
        }
        function scheduleV2TaskBroadcastRetry() {
          if (v2TaskBroadcastRetryTimer != null || v2TaskBroadcastRetryCount >= V2_TASK_BROADCAST_RETRY_DELAYS.length) return false;
          const delay = V2_TASK_BROADCAST_RETRY_DELAYS[v2TaskBroadcastRetryCount++];
          v2TaskBroadcastRetryTimer = setTimeout(() => {
            v2TaskBroadcastRetryTimer = null;
            v2TaskBroadcastPending = true;
            void broadcastV2TaskDataChanged();
          }, delay);
          return true;
        }
        function broadcastV2TaskDataChanged(mutationType) {
          if (mutationType) {
            pendingV2TaskMutationTypes.add(String(mutationType));
            v2TaskMutationRevision += 1;
            if (!v2TaskBroadcastWork && v2TaskBroadcastRetryTimer == null && v2TaskBroadcastRetryCount >= V2_TASK_BROADCAST_RETRY_DELAYS.length) {
              v2TaskBroadcastRetryCount = 0;
            }
          }
          v2TaskBroadcastPending = true;
          if (v2TaskBroadcastWork) return v2TaskBroadcastWork;
          v2TaskBroadcastWork = (async () => {
            let broadcasted = false;
            await Promise.resolve();
            await Promise.resolve();
            while (v2TaskBroadcastPending) {
              v2TaskBroadcastPending = false;
              const mutationTypes = [...pendingV2TaskMutationTypes];
              const mutationRevision = v2TaskMutationRevision;
              if (!mutationTypes.length) continue;
              let tabs;
              try {
                tabs = await chromeApi.tabs.query({});
              } catch (_) {
                scheduleV2TaskBroadcastRetry();
                break;
              }
              const newTabOpen = tabs.some((tab) => Number.isInteger(tab.id) && isNewTabWorkspaceUrl(tab.url || ""));
              const webTabs = tabs.filter((tab) => Number.isInteger(tab.id) && !isNewTabWorkspaceUrl(tab.url || "") && isInjectableUrl(tab.url || ""));
              const visibility = await Promise.all(webTabs.map((tab) => Promise.resolve().then(() => chromeApi.tabs.sendMessage(tab.id, { type: "TFA_QUERY_OVERLAY_VISIBILITY" })).then((response) => response?.visible === true, () => false)));
              const visibleOverlayTabs = webTabs.filter((_tab, index) => visibility[index]);
              if (!newTabOpen && !visibleOverlayTabs.length) {
                if (mutationRevision === v2TaskMutationRevision) pendingV2TaskMutationTypes.clear();
                v2TaskBroadcastRetryCount = 0;
                continue;
              }
              const epoch = v2TaskBroadcastEpoch = Math.max(Date.now(), v2TaskBroadcastEpoch + 1);
              const message = {
                type: "V2_TASK_DATA_CHANGED",
                surface: "newtab",
                mutationTypes,
                epoch,
                token: `${dockBroadcastSessionToken}:v2-task:${epoch}`
              };
              const deliveries = await Promise.allSettled([
                ...newTabOpen ? [Promise.resolve().then(() => chromeApi.runtime.sendMessage(message))] : [],
                ...visibleOverlayTabs.map((tab) => Promise.resolve().then(() => chromeApi.tabs.sendMessage(tab.id, message)))
              ]);
              if (deliveries.some((delivery) => delivery.status === "rejected")) {
                scheduleV2TaskBroadcastRetry();
                break;
              }
              if (mutationRevision === v2TaskMutationRevision) pendingV2TaskMutationTypes.clear();
              v2TaskBroadcastRetryCount = 0;
              broadcasted = true;
            }
            return broadcasted;
          })().finally(() => {
            v2TaskBroadcastWork = null;
            if (v2TaskBroadcastPending && v2TaskBroadcastRetryTimer == null) void broadcastV2TaskDataChanged();
          });
          return v2TaskBroadcastWork;
        }
        const activeTriggerService = ActiveTriggerModule.createActiveTriggerService({
          chrome: chromeApi,
          logic: root.TfaActiveTriggerLogic
        });
        activeTriggerService.retryCompensations({ all: true }).catch(() => void 0);
        const newTabSiteSpaceService = NewTabSiteSpaceModule.createNewTabSiteSpaceService({
          chrome: chromeApi,
          model: root.TfaNewTabSiteSpaceModel
        });
        let aiService = null;
        let aiInitializationAttempted = false;
        function initializeAiService() {
          if (aiService) return aiService;
          if (aiInitializationAttempted) return null;
          aiInitializationAttempted = true;
          try {
            if (!V2AiSettings || !V2AiSettingsStoreModule || !V2OpenAiCompatibleProviderModule || !V2AiServiceModule) {
              return null;
            }
            const settingsStore = V2AiSettingsStoreModule.createV2AiSettingsStore({
              storage: chromeApi.storage.local,
              settingsLogic: V2AiSettings
            });
            aiService = V2AiServiceModule.createV2AiService({
              settingsStore,
              settingsLogic: V2AiSettings,
              providerFactory: V2OpenAiCompatibleProviderModule.createOpenAiCompatibleProvider
            });
            return aiService;
          } catch (_error) {
            aiService = null;
            return null;
          }
        }
        function aiUnavailable() {
          return { ok: false, errorCode: "AI_PROVIDER_UNAVAILABLE" };
        }
        function aiSettingsPayload(message = {}) {
          const source = message.settings && typeof message.settings === "object" && !Array.isArray(message.settings) ? message.settings : {};
          const settings = Object.fromEntries([
            "enabled",
            "autoOrganize",
            "provider",
            "baseUrl",
            "model",
            "timeoutMs",
            "excludeSensitiveSites"
          ].filter((field) => Object.prototype.hasOwnProperty.call(source, field)).map((field) => [field, source[field]]));
          const payload = { settings, apiKeyAction: message.apiKeyAction };
          if (message.apiKeyAction === "replace" && typeof message.apiKey === "string") payload.apiKey = message.apiKey;
          return payload;
        }
        async function invokeAiService(method, payload) {
          const service = initializeAiService();
          if (!service || typeof service[method] !== "function") return aiUnavailable();
          try {
            return await service[method](payload);
          } catch (_error) {
            return aiUnavailable();
          }
        }
        let v2Services = null;
        let v2DataStore = null;
        let v2Initialization = null;
        let v2BehaviorObserver = null;
        let v2ImportQueue = Promise.resolve();
        let v2ImportPendingCount = 0;
        let v2ImportGate = Promise.resolve();
        let releaseV2ImportGate = null;
        let v2ActiveMutationCount = 0;
        const v2MutationIdleWaiters = /* @__PURE__ */ new Set();
        const v2ImportPreviewTickets = /* @__PURE__ */ new Map();
        const V2_IMPORT_PREVIEW_TTL_MS = 10 * 60 * 1e3;
        function notifyV2MutationIdle() {
          if (v2ActiveMutationCount !== 0) return;
          for (const resolve of v2MutationIdleWaiters) resolve();
          v2MutationIdleWaiters.clear();
        }
        function whenV2MutationsIdle() {
          if (v2ActiveMutationCount === 0) return Promise.resolve();
          return new Promise((resolve) => v2MutationIdleWaiters.add(resolve));
        }
        function runV2Mutation(work) {
          if (v2ImportPendingCount > 0) return v2ImportGate.then(() => runV2Mutation(work));
          v2ActiveMutationCount += 1;
          return Promise.resolve().then(work).finally(() => {
            v2ActiveMutationCount = Math.max(0, v2ActiveMutationCount - 1);
            notifyV2MutationIdle();
          });
        }
        function queueV2Import(work) {
          v2ImportPendingCount += 1;
          if (v2ImportPendingCount === 1) {
            v2ImportGate = new Promise((resolve) => {
              releaseV2ImportGate = resolve;
            });
          }
          const queued = v2ImportQueue.then(async () => {
            await whenV2MutationsIdle();
            return work();
          });
          v2ImportQueue = queued.catch(() => void 0);
          return queued.finally(() => {
            v2ImportPendingCount = Math.max(0, v2ImportPendingCount - 1);
            if (v2ImportPendingCount === 0) {
              const release = releaseV2ImportGate;
              releaseV2ImportGate = null;
              v2ImportGate = Promise.resolve();
              release?.();
            }
          });
        }
        function importSenderKey(sender = {}) {
          const tabId = Number.isInteger(sender?.tab?.id) ? sender.tab.id : -1;
          const frameId = Number.isInteger(sender?.frameId) ? sender.frameId : 0;
          const documentId = String(sender?.documentId || "").slice(0, 128);
          return `${String(sender?.id || chromeApi.runtime?.id || "extension")}:${tabId}:${frameId}:${documentId}`;
        }
        function backupTicketPayload(backup) {
          if (typeof backup === "string") return backup;
          try {
            return JSON.stringify(backup);
          } catch (_error) {
            return "";
          }
        }
        function pruneImportPreviewTickets(now = Date.now()) {
          for (const [token, ticket] of v2ImportPreviewTickets) {
            if (!ticket || ticket.expiresAt <= now) v2ImportPreviewTickets.delete(token);
          }
          while (v2ImportPreviewTickets.size > 24) {
            v2ImportPreviewTickets.delete(v2ImportPreviewTickets.keys().next().value);
          }
        }
        function issueImportPreviewTicket(backup, sender) {
          pruneImportPreviewTickets();
          const token = root.crypto?.randomUUID?.() || `preview-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 14)}`;
          v2ImportPreviewTickets.set(token, {
            senderKey: importSenderKey(sender),
            payload: backupTicketPayload(backup),
            expiresAt: Date.now() + V2_IMPORT_PREVIEW_TTL_MS,
            inUse: false
          });
          return token;
        }
        function claimImportPreviewTicket(token, backup, sender) {
          pruneImportPreviewTickets();
          const safeToken = String(token || "");
          const ticket = v2ImportPreviewTickets.get(safeToken);
          if (!ticket || ticket.inUse || ticket.senderKey !== importSenderKey(sender) || ticket.payload !== backupTicketPayload(backup)) return null;
          ticket.inUse = true;
          return { token: safeToken, ticket };
        }
        function releaseImportPreviewTicket(claim, { consume = false } = {}) {
          if (!claim) return;
          if (consume) v2ImportPreviewTickets.delete(claim.token);
          else if (v2ImportPreviewTickets.get(claim.token) === claim.ticket) claim.ticket.inUse = false;
        }
        function initializeV2Services() {
          if (v2Services) return Promise.resolve(v2Services);
          if (v2Initialization) return v2Initialization;
          v2Initialization = Promise.resolve().then(async () => {
            if (!V2DataSchema || !V2DataStoreModule || !V2TaskServiceModule || !root.indexedDB) {
              throw new Error("V2 local data is unavailable");
            }
            v2DataStore ||= V2DataStoreModule.createV2DataStore({
              indexedDB: root.indexedDB,
              storage: chromeApi.storage.local,
              schema: V2DataSchema
            });
            let taskOnlyFallback = false;
            try {
              await v2DataStore.migrateLegacyTasks();
            } catch (error) {
              if (typeof v2DataStore.openCurrentVersion !== "function") throw error;
              await v2DataStore.openCurrentVersion();
              taskOnlyFallback = true;
            }
            const taskService = V2TaskServiceModule.createV2TaskService({ store: v2DataStore, schema: V2DataSchema });
            let backupService = null;
            try {
              backupService = V2BackupServiceModule?.createV2BackupService?.({
                taskService,
                siteSpaceAdapter: {
                  read: async () => {
                    const result = await newTabSiteSpaceService.get();
                    if (result?.ok !== true || !result.space) throw new Error("New tab site space is unavailable");
                    return result.space;
                  },
                  replace: async (space) => {
                    const result = await newTabSiteSpaceService.save(space);
                    if (result?.ok !== true) throw new Error(result?.message || "New tab site space could not be restored");
                    return result.space;
                  }
                },
                dockAdapter: {
                  read: readDockConfig,
                  replace: async (config) => (await queueDockMutation(() => ({ config }))).config
                },
                extensionVersion: BuildIdentity.version
              }) || null;
            } catch (_error) {
              backupService = null;
            }
            let behaviorStore = null;
            let inboxService = null;
            let inboxPreferenceStore = null;
            let suggestionService = null;
            let aiPlanApplication = null;
            if (!taskOnlyFallback) {
              if (!V2BehaviorStoreModule || !V2InboxServiceModule || typeof V2DataStoreModule.stablePageId !== "function") {
                throw new Error("V2 behavior data is unavailable");
              }
              behaviorStore = V2BehaviorStoreModule.createV2BehaviorStore({
                dataStore: v2DataStore,
                schema: V2DataSchema
              });
              inboxPreferenceStore = V2InboxPreferenceStoreModule?.createV2InboxPreferenceStore({ storage: chromeApi.storage.local });
              inboxService = V2InboxServiceModule.createV2InboxService({
                dataStore: v2DataStore,
                schema: V2DataSchema,
                stablePageId: V2DataStoreModule.stablePageId,
                preferenceStore: inboxPreferenceStore,
                pageObservationLogic: V2PageObservationLogic
              });
              if (V2SignalAggregationLogic && V2SuggestionRules && V2SuggestionServiceModule) {
                try {
                  const scoreSuggestionCandidates = (input) => V2SuggestionRules.scoreSuggestionCandidates(input);
                  scoreSuggestionCandidates.RULE_VERSION = V2SuggestionRules.RULE_VERSION;
                  const createSuggestionId = (prefix) => {
                    const uuidFactory = () => root.crypto?.randomUUID?.() || `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 12)}`;
                    return prefix === "analysis" ? `analysis-${uuidFactory()}` : V2DataSchema.makeEntityId(prefix, uuidFactory);
                  };
                  let aiCoordinator = null;
                  let readCurrentAiInputVersion = null;
                  const configuredAiService = initializeAiService();
                  if (configuredAiService && V2AiContract && V2AiCoordinatorStateStoreModule && V2AiInputBuilderModule && V2AiSuggestionRerankerModule && V2AiPlanApplicationServiceModule && V2AiOrganizationCoordinatorModule) {
                    try {
                      const stateStore = V2AiCoordinatorStateStoreModule.createV2AiCoordinatorStateStore({
                        storage: chromeApi.storage.local
                      });
                      const inputBuilder = V2AiInputBuilderModule.createV2AiInputBuilder({ contract: V2AiContract });
                      const reranker = V2AiSuggestionRerankerModule.createV2AiSuggestionReranker({
                        aiService: configuredAiService,
                        aiContract: V2AiContract
                      });
                      aiPlanApplication = V2AiPlanApplicationServiceModule.createV2AiPlanApplicationService({
                        store: v2DataStore,
                        schema: V2DataSchema,
                        createId: createSuggestionId
                      });
                      aiCoordinator = V2AiOrganizationCoordinatorModule.createV2AiOrganizationCoordinator({
                        aiService: configuredAiService,
                        inputBuilder,
                        reranker,
                        planApplication: aiPlanApplication,
                        stateStore,
                        getCurrentInputVersion: (request) => typeof readCurrentAiInputVersion === "function" ? readCurrentAiInputVersion(request) : ""
                      });
                    } catch (_error) {
                      aiPlanApplication = null;
                      aiCoordinator = null;
                    }
                  }
                  suggestionService = V2SuggestionServiceModule.createV2SuggestionService({
                    store: v2DataStore,
                    schema: V2DataSchema,
                    aggregateSuggestionSignals: V2SignalAggregationLogic.aggregateSuggestionSignals,
                    scoreSuggestionCandidates,
                    buildNewTaskClusters: V2NewTaskClusterLogic?.buildNewTaskClusters,
                    observationGate: V2SuggestionObservationGateModule?.createV2SuggestionObservationGate({
                      pageObservationLogic: V2PageObservationLogic
                    }),
                    getBrowserSessionId: async () => {
                      const observer = v2BehaviorObserver || startV2BehaviorObserver();
                      if (!observer) throw new Error("V2 behavior observer is unavailable");
                      await Promise.resolve(observer.whenIdle()).catch(() => void 0);
                      return observer.ensureBrowserSessionId();
                    },
                    createId: createSuggestionId,
                    aiCoordinator
                  });
                  readCurrentAiInputVersion = (request) => suggestionService?.currentOrganizationInputVersion?.(request) || "";
                } catch (_error) {
                  console.warn("[TFA] V2 suggestion service unavailable", _error);
                  suggestionService = null;
                }
              }
            }
            v2Services = Object.freeze({
              taskService,
              behaviorStore,
              inboxService,
              inboxPreferenceStore,
              suggestionService,
              aiPlanApplication,
              backupService,
              taskOnlyFallback
            });
            return v2Services;
          }).catch((error) => {
            v2Initialization = null;
            throw error;
          });
          return v2Initialization;
        }
        const V2_TASK_MESSAGE_TYPES = Object.freeze([
          "V2_TASK_LIST",
          "V2_TASK_CREATE",
          "V2_TASK_UPDATE",
          "V2_TASK_REORDER",
          "V2_TASK_DELETE",
          "V2_TASK_RESTORE",
          "V2_RELATION_ADD",
          "V2_RELATION_REMOVE",
          "V2_RELATION_MOVE",
          "V2_RELATION_REORDER",
          "V2_TASK_UNDO_LAST",
          "V2_TASK_DATA_CLEAR"
        ]);
        const V2_MESSAGE_TYPES = Object.freeze([
          ...V2_TASK_MESSAGE_TYPES,
          "V2_INBOX_LIST",
          "V2_INBOX_PREFERENCE_SET",
          "V2_BEHAVIOR_CLEAR",
          "V2_DATA_EXPORT",
          "V2_DATA_IMPORT_PREVIEW",
          "V2_DATA_IMPORT",
          "V2_SUGGESTION_REFRESH",
          "V2_SUGGESTION_LIST",
          "V2_SUGGESTION_ORGANIZE",
          "V2_SUGGESTION_RESOLVE",
          "V2_SUGGESTION_UNDO",
          "V2_AI_PLAN_UNDO"
        ]);
        const V2_AI_MESSAGE_TYPES = Object.freeze([
          "V2_AI_SETTINGS_GET",
          "V2_AI_SETTINGS_SAVE",
          "V2_AI_TEST_CONNECTION",
          "V2_AI_RANK_CANDIDATES",
          "V2_AI_PLAN_WORKSPACE",
          "V2_AI_DATA_CLEAR"
        ]);
        function unavailable(code = "V2_DATA_UNAVAILABLE", error = "V2 local task data is unavailable") {
          return { ok: false, code, error };
        }
        function resetV2ServiceState() {
          v2Services = null;
          v2Initialization = null;
          v2DataStore = null;
        }
        function startV2BehaviorObserver() {
          if (v2BehaviorObserver || !root.indexedDB || !V2BehaviorLogic || !V2BehaviorObserverModule) {
            return v2BehaviorObserver;
          }
          const observer = V2BehaviorObserverModule.createV2BehaviorObserver({
            chrome: chromeApi,
            logic: V2BehaviorLogic,
            getStore: async () => {
              const services = await initializeV2Services();
              if (!services.behaviorStore) throw new Error("V2 behavior data is unavailable");
              return services.behaviorStore;
            },
            resolvePage: async (tab) => {
              const services = await initializeV2Services();
              if (!services.inboxService) throw new Error("V2 inbox data is unavailable");
              return services.inboxService.observePage(tab);
            }
          });
          v2BehaviorObserver = observer;
          Promise.resolve(observer.start()).catch(() => void 0);
          return observer;
        }
        async function reconcileV2Behavior() {
          const existingObserver = v2BehaviorObserver;
          const observer = startV2BehaviorObserver();
          if (!observer) return false;
          if (!existingObserver) return Promise.resolve(observer.whenIdle()).then((result) => result === true);
          if (typeof observer.reconcile !== "function") return false;
          return observer.reconcile();
        }
        async function stopV2BehaviorObserver() {
          const observer = v2BehaviorObserver;
          if (!observer) return null;
          observer.stop();
          await Promise.resolve(observer.whenIdle()).catch(() => void 0);
          return observer;
        }
        function explicitRestoreEpisodeId(kind, operationId = "") {
          const safeKind = String(kind || "restore").replace(/[^A-Za-z0-9._-]/g, "-").slice(0, 32);
          const safeOperation = String(operationId || "").replace(/[^A-Za-z0-9._-]/g, "-").slice(0, 96) || `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
          return `restore-${safeKind}-${safeOperation}`;
        }
        function acceptedRestoreBatchId(value) {
          const token = String(value || "").trim();
          return /^restore-batch-[A-Za-z0-9._-]{1,100}$/.test(token) ? token : "";
        }
        async function recordExplicitRestore(kind, operationId, pages = []) {
          try {
            const services = await initializeV2Services();
            if (!services.behaviorStore?.recordExplicitRestoreEpisode || !services.inboxService) return false;
            const pageIds = [];
            for (const page of Array.isArray(pages) ? pages : []) {
              if (typeof page === "string" && page.startsWith("page-")) pageIds.push(page);
              else {
                const observed = await services.inboxService.observePage(page || {});
                if (observed?.id) pageIds.push(observed.id);
              }
            }
            const uniquePageIds = [...new Set(pageIds)].slice(0, 32);
            if (!uniquePageIds.length) return false;
            const observer = v2BehaviorObserver || startV2BehaviorObserver();
            if (!observer) return false;
            await Promise.resolve(observer.whenIdle()).catch(() => void 0);
            const browserSessionId = await observer.ensureBrowserSessionId();
            const episodeId = explicitRestoreEpisodeId(kind, operationId);
            await services.behaviorStore.recordExplicitRestoreEpisode({
              id: `event-${episodeId}`,
              episodeId,
              restoreKind: kind,
              browserSessionId,
              occurredAt: Date.now(),
              pageIds: uniquePageIds
            });
            return true;
          } catch (_error) {
            return false;
          }
        }
        function resumeV2BehaviorObserver(observer) {
          if (!observer) return;
          v2BehaviorObserver = observer;
          Promise.resolve(observer.start()).catch(() => void 0);
        }
        async function resumeAndReconcileV2BehaviorObserver(observer) {
          if (!observer) return reconcileV2Behavior();
          v2BehaviorObserver = observer;
          const started = await Promise.resolve(observer.start());
          if (started === true) return true;
          if (typeof observer.reconcile !== "function") return false;
          return observer.reconcile("resume-retry");
        }
        async function recoverV2BehaviorObserverAfterReconcileFailure(observer) {
          if (observer) {
            try {
              observer.stop();
            } catch (_error) {
            }
            await Promise.resolve(observer.whenIdle?.()).catch(() => void 0);
          }
          if (v2BehaviorObserver === observer) v2BehaviorObserver = null;
          const replacement = startV2BehaviorObserver();
          if (!replacement) return false;
          const started = await Promise.resolve(replacement.start()).catch(() => false);
          if (started === true) return true;
          if (typeof replacement.reconcile !== "function") return false;
          return Promise.resolve(replacement.reconcile("import-maintenance-retry")).catch(() => false);
        }
        async function resumeObserverAfterImport(observer) {
          let reconciled = false;
          try {
            reconciled = await resumeAndReconcileV2BehaviorObserver(observer);
          } catch (_error) {
            reconciled = false;
          }
          if (reconciled) return true;
          return recoverV2BehaviorObserverAfterReconcileFailure(observer);
        }
        function rebuildV2RuntimeAfterClear() {
          v2BehaviorObserver = null;
          resetV2ServiceState();
          startV2BehaviorObserver();
        }
        async function clearAiLocalData() {
          const result = await invokeAiService("clearAll");
          const coordinatorStateKey = V2AiCoordinatorStateStoreModule?.STORAGE_KEY || "tfa_v2_ai_coordinator_state";
          if (result?.ok === true) {
            try {
              await chromeApi.storage.local.remove(coordinatorStateKey);
              return true;
            } catch (_error) {
              return false;
            }
          }
          try {
            await chromeApi.storage.local.remove([
              V2AiSettings?.AI_SETTINGS_STORAGE_KEY || "tfa_v2_ai_settings",
              V2AiSettings?.AI_API_KEY_STORAGE_KEY || "tfa_v2_ai_api_key",
              coordinatorStateKey
            ]);
            return true;
          } catch (_error) {
            return false;
          }
        }
        async function clearAllV2Data(message) {
          const previousObserver = await stopV2BehaviorObserver();
          try {
            const services = await initializeV2Services();
            const result = await services.taskService.handlers.V2_TASK_DATA_CLEAR(message);
            if (result?.ok) {
              await services.inboxPreferenceStore?.clear?.();
              if (!await clearAiLocalData()) return unavailable("AI_STORAGE_ERROR", "AI local settings could not be cleared");
              rebuildV2RuntimeAfterClear();
            } else resumeV2BehaviorObserver(previousObserver);
            return result;
          } catch (_error) {
            if (v2DataStore) {
              try {
                await v2DataStore.clearAllData();
                try {
                  await chromeApi.storage.local.remove(V2InboxPreferenceStoreModule?.STORAGE_KEY || "tfa_v2_inbox_preferences");
                } catch (_error2) {
                }
                if (!await clearAiLocalData()) return unavailable("AI_STORAGE_ERROR", "AI local settings could not be cleared");
                rebuildV2RuntimeAfterClear();
                return { ok: true };
              } catch (_clearError) {
              }
            }
            resumeV2BehaviorObserver(previousObserver);
            return unavailable();
          }
        }
        const v2MessageHandlers = Object.fromEntries(V2_TASK_MESSAGE_TYPES.map((type) => [type, async (msg) => {
          const execute = async () => {
            if (type === "V2_TASK_DATA_CLEAR") {
              const result = await clearAllV2Data(msg);
              if (result?.ok === true) void broadcastV2TaskDataChanged(type);
              return result;
            }
            try {
              const services = await initializeV2Services();
              const result = await services.taskService.handlers[type](msg);
              if (type === "V2_TASK_RESTORE" && result?.ok === true && result.existing !== true) {
                await recordExplicitRestore("task_restore", msg.restoreToken, (result.relations || []).map((item) => item.pageId));
              }
              if (type !== "V2_TASK_LIST" && result?.ok === true && result.unchanged !== true) {
                void broadcastV2TaskDataChanged(type);
              }
              return result;
            } catch (_error) {
              return unavailable();
            }
          };
          return type === "V2_TASK_LIST" ? execute() : runV2Mutation(execute);
        }]));
        const explicitRestoreMessageHandlers = Object.freeze({
          RESTORE_TRASH_ITEM: (msg) => runV2Mutation(async () => {
            const trash = await trashService.getTrash();
            const item = trash.find((entry) => entry.id === msg.id);
            const result = await trashService.handlers.RESTORE_TRASH_ITEM(msg);
            if (result?.ok === true && item && item.type !== "task") {
              const pages = item.type === "group" ? item.tabs : [item];
              const restoreBatchId = acceptedRestoreBatchId(msg.restoreBatchId);
              const kind = restoreBatchId ? "trash_batch_restore" : item.type === "group" ? "trash_group_restore" : "trash_tab_restore";
              await recordExplicitRestore(kind, restoreBatchId || item.id, pages);
            }
            return result;
          }),
          RESTORE_SESSION: (msg) => runV2Mutation(async () => {
            const result = await tabService.handlers.RESTORE_SESSION(msg);
            if (result?.ok === true && result.tab) {
              await recordExplicitRestore("session_restore", msg.sessionId, [result.tab]);
            }
            await broadcastRecentlyClosedChanged();
            return result;
          })
        });
        const recentlyClosedMessageHandlers = Object.freeze({
          CONSUME_RECENTLY_CLOSED_ITEM: async (msg) => {
            const result = await tabService.handlers.CONSUME_RECENTLY_CLOSED_ITEM(msg);
            if (result?.ok === true) await broadcastRecentlyClosedChanged();
            return result;
          }
        });
        v2MessageHandlers.V2_INBOX_LIST = async () => {
          try {
            const services = await initializeV2Services();
            if (!services.inboxService || !v2BehaviorObserver) {
              return unavailable("V2_INBOX_UNAVAILABLE", "V2 inbox data is unavailable");
            }
            await Promise.resolve(v2BehaviorObserver.whenIdle()).catch(() => void 0);
            const browserSessionId = await v2BehaviorObserver.ensureBrowserSessionId();
            return services.inboxService.list(browserSessionId);
          } catch (_error) {
            return unavailable("V2_INBOX_UNAVAILABLE", "V2 inbox data is unavailable");
          }
        };
        v2MessageHandlers.V2_INBOX_PREFERENCE_SET = (message) => runV2Mutation(async () => {
          try {
            const services = await initializeV2Services(), store = services.inboxPreferenceStore;
            if (!store) return unavailable("V2_INBOX_UNAVAILABLE", "V2 inbox preferences are unavailable");
            await store.apply(message);
            if (message?.pageId && typeof services.suggestionService?.invalidatePending === "function") {
              await services.suggestionService.invalidatePending([message.pageId]);
            }
            return { ok: true };
          } catch (_error) {
            return unavailable("V2_INBOX_PREFERENCE_INVALID", "Inbox preference could not be saved");
          }
        });
        v2MessageHandlers.V2_BEHAVIOR_CLEAR = () => runV2Mutation(async () => {
          const previousObserver = await stopV2BehaviorObserver();
          try {
            const services = await initializeV2Services();
            if (!services.behaviorStore) {
              resumeV2BehaviorObserver(previousObserver);
              return unavailable("V2_BEHAVIOR_UNAVAILABLE", "V2 behavior data is unavailable");
            }
            let deleted = await services.behaviorStore.clearBehaviorEvents();
            const rebuilt = await resumeAndReconcileV2BehaviorObserver(previousObserver);
            if (!rebuilt) throw new Error("V2 behavior reconciliation failed");
            const rebuiltObserver = await stopV2BehaviorObserver();
            deleted += await services.behaviorStore.clearBehaviorEvents();
            if (typeof services.suggestionService?.invalidatePending === "function") {
              await services.suggestionService.invalidatePending();
            }
            const resumed = await resumeAndReconcileV2BehaviorObserver(rebuiltObserver);
            if (!resumed) throw new Error("V2 behavior restart failed");
            return { ok: true, deleted };
          } catch (_error) {
            resumeV2BehaviorObserver(previousObserver);
            return unavailable("V2_BEHAVIOR_UNAVAILABLE", "V2 behavior data is unavailable");
          }
        });
        v2MessageHandlers.V2_DATA_EXPORT = async () => {
          try {
            const services = await initializeV2Services();
            if (!services.backupService) return unavailable("V2_EXPORT_UNAVAILABLE", "V2 data export is unavailable");
            return { ok: true, export: await services.backupService.exportBackup() };
          } catch (_error) {
            return unavailable("V2_EXPORT_UNAVAILABLE", "V2 data export is unavailable");
          }
        };
        v2MessageHandlers.V2_DATA_IMPORT_PREVIEW = async (message, sender) => {
          try {
            const services = await initializeV2Services();
            if (!services.backupService) return unavailable("V2_IMPORT_UNAVAILABLE", "V2 data import is unavailable");
            const result = await services.backupService.previewImport(message?.backup);
            if (result?.ok !== true) return result;
            return {
              ...result,
              previewToken: issueImportPreviewTicket(message?.backup, sender)
            };
          } catch (error) {
            return unavailable(error?.code || "V2_IMPORT_INVALID", error?.message || "Backup file is invalid");
          }
        };
        v2MessageHandlers.V2_DATA_IMPORT = async (message, sender) => {
          if (message?.confirmed !== true) {
            return unavailable("V2_IMPORT_CONFIRMATION_REQUIRED", "Explicit overwrite confirmation is required");
          }
          const claim = claimImportPreviewTicket(message?.previewToken, message?.backup, sender);
          if (!claim) {
            return unavailable("V2_IMPORT_PREVIEW_REQUIRED", "Preview this exact backup again before restoring");
          }
          return queueV2Import(async () => {
            let previousObserver = null;
            let result = null;
            let committed = false;
            try {
              previousObserver = await stopV2BehaviorObserver();
              const services = await initializeV2Services();
              if (!services.backupService) {
                releaseImportPreviewTicket(claim);
                resumeV2BehaviorObserver(previousObserver);
                return unavailable("V2_IMPORT_UNAVAILABLE", "V2 data import is unavailable");
              }
              result = await services.backupService.importBackup(message?.backup);
              committed = result?.ok === true;
              if (committed) releaseImportPreviewTicket(claim, { consume: true });
              const failedMaintenance = [];
              try {
                if (typeof services.suggestionService?.invalidatePending === "function") {
                  await services.suggestionService.invalidatePending();
                }
              } catch (_error) {
                failedMaintenance.push("suggestions");
              }
              const reconciled = await resumeObserverAfterImport(previousObserver);
              previousObserver = null;
              if (!reconciled) failedMaintenance.push("observer");
              if (committed) {
                void broadcastV2TaskDataChanged("V2_DATA_IMPORT_COMMITTED");
                if (failedMaintenance.length) {
                  void broadcastV2TaskDataChanged("V2_DATA_IMPORT_MAINTENANCE_DEGRADED");
                  return {
                    ok: false,
                    code: "V2_IMPORT_RECONCILE_FAILED",
                    error: "Backup data was imported, but follow-up synchronization maintenance failed",
                    committed: true,
                    maintenanceDegraded: true,
                    details: { failedMaintenance },
                    summary: result.summary
                  };
                }
              } else {
                releaseImportPreviewTicket(claim);
                void broadcastV2TaskDataChanged("V2_DATA_IMPORT_ROLLBACK");
              }
              if (result?.ok !== false) return result;
              const rollbackSections = Array.isArray(result?.details?.failedSections) ? result.details.failedSections.map((item) => String(item || "")).filter((section) => ["taskGraph", "siteSpace", "dock"].includes(section)) : [];
              return rollbackSections.length ? { ...result, details: { rollbackSections } } : result;
            } catch (error) {
              if (!committed) releaseImportPreviewTicket(claim);
              if (previousObserver) {
                await resumeObserverAfterImport(previousObserver).catch(() => false);
                previousObserver = null;
              }
              void broadcastV2TaskDataChanged(committed ? "V2_DATA_IMPORT_MAINTENANCE_DEGRADED" : "V2_DATA_IMPORT_ROLLBACK");
              if (committed) {
                return {
                  ok: false,
                  code: "V2_IMPORT_RECONCILE_FAILED",
                  error: "Backup data was imported, but follow-up synchronization maintenance failed",
                  committed: true,
                  maintenanceDegraded: true
                };
              }
              const rollbackSections = Array.isArray(error?.details?.failedSections) ? [...new Set(error.details.failedSections.map((item) => String(item || "")).filter((section) => ["taskGraph", "siteSpace", "dock"].includes(section)))] : [];
              if (error?.code === "BACKUP_IMPORT_PARTIAL_ROLLBACK") {
                const response = unavailable(
                  "BACKUP_IMPORT_PARTIAL_ROLLBACK",
                  "Backup import failed and one or more previous snapshots require verification"
                );
                return rollbackSections.length ? { ...response, details: { rollbackSections } } : response;
              }
              const safeCode = [
                "BACKUP_IMPORT_ROLLED_BACK",
                "BACKUP_IMPORT_IN_PROGRESS",
                "BACKUP_SECTION_UNAVAILABLE"
              ].includes(error?.code) ? error.code : "V2_IMPORT_FAILED";
              return unavailable(safeCode, "Backup import failed");
            }
          });
        };
        for (const [type, method] of Object.entries({
          V2_SUGGESTION_REFRESH: "refresh",
          V2_SUGGESTION_LIST: "list",
          V2_SUGGESTION_ORGANIZE: "organize",
          V2_SUGGESTION_RESOLVE: "resolve",
          V2_SUGGESTION_UNDO: "undo"
        })) {
          v2MessageHandlers[type] = async (message) => {
            const execute = async () => {
              try {
                const services = await initializeV2Services();
                if (Array.isArray(message?.pageIds) && ["refresh", "list", "organize"].includes(method)) {
                  const preferences = await services.inboxPreferenceStore?.load?.() || {};
                  const suppressed = /* @__PURE__ */ new Set([...preferences.ignoredPageIds || [], ...preferences.sharedPageIds || []]);
                  message = { ...message, pageIds: message.pageIds.filter((pageId) => !suppressed.has(String(pageId))) };
                }
                const handler = services.suggestionService?.[method];
                if (typeof handler !== "function") {
                  return unavailable("V2_SUGGESTION_UNAVAILABLE", "V2 suggestions are unavailable");
                }
                return await handler(message);
              } catch (error) {
                return unavailable(error?.code || "V2_SUGGESTION_UNAVAILABLE", error?.message || "V2 suggestions are unavailable");
              }
            };
            return method === "list" ? execute() : runV2Mutation(execute);
          };
        }
        v2MessageHandlers.V2_AI_PLAN_UNDO = (message) => runV2Mutation(async () => {
          try {
            const services = await initializeV2Services();
            if (typeof services.aiPlanApplication?.undo !== "function") {
              return unavailable("AI_PLAN_UNAVAILABLE", "AI task adjustment is unavailable");
            }
            return await services.aiPlanApplication.undo(message?.changeId);
          } catch (error) {
            return unavailable(error?.code || "AI_PLAN_UNDO_UNAVAILABLE", "AI task adjustment could not be undone");
          }
        });
        const aiMessageHandlers = Object.freeze({
          V2_AI_SETTINGS_GET: () => invokeAiService("getSettings"),
          V2_AI_SETTINGS_SAVE: (message) => invokeAiService("saveSettings", aiSettingsPayload(message)),
          V2_AI_TEST_CONNECTION: (message) => invokeAiService("testConnection", aiSettingsPayload(message)),
          V2_AI_RANK_CANDIDATES: (message) => invokeAiService("rankCandidates", message?.input),
          V2_AI_PLAN_WORKSPACE: (message) => invokeAiService("planWorkspace", message?.input),
          V2_AI_DATA_CLEAR: async () => await clearAiLocalData() ? { ok: true } : unavailable("AI_STORAGE_ERROR", "AI local settings could not be cleared")
        });
        const dockConfigMutationTypes = /* @__PURE__ */ new Set([
          "DOCK_UPDATE_SETTINGS",
          "DOCK_UPSERT_SITE",
          "DOCK_REMOVE_SITE",
          "DOCK_REORDER_SITES",
          "DOCK_TOGGLE_SITE"
        ]);
        const dockMessageHandlers = Object.fromEntries(Object.entries(dockService.handlers).map(([type, handler]) => [
          type,
          dockConfigMutationTypes.has(type) ? (message, sender) => runV2Mutation(() => handler(message, sender)) : handler
        ]));
        const siteSpaceMessageHandlers = Object.fromEntries(Object.entries(newTabSiteSpaceService.handlers).map(([type, handler]) => [
          type,
          type === "NEWTAB_SITE_SPACE_GET" ? handler : (message, sender) => runV2Mutation(() => handler(message, sender))
        ]));
        const activeGuidanceHandlers = {
          ...activeTriggerService.handlers,
          [root.TfaActiveTriggerLogic.TEST_PROMPT_MESSAGE]: (message, sender) => isActiveGuidanceTestSender(sender) ? activeTriggerService.showTestPrompt(message?.promptType) : { ok: false, code: "INVALID_GUIDANCE_TEST_SOURCE" },
          [root.TfaActiveTriggerLogic.TEST_WINDOW_MESSAGE]: (message, sender) => isActiveGuidanceTestSender(sender) ? activeTriggerService.openTestWindow(message?.count) : { ok: false, code: "INVALID_GUIDANCE_TEST_SOURCE" }
        };
        const messageHandlers = Object.assign(
          /* @__PURE__ */ Object.create(null),
          tabService.handlers,
          trashService.handlers,
          screenshotService.handlers,
          licenseService.handlers,
          dockMessageHandlers,
          dockFaviconService.handlers,
          dockSearchService.handlers,
          activeGuidanceHandlers,
          siteSpaceMessageHandlers,
          v2MessageHandlers,
          explicitRestoreMessageHandlers,
          recentlyClosedMessageHandlers,
          aiMessageHandlers,
          {
            TFA_LOAD_CONTENT_FEATURE: async (msg, sender) => {
              const tabId = sender && sender.tab && sender.tab.id;
              if (!Number.isInteger(tabId) || !isContentFeature(msg && msg.feature)) {
                return { ok: false, ready: false, feature: null, error: "Invalid content feature request" };
              }
              let url = sender.tab.url || "";
              if (!url) {
                try {
                  url = (await chromeApi.tabs.get(tabId)).url || "";
                } catch (_) {
                }
              }
              if (!isInjectableUrl(url)) {
                return { ok: false, ready: false, feature: msg.feature, error: "Page cannot be injected" };
              }
              const ready = await ensureContentFeatureReady(tabId, msg.feature);
              return { ok: ready, ready, feature: msg.feature };
            },
            DOCK_REQUEST_BOOKMARKS_PERMISSION: async (msg) => {
              const result = await dockSearchService.handlers.DOCK_REQUEST_BOOKMARKS_PERMISSION(msg);
              await broadcastSearchPermissionsChanged();
              return result;
            },
            DOCK_REQUEST_HISTORY_PERMISSION: async (msg) => {
              const result = await dockSearchService.handlers.DOCK_REQUEST_HISTORY_PERMISSION(msg);
              await broadcastSearchPermissionsChanged();
              return result;
            },
            DOCK_SEARCH_SYNC_PERMISSIONS: async (msg, sender) => {
              const senderUrl = sender?.url || sender?.tab?.url || "";
              if (!isNewTabWorkspaceUrl(senderUrl)) return { ok: false, code: "INVALID_PERMISSION_SYNC_SOURCE" };
              const permission = ["bookmarks", "history"].includes(msg?.permission) ? msg.permission : "";
              if (!permission) return { ok: false, code: "INVALID_OPTIONAL_PERMISSION" };
              if (typeof msg?.granted !== "boolean") return { ok: false, code: "INVALID_PERMISSION_DECISION" };
              const result = await dockSearchService.syncPermissionDecision(permission);
              const authority = await broadcastSearchPermissionsChanged();
              const authorityPermissions = authority?.permissions && Object.keys(authority.permissions).length > 0 ? authority.permissions : { [permission]: result };
              const authoritativeResult = authorityPermissions[permission] || result;
              return {
                ok: true,
                synced: authority?.synced === true,
                permission,
                granted: authoritativeResult?.granted === true,
                code: String(authoritativeResult?.code || ""),
                recoverable: authoritativeResult?.recoverable !== false,
                permissions: authorityPermissions,
                revision: Number(authority?.revision) || 0,
                token: String(authority?.token || "")
              };
            },
            ENSURE_CONTENT_READY_AND_TOGGLE: async (msg) => {
              if (!Number.isInteger(msg.tabId)) return { ok: false, ready: false, error: "Invalid tab id" };
              let tab;
              try {
                tab = await chromeApi.tabs.get(msg.tabId);
              } catch (_) {
                return { ok: false, ready: false, error: "Tab not found" };
              }
              if (isNewTabWorkspaceUrl(tab.url || "")) return toggleNewTabWorkspace(msg.tabId);
              if (!isInjectableUrl(tab.url || "")) return { ok: false, ready: false, error: "Page cannot be injected" };
              return toggleOverlayWhenReady(msg.tabId);
            }
          }
        );
        const EXPECTED_MESSAGE_TYPES = Object.freeze([
          "GET_ALL_TABS",
          "SWITCH_TAB",
          "CLOSE_TABS",
          "CLOSE_GROUP",
          "GET_TRASH",
          "ADD_TASK_TO_TRASH",
          "UPDATE_TASK_TRASH_OPERATION",
          "GET_TASK_TRASH_OPERATIONS",
          "RESTORE_TRASH_ITEM",
          "DELETE_TRASH_ITEM",
          "CLEAR_TRASH",
          "DUPLICATE_TAB",
          "PIN_TAB",
          "GET_RECENTLY_CLOSED",
          "CONSUME_RECENTLY_CLOSED_ITEM",
          "RESTORE_SESSION",
          "MOVE_TAB",
          "MOVE_TAB_RELATIVE",
          "GROUP_TABS",
          "GROUP_BY_DOMAIN",
          "UNGROUP_ALL",
          "UNGROUP_TABS",
          "ADD_TO_GROUP",
          "UPDATE_GROUP",
          "NEW_TAB",
          "GET_HOT_BOOKMARKS",
          "ADD_HOT_BOOKMARK",
          "REMOVE_HOT_BOOKMARK",
          "PIN_AS_BOOKMARK",
          "DOCK_GET_BOOKMARKS",
          "DOCK_REQUEST_BOOKMARKS_PERMISSION",
          "DOCK_REQUEST_HISTORY_PERMISSION",
          "DOCK_SEARCH_GET_PERMISSIONS",
          "DOCK_SEARCH_SYNC_PERMISSIONS",
          "CAPTURE_CURRENT_TAB",
          "GET_ALL_SCREENSHOTS",
          "GET_TAB_PREVIEW",
          "LICENSE_GET_STATE",
          "LICENSE_ACTIVATE",
          "LICENSE_DEACTIVATE",
          "DOCK_GET_CONFIG",
          "DOCK_GET_TAB_ZOOM",
          "DOCK_RESOLVE_FAVICONS",
          "DOCK_UPDATE_SETTINGS",
          "DOCK_UPSERT_SITE",
          "DOCK_REMOVE_SITE",
          "DOCK_REORDER_SITES",
          "DOCK_TOGGLE_SITE",
          "DOCK_OPEN_SITE",
          "DOCK_SEARCH_SUGGEST",
          "DOCK_SEARCH_GET_RECENTS",
          "DOCK_SEARCH_OPEN",
          "TFA_LOAD_CONTENT_FEATURE",
          "ENSURE_CONTENT_READY_AND_TOGGLE",
          "ACTIVE_GUIDANCE_DISMISS",
          "ACTIVE_GUIDANCE_MUTE_TODAY",
          "ACTIVE_GUIDANCE_CLAIM",
          "ACTIVE_GUIDANCE_CONFIRM",
          "ACTIVE_GUIDANCE_RELEASE",
          "ACTIVE_GUIDANCE_TEST_PROMPT",
          "ACTIVE_GUIDANCE_TEST_WINDOW",
          "NEWTAB_SITE_SPACE_GET",
          "NEWTAB_SITE_SPACE_SAVE",
          "NEWTAB_SITE_SPACE_RESET",
          ...V2_MESSAGE_TYPES,
          ...V2_AI_MESSAGE_TYPES
        ]);
        const actualTypes = Object.keys(messageHandlers);
        if (actualTypes.length !== EXPECTED_MESSAGE_TYPES.length || EXPECTED_MESSAGE_TYPES.some((type) => typeof messageHandlers[type] !== "function")) {
          throw new Error("background message handler set mismatch");
        }
        async function handle(msg, sender = {}) {
          const handler = messageHandlers[msg.type];
          if (!handler) return { error: "Unknown message type: " + msg.type };
          return handler(msg, sender);
        }
        reconcileActiveContentScripts().catch(() => void 0);
        chromeApi.runtime.onInstalled.addListener(async (details) => {
          await activeTriggerService.handleInstalled(details).catch(() => void 0);
          await reconcileV2Behavior().catch(() => false);
          const { hotBookmarks } = await chromeApi.storage.local.get("hotBookmarks");
          if (!hotBookmarks) await chromeApi.storage.local.set({ hotBookmarks: tabService.defaultHotBookmarks });
          if (details.reason !== "update") return;
          try {
            const tabs = await chromeApi.tabs.query({});
            for (const tab of tabs) {
              if (!isInjectableUrl(tab.url || "")) continue;
              await ensureContentScriptReady(tab.id);
            }
          } catch (_) {
          }
        });
        chromeApi.runtime.onStartup.addListener(() => {
          activeTriggerService.handleStartup().catch(() => void 0);
          return reconcileV2Behavior().catch(() => false);
        });
        chromeApi.commands.onCommand.addListener((command) => {
          if (command !== "toggle-overlay") return void 0;
          return chromeApi.tabs.query({ active: true, currentWindow: true }).then(([tab]) => {
            if (tab && Number.isInteger(tab.id) && isNewTabWorkspaceUrl(tab.url || "")) {
              return toggleNewTabWorkspace(tab.id);
            }
            if (!tab || !isInjectableUrl(tab.url || "")) return null;
            return toggleOverlayWhenReady(tab.id);
          }).catch(() => {
          });
        });
        chromeApi.tabs.onActivated.addListener(async (activeInfo) => {
          activeTriggerService.handleTabActivated().catch(() => void 0);
          screenshotService.noteActivation(activeInfo.windowId);
          try {
            const contentReady = await ensureContentScriptReady(activeInfo.tabId);
            if (contentReady) await syncDockConfigToTab(activeInfo.tabId);
            await chromeApi.tabs.sendMessage(activeInfo.tabId, { type: "TAB_ACTIVATED" }).catch(() => {
            });
          } catch (_) {
          }
          if (lastActiveTabId && lastActiveTabId !== activeInfo.tabId) {
            try {
              await chromeApi.tabs.sendMessage(lastActiveTabId, { type: "TAB_ACTIVATED" }).catch(() => {
              });
            } catch (_) {
            }
          }
          lastActiveTabId = activeInfo.tabId;
          screenshotService.schedule(activeInfo.tabId, activeInfo.windowId, screenshotService.delays.activated, { ignoreThrottle: false });
        });
        chromeApi.tabs.onUpdated.addListener(async (tabId, changeInfo, tab) => {
          activeTriggerService.handleTabUpdated(tabId, changeInfo).catch(() => void 0);
          if (changeInfo.url || changeInfo.status === "loading") {
            screenshotService.clearScheduled(tabId);
            screenshotService.invalidate(tabId);
          }
          if (changeInfo.status === "complete" && tab.active) {
            screenshotService.schedule(tabId, tab.windowId, screenshotService.delays.loaded, { ignoreThrottle: true });
          }
          if (changeInfo.status === "complete" && isPageUrl(tab.url || "")) {
            await ensureContentScriptReady(tabId).catch(() => false);
          }
        });
        chromeApi.tabs.onRemoved.addListener((tabId, removeInfo) => {
          activeTriggerService.handleTabRemoved(tabId, removeInfo).catch(() => void 0);
          screenshotService.clearScheduled(tabId);
          screenshotService.forgetTab(tabId);
        });
        chromeApi.windows?.onRemoved?.addListener?.((windowId) => {
          activeTriggerService.handleWindowRemoved(windowId).catch(() => void 0);
          screenshotService.forgetWindow(windowId);
        });
        chromeApi.tabs.onCreated.addListener((tab) => {
          activeTriggerService.handleTabCreated(tab).catch(() => void 0);
        });
        if (chromeApi.sessions?.onChanged && typeof chromeApi.sessions.onChanged.addListener === "function") {
          chromeApi.sessions.onChanged.addListener(() => {
            tabService.reconcileRecentlyClosedConsumption().catch(() => void 0);
            broadcastRecentlyClosedChanged().catch(() => void 0);
          });
        }
        const handleOptionalPermissionChange = (details = {}) => {
          const permissions = (details.permissions || []).filter((permission) => ["bookmarks", "history"].includes(permission));
          if (!permissions.length) return;
          const cleared = dockSearchService.clearPermissionDecisions(permissions);
          Promise.resolve(cleared).then(() => broadcastSearchPermissionsChanged()).catch(() => void 0);
        };
        chromeApi.permissions?.onAdded?.addListener?.(handleOptionalPermissionChange);
        chromeApi.permissions?.onRemoved?.addListener?.(handleOptionalPermissionChange);
        if (chromeApi.tabs.onZoomChange && typeof chromeApi.tabs.onZoomChange.addListener === "function") {
          chromeApi.tabs.onZoomChange.addListener(({ tabId, newZoomFactor }) => {
            Promise.resolve().then(() => chromeApi.tabs.sendMessage(tabId, {
              type: "WEB_DOCK_ZOOM_CHANGED",
              zoom: newZoomFactor
            })).catch(() => void 0);
          });
        }
        reconcileV2Behavior().catch(() => false);
        initializeV2Services().catch(() => void 0);
        initializeAiService();
        chromeApi.runtime.onMessage.addListener((msg, sender, sendResponse) => {
          handle(msg, sender).then((result) => sendResponse(result)).catch((err) => {
            console.error("[TFA background]", err);
            sendResponse({ error: err && err.message ? err.message : String(err) });
          });
          return true;
        });
        root.__tfaBackgroundRuntime = Object.freeze({
          handle,
          messageHandlers,
          screenshotService,
          activeTriggerService,
          newTabSiteSpaceService,
          ensureContentScriptReady,
          ensureContentFeatureReady,
          expectedMessageTypes: EXPECTED_MESSAGE_TYPES
        });
      })(typeof globalThis === "object" ? globalThis : exports);
    }
  });

  // src/background/index.js
  var require_index = __commonJS({
    "src/background/index.js"() {
      var import_version = __toESM(require_version());
      var import_license = __toESM(require_license());
      var import_dock_logic = __toESM(require_dock_logic());
      var import_web_dock_recently_closed_logic = __toESM(require_web_dock_recently_closed_logic());
      var import_runtime_contract = __toESM(require_runtime_contract());
      var import_active_trigger_logic = __toESM(require_active_trigger_logic());
      var import_newtab_site_space_model = __toESM(require_newtab_site_space_model());
      var import_v2_data_schema = __toESM(require_v2_data_schema());
      var import_v2_behavior_logic = __toESM(require_v2_behavior_logic());
      var import_v2_page_context_logic = __toESM(require_v2_page_context_logic());
      var import_v2_page_observation_logic = __toESM(require_v2_page_observation_logic());
      var import_v2_new_task_cluster_logic = __toESM(require_v2_new_task_cluster_logic());
      var import_v2_signal_aggregation_logic = __toESM(require_v2_signal_aggregation_logic());
      var import_v2_suggestion_rules = __toESM(require_v2_suggestion_rules());
      var import_v2_ai_settings = __toESM(require_v2_ai_settings());
      var import_v2_ai_contract = __toESM(require_v2_ai_contract());
      var import_v2_safe_data_backup = __toESM(require_v2_safe_data_backup());
      var import_v2_safe_data_export = __toESM(require_v2_safe_data_export());
      var import_license_service = __toESM(require_license_service());
      var import_screenshot_service = __toESM(require_screenshot_service());
      var import_trash_service = __toESM(require_trash_service());
      var import_tab_service = __toESM(require_tab_service());
      var import_dock_favicon_service = __toESM(require_dock_favicon_service());
      var import_dock_search_service = __toESM(require_dock_search_service());
      var import_dock_service = __toESM(require_dock_service());
      var import_active_trigger_service = __toESM(require_active_trigger_service());
      var import_newtab_site_space_service = __toESM(require_newtab_site_space_service());
      var import_v2_data_store = __toESM(require_v2_data_store());
      var import_v2_task_service = __toESM(require_v2_task_service());
      var import_v2_backup_service = __toESM(require_v2_backup_service());
      var import_v2_behavior_store = __toESM(require_v2_behavior_store());
      var import_v2_inbox_service = __toESM(require_v2_inbox_service());
      var import_v2_inbox_preference_store = __toESM(require_v2_inbox_preference_store());
      var import_v2_behavior_observer = __toESM(require_v2_behavior_observer());
      var import_v2_suggestion_observation_gate = __toESM(require_v2_suggestion_observation_gate());
      var import_v2_suggestion_organization_input = __toESM(require_v2_suggestion_organization_input());
      var import_v2_suggestion_service = __toESM(require_v2_suggestion_service());
      var import_v2_ai_settings_store = __toESM(require_v2_ai_settings_store());
      var import_v2_openai_compatible_provider = __toESM(require_v2_openai_compatible_provider());
      var import_v2_ai_service = __toESM(require_v2_ai_service());
      var import_v2_ai_coordinator_state_store = __toESM(require_v2_ai_coordinator_state_store());
      var import_v2_ai_input_builder = __toESM(require_v2_ai_input_builder());
      var import_v2_ai_suggestion_reranker = __toESM(require_v2_ai_suggestion_reranker());
      var import_v2_ai_plan_application_service = __toESM(require_v2_ai_plan_application_service());
      var import_v2_ai_organization_coordinator = __toESM(require_v2_ai_organization_coordinator());
      var import_runtime = __toESM(require_runtime());
    }
  });
  require_index();
})();
