From 3e9429535810d93fb5abed25f27801435e527d6a Mon Sep 17 00:00:00 2001 From: Math Date: Tue, 20 Jan 2026 03:03:33 -0600 Subject: [PATCH 01/19] Fix MV3 service worker crash by shimming ajv/ajv-formats --- README.md | 19 ++++ chrome-extension/manifest.ts | 3 +- chrome-extension/src/shims/ajv-codegen.ts | 57 +++++++++++ chrome-extension/src/shims/ajv-formats.ts | 19 ++++ chrome-extension/src/shims/ajv.ts | 48 +++++++++ chrome-extension/vite.config.mts | 21 +++- package.json | 6 +- scripts/copy-env.mjs | 11 +++ scripts/scan-unsafe-eval.mjs | 76 +++++++++++++++ scripts/set-global-env.mjs | 114 ++++++++++++++++++++++ 10 files changed, 365 insertions(+), 9 deletions(-) create mode 100644 chrome-extension/src/shims/ajv-codegen.ts create mode 100644 chrome-extension/src/shims/ajv-formats.ts create mode 100644 chrome-extension/src/shims/ajv.ts create mode 100644 scripts/copy-env.mjs create mode 100644 scripts/scan-unsafe-eval.mjs create mode 100644 scripts/set-global-env.mjs diff --git a/README.md b/README.md index 930941e0..cdbaca93 100755 --- a/README.md +++ b/README.md @@ -278,6 +278,25 @@ pnpm build pnpm zip ``` +### MV3 CSP (`unsafe-eval`) note + +Chrome Manifest V3 forbids `eval` / `new Function()` in the extension service worker. Some transitive dependencies (commonly Ajv via the MCP SDK) can trigger this and crash the background script. + +- The background build aliases any `ajv` import (including subpaths) to a CSP-safe stub in `chrome-extension/src/shims/ajv.ts` via `chrome-extension/vite.config.mts`. +- To guard against regressions in bundled output, run: + +```bash +pnpm scan:unsafe-eval +``` + +If `pnpm`/Corepack activation is blocked by permissions on your machine, you can still validate the bundle using npm: + +```powershell +npm install --legacy-peer-deps +npm run build +npm run scan:unsafe-eval +``` + ## Contributing Contributions are welcome! Please feel free to submit a Pull Request. diff --git a/chrome-extension/manifest.ts b/chrome-extension/manifest.ts index a8f3fec7..c30e5747 100644 --- a/chrome-extension/manifest.ts +++ b/chrome-extension/manifest.ts @@ -67,7 +67,6 @@ const manifest = { icons: { 128: 'icon-128.png', 34: 'icon-34.png', - 16: 'icon-16.png', }, content_scripts: [ // { @@ -168,7 +167,7 @@ const manifest = { // devtools_page: 'devtools/index.html', web_accessible_resources: [ { - resources: ['*.js', '*.css', 'content/*.css', '*.svg', 'icon-128.png', 'icon-34.png', 'icon-16.png'], + resources: ['*.js', '*.css', 'content/*.css', '*.svg', 'icon-128.png', 'icon-34.png'], matches: ['*://*/*'], }, ], diff --git a/chrome-extension/src/shims/ajv-codegen.ts b/chrome-extension/src/shims/ajv-codegen.ts new file mode 100644 index 00000000..6ad06a1e --- /dev/null +++ b/chrome-extension/src/shims/ajv-codegen.ts @@ -0,0 +1,57 @@ +// CSP-safe stub for Ajv internal codegen helpers. +// +// Some packages (notably ajv-formats) import `ajv/dist/compile/codegen` and expect +// helpers like `operators`, `str`, `_`, `or`, and `KeywordCxt` to exist. +// +// In MV3 service workers we must avoid Ajv's real codegen (it can use `new Function()`). +// This module provides just enough surface area to prevent crashes during module init. + +export const operators = { + LT: '<', + LTE: '<=', + GT: '>', + GTE: '>=', + EQ: '===', + NEQ: '!==', +} as const; + +function interpolate(strings: TemplateStringsArray, exprs: unknown[]): string { + let out = ''; + for (let i = 0; i < strings.length; i++) { + out += strings[i] ?? ''; + if (i < exprs.length) out += String(exprs[i]); + } + return out; +} + +// Ajv's codegen exports `_` and `str` as tagged-template helpers. We return plain strings. +export function _(strings: TemplateStringsArray, ...exprs: unknown[]): string { + return interpolate(strings, exprs); +} + +export function str(strings: TemplateStringsArray, ...exprs: unknown[]): string { + return interpolate(strings, exprs); +} + +export function or(...parts: unknown[]): string { + return parts.map((p) => String(p)).join(' || '); +} + +export function and(...parts: unknown[]): string { + return parts.map((p) => String(p)).join(' && '); +} + +export function getProperty(prop: unknown): string { + if (typeof prop === 'string' && /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(prop)) return `.${prop}`; + const safe = String(prop).replace(/\\/g, '\\\\').replace(/'/g, "\\'"); + return `['${safe}']`; +} + +// Minimal KeywordCxt shape used by ajv-formats at runtime. +export class KeywordCxt { + public $data = false; + public schemaCode: string = '""'; + public schema: unknown = undefined; + + constructor(_it: unknown, _def: unknown, _keyword: string) {} +} diff --git a/chrome-extension/src/shims/ajv-formats.ts b/chrome-extension/src/shims/ajv-formats.ts new file mode 100644 index 00000000..9c410481 --- /dev/null +++ b/chrome-extension/src/shims/ajv-formats.ts @@ -0,0 +1,19 @@ +// CSP-safe stub for `ajv-formats`. +// +// Some dependencies import `ajv-formats` (often via `ajv-formats/dist/formats`). +// The real package expects full Ajv internals/codegen to exist and can crash in MV3. +// This shim ensures those imports are no-ops. + +export type AjvLike = { + addFormat?: (name: string, format: unknown) => unknown; + addKeyword?: (keyword: string, definition?: unknown) => unknown; +}; + +export default function addFormats(ajv: AjvLike, _opts?: unknown): AjvLike { + return ajv; +} + +// Common ajv-formats named exports (keep harmless). +export const formats: Record = {}; +export const fullFormats: Record = {}; +export const fastFormats: Record = {}; diff --git a/chrome-extension/src/shims/ajv.ts b/chrome-extension/src/shims/ajv.ts new file mode 100644 index 00000000..b24855ed --- /dev/null +++ b/chrome-extension/src/shims/ajv.ts @@ -0,0 +1,48 @@ +// src/shims/ajv.ts + +export class Name { + public str: string; + + constructor(s: unknown) { + this.str = String(s); + } + + toString() { + return this.str; + } +} + +export default class Ajv { + constructor(options?: any) {} + + // CRITICAL: Return 'this' to allow method chaining (e.g. ajv.addSchema().compile()) + addSchema(schema: any, key?: string) { + return this; + } + addKeyword(keyword: string, definition?: any) { + return this; + } + addFormat(name: string, format: any) { + return this; + } + + // The actual validator + compile(schema: any) { + // Return a function that always says "Valid" (true) + return (data: any) => true; + } + + // Direct validation + validate(schema: any, data: any) { + return true; + } + + // Properties + errors = null; +} + +// Export dummy codegen helpers just in case something imports them. +export const _ = () => {}; +export const str = () => {}; +export const nil = {}; +export const CodeGen = {}; diff --git a/chrome-extension/vite.config.mts b/chrome-extension/vite.config.mts index 12a10f8c..37dc74be 100644 --- a/chrome-extension/vite.config.mts +++ b/chrome-extension/vite.config.mts @@ -17,11 +17,22 @@ export default defineConfig({ }, envPrefix: ['VITE_', 'CEB_'], resolve: { - alias: { - '@root': rootDir, - '@src': srcDir, - '@assets': resolve(srcDir, 'assets'), - }, + // NOTE: Vite only supports RegExp aliases via the array form. + alias: [ + { find: '@root', replacement: rootDir }, + { find: '@src', replacement: srcDir }, + { find: '@assets', replacement: resolve(srcDir, 'assets') }, + // MV3 CSP: ajv-formats can import deep subpaths (e.g. `ajv-formats/dist/formats`). + // Catch all of them and route to a no-op shim. + { find: /^ajv-formats(\/.*)?$/, replacement: resolve(srcDir, 'shims', 'ajv-formats.ts') }, + // MV3 CSP: some dependencies (often via MCP SDK) may pull Ajv which uses `new Function()`. + // Alias all Ajv imports (including subpaths) to a CSP-safe stub to prevent service worker crashes. + // If upstream removes Ajv, this alias becomes a no-op. + // Ajv internal imports used by ajv-formats expect codegen exports like `operators`, `_`, `str`, etc. + { find: 'ajv/dist/compile/codegen', replacement: resolve(srcDir, 'shims', 'ajv-codegen.ts') }, + // Fallback: route any other Ajv entry/subpath to the main Ajv shim. + { find: /^ajv(\/.*)?$/, replacement: resolve(srcDir, 'shims', 'ajv.ts') }, + ], }, plugins: [ libAssetsPlugin({ diff --git a/package.json b/package.json index 2fa66a31..68af17c7 100644 --- a/package.json +++ b/package.json @@ -33,10 +33,12 @@ "prettier": "turbo prettier --continue -- --cache --cache-location node_modules/.cache/.prettiercache", "prepare": "husky", "update-version": "bash bash-scripts/update_version.sh", - "copy_env": "bash bash-scripts/copy_env.sh", - "set-global-env": "bash bash-scripts/set_global_env.sh", + "copy_env": "node scripts/copy-env.mjs", + "set-global-env": "node scripts/set-global-env.mjs", "postinstall": "pnpm build:eslint && pnpm copy_env", "module-manager": "pnpm -F module-manager start" + , + "scan:unsafe-eval": "node scripts/scan-unsafe-eval.mjs dist" }, "dependencies": { "react": "19.1.0", diff --git a/scripts/copy-env.mjs b/scripts/copy-env.mjs new file mode 100644 index 00000000..3a2d5381 --- /dev/null +++ b/scripts/copy-env.mjs @@ -0,0 +1,11 @@ +import fs from 'node:fs'; +import path from 'node:path'; + +const root = process.cwd(); +const envPath = path.join(root, '.env'); +const exampleEnvPath = path.join(root, '.example.env'); + +if (!fs.existsSync(envPath) && fs.existsSync(exampleEnvPath)) { + fs.copyFileSync(exampleEnvPath, envPath); + console.log('.example.env has been copied to .env'); +} diff --git a/scripts/scan-unsafe-eval.mjs b/scripts/scan-unsafe-eval.mjs new file mode 100644 index 00000000..d261d8c3 --- /dev/null +++ b/scripts/scan-unsafe-eval.mjs @@ -0,0 +1,76 @@ +import fs from 'node:fs'; +import path from 'node:path'; + +const roots = process.argv.slice(2); +const targets = roots.length ? roots : ['dist']; + +const needles = [ + { label: 'new Function', re: /\bnew\s+Function\b/ }, + { label: 'eval(', re: /\beval\s*\(/ }, + { label: 'unsafe-eval', re: /unsafe-eval/ }, +]; + +const exts = new Set(['.js', '.mjs', '.cjs', '.html']); + +function walk(dir, out) { + const entries = fs.readdirSync(dir, { withFileTypes: true }); + for (const entry of entries) { + const full = path.join(dir, entry.name); + if (entry.isDirectory()) { + walk(full, out); + } else { + out.push(full); + } + } +} + +function readText(file) { + try { + return fs.readFileSync(file, 'utf8'); + } catch { + return null; + } +} + +let filesScanned = 0; +const hits = []; + +for (const target of targets) { + const abs = path.resolve(process.cwd(), target); + if (!fs.existsSync(abs)) { + continue; + } + + const files = []; + const stat = fs.statSync(abs); + if (stat.isDirectory()) { + walk(abs, files); + } else { + files.push(abs); + } + + for (const file of files) { + if (!exts.has(path.extname(file))) continue; + + const text = readText(file); + if (text == null) continue; + filesScanned++; + + for (const { label, re } of needles) { + const match = re.exec(text); + if (match) { + hits.push({ file, label, index: match.index }); + } + } + } +} + +if (hits.length) { + console.error(`Unsafe-eval patterns detected (${hits.length}):`); + for (const hit of hits) { + console.error(`- ${hit.label}: ${hit.file}`); + } + process.exit(1); +} + +console.log(`OK: no unsafe-eval patterns found (scanned ${filesScanned} files).`); diff --git a/scripts/set-global-env.mjs b/scripts/set-global-env.mjs new file mode 100644 index 00000000..c5a2a4d1 --- /dev/null +++ b/scripts/set-global-env.mjs @@ -0,0 +1,114 @@ +import fs from 'node:fs'; +import path from 'node:path'; + +const root = process.cwd(); +const envPath = path.join(root, '.env'); + +const DEFAULTS = { + CLI_CEB_DEV: 'false', + CLI_CEB_FIREFOX: 'false', +}; + +function isBooleanString(value) { + return value === 'true' || value === 'false'; +} + +function validateKey(key, editableSection) { + if (!key || key.startsWith('#')) return; + if (editableSection) { + if (!key.startsWith('CEB_')) { + throw new Error(`Invalid key: <${key}>. All keys in the editable section must start with 'CEB_'.`); + } + return; + } + if (!key.startsWith('CLI_CEB_')) { + throw new Error(`Invalid key: <${key}>. All CLI keys must start with 'CLI_CEB_'.`); + } +} + +function parseArgs(argv) { + const cliValues = { ...DEFAULTS }; + const extraCliValues = []; + + for (const arg of argv) { + const idx = arg.indexOf('='); + if (idx <= 0) continue; + + const key = arg.slice(0, idx); + const value = arg.slice(idx + 1); + + validateKey(key, false); + + if (key === 'CLI_CEB_DEV' || key === 'CLI_CEB_FIREFOX') { + if (!isBooleanString(value)) { + throw new Error(`Invalid value for <${key}>. Please use 'true' or 'false'.`); + } + cliValues[key] = value; + } else { + extraCliValues.push(`${key}=${value}`); + } + } + + return { cliValues, extraCliValues }; +} + +function readEnvLines() { + if (!fs.existsSync(envPath)) return []; + return fs.readFileSync(envPath, 'utf8').split(/\r?\n/); +} + +function validateExistingEnv(lines) { + for (const line of lines) { + const trimmed = line.trim(); + if (!trimmed || trimmed.startsWith('#')) continue; + const idx = trimmed.indexOf('='); + if (idx <= 0) continue; + const key = trimmed.slice(0, idx); + + if (key.startsWith('CLI_CEB_')) { + validateKey(key, false); + } else if (key.startsWith('CEB_')) { + validateKey(key, true); + } + // allow all other keys (e.g. FIREBASE_*) + } +} + +function stripCliSection(lines) { + const out = []; + let skipCliSection = false; + + for (const line of lines) { + if (line === '# THOSE VALUES ARE EDITABLE ONLY VIA CLI') { + skipCliSection = true; + continue; + } + if (line === '# THOSE VALUES ARE EDITABLE') { + skipCliSection = false; + continue; + } + if (skipCliSection) continue; + if (/^CLI_CEB_/.test(line)) continue; + out.push(line); + } + + return out; +} + +const { cliValues, extraCliValues } = parseArgs(process.argv.slice(2)); +const existing = readEnvLines(); +validateExistingEnv(existing); + +const preserved = stripCliSection(existing); + +const next = [ + '# THOSE VALUES ARE EDITABLE ONLY VIA CLI', + `CLI_CEB_DEV=${cliValues.CLI_CEB_DEV}`, + `CLI_CEB_FIREFOX=${cliValues.CLI_CEB_FIREFOX}`, + ...extraCliValues, + '', + '# THOSE VALUES ARE EDITABLE', + ...preserved, +].join('\n'); + +fs.writeFileSync(envPath, next, 'utf8'); From f2770e46916ec630bd0065486ac2702aa648f8f7 Mon Sep 17 00:00:00 2001 From: Math Shamenson <8126035+insane66613@users.noreply.github.com> Date: Fri, 23 Jan 2026 05:03:15 -0600 Subject: [PATCH 02/19] Update chrome-extension/src/shims/ajv.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- chrome-extension/src/shims/ajv.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/chrome-extension/src/shims/ajv.ts b/chrome-extension/src/shims/ajv.ts index b24855ed..4e4e5491 100644 --- a/chrome-extension/src/shims/ajv.ts +++ b/chrome-extension/src/shims/ajv.ts @@ -28,6 +28,18 @@ export default class Ajv { // The actual validator compile(schema: any) { + // In development, warn that this CSP-safe shim bypasses real validation. + if ( + typeof process !== 'undefined' && + process.env && + process.env.NODE_ENV === 'development' + ) { + // eslint-disable-next-line no-console + console.warn( + '[Ajv shim] Using CSP-compatible Ajv shim: all validations will always pass. ' + + 'This is intended for the Chrome extension environment and may mask schema errors.', + ); + } // Return a function that always says "Valid" (true) return (data: any) => true; } From 13c6962aa386e0e60e2fbb305b6b11f9084cebd2 Mon Sep 17 00:00:00 2001 From: Math Shamenson <8126035+insane66613@users.noreply.github.com> Date: Fri, 23 Jan 2026 05:03:34 -0600 Subject: [PATCH 03/19] Update package.json Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index 68af17c7..3d146aaf 100644 --- a/package.json +++ b/package.json @@ -36,8 +36,7 @@ "copy_env": "node scripts/copy-env.mjs", "set-global-env": "node scripts/set-global-env.mjs", "postinstall": "pnpm build:eslint && pnpm copy_env", - "module-manager": "pnpm -F module-manager start" - , + "module-manager": "pnpm -F module-manager start", "scan:unsafe-eval": "node scripts/scan-unsafe-eval.mjs dist" }, "dependencies": { From 988a0d3d26f0ad7ae29e847a283aed555e69e6f3 Mon Sep 17 00:00:00 2001 From: Math Shamenson <8126035+insane66613@users.noreply.github.com> Date: Fri, 23 Jan 2026 05:03:44 -0600 Subject: [PATCH 04/19] Update chrome-extension/src/shims/ajv-codegen.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- chrome-extension/src/shims/ajv-codegen.ts | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/chrome-extension/src/shims/ajv-codegen.ts b/chrome-extension/src/shims/ajv-codegen.ts index 6ad06a1e..3ce35469 100644 --- a/chrome-extension/src/shims/ajv-codegen.ts +++ b/chrome-extension/src/shims/ajv-codegen.ts @@ -43,7 +43,24 @@ export function and(...parts: unknown[]): string { export function getProperty(prop: unknown): string { if (typeof prop === 'string' && /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(prop)) return `.${prop}`; - const safe = String(prop).replace(/\\/g, '\\\\').replace(/'/g, "\\'"); + const safe = String(prop).replace(/[\\'\n\r\u2028\u2029]/g, (ch) => { + switch (ch) { + case '\\': + return '\\\\'; + case "'": + return "\\'"; + case '\n': + return '\\n'; + case '\r': + return '\\r'; + case '\u2028': + return '\\u2028'; + case '\u2029': + return '\\u2029'; + default: + return ch; + } + }); return `['${safe}']`; } From e0e6658716d6ef7affe54a4213873e7ca37c83fa Mon Sep 17 00:00:00 2001 From: Math Shamenson <8126035+insane66613@users.noreply.github.com> Date: Fri, 6 Mar 2026 19:51:14 -0600 Subject: [PATCH 05/19] Enable manual triggering of build-zip workflow Add workflow_dispatch trigger to build-zip.yml --- .github/workflows/build-zip.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build-zip.yml b/.github/workflows/build-zip.yml index 97312bb4..29e0f525 100644 --- a/.github/workflows/build-zip.yml +++ b/.github/workflows/build-zip.yml @@ -4,6 +4,7 @@ on: push: branches: [ main, dev ] pull_request: + workflow_dispatch: jobs: build: From fc018c8c769fc055d42fe9e48fd13b2b33163659 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 7 Mar 2026 01:53:20 +0000 Subject: [PATCH 06/19] Initial plan From 0b519433726b69feec4777253ec93c33ddb04341 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 7 Mar 2026 01:54:08 +0000 Subject: [PATCH 07/19] chore: bump version to v0.6.1 Co-authored-by: insane66613 <8126035+insane66613@users.noreply.github.com> --- chrome-extension/package.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/chrome-extension/package.json b/chrome-extension/package.json index 1d3e15e8..a69c1865 100644 --- a/chrome-extension/package.json +++ b/chrome-extension/package.json @@ -1,6 +1,6 @@ { "name": "chrome-extension", - "version": "0.6.0", + "version": "0.6.1", "description": "chrome extension - core settings", "type": "module", "private": true, diff --git a/package.json b/package.json index 61348286..ba5a1092 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mcp-superassistant", - "version": "0.6.0", + "version": "0.6.1", "description": "MCP SuperAssistant", "license": "MIT", "private": true, From 1d3cfa4fdb6c5ab40292433ded0616f04e7f1612 Mon Sep 17 00:00:00 2001 From: Math Shamenson <8126035+insane66613@users.noreply.github.com> Date: Fri, 6 Mar 2026 20:16:31 -0600 Subject: [PATCH 08/19] Add GitHub Actions workflow for releasing extension --- .github/workflows/release.yml | 45 +++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..cd4a4e10 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,45 @@ +name: Release Extension + +on: + workflow_dispatch: + +jobs: + release: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install pnpm + uses: pnpm/action-setup@v4 + with: + version: 9 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: 'pnpm' + + - name: Install dependencies + run: pnpm install + + - name: Build extension + run: pnpm build + + - name: Create ZIP package + run: pnpm zip + + - name: Upload Release Artifact + uses: actions/upload-artifact@v4 + with: + name: extension-zip + path: ./chrome-extension/dist/*.zip + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + files: ./chrome-extension/dist/*.zip + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From f806c7b0617dbaa9ea4bdc5053d564ebe1b7c1e1 Mon Sep 17 00:00:00 2001 From: Math Shamenson <8126035+insane66613@users.noreply.github.com> Date: Fri, 6 Mar 2026 20:23:34 -0600 Subject: [PATCH 09/19] Update pnpm version to 9.15.0 in release workflow --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cd4a4e10..13792ed2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,7 +14,7 @@ jobs: - name: Install pnpm uses: pnpm/action-setup@v4 with: - version: 9 + version: 9.15.0 - name: Setup Node uses: actions/setup-node@v4 From 44d5e2440f158946326fcb6a0a56f09a14de3a49 Mon Sep 17 00:00:00 2001 From: Math Shamenson <8126035+insane66613@users.noreply.github.com> Date: Fri, 6 Mar 2026 20:28:16 -0600 Subject: [PATCH 10/19] Update pnpm version to 9.15.1 --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 13792ed2..04a1b180 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,7 +14,7 @@ jobs: - name: Install pnpm uses: pnpm/action-setup@v4 with: - version: 9.15.0 + version: 9.15.1 - name: Setup Node uses: actions/setup-node@v4 From 3d0cef23ef824751806646cfc4f80de860a8b275 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 7 Mar 2026 02:30:43 +0000 Subject: [PATCH 11/19] Initial plan From d9c58426cf216a7587263877c016091442304613 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 7 Mar 2026 02:36:06 +0000 Subject: [PATCH 12/19] Initial plan From 994c299705bf8d949445245498461c05d3447f48 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 7 Mar 2026 02:36:44 +0000 Subject: [PATCH 13/19] Update release workflow to use Node.js 22.12.0 Co-authored-by: insane66613 <8126035+insane66613@users.noreply.github.com> --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 04a1b180..3c929c4e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -19,7 +19,7 @@ jobs: - name: Setup Node uses: actions/setup-node@v4 with: - node-version: 20 + node-version: '22.12.0' cache: 'pnpm' - name: Install dependencies From c81ba75d9f079dd4c759eb99d243b6caa25216f5 Mon Sep 17 00:00:00 2001 From: Math Shamenson <8126035+insane66613@users.noreply.github.com> Date: Fri, 6 Mar 2026 20:50:52 -0600 Subject: [PATCH 14/19] Update upload-artifact and action-gh-release paths in release.yml --- .github/workflows/release.yml | 46 +---------------------------------- 1 file changed, 1 insertion(+), 45 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3c929c4e..f55b6be2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,45 +1 @@ -name: Release Extension - -on: - workflow_dispatch: - -jobs: - release: - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Install pnpm - uses: pnpm/action-setup@v4 - with: - version: 9.15.1 - - - name: Setup Node - uses: actions/setup-node@v4 - with: - node-version: '22.12.0' - cache: 'pnpm' - - - name: Install dependencies - run: pnpm install - - - name: Build extension - run: pnpm build - - - name: Create ZIP package - run: pnpm zip - - - name: Upload Release Artifact - uses: actions/upload-artifact@v4 - with: - name: extension-zip - path: ./chrome-extension/dist/*.zip - - - name: Create GitHub Release - uses: softprops/action-gh-release@v2 - with: - files: ./chrome-extension/dist/*.zip - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} +name: Release\n\non: [push]\n\njobs:\n release:\n runs-on: ubuntu-latest\n steps:\n - name: Checkout code\n uses: actions/checkout@v2\n - name: Install dependencies\n run: npm install\n - name: Build application\n run: npm run build\n - name: Upload artifacts\n uses: actions/upload-artifact@v2\n with:\n name: zip-files\n path: ./dist-zip/*.zip\n - name: Release\n uses: softprops/action-gh-release@v1\n with:\n files: ./dist-zip/*.zip\n - name: Notify Release\n run: echo 'Release completed!'\n \ No newline at end of file From 55b6a52c7794780ebdcb330ed24e350ed002c925 Mon Sep 17 00:00:00 2001 From: Math Shamenson <8126035+insane66613@users.noreply.github.com> Date: Fri, 6 Mar 2026 20:55:03 -0600 Subject: [PATCH 15/19] Update release workflow for version tagging and pnpm --- .github/workflows/release.yml | 56 ++++++++++++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f55b6be2..2565a6e7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1 +1,55 @@ -name: Release\n\non: [push]\n\njobs:\n release:\n runs-on: ubuntu-latest\n steps:\n - name: Checkout code\n uses: actions/checkout@v2\n - name: Install dependencies\n run: npm install\n - name: Build application\n run: npm run build\n - name: Upload artifacts\n uses: actions/upload-artifact@v2\n with:\n name: zip-files\n path: ./dist-zip/*.zip\n - name: Release\n uses: softprops/action-gh-release@v1\n with:\n files: ./dist-zip/*.zip\n - name: Notify Release\n run: echo 'Release completed!'\n \ No newline at end of file +name: Release Extension + +on: + push: + tags: + - "v*" + workflow_dispatch: + inputs: + tag: + description: "Tag to release (e.g. v0.6.1)" + required: true + type: string + +jobs: + release: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Install pnpm + uses: pnpm/action-setup@v4 + with: + version: 9.15.1 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: "22.12.0" + cache: "pnpm" + + - name: Install dependencies + run: pnpm install + + - name: Build extension + run: pnpm build + + - name: Create ZIP package + run: pnpm zip + + - name: Upload Release Artifact + uses: actions/upload-artifact@v4 + with: + name: extension-zip + path: ./dist-zip/*.zip + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ github.event.inputs.tag || github.ref_name }} + files: ./dist-zip/*.zip + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From a51be3c2fa7e75b5aa91fbd0a8424ae46a781778 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 7 Mar 2026 03:00:43 +0000 Subject: [PATCH 16/19] Initial plan From 143178e7a0a1b7c9607758210be622c97b066392 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 7 Mar 2026 03:02:07 +0000 Subject: [PATCH 17/19] Add icon-16.png and remove from .gitignore Co-authored-by: insane66613 <8126035+insane66613@users.noreply.github.com> --- .gitignore | 1 - chrome-extension/public/icon-16.png | Bin 0 -> 327 bytes 2 files changed, 1 deletion(-) create mode 100644 chrome-extension/public/icon-16.png diff --git a/.gitignore b/.gitignore index 8abad74a..ccff6ca6 100644 --- a/.gitignore +++ b/.gitignore @@ -79,7 +79,6 @@ MCP_COMMUNICATION_FIXES.md SESSION_10_COMPLETE.md TEST_MCP_TOGGLE.md websocket doc.md -chrome-extension/public/icon-16.png chrome-extension/public/icon-34 copy.png chrome-extension/public/icon-34.png chrome-extension/src/mcpclient/architecture-option-3-plugin-based.md diff --git a/chrome-extension/public/icon-16.png b/chrome-extension/public/icon-16.png new file mode 100644 index 0000000000000000000000000000000000000000..6161fa79f1510d4c90237e0c05ef9b4d237e0160 GIT binary patch literal 327 zcmV-N0l5B&P)<&q68znK+6f!Z17wxi}&kgSD37@S6LFJH8GF zrqf3ji|73BYzL}}>$)V#h?muh*Ug6U_y)&uP(9vw0bq Date: Sat, 7 Mar 2026 03:06:03 +0000 Subject: [PATCH 18/19] Initial plan From 1a95de7f4ae5334130f56b034267926104ebd4eb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 7 Mar 2026 03:14:56 +0000 Subject: [PATCH 19/19] Fix: unload policy violation, SSE error logging/categorisation, ResizeObserver loop Co-authored-by: insane66613 <8126035+insane66613@users.noreply.github.com> --- chrome-extension/src/background/index.ts | 2 ++ chrome-extension/src/mcpclient/index.ts | 2 +- pages/content/src/index.ts | 4 ++- .../src/utils/themeDetector.ts | 35 ++++++++++++------- 4 files changed, 28 insertions(+), 15 deletions(-) diff --git a/chrome-extension/src/background/index.ts b/chrome-extension/src/background/index.ts index 65c2ca08..37a3c6c5 100644 --- a/chrome-extension/src/background/index.ts +++ b/chrome-extension/src/background/index.ts @@ -205,6 +205,8 @@ function categorizeToolError(error: Error): { isConnectionError: boolean; isTool /connection failed/i, /transport error/i, /fetch failed/i, + /failed to fetch/i, + /sse error/i, ]; // Check tool errors first (highest priority) diff --git a/chrome-extension/src/mcpclient/index.ts b/chrome-extension/src/mcpclient/index.ts index 6280482d..5dfb2160 100644 --- a/chrome-extension/src/mcpclient/index.ts +++ b/chrome-extension/src/mcpclient/index.ts @@ -111,7 +111,7 @@ function setupGlobalClientEventListeners(client: McpClient): void { }); client.on('client:error', (event) => { - logger.error('[Global Client] Client error:', event); + logger.error('[Global Client] Client error:', event.error); }); } diff --git a/pages/content/src/index.ts b/pages/content/src/index.ts index cf8a9e04..465b1131 100644 --- a/pages/content/src/index.ts +++ b/pages/content/src/index.ts @@ -89,7 +89,9 @@ function setupSidebarRecovery(): void { }, 1000); // Check every second // Clean up when navigating away - window.addEventListener('unload', () => { + // Using 'pagehide' instead of 'unload' to comply with the Permissions-Policy that + // disallows 'unload' handlers (they prevent bfcache usage in modern browsers). + window.addEventListener('pagehide', () => { clearInterval(recoveryInterval); }); diff --git a/pages/content/src/render_prescript/src/utils/themeDetector.ts b/pages/content/src/render_prescript/src/utils/themeDetector.ts index db11daa1..3af23cf8 100644 --- a/pages/content/src/render_prescript/src/utils/themeDetector.ts +++ b/pages/content/src/render_prescript/src/utils/themeDetector.ts @@ -1051,20 +1051,29 @@ export function startThemeMonitoring(): void { let lastBgColor = lastComputedStyle.backgroundColor; let lastTextColor = lastComputedStyle.color; + let rafPending = false; const variableObserver = new ResizeObserver(() => { - const currentStyle = window.getComputedStyle(themeVariableWatcher); - const currentBgColor = currentStyle.backgroundColor; - const currentTextColor = currentStyle.color; - - if (currentBgColor !== lastBgColor || currentTextColor !== lastTextColor) { - logThemeDetection('CSS variable change detected', { - bgColor: { old: lastBgColor, new: currentBgColor }, - textColor: { old: lastTextColor, new: currentTextColor }, - }); - lastBgColor = currentBgColor; - lastTextColor = currentTextColor; - checkThemeChange(); - } + // Coalesce multiple rapid resize notifications into a single deferred DOM + // read to avoid "ResizeObserver loop completed with undelivered notifications" + // errors while still capturing the latest theme state. + if (rafPending) return; + rafPending = true; + requestAnimationFrame(() => { + rafPending = false; + const currentStyle = window.getComputedStyle(themeVariableWatcher); + const currentBgColor = currentStyle.backgroundColor; + const currentTextColor = currentStyle.color; + + if (currentBgColor !== lastBgColor || currentTextColor !== lastTextColor) { + logThemeDetection('CSS variable change detected', { + bgColor: { old: lastBgColor, new: currentBgColor }, + textColor: { old: lastTextColor, new: currentTextColor }, + }); + lastBgColor = currentBgColor; + lastTextColor = currentTextColor; + checkThemeChange(); + } + }); }); // Trigger observation by changing a property