Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
103 lines
3.8 KiB
Go
103 lines
3.8 KiB
Go
package browser
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"log/slog"
|
|
"os"
|
|
"strings"
|
|
|
|
preferencesbiz "github.com/tutti-os/tutti/services/tuttid/biz/preferences"
|
|
managedruntime "github.com/tutti-os/tutti/services/tuttid/service/managedruntime"
|
|
)
|
|
|
|
// chrome-devtools-mcp launch resolution. The daemon now consumes this command
|
|
// directly (it runs the MCP server as its own subprocess) instead of
|
|
// advertising it to agent providers. Operator overrides via
|
|
// TUTTI_BROWSER_MCP_COMMAND / TUTTI_BROWSER_MCP_ARGS still apply. Packaged
|
|
// desktop points TUTTI_BROWSER_MCP_ENTRY_PATH at its vendored entry script; dev
|
|
// falls back to the pinned npx command below.
|
|
const (
|
|
browserMCPCommandOverrideEnv = "TUTTI_BROWSER_MCP_COMMAND"
|
|
browserMCPArgsOverrideEnv = "TUTTI_BROWSER_MCP_ARGS"
|
|
browserMCPEntryPathEnv = "TUTTI_BROWSER_MCP_ENTRY_PATH"
|
|
|
|
// browserMCPPinnedVersion pins the chrome-devtools-mcp release for the npx
|
|
// fallback so dev runs are reproducible.
|
|
browserMCPPinnedVersion = "chrome-devtools-mcp@1.2.0"
|
|
)
|
|
|
|
// defaultBrowserMCPCommand / Args launch chrome-devtools-mcp in external-Chrome
|
|
// mode (the server manages its own Chrome). Launch-mode args are appended after
|
|
// resolving desktop preferences.
|
|
var (
|
|
defaultBrowserMCPCommand = "npx"
|
|
defaultBrowserMCPArgs = []string{"-y", browserMCPPinnedVersion}
|
|
)
|
|
|
|
// resolveBrowserMCPCommand returns the full command (command + args) used to
|
|
// launch the browser MCP server, honoring operator overrides.
|
|
func resolveBrowserMCPCommand(ctx context.Context, preferences PreferencesReader, runtimeResolver managedruntime.ProfileResolver) []string {
|
|
command, args := resolveBrowserMCPBaseCommand(ctx, runtimeResolver)
|
|
if raw := strings.TrimSpace(os.Getenv(browserMCPArgsOverrideEnv)); raw != "" {
|
|
var override []string
|
|
if err := json.Unmarshal([]byte(raw), &override); err == nil {
|
|
args = override
|
|
return append([]string{command}, args...)
|
|
}
|
|
}
|
|
mode := resolveBrowserUseConnectionMode(ctx, preferences)
|
|
args = append(args, resolveBrowserMCPConnectionArgs(mode)...)
|
|
return append([]string{command}, args...)
|
|
}
|
|
|
|
func resolveBrowserMCPBaseCommand(ctx context.Context, runtimeResolver managedruntime.ProfileResolver) (string, []string) {
|
|
if command := strings.TrimSpace(os.Getenv(browserMCPCommandOverrideEnv)); command != "" {
|
|
return command, []string{}
|
|
}
|
|
if entry := strings.TrimSpace(os.Getenv(browserMCPEntryPathEnv)); entry != "" {
|
|
if node := strings.TrimSpace(os.Getenv("TUTTI_APP_NODE")); node != "" {
|
|
return node, []string{entry}
|
|
}
|
|
if runtimeResolver != nil {
|
|
appRuntime, err := runtimeResolver.ResolveProfile(ctx, managedruntime.NodeStaticProfile)
|
|
if err == nil && strings.TrimSpace(appRuntime.Node) != "" {
|
|
return strings.TrimSpace(appRuntime.Node), []string{entry}
|
|
}
|
|
if err != nil {
|
|
slog.Warn(
|
|
"browser mcp managed node runtime unavailable for vendored entry",
|
|
"event", "browser_mcp.managed_node_runtime_unavailable",
|
|
"error", err,
|
|
)
|
|
}
|
|
}
|
|
return "node", []string{entry}
|
|
}
|
|
return defaultBrowserMCPCommand, append([]string(nil), defaultBrowserMCPArgs...)
|
|
}
|
|
|
|
func resolveBrowserMCPConnectionArgs(mode string) []string {
|
|
switch mode {
|
|
case "autoConnect":
|
|
return resolveAutoConnectMCPConnectionArgs()
|
|
default:
|
|
return []string{"--isolated", "--no-usage-statistics"}
|
|
}
|
|
}
|
|
|
|
func resolveBrowserUseConnectionMode(ctx context.Context, preferences PreferencesReader) string {
|
|
if preferences == nil {
|
|
return preferencesbiz.DefaultDesktopBrowserUseConnectionMode
|
|
}
|
|
stored, err := preferences.GetDesktopPreferences(ctx)
|
|
if err != nil {
|
|
return preferencesbiz.DefaultDesktopBrowserUseConnectionMode
|
|
}
|
|
mode := strings.TrimSpace(stored.BrowserUseConnectionMode)
|
|
if preferencesbiz.IsDesktopBrowserUseConnectionMode(mode) {
|
|
return mode
|
|
}
|
|
return preferencesbiz.DefaultDesktopBrowserUseConnectionMode
|
|
}
|