patches & fixes for 0.31.1 release (#219)

* chore: new browseros-server binaries

* bugfix: was writing chromium_patches/ in wrong location

* patch: ntp footer disabled by default

* patch: browseros alpha flag

* patch: add log for port saving

* chore: increment PATCH

* feat: use packages/browseros as root_dir properly in context.py
This commit is contained in:
Nikhil
2025-12-06 00:39:56 +00:00
committed by GitHub
parent 8f58d279bb
commit 2901e73547
22 changed files with 212 additions and 1101 deletions

View File

@@ -1,126 +0,0 @@
diff --git a/chrome/browser/extensions/browseros_extension_constants.h b/chrome/browser/extensions/browseros_extension_constants.h
new file mode 100644
index 0000000000000..5a3b518b224a7
--- /dev/null
+++ b/chrome/browser/extensions/browseros_extension_constants.h
@@ -0,0 +1,120 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_EXTENSIONS_BROWSEROS_EXTENSION_CONSTANTS_H_
+#define CHROME_BROWSER_EXTENSIONS_BROWSEROS_EXTENSION_CONSTANTS_H_
+
+#include <cstddef>
+#include <optional>
+#include <string>
+#include <vector>
+
+namespace extensions {
+namespace browseros {
+
+// AI Agent Extension ID
+inline constexpr char kAISidePanelExtensionId[] =
+ "djhdjhlnljbjgejbndockeedocneiaei";
+
+// Agent V2 Extension ID
+inline constexpr char kAgentV2ExtensionId[] =
+ "bflpfmnmnokmjhmgnolecpppdbdophmk";
+
+// BrowserOS extension config URLs
+inline constexpr char kBrowserOSConfigUrl[] =
+ "https://cdn.browseros.com/extensions/extensions.json";
+inline constexpr char kBrowserOSAlphaConfigUrl[] =
+ "https://cdn.browseros.com/extensions/extensions.alpha.json";
+
+// Bug Reporter Extension ID
+inline constexpr char kBugReporterExtensionId[] =
+ "adlpneommgkgeanpaekgoaolcpncohkf";
+
+// Controller Extension ID
+inline constexpr char kControllerExtensionId[] =
+ "nlnihljpboknmfagkikhkdblbedophja";
+
+// BrowserOS CDN update manifest URL
+// Used for extensions installed from local .crx files that don't have
+// an update_url in their manifest
+inline constexpr char kBrowserOSUpdateUrl[] =
+ "https://cdn.browseros.com/extensions/update-manifest.xml";
+
+struct BrowserOSExtensionInfo {
+ const char* id;
+ const char* display_name;
+ bool is_pinned;
+ bool is_labelled;
+};
+
+inline constexpr BrowserOSExtensionInfo kBrowserOSExtensions[] = {
+ {kAISidePanelExtensionId, "BrowserOS", true, true},
+ {kBugReporterExtensionId, "BrowserOS/bug-reporter", true, false},
+ {kControllerExtensionId, "BrowserOS/controller", false, false},
+ {kAgentV2ExtensionId, "BrowserOS", true, true},
+};
+
+// Allowlist of BrowserOS extension IDs that are permitted to be installed.
+// Only extensions with these IDs will be loaded from the config.
+inline constexpr const char* kAllowedExtensions[] = {
+ kBrowserOSExtensions[0].id,
+ kBrowserOSExtensions[1].id,
+ kBrowserOSExtensions[2].id,
+ kBrowserOSExtensions[3].id,
+};
+
+inline constexpr size_t kBrowserOSExtensionsCount =
+ sizeof(kBrowserOSExtensions) / sizeof(kBrowserOSExtensions[0]);
+
+inline const BrowserOSExtensionInfo* FindBrowserOSExtensionInfo(
+ const std::string& extension_id) {
+ for (const auto& info : kBrowserOSExtensions) {
+ if (extension_id == info.id)
+ return &info;
+ }
+ return nullptr;
+}
+
+// Check if an extension is a BrowserOS extension
+inline bool IsBrowserOSExtension(const std::string& extension_id) {
+ return FindBrowserOSExtensionInfo(extension_id) != nullptr;
+}
+
+inline bool IsBrowserOSPinnedExtension(const std::string& extension_id) {
+ const BrowserOSExtensionInfo* info =
+ FindBrowserOSExtensionInfo(extension_id);
+ return info && info->is_pinned;
+}
+
+inline bool IsBrowserOSLabelledExtension(const std::string& extension_id) {
+ const BrowserOSExtensionInfo* info =
+ FindBrowserOSExtensionInfo(extension_id);
+ return info && info->is_labelled;
+}
+
+// Get all BrowserOS extension IDs
+inline std::vector<std::string> GetBrowserOSExtensionIds() {
+ std::vector<std::string> ids;
+ ids.reserve(kBrowserOSExtensionsCount);
+ for (const auto& info : kBrowserOSExtensions)
+ ids.push_back(info.id);
+ return ids;
+}
+
+// Get display name for BrowserOS extensions in omnibox
+// Returns the display name if extension_id is a BrowserOS extension,
+// otherwise returns std::nullopt
+inline std::optional<std::string> GetExtensionDisplayName(
+ const std::string& extension_id) {
+ if (const BrowserOSExtensionInfo* info =
+ FindBrowserOSExtensionInfo(extension_id)) {
+ return info->display_name;
+ }
+ return std::nullopt;
+}
+
+} // namespace browseros
+} // namespace extensions
+
+#endif // CHROME_BROWSER_EXTENSIONS_BROWSEROS_EXTENSION_CONSTANTS_H_

View File

@@ -1,763 +0,0 @@
diff --git a/chrome/browser/extensions/browseros_external_loader.cc b/chrome/browser/extensions/browseros_external_loader.cc
new file mode 100644
index 0000000000000..8f01037dfcb90
--- /dev/null
+++ b/chrome/browser/extensions/browseros_external_loader.cc
@@ -0,0 +1,757 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/extensions/browseros_external_loader.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/feature_list.h"
+#include "base/files/file_util.h"
+#include "base/functional/bind.h"
+#include "base/json/json_reader.h"
+#include "base/logging.h"
+#include "base/memory/ptr_util.h"
+#include "base/strings/string_util.h"
+#include "base/task/thread_pool.h"
+#include "base/task/single_thread_task_runner.h"
+#include "base/values.h"
+#include "chrome/browser/browser_features.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/extensions/browseros_extension_constants.h"
+#include "chrome/browser/extensions/extension_service.h"
+#include "chrome/browser/extensions/external_provider_impl.h"
+#include "chrome/browser/extensions/updater/extension_updater.h"
+#include "chrome/browser/profiles/profile.h"
+#include "components/metrics/browseros_metrics/browseros_metrics.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/storage_partition.h"
+#include "extensions/browser/disable_reason.h"
+#include "extensions/browser/extension_prefs.h"
+#include "extensions/browser/extension_registrar.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/browser/extension_system.h"
+#include "extensions/browser/pending_extension_manager.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/mojom/manifest.mojom-shared.h"
+#include "net/base/load_flags.h"
+#include "net/traffic_annotation/network_traffic_annotation.h"
+#include "services/network/public/cpp/resource_request.h"
+#include "services/network/public/cpp/simple_url_loader.h"
+#include "services/network/public/mojom/url_response_head.mojom.h"
+
+namespace extensions {
+
+namespace {
+
+// Interval for periodic maintenance
+constexpr base::TimeDelta kPeriodicMaintenanceInterval = base::Minutes(15);
+
+// Network traffic annotation for the extension configuration fetch.
+constexpr net::NetworkTrafficAnnotationTag kBrowserOSExtensionsFetchTrafficAnnotation =
+ net::DefineNetworkTrafficAnnotation("browseros_extensions_fetch", R"(
+ semantics {
+ sender: "BrowserOS External Extension Loader"
+ description:
+ "Fetches a JSON configuration file that specifies which extensions "
+ "should be installed for BrowserOS users at startup."
+ trigger:
+ "Triggered during browser startup when BrowserOS mode is enabled."
+ data:
+ "No user data is sent. Only a GET request to fetch the configuration."
+ destination: OTHER
+ destination_other:
+ "The BrowserOS configuration server specified by the config URL."
+ }
+ policy {
+ cookies_allowed: NO
+ setting:
+ "This feature can be controlled via command-line flags or "
+ "enterprise policies."
+ policy_exception_justification:
+ "Not implemented yet. This is a new feature for BrowserOS."
+ })");
+
+// Example JSON format:
+// {
+// "extensions": {
+// "extension_id_1": {
+// "external_update_url": "https://example.com/extension1/updates.xml"
+// },
+// "extension_id_2": {
+// "external_crx": "https://example.com/extension2.crx",
+// "external_version": "1.0"
+// }
+// }
+// }
+
+// Determines if a BrowserOS extension should be enabled based on the
+// kBrowserOsAlphaFeatures flag state.
+// - kAgentV2ExtensionId: enabled only when flag is ON
+// - kAISidePanelExtensionId: enabled only when flag is OFF
+// - All other extensions: always enabled
+bool ShouldExtensionBeEnabled(const std::string& extension_id) {
+ bool alpha_features_enabled =
+ base::FeatureList::IsEnabled(features::kBrowserOsAlphaFeatures);
+
+ if (extension_id == browseros::kAgentV2ExtensionId) {
+ return alpha_features_enabled;
+ }
+
+ if (extension_id == browseros::kAISidePanelExtensionId) {
+ return !alpha_features_enabled;
+ }
+
+ // All other BrowserOS extensions are always enabled
+ return true;
+}
+
+// Returns the appropriate config URL based on the alpha features flag.
+const char* GetConfigUrl() {
+ if (base::FeatureList::IsEnabled(features::kBrowserOsAlphaFeatures)) {
+ return browseros::kBrowserOSAlphaConfigUrl;
+ }
+ return browseros::kBrowserOSConfigUrl;
+}
+
+} // namespace
+
+BrowserOSExternalLoader::BrowserOSExternalLoader(Profile* profile)
+ : profile_(profile) {
+ // Select config URL based on alpha features flag
+ config_url_ = GURL(GetConfigUrl());
+
+ // Add known BrowserOS extension IDs
+ for (const char* extension_id : browseros::kAllowedExtensions) {
+ browseros_extension_ids_.insert(extension_id);
+ }
+}
+
+BrowserOSExternalLoader::~BrowserOSExternalLoader() = default;
+
+void BrowserOSExternalLoader::StartLoading() {
+ LOG(INFO) << "BrowserOS external extension loader starting...";
+
+ if (!config_file_for_testing_.empty()) {
+ LoadFromFile();
+ return;
+ }
+
+ if (!config_url_.is_valid()) {
+ LOG(ERROR) << "Invalid BrowserOS extensions config URL";
+ LoadFinished(base::Value::Dict());
+ return;
+ }
+
+ LOG(INFO) << "Fetching BrowserOS extensions from: " << config_url_.spec();
+
+ // Create the URL loader factory
+ url_loader_factory_ = profile_->GetDefaultStoragePartition()
+ ->GetURLLoaderFactoryForBrowserProcess();
+
+ // Create the resource request
+ auto resource_request = std::make_unique<network::ResourceRequest>();
+ resource_request->url = config_url_;
+ resource_request->method = "GET";
+ resource_request->load_flags = net::LOAD_BYPASS_CACHE | net::LOAD_DISABLE_CACHE;
+
+ // Create the URL loader
+ url_loader_ = network::SimpleURLLoader::Create(
+ std::move(resource_request), kBrowserOSExtensionsFetchTrafficAnnotation);
+
+ // Start the download
+ url_loader_->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
+ url_loader_factory_.get(),
+ base::BindOnce(&BrowserOSExternalLoader::OnURLFetchComplete,
+ weak_ptr_factory_.GetWeakPtr()));
+}
+
+void BrowserOSExternalLoader::OnURLFetchComplete(
+ std::unique_ptr<std::string> response_body) {
+ if (!response_body) {
+ LOG(ERROR) << "Failed to fetch BrowserOS extensions config from "
+ << config_url_.spec();
+ LoadFinished(base::Value::Dict());
+ return;
+ }
+
+ ParseConfiguration(*response_body);
+}
+
+void BrowserOSExternalLoader::ParseConfiguration(
+ const std::string& json_content) {
+ std::optional<base::Value> parsed_json = base::JSONReader::Read(json_content);
+
+ if (!parsed_json || !parsed_json->is_dict()) {
+ LOG(ERROR) << "Failed to parse BrowserOS extensions config JSON";
+ LoadFinished(base::Value::Dict());
+ return;
+ }
+
+ const base::Value::Dict* extensions_dict =
+ parsed_json->GetDict().FindDict("extensions");
+
+ if (!extensions_dict) {
+ LOG(ERROR) << "No 'extensions' key found in BrowserOS config";
+ LoadFinished(base::Value::Dict());
+ return;
+ }
+
+ // Create the prefs dictionary in the format expected by ExternalProviderImpl
+ base::Value::Dict prefs;
+
+ for (const auto [extension_id, extension_config] : *extensions_dict) {
+ if (!extension_config.is_dict()) {
+ LOG(WARNING) << "Invalid config for extension " << extension_id;
+ continue;
+ }
+
+ const base::Value::Dict& config_dict = extension_config.GetDict();
+ base::Value::Dict extension_prefs;
+
+ // Copy supported fields
+ if (const std::string* update_url =
+ config_dict.FindString(ExternalProviderImpl::kExternalUpdateUrl)) {
+ extension_prefs.Set(ExternalProviderImpl::kExternalUpdateUrl, *update_url);
+ }
+
+ if (const std::string* crx_path =
+ config_dict.FindString(ExternalProviderImpl::kExternalCrx)) {
+ extension_prefs.Set(ExternalProviderImpl::kExternalCrx, *crx_path);
+ }
+
+ if (const std::string* version =
+ config_dict.FindString(ExternalProviderImpl::kExternalVersion)) {
+ extension_prefs.Set(ExternalProviderImpl::kExternalVersion, *version);
+ }
+
+ // Add other supported fields as needed
+ std::optional<bool> keep_if_present =
+ config_dict.FindBool(ExternalProviderImpl::kKeepIfPresent);
+ if (keep_if_present.has_value()) {
+ extension_prefs.Set(ExternalProviderImpl::kKeepIfPresent,
+ keep_if_present.value());
+ }
+
+ if (!extension_prefs.empty()) {
+ prefs.Set(extension_id, std::move(extension_prefs));
+ }
+ }
+
+ LOG(INFO) << "Loaded " << prefs.size() << " extensions from BrowserOS config";
+
+ // Track the extension IDs we're managing
+ for (const auto [extension_id, _] : prefs) {
+ browseros_extension_ids_.insert(extension_id);
+ }
+
+ // Store the initial config for comparison
+ if (!extensions_dict->empty()) {
+ last_config_ = extensions_dict->Clone();
+ }
+
+ // Pass the prefs to the external provider system
+ LoadFinished(std::move(prefs));
+
+ // Immediately trigger high-priority installation of all BrowserOS extensions
+ // This ensures they get installed right away instead of waiting for Chrome's
+ // default external extension installation process
+ if (!browseros_extension_ids_.empty()) {
+ LOG(INFO) << "browseros: Triggering immediate high-priority installation for "
+ << browseros_extension_ids_.size() << " BrowserOS extensions";
+
+ // Use a delayed task to ensure the extension system is fully initialized
+ base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
+ FROM_HERE,
+ base::BindOnce(&BrowserOSExternalLoader::TriggerImmediateInstallation,
+ weak_ptr_factory_.GetWeakPtr()),
+ base::Seconds(2)); // Small delay to ensure extension system is ready
+ }
+
+ // Start periodic checking after initial load
+ StartPeriodicCheck();
+
+ // Log initial extension state at startup
+ CheckAndLogExtensionState("startup");
+}
+
+void BrowserOSExternalLoader::StartPeriodicCheck() {
+ LOG(INFO) << "browseros: Starting periodic maintenance (every "
+ << kPeriodicMaintenanceInterval.InMinutes() << " minutes)";
+
+ // Schedule the periodic maintenance
+ base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
+ FROM_HERE,
+ base::BindOnce(&BrowserOSExternalLoader::PeriodicMaintenance,
+ weak_ptr_factory_.GetWeakPtr()),
+ kPeriodicMaintenanceInterval);
+}
+
+void BrowserOSExternalLoader::PeriodicMaintenance() {
+ LOG(INFO) << "browseros: Running periodic maintenance";
+
+ if (!profile_) {
+ // Schedule next check even if profile isn't ready
+ StartPeriodicCheck();
+ return;
+ }
+
+ // 1. Check for and reinstall any uninstalled BrowserOS extensions
+ ReinstallUninstalledExtensions();
+
+ // 2. Re-enable any disabled BrowserOS extensions (respects flag state)
+ ReenableDisabledExtensions();
+
+ // 3. Enforce correct enabled/disabled state based on alpha features flag
+ EnforceExtensionStateBasedOnFlag();
+
+ // 4. Fetch latest config and check for changes
+ FetchAndCheckConfig();
+
+ // 5. Force immediate update check for all BrowserOS extensions
+ ForceUpdateCheck();
+
+ // 6. Log extension state after all maintenance attempts
+ CheckAndLogExtensionState("periodic_maintenance");
+
+ // Schedule the next maintenance
+ StartPeriodicCheck();
+}
+
+void BrowserOSExternalLoader::ReinstallUninstalledExtensions() {
+ ExtensionService* service = ExtensionSystem::Get(profile_)->extension_service();
+ if (!service) {
+ return;
+ }
+
+ ExtensionRegistry* registry = ExtensionRegistry::Get(profile_);
+ PendingExtensionManager* pending_manager = PendingExtensionManager::Get(profile_);
+
+ if (!registry || !pending_manager || last_config_.empty()) {
+ return;
+ }
+
+ for (const std::string& extension_id : browseros_extension_ids_) {
+ // Check if extension exists (installed or disabled)
+ if (registry->GetInstalledExtension(extension_id)) {
+ continue; // Extension is installed, skip to next
+ }
+
+ LOG(INFO) << "browseros: Extension " << extension_id
+ << " was uninstalled, attempting to reinstall";
+
+ // Find the extension's configuration
+ const base::Value::Dict* extension_config = last_config_.FindDict(extension_id);
+ if (!extension_config) {
+ LOG(WARNING) << "browseros: No config found for " << extension_id;
+ continue;
+ }
+
+ // Get the update URL from config
+ const std::string* update_url =
+ extension_config->FindString(ExternalProviderImpl::kExternalUpdateUrl);
+ if (!update_url) {
+ LOG(WARNING) << "browseros: No update URL found for " << extension_id;
+ continue;
+ }
+
+ // Validate and add to pending extensions
+ GURL update_gurl(*update_url);
+ if (!update_gurl.is_valid()) {
+ LOG(WARNING) << "browseros: Invalid update URL for " << extension_id;
+ continue;
+ }
+
+ // Add as pending extension for installation
+ pending_manager->AddFromExternalUpdateUrl(
+ extension_id,
+ std::string(), // No install param
+ update_gurl,
+ mojom::ManifestLocation::kExternalComponent,
+ Extension::WAS_INSTALLED_BY_DEFAULT,
+ false);
+
+ LOG(INFO) << "browseros: Added " << extension_id
+ << " to pending extensions for reinstall";
+
+ // Trigger immediate installation
+ ExtensionUpdater* updater = ExtensionUpdater::Get(profile_);
+ if (updater) {
+ ExtensionUpdater::CheckParams params;
+ params.ids = {extension_id};
+ params.install_immediately = true;
+ params.fetch_priority = DownloadFetchPriority::kForeground;
+ updater->CheckNow(std::move(params));
+ LOG(INFO) << "browseros: Triggered immediate install for " << extension_id;
+ }
+ }
+}
+
+void BrowserOSExternalLoader::ReenableDisabledExtensions() {
+ ExtensionService* service = ExtensionSystem::Get(profile_)->extension_service();
+ if (!service) {
+ return;
+ }
+
+ ExtensionRegistry* registry = ExtensionRegistry::Get(profile_);
+ ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_);
+
+ if (!registry || !prefs) {
+ return;
+ }
+
+ for (const std::string& extension_id : browseros_extension_ids_) {
+ // Check if extension is disabled
+ if (!registry->disabled_extensions().Contains(extension_id)) {
+ continue; // Extension is not disabled, skip to next
+ }
+
+ // Only re-enable if this extension should be enabled based on flag state
+ if (!ShouldExtensionBeEnabled(extension_id)) {
+ continue; // Extension should stay disabled based on flag
+ }
+
+ // Re-enable BrowserOS extensions regardless of disable reason
+ auto* registrar = extensions::ExtensionRegistrar::Get(profile_);
+ if (!registrar) {
+ LOG(WARNING) << "browseros: Cannot re-enable " << extension_id
+ << " because ExtensionRegistrar is unavailable";
+ continue;
+ }
+
+ LOG(INFO) << "browseros: Re-enabling extension " << extension_id;
+ registrar->EnableExtension(extension_id);
+ }
+}
+
+void BrowserOSExternalLoader::EnforceExtensionStateBasedOnFlag() {
+ if (!profile_) {
+ return;
+ }
+
+ ExtensionRegistry* registry = ExtensionRegistry::Get(profile_);
+ ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_);
+ auto* registrar = extensions::ExtensionRegistrar::Get(profile_);
+
+ if (!registry || !prefs || !registrar) {
+ return;
+ }
+
+ bool alpha_features_enabled =
+ base::FeatureList::IsEnabled(features::kBrowserOsAlphaFeatures);
+
+ LOG(INFO) << "browseros: Enforcing extension state (alpha_features="
+ << (alpha_features_enabled ? "ON" : "OFF") << ")";
+
+ for (const std::string& extension_id : browseros_extension_ids_) {
+ bool should_be_enabled = ShouldExtensionBeEnabled(extension_id);
+ bool is_enabled = registry->enabled_extensions().Contains(extension_id);
+ bool is_disabled = registry->disabled_extensions().Contains(extension_id);
+
+ // Skip if extension is not installed yet
+ if (!is_enabled && !is_disabled) {
+ continue;
+ }
+
+ if (should_be_enabled && is_disabled) {
+ // Extension should be enabled but is disabled - enable it
+ LOG(INFO) << "browseros: Enabling extension " << extension_id
+ << " (flag state requires it to be enabled)";
+ registrar->EnableExtension(extension_id);
+ } else if (!should_be_enabled && is_enabled) {
+ // Extension should be disabled but is enabled - disable it
+ LOG(INFO) << "browseros: Disabling extension " << extension_id
+ << " (flag state requires it to be disabled)";
+ registrar->DisableExtension(extension_id,
+ {disable_reason::DISABLE_USER_ACTION});
+ }
+ }
+}
+
+void BrowserOSExternalLoader::FetchAndCheckConfig() {
+ LOG(INFO) << "browseros: Fetching latest config to check for changes";
+
+ if (config_file_for_testing_.empty() && config_url_.is_valid()) {
+ // Fetch from URL
+ if (!url_loader_factory_) {
+ url_loader_factory_ = profile_->GetDefaultStoragePartition()
+ ->GetURLLoaderFactoryForBrowserProcess();
+ }
+
+ auto resource_request = std::make_unique<network::ResourceRequest>();
+ resource_request->url = config_url_;
+ resource_request->method = "GET";
+ resource_request->load_flags = net::LOAD_BYPASS_CACHE | net::LOAD_DISABLE_CACHE;
+
+ auto config_check_loader = network::SimpleURLLoader::Create(
+ std::move(resource_request), kBrowserOSExtensionsFetchTrafficAnnotation);
+
+ // Store the loader to keep it alive during the request
+ auto* loader_ptr = config_check_loader.get();
+ loader_ptr->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
+ url_loader_factory_.get(),
+ base::BindOnce(&BrowserOSExternalLoader::OnConfigCheckComplete,
+ weak_ptr_factory_.GetWeakPtr(),
+ std::move(config_check_loader)));
+ }
+}
+
+void BrowserOSExternalLoader::OnConfigCheckComplete(
+ std::unique_ptr<network::SimpleURLLoader> loader,
+ std::unique_ptr<std::string> response_body) {
+ if (!response_body) {
+ LOG(WARNING) << "browseros: Failed to fetch config for update check";
+ return;
+ }
+
+ std::optional<base::Value> parsed_json = base::JSONReader::Read(*response_body);
+ if (!parsed_json || !parsed_json->is_dict()) {
+ LOG(WARNING) << "browseros: Invalid config JSON during update check";
+ return;
+ }
+
+ const base::Value::Dict* extensions_dict =
+ parsed_json->GetDict().FindDict("extensions");
+ if (!extensions_dict) {
+ return;
+ }
+
+ // Check if config has changed
+ bool config_changed = false;
+ if (last_config_.empty()) {
+ config_changed = true; // First time
+ } else {
+ // Compare with last config
+ for (const auto [extension_id, new_config] : *extensions_dict) {
+ const base::Value::Dict* old_config = last_config_.FindDict(extension_id);
+ if (!old_config || *old_config != new_config.GetDict()) {
+ config_changed = true;
+ LOG(INFO) << "browseros: Config changed for extension " << extension_id;
+ break;
+ }
+ }
+
+ // Check for removed extensions
+ for (const auto [extension_id, _] : last_config_) {
+ if (!extensions_dict->contains(extension_id)) {
+ config_changed = true;
+ LOG(INFO) << "browseros: Extension " << extension_id << " removed from config";
+ break;
+ }
+ }
+ }
+
+ if (config_changed) {
+ LOG(INFO) << "browseros: Config has changed, reloading extensions";
+
+ // Store the new config
+ last_config_ = extensions_dict->Clone();
+
+ // Parse and reload with new config
+ ParseConfiguration(*response_body);
+ } else {
+ LOG(INFO) << "browseros: Config unchanged";
+ }
+}
+
+void BrowserOSExternalLoader::TriggerImmediateInstallation() {
+ if (!profile_ || browseros_extension_ids_.empty()) {
+ return;
+ }
+
+ LOG(INFO) << "browseros: Triggering immediate installation on first start";
+
+ // First, add all extensions to pending if they're not already installed
+ ExtensionRegistry* registry = ExtensionRegistry::Get(profile_);
+ PendingExtensionManager* pending_manager = PendingExtensionManager::Get(profile_);
+
+ if (registry && pending_manager && !last_config_.empty()) {
+ for (const std::string& extension_id : browseros_extension_ids_) {
+ // Skip if already installed
+ if (registry->GetInstalledExtension(extension_id)) {
+ LOG(INFO) << "browseros: Extension " << extension_id << " already installed";
+ continue;
+ }
+
+ // Add to pending extensions
+ const base::Value::Dict* extension_config = last_config_.FindDict(extension_id);
+ if (extension_config) {
+ const std::string* update_url =
+ extension_config->FindString(ExternalProviderImpl::kExternalUpdateUrl);
+ if (update_url) {
+ GURL update_gurl(*update_url);
+ if (update_gurl.is_valid()) {
+ pending_manager->AddFromExternalUpdateUrl(
+ extension_id,
+ std::string(), // No install param
+ update_gurl,
+ mojom::ManifestLocation::kExternalComponent,
+ Extension::WAS_INSTALLED_BY_DEFAULT,
+ false); // Don't mark acknowledged
+ LOG(INFO) << "browseros: Added " << extension_id
+ << " to pending for immediate installation";
+ }
+ }
+ }
+ }
+ }
+
+ // Now trigger immediate high-priority installation
+ ExtensionUpdater* updater = ExtensionUpdater::Get(profile_);
+ if (!updater) {
+ LOG(WARNING) << "browseros: No extension updater available for immediate installation";
+ return;
+ }
+
+ LOG(INFO) << "browseros: Executing CheckNow with immediate install for "
+ << browseros_extension_ids_.size() << " BrowserOS extensions";
+
+ // Create CheckParams for immediate foreground installation
+ ExtensionUpdater::CheckParams params;
+ params.ids = std::list<ExtensionId>(browseros_extension_ids_.begin(),
+ browseros_extension_ids_.end());
+ params.install_immediately = true;
+ params.fetch_priority = DownloadFetchPriority::kForeground;
+
+ // Trigger the installation
+ updater->CheckNow(std::move(params));
+
+ // Schedule enforcement of extension state after installation completes.
+ // This ensures the correct extension is enabled/disabled based on the
+ // kBrowserOsAlphaFeatures flag after fresh installation.
+ base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
+ FROM_HERE,
+ base::BindOnce(&BrowserOSExternalLoader::EnforceExtensionStateBasedOnFlag,
+ weak_ptr_factory_.GetWeakPtr()),
+ base::Seconds(30));
+}
+
+void BrowserOSExternalLoader::ForceUpdateCheck() {
+ if (!profile_ || browseros_extension_ids_.empty()) {
+ return;
+ }
+
+ ExtensionUpdater* updater = ExtensionUpdater::Get(profile_);
+ if (!updater) {
+ LOG(WARNING) << "browseros: No extension updater available";
+ return;
+ }
+
+ LOG(INFO) << "browseros: Forcing immediate update check for "
+ << browseros_extension_ids_.size() << " BrowserOS extensions";
+
+ // Create CheckParams for immediate foreground update
+ ExtensionUpdater::CheckParams params;
+ params.ids = std::list<ExtensionId>(browseros_extension_ids_.begin(),
+ browseros_extension_ids_.end());
+ params.install_immediately = true;
+ params.fetch_priority = DownloadFetchPriority::kForeground;
+
+ // Trigger the update check
+ updater->CheckNow(std::move(params));
+}
+
+void BrowserOSExternalLoader::LoadFromFile() {
+ // This runs on a background thread to avoid blocking the UI
+ base::ThreadPool::PostTaskAndReplyWithResult(
+ FROM_HERE,
+ {base::MayBlock(), base::TaskPriority::USER_VISIBLE},
+ base::BindOnce([](const base::FilePath& path) -> std::string {
+ std::string contents;
+ if (!base::ReadFileToString(path, &contents)) {
+ LOG(ERROR) << "Failed to read BrowserOS config file: " << path;
+ return std::string();
+ }
+ return contents;
+ }, config_file_for_testing_),
+ base::BindOnce(&BrowserOSExternalLoader::ParseConfiguration,
+ weak_ptr_factory_.GetWeakPtr()));
+}
+
+void BrowserOSExternalLoader::CheckAndLogExtensionState(
+ const std::string& context) {
+ if (!profile_) {
+ return;
+ }
+
+ ExtensionRegistry* registry = ExtensionRegistry::Get(profile_);
+ ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_);
+
+ if (!registry || !prefs) {
+ return;
+ }
+
+ for (const std::string& extension_id : browseros_extension_ids_) {
+ // If extension is enabled, it's healthy - skip logging
+ if (registry->enabled_extensions().Contains(extension_id)) {
+ continue;
+ }
+
+ // Extension is NOT enabled - gather diagnostic information
+ base::Value::Dict properties;
+ properties.Set("extension_id", extension_id);
+ properties.Set("context", context);
+
+ std::string state;
+
+ if (registry->disabled_extensions().Contains(extension_id)) {
+ state = "disabled";
+
+ // Get extension version if available
+ const Extension* extension = registry->disabled_extensions().GetByID(extension_id);
+ if (extension) {
+ properties.Set("version", extension->version().GetString());
+ }
+
+ // Get disable reasons using public API
+ DisableReasonSet disable_reasons = prefs->GetDisableReasons(extension_id);
+
+ // Convert to bitmask by ORing all reason values
+ int bitmask = 0;
+ for (disable_reason::DisableReason reason : disable_reasons) {
+ bitmask |= static_cast<int>(reason);
+ }
+ properties.Set("disable_reasons_bitmask", bitmask);
+
+ // Log individual disable reason flags for easy querying
+ properties.Set("reason_user_action",
+ disable_reasons.contains(disable_reason::DISABLE_USER_ACTION));
+ properties.Set("reason_permissions_increase",
+ disable_reasons.contains(disable_reason::DISABLE_PERMISSIONS_INCREASE));
+ properties.Set("reason_reload",
+ disable_reasons.contains(disable_reason::DISABLE_RELOAD));
+ properties.Set("reason_corrupted",
+ disable_reasons.contains(disable_reason::DISABLE_CORRUPTED));
+ properties.Set("reason_greylist",
+ disable_reasons.contains(disable_reason::DISABLE_GREYLIST));
+ properties.Set("reason_remote_install",
+ disable_reasons.contains(disable_reason::DISABLE_REMOTE_INSTALL));
+
+ } else if (registry->blocklisted_extensions().Contains(extension_id)) {
+ state = "blocklisted";
+
+ } else if (registry->blocked_extensions().Contains(extension_id)) {
+ state = "blocked";
+
+ } else if (registry->terminated_extensions().Contains(extension_id)) {
+ state = "terminated";
+
+ } else {
+ state = "not_installed";
+ }
+
+ properties.Set("state", state);
+
+ // Log to metrics
+ browseros_metrics::BrowserOSMetrics::Log("ota.extension.unexpected_state",
+ std::move(properties));
+
+ // Also log to Chrome logs for local debugging
+ LOG(WARNING) << "browseros: Extension " << extension_id
+ << " in unexpected state: " << state
+ << " (context: " << context << ")";
+ }
+}
+
+} // namespace extensions

View File

@@ -1,134 +0,0 @@
diff --git a/chrome/browser/extensions/browseros_external_loader.h b/chrome/browser/extensions/browseros_external_loader.h
new file mode 100644
index 0000000000000..0365a5e231215
--- /dev/null
+++ b/chrome/browser/extensions/browseros_external_loader.h
@@ -0,0 +1,128 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_EXTENSIONS_BROWSEROS_EXTERNAL_LOADER_H_
+#define CHROME_BROWSER_EXTENSIONS_BROWSEROS_EXTERNAL_LOADER_H_
+
+#include <memory>
+#include <set>
+#include <string>
+
+#include "base/files/file_path.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/timer/timer.h"
+#include "chrome/browser/extensions/external_loader.h"
+#include "services/network/public/cpp/simple_url_loader.h"
+
+class Profile;
+
+namespace network {
+class SharedURLLoaderFactory;
+} // namespace network
+
+namespace extensions {
+
+// A specialization of the ExternalLoader that loads extension information
+// from a remote URL. This is designed for BrowserOS to specify a set of
+// extensions that should be installed at startup.
+class BrowserOSExternalLoader : public ExternalLoader {
+ public:
+ explicit BrowserOSExternalLoader(Profile* profile);
+
+ BrowserOSExternalLoader(const BrowserOSExternalLoader&) = delete;
+ BrowserOSExternalLoader& operator=(const BrowserOSExternalLoader&) = delete;
+
+ // Sets the URL from which to fetch the extension configuration.
+ // Must be called before StartLoading().
+ void SetConfigUrl(const GURL& url) { config_url_ = url; }
+
+ // For testing: sets a local file path instead of fetching from URL.
+ void SetConfigFileForTesting(const base::FilePath& path) {
+ config_file_for_testing_ = path;
+ }
+
+ // Starts periodic maintenance loop (no-op if already running).
+ void StartPeriodicCheck();
+
+ // Periodic maintenance: re-enables disabled extensions, checks config, and forces updates
+ void PeriodicMaintenance();
+
+ // Fetches the latest config and checks for changes
+ void FetchAndCheckConfig();
+
+ // Forces immediate update check for BrowserOS extensions
+ void ForceUpdateCheck();
+
+ protected:
+ ~BrowserOSExternalLoader() override;
+
+ // ExternalLoader:
+ void StartLoading() override;
+
+ private:
+ friend class base::RefCountedThreadSafe<ExternalLoader>;
+
+ // Called when the URL fetch completes.
+ void OnURLFetchComplete(std::unique_ptr<std::string> response_body);
+
+ // Called when config check fetch completes
+ void OnConfigCheckComplete(std::unique_ptr<network::SimpleURLLoader> loader,
+ std::unique_ptr<std::string> response_body);
+
+ // Parses the fetched JSON configuration and loads extensions.
+ void ParseConfiguration(const std::string& json_content);
+
+ // Loads configuration from a local file (for testing).
+ void LoadFromFile();
+
+ // Checks for uninstalled BrowserOS extensions and reinstalls them
+ void ReinstallUninstalledExtensions();
+
+ // Re-enables BrowserOS extensions that were disabled by user action
+ void ReenableDisabledExtensions();
+
+ // Enforces extension enabled/disabled state based on kBrowserOsAlphaFeatures flag.
+ // - When flag is ON: enables kAgentV2ExtensionId, disables kAISidePanelExtensionId
+ // - When flag is OFF: enables kAISidePanelExtensionId, disables kAgentV2ExtensionId
+ void EnforceExtensionStateBasedOnFlag();
+
+ // Triggers immediate installation of all BrowserOS extensions on first start
+ void TriggerImmediateInstallation();
+
+ // Checks extension state and logs to metrics if not enabled
+ void CheckAndLogExtensionState(const std::string& context);
+
+ // The profile associated with this loader.
+ raw_ptr<Profile> profile_;
+
+ // URL from which to fetch the extension configuration.
+ GURL config_url_;
+
+ // For testing: local file path instead of URL.
+ base::FilePath config_file_for_testing_;
+
+ // URL loader for fetching the configuration.
+ std::unique_ptr<network::SimpleURLLoader> url_loader_;
+
+ // URLLoaderFactory for making network requests.
+ scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_;
+
+ // List of BrowserOS extension IDs to monitor
+ std::set<std::string> browseros_extension_ids_;
+
+ // Last fetched config for comparison
+ base::Value::Dict last_config_;
+
+ // Tracks whether we have successfully applied a configuration during this session.
+ bool has_successful_config_ = false;
+
+ base::RepeatingTimer periodic_timer_;
+
+ base::WeakPtrFactory<BrowserOSExternalLoader> weak_ptr_factory_{this};
+};
+
+} // namespace extensions
+
+#endif // CHROME_BROWSER_EXTENSIONS_BROWSEROS_EXTERNAL_LOADER_H_

View File

@@ -33,7 +33,6 @@ def create_build_context(chromium_src: Optional[Path] = None) -> Optional[Contex
return None
ctx = Context(
root_dir=Path.cwd(),
chromium_src=chromium_src,
architecture="", # Not needed for patch operations
build_type="debug", # Not needed for patch operations

View File

@@ -20,6 +20,7 @@ from .utils import (
IS_MACOS,
)
from .env import EnvConfig
from .paths import get_package_root
# =============================================================================
@@ -182,7 +183,7 @@ class Context:
Context Object pattern - ONE place for all build state
"""
root_dir: Path
root_dir: Path = field(default_factory=get_package_root)
chromium_src: Path = Path()
out_dir: str = "out/Default"
architecture: str = "" # Will be set in __post_init__
@@ -301,9 +302,9 @@ class Context:
"""
Initialize context from config
Replaces __post_init__ logic for better testability
"""
root_dir = Path(config.get("root_dir", Path.cwd()))
Note: root_dir is always computed from package location, never from config.
"""
chromium_src = (
Path(config.get("chromium_src", ""))
if config.get("chromium_src")
@@ -313,9 +314,8 @@ class Context:
# Get architecture or use platform default
arch = config.get("architecture") or get_platform_arch()
# Create instance
# Create instance - root_dir uses default_factory (get_package_root)
ctx = cls(
root_dir=root_dir,
chromium_src=chromium_src,
architecture=arch,
build_type=config.get("build_type", "debug"),

View File

@@ -16,12 +16,10 @@ from dotenv import load_dotenv
def _load_dotenv_file():
"""Load .env file from project root (packages/browseros parent directory)"""
# Find project root by going up from this file's location
# This file is at: packages/browseros/build/common/env.py
# Project root is at: packages/browseros/../../ (the repo root)
current_dir = Path(__file__).parent # common/
browseros_root = current_dir.parent.parent # packages/browseros/
"""Load .env file from project root"""
from .paths import get_package_root
browseros_root = get_package_root()
project_root = browseros_root.parent.parent # repo root
# Try loading .env from multiple locations (most specific first)

View File

@@ -16,8 +16,10 @@ def _ensure_log_file():
"""Ensure log file is created with timestamp"""
global _log_file
if _log_file is None:
from .paths import get_package_root
# Create logs directory if it doesn't exist
log_dir = Path(__file__).parent.parent / "logs"
log_dir = get_package_root() / "logs"
log_dir.mkdir(exist_ok=True)
# Create log file with timestamp

View File

@@ -0,0 +1,45 @@
#!/usr/bin/env python3
"""
Package root detection for BrowserOS build system
This module provides a single function to find the browseros package root
directory, regardless of the current working directory.
IMPORTANT: This module must have NO local imports to avoid circular dependencies.
It is imported by both context.py and env.py at module load time.
"""
import re
from functools import lru_cache
from pathlib import Path
@lru_cache(maxsize=1)
def get_package_root() -> Path:
"""Find browseros package root by walking up looking for pyproject.toml.
Walks up from this file's location looking for a pyproject.toml that
contains the browseros package definition.
Returns:
Path to the browseros package root (e.g., packages/browseros/)
Raises:
RuntimeError: If package root cannot be found
"""
current = Path(__file__).resolve().parent
while current != current.parent:
pyproject = current / "pyproject.toml"
if pyproject.exists():
content = pyproject.read_text()
# Match name = "browseros" with flexible whitespace
if re.search(r'^name\s*=\s*["\']browseros["\']', content, re.MULTILINE):
return current
current = current.parent
raise RuntimeError(
"Could not find browseros package root. "
"Expected to find pyproject.toml with name = 'browseros' "
f"in ancestors of {Path(__file__).resolve()}"
)

View File

@@ -30,14 +30,12 @@ from .utils import get_platform_arch, log_info
def resolve_config(
cli_args: Dict[str, Any],
yaml_config: Optional[Dict[str, Any]] = None,
root_dir: Optional[Path] = None,
) -> Context:
"""Resolve build configuration - single entry point.
Args:
cli_args: Dictionary of CLI arguments (all values should be None if not provided)
yaml_config: Optional YAML configuration (triggers CONFIG mode)
root_dir: Optional root directory (defaults to CWD)
Returns:
Fully resolved Context object
@@ -48,24 +46,25 @@ def resolve_config(
Modes:
- CONFIG mode (yaml_config provided): YAML is authoritative
- DIRECT mode (no yaml_config): CLI > Env > Defaults
"""
root_dir = root_dir or Path.cwd()
Note:
root_dir is always computed from package location via get_package_root(),
never from config or cwd.
"""
if yaml_config:
return _resolve_config_mode(yaml_config, cli_args, root_dir)
return _resolve_config_mode(yaml_config, cli_args)
else:
return _resolve_direct_mode(cli_args, root_dir)
return _resolve_direct_mode(cli_args)
def _resolve_config_mode(
yaml_config: Dict[str, Any], cli_args: Dict[str, Any], root_dir: Path
yaml_config: Dict[str, Any], cli_args: Dict[str, Any]
) -> Context:
"""CONFIG MODE: YAML is base, CLI can override.
Args:
yaml_config: YAML configuration dictionary
cli_args: CLI arguments (can override YAML values)
root_dir: Project root directory
Returns:
Context with values from YAML, optionally overridden by CLI
@@ -116,19 +115,17 @@ def _resolve_config_mode(
log_info(f"✓ CONFIG MODE: build_type={build_type} ({build_type_source})")
return Context(
root_dir=root_dir,
chromium_src=chromium_src,
architecture=architecture,
build_type=build_type,
)
def _resolve_direct_mode(cli_args: Dict[str, Any], root_dir: Path) -> Context:
def _resolve_direct_mode(cli_args: Dict[str, Any]) -> Context:
"""DIRECT MODE: CLI > Env > Defaults.
Args:
cli_args: CLI arguments (None if not provided by user)
root_dir: Project root directory
Returns:
Context with resolved values
@@ -171,7 +168,6 @@ def _resolve_direct_mode(cli_args: Dict[str, Any], root_dir: Path) -> Context:
log_info(f"✓ DIRECT MODE: build_type={build_type} (cli/default)")
return Context(
root_dir=root_dir,
chromium_src=chromium_src,
architecture=architecture,
build_type=build_type,

View File

@@ -264,16 +264,15 @@ def handle_merge_command(
log_error(f"Architecture 2 app not found: {arch2_path}")
return False
# Get root_dir from where this module is located
root_dir = Path(__file__).parent.parent.parent.parent
log_info(f"📂 Using root directory: {root_dir}")
# Auto-generate output path in chromium source
# Get the app name from BuildContext
from ...common.context import Context
from ...common.paths import get_package_root
root_dir = get_package_root()
log_info(f"📂 Using root directory: {root_dir}")
temp_ctx = Context(
root_dir=root_dir,
chromium_src=chromium_src,
architecture="universal",
build_type="release",

View File

@@ -1,9 +1,9 @@
diff --git a/chrome/browser/browseros_server/browseros_server_manager.cc b/chrome/browser/browseros_server/browseros_server_manager.cc
new file mode 100644
index 0000000000000..81ab3d65f5a38
index 0000000000000..0a3a2d12d08fc
--- /dev/null
+++ b/chrome/browser/browseros_server/browseros_server_manager.cc
@@ -0,0 +1,1072 @@
@@ -0,0 +1,1076 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
@@ -315,6 +315,7 @@ index 0000000000000..81ab3d65f5a38
+void BrowserOSServerManager::SavePortsToPrefs() {
+ PrefService* prefs = g_browser_process->local_state();
+ if (!prefs) {
+ LOG(WARNING) << "browseros: SavePortsToPrefs - no prefs available, skipping save";
+ return;
+ }
+
@@ -324,7 +325,10 @@ index 0000000000000..81ab3d65f5a38
+ prefs->SetInteger(browseros_server::kExtensionServerPort, extension_port_);
+ prefs->SetBoolean(browseros_server::kMCPServerEnabled, mcp_enabled_);
+
+ LOG(INFO) << "browseros: Saved finalized ports to prefs";
+ LOG(INFO) << "browseros: Saving to prefs - CDP: " << cdp_port_
+ << ", MCP: " << mcp_port_ << ", Agent: " << agent_port_
+ << ", Extension: " << extension_port_
+ << ", MCP enabled: " << (mcp_enabled_ ? "true" : "false");
+}
+
+void BrowserOSServerManager::Start() {

View File

@@ -1,9 +1,9 @@
diff --git a/chrome/browser/extensions/browseros_extension_constants.h b/chrome/browser/extensions/browseros_extension_constants.h
new file mode 100644
index 0000000000000..e2ffd24bff8d5
index 0000000000000..5a3b518b224a7
--- /dev/null
+++ b/chrome/browser/extensions/browseros_extension_constants.h
@@ -0,0 +1,118 @@
@@ -0,0 +1,120 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
@@ -27,6 +27,12 @@ index 0000000000000..e2ffd24bff8d5
+inline constexpr char kAgentV2ExtensionId[] =
+ "bflpfmnmnokmjhmgnolecpppdbdophmk";
+
+// BrowserOS extension config URLs
+inline constexpr char kBrowserOSConfigUrl[] =
+ "https://cdn.browseros.com/extensions/extensions.json";
+inline constexpr char kBrowserOSAlphaConfigUrl[] =
+ "https://cdn.browseros.com/extensions/extensions.alpha.json";
+
+// Bug Reporter Extension ID
+inline constexpr char kBugReporterExtensionId[] =
+ "adlpneommgkgeanpaekgoaolcpncohkf";
@@ -41,10 +47,6 @@ index 0000000000000..e2ffd24bff8d5
+inline constexpr char kBrowserOSUpdateUrl[] =
+ "https://cdn.browseros.com/extensions/update-manifest.xml";
+
+// BrowserOS extension config URL
+inline constexpr char kBrowserOSConfigUrl[] =
+ "https://cdn.browseros.com/extensions/extensions.json";
+
+struct BrowserOSExtensionInfo {
+ const char* id;
+ const char* display_name;
@@ -56,7 +58,7 @@ index 0000000000000..e2ffd24bff8d5
+ {kAISidePanelExtensionId, "BrowserOS", true, true},
+ {kBugReporterExtensionId, "BrowserOS/bug-reporter", true, false},
+ {kControllerExtensionId, "BrowserOS/controller", false, false},
+ {kAgentV2ExtensionId, "BrowserOS", false, false},
+ {kAgentV2ExtensionId, "BrowserOS", true, true},
+};
+
+// Allowlist of BrowserOS extension IDs that are permitted to be installed.

View File

@@ -1,9 +1,9 @@
diff --git a/chrome/browser/extensions/browseros_external_loader.cc b/chrome/browser/extensions/browseros_external_loader.cc
new file mode 100644
index 0000000000000..70842c316df3c
index 0000000000000..8f01037dfcb90
--- /dev/null
+++ b/chrome/browser/extensions/browseros_external_loader.cc
@@ -0,0 +1,669 @@
@@ -0,0 +1,757 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
@@ -13,6 +13,7 @@ index 0000000000000..70842c316df3c
+#include <memory>
+#include <utility>
+
+#include "base/feature_list.h"
+#include "base/files/file_util.h"
+#include "base/functional/bind.h"
+#include "base/json/json_reader.h"
@@ -22,6 +23,7 @@ index 0000000000000..70842c316df3c
+#include "base/task/thread_pool.h"
+#include "base/task/single_thread_task_runner.h"
+#include "base/values.h"
+#include "chrome/browser/browser_features.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/extensions/browseros_extension_constants.h"
+#include "chrome/browser/extensions/extension_service.h"
@@ -49,10 +51,6 @@ index 0000000000000..70842c316df3c
+
+namespace {
+
+// Default config URL - should be updated to actual BrowserOS server
+// Can be overridden via --browseros-extensions-url command line flag
+constexpr char kBrowserOSConfigUrl[] = "https://cdn.browseros.com/extensions/extensions.json";
+
+// Interval for periodic maintenance
+constexpr base::TimeDelta kPeriodicMaintenanceInterval = base::Minutes(15);
+
@@ -94,12 +92,41 @@ index 0000000000000..70842c316df3c
+// }
+// }
+
+// Determines if a BrowserOS extension should be enabled based on the
+// kBrowserOsAlphaFeatures flag state.
+// - kAgentV2ExtensionId: enabled only when flag is ON
+// - kAISidePanelExtensionId: enabled only when flag is OFF
+// - All other extensions: always enabled
+bool ShouldExtensionBeEnabled(const std::string& extension_id) {
+ bool alpha_features_enabled =
+ base::FeatureList::IsEnabled(features::kBrowserOsAlphaFeatures);
+
+ if (extension_id == browseros::kAgentV2ExtensionId) {
+ return alpha_features_enabled;
+ }
+
+ if (extension_id == browseros::kAISidePanelExtensionId) {
+ return !alpha_features_enabled;
+ }
+
+ // All other BrowserOS extensions are always enabled
+ return true;
+}
+
+// Returns the appropriate config URL based on the alpha features flag.
+const char* GetConfigUrl() {
+ if (base::FeatureList::IsEnabled(features::kBrowserOsAlphaFeatures)) {
+ return browseros::kBrowserOSAlphaConfigUrl;
+ }
+ return browseros::kBrowserOSConfigUrl;
+}
+
+} // namespace
+
+BrowserOSExternalLoader::BrowserOSExternalLoader(Profile* profile)
+ : profile_(profile) {
+ // Default config URL - can be overridden via SetConfigUrl
+ config_url_ = GURL(kBrowserOSConfigUrl);
+ // Select config URL based on alpha features flag
+ config_url_ = GURL(GetConfigUrl());
+
+ // Add known BrowserOS extension IDs
+ for (const char* extension_id : browseros::kAllowedExtensions) {
@@ -278,17 +305,20 @@ index 0000000000000..70842c316df3c
+
+ // 1. Check for and reinstall any uninstalled BrowserOS extensions
+ ReinstallUninstalledExtensions();
+
+ // 2. Re-enable any disabled BrowserOS extensions
+
+ // 2. Re-enable any disabled BrowserOS extensions (respects flag state)
+ ReenableDisabledExtensions();
+
+ // 3. Fetch latest config and check for changes
+
+ // 3. Enforce correct enabled/disabled state based on alpha features flag
+ EnforceExtensionStateBasedOnFlag();
+
+ // 4. Fetch latest config and check for changes
+ FetchAndCheckConfig();
+
+ // 4. Force immediate update check for all BrowserOS extensions
+
+ // 5. Force immediate update check for all BrowserOS extensions
+ ForceUpdateCheck();
+
+ // 5. Log extension state after all maintenance attempts
+ // 6. Log extension state after all maintenance attempts
+ CheckAndLogExtensionState("periodic_maintenance");
+
+ // Schedule the next maintenance
@@ -383,6 +413,11 @@ index 0000000000000..70842c316df3c
+ continue; // Extension is not disabled, skip to next
+ }
+
+ // Only re-enable if this extension should be enabled based on flag state
+ if (!ShouldExtensionBeEnabled(extension_id)) {
+ continue; // Extension should stay disabled based on flag
+ }
+
+ // Re-enable BrowserOS extensions regardless of disable reason
+ auto* registrar = extensions::ExtensionRegistrar::Get(profile_);
+ if (!registrar) {
@@ -396,6 +431,50 @@ index 0000000000000..70842c316df3c
+ }
+}
+
+void BrowserOSExternalLoader::EnforceExtensionStateBasedOnFlag() {
+ if (!profile_) {
+ return;
+ }
+
+ ExtensionRegistry* registry = ExtensionRegistry::Get(profile_);
+ ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_);
+ auto* registrar = extensions::ExtensionRegistrar::Get(profile_);
+
+ if (!registry || !prefs || !registrar) {
+ return;
+ }
+
+ bool alpha_features_enabled =
+ base::FeatureList::IsEnabled(features::kBrowserOsAlphaFeatures);
+
+ LOG(INFO) << "browseros: Enforcing extension state (alpha_features="
+ << (alpha_features_enabled ? "ON" : "OFF") << ")";
+
+ for (const std::string& extension_id : browseros_extension_ids_) {
+ bool should_be_enabled = ShouldExtensionBeEnabled(extension_id);
+ bool is_enabled = registry->enabled_extensions().Contains(extension_id);
+ bool is_disabled = registry->disabled_extensions().Contains(extension_id);
+
+ // Skip if extension is not installed yet
+ if (!is_enabled && !is_disabled) {
+ continue;
+ }
+
+ if (should_be_enabled && is_disabled) {
+ // Extension should be enabled but is disabled - enable it
+ LOG(INFO) << "browseros: Enabling extension " << extension_id
+ << " (flag state requires it to be enabled)";
+ registrar->EnableExtension(extension_id);
+ } else if (!should_be_enabled && is_enabled) {
+ // Extension should be disabled but is enabled - disable it
+ LOG(INFO) << "browseros: Disabling extension " << extension_id
+ << " (flag state requires it to be disabled)";
+ registrar->DisableExtension(extension_id,
+ {disable_reason::DISABLE_USER_ACTION});
+ }
+ }
+}
+
+void BrowserOSExternalLoader::FetchAndCheckConfig() {
+ LOG(INFO) << "browseros: Fetching latest config to check for changes";
+
@@ -536,13 +615,22 @@ index 0000000000000..70842c316df3c
+
+ // Create CheckParams for immediate foreground installation
+ ExtensionUpdater::CheckParams params;
+ params.ids = std::list<ExtensionId>(browseros_extension_ids_.begin(),
+ params.ids = std::list<ExtensionId>(browseros_extension_ids_.begin(),
+ browseros_extension_ids_.end());
+ params.install_immediately = true;
+ params.fetch_priority = DownloadFetchPriority::kForeground;
+
+
+ // Trigger the installation
+ updater->CheckNow(std::move(params));
+
+ // Schedule enforcement of extension state after installation completes.
+ // This ensures the correct extension is enabled/disabled based on the
+ // kBrowserOsAlphaFeatures flag after fresh installation.
+ base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
+ FROM_HERE,
+ base::BindOnce(&BrowserOSExternalLoader::EnforceExtensionStateBasedOnFlag,
+ weak_ptr_factory_.GetWeakPtr()),
+ base::Seconds(30));
+}
+
+void BrowserOSExternalLoader::ForceUpdateCheck() {

View File

@@ -1,9 +1,9 @@
diff --git a/chrome/browser/extensions/browseros_external_loader.h b/chrome/browser/extensions/browseros_external_loader.h
new file mode 100644
index 0000000000000..33642a2fec9ee
index 0000000000000..0365a5e231215
--- /dev/null
+++ b/chrome/browser/extensions/browseros_external_loader.h
@@ -0,0 +1,123 @@
@@ -0,0 +1,128 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
@@ -88,6 +88,11 @@ index 0000000000000..33642a2fec9ee
+
+ // Re-enables BrowserOS extensions that were disabled by user action
+ void ReenableDisabledExtensions();
+
+ // Enforces extension enabled/disabled state based on kBrowserOsAlphaFeatures flag.
+ // - When flag is ON: enables kAgentV2ExtensionId, disables kAISidePanelExtensionId
+ // - When flag is OFF: enables kAISidePanelExtensionId, disables kAgentV2ExtensionId
+ void EnforceExtensionStateBasedOnFlag();
+
+ // Triggers immediate installation of all BrowserOS extensions on first start
+ void TriggerImmediateInstallation();

View File

@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "browseros"
version = "0.29.0"
version = "0.0.1"
description = "BrowserOS Build System"
requires-python = ">=3.12"
dependencies = [
@@ -21,18 +21,18 @@ browseros = "build.browseros:app"
[tool.setuptools]
packages = [
"build",
"build.cli",
"build.common",
"build.modules",
"build.modules.resources",
"build.modules.patches",
"build.modules.setup",
"build.modules.package",
"build.modules.sign",
"build.modules.extract",
"build.modules.apply",
"build.modules.feature"
"build",
"build.cli",
"build.common",
"build.modules",
"build.modules.resources",
"build.modules.patches",
"build.modules.setup",
"build.modules.package",
"build.modules.sign",
"build.modules.extract",
"build.modules.apply",
"build.modules.feature",
]
[tool.black]
@@ -65,8 +65,4 @@ line_length = 88
skip_glob = ["env/*", "chromium_src/*", "chromium_src_bak/*", "third_party/*"]
[dependency-groups]
dev = [
"ruff>=0.14.7",
"pyright>=1.1.390",
]
dev = ["ruff>=0.14.7", "pyright>=1.1.390"]

View File

@@ -1,4 +1,4 @@
BROWSEROS_MAJOR=0
BROWSEROS_MINOR=31
BROWSEROS_BUILD=0
BROWSEROS_PATCH=4
BROWSEROS_PATCH=5