Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions patches/apply_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -1054,14 +1054,17 @@ def main(src: Path):
edit(
main_cc,
'TRACE_EVENT0("startup", "ChromeBrowserMainParts::PostBrowserStart");',
f"\n {MARKER}: start the AI-native agent gateway (HTTP 9334).\n"
f"\n {MARKER}: provision the grok CLI config on first run so the browser\n"
f" {MARKER}: works on machines without ~/.grok set up, then\n"
f" {MARKER}: start the AI-native agent gateway (HTTP 9334).\n"
" agent_gateway::ProvisionGrok();\n"
" agent_gateway::AgentGateway::Start(0);\n",
)
edit(
main_cc,
'#include "chrome/browser/chrome_browser_main.h"',
f'\n#include "chrome/browser/agent_gateway/agent_gateway.h"'
f" {MARKER}\n",
f'\n#include "chrome/browser/agent_gateway/agent_gateway.h" {MARKER}'
f'\n#include "chrome/browser/agent_gateway/grok_provisioner.h" {MARKER}\n',
)
# 1b. Cleanly shut the gateway down in PostMainMessageLoopRun (after the main
# loop quits, before thread teardown). The gateway is a leaked raw global
Expand Down
5 changes: 5 additions & 0 deletions scripts/package.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,11 @@ try {
# the lingering code isn't mistaken for a script failure by callers/CI.
$global:LASTEXITCODE = 0

# Bundle the MCP servers (sdk\*.py) beside the exe so the grok provisioner finds
# them (SdkDir() checks DIR_MODULE\sdk and <exe_dir>\sdk on Windows).
New-Item -ItemType Directory -Force -Path (Join-Path $Root "sdk") | Out-Null
Copy-Item (Join-Path $Xplorer "sdk\*.py") (Join-Path $Root "sdk") -Force -ErrorAction SilentlyContinue

# Fail loudly if a load-bearing runtime file is missing rather than shipping a
# broken zip (extension-whitelist staging can silently drop new file types).
$required = @('Xplorer.exe', 'chrome.dll', 'chrome_elf.dll', 'icudtl.dat',
Expand Down
9 changes: 9 additions & 0 deletions scripts/package_linux.sh
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,15 @@ else
echo "WARN: companion UI not found at $UI_SRC — UI routes will 401." >&2
fi

# --- MCP servers (sdk/*.py) so the grok provisioner finds them on a packaged
# build (DIR_EXE/sdk, mirrored under Resources/ like the companion UI). ---
SDK_SRC="$XPLORER/sdk"
if [ -d "$SDK_SRC" ]; then
mkdir -p "$ROOT/sdk" "$ROOT/Resources/sdk"
cp "$SDK_SRC"/*.py "$ROOT/sdk/" 2>/dev/null || true
cp "$SDK_SRC"/*.py "$ROOT/Resources/sdk/" 2>/dev/null || true
fi

# --- xplorer launcher wrapper ----------------------------------------------
# Small POSIX-sh wrapper: resolves its own real dir (following symlinks), points
# the gateway at the bundled companion UI, and execs the real chrome binary,
Expand Down
8 changes: 8 additions & 0 deletions scripts/release_arch.sh
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,14 @@ mkdir -p "$(dirname "$UI_DST")"
rm -rf "$UI_DST"
cp -R "$XPLORER/companion/ui" "$UI_DST"

# XPLOR: bundle the MCP servers (sdk/*.py) so the grok provisioner can write a
# ~/.grok/config.toml pointing at them on machines without a dev checkout.
echo "==> [$ARCH] Bundling sdk (MCP servers for the grok provisioner)"
SDK_DST="$APP/Contents/Resources/sdk"
rm -rf "$SDK_DST"
mkdir -p "$SDK_DST"
cp "$XPLORER/sdk"/*.py "$SDK_DST/" 2>/dev/null || true

# Stage Sparkle.framework into the bundle BEFORE signing so it is covered by the
# signature. ditto preserves the Versions/Current symlink layout that a plain
# cp -R would mangle.
Expand Down
2 changes: 2 additions & 0 deletions src/chrome/browser/agent_gateway/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ source_set("agent_gateway") {
"grok_companion_launcher.h",
"grok_native.cc",
"grok_native.h",
"grok_provisioner.cc",
"grok_provisioner.h",
"scheduler.cc",
"scheduler.h",
"tab_ownership.cc",
Expand Down
105 changes: 105 additions & 0 deletions src/chrome/browser/agent_gateway/grok_provisioner.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
// XPLORER: first-run provisioning of the grok CLI config (see header).
#include "chrome/browser/agent_gateway/grok_provisioner.h"

#include <cstdlib>
#include <string>

#include "base/base_paths.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/path_service.h"
#include "base/strings/stringprintf.h"
#include "build/build_config.h"

namespace agent_gateway {
namespace {

// The xplorer MCP server script is Python; the launch command differs by OS —
// Windows ships "python" (python.org) rather than "python3".
#if BUILDFLAG(IS_WIN)
constexpr char kPython[] = "python";
#else
constexpr char kPython[] = "python3";
#endif

base::FilePath HomeDir() {
base::FilePath home;
base::PathService::Get(base::DIR_HOME, &home);
return home;
}

// Locate the bundled sdk/ dir holding the MCP server script (xplorer_mcp.py).
// Mirrors the companion-UI resolver: env override -> the app bundle
// (Contents/Resources/sdk on mac, exe_dir/sdk elsewhere) -> a dev checkout.
base::FilePath SdkDir() {
if (const char* env = std::getenv("XPLORER_SDK"); env && *env)
return base::FilePath::FromUTF8Unsafe(env);
base::FilePath dir;
#if BUILDFLAG(IS_WIN)
// Installer layout: ships beside chrome.dll in the versioned dir (DIR_MODULE).
if (base::PathService::Get(base::DIR_MODULE, &dir)) {
base::FilePath c = dir.AppendASCII("sdk");
if (base::DirectoryExists(c))
return c;
}
#endif
if (base::PathService::Get(base::DIR_EXE, &dir)) {
#if BUILDFLAG(IS_MAC)
base::FilePath c = dir.DirName().AppendASCII("Resources").AppendASCII("sdk");
#else
base::FilePath c = dir.AppendASCII("sdk");
#endif
if (base::DirectoryExists(c))
return c;
}
base::FilePath dev = HomeDir()
.AppendASCII("cli_experiment")
.AppendASCII("xplorer")
.AppendASCII("sdk");
if (base::DirectoryExists(dev))
return dev;
return base::FilePath();
}

std::string XplorerBlock(const std::string& sdk) {
return base::StringPrintf(
"\n[mcp_servers.xplorer]\ncommand = \"%s\"\n"
"args = [\"%s/xplorer_mcp.py\"]\nenabled = true\n",
kPython, sdk.c_str());
}

} // namespace

void ProvisionGrok() {
base::FilePath sdk = SdkDir();
if (sdk.empty())
return; // No MCP server to point at; nothing safe to write.
const std::string sdk_s = sdk.AsUTF8Unsafe();

base::FilePath grok_dir = HomeDir().AppendASCII(".grok");
if (!base::CreateDirectory(grok_dir))
return;
base::FilePath cfg = grok_dir.AppendASCII("config.toml");

if (!base::PathExists(cfg)) {
// Fresh machine: write a complete minimal config wired to the xplorer MCP.
std::string full =
"[cli]\ninstaller = \"internal\"\nauto_update = false\n\n"
"[ui]\npermission_mode = \"always-approve\"\n\n"
"[features]\ncodebase_indexing = true\n";
full += XplorerBlock(sdk_s);
full += "\n[models]\ndefault = \"grok-composer-2.5-fast\"\n";
base::WriteFile(cfg, full);
return;
}

// Existing config: append ONLY the missing xplorer MCP block; never clobber
// the user's own edits.
std::string existing;
if (!base::ReadFileToString(cfg, &existing))
return;
if (existing.find("[mcp_servers.xplorer]") == std::string::npos)
base::AppendToFile(cfg, XplorerBlock(sdk_s));
}

} // namespace agent_gateway
20 changes: 20 additions & 0 deletions src/chrome/browser/agent_gateway/grok_provisioner.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// XPLORER: first-run provisioning of the grok CLI config.
#ifndef CHROME_BROWSER_AGENT_GATEWAY_GROK_PROVISIONER_H_
#define CHROME_BROWSER_AGENT_GATEWAY_GROK_PROVISIONER_H_

namespace agent_gateway {

// Xplor is distributed to machines that may have neither the grok CLI nor a
// ~/.grok/config.toml; without a config, the sidebar agent fails. ProvisionGrok()
// makes the browser self-configuring:
// * if ~/.grok/config.toml is MISSING, it writes a complete minimal config
// wired to the bundled xplorer MCP server;
// * if it EXISTS, it appends ONLY the missing xplorer [mcp_servers.*] block,
// never clobbering the user's own edits.
// Idempotent and cheap; safe to call on every startup. Call before the gateway
// or grok is used (PostBrowserStart).
void ProvisionGrok();

} // namespace agent_gateway

#endif // CHROME_BROWSER_AGENT_GATEWAY_GROK_PROVISIONER_H_