baa-conductor


commit
818272a
parent
9674c30
author
im_wower
date
2026-04-02 17:49:36 +0800 CST
feat: scaffold safari extension host shell
32 files changed,  +2149, -0
Raw patch view.
   1diff --git a/plugins/baa-safari/README.md b/plugins/baa-safari/README.md
   2new file mode 100644
   3index 0000000000000000000000000000000000000000..71d3d52756def3e4ed0c75a100ed527a1f95045f
   4--- /dev/null
   5+++ b/plugins/baa-safari/README.md
   6@@ -0,0 +1,69 @@
   7+# BAA Safari
   8+
   9+`plugins/baa-safari/` 是 Safari Web Extension 的 Phase 1 基础壳。
  10+
  11+当前这一版只解决三件事:
  12+
  13+- 固定 Safari 插件目录和文件边界
  14+- 固定最小 Xcode 宿主 App / Extension target 命名
  15+- 固定 runtime 归属合同,避免后续把长期状态 owner 放错到 `background.js`
  16+
  17+## Runtime 合同
  18+
  19+Safari 首版明确保留 `controller.html + controller.js` 作为长期 runtime owner 的正式选项。
  20+
  21+- `controller.html`
  22+  预留为 WS、状态机、壳页路由和调试视图的长期容器
  23+- `background.js`
  24+  只负责 bootstrap、打开/聚焦控制页、汇总轻量占位状态
  25+- `content-script.js`
  26+  只负责页面入口和注入 `page-interceptor.js`
  27+- `page-interceptor.js`
  28+  当前仅保留页面 world 占位钩子,不提前实现 final-message / proxy_delivery
  29+
  30+这和 Firefox 当前真实结构保持一致:不要把 `background.js` 误当成主 runtime。
  31+
  32+## 目录
  33+
  34+- [manifest.json](/Users/george/code/baa-conductor/plugins/baa-safari/manifest.json)
  35+  Safari Web Extension 基础声明,首版只覆盖 Claude / ChatGPT / Gemini
  36+- [controller.html](/Users/george/code/baa-conductor/plugins/baa-safari/controller.html)
  37+  控制页容器,明确 runtime owner 合同和启用步骤
  38+- [controller.js](/Users/george/code/baa-conductor/plugins/baa-safari/controller.js)
  39+  控制页最小状态渲染
  40+- [controller.css](/Users/george/code/baa-conductor/plugins/baa-safari/controller.css)
  41+  控制页样式
  42+- [background.js](/Users/george/code/baa-conductor/plugins/baa-safari/background.js)
  43+  bootstrap / tab orchestration / placeholder state fan-out
  44+- [content-script.js](/Users/george/code/baa-conductor/plugins/baa-safari/content-script.js)
  45+  AI 页面入口,占位注入 `page-interceptor.js`
  46+- [page-interceptor.js](/Users/george/code/baa-conductor/plugins/baa-safari/page-interceptor.js)
  47+  页面 world 占位钩子
  48+- [baa-safari-host](/Users/george/code/baa-conductor/plugins/baa-safari/baa-safari-host)
  49+  Xcode 宿主 App 与 Safari Extension target
  50+
  51+## 本地构建
  52+
  53+```bash
  54+cd /Users/george/code/baa-conductor
  55+xcodebuild -project plugins/baa-safari/baa-safari-host/BAASafariHost.xcodeproj -scheme BAASafariHost -configuration Debug build
  56+```
  57+
  58+## 首次启用
  59+
  60+1. 先构建 [BAASafariHost.xcodeproj](/Users/george/code/baa-conductor/plugins/baa-safari/baa-safari-host/BAASafariHost.xcodeproj)。
  61+2. 运行 `BAASafariHost.app`。
  62+3. 打开 Safari。
  63+4. 进入 `Safari -> Settings -> Extensions`。
  64+5. 启用 `BAA Safari Extension`。
  65+6. 在 `Website Access` 里给 `claude.ai`、`chatgpt.com` / `chat.openai.com`、`gemini.google.com` 授权。
  66+7. 点击 Safari 工具栏里的扩展按钮,让 `background.js` 打开 [controller.html](/Users/george/code/baa-conductor/plugins/baa-safari/controller.html)。
  67+
  68+## 当前不做
  69+
  70+- 不在这一步接 WS
  71+- 不在这一步接 final-message
  72+- 不在这一步接 proxy_delivery
  73+- 不在这一步改 conductor 后端
  74+
  75+后续任务会在这个稳定骨架上继续接线。
  76diff --git a/plugins/baa-safari/baa-safari-host/BAASafariHost Extension/BAASafariHost_Extension.entitlements b/plugins/baa-safari/baa-safari-host/BAASafariHost Extension/BAASafariHost_Extension.entitlements
  77new file mode 100644
  78index 0000000000000000000000000000000000000000..f2ef3ae0265b40c475e8ef90e3a311c31786c594
  79--- /dev/null
  80+++ b/plugins/baa-safari/baa-safari-host/BAASafariHost Extension/BAASafariHost_Extension.entitlements	
  81@@ -0,0 +1,10 @@
  82+<?xml version="1.0" encoding="UTF-8"?>
  83+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
  84+<plist version="1.0">
  85+<dict>
  86+    <key>com.apple.security.app-sandbox</key>
  87+    <true/>
  88+    <key>com.apple.security.files.user-selected.read-only</key>
  89+    <true/>
  90+</dict>
  91+</plist>
  92diff --git a/plugins/baa-safari/baa-safari-host/BAASafariHost Extension/Info.plist b/plugins/baa-safari/baa-safari-host/BAASafariHost Extension/Info.plist
  93new file mode 100644
  94index 0000000000000000000000000000000000000000..9ee504dc51f3d3c54be0fb0038a89b13b2250107
  95--- /dev/null
  96+++ b/plugins/baa-safari/baa-safari-host/BAASafariHost Extension/Info.plist	
  97@@ -0,0 +1,13 @@
  98+<?xml version="1.0" encoding="UTF-8"?>
  99+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
 100+<plist version="1.0">
 101+<dict>
 102+	<key>NSExtension</key>
 103+	<dict>
 104+		<key>NSExtensionPointIdentifier</key>
 105+		<string>com.apple.Safari.web-extension</string>
 106+		<key>NSExtensionPrincipalClass</key>
 107+		<string>$(PRODUCT_MODULE_NAME).SafariWebExtensionHandler</string>
 108+	</dict>
 109+</dict>
 110+</plist>
 111diff --git a/plugins/baa-safari/baa-safari-host/BAASafariHost Extension/SafariWebExtensionHandler.swift b/plugins/baa-safari/baa-safari-host/BAASafariHost Extension/SafariWebExtensionHandler.swift
 112new file mode 100644
 113index 0000000000000000000000000000000000000000..12ffd9f1016a8d8b23f75a9851d70af46ece0569
 114--- /dev/null
 115+++ b/plugins/baa-safari/baa-safari-host/BAASafariHost Extension/SafariWebExtensionHandler.swift	
 116@@ -0,0 +1,42 @@
 117+//
 118+//  SafariWebExtensionHandler.swift
 119+//  BAASafariHost Extension
 120+//
 121+//  Created by george on 2026/4/2.
 122+//
 123+
 124+import SafariServices
 125+import os.log
 126+
 127+class SafariWebExtensionHandler: NSObject, NSExtensionRequestHandling {
 128+
 129+    func beginRequest(with context: NSExtensionContext) {
 130+        let request = context.inputItems.first as? NSExtensionItem
 131+
 132+        let profile: UUID?
 133+        if #available(iOS 17.0, macOS 14.0, *) {
 134+            profile = request?.userInfo?[SFExtensionProfileKey] as? UUID
 135+        } else {
 136+            profile = request?.userInfo?["profile"] as? UUID
 137+        }
 138+
 139+        let message: Any?
 140+        if #available(iOS 15.0, macOS 11.0, *) {
 141+            message = request?.userInfo?[SFExtensionMessageKey]
 142+        } else {
 143+            message = request?.userInfo?["message"]
 144+        }
 145+
 146+        os_log(.default, "Received message from browser.runtime.sendNativeMessage: %@ (profile: %@)", String(describing: message), profile?.uuidString ?? "none")
 147+
 148+        let response = NSExtensionItem()
 149+        if #available(iOS 15.0, macOS 11.0, *) {
 150+            response.userInfo = [ SFExtensionMessageKey: [ "echo": message ] ]
 151+        } else {
 152+            response.userInfo = [ "message": [ "echo": message ] ]
 153+        }
 154+
 155+        context.completeRequest(returningItems: [ response ], completionHandler: nil)
 156+    }
 157+
 158+}
 159diff --git a/plugins/baa-safari/baa-safari-host/BAASafariHost.xcodeproj/project.pbxproj b/plugins/baa-safari/baa-safari-host/BAASafariHost.xcodeproj/project.pbxproj
 160new file mode 100644
 161index 0000000000000000000000000000000000000000..215d14bccf2d7171c2b6a4f76924f7bc65631ec4
 162--- /dev/null
 163+++ b/plugins/baa-safari/baa-safari-host/BAASafariHost.xcodeproj/project.pbxproj
 164@@ -0,0 +1,853 @@
 165+// !$*UTF8*$!
 166+{
 167+	archiveVersion = 1;
 168+	classes = {
 169+	};
 170+	objectVersion = 77;
 171+	objects = {
 172+
 173+/* Begin PBXBuildFile section */
 174+		5EF028BD2F7E7177006A58E2 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EF028BC2F7E7177006A58E2 /* AppDelegate.swift */; };
 175+		5EF028C12F7E7177006A58E2 /* Main.html in Resources */ = {isa = PBXBuildFile; fileRef = 5EF028BF2F7E7177006A58E2 /* Main.html */; };
 176+		5EF028C32F7E7177006A58E2 /* Icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 5EF028C22F7E7177006A58E2 /* Icon.png */; };
 177+		5EF028C52F7E7177006A58E2 /* Style.css in Resources */ = {isa = PBXBuildFile; fileRef = 5EF028C42F7E7177006A58E2 /* Style.css */; };
 178+		5EF028C72F7E7177006A58E2 /* Script.js in Resources */ = {isa = PBXBuildFile; fileRef = 5EF028C62F7E7177006A58E2 /* Script.js */; };
 179+		5EF028C92F7E7177006A58E2 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EF028C82F7E7177006A58E2 /* ViewController.swift */; };
 180+		5EF028CC2F7E7177006A58E2 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5EF028CA2F7E7177006A58E2 /* Main.storyboard */; };
 181+		5EF028CE2F7E7177006A58E2 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5EF028CD2F7E7177006A58E2 /* Assets.xcassets */; };
 182+		5EF028DB2F7E7177006A58E2 /* BAASafariHostTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EF028DA2F7E7177006A58E2 /* BAASafariHostTests.swift */; };
 183+		5EF028E52F7E7177006A58E2 /* BAASafariHostUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EF028E42F7E7177006A58E2 /* BAASafariHostUITests.swift */; };
 184+		5EF028E72F7E7177006A58E2 /* BAASafariHostUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EF028E62F7E7177006A58E2 /* BAASafariHostUITestsLaunchTests.swift */; };
 185+		5EF028ED2F7E7177006A58E2 /* BAASafariHost Extension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 5EF028EC2F7E7177006A58E2 /* BAASafariHost Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
 186+		5EF028F22F7E7177006A58E2 /* SafariWebExtensionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EF028F12F7E7177006A58E2 /* SafariWebExtensionHandler.swift */; };
 187+		5EF0290E2F7E7178006A58E2 /* background.js in Resources */ = {isa = PBXBuildFile; fileRef = 5EF029052F7E7178006A58E2 /* background.js */; };
 188+		5EF0290F2F7E7178006A58E2 /* controller.css in Resources */ = {isa = PBXBuildFile; fileRef = 5EF029062F7E7178006A58E2 /* controller.css */; };
 189+		5EF029102F7E7178006A58E2 /* controller.js in Resources */ = {isa = PBXBuildFile; fileRef = 5EF029072F7E7178006A58E2 /* controller.js */; };
 190+		5EF029112F7E7178006A58E2 /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = 5EF029082F7E7178006A58E2 /* README.md */; };
 191+		5EF029122F7E7178006A58E2 /* controller.html in Resources */ = {isa = PBXBuildFile; fileRef = 5EF029092F7E7178006A58E2 /* controller.html */; };
 192+		5EF029132F7E7178006A58E2 /* manifest.json in Resources */ = {isa = PBXBuildFile; fileRef = 5EF0290A2F7E7178006A58E2 /* manifest.json */; };
 193+		5EF029142F7E7178006A58E2 /* content-script.js in Resources */ = {isa = PBXBuildFile; fileRef = 5EF0290B2F7E7178006A58E2 /* content-script.js */; };
 194+		5EF029162F7E7178006A58E2 /* page-interceptor.js in Resources */ = {isa = PBXBuildFile; fileRef = 5EF0290D2F7E7178006A58E2 /* page-interceptor.js */; };
 195+		5EF029172F7E7178006A58E2 /* 48.png in Resources */ = {isa = PBXBuildFile; fileRef = 5EF029182F7E7178006A58E2 /* 48.png */; };
 196+		5EF029192F7E7178006A58E2 /* 96.png in Resources */ = {isa = PBXBuildFile; fileRef = 5EF0291A2F7E7178006A58E2 /* 96.png */; };
 197+		5EF0291B2F7E7178006A58E2 /* 128.png in Resources */ = {isa = PBXBuildFile; fileRef = 5EF0291C2F7E7178006A58E2 /* 128.png */; };
 198+/* End PBXBuildFile section */
 199+
 200+/* Begin PBXContainerItemProxy section */
 201+		5EF028D72F7E7177006A58E2 /* PBXContainerItemProxy */ = {
 202+			isa = PBXContainerItemProxy;
 203+			containerPortal = 5EF028B12F7E7177006A58E2 /* Project object */;
 204+			proxyType = 1;
 205+			remoteGlobalIDString = 5EF028B82F7E7177006A58E2;
 206+			remoteInfo = BAASafariHost;
 207+		};
 208+		5EF028E12F7E7177006A58E2 /* PBXContainerItemProxy */ = {
 209+			isa = PBXContainerItemProxy;
 210+			containerPortal = 5EF028B12F7E7177006A58E2 /* Project object */;
 211+			proxyType = 1;
 212+			remoteGlobalIDString = 5EF028B82F7E7177006A58E2;
 213+			remoteInfo = BAASafariHost;
 214+		};
 215+		5EF028EE2F7E7177006A58E2 /* PBXContainerItemProxy */ = {
 216+			isa = PBXContainerItemProxy;
 217+			containerPortal = 5EF028B12F7E7177006A58E2 /* Project object */;
 218+			proxyType = 1;
 219+			remoteGlobalIDString = 5EF028EB2F7E7177006A58E2;
 220+			remoteInfo = "BAASafariHost Extension";
 221+		};
 222+/* End PBXContainerItemProxy section */
 223+
 224+/* Begin PBXCopyFilesBuildPhase section */
 225+		5EF028FA2F7E7177006A58E2 /* Embed Foundation Extensions */ = {
 226+			isa = PBXCopyFilesBuildPhase;
 227+			buildActionMask = 2147483647;
 228+			dstPath = "";
 229+			dstSubfolderSpec = 13;
 230+			files = (
 231+				5EF028ED2F7E7177006A58E2 /* BAASafariHost Extension.appex in Embed Foundation Extensions */,
 232+			);
 233+			name = "Embed Foundation Extensions";
 234+			runOnlyForDeploymentPostprocessing = 0;
 235+		};
 236+/* End PBXCopyFilesBuildPhase section */
 237+
 238+/* Begin PBXFileReference section */
 239+		5EF028B92F7E7177006A58E2 /* BAASafariHost.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = BAASafariHost.app; sourceTree = BUILT_PRODUCTS_DIR; };
 240+		5EF028BC2F7E7177006A58E2 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
 241+		5EF028C02F7E7177006A58E2 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.html; name = Base; path = Base.lproj/Main.html; sourceTree = "<group>"; };
 242+		5EF028C22F7E7177006A58E2 /* Icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Icon.png; sourceTree = "<group>"; };
 243+		5EF028C42F7E7177006A58E2 /* Style.css */ = {isa = PBXFileReference; lastKnownFileType = text.css; path = Style.css; sourceTree = "<group>"; };
 244+		5EF028C62F7E7177006A58E2 /* Script.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = Script.js; sourceTree = "<group>"; };
 245+		5EF028C82F7E7177006A58E2 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
 246+		5EF028CB2F7E7177006A58E2 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
 247+		5EF028CD2F7E7177006A58E2 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
 248+		5EF028CF2F7E7177006A58E2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
 249+		5EF028D02F7E7177006A58E2 /* BAASafariHost.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = BAASafariHost.entitlements; sourceTree = "<group>"; };
 250+		5EF028D12F7E7177006A58E2 /* BAASafariHost.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = BAASafariHost.entitlements; sourceTree = "<group>"; };
 251+		5EF028D62F7E7177006A58E2 /* BAASafariHostTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BAASafariHostTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
 252+		5EF028DA2F7E7177006A58E2 /* BAASafariHostTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BAASafariHostTests.swift; sourceTree = "<group>"; };
 253+		5EF028E02F7E7177006A58E2 /* BAASafariHostUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BAASafariHostUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
 254+		5EF028E42F7E7177006A58E2 /* BAASafariHostUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BAASafariHostUITests.swift; sourceTree = "<group>"; };
 255+		5EF028E62F7E7177006A58E2 /* BAASafariHostUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BAASafariHostUITestsLaunchTests.swift; sourceTree = "<group>"; };
 256+		5EF028EC2F7E7177006A58E2 /* BAASafariHost Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "BAASafariHost Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
 257+		5EF028F12F7E7177006A58E2 /* SafariWebExtensionHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariWebExtensionHandler.swift; sourceTree = "<group>"; };
 258+		5EF028F32F7E7177006A58E2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
 259+		5EF028F42F7E7177006A58E2 /* BAASafariHost_Extension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = BAASafariHost_Extension.entitlements; sourceTree = "<group>"; };
 260+		5EF029052F7E7178006A58E2 /* background.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; name = background.js; path = ../../background.js; sourceTree = "<group>"; };
 261+		5EF029062F7E7178006A58E2 /* controller.css */ = {isa = PBXFileReference; lastKnownFileType = text.css; name = controller.css; path = ../../controller.css; sourceTree = "<group>"; };
 262+		5EF029072F7E7178006A58E2 /* controller.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; name = controller.js; path = ../../controller.js; sourceTree = "<group>"; };
 263+		5EF029082F7E7178006A58E2 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../../README.md; sourceTree = "<group>"; };
 264+		5EF029092F7E7178006A58E2 /* controller.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; name = controller.html; path = ../../controller.html; sourceTree = "<group>"; };
 265+		5EF0290A2F7E7178006A58E2 /* manifest.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; name = manifest.json; path = ../../manifest.json; sourceTree = "<group>"; };
 266+		5EF0290B2F7E7178006A58E2 /* content-script.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; name = "content-script.js"; path = "../../content-script.js"; sourceTree = "<group>"; };
 267+		5EF0290D2F7E7178006A58E2 /* page-interceptor.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; name = "page-interceptor.js"; path = "../../page-interceptor.js"; sourceTree = "<group>"; };
 268+		5EF029182F7E7178006A58E2 /* 48.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = 48.png; path = ../../icons/48.png; sourceTree = "<group>"; };
 269+		5EF0291A2F7E7178006A58E2 /* 96.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = 96.png; path = ../../icons/96.png; sourceTree = "<group>"; };
 270+		5EF0291C2F7E7178006A58E2 /* 128.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = 128.png; path = ../../icons/128.png; sourceTree = "<group>"; };
 271+/* End PBXFileReference section */
 272+
 273+/* Begin PBXFrameworksBuildPhase section */
 274+		5EF028B62F7E7177006A58E2 /* Frameworks */ = {
 275+			isa = PBXFrameworksBuildPhase;
 276+			buildActionMask = 2147483647;
 277+			files = (
 278+			);
 279+			runOnlyForDeploymentPostprocessing = 0;
 280+		};
 281+		5EF028D32F7E7177006A58E2 /* Frameworks */ = {
 282+			isa = PBXFrameworksBuildPhase;
 283+			buildActionMask = 2147483647;
 284+			files = (
 285+			);
 286+			runOnlyForDeploymentPostprocessing = 0;
 287+		};
 288+		5EF028DD2F7E7177006A58E2 /* Frameworks */ = {
 289+			isa = PBXFrameworksBuildPhase;
 290+			buildActionMask = 2147483647;
 291+			files = (
 292+			);
 293+			runOnlyForDeploymentPostprocessing = 0;
 294+		};
 295+		5EF028E92F7E7177006A58E2 /* Frameworks */ = {
 296+			isa = PBXFrameworksBuildPhase;
 297+			buildActionMask = 2147483647;
 298+			files = (
 299+			);
 300+			runOnlyForDeploymentPostprocessing = 0;
 301+		};
 302+/* End PBXFrameworksBuildPhase section */
 303+
 304+/* Begin PBXGroup section */
 305+		5EF028B02F7E7177006A58E2 = {
 306+			isa = PBXGroup;
 307+			children = (
 308+				5EF028BB2F7E7177006A58E2 /* BAASafariHost */,
 309+				5EF028D92F7E7177006A58E2 /* BAASafariHostTests */,
 310+				5EF028E32F7E7177006A58E2 /* BAASafariHostUITests */,
 311+				5EF028F02F7E7177006A58E2 /* BAASafariHost Extension */,
 312+				5EF028BA2F7E7177006A58E2 /* Products */,
 313+			);
 314+			sourceTree = "<group>";
 315+		};
 316+		5EF028BA2F7E7177006A58E2 /* Products */ = {
 317+			isa = PBXGroup;
 318+			children = (
 319+				5EF028B92F7E7177006A58E2 /* BAASafariHost.app */,
 320+				5EF028D62F7E7177006A58E2 /* BAASafariHostTests.xctest */,
 321+				5EF028E02F7E7177006A58E2 /* BAASafariHostUITests.xctest */,
 322+				5EF028EC2F7E7177006A58E2 /* BAASafariHost Extension.appex */,
 323+			);
 324+			name = Products;
 325+			sourceTree = "<group>";
 326+		};
 327+		5EF028BB2F7E7177006A58E2 /* BAASafariHost */ = {
 328+			isa = PBXGroup;
 329+			children = (
 330+				5EF028BC2F7E7177006A58E2 /* AppDelegate.swift */,
 331+				5EF028C82F7E7177006A58E2 /* ViewController.swift */,
 332+				5EF028CA2F7E7177006A58E2 /* Main.storyboard */,
 333+				5EF028CD2F7E7177006A58E2 /* Assets.xcassets */,
 334+				5EF028CF2F7E7177006A58E2 /* Info.plist */,
 335+				5EF028D02F7E7177006A58E2 /* BAASafariHost.entitlements */,
 336+				5EF028D12F7E7177006A58E2 /* BAASafariHost.entitlements */,
 337+				5EF028BE2F7E7177006A58E2 /* Resources */,
 338+			);
 339+			path = BAASafariHost;
 340+			sourceTree = "<group>";
 341+		};
 342+		5EF028BE2F7E7177006A58E2 /* Resources */ = {
 343+			isa = PBXGroup;
 344+			children = (
 345+				5EF028BF2F7E7177006A58E2 /* Main.html */,
 346+				5EF028C22F7E7177006A58E2 /* Icon.png */,
 347+				5EF028C42F7E7177006A58E2 /* Style.css */,
 348+				5EF028C62F7E7177006A58E2 /* Script.js */,
 349+			);
 350+			path = Resources;
 351+			sourceTree = "<group>";
 352+		};
 353+		5EF028D92F7E7177006A58E2 /* BAASafariHostTests */ = {
 354+			isa = PBXGroup;
 355+			children = (
 356+				5EF028DA2F7E7177006A58E2 /* BAASafariHostTests.swift */,
 357+			);
 358+			path = BAASafariHostTests;
 359+			sourceTree = "<group>";
 360+		};
 361+		5EF028E32F7E7177006A58E2 /* BAASafariHostUITests */ = {
 362+			isa = PBXGroup;
 363+			children = (
 364+				5EF028E42F7E7177006A58E2 /* BAASafariHostUITests.swift */,
 365+				5EF028E62F7E7177006A58E2 /* BAASafariHostUITestsLaunchTests.swift */,
 366+			);
 367+			path = BAASafariHostUITests;
 368+			sourceTree = "<group>";
 369+		};
 370+		5EF028F02F7E7177006A58E2 /* BAASafariHost Extension */ = {
 371+			isa = PBXGroup;
 372+			children = (
 373+				5EF029042F7E7178006A58E2 /* Resources */,
 374+				5EF028F12F7E7177006A58E2 /* SafariWebExtensionHandler.swift */,
 375+				5EF028F32F7E7177006A58E2 /* Info.plist */,
 376+				5EF028F42F7E7177006A58E2 /* BAASafariHost_Extension.entitlements */,
 377+			);
 378+			path = "BAASafariHost Extension";
 379+			sourceTree = "<group>";
 380+		};
 381+		5EF029042F7E7178006A58E2 /* Resources */ = {
 382+			isa = PBXGroup;
 383+			children = (
 384+				5EF029052F7E7178006A58E2 /* background.js */,
 385+				5EF029062F7E7178006A58E2 /* controller.css */,
 386+				5EF029072F7E7178006A58E2 /* controller.js */,
 387+				5EF029082F7E7178006A58E2 /* README.md */,
 388+				5EF029092F7E7178006A58E2 /* controller.html */,
 389+				5EF0290A2F7E7178006A58E2 /* manifest.json */,
 390+				5EF0290B2F7E7178006A58E2 /* content-script.js */,
 391+				5EF0290D2F7E7178006A58E2 /* page-interceptor.js */,
 392+				5EF029182F7E7178006A58E2 /* 48.png */,
 393+				5EF0291A2F7E7178006A58E2 /* 96.png */,
 394+				5EF0291C2F7E7178006A58E2 /* 128.png */,
 395+			);
 396+			name = Resources;
 397+			path = "BAASafariHost Extension";
 398+			sourceTree = SOURCE_ROOT;
 399+		};
 400+/* End PBXGroup section */
 401+
 402+/* Begin PBXNativeTarget section */
 403+		5EF028B82F7E7177006A58E2 /* BAASafariHost */ = {
 404+			isa = PBXNativeTarget;
 405+			buildConfigurationList = 5EF028FB2F7E7177006A58E2 /* Build configuration list for PBXNativeTarget "BAASafariHost" */;
 406+			buildPhases = (
 407+				5EF028B52F7E7177006A58E2 /* Sources */,
 408+				5EF028B62F7E7177006A58E2 /* Frameworks */,
 409+				5EF028B72F7E7177006A58E2 /* Resources */,
 410+				5EF028FA2F7E7177006A58E2 /* Embed Foundation Extensions */,
 411+			);
 412+			buildRules = (
 413+			);
 414+			dependencies = (
 415+				5EF028EF2F7E7177006A58E2 /* PBXTargetDependency */,
 416+			);
 417+			name = BAASafariHost;
 418+			packageProductDependencies = (
 419+			);
 420+			productName = BAASafariHost;
 421+			productReference = 5EF028B92F7E7177006A58E2 /* BAASafariHost.app */;
 422+			productType = "com.apple.product-type.application";
 423+		};
 424+		5EF028D52F7E7177006A58E2 /* BAASafariHostTests */ = {
 425+			isa = PBXNativeTarget;
 426+			buildConfigurationList = 5EF028FE2F7E7177006A58E2 /* Build configuration list for PBXNativeTarget "BAASafariHostTests" */;
 427+			buildPhases = (
 428+				5EF028D22F7E7177006A58E2 /* Sources */,
 429+				5EF028D32F7E7177006A58E2 /* Frameworks */,
 430+				5EF028D42F7E7177006A58E2 /* Resources */,
 431+			);
 432+			buildRules = (
 433+			);
 434+			dependencies = (
 435+				5EF028D82F7E7177006A58E2 /* PBXTargetDependency */,
 436+			);
 437+			name = BAASafariHostTests;
 438+			packageProductDependencies = (
 439+			);
 440+			productName = BAASafariHostTests;
 441+			productReference = 5EF028D62F7E7177006A58E2 /* BAASafariHostTests.xctest */;
 442+			productType = "com.apple.product-type.bundle.unit-test";
 443+		};
 444+		5EF028DF2F7E7177006A58E2 /* BAASafariHostUITests */ = {
 445+			isa = PBXNativeTarget;
 446+			buildConfigurationList = 5EF029012F7E7177006A58E2 /* Build configuration list for PBXNativeTarget "BAASafariHostUITests" */;
 447+			buildPhases = (
 448+				5EF028DC2F7E7177006A58E2 /* Sources */,
 449+				5EF028DD2F7E7177006A58E2 /* Frameworks */,
 450+				5EF028DE2F7E7177006A58E2 /* Resources */,
 451+			);
 452+			buildRules = (
 453+			);
 454+			dependencies = (
 455+				5EF028E22F7E7177006A58E2 /* PBXTargetDependency */,
 456+			);
 457+			name = BAASafariHostUITests;
 458+			packageProductDependencies = (
 459+			);
 460+			productName = BAASafariHostUITests;
 461+			productReference = 5EF028E02F7E7177006A58E2 /* BAASafariHostUITests.xctest */;
 462+			productType = "com.apple.product-type.bundle.ui-testing";
 463+		};
 464+		5EF028EB2F7E7177006A58E2 /* BAASafariHost Extension */ = {
 465+			isa = PBXNativeTarget;
 466+			buildConfigurationList = 5EF028F72F7E7177006A58E2 /* Build configuration list for PBXNativeTarget "BAASafariHost Extension" */;
 467+			buildPhases = (
 468+				5EF028E82F7E7177006A58E2 /* Sources */,
 469+				5EF028E92F7E7177006A58E2 /* Frameworks */,
 470+				5EF028EA2F7E7177006A58E2 /* Resources */,
 471+			);
 472+			buildRules = (
 473+			);
 474+			dependencies = (
 475+			);
 476+			name = "BAASafariHost Extension";
 477+			packageProductDependencies = (
 478+			);
 479+			productName = "BAASafariHost Extension";
 480+			productReference = 5EF028EC2F7E7177006A58E2 /* BAASafariHost Extension.appex */;
 481+			productType = "com.apple.product-type.app-extension";
 482+		};
 483+/* End PBXNativeTarget section */
 484+
 485+/* Begin PBXProject section */
 486+		5EF028B12F7E7177006A58E2 /* Project object */ = {
 487+			isa = PBXProject;
 488+			attributes = {
 489+				BuildIndependentTargetsInParallel = 1;
 490+				LastSwiftUpdateCheck = 1620;
 491+				LastUpgradeCheck = 1620;
 492+				TargetAttributes = {
 493+					5EF028B82F7E7177006A58E2 = {
 494+						CreatedOnToolsVersion = 16.2;
 495+					};
 496+					5EF028D52F7E7177006A58E2 = {
 497+						CreatedOnToolsVersion = 16.2;
 498+						TestTargetID = 5EF028B82F7E7177006A58E2;
 499+					};
 500+					5EF028DF2F7E7177006A58E2 = {
 501+						CreatedOnToolsVersion = 16.2;
 502+						TestTargetID = 5EF028B82F7E7177006A58E2;
 503+					};
 504+					5EF028EB2F7E7177006A58E2 = {
 505+						CreatedOnToolsVersion = 16.2;
 506+					};
 507+				};
 508+			};
 509+			buildConfigurationList = 5EF028B42F7E7177006A58E2 /* Build configuration list for PBXProject "BAASafariHost" */;
 510+			developmentRegion = en;
 511+			hasScannedForEncodings = 0;
 512+			knownRegions = (
 513+				en,
 514+				Base,
 515+			);
 516+			mainGroup = 5EF028B02F7E7177006A58E2;
 517+			minimizedProjectReferenceProxies = 1;
 518+			preferredProjectObjectVersion = 77;
 519+			productRefGroup = 5EF028BA2F7E7177006A58E2 /* Products */;
 520+			projectDirPath = "";
 521+			projectRoot = "";
 522+			targets = (
 523+				5EF028B82F7E7177006A58E2 /* BAASafariHost */,
 524+				5EF028D52F7E7177006A58E2 /* BAASafariHostTests */,
 525+				5EF028DF2F7E7177006A58E2 /* BAASafariHostUITests */,
 526+				5EF028EB2F7E7177006A58E2 /* BAASafariHost Extension */,
 527+			);
 528+		};
 529+/* End PBXProject section */
 530+
 531+/* Begin PBXResourcesBuildPhase section */
 532+		5EF028B72F7E7177006A58E2 /* Resources */ = {
 533+			isa = PBXResourcesBuildPhase;
 534+			buildActionMask = 2147483647;
 535+			files = (
 536+				5EF028C32F7E7177006A58E2 /* Icon.png in Resources */,
 537+				5EF028CC2F7E7177006A58E2 /* Main.storyboard in Resources */,
 538+				5EF028C72F7E7177006A58E2 /* Script.js in Resources */,
 539+				5EF028C12F7E7177006A58E2 /* Main.html in Resources */,
 540+				5EF028CE2F7E7177006A58E2 /* Assets.xcassets in Resources */,
 541+				5EF028C52F7E7177006A58E2 /* Style.css in Resources */,
 542+			);
 543+			runOnlyForDeploymentPostprocessing = 0;
 544+		};
 545+		5EF028D42F7E7177006A58E2 /* Resources */ = {
 546+			isa = PBXResourcesBuildPhase;
 547+			buildActionMask = 2147483647;
 548+			files = (
 549+			);
 550+			runOnlyForDeploymentPostprocessing = 0;
 551+		};
 552+		5EF028DE2F7E7177006A58E2 /* Resources */ = {
 553+			isa = PBXResourcesBuildPhase;
 554+			buildActionMask = 2147483647;
 555+			files = (
 556+			);
 557+			runOnlyForDeploymentPostprocessing = 0;
 558+		};
 559+		5EF028EA2F7E7177006A58E2 /* Resources */ = {
 560+			isa = PBXResourcesBuildPhase;
 561+			buildActionMask = 2147483647;
 562+			files = (
 563+				5EF029142F7E7178006A58E2 /* content-script.js in Resources */,
 564+				5EF0290E2F7E7178006A58E2 /* background.js in Resources */,
 565+				5EF029102F7E7178006A58E2 /* controller.js in Resources */,
 566+				5EF029122F7E7178006A58E2 /* controller.html in Resources */,
 567+				5EF029162F7E7178006A58E2 /* page-interceptor.js in Resources */,
 568+				5EF029172F7E7178006A58E2 /* 48.png in Resources */,
 569+				5EF029192F7E7178006A58E2 /* 96.png in Resources */,
 570+				5EF0291B2F7E7178006A58E2 /* 128.png in Resources */,
 571+				5EF029112F7E7178006A58E2 /* README.md in Resources */,
 572+				5EF029132F7E7178006A58E2 /* manifest.json in Resources */,
 573+				5EF0290F2F7E7178006A58E2 /* controller.css in Resources */,
 574+			);
 575+			runOnlyForDeploymentPostprocessing = 0;
 576+		};
 577+/* End PBXResourcesBuildPhase section */
 578+
 579+/* Begin PBXSourcesBuildPhase section */
 580+		5EF028B52F7E7177006A58E2 /* Sources */ = {
 581+			isa = PBXSourcesBuildPhase;
 582+			buildActionMask = 2147483647;
 583+			files = (
 584+				5EF028C92F7E7177006A58E2 /* ViewController.swift in Sources */,
 585+				5EF028BD2F7E7177006A58E2 /* AppDelegate.swift in Sources */,
 586+			);
 587+			runOnlyForDeploymentPostprocessing = 0;
 588+		};
 589+		5EF028D22F7E7177006A58E2 /* Sources */ = {
 590+			isa = PBXSourcesBuildPhase;
 591+			buildActionMask = 2147483647;
 592+			files = (
 593+				5EF028DB2F7E7177006A58E2 /* BAASafariHostTests.swift in Sources */,
 594+			);
 595+			runOnlyForDeploymentPostprocessing = 0;
 596+		};
 597+		5EF028DC2F7E7177006A58E2 /* Sources */ = {
 598+			isa = PBXSourcesBuildPhase;
 599+			buildActionMask = 2147483647;
 600+			files = (
 601+				5EF028E52F7E7177006A58E2 /* BAASafariHostUITests.swift in Sources */,
 602+				5EF028E72F7E7177006A58E2 /* BAASafariHostUITestsLaunchTests.swift in Sources */,
 603+			);
 604+			runOnlyForDeploymentPostprocessing = 0;
 605+		};
 606+		5EF028E82F7E7177006A58E2 /* Sources */ = {
 607+			isa = PBXSourcesBuildPhase;
 608+			buildActionMask = 2147483647;
 609+			files = (
 610+				5EF028F22F7E7177006A58E2 /* SafariWebExtensionHandler.swift in Sources */,
 611+			);
 612+			runOnlyForDeploymentPostprocessing = 0;
 613+		};
 614+/* End PBXSourcesBuildPhase section */
 615+
 616+/* Begin PBXTargetDependency section */
 617+		5EF028D82F7E7177006A58E2 /* PBXTargetDependency */ = {
 618+			isa = PBXTargetDependency;
 619+			target = 5EF028B82F7E7177006A58E2 /* BAASafariHost */;
 620+			targetProxy = 5EF028D72F7E7177006A58E2 /* PBXContainerItemProxy */;
 621+		};
 622+		5EF028E22F7E7177006A58E2 /* PBXTargetDependency */ = {
 623+			isa = PBXTargetDependency;
 624+			target = 5EF028B82F7E7177006A58E2 /* BAASafariHost */;
 625+			targetProxy = 5EF028E12F7E7177006A58E2 /* PBXContainerItemProxy */;
 626+		};
 627+		5EF028EF2F7E7177006A58E2 /* PBXTargetDependency */ = {
 628+			isa = PBXTargetDependency;
 629+			target = 5EF028EB2F7E7177006A58E2 /* BAASafariHost Extension */;
 630+			targetProxy = 5EF028EE2F7E7177006A58E2 /* PBXContainerItemProxy */;
 631+		};
 632+/* End PBXTargetDependency section */
 633+
 634+/* Begin PBXVariantGroup section */
 635+		5EF028BF2F7E7177006A58E2 /* Main.html */ = {
 636+			isa = PBXVariantGroup;
 637+			children = (
 638+				5EF028C02F7E7177006A58E2 /* Base */,
 639+			);
 640+			name = Main.html;
 641+			sourceTree = "<group>";
 642+		};
 643+		5EF028CA2F7E7177006A58E2 /* Main.storyboard */ = {
 644+			isa = PBXVariantGroup;
 645+			children = (
 646+				5EF028CB2F7E7177006A58E2 /* Base */,
 647+			);
 648+			name = Main.storyboard;
 649+			sourceTree = "<group>";
 650+		};
 651+/* End PBXVariantGroup section */
 652+
 653+/* Begin XCBuildConfiguration section */
 654+		5EF028F52F7E7177006A58E2 /* Debug */ = {
 655+			isa = XCBuildConfiguration;
 656+			buildSettings = {
 657+				ALWAYS_SEARCH_USER_PATHS = NO;
 658+				ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
 659+				CLANG_ANALYZER_NONNULL = YES;
 660+				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
 661+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
 662+				CLANG_ENABLE_MODULES = YES;
 663+				CLANG_ENABLE_OBJC_ARC = YES;
 664+				CLANG_ENABLE_OBJC_WEAK = YES;
 665+				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
 666+				CLANG_WARN_BOOL_CONVERSION = YES;
 667+				CLANG_WARN_COMMA = YES;
 668+				CLANG_WARN_CONSTANT_CONVERSION = YES;
 669+				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
 670+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
 671+				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
 672+				CLANG_WARN_EMPTY_BODY = YES;
 673+				CLANG_WARN_ENUM_CONVERSION = YES;
 674+				CLANG_WARN_INFINITE_RECURSION = YES;
 675+				CLANG_WARN_INT_CONVERSION = YES;
 676+				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
 677+				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
 678+				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
 679+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
 680+				CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
 681+				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
 682+				CLANG_WARN_STRICT_PROTOTYPES = YES;
 683+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
 684+				CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
 685+				CLANG_WARN_UNREACHABLE_CODE = YES;
 686+				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
 687+				COPY_PHASE_STRIP = NO;
 688+				DEBUG_INFORMATION_FORMAT = dwarf;
 689+				ENABLE_STRICT_OBJC_MSGSEND = YES;
 690+				ENABLE_TESTABILITY = YES;
 691+				ENABLE_USER_SCRIPT_SANDBOXING = YES;
 692+				GCC_C_LANGUAGE_STANDARD = gnu17;
 693+				GCC_DYNAMIC_NO_PIC = NO;
 694+				GCC_NO_COMMON_BLOCKS = YES;
 695+				GCC_OPTIMIZATION_LEVEL = 0;
 696+				GCC_PREPROCESSOR_DEFINITIONS = (
 697+					"DEBUG=1",
 698+					"$(inherited)",
 699+				);
 700+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
 701+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
 702+				GCC_WARN_UNDECLARED_SELECTOR = YES;
 703+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
 704+				GCC_WARN_UNUSED_FUNCTION = YES;
 705+				GCC_WARN_UNUSED_VARIABLE = YES;
 706+				LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
 707+				MACOSX_DEPLOYMENT_TARGET = 15.1;
 708+				MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
 709+				MTL_FAST_MATH = YES;
 710+				ONLY_ACTIVE_ARCH = YES;
 711+				SDKROOT = macosx;
 712+				SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
 713+				SWIFT_OPTIMIZATION_LEVEL = "-Onone";
 714+			};
 715+			name = Debug;
 716+		};
 717+		5EF028F62F7E7177006A58E2 /* Release */ = {
 718+			isa = XCBuildConfiguration;
 719+			buildSettings = {
 720+				ALWAYS_SEARCH_USER_PATHS = NO;
 721+				ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
 722+				CLANG_ANALYZER_NONNULL = YES;
 723+				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
 724+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
 725+				CLANG_ENABLE_MODULES = YES;
 726+				CLANG_ENABLE_OBJC_ARC = YES;
 727+				CLANG_ENABLE_OBJC_WEAK = YES;
 728+				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
 729+				CLANG_WARN_BOOL_CONVERSION = YES;
 730+				CLANG_WARN_COMMA = YES;
 731+				CLANG_WARN_CONSTANT_CONVERSION = YES;
 732+				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
 733+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
 734+				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
 735+				CLANG_WARN_EMPTY_BODY = YES;
 736+				CLANG_WARN_ENUM_CONVERSION = YES;
 737+				CLANG_WARN_INFINITE_RECURSION = YES;
 738+				CLANG_WARN_INT_CONVERSION = YES;
 739+				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
 740+				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
 741+				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
 742+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
 743+				CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
 744+				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
 745+				CLANG_WARN_STRICT_PROTOTYPES = YES;
 746+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
 747+				CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
 748+				CLANG_WARN_UNREACHABLE_CODE = YES;
 749+				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
 750+				COPY_PHASE_STRIP = NO;
 751+				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
 752+				ENABLE_NS_ASSERTIONS = NO;
 753+				ENABLE_STRICT_OBJC_MSGSEND = YES;
 754+				ENABLE_USER_SCRIPT_SANDBOXING = YES;
 755+				GCC_C_LANGUAGE_STANDARD = gnu17;
 756+				GCC_NO_COMMON_BLOCKS = YES;
 757+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
 758+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
 759+				GCC_WARN_UNDECLARED_SELECTOR = YES;
 760+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
 761+				GCC_WARN_UNUSED_FUNCTION = YES;
 762+				GCC_WARN_UNUSED_VARIABLE = YES;
 763+				LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
 764+				MACOSX_DEPLOYMENT_TARGET = 15.1;
 765+				MTL_ENABLE_DEBUG_INFO = NO;
 766+				MTL_FAST_MATH = YES;
 767+				SDKROOT = macosx;
 768+				SWIFT_COMPILATION_MODE = wholemodule;
 769+			};
 770+			name = Release;
 771+		};
 772+		5EF028F82F7E7177006A58E2 /* Debug */ = {
 773+			isa = XCBuildConfiguration;
 774+			buildSettings = {
 775+				CODE_SIGN_ENTITLEMENTS = "BAASafariHost Extension/BAASafariHost_Extension.entitlements";
 776+				CODE_SIGN_STYLE = Automatic;
 777+				CURRENT_PROJECT_VERSION = 1;
 778+				ENABLE_HARDENED_RUNTIME = YES;
 779+				GENERATE_INFOPLIST_FILE = YES;
 780+				INFOPLIST_FILE = "BAASafariHost Extension/Info.plist";
 781+				INFOPLIST_KEY_CFBundleDisplayName = "BAASafariHost Extension";
 782+				INFOPLIST_KEY_NSHumanReadableCopyright = "";
 783+				LD_RUNPATH_SEARCH_PATHS = (
 784+					"$(inherited)",
 785+					"@executable_path/../Frameworks",
 786+					"@executable_path/../../../../Frameworks",
 787+				);
 788+				MACOSX_DEPLOYMENT_TARGET = 10.14;
 789+				MARKETING_VERSION = 1.0;
 790+				OTHER_LDFLAGS = (
 791+					"-framework",
 792+					SafariServices,
 793+				);
 794+				PRODUCT_BUNDLE_IDENTIFIER = com.baa.safari.BAASafariHost.Extension;
 795+				PRODUCT_NAME = "$(TARGET_NAME)";
 796+				SKIP_INSTALL = YES;
 797+				SWIFT_EMIT_LOC_STRINGS = YES;
 798+				SWIFT_VERSION = 5.0;
 799+			};
 800+			name = Debug;
 801+		};
 802+		5EF028F92F7E7177006A58E2 /* Release */ = {
 803+			isa = XCBuildConfiguration;
 804+			buildSettings = {
 805+				CODE_SIGN_ENTITLEMENTS = "BAASafariHost Extension/BAASafariHost_Extension.entitlements";
 806+				CODE_SIGN_STYLE = Automatic;
 807+				CURRENT_PROJECT_VERSION = 1;
 808+				ENABLE_HARDENED_RUNTIME = YES;
 809+				GENERATE_INFOPLIST_FILE = YES;
 810+				INFOPLIST_FILE = "BAASafariHost Extension/Info.plist";
 811+				INFOPLIST_KEY_CFBundleDisplayName = "BAASafariHost Extension";
 812+				INFOPLIST_KEY_NSHumanReadableCopyright = "";
 813+				LD_RUNPATH_SEARCH_PATHS = (
 814+					"$(inherited)",
 815+					"@executable_path/../Frameworks",
 816+					"@executable_path/../../../../Frameworks",
 817+				);
 818+				MACOSX_DEPLOYMENT_TARGET = 10.14;
 819+				MARKETING_VERSION = 1.0;
 820+				OTHER_LDFLAGS = (
 821+					"-framework",
 822+					SafariServices,
 823+				);
 824+				PRODUCT_BUNDLE_IDENTIFIER = com.baa.safari.BAASafariHost.Extension;
 825+				PRODUCT_NAME = "$(TARGET_NAME)";
 826+				SKIP_INSTALL = YES;
 827+				SWIFT_EMIT_LOC_STRINGS = YES;
 828+				SWIFT_VERSION = 5.0;
 829+			};
 830+			name = Release;
 831+		};
 832+		5EF028FC2F7E7177006A58E2 /* Debug */ = {
 833+			isa = XCBuildConfiguration;
 834+			buildSettings = {
 835+				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 836+				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
 837+				CODE_SIGN_ENTITLEMENTS = BAASafariHost/BAASafariHost.entitlements;
 838+				CODE_SIGN_STYLE = Automatic;
 839+				COMBINE_HIDPI_IMAGES = YES;
 840+				CURRENT_PROJECT_VERSION = 1;
 841+				ENABLE_HARDENED_RUNTIME = YES;
 842+				GENERATE_INFOPLIST_FILE = YES;
 843+				INFOPLIST_FILE = BAASafariHost/Info.plist;
 844+				INFOPLIST_KEY_CFBundleDisplayName = BAASafariHost;
 845+				INFOPLIST_KEY_NSHumanReadableCopyright = "";
 846+				INFOPLIST_KEY_NSMainStoryboardFile = Main;
 847+				INFOPLIST_KEY_NSPrincipalClass = NSApplication;
 848+				LD_RUNPATH_SEARCH_PATHS = (
 849+					"$(inherited)",
 850+					"@executable_path/../Frameworks",
 851+				);
 852+				MACOSX_DEPLOYMENT_TARGET = 10.14;
 853+				MARKETING_VERSION = 1.0;
 854+				OTHER_LDFLAGS = (
 855+					"-framework",
 856+					SafariServices,
 857+					"-framework",
 858+					WebKit,
 859+				);
 860+				PRODUCT_BUNDLE_IDENTIFIER = com.baa.safari.BAASafariHost;
 861+				PRODUCT_NAME = "$(TARGET_NAME)";
 862+				SWIFT_EMIT_LOC_STRINGS = YES;
 863+				SWIFT_VERSION = 5.0;
 864+			};
 865+			name = Debug;
 866+		};
 867+		5EF028FD2F7E7177006A58E2 /* Release */ = {
 868+			isa = XCBuildConfiguration;
 869+			buildSettings = {
 870+				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 871+				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
 872+				CODE_SIGN_ENTITLEMENTS = BAASafariHost/BAASafariHost.entitlements;
 873+				CODE_SIGN_STYLE = Automatic;
 874+				COMBINE_HIDPI_IMAGES = YES;
 875+				CURRENT_PROJECT_VERSION = 1;
 876+				ENABLE_HARDENED_RUNTIME = YES;
 877+				GENERATE_INFOPLIST_FILE = YES;
 878+				INFOPLIST_FILE = BAASafariHost/Info.plist;
 879+				INFOPLIST_KEY_CFBundleDisplayName = BAASafariHost;
 880+				INFOPLIST_KEY_NSHumanReadableCopyright = "";
 881+				INFOPLIST_KEY_NSMainStoryboardFile = Main;
 882+				INFOPLIST_KEY_NSPrincipalClass = NSApplication;
 883+				LD_RUNPATH_SEARCH_PATHS = (
 884+					"$(inherited)",
 885+					"@executable_path/../Frameworks",
 886+				);
 887+				MACOSX_DEPLOYMENT_TARGET = 10.14;
 888+				MARKETING_VERSION = 1.0;
 889+				OTHER_LDFLAGS = (
 890+					"-framework",
 891+					SafariServices,
 892+					"-framework",
 893+					WebKit,
 894+				);
 895+				PRODUCT_BUNDLE_IDENTIFIER = com.baa.safari.BAASafariHost;
 896+				PRODUCT_NAME = "$(TARGET_NAME)";
 897+				SWIFT_EMIT_LOC_STRINGS = YES;
 898+				SWIFT_VERSION = 5.0;
 899+			};
 900+			name = Release;
 901+		};
 902+		5EF028FF2F7E7177006A58E2 /* Debug */ = {
 903+			isa = XCBuildConfiguration;
 904+			buildSettings = {
 905+				BUNDLE_LOADER = "$(TEST_HOST)";
 906+				CODE_SIGN_STYLE = Automatic;
 907+				CURRENT_PROJECT_VERSION = 1;
 908+				GENERATE_INFOPLIST_FILE = YES;
 909+				MACOSX_DEPLOYMENT_TARGET = 10.14;
 910+				MARKETING_VERSION = 1.0;
 911+				PRODUCT_BUNDLE_IDENTIFIER = com.baa.safari.BAASafariHostTests;
 912+				PRODUCT_NAME = "$(TARGET_NAME)";
 913+				SWIFT_EMIT_LOC_STRINGS = NO;
 914+				SWIFT_VERSION = 5.0;
 915+				TEST_HOST = "$(BUILT_PRODUCTS_DIR)/BAASafariHost.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/BAASafariHost";
 916+			};
 917+			name = Debug;
 918+		};
 919+		5EF029002F7E7177006A58E2 /* Release */ = {
 920+			isa = XCBuildConfiguration;
 921+			buildSettings = {
 922+				BUNDLE_LOADER = "$(TEST_HOST)";
 923+				CODE_SIGN_STYLE = Automatic;
 924+				CURRENT_PROJECT_VERSION = 1;
 925+				GENERATE_INFOPLIST_FILE = YES;
 926+				MACOSX_DEPLOYMENT_TARGET = 10.14;
 927+				MARKETING_VERSION = 1.0;
 928+				PRODUCT_BUNDLE_IDENTIFIER = com.baa.safari.BAASafariHostTests;
 929+				PRODUCT_NAME = "$(TARGET_NAME)";
 930+				SWIFT_EMIT_LOC_STRINGS = NO;
 931+				SWIFT_VERSION = 5.0;
 932+				TEST_HOST = "$(BUILT_PRODUCTS_DIR)/BAASafariHost.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/BAASafariHost";
 933+			};
 934+			name = Release;
 935+		};
 936+		5EF029022F7E7177006A58E2 /* Debug */ = {
 937+			isa = XCBuildConfiguration;
 938+			buildSettings = {
 939+				CODE_SIGN_STYLE = Automatic;
 940+				CURRENT_PROJECT_VERSION = 1;
 941+				GENERATE_INFOPLIST_FILE = YES;
 942+				MARKETING_VERSION = 1.0;
 943+				PRODUCT_BUNDLE_IDENTIFIER = com.baa.safari.BAASafariHostUITests;
 944+				PRODUCT_NAME = "$(TARGET_NAME)";
 945+				SWIFT_EMIT_LOC_STRINGS = NO;
 946+				SWIFT_VERSION = 5.0;
 947+				TEST_TARGET_NAME = BAASafariHost;
 948+			};
 949+			name = Debug;
 950+		};
 951+		5EF029032F7E7177006A58E2 /* Release */ = {
 952+			isa = XCBuildConfiguration;
 953+			buildSettings = {
 954+				CODE_SIGN_STYLE = Automatic;
 955+				CURRENT_PROJECT_VERSION = 1;
 956+				GENERATE_INFOPLIST_FILE = YES;
 957+				MARKETING_VERSION = 1.0;
 958+				PRODUCT_BUNDLE_IDENTIFIER = com.baa.safari.BAASafariHostUITests;
 959+				PRODUCT_NAME = "$(TARGET_NAME)";
 960+				SWIFT_EMIT_LOC_STRINGS = NO;
 961+				SWIFT_VERSION = 5.0;
 962+				TEST_TARGET_NAME = BAASafariHost;
 963+			};
 964+			name = Release;
 965+		};
 966+/* End XCBuildConfiguration section */
 967+
 968+/* Begin XCConfigurationList section */
 969+		5EF028B42F7E7177006A58E2 /* Build configuration list for PBXProject "BAASafariHost" */ = {
 970+			isa = XCConfigurationList;
 971+			buildConfigurations = (
 972+				5EF028F52F7E7177006A58E2 /* Debug */,
 973+				5EF028F62F7E7177006A58E2 /* Release */,
 974+			);
 975+			defaultConfigurationIsVisible = 0;
 976+			defaultConfigurationName = Release;
 977+		};
 978+		5EF028F72F7E7177006A58E2 /* Build configuration list for PBXNativeTarget "BAASafariHost Extension" */ = {
 979+			isa = XCConfigurationList;
 980+			buildConfigurations = (
 981+				5EF028F82F7E7177006A58E2 /* Debug */,
 982+				5EF028F92F7E7177006A58E2 /* Release */,
 983+			);
 984+			defaultConfigurationIsVisible = 0;
 985+			defaultConfigurationName = Release;
 986+		};
 987+		5EF028FB2F7E7177006A58E2 /* Build configuration list for PBXNativeTarget "BAASafariHost" */ = {
 988+			isa = XCConfigurationList;
 989+			buildConfigurations = (
 990+				5EF028FC2F7E7177006A58E2 /* Debug */,
 991+				5EF028FD2F7E7177006A58E2 /* Release */,
 992+			);
 993+			defaultConfigurationIsVisible = 0;
 994+			defaultConfigurationName = Release;
 995+		};
 996+		5EF028FE2F7E7177006A58E2 /* Build configuration list for PBXNativeTarget "BAASafariHostTests" */ = {
 997+			isa = XCConfigurationList;
 998+			buildConfigurations = (
 999+				5EF028FF2F7E7177006A58E2 /* Debug */,
1000+				5EF029002F7E7177006A58E2 /* Release */,
1001+			);
1002+			defaultConfigurationIsVisible = 0;
1003+			defaultConfigurationName = Release;
1004+		};
1005+		5EF029012F7E7177006A58E2 /* Build configuration list for PBXNativeTarget "BAASafariHostUITests" */ = {
1006+			isa = XCConfigurationList;
1007+			buildConfigurations = (
1008+				5EF029022F7E7177006A58E2 /* Debug */,
1009+				5EF029032F7E7177006A58E2 /* Release */,
1010+			);
1011+			defaultConfigurationIsVisible = 0;
1012+			defaultConfigurationName = Release;
1013+		};
1014+/* End XCConfigurationList section */
1015+	};
1016+	rootObject = 5EF028B12F7E7177006A58E2 /* Project object */;
1017+}
1018diff --git a/plugins/baa-safari/baa-safari-host/BAASafariHost.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/plugins/baa-safari/baa-safari-host/BAASafariHost.xcodeproj/project.xcworkspace/contents.xcworkspacedata
1019new file mode 100644
1020index 0000000000000000000000000000000000000000..919434a6254f0e9651f402737811be6634a03e9c
1021--- /dev/null
1022+++ b/plugins/baa-safari/baa-safari-host/BAASafariHost.xcodeproj/project.xcworkspace/contents.xcworkspacedata
1023@@ -0,0 +1,7 @@
1024+<?xml version="1.0" encoding="UTF-8"?>
1025+<Workspace
1026+   version = "1.0">
1027+   <FileRef
1028+      location = "self:">
1029+   </FileRef>
1030+</Workspace>
1031diff --git a/plugins/baa-safari/baa-safari-host/BAASafariHost/AppDelegate.swift b/plugins/baa-safari/baa-safari-host/BAASafariHost/AppDelegate.swift
1032new file mode 100644
1033index 0000000000000000000000000000000000000000..b75d6d6405b5fd844d648740581701f2e06f997a
1034--- /dev/null
1035+++ b/plugins/baa-safari/baa-safari-host/BAASafariHost/AppDelegate.swift
1036@@ -0,0 +1,21 @@
1037+//
1038+//  AppDelegate.swift
1039+//  BAASafariHost
1040+//
1041+//  Created by george on 2026/4/2.
1042+//
1043+
1044+import Cocoa
1045+
1046+@main
1047+class AppDelegate: NSObject, NSApplicationDelegate {
1048+
1049+    func applicationDidFinishLaunching(_ notification: Notification) {
1050+        // Override point for customization after application launch.
1051+    }
1052+
1053+    func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
1054+        return true
1055+    }
1056+
1057+}
1058diff --git a/plugins/baa-safari/baa-safari-host/BAASafariHost/Assets.xcassets/AccentColor.colorset/Contents.json b/plugins/baa-safari/baa-safari-host/BAASafariHost/Assets.xcassets/AccentColor.colorset/Contents.json
1059new file mode 100644
1060index 0000000000000000000000000000000000000000..eb8789700816459c1e1480e0b34781d9fb78a1ca
1061--- /dev/null
1062+++ b/plugins/baa-safari/baa-safari-host/BAASafariHost/Assets.xcassets/AccentColor.colorset/Contents.json
1063@@ -0,0 +1,11 @@
1064+{
1065+  "colors" : [
1066+    {
1067+      "idiom" : "universal"
1068+    }
1069+  ],
1070+  "info" : {
1071+    "author" : "xcode",
1072+    "version" : 1
1073+  }
1074+}
1075diff --git a/plugins/baa-safari/baa-safari-host/BAASafariHost/Assets.xcassets/AppIcon.appiconset/Contents.json b/plugins/baa-safari/baa-safari-host/BAASafariHost/Assets.xcassets/AppIcon.appiconset/Contents.json
1076new file mode 100644
1077index 0000000000000000000000000000000000000000..3f00db43ec3c8b462759505d635dc5545d4e8e50
1078--- /dev/null
1079+++ b/plugins/baa-safari/baa-safari-host/BAASafariHost/Assets.xcassets/AppIcon.appiconset/Contents.json
1080@@ -0,0 +1,58 @@
1081+{
1082+  "images" : [
1083+    {
1084+      "idiom" : "mac",
1085+      "scale" : "1x",
1086+      "size" : "16x16"
1087+    },
1088+    {
1089+      "idiom" : "mac",
1090+      "scale" : "2x",
1091+      "size" : "16x16"
1092+    },
1093+    {
1094+      "idiom" : "mac",
1095+      "scale" : "1x",
1096+      "size" : "32x32"
1097+    },
1098+    {
1099+      "idiom" : "mac",
1100+      "scale" : "2x",
1101+      "size" : "32x32"
1102+    },
1103+    {
1104+      "idiom" : "mac",
1105+      "scale" : "1x",
1106+      "size" : "128x128"
1107+    },
1108+    {
1109+      "idiom" : "mac",
1110+      "scale" : "2x",
1111+      "size" : "128x128"
1112+    },
1113+    {
1114+      "idiom" : "mac",
1115+      "scale" : "1x",
1116+      "size" : "256x256"
1117+    },
1118+    {
1119+      "idiom" : "mac",
1120+      "scale" : "2x",
1121+      "size" : "256x256"
1122+    },
1123+    {
1124+      "idiom" : "mac",
1125+      "scale" : "1x",
1126+      "size" : "512x512"
1127+    },
1128+    {
1129+      "idiom" : "mac",
1130+      "scale" : "2x",
1131+      "size" : "512x512"
1132+    }
1133+  ],
1134+  "info" : {
1135+    "author" : "xcode",
1136+    "version" : 1
1137+  }
1138+}
1139diff --git a/plugins/baa-safari/baa-safari-host/BAASafariHost/Assets.xcassets/Contents.json b/plugins/baa-safari/baa-safari-host/BAASafariHost/Assets.xcassets/Contents.json
1140new file mode 100644
1141index 0000000000000000000000000000000000000000..73c00596a7fca3f3d4bdd64053b69d86745f9e10
1142--- /dev/null
1143+++ b/plugins/baa-safari/baa-safari-host/BAASafariHost/Assets.xcassets/Contents.json
1144@@ -0,0 +1,6 @@
1145+{
1146+  "info" : {
1147+    "author" : "xcode",
1148+    "version" : 1
1149+  }
1150+}
1151diff --git a/plugins/baa-safari/baa-safari-host/BAASafariHost/Assets.xcassets/LargeIcon.imageset/Contents.json b/plugins/baa-safari/baa-safari-host/BAASafariHost/Assets.xcassets/LargeIcon.imageset/Contents.json
1152new file mode 100644
1153index 0000000000000000000000000000000000000000..a19a5492203a8d30fc85ccc30497536a9500155d
1154--- /dev/null
1155+++ b/plugins/baa-safari/baa-safari-host/BAASafariHost/Assets.xcassets/LargeIcon.imageset/Contents.json
1156@@ -0,0 +1,20 @@
1157+{
1158+  "images" : [
1159+    {
1160+      "idiom" : "universal",
1161+      "scale" : "1x"
1162+    },
1163+    {
1164+      "idiom" : "universal",
1165+      "scale" : "2x"
1166+    },
1167+    {
1168+      "idiom" : "universal",
1169+      "scale" : "3x"
1170+    }
1171+  ],
1172+  "info" : {
1173+    "author" : "xcode",
1174+    "version" : 1
1175+  }
1176+}
1177diff --git a/plugins/baa-safari/baa-safari-host/BAASafariHost/BAASafariHost.entitlements b/plugins/baa-safari/baa-safari-host/BAASafariHost/BAASafariHost.entitlements
1178new file mode 100644
1179index 0000000000000000000000000000000000000000..625af03d99b2ea991b4a6dc8eed9db088e5afba1
1180--- /dev/null
1181+++ b/plugins/baa-safari/baa-safari-host/BAASafariHost/BAASafariHost.entitlements
1182@@ -0,0 +1,12 @@
1183+<?xml version="1.0" encoding="UTF-8"?>
1184+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
1185+<plist version="1.0">
1186+<dict>
1187+	<key>com.apple.security.app-sandbox</key>
1188+	<true/>
1189+	<key>com.apple.security.files.user-selected.read-only</key>
1190+	<true/>
1191+	<key>com.apple.security.network.client</key>
1192+	<true/>
1193+</dict>
1194+</plist>
1195diff --git a/plugins/baa-safari/baa-safari-host/BAASafariHost/Base.lproj/Main.storyboard b/plugins/baa-safari/baa-safari-host/BAASafariHost/Base.lproj/Main.storyboard
1196new file mode 100644
1197index 0000000000000000000000000000000000000000..f31b8241388f186e0882183ea04dbe6af071083a
1198--- /dev/null
1199+++ b/plugins/baa-safari/baa-safari-host/BAASafariHost/Base.lproj/Main.storyboard
1200@@ -0,0 +1,124 @@
1201+<?xml version="1.0" encoding="UTF-8"?>
1202+<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="19085" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" initialViewController="B8D-0N-5wS">
1203+    <dependencies>
1204+        <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="19085"/>
1205+        <plugIn identifier="com.apple.WebKit2IBPlugin" version="19085"/>
1206+        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
1207+    </dependencies>
1208+    <scenes>
1209+        <!--Application-->
1210+        <scene sceneID="JPo-4y-FX3">
1211+            <objects>
1212+                <application id="hnw-xV-0zn" sceneMemberID="viewController">
1213+                    <menu key="mainMenu" title="Main Menu" systemMenu="main" id="AYu-sK-qS6">
1214+                        <items>
1215+                            <menuItem title="BAASafariHost" id="1Xt-HY-uBw">
1216+                                <modifierMask key="keyEquivalentModifierMask"/>
1217+                                <menu key="submenu" title="BAASafariHost" systemMenu="apple" id="uQy-DD-JDr">
1218+                                    <items>
1219+                                        <menuItem title="About BAASafariHost" id="5kV-Vb-QxS">
1220+                                            <modifierMask key="keyEquivalentModifierMask"/>
1221+                                            <connections>
1222+                                                <action selector="orderFrontStandardAboutPanel:" target="Ady-hI-5gd" id="Exp-CZ-Vem"/>
1223+                                            </connections>
1224+                                        </menuItem>
1225+                                        <menuItem isSeparatorItem="YES" id="VOq-y0-SEH"/>
1226+                                        <menuItem title="Hide BAASafariHost" keyEquivalent="h" id="Olw-nP-bQN">
1227+                                            <connections>
1228+                                                <action selector="hide:" target="Ady-hI-5gd" id="PnN-Uc-m68"/>
1229+                                            </connections>
1230+                                        </menuItem>
1231+                                        <menuItem title="Hide Others" keyEquivalent="h" id="Vdr-fp-XzO">
1232+                                            <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
1233+                                            <connections>
1234+                                                <action selector="hideOtherApplications:" target="Ady-hI-5gd" id="VT4-aY-XCT"/>
1235+                                            </connections>
1236+                                        </menuItem>
1237+                                        <menuItem title="Show All" id="Kd2-mp-pUS">
1238+                                            <modifierMask key="keyEquivalentModifierMask"/>
1239+                                            <connections>
1240+                                                <action selector="unhideAllApplications:" target="Ady-hI-5gd" id="Dhg-Le-xox"/>
1241+                                            </connections>
1242+                                        </menuItem>
1243+                                        <menuItem isSeparatorItem="YES" id="kCx-OE-vgT"/>
1244+                                        <menuItem title="Quit BAASafariHost" keyEquivalent="q" id="4sb-4s-VLi">
1245+                                            <connections>
1246+                                                <action selector="terminate:" target="Ady-hI-5gd" id="Te7-pn-YzF"/>
1247+                                            </connections>
1248+                                        </menuItem>
1249+                                    </items>
1250+                                </menu>
1251+                            </menuItem>
1252+                            <menuItem title="Help" id="wpr-3q-Mcd">
1253+                                <modifierMask key="keyEquivalentModifierMask"/>
1254+                                <menu key="submenu" title="Help" systemMenu="help" id="F2S-fz-NVQ">
1255+                                    <items>
1256+                                        <menuItem title="BAASafariHost Help" keyEquivalent="?" id="FKE-Sm-Kum">
1257+                                            <connections>
1258+                                                <action selector="showHelp:" target="Ady-hI-5gd" id="y7X-2Q-9no"/>
1259+                                            </connections>
1260+                                        </menuItem>
1261+                                    </items>
1262+                                </menu>
1263+                            </menuItem>
1264+                        </items>
1265+                    </menu>
1266+                    <connections>
1267+                        <outlet property="delegate" destination="Voe-Tx-rLC" id="PrD-fu-P6m"/>
1268+                    </connections>
1269+                </application>
1270+                <customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModuleProvider="target"/>
1271+                <customObject id="YLy-65-1bz" customClass="NSFontManager"/>
1272+                <customObject id="Ady-hI-5gd" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
1273+            </objects>
1274+            <point key="canvasLocation" x="76" y="-134"/>
1275+        </scene>
1276+        <!--Window Controller-->
1277+        <scene sceneID="R2V-B0-nI4">
1278+            <objects>
1279+                <windowController showSeguePresentationStyle="single" id="B8D-0N-5wS" sceneMemberID="viewController">
1280+                    <window key="window" title="BAASafariHost" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" restorable="NO" releasedWhenClosed="NO" animationBehavior="default" id="IQv-IB-iLA">
1281+                        <windowStyleMask key="styleMask" titled="YES" closable="YES"/>
1282+                        <windowCollectionBehavior key="collectionBehavior" fullScreenNone="YES"/>
1283+                        <rect key="contentRect" x="196" y="240" width="425" height="325"/>
1284+                        <rect key="screenRect" x="0.0" y="0.0" width="1680" height="1027"/>
1285+                        <connections>
1286+                            <outlet property="delegate" destination="B8D-0N-5wS" id="98r-iN-zZc"/>
1287+                        </connections>
1288+                    </window>
1289+                    <connections>
1290+                        <segue destination="XfG-lQ-9wD" kind="relationship" relationship="window.shadowedContentViewController" id="cq2-FE-JQM"/>
1291+                    </connections>
1292+                </windowController>
1293+                <customObject id="Oky-zY-oP4" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
1294+            </objects>
1295+            <point key="canvasLocation" x="75" y="250"/>
1296+        </scene>
1297+        <!--View Controller-->
1298+        <scene sceneID="hIz-AP-VOD">
1299+            <objects>
1300+                <viewController id="XfG-lQ-9wD" customClass="ViewController" customModuleProvider="target" sceneMemberID="viewController">
1301+                    <view key="view" id="m2S-Jp-Qdl">
1302+                        <rect key="frame" x="0.0" y="0.0" width="425" height="325"/>
1303+                        <autoresizingMask key="autoresizingMask"/>
1304+                        <subviews>
1305+                            <wkWebView wantsLayer="YES" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="eOr-cG-IQY">
1306+                                <rect key="frame" x="0.0" y="0.0" width="425" height="325"/>
1307+                                <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
1308+                                <wkWebViewConfiguration key="configuration">
1309+                                    <audiovisualMediaTypes key="mediaTypesRequiringUserActionForPlayback" none="YES"/>
1310+                                    <wkPreferences key="preferences"/>
1311+                                </wkWebViewConfiguration>
1312+                            </wkWebView>
1313+                        </subviews>
1314+                    </view>
1315+                    <connections>
1316+                        <outlet property="webView" destination="eOr-cG-IQY" id="GFe-mU-dBY"/>
1317+                    </connections>
1318+                </viewController>
1319+                <customObject id="rPt-NT-nkU" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
1320+            </objects>
1321+            <point key="canvasLocation" x="75" y="655"/>
1322+        </scene>
1323+    </scenes>
1324+</document>
1325diff --git a/plugins/baa-safari/baa-safari-host/BAASafariHost/Info.plist b/plugins/baa-safari/baa-safari-host/BAASafariHost/Info.plist
1326new file mode 100644
1327index 0000000000000000000000000000000000000000..716d66f1ba524566386509a943210e39fc8e4dd0
1328--- /dev/null
1329+++ b/plugins/baa-safari/baa-safari-host/BAASafariHost/Info.plist
1330@@ -0,0 +1,8 @@
1331+<?xml version="1.0" encoding="UTF-8"?>
1332+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
1333+<plist version="1.0">
1334+<dict>
1335+	<key>SFSafariWebExtensionConverterVersion</key>
1336+	<string>16.2</string>
1337+</dict>
1338+</plist>
1339diff --git a/plugins/baa-safari/baa-safari-host/BAASafariHost/Resources/Base.lproj/Main.html b/plugins/baa-safari/baa-safari-host/BAASafariHost/Resources/Base.lproj/Main.html
1340new file mode 100644
1341index 0000000000000000000000000000000000000000..e591d7054e219cc6e52a6744eac938dd88e4bfa6
1342--- /dev/null
1343+++ b/plugins/baa-safari/baa-safari-host/BAASafariHost/Resources/Base.lproj/Main.html
1344@@ -0,0 +1,31 @@
1345+<!DOCTYPE html>
1346+<html>
1347+<head>
1348+    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
1349+    <meta http-equiv="Content-Security-Policy" content="default-src 'self'">
1350+
1351+    <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
1352+
1353+    <link rel="stylesheet" href="../Style.css">
1354+    <script src="../Script.js" defer></script>
1355+</head>
1356+<body>
1357+    <img src="../Icon.png" width="128" height="128" alt="BAASafariHost Icon">
1358+    <p class="eyebrow">BAA Safari Host</p>
1359+    <h1>请在 Safari 设置中启用 BAA 扩展</h1>
1360+    <p class="lead">
1361+        这个宿主 App 只负责承载 Safari Web Extension。首版长期 runtime owner 仍然预留给
1362+        <code>controller.html</code>,不是这个宿主窗口。
1363+    </p>
1364+    <p class="state-unknown">你可以在 Safari 设置的“扩展”里启用 BAA Safari Extension。</p>
1365+    <p class="state-on">BAA Safari Extension 当前已启用。接下来请点击 Safari 工具栏里的扩展按钮打开控制页。</p>
1366+    <p class="state-off">BAA Safari Extension 当前未启用。请先在 Safari 设置的“扩展”里打开它。</p>
1367+    <ol class="steps">
1368+        <li>打开 Safari 设置。</li>
1369+        <li>进入“扩展”。</li>
1370+        <li>启用 BAA Safari Extension。</li>
1371+        <li>给 Claude / ChatGPT / Gemini 网站授权。</li>
1372+    </ol>
1373+    <button class="open-preferences">退出并打开 Safari 设置…</button>
1374+</body>
1375+</html>
1376diff --git a/plugins/baa-safari/baa-safari-host/BAASafariHost/Resources/Icon.png b/plugins/baa-safari/baa-safari-host/BAASafariHost/Resources/Icon.png
1377new file mode 100644
1378index 0000000000000000000000000000000000000000..423b491de9f0c035d4c9d7736c535bebfe078c8e
1379Binary files /dev/null and b/plugins/baa-safari/baa-safari-host/BAASafariHost/Resources/Icon.png differ
1380diff --git a/plugins/baa-safari/baa-safari-host/BAASafariHost/Resources/Script.js b/plugins/baa-safari/baa-safari-host/BAASafariHost/Resources/Script.js
1381new file mode 100644
1382index 0000000000000000000000000000000000000000..48a5ffa86d180d371570d5f63658d71a88ee25c3
1383--- /dev/null
1384+++ b/plugins/baa-safari/baa-safari-host/BAASafariHost/Resources/Script.js
1385@@ -0,0 +1,22 @@
1386+function show(enabled, useSettingsInsteadOfPreferences) {
1387+    if (useSettingsInsteadOfPreferences) {
1388+        document.getElementsByClassName('state-on')[0].innerText = "BAA Safari Extension 当前已启用。接下来请点击 Safari 工具栏里的扩展按钮打开控制页。";
1389+        document.getElementsByClassName('state-off')[0].innerText = "BAA Safari Extension 当前未启用。请先在 Safari 设置的“扩展”里打开它。";
1390+        document.getElementsByClassName('state-unknown')[0].innerText = "你可以在 Safari 设置的“扩展”里启用 BAA Safari Extension。";
1391+        document.getElementsByClassName('open-preferences')[0].innerText = "退出并打开 Safari 设置…";
1392+    }
1393+
1394+    if (typeof enabled === "boolean") {
1395+        document.body.classList.toggle(`state-on`, enabled);
1396+        document.body.classList.toggle(`state-off`, !enabled);
1397+    } else {
1398+        document.body.classList.remove(`state-on`);
1399+        document.body.classList.remove(`state-off`);
1400+    }
1401+}
1402+
1403+function openPreferences() {
1404+    webkit.messageHandlers.controller.postMessage("open-preferences");
1405+}
1406+
1407+document.querySelector("button.open-preferences").addEventListener("click", openPreferences);
1408diff --git a/plugins/baa-safari/baa-safari-host/BAASafariHost/Resources/Style.css b/plugins/baa-safari/baa-safari-host/BAASafariHost/Resources/Style.css
1409new file mode 100644
1410index 0000000000000000000000000000000000000000..f7aadd600b769dd3a27f5314f87c890ac57b7ea9
1411--- /dev/null
1412+++ b/plugins/baa-safari/baa-safari-host/BAASafariHost/Resources/Style.css
1413@@ -0,0 +1,82 @@
1414+* {
1415+    -webkit-user-select: none;
1416+    -webkit-user-drag: none;
1417+    cursor: default;
1418+}
1419+
1420+:root {
1421+    color-scheme: light dark;
1422+
1423+    --spacing: 20px;
1424+}
1425+
1426+html {
1427+    height: 100%;
1428+}
1429+
1430+body {
1431+    display: flex;
1432+    align-items: center;
1433+    justify-content: center;
1434+    flex-direction: column;
1435+
1436+    gap: var(--spacing);
1437+    margin: 0 calc(var(--spacing) * 2);
1438+    height: 100%;
1439+
1440+    font: -apple-system-short-body;
1441+    text-align: center;
1442+}
1443+
1444+img {
1445+    border-radius: 24px;
1446+}
1447+
1448+.eyebrow {
1449+    margin: 0;
1450+    font-size: 12px;
1451+    font-weight: 700;
1452+    letter-spacing: 0.08em;
1453+    text-transform: uppercase;
1454+    color: #2f688f;
1455+}
1456+
1457+h1 {
1458+    margin: 0;
1459+    max-width: 16em;
1460+    font-size: 28px;
1461+    line-height: 1.25;
1462+}
1463+
1464+.lead,
1465+.steps,
1466+p {
1467+    margin: 0;
1468+    max-width: 34rem;
1469+    line-height: 1.6;
1470+}
1471+
1472+.steps {
1473+    text-align: left;
1474+}
1475+
1476+body:not(.state-on, .state-off) :is(.state-on, .state-off) {
1477+    display: none;
1478+}
1479+
1480+body.state-on :is(.state-off, .state-unknown) {
1481+    display: none;
1482+}
1483+
1484+body.state-off :is(.state-on, .state-unknown) {
1485+    display: none;
1486+}
1487+
1488+button {
1489+    font-size: 1em;
1490+    border-radius: 999px;
1491+    border: 0;
1492+    padding: 11px 16px;
1493+    background: #1f6f5f;
1494+    color: #fff;
1495+}
1496diff --git a/plugins/baa-safari/baa-safari-host/BAASafariHost/ViewController.swift b/plugins/baa-safari/baa-safari-host/BAASafariHost/ViewController.swift
1497new file mode 100644
1498index 0000000000000000000000000000000000000000..42a6de11a74f77d85e090d5320a7f5dc05bfcb70
1499--- /dev/null
1500+++ b/plugins/baa-safari/baa-safari-host/BAASafariHost/ViewController.swift
1501@@ -0,0 +1,57 @@
1502+//
1503+//  ViewController.swift
1504+//  BAASafariHost
1505+//
1506+//  Created by george on 2026/4/2.
1507+//
1508+
1509+import Cocoa
1510+import SafariServices
1511+import WebKit
1512+
1513+let extensionBundleIdentifier = "com.baa.safari.BAASafariHost.Extension"
1514+
1515+class ViewController: NSViewController, WKNavigationDelegate, WKScriptMessageHandler {
1516+
1517+    @IBOutlet var webView: WKWebView!
1518+
1519+    override func viewDidLoad() {
1520+        super.viewDidLoad()
1521+
1522+        self.webView.navigationDelegate = self
1523+
1524+        self.webView.configuration.userContentController.add(self, name: "controller")
1525+
1526+        self.webView.loadFileURL(Bundle.main.url(forResource: "Main", withExtension: "html")!, allowingReadAccessTo: Bundle.main.resourceURL!)
1527+    }
1528+
1529+    func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
1530+        SFSafariExtensionManager.getStateOfSafariExtension(withIdentifier: extensionBundleIdentifier) { (state, error) in
1531+            guard let state = state, error == nil else {
1532+                // Insert code to inform the user that something went wrong.
1533+                return
1534+            }
1535+
1536+            DispatchQueue.main.async {
1537+                if #available(macOS 13, *) {
1538+                    webView.evaluateJavaScript("show(\(state.isEnabled), true)")
1539+                } else {
1540+                    webView.evaluateJavaScript("show(\(state.isEnabled), false)")
1541+                }
1542+            }
1543+        }
1544+    }
1545+
1546+    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
1547+        if (message.body as! String != "open-preferences") {
1548+            return;
1549+        }
1550+
1551+        SFSafariApplication.showPreferencesForExtension(withIdentifier: extensionBundleIdentifier) { error in
1552+            DispatchQueue.main.async {
1553+                NSApplication.shared.terminate(nil)
1554+            }
1555+        }
1556+    }
1557+
1558+}
1559diff --git a/plugins/baa-safari/baa-safari-host/BAASafariHostTests/BAASafariHostTests.swift b/plugins/baa-safari/baa-safari-host/BAASafariHostTests/BAASafariHostTests.swift
1560new file mode 100644
1561index 0000000000000000000000000000000000000000..6649c2b53dbb3bf4edb2cda2ffd30d4a259909ce
1562--- /dev/null
1563+++ b/plugins/baa-safari/baa-safari-host/BAASafariHostTests/BAASafariHostTests.swift
1564@@ -0,0 +1,17 @@
1565+//
1566+//  BAASafariHostTests.swift
1567+//  BAASafariHostTests
1568+//
1569+//  Created by george on 2026/4/2.
1570+//
1571+
1572+import Testing
1573+@testable import BAASafariHost
1574+
1575+struct BAASafariHostTests {
1576+
1577+    @Test func example() async throws {
1578+        // Write your test here and use APIs like `#expect(...)` to check expected conditions.
1579+    }
1580+
1581+}
1582diff --git a/plugins/baa-safari/baa-safari-host/BAASafariHostUITests/BAASafariHostUITests.swift b/plugins/baa-safari/baa-safari-host/BAASafariHostUITests/BAASafariHostUITests.swift
1583new file mode 100644
1584index 0000000000000000000000000000000000000000..8b2a1710cbe0c292e8b5293c2facfa13288ee051
1585--- /dev/null
1586+++ b/plugins/baa-safari/baa-safari-host/BAASafariHostUITests/BAASafariHostUITests.swift
1587@@ -0,0 +1,43 @@
1588+//
1589+//  BAASafariHostUITests.swift
1590+//  BAASafariHostUITests
1591+//
1592+//  Created by george on 2026/4/2.
1593+//
1594+
1595+import XCTest
1596+
1597+final class BAASafariHostUITests: XCTestCase {
1598+
1599+    override func setUpWithError() throws {
1600+        // Put setup code here. This method is called before the invocation of each test method in the class.
1601+
1602+        // In UI tests it is usually best to stop immediately when a failure occurs.
1603+        continueAfterFailure = false
1604+
1605+        // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
1606+    }
1607+
1608+    override func tearDownWithError() throws {
1609+        // Put teardown code here. This method is called after the invocation of each test method in the class.
1610+    }
1611+
1612+    @MainActor
1613+    func testExample() throws {
1614+        // UI tests must launch the application that they test.
1615+        let app = XCUIApplication()
1616+        app.launch()
1617+
1618+        // Use XCTAssert and related functions to verify your tests produce the correct results.
1619+    }
1620+
1621+    @MainActor
1622+    func testLaunchPerformance() throws {
1623+        if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) {
1624+            // This measures how long it takes to launch your application.
1625+            measure(metrics: [XCTApplicationLaunchMetric()]) {
1626+                XCUIApplication().launch()
1627+            }
1628+        }
1629+    }
1630+}
1631diff --git a/plugins/baa-safari/baa-safari-host/BAASafariHostUITests/BAASafariHostUITestsLaunchTests.swift b/plugins/baa-safari/baa-safari-host/BAASafariHostUITests/BAASafariHostUITestsLaunchTests.swift
1632new file mode 100644
1633index 0000000000000000000000000000000000000000..a7a8c9bd5e63f7e4c7fd458161e8f764fe046a80
1634--- /dev/null
1635+++ b/plugins/baa-safari/baa-safari-host/BAASafariHostUITests/BAASafariHostUITestsLaunchTests.swift
1636@@ -0,0 +1,33 @@
1637+//
1638+//  BAASafariHostUITestsLaunchTests.swift
1639+//  BAASafariHostUITests
1640+//
1641+//  Created by george on 2026/4/2.
1642+//
1643+
1644+import XCTest
1645+
1646+final class BAASafariHostUITestsLaunchTests: XCTestCase {
1647+
1648+    override class var runsForEachTargetApplicationUIConfiguration: Bool {
1649+        true
1650+    }
1651+
1652+    override func setUpWithError() throws {
1653+        continueAfterFailure = false
1654+    }
1655+
1656+    @MainActor
1657+    func testLaunch() throws {
1658+        let app = XCUIApplication()
1659+        app.launch()
1660+
1661+        // Insert steps here to perform after app launch but before taking a screenshot,
1662+        // such as logging into a test account or navigating somewhere in the app
1663+
1664+        let attachment = XCTAttachment(screenshot: app.screenshot())
1665+        attachment.name = "Launch Screen"
1666+        attachment.lifetime = .keepAlways
1667+        add(attachment)
1668+    }
1669+}
1670diff --git a/plugins/baa-safari/background.js b/plugins/baa-safari/background.js
1671new file mode 100644
1672index 0000000000000000000000000000000000000000..2b90865078f2d679190c4eae5aaac77243d150ca
1673--- /dev/null
1674+++ b/plugins/baa-safari/background.js
1675@@ -0,0 +1,166 @@
1676+const CONTROLLER_URL = browser.runtime.getURL("controller.html");
1677+const STORAGE_KEY = "baaSafari.controllerTabId";
1678+
1679+const runtimeState = {
1680+  background: {
1681+    startedAt: Date.now()
1682+  },
1683+  controller: {
1684+    lastReadyAt: null,
1685+    tabId: null
1686+  },
1687+  lastContentScript: null,
1688+  lastPageInterceptor: null,
1689+  lastUpdatedAt: Date.now(),
1690+  runtimeOwner: "controller.html"
1691+};
1692+
1693+async function getStoredControllerTabId() {
1694+  const data = await browser.storage.local.get(STORAGE_KEY);
1695+  return Number.isInteger(data[STORAGE_KEY]) ? data[STORAGE_KEY] : null;
1696+}
1697+
1698+async function setStoredControllerTabId(tabId) {
1699+  if (Number.isInteger(tabId)) {
1700+    await browser.storage.local.set({ [STORAGE_KEY]: tabId });
1701+    return;
1702+  }
1703+
1704+  await browser.storage.local.remove(STORAGE_KEY);
1705+}
1706+
1707+async function queryControllerTabs() {
1708+  return browser.tabs.query({ url: CONTROLLER_URL });
1709+}
1710+
1711+async function resolveControllerTab() {
1712+  const tabs = await queryControllerTabs();
1713+
1714+  if (tabs.length === 0) {
1715+    await setStoredControllerTabId(null);
1716+    runtimeState.controller.tabId = null;
1717+    runtimeState.lastUpdatedAt = Date.now();
1718+    return null;
1719+  }
1720+
1721+  const storedTabId = await getStoredControllerTabId();
1722+  const canonical =
1723+    tabs.find((tab) => tab.id === storedTabId)
1724+    ?? tabs.find((tab) => Number.isInteger(tab.id))
1725+    ?? null;
1726+
1727+  if (!canonical || !Number.isInteger(canonical.id)) {
1728+    return null;
1729+  }
1730+
1731+  const duplicateIds = tabs
1732+    .map((tab) => tab.id)
1733+    .filter((tabId) => Number.isInteger(tabId) && tabId !== canonical.id);
1734+
1735+  if (duplicateIds.length > 0) {
1736+    await browser.tabs.remove(duplicateIds);
1737+  }
1738+
1739+  runtimeState.controller.tabId = canonical.id;
1740+  runtimeState.lastUpdatedAt = Date.now();
1741+  await setStoredControllerTabId(canonical.id);
1742+  return canonical;
1743+}
1744+
1745+async function ensureControllerTab(options = {}) {
1746+  const activate = options.activate === true;
1747+  let tab = await resolveControllerTab();
1748+
1749+  if (tab == null) {
1750+    tab = await browser.tabs.create({
1751+      active: activate,
1752+      url: CONTROLLER_URL
1753+    });
1754+    await setStoredControllerTabId(tab.id ?? null);
1755+    runtimeState.controller.tabId = Number.isInteger(tab.id) ? tab.id : null;
1756+  } else if (activate) {
1757+    await browser.tabs.update(tab.id, { active: true });
1758+
1759+    if (Number.isInteger(tab.windowId)) {
1760+      await browser.windows.update(tab.windowId, { focused: true });
1761+    }
1762+  }
1763+
1764+  runtimeState.lastUpdatedAt = Date.now();
1765+  return tab;
1766+}
1767+
1768+function updateRuntimeState(patch) {
1769+  Object.assign(runtimeState, patch);
1770+  runtimeState.lastUpdatedAt = Date.now();
1771+}
1772+
1773+browser.runtime.onInstalled.addListener(() => {
1774+  void ensureControllerTab({ activate: true });
1775+});
1776+
1777+browser.action.onClicked.addListener(() => {
1778+  void ensureControllerTab({ activate: true });
1779+});
1780+
1781+browser.tabs.onRemoved.addListener((tabId) => {
1782+  if (runtimeState.controller.tabId !== tabId) {
1783+    return;
1784+  }
1785+
1786+  runtimeState.controller.tabId = null;
1787+  runtimeState.controller.lastReadyAt = null;
1788+  runtimeState.lastUpdatedAt = Date.now();
1789+  void setStoredControllerTabId(null);
1790+});
1791+
1792+browser.runtime.onMessage.addListener((message) => {
1793+  if (!message || typeof message !== "object") {
1794+    return undefined;
1795+  }
1796+
1797+  switch (message.type) {
1798+    case "baa_safari.controller_ready":
1799+      updateRuntimeState({
1800+        controller: {
1801+          lastReadyAt: Date.now(),
1802+          tabId: Number.isInteger(message.tabId) ? message.tabId : runtimeState.controller.tabId
1803+        }
1804+      });
1805+      if (Number.isInteger(message.tabId)) {
1806+        void setStoredControllerTabId(message.tabId);
1807+      }
1808+      return Promise.resolve({
1809+        ok: true,
1810+        runtimeOwner: runtimeState.runtimeOwner
1811+      });
1812+    case "baa_safari.get_state":
1813+      return Promise.resolve({
1814+        ...runtimeState
1815+      });
1816+    case "baa_safari.focus_controller":
1817+      return ensureControllerTab({ activate: true }).then((tab) => ({
1818+        ok: true,
1819+        tabId: tab?.id ?? null
1820+      }));
1821+    case "baa_safari.content_script_seen":
1822+      updateRuntimeState({
1823+        lastContentScript: {
1824+          platform: typeof message.platform === "string" ? message.platform : null,
1825+          seenAt: Date.now(),
1826+          url: typeof message.url === "string" ? message.url : null
1827+        }
1828+      });
1829+      return Promise.resolve({ ok: true });
1830+    case "baa_safari.page_interceptor_seen":
1831+      updateRuntimeState({
1832+        lastPageInterceptor: {
1833+          seenAt: Date.now(),
1834+          url: typeof message.url === "string" ? message.url : null
1835+        }
1836+      });
1837+      return Promise.resolve({ ok: true });
1838+    default:
1839+      return undefined;
1840+  }
1841+});
1842diff --git a/plugins/baa-safari/content-script.js b/plugins/baa-safari/content-script.js
1843new file mode 100644
1844index 0000000000000000000000000000000000000000..19fb382833235c1d7f2d9c6507b144b2a287357c
1845--- /dev/null
1846+++ b/plugins/baa-safari/content-script.js
1847@@ -0,0 +1,44 @@
1848+(function bootstrapBaaSafariContentScript() {
1849+  const host = globalThis.location?.hostname || "";
1850+  const platform =
1851+    host === "claude.ai"
1852+      ? "claude"
1853+      : host === "chatgpt.com" || host === "chat.openai.com"
1854+        ? "chatgpt"
1855+        : host === "gemini.google.com"
1856+          ? "gemini"
1857+          : "unknown";
1858+
1859+  function injectPageInterceptor() {
1860+    const root = document.documentElement;
1861+
1862+    if (!root) {
1863+      return;
1864+    }
1865+
1866+    const script = document.createElement("script");
1867+    script.src = browser.runtime.getURL("page-interceptor.js");
1868+    script.dataset.baaSafari = "page-interceptor";
1869+    script.onload = () => {
1870+      script.remove();
1871+    };
1872+    (document.head || root).appendChild(script);
1873+  }
1874+
1875+  browser.runtime.sendMessage({
1876+    platform,
1877+    type: "baa_safari.content_script_seen",
1878+    url: globalThis.location?.href || null
1879+  }).catch(() => {});
1880+
1881+  document.addEventListener("baa:safari-page-interceptor-ready", (event) => {
1882+    browser.runtime.sendMessage({
1883+      type: "baa_safari.page_interceptor_seen",
1884+      url: event?.detail?.url || globalThis.location?.href || null
1885+    }).catch(() => {});
1886+  }, {
1887+    once: true
1888+  });
1889+
1890+  injectPageInterceptor();
1891+})();
1892diff --git a/plugins/baa-safari/controller.css b/plugins/baa-safari/controller.css
1893new file mode 100644
1894index 0000000000000000000000000000000000000000..60a18009fe48faec0fccc04b40d484cd770e6b66
1895--- /dev/null
1896+++ b/plugins/baa-safari/controller.css
1897@@ -0,0 +1,129 @@
1898+* {
1899+  box-sizing: border-box;
1900+}
1901+
1902+body {
1903+  margin: 0;
1904+  font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", sans-serif;
1905+  background:
1906+    radial-gradient(circle at top left, rgba(21, 92, 159, 0.18), transparent 34%),
1907+    linear-gradient(180deg, #f4f8fc 0%, #eef4f8 100%);
1908+  color: #173247;
1909+}
1910+
1911+.shell {
1912+  max-width: 980px;
1913+  margin: 0 auto;
1914+  padding: 32px 20px 48px;
1915+}
1916+
1917+.hero,
1918+.actions,
1919+.grid,
1920+.panel {
1921+  margin-bottom: 20px;
1922+}
1923+
1924+.eyebrow {
1925+  margin: 0 0 8px;
1926+  font-size: 12px;
1927+  font-weight: 700;
1928+  letter-spacing: 0.08em;
1929+  text-transform: uppercase;
1930+  color: #245e8a;
1931+}
1932+
1933+h1,
1934+h2,
1935+p {
1936+  margin: 0;
1937+}
1938+
1939+.lead {
1940+  margin-top: 10px;
1941+  max-width: 720px;
1942+  line-height: 1.6;
1943+  color: #36566f;
1944+}
1945+
1946+.actions {
1947+  display: flex;
1948+  gap: 12px;
1949+}
1950+
1951+button {
1952+  border: 0;
1953+  border-radius: 999px;
1954+  padding: 11px 16px;
1955+  font: inherit;
1956+  font-weight: 600;
1957+  color: #fff;
1958+  background: #1f6f5f;
1959+  cursor: pointer;
1960+}
1961+
1962+button:last-child {
1963+  background: #245e8a;
1964+}
1965+
1966+.grid {
1967+  display: grid;
1968+  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
1969+  gap: 14px;
1970+}
1971+
1972+.card,
1973+.panel {
1974+  border: 1px solid rgba(23, 50, 71, 0.08);
1975+  border-radius: 18px;
1976+  padding: 16px;
1977+  background: rgba(255, 255, 255, 0.84);
1978+  backdrop-filter: blur(12px);
1979+}
1980+
1981+.label {
1982+  font-size: 12px;
1983+  font-weight: 700;
1984+  letter-spacing: 0.06em;
1985+  text-transform: uppercase;
1986+  color: #6a879d;
1987+}
1988+
1989+.value {
1990+  margin-top: 10px;
1991+  font-size: 24px;
1992+  font-weight: 700;
1993+}
1994+
1995+.value.off {
1996+  color: #7d8d99;
1997+}
1998+
1999+.meta {
2000+  margin-top: 10px;
2001+  line-height: 1.5;
2002+  color: #4b6578;
2003+}
2004+
2005+.panel-head {
2006+  margin-bottom: 12px;
2007+}
2008+
2009+.code {
2010+  margin: 0;
2011+  overflow: auto;
2012+  white-space: pre-wrap;
2013+  font: 13px/1.6 "SFMono-Regular", ui-monospace, monospace;
2014+  color: #204157;
2015+}
2016+
2017+.steps {
2018+  margin: 0;
2019+  padding-left: 20px;
2020+  color: #36566f;
2021+  line-height: 1.7;
2022+}
2023+
2024+code {
2025+  font-family: "SFMono-Regular", ui-monospace, monospace;
2026+}
2027diff --git a/plugins/baa-safari/controller.html b/plugins/baa-safari/controller.html
2028new file mode 100644
2029index 0000000000000000000000000000000000000000..663742bf9469d73e8b9e20b51dcd71fed2649abe
2030--- /dev/null
2031+++ b/plugins/baa-safari/controller.html
2032@@ -0,0 +1,77 @@
2033+<!doctype html>
2034+<html lang="zh-CN">
2035+<head>
2036+  <meta charset="utf-8">
2037+  <meta name="viewport" content="width=device-width,initial-scale=1">
2038+  <title>BAA Safari 控制页</title>
2039+  <link rel="stylesheet" href="controller.css">
2040+</head>
2041+<body>
2042+  <main class="shell">
2043+    <section class="hero">
2044+      <p class="eyebrow">BAA Safari 控制页</p>
2045+      <h1>Safari Web Extension 基础壳</h1>
2046+      <p class="lead">
2047+        Phase 1 先固定 runtime 合同:长期状态 owner 预留给 <code>controller.html</code>,
2048+        <code>background.js</code> 只负责 bootstrap、聚合轻量状态和聚焦控制页。
2049+      </p>
2050+    </section>
2051+
2052+    <section class="actions">
2053+      <button id="refresh-btn" type="button">刷新状态</button>
2054+      <button id="focus-btn" type="button">聚焦控制页</button>
2055+    </section>
2056+
2057+    <section class="grid">
2058+      <article class="card">
2059+        <p class="label">Runtime Owner</p>
2060+        <p id="runtime-owner" class="value">controller.html</p>
2061+        <p class="meta">Safari 首版不把 WS / 状态机 owner 默认塞进 background。</p>
2062+      </article>
2063+      <article class="card">
2064+        <p class="label">Background</p>
2065+        <p id="background-status" class="value off">等待同步</p>
2066+        <p id="background-meta" class="meta">只承担 bootstrap / tab orchestration。</p>
2067+      </article>
2068+      <article class="card">
2069+        <p class="label">Content Script</p>
2070+        <p id="content-status" class="value off">暂无页面回报</p>
2071+        <p id="content-meta" class="meta">等待 AI 页面注入占位脚本。</p>
2072+      </article>
2073+      <article class="card">
2074+        <p class="label">Page Interceptor</p>
2075+        <p id="interceptor-status" class="value off">未注入</p>
2076+        <p id="interceptor-meta" class="meta">当前只保留页面侧占位钩子。</p>
2077+      </article>
2078+    </section>
2079+
2080+    <section class="panel">
2081+      <div class="panel-head">
2082+        <h2>职责边界</h2>
2083+      </div>
2084+      <pre id="contract-view" class="code"></pre>
2085+    </section>
2086+
2087+    <section class="panel">
2088+      <div class="panel-head">
2089+        <h2>最近状态</h2>
2090+      </div>
2091+      <pre id="state-view" class="code"></pre>
2092+    </section>
2093+
2094+    <section class="panel">
2095+      <div class="panel-head">
2096+        <h2>本地启用步骤</h2>
2097+      </div>
2098+      <ol class="steps">
2099+        <li>先构建并启动 <code>BAASafariHost</code> 宿主 App。</li>
2100+        <li>在 Safari 设置里启用 BAA 扩展。</li>
2101+        <li>给 Claude / ChatGPT / Gemini 站点授权。</li>
2102+        <li>点击扩展按钮,让 <code>background.js</code> 打开本控制页。</li>
2103+      </ol>
2104+    </section>
2105+  </main>
2106+
2107+  <script src="controller.js"></script>
2108+</body>
2109+</html>
2110diff --git a/plugins/baa-safari/controller.js b/plugins/baa-safari/controller.js
2111new file mode 100644
2112index 0000000000000000000000000000000000000000..bcf0ab3fd4323d16e2ef0aaf1574e65ec99b3c4c
2113--- /dev/null
2114+++ b/plugins/baa-safari/controller.js
2115@@ -0,0 +1,129 @@
2116+const REFRESH_INTERVAL_MS = 2_000;
2117+
2118+const elements = {
2119+  backgroundMeta: document.getElementById("background-meta"),
2120+  backgroundStatus: document.getElementById("background-status"),
2121+  contentMeta: document.getElementById("content-meta"),
2122+  contentStatus: document.getElementById("content-status"),
2123+  contractView: document.getElementById("contract-view"),
2124+  focusButton: document.getElementById("focus-btn"),
2125+  interceptorMeta: document.getElementById("interceptor-meta"),
2126+  interceptorStatus: document.getElementById("interceptor-status"),
2127+  refreshButton: document.getElementById("refresh-btn"),
2128+  runtimeOwner: document.getElementById("runtime-owner"),
2129+  stateView: document.getElementById("state-view")
2130+};
2131+
2132+function formatTimestamp(value) {
2133+  return Number.isFinite(value) ? new Date(value).toLocaleString("zh-CN", { hour12: false }) : "未记录";
2134+}
2135+
2136+function stringify(value) {
2137+  return JSON.stringify(value, null, 2);
2138+}
2139+
2140+function setValue(node, text, active = true) {
2141+  if (!node) {
2142+    return;
2143+  }
2144+
2145+  node.textContent = text;
2146+  node.classList.toggle("off", !active);
2147+}
2148+
2149+function buildContractSnapshot() {
2150+  return {
2151+    background_js: "bootstrap / focus controller / aggregate placeholder state",
2152+    content_script_js: "page entrypoint and injector placeholder",
2153+    controller_html: "intended long-lived runtime owner for WS and state machine",
2154+    controller_js: "render runtime contract and bootstrap shell diagnostics",
2155+    page_interceptor_js: "page-world placeholder only; no final-message or proxy_delivery yet",
2156+    runtime_owner: "controller.html"
2157+  };
2158+}
2159+
2160+async function readCurrentTabId() {
2161+  if (!browser?.tabs?.getCurrent) {
2162+    return null;
2163+  }
2164+
2165+  try {
2166+    const tab = await browser.tabs.getCurrent();
2167+    return Number.isInteger(tab?.id) ? tab.id : null;
2168+  } catch {
2169+    return null;
2170+  }
2171+}
2172+
2173+async function fetchState() {
2174+  return browser.runtime.sendMessage({
2175+    type: "baa_safari.get_state"
2176+  });
2177+}
2178+
2179+function renderState(state) {
2180+  elements.runtimeOwner.textContent = state.runtimeOwner;
2181+  setValue(
2182+    elements.backgroundStatus,
2183+    state.background.startedAt == null ? "未启动" : "已启动",
2184+    state.background.startedAt != null
2185+  );
2186+  elements.backgroundMeta.textContent =
2187+    `最近同步: ${formatTimestamp(state.lastUpdatedAt)}\ncontrollerTabId: ${state.controller.tabId ?? "无"}`;
2188+
2189+  const lastContent = state.lastContentScript ?? null;
2190+  setValue(
2191+    elements.contentStatus,
2192+    lastContent == null ? "暂无页面回报" : (lastContent.platform || "unknown"),
2193+    lastContent != null
2194+  );
2195+  elements.contentMeta.textContent =
2196+    lastContent == null
2197+      ? "等待 Claude / ChatGPT / Gemini 页面触发 content-script。"
2198+      : `${lastContent.url}\n最近触达: ${formatTimestamp(lastContent.seenAt)}`;
2199+
2200+  const lastInterceptor = state.lastPageInterceptor ?? null;
2201+  setValue(
2202+    elements.interceptorStatus,
2203+    lastInterceptor == null ? "未注入" : "已注入",
2204+    lastInterceptor != null
2205+  );
2206+  elements.interceptorMeta.textContent =
2207+    lastInterceptor == null
2208+      ? "还没有页面 world 侧占位事件。"
2209+      : `${lastInterceptor.url}\n最近触达: ${formatTimestamp(lastInterceptor.seenAt)}`;
2210+
2211+  elements.contractView.textContent = stringify(buildContractSnapshot());
2212+  elements.stateView.textContent = stringify(state);
2213+}
2214+
2215+async function refreshState() {
2216+  const state = await fetchState();
2217+  renderState(state);
2218+}
2219+
2220+async function boot() {
2221+  const tabId = await readCurrentTabId();
2222+
2223+  await browser.runtime.sendMessage({
2224+    tabId,
2225+    type: "baa_safari.controller_ready"
2226+  });
2227+
2228+  await refreshState();
2229+
2230+  elements.refreshButton?.addEventListener("click", () => {
2231+    void refreshState();
2232+  });
2233+  elements.focusButton?.addEventListener("click", () => {
2234+    void browser.runtime.sendMessage({
2235+      type: "baa_safari.focus_controller"
2236+    });
2237+  });
2238+
2239+  globalThis.setInterval(() => {
2240+    void refreshState();
2241+  }, REFRESH_INTERVAL_MS);
2242+}
2243+
2244+void boot();
2245diff --git a/plugins/baa-safari/icons/128.png b/plugins/baa-safari/icons/128.png
2246new file mode 100644
2247index 0000000000000000000000000000000000000000..423b491de9f0c035d4c9d7736c535bebfe078c8e
2248Binary files /dev/null and b/plugins/baa-safari/icons/128.png differ
2249diff --git a/plugins/baa-safari/icons/48.png b/plugins/baa-safari/icons/48.png
2250new file mode 100644
2251index 0000000000000000000000000000000000000000..e5bb1f5ad3a8b1842c8a6020d85f3aab85586956
2252Binary files /dev/null and b/plugins/baa-safari/icons/48.png differ
2253diff --git a/plugins/baa-safari/icons/96.png b/plugins/baa-safari/icons/96.png
2254new file mode 100644
2255index 0000000000000000000000000000000000000000..604f9c2730425810c11ca374123c4819bc82b9a4
2256Binary files /dev/null and b/plugins/baa-safari/icons/96.png differ
2257diff --git a/plugins/baa-safari/manifest.json b/plugins/baa-safari/manifest.json
2258new file mode 100644
2259index 0000000000000000000000000000000000000000..1a1f7dbe5930ee80aeea1409bd10e88ecb06257d
2260--- /dev/null
2261+++ b/plugins/baa-safari/manifest.json
2262@@ -0,0 +1,47 @@
2263+{
2264+  "manifest_version": 2,
2265+  "name": "BAA Safari",
2266+  "version": "0.1.0",
2267+  "description": "Minimal Safari Web Extension shell for BAA conductor integration.",
2268+  "icons": {
2269+    "48": "icons/48.png",
2270+    "96": "icons/96.png",
2271+    "128": "icons/128.png"
2272+  },
2273+  "permissions": [
2274+    "storage",
2275+    "tabs",
2276+    "webNavigation",
2277+    "webRequest",
2278+    "https://claude.ai/*",
2279+    "https://chatgpt.com/*",
2280+    "https://chat.openai.com/*",
2281+    "https://gemini.google.com/*"
2282+  ],
2283+  "background": {
2284+    "scripts": [
2285+      "background.js"
2286+    ],
2287+    "persistent": true
2288+  },
2289+  "browser_action": {
2290+    "default_title": "BAA Safari"
2291+  },
2292+  "content_scripts": [
2293+    {
2294+      "matches": [
2295+        "https://claude.ai/*",
2296+        "https://chatgpt.com/*",
2297+        "https://chat.openai.com/*",
2298+        "https://gemini.google.com/*"
2299+      ],
2300+      "js": [
2301+        "content-script.js"
2302+      ],
2303+      "run_at": "document_start"
2304+    }
2305+  ],
2306+  "web_accessible_resources": [
2307+    "page-interceptor.js"
2308+  ]
2309+}
2310diff --git a/plugins/baa-safari/page-interceptor.js b/plugins/baa-safari/page-interceptor.js
2311new file mode 100644
2312index 0000000000000000000000000000000000000000..45a8c64beda0bfc3fa3347d0deb56ebd5c7e87cf
2313--- /dev/null
2314+++ b/plugins/baa-safari/page-interceptor.js
2315@@ -0,0 +1,18 @@
2316+(function bootstrapBaaSafariPageInterceptor() {
2317+  if (globalThis.__BAA_SAFARI_PAGE_INTERCEPTOR__?.ready === true) {
2318+    return;
2319+  }
2320+
2321+  globalThis.__BAA_SAFARI_PAGE_INTERCEPTOR__ = {
2322+    ready: true,
2323+    installedAt: Date.now(),
2324+    url: globalThis.location?.href || null
2325+  };
2326+
2327+  document.dispatchEvent(new CustomEvent("baa:safari-page-interceptor-ready", {
2328+    detail: {
2329+      installedAt: globalThis.__BAA_SAFARI_PAGE_INTERCEPTOR__.installedAt,
2330+      url: globalThis.__BAA_SAFARI_PAGE_INTERCEPTOR__.url
2331+    }
2332+  }));
2333+})();