Files
openclaw/extensions/browser/src/control-service.ts
Peter Steinberger 0d7fb8eb39 refactor(fs): adopt fs-safe 0.5 core primitives (#113705)
* refactor(fs): unify exclusive file publication

* fix(fs): fence stale lock reclamation

* refactor(fs): bound wiki scans and secret reads

* chore(fs): finalize fs-safe 0.5 compatibility

* fix(fs): preserve publication ownership and legacy mode

* fix(fs): fail closed on unverifiable lock owners

* fix(fs): preserve concurrent backup publications

* refactor(fs): preserve ambiguous backup outputs

* fix(fs): preserve mixed-version lock coordination

* refactor(file-transfer): adopt fs-safe archive extraction

* refactor(fs): add bounded walk and secret seams

* refactor(auth): replace proper-lockfile with fs-safe

* fix(fs): honor Windows mode override casing

* refactor(snapshot): adopt fs-safe publication

* refactor(memory-wiki): adopt prunable root walks

* refactor(fleet): adopt bounded archive restore

* fix(fs): preserve post-publication ownership receipts

* refactor(fs): harvest final fs-safe primitives

* style(fs): clean harvest lint

* chore(plugin-sdk): refresh move helper API baseline

* refactor(snapshot): adopt native Windows ACL facts

* refactor(fs): adopt hardened atomic outputs

* fix(fs): scope lock reentrancy to logical owners

* chore(config): lower env var count budget

* fix(deps): adopt published fs-safe 0.5.0

* fix(ci): align SDK surface ratchets

* fix(ci): regenerate SDK API baseline after rebase

* fix(fs): preserve owner-scoped file lock nesting

* fix(ci): refresh SDK API baseline for file locks

* fix(fs): separate SQLite and file lock reentrancy

* fix(imessage): bound pinned attachment reads

* fix(agents): narrow session-key lock options

* fix(fs): preserve fs-safe 0.5 compatibility contracts

* fix(windows): retain private SQLite directory owner

* refactor(sqlite): centralize exclusive coordinator

* refactor(snapshot): isolate Windows ACL policy

* fix(windows): retain snapshot ACL inspector

* chore(config): realign env budget after rebase

* test(agents): accept canonical sandbox escape error

* docs(changelog): defer fs-safe release note
2026-07-28 03:41:47 -04:00

100 lines
3.7 KiB
TypeScript

/**
* Browser control service lifecycle for plugin-managed, in-process operation.
*/
import {
createBrowserControlContext,
ensureBrowserControlRuntime,
getBrowserControlState,
stopBrowserControlRuntime,
withBrowserControlStart,
} from "./browser-control-state.js";
import { loadBrowserConfigForRuntimeRefresh } from "./browser/config-refresh-source.js";
import { resolveBrowserConfig, resolveProfile } from "./browser/config.js";
import { ensureBrowserControlAuth } from "./browser/control-auth.js";
import { getExtensionRelayModule } from "./browser/extension-relay.runtime.js";
import type { BrowserServerState } from "./browser/server-context.js";
import { getRuntimeConfig } from "./config/config.js";
import { createSubsystemLogger } from "./logging/subsystem.js";
import { isDefaultBrowserPluginEnabled } from "./plugin-enabled.js";
const log = createSubsystemLogger("browser");
const logService = log.child("service");
async function startBrowserControlServiceUnlocked(): Promise<BrowserServerState | null> {
const current = getBrowserControlState();
if (current) {
return current;
}
const cfg = getRuntimeConfig();
const browserCfg = loadBrowserConfigForRuntimeRefresh();
if (!isDefaultBrowserPluginEnabled(browserCfg)) {
return null;
}
let resolved = resolveBrowserConfig(browserCfg.browser, browserCfg);
if (!resolved.enabled) {
return null;
}
try {
const ensured = await ensureBrowserControlAuth({ cfg });
if (ensured.generatedToken) {
logService.info("No browser auth configured; generated gateway.auth.token automatically.");
}
} catch (err) {
logService.warn(`failed to auto-configure browser auth: ${String(err)}`);
}
// Ensure the host-local relay secret exists before profiles are consumed so
// the extension cdpUrl carries auth. Works identically on the gateway host
// and on a browser node host — each owns its own secret.
const hasExtensionProfiles = Object.values(resolved.profiles).some(
(profile) => profile.driver === "extension",
);
if (hasExtensionProfiles) {
const { ensureExtensionRelayToken } = await import("./browser/extension-relay/relay-auth.js");
await ensureExtensionRelayToken();
const refreshed = loadBrowserConfigForRuntimeRefresh();
resolved = resolveBrowserConfig(refreshed.browser, refreshed);
}
const state = await ensureBrowserControlRuntime({
server: null,
port: resolved.controlPort,
resolved,
owner: "service",
onWarn: (message) => logService.warn(message),
});
// Extension relays listen from service start so the Chrome extension can
// (re)connect before the first agent browser request arrives.
if (hasExtensionProfiles) {
const { startConfiguredExtensionRelays } = await getExtensionRelayModule();
await startConfiguredExtensionRelays(
state,
(name) => resolveProfile(resolved, name),
(message) => logService.warn(message),
);
}
logService.info(
`Browser control service ready (profiles=${Object.keys(resolved.profiles).length})`,
);
return state;
}
/** Starts Browser control without binding the HTTP server when config enables it. */
export async function startBrowserControlServiceFromConfig(): Promise<BrowserServerState | null> {
return await withBrowserControlStart(startBrowserControlServiceUnlocked);
}
/** Stops the in-process Browser control service runtime. */
export async function stopBrowserControlService(): Promise<void> {
await stopBrowserControlRuntime({
requestedBy: "service",
onWarn: (message) => logService.warn(message),
});
}
/** Re-export Browser control context accessors for gateway-local dispatch. */
export { createBrowserControlContext, getBrowserControlState };