mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-18 13:30:48 +00:00
Merged via squash.
Prepared head SHA: e490035a24
Co-authored-by: velvet-shark <126378+velvet-shark@users.noreply.github.com>
Co-authored-by: velvet-shark <126378+velvet-shark@users.noreply.github.com>
Reviewed-by: @velvet-shark
108 lines
3.5 KiB
TypeScript
108 lines
3.5 KiB
TypeScript
import { createConfigIO, getRuntimeConfigSnapshot } from "../config/config.js";
|
|
import { resolveBrowserConfig, resolveProfile, type ResolvedBrowserProfile } from "./config.js";
|
|
import type { BrowserServerState } from "./server-context.types.js";
|
|
|
|
function changedProfileInvariants(
|
|
current: ResolvedBrowserProfile,
|
|
next: ResolvedBrowserProfile,
|
|
): string[] {
|
|
const changed: string[] = [];
|
|
if (current.cdpUrl !== next.cdpUrl) {
|
|
changed.push("cdpUrl");
|
|
}
|
|
if (current.cdpPort !== next.cdpPort) {
|
|
changed.push("cdpPort");
|
|
}
|
|
if (current.driver !== next.driver) {
|
|
changed.push("driver");
|
|
}
|
|
if (current.attachOnly !== next.attachOnly) {
|
|
changed.push("attachOnly");
|
|
}
|
|
if (current.cdpIsLoopback !== next.cdpIsLoopback) {
|
|
changed.push("cdpIsLoopback");
|
|
}
|
|
if ((current.userDataDir ?? "") !== (next.userDataDir ?? "")) {
|
|
changed.push("userDataDir");
|
|
}
|
|
return changed;
|
|
}
|
|
|
|
function applyResolvedConfig(
|
|
current: BrowserServerState,
|
|
freshResolved: BrowserServerState["resolved"],
|
|
) {
|
|
current.resolved = {
|
|
...freshResolved,
|
|
// Keep the runtime evaluate gate stable across request-time profile refreshes.
|
|
// Security-sensitive behavior should only change via full runtime config reload,
|
|
// not as a side effect of resolving profiles/tabs during a request.
|
|
evaluateEnabled: current.resolved.evaluateEnabled,
|
|
};
|
|
for (const [name, runtime] of current.profiles) {
|
|
const nextProfile = resolveProfile(freshResolved, name);
|
|
if (nextProfile) {
|
|
const changed = changedProfileInvariants(runtime.profile, nextProfile);
|
|
if (changed.length > 0) {
|
|
runtime.reconcile = {
|
|
previousProfile: runtime.profile,
|
|
reason: `profile invariants changed: ${changed.join(", ")}`,
|
|
};
|
|
runtime.lastTargetId = null;
|
|
}
|
|
runtime.profile = nextProfile;
|
|
continue;
|
|
}
|
|
runtime.reconcile = {
|
|
previousProfile: runtime.profile,
|
|
reason: "profile removed from config",
|
|
};
|
|
runtime.lastTargetId = null;
|
|
if (!runtime.running) {
|
|
current.profiles.delete(name);
|
|
}
|
|
}
|
|
}
|
|
|
|
export function refreshResolvedBrowserConfigFromDisk(params: {
|
|
current: BrowserServerState;
|
|
refreshConfigFromDisk: boolean;
|
|
mode: "cached" | "fresh";
|
|
}) {
|
|
if (!params.refreshConfigFromDisk) {
|
|
return;
|
|
}
|
|
|
|
// Route-level browser config hot reload should observe on-disk changes immediately.
|
|
// The shared loadConfig() helper may return a cached snapshot for the configured TTL,
|
|
// which can leave request-time browser guards stale (for example evaluateEnabled).
|
|
const cfg = getRuntimeConfigSnapshot() ?? createConfigIO().loadConfig();
|
|
const freshResolved = resolveBrowserConfig(cfg.browser, cfg);
|
|
applyResolvedConfig(params.current, freshResolved);
|
|
}
|
|
|
|
export function resolveBrowserProfileWithHotReload(params: {
|
|
current: BrowserServerState;
|
|
refreshConfigFromDisk: boolean;
|
|
name: string;
|
|
}): ResolvedBrowserProfile | null {
|
|
refreshResolvedBrowserConfigFromDisk({
|
|
current: params.current,
|
|
refreshConfigFromDisk: params.refreshConfigFromDisk,
|
|
mode: "cached",
|
|
});
|
|
let profile = resolveProfile(params.current.resolved, params.name);
|
|
if (profile) {
|
|
return profile;
|
|
}
|
|
|
|
// Hot-reload: profile missing; retry with a fresh disk read without flushing the global cache.
|
|
refreshResolvedBrowserConfigFromDisk({
|
|
current: params.current,
|
|
refreshConfigFromDisk: params.refreshConfigFromDisk,
|
|
mode: "fresh",
|
|
});
|
|
profile = resolveProfile(params.current.resolved, params.name);
|
|
return profile;
|
|
}
|