mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 06:40:44 +00:00
Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
@@ -86,6 +86,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Browser/security: require `operator.admin` for the `browser.request` gateway method, matching the host/browser-node control authority exposed by that route. Thanks @RichardCao.
|
||||
- Browser/profiles: allow local managed profiles to override `browser.executablePath`, so different profiles can launch different Chromium-based browsers. Thanks @nobrainer-tech.
|
||||
- Agents/replay: repair displaced or missing tool results before strict provider replay, use Codex-compatible `aborted` outputs for OpenAI Responses history, and drop partial aborted/error transport turns before retries.
|
||||
- Browser/startup: deduplicate concurrent lazy-start calls per profile so simultaneous browser tool requests no longer race into duplicate Chrome launches and `PortInUseError`. (#61772) Thanks @sukhdeepjohar.
|
||||
- Browser/profiles: recover from stale Chromium `Singleton*` profile locks after crashes or host moves by clearing dead/foreign locks and retrying launch once. Thanks @seanc-dev.
|
||||
- Reply media: allow sandboxed replies to deliver OpenClaw-managed `media/outbound` and `media/tool-*` attachments without treating them as sandbox escapes, while keeping alias-escape checks on the managed media root. Fixes #71138. Thanks @mayor686, @truffle-dev, and @neeravmakwana.
|
||||
- CLI/agent: keep `openclaw agent --json` stdout reserved for the JSON response by routing gateway, plugin, and embedded-fallback diagnostics to stderr before execution starts. Fixes #71319.
|
||||
|
||||
@@ -200,7 +200,9 @@ export function createProfileAvailability({
|
||||
throw new BrowserProfileUnavailableError(formatChromeMcpAttachFailure(lastError));
|
||||
};
|
||||
|
||||
const ensureBrowserAvailable = async (): Promise<void> => {
|
||||
let inflightEnsureBrowserAvailable: Promise<void> | null = null;
|
||||
|
||||
const ensureBrowserAvailableOnce = async (): Promise<void> => {
|
||||
await reconcileProfileRuntime();
|
||||
if (capabilities.usesChromeMcp) {
|
||||
if (profile.userDataDir && !fs.existsSync(profile.userDataDir)) {
|
||||
@@ -305,6 +307,16 @@ export function createProfileAvailability({
|
||||
}
|
||||
};
|
||||
|
||||
const ensureBrowserAvailable = async (): Promise<void> => {
|
||||
if (inflightEnsureBrowserAvailable) {
|
||||
return inflightEnsureBrowserAvailable;
|
||||
}
|
||||
inflightEnsureBrowserAvailable = ensureBrowserAvailableOnce().finally(() => {
|
||||
inflightEnsureBrowserAvailable = null;
|
||||
});
|
||||
return inflightEnsureBrowserAvailable;
|
||||
};
|
||||
|
||||
const stopRunningBrowser = async (): Promise<{ stopped: boolean }> => {
|
||||
await reconcileProfileRuntime();
|
||||
if (capabilities.usesChromeMcp) {
|
||||
|
||||
@@ -88,6 +88,42 @@ describe("browser server-context ensureBrowserAvailable", () => {
|
||||
expect(stopOpenClawChrome).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("deduplicates concurrent lazy-start calls to prevent PortInUseError", async () => {
|
||||
const { launchOpenClawChrome, stopOpenClawChrome, isChromeCdpReady, profile } =
|
||||
setupEnsureBrowserAvailableHarness();
|
||||
isChromeCdpReady.mockResolvedValue(true);
|
||||
mockLaunchedChrome(launchOpenClawChrome, 456);
|
||||
|
||||
const first = profile.ensureBrowserAvailable();
|
||||
const second = profile.ensureBrowserAvailable();
|
||||
await vi.advanceTimersByTimeAsync(100);
|
||||
await expect(Promise.all([first, second])).resolves.toEqual([undefined, undefined]);
|
||||
|
||||
expect(launchOpenClawChrome).toHaveBeenCalledTimes(1);
|
||||
expect(stopOpenClawChrome).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("clears the concurrent lazy-start guard after launch failure", async () => {
|
||||
const { launchOpenClawChrome, stopOpenClawChrome, isChromeCdpReady, profile } =
|
||||
setupEnsureBrowserAvailableHarness();
|
||||
isChromeCdpReady.mockResolvedValue(true);
|
||||
launchOpenClawChrome.mockRejectedValueOnce(
|
||||
new Error("PortInUseError: listen EADDRINUSE 127.0.0.1:18800"),
|
||||
);
|
||||
|
||||
const first = profile.ensureBrowserAvailable();
|
||||
const second = profile.ensureBrowserAvailable();
|
||||
await expect(Promise.all([first, second])).rejects.toThrow("PortInUseError");
|
||||
|
||||
mockLaunchedChrome(launchOpenClawChrome, 789);
|
||||
const retry = profile.ensureBrowserAvailable();
|
||||
await vi.advanceTimersByTimeAsync(100);
|
||||
await expect(retry).resolves.toBeUndefined();
|
||||
|
||||
expect(launchOpenClawChrome).toHaveBeenCalledTimes(2);
|
||||
expect(stopOpenClawChrome).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("reuses a pre-existing loopback browser after an initial short probe miss", async () => {
|
||||
const { launchOpenClawChrome, stopOpenClawChrome, isChromeCdpReady, profile, state } =
|
||||
setupEnsureBrowserAvailableHarness();
|
||||
|
||||
Reference in New Issue
Block a user