


























@@ -56,6 +56,10 @@ type BrowserEnsureOptions = {
5656headless?: boolean;
5757};
585859+const MANAGED_LAUNCH_FAILURE_THRESHOLD = 3;
60+const MANAGED_LAUNCH_COOLDOWN_BASE_MS = 30_000;
61+const MANAGED_LAUNCH_COOLDOWN_MAX_MS = 5 * 60_000;
62+5963function launchOptionsForEnsure(options?: BrowserEnsureOptions) {
6064return typeof options?.headless === "boolean"
6165 ? { headlessOverride: options.headless }
@@ -81,6 +85,51 @@ function formatLocalPortOwnershipHint(profile: ResolvedBrowserProfile): string {
8185);
8286}
838788+function normalizeFailureMessage(err: unknown): string {
89+const raw = err instanceof Error ? err.message : String(err);
90+const trimmed = raw.trim();
91+return trimmed || "unknown browser launch failure";
92+}
93+94+function resetManagedLaunchFailure(profileState: ProfileRuntimeState): void {
95+profileState.managedLaunchFailure = undefined;
96+}
97+98+function recordManagedLaunchFailure(profileState: ProfileRuntimeState, err: unknown): void {
99+const previous = profileState.managedLaunchFailure;
100+const consecutiveFailures = (previous?.consecutiveFailures ?? 0) + 1;
101+const exponent = Math.max(0, consecutiveFailures - MANAGED_LAUNCH_FAILURE_THRESHOLD);
102+const cooldownMs =
103+consecutiveFailures >= MANAGED_LAUNCH_FAILURE_THRESHOLD
104+ ? Math.min(MANAGED_LAUNCH_COOLDOWN_MAX_MS, MANAGED_LAUNCH_COOLDOWN_BASE_MS * 2 ** exponent)
105+ : 0;
106+const now = Date.now();
107+profileState.managedLaunchFailure = {
108+ consecutiveFailures,
109+lastFailureAt: now,
110+ ...(cooldownMs > 0 ? { cooldownUntil: now + cooldownMs } : {}),
111+lastError: normalizeFailureMessage(err),
112+};
113+}
114+115+function assertManagedLaunchNotCoolingDown(profileName: string, profileState: ProfileRuntimeState) {
116+const failure = profileState.managedLaunchFailure;
117+if (!failure || failure.consecutiveFailures < MANAGED_LAUNCH_FAILURE_THRESHOLD) {
118+return;
119+}
120+const cooldownUntil = failure.cooldownUntil ?? 0;
121+const remainingMs = cooldownUntil - Date.now();
122+if (remainingMs <= 0) {
123+return;
124+}
125+const retrySeconds = Math.max(1, Math.ceil(remainingMs / 1000));
126+throw new BrowserProfileUnavailableError(
127+`Browser launch for profile "${profileName}" is cooling down after ${failure.consecutiveFailures} consecutive managed Chrome launch failures. ` +
128+`Retry in ${retrySeconds}s after fixing Chrome startup, or set browser.enabled=false if the browser tool is not needed. ` +
129+`Last error: ${failure.lastError}`,
130+);
131+}
132+84133export function createProfileAvailability({
85134 opts,
86135 profile,
@@ -189,6 +238,7 @@ export function createProfileAvailability({
189238profileState.lastTargetId = null;
190239191240const previousProfile = reconcile.previousProfile;
241+resetManagedLaunchFailure(profileState);
192242if (profileState.running) {
193243await stopOpenClawChrome(profileState.running).catch(() => {});
194244setProfileRunning(null);
@@ -243,7 +293,19 @@ export function createProfileAvailability({
243293throw new BrowserProfileUnavailableError(formatChromeMcpAttachFailure(lastError));
244294};
245295246-let inflightEnsureBrowserAvailable: { key: string; promise: Promise<void> } | null = null;
296+const launchManagedChrome = async (
297+profileState: ProfileRuntimeState,
298+current: BrowserServerState,
299+launchOptions: ReturnType<typeof launchOptionsForEnsure>,
300+) => {
301+assertManagedLaunchNotCoolingDown(profile.name, profileState);
302+try {
303+return await launchOpenClawChrome(current.resolved, profile, launchOptions);
304+} catch (err) {
305+recordManagedLaunchFailure(profileState, err);
306+throw err;
307+}
308+};
247309248310const ensureBrowserAvailableOnce = async (options?: BrowserEnsureOptions): Promise<void> => {
249311await reconcileProfileRuntime();
@@ -280,6 +342,7 @@ export function createProfileAvailability({
280342(await isHttpReachable(PROFILE_ATTACH_RETRY_TIMEOUT_MS)) &&
281343(await isReachable(PROFILE_ATTACH_RETRY_TIMEOUT_MS))
282344) {
345+resetManagedLaunchFailure(profileState);
283346return;
284347}
285348}
@@ -290,20 +353,23 @@ export function createProfileAvailability({
290353 : `Browser attachOnly is enabled and profile "${profile.name}" is not running.`,
291354);
292355}
293-const launched = await launchOpenClawChrome(current.resolved, profile, launchOptions);
356+const launched = await launchManagedChrome(profileState, current, launchOptions);
294357attachRunning(launched);
295358try {
296359await waitForCdpReadyAfterLaunch();
360+resetManagedLaunchFailure(profileState);
297361} catch (err) {
298362await stopOpenClawChrome(launched).catch(() => {});
299363setProfileRunning(null);
364+recordManagedLaunchFailure(profileState, err);
300365throw err;
301366}
302367return;
303368}
304369305370// Port is reachable - check if we own it.
306371if (await isReachable()) {
372+resetManagedLaunchFailure(profileState);
307373return;
308374}
309375@@ -339,22 +405,26 @@ export function createProfileAvailability({
339405await stopOpenClawChrome(profileState.running);
340406setProfileRunning(null);
341407342-const relaunched = await launchOpenClawChrome(current.resolved, profile, launchOptions);
408+const relaunched = await launchManagedChrome(profileState, current, launchOptions);
343409attachRunning(relaunched);
344410345411if (!(await isReachable(PROFILE_POST_RESTART_WS_TIMEOUT_MS))) {
346-throw new Error(
412+const err = new Error(
347413`Chrome CDP websocket for profile "${profile.name}" is not reachable after restart. ${await describeCdpFailure(
348414 PROFILE_POST_RESTART_WS_TIMEOUT_MS,
349415 )}`,
350416);
417+recordManagedLaunchFailure(profileState, err);
418+throw err;
351419}
420+resetManagedLaunchFailure(profileState);
352421};
353422354423const ensureBrowserAvailable = async (options?: BrowserEnsureOptions): Promise<void> => {
355424const key = ensureOptionsKey(options);
425+const profileState = getProfileState();
356426for (;;) {
357-const current = inflightEnsureBrowserAvailable;
427+const current = profileState.ensureBrowserAvailable;
358428if (!current) {
359429break;
360430}
@@ -364,11 +434,11 @@ export function createProfileAvailability({
364434await current.promise.catch(() => {});
365435}
366436const promise = ensureBrowserAvailableOnce(options).finally(() => {
367-if (inflightEnsureBrowserAvailable?.promise === promise) {
368-inflightEnsureBrowserAvailable = null;
437+if (profileState.ensureBrowserAvailable?.promise === promise) {
438+profileState.ensureBrowserAvailable = null;
369439}
370440});
371-inflightEnsureBrowserAvailable = { key, promise };
441+profileState.ensureBrowserAvailable = { key, promise };
372442return promise;
373443};
374444@@ -380,6 +450,7 @@ export function createProfileAvailability({
380450return { stopped };
381451}
382452const profileState = getProfileState();
453+resetManagedLaunchFailure(profileState);
383454if (!profileState.running) {
384455const idleStop = resolveIdleProfileStopOutcome(profile);
385456if (idleStop.closePlaywright) {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。