Skip to content

Add Lightpanda browser backend and MCP catalog option#220

Open
adybag14-cyber wants to merge 11 commits intompfaffenberger:mainfrom
adybag14-cyber:feat/lightpanda-browser-integration
Open

Add Lightpanda browser backend and MCP catalog option#220
adybag14-cyber wants to merge 11 commits intompfaffenberger:mainfrom
adybag14-cyber:feat/lightpanda-browser-integration

Conversation

@adybag14-cyber
Copy link
Copy Markdown

@adybag14-cyber adybag14-cyber commented Mar 2, 2026

Light panda is fully in zig and the CDP server too, TLDR: way faster than playwright and way less ram used.

Also read all code, I don't want to cause any regressions at all, perhaps i can add a minimal readme if you want.

Summary by CodeRabbit

  • New Features

    • Added Lightpanda as a new browser backend with configurable executable, host/port, startup timeout, retries, and extra args.
    • Added Lightpanda entry to the public MCP server catalog.
  • Bug Fixes

    • Refined environment-variable parsing to ignore ${...} placeholders when extracting env values.
  • Tests

    • Added tests for Lightpanda initialization, retries, cleanup, executable/port/startup validation, and env-var placeholder handling.

nhicks00 and others added 4 commits March 1, 2026 14:03
…T.md cache (mpfaffenberger#212)

* fix(cd): reload agent after /cd to flush stale system prompt and AGENT.md cache

After a successful /cd, the agent's PydanticAgent instance was reused
verbatim for subsequent messages.  This meant:

  1. Any CWD-dependent content baked into the system prompt at agent
     construction time (e.g. the Walmart plugin's working-directory line)
     was never refreshed, so the LLM kept receiving the old path.

  2. Project-local AGENT.md rules loaded by load_puppy_rules() were
     cached in self._puppy_rules and never re-read for the new directory,
     silently ignoring the new project's rules (or incorrectly carrying
     the old ones forward).

Fixes:

* base_agent.py — set self._puppy_rules = None at the top of
  reload_code_generation_agent() so every reload always performs a
  fresh disk read from the current working directory.

* core_commands.py — call get_current_agent().reload_code_generation_agent()
  after os.chdir() succeeds.  The reload is best-effort (wrapped in
  try/except) so a failure never aborts the directory change itself.

Tests:

* test_command_handler.py — update test_cd_valid_change to assert the
  agent is reloaded; add test_cd_valid_change_reload_failure_is_nonfatal
  to verify a broken reload does not bubble up.

* test_base_agent_full_coverage.py — add test_reload_clears_puppy_rules_cache
  which uses a spy on load_puppy_rules() to prove _puppy_rules is None
  (i.e. cache cleared) at the moment the fresh read begins.

Closes mpfaffenberger#205

* fix(cd): emit warning on agent reload failure instead of silent pass

Address CodeRabbit review comments on PR mpfaffenberger#212:
- Replace bare `except: pass` with `emit_warning()` so users see
  feedback when agent reload fails after /cd
- Strengthen test assertions to verify chdir, success message,
  reload attempt, and warning emission

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: n0h0123 <nathan.hicks0@walmart.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…tions

The DEFAULT_BANNER_COLORS dict in config.py doesn't have 'mcp_tool_call',
so the tests were failing. Updated both test cases in TestGetConfigKeys
to match the actual implementation.
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 2, 2026

📝 Walkthrough

Walkthrough

Adds a Lightpanda MCP server template and integrates Lightpanda as a BrowserManager backend; refines MCPServerTemplate env-var parsing to ignore ${...} placeholders; expands tests for Lightpanda startup, retries, validation, cleanup, and env-var handling.

Changes

Cohort / File(s) Summary
MCP Server Registry
code_puppy/mcp_/server_registry_catalog.py
Appends a MCPServerTemplate entry with id lightpanda; updates MCPServerTemplate.get_environment_vars to only include $NAME-style refs and ignore ${...} placeholders.
Browser Tool Doc
code_puppy/tools/browser/browser_control.py
Docstring updated to list lightpanda as an accepted browser_type; no behavioral changes.
Browser Manager Implementation
code_puppy/tools/browser/browser_manager.py
Introduces Lightpanda backend and built-in Playwright browser routing: exec/host/port resolution (including auto free-port), command assembly, async subprocess launch with stderr draining, CDP connection with retry/timeout, Playwright attach and persistent profile handling, shutdown/cleanup flows, and supporting private utilities and lifecycle hooks.
Browser Manager Tests
tests/tools/browser/test_browser_manager_full.py
Adds extensive tests for Lightpanda: initialization dispatch (case-insensitive), context setup, startup retries/timeouts, CDP connect timeout budgeting, executable/port/startup validation, and cleanup behavior.
MCP Registry Tests
tests/mcp/test_server_registry_catalog.py
Adds test_get_environment_vars_ignores_cmd_placeholders to ensure ${...} placeholders are ignored while $REAL_API_KEY-style refs are captured.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client
    participant BM as BrowserManager
    participant Sys as System
    participant LP as Lightpanda
    participant PW as Playwright

    Client->>BM: async_initialize(browser_type="lightpanda")
    BM->>Sys: resolve executable, host, port (or find free port)
    BM->>Sys: spawn Lightpanda subprocess (with args)
    Sys-->>LP: process started
    BM->>LP: read stderr / poll for readiness
    BM->>BM: _connect_lightpanda_over_cdp (retry / timeout)
    LP-->>BM: CDP endpoint responds
    BM->>PW: attach/connect over CDP / create context
    PW-->>BM: browser/context ready
    BM-->>Client: initialized

    Client->>BM: cleanup()
    BM->>PW: close context/browser
    BM->>Sys: terminate Lightpanda subprocess
    Sys-->>LP: process stopped
    BM-->>Client: cleaned up
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I nudged a port and woke a light,

a panda rose to speak CDP.
Playwright hopped in, contexts spun bright,
tests checked the hops and set things free.
A rabbit cheered — small change, big spree!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main changes: addition of Lightpanda as a browser backend and its integration as an MCP catalog option, which are the primary objectives of the PR.
Docstring Coverage ✅ Passed Docstring coverage is 93.48% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
code_puppy/tools/browser/browser_manager.py (1)

154-159: Free-port probing is race-prone during concurrent startup.

At Lines 154-159, the socket is closed before Lightpanda binds, so another process can grab the same port and cause intermittent startup failures. Consider retrying startup with a newly selected port when bind/connect fails.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@code_puppy/tools/browser/browser_manager.py` around lines 154 - 159, The
current _find_free_port() probe is race-prone because the socket is closed
before the browser (Lightpanda) binds; change the startup/launch logic that uses
_find_free_port() so it handles bind/connect failures by catching the
binding-related exceptions (e.g., OSError/ConnectionRefused) from the Lightpanda
process or client socket and retrying: on failure call _find_free_port() again,
pick a new port, and retry launching/connecting up to a reasonable max attempts
with short backoff; ensure the retry loop lives around the code that starts or
connects to Lightpanda (the caller of _find_free_port()) so a new free port is
chosen only when a bind/connect error is observed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@code_puppy/mcp_/server_registry_catalog.py`:
- Around line 474-475: The env entry "LIGHTPANDA_CDP_URL": "${cdp_url}" is being
parsed by get_environment_vars() as an environment-variable reference; change
this to a non-env placeholder (e.g., "LIGHTPANDA_CDP_URL": "{cdp_url}" or remove
it from the "env" dict and pass it as a command arg) so get_environment_vars()
does not treat it as a required env var; update usages that substitute the
placeholder accordingly (refer to the LIGHTPANDA_CDP_URL key and the
get_environment_vars() function to locate where to change).

In `@code_puppy/tools/browser/browser_manager.py`:
- Around line 360-364: The branch handling self.browser_type in the
BrowserManager silently falls through to the Chromium path for unknown values;
update the logic in the method that contains the lightpanda check (references:
self.browser_type, _initialize_lightpanda_browser, self._initialized) to
explicitly raise a clear exception (e.g., ValueError) for unsupported
browser_type values instead of silently defaulting to Chromium, so callers fail
fast and can correct invalid inputs.
- Around line 184-193: The _get_lightpanda_port function currently parses
LIGHTPANDA_PORT but doesn't validate its numeric range; update the code in
_get_lightpanda_port to parse configured_port (as it's already doing) and then
explicitly validate that the integer is within the valid TCP port range
(1–65535), raising a RuntimeError with a clear message including configured_port
if it is out of range or not a valid integer (reuse the existing except
ValueError handling to wrap parsing errors), otherwise return the validated int;
reference: function _get_lightpanda_port and variable configured_port.

---

Nitpick comments:
In `@code_puppy/tools/browser/browser_manager.py`:
- Around line 154-159: The current _find_free_port() probe is race-prone because
the socket is closed before the browser (Lightpanda) binds; change the
startup/launch logic that uses _find_free_port() so it handles bind/connect
failures by catching the binding-related exceptions (e.g.,
OSError/ConnectionRefused) from the Lightpanda process or client socket and
retrying: on failure call _find_free_port() again, pick a new port, and retry
launching/connecting up to a reasonable max attempts with short backoff; ensure
the retry loop lives around the code that starts or connects to Lightpanda (the
caller of _find_free_port()) so a new free port is chosen only when a
bind/connect error is observed.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8de83d7 and 847fc00.

📒 Files selected for processing (4)
  • code_puppy/mcp_/server_registry_catalog.py
  • code_puppy/tools/browser/browser_control.py
  • code_puppy/tools/browser/browser_manager.py
  • tests/tools/browser/test_browser_manager_full.py

Comment thread code_puppy/mcp_/server_registry_catalog.py
Comment thread code_puppy/tools/browser/browser_manager.py
Comment thread code_puppy/tools/browser/browser_manager.py Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
tests/mcp/test_server_registry_catalog.py (1)

261-264: Strengthen the negative assertion for placeholder leakage.

Line 263 only checks "{cdp_url}", which is specific to one failure shape. Add a direct check for "cdp_url" too, so regressions are caught even if parsing changes.

🧪 Suggested assertion hardening
         env_vars = template.get_environment_vars()
         assert "REAL_API_KEY" in env_vars
+        assert "cdp_url" not in env_vars
         assert "{cdp_url}" not in env_vars
+        assert len(env_vars) == 1
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/mcp/test_server_registry_catalog.py` around lines 261 - 264, Add a
stronger negative assertion to the test that checks for placeholder leakage: in
the test using template.get_environment_vars() (the same block that currently
asserts "{cdp_url}" not in env_vars), also assert that "cdp_url" not in env_vars
to catch regressions where the braces are stripped or parsing changes; update
the assertions near the env_vars = template.get_environment_vars() line
accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@code_puppy/tools/browser/browser_manager.py`:
- Around line 323-327: The subprocess stderr is currently piped in
self._lightpanda_process via asyncio.create_subprocess_exec which risks blocking
if stderr fills before you read it; either change
stderr=asyncio.subprocess.DEVNULL in the create_subprocess_exec call to discard
output, or keep stderr=PIPE but spawn a background asyncio Task immediately
after process creation (e.g., _drain_lightpanda_stderr task) that continuously
reads from self._lightpanda_process.stderr (readuntil/read or readexactly in a
loop until EOF) to drain the buffer while you await the CDP connection in the
existing connection logic (the background task should be cancelled/awaited on
process exit).

---

Nitpick comments:
In `@tests/mcp/test_server_registry_catalog.py`:
- Around line 261-264: Add a stronger negative assertion to the test that checks
for placeholder leakage: in the test using template.get_environment_vars() (the
same block that currently asserts "{cdp_url}" not in env_vars), also assert that
"cdp_url" not in env_vars to catch regressions where the braces are stripped or
parsing changes; update the assertions near the env_vars =
template.get_environment_vars() line accordingly.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 847fc00 and 69bcce6.

📒 Files selected for processing (4)
  • code_puppy/mcp_/server_registry_catalog.py
  • code_puppy/tools/browser/browser_manager.py
  • tests/mcp/test_server_registry_catalog.py
  • tests/tools/browser/test_browser_manager_full.py

Comment thread code_puppy/tools/browser/browser_manager.py
@adybag14-cyber
Copy link
Copy Markdown
Author

thank you code rabbit for accepting me

@adybag14-cyber
Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 2, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@adybag14-cyber
Copy link
Copy Markdown
Author

@codex review

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
code_puppy/tools/browser/browser_manager.py (1)

173-177: Validate path-like LIGHTPANDA_EXECUTABLE as an executable file, not just existing.

At Line 174, Path(...).exists() also accepts directories/non-executable files, which fails later with less clear process-launch errors.

🛠️ Proposed refinement
         if path_like:
-            if Path(executable).exists():
+            candidate = Path(executable)
+            if candidate.is_file() and os.access(candidate, os.X_OK):
                 return executable
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@code_puppy/tools/browser/browser_manager.py` around lines 173 - 177, The
current check uses Path(executable).exists() which allows directories or
non-executable files; update the path-like branch to ensure the target is a
regular file and is executable before returning. In the function where you
inspect path_like and the variable executable (the block using
Path(executable).exists()), replace the exists() check with a combined check
such as Path(executable).is_file() and os.access(executable, os.X_OK) (or
equivalent Path permission check) so only an actual executable file is accepted;
keep the else branch using shutil.which(executable) unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@code_puppy/tools/browser/browser_manager.py`:
- Around line 470-477: The lightpanda branch is doing a case-sensitive check (if
self.browser_type == "lightpanda") while the later logic lowercases
requested_browser; normalize detection by using the same lowercasing approach:
compute a normalized value from self.browser_type (e.g., requested_browser =
(self.browser_type or "chromium").lower()) before the lightpanda check, then use
that normalized value to decide whether to call
_initialize_lightpanda_browser(), emit_info(session_id) and set _initialized;
ensure references to self.browser_type, requested_browser,
_initialize_lightpanda_browser, session_id, and _initialized are updated
accordingly so inputs like "LightPanda" match.
- Around line 330-340: The loop that repeatedly tries to connect via
self._playwright.chromium.connect_over_cdp(endpoint) can block past the overall
LIGHTPANDA_STARTUP_TIMEOUT because connect_over_cdp is awaited directly; wrap
each connect attempt in asyncio.wait_for with the remaining time until the
startup deadline so any single hung attempt is bounded, compute a deadline =
start + LIGHTPANDA_STARTUP_TIMEOUT and before each try calculate remaining =
max(0.0, deadline - loop.time()), call
asyncio.wait_for(self._playwright.chromium.connect_over_cdp(endpoint),
timeout=remaining_or_short_default) and catch asyncio.TimeoutError to continue
looping and set last_error, keeping existing last_error handling and the final
RuntimeError message that includes last_error and endpoint.

---

Nitpick comments:
In `@code_puppy/tools/browser/browser_manager.py`:
- Around line 173-177: The current check uses Path(executable).exists() which
allows directories or non-executable files; update the path-like branch to
ensure the target is a regular file and is executable before returning. In the
function where you inspect path_like and the variable executable (the block
using Path(executable).exists()), replace the exists() check with a combined
check such as Path(executable).is_file() and os.access(executable, os.X_OK) (or
equivalent Path permission check) so only an actual executable file is accepted;
keep the else branch using shutil.which(executable) unchanged.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 69bcce6 and 3ede308.

📒 Files selected for processing (2)
  • code_puppy/tools/browser/browser_manager.py
  • tests/mcp/test_server_registry_catalog.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/mcp/test_server_registry_catalog.py

Comment thread code_puppy/tools/browser/browser_manager.py
Comment thread code_puppy/tools/browser/browser_manager.py Outdated
@adybag14-cyber
Copy link
Copy Markdown
Author

@codex

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@code_puppy/tools/browser/browser_manager.py`:
- Around line 217-227: Parse LIGHTPANDA_STARTUP_TIMEOUT as a float in
_get_lightpanda_startup_timeout, then validate that the parsed value is finite
and strictly positive using math.isfinite(timeout) and timeout > 0.0; if the
value is non-finite (nan/inf) or non-positive, raise a RuntimeError with a clear
message including the raw value, otherwise return the timeout (optionally
preserving the existing minimum of 1.0 via max(timeout, 1.0)).

In `@tests/tools/browser/test_browser_manager_full.py`:
- Around line 424-425: The test currently uses
mock_playwright.stop.assert_called_once() and
mock_stop_lightpanda.assert_called_once() but those targets are awaited in
BrowserManager._cleanup(), so change the assertions to use assert_awaited_once()
to verify they were awaited; locate the assertions referencing
mock_playwright.stop and mock_stop_lightpanda in
tests/tools/browser/test_browser_manager_full.py and replace
assert_called_once() with assert_awaited_once() for those async mocks.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3ede308 and 6383e4b.

📒 Files selected for processing (2)
  • code_puppy/tools/browser/browser_manager.py
  • tests/tools/browser/test_browser_manager_full.py

Comment thread code_puppy/tools/browser/browser_manager.py
Comment thread tests/tools/browser/test_browser_manager_full.py Outdated
@adybag14-cyber
Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 2, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
tests/tools/browser/test_browser_manager_full.py (1)

150-150: ⚠️ Potential issue | 🟡 Minor

Use awaited assertions for async initializer mocks.

Line 150 and Line 173 currently use assert_called_once(), which does not guarantee the coroutine was awaited.

Proposed fix
-            mock_init_lightpanda.assert_called_once()
+            mock_init_lightpanda.assert_awaited_once()
...
-            mock_init_lightpanda.assert_called_once()
+            mock_init_lightpanda.assert_awaited_once()

Also applies to: 173-173

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/tools/browser/test_browser_manager_full.py` at line 150, The test uses
synchronous assert_called_once() on async initializer mocks; replace those with
await-aware assertions (e.g., use AsyncMock.assert_awaited_once() or
assert_awaited_once()) for the async initializer mocks such as
mock_init_lightpanda (and the other initializer mock at line 173) so the test
verifies the coroutine was actually awaited rather than only called.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@code_puppy/tools/browser/browser_manager.py`:
- Around line 428-447: The shutdown sequence in _stop_lightpanda_process should
guarantee calling _stop_lightpanda_stderr_drain even if terminate/kill/wait
raise; wrap the existing "if process: ..." termination/kill/wait logic in a
try/finally and move the await self._stop_lightpanda_stderr_drain() call into
the finally block so cleanup always runs, preserving existing
contextlib.suppress usages and allowing any exceptions from the termination
logic to propagate after the finally completes.

---

Duplicate comments:
In `@tests/tools/browser/test_browser_manager_full.py`:
- Line 150: The test uses synchronous assert_called_once() on async initializer
mocks; replace those with await-aware assertions (e.g., use
AsyncMock.assert_awaited_once() or assert_awaited_once()) for the async
initializer mocks such as mock_init_lightpanda (and the other initializer mock
at line 173) so the test verifies the coroutine was actually awaited rather than
only called.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6383e4b and 660b1bc.

📒 Files selected for processing (2)
  • code_puppy/tools/browser/browser_manager.py
  • tests/tools/browser/test_browser_manager_full.py

Comment thread code_puppy/tools/browser/browser_manager.py Outdated
@adybag14-cyber
Copy link
Copy Markdown
Author

@coderabbitai resume

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 2, 2026

✅ Actions performed

Reviews resumed.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
code_puppy/tools/browser/browser_manager.py (1)

482-511: Normalize custom browser type lookup for consistency.

Built-in and Lightpanda routing are case-insensitive, but plugin custom type lookup still uses exact-case keys. Consider normalizing custom type matching too, so browser_type="MyPlugin" and "myplugin" behave consistently.

♻️ Suggested adjustment
-        if self.browser_type and self.browser_type in _CUSTOM_BROWSER_TYPES:
+        custom_key_map = {key.lower(): key for key in _CUSTOM_BROWSER_TYPES}
+        if self.browser_type and requested_browser in custom_key_map:
+            custom_key = custom_key_map[requested_browser]
             emit_info(
-                f"Using custom browser type '{self.browser_type}' "
+                f"Using custom browser type '{custom_key}' "
                 f"(session: {self.session_id})"
             )
-            init_func = _CUSTOM_BROWSER_TYPES[self.browser_type]
+            init_func = _CUSTOM_BROWSER_TYPES[custom_key]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@code_puppy/tools/browser/browser_manager.py` around lines 482 - 511, The
custom browser lookup is case-sensitive while built-in routing is not; modify
the logic in initialize (where requested_browser is computed, and where
_CUSTOM_BROWSER_TYPES is consulted) to normalize case for custom types too:
compute a lowercased key (e.g., requested_browser) and map it to the actual init
function from _CUSTOM_BROWSER_TYPES by using a normalized-key mapping or by
storing/looking-up keys lowercased before calling init_func (refer to
self.browser_type, requested_browser, _CUSTOM_BROWSER_TYPES, and init_func) so
that "MyPlugin" and "myplugin" resolve the same way and still set
self._context/self._browser and self._initialized as before.
tests/tools/browser/test_browser_manager_full.py (1)

192-195: Use asyncio.StreamReader for mock_process.stderr to exercise drain startup path.

These tests currently set stderr to AsyncMock, which skips _start_lightpanda_stderr_drain()’s isinstance(..., asyncio.StreamReader) branch. Swapping to a real StreamReader would cover the drain-task path more accurately.

Also applies to: 229-235

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/tools/browser/test_browser_manager_full.py` around lines 192 - 195,
Replace the test's mock_process.stderr AsyncMock with a real
asyncio.StreamReader so _start_lightpanda_stderr_drain()'s isinstance(...,
asyncio.StreamReader) branch executes; specifically, create an
asyncio.StreamReader() instance and assign it to mock_process.stderr (and if
needed use a helper to attach a dummy transport or call feed_data/feed_eof to
simulate stderr output) for both the mock_process setup at
mock_process.returncode = None / mock_process.stderr and the similar block
around lines 229-235 so the drain task path is exercised.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@code_puppy/tools/browser/browser_manager.py`:
- Around line 482-511: The custom browser lookup is case-sensitive while
built-in routing is not; modify the logic in initialize (where requested_browser
is computed, and where _CUSTOM_BROWSER_TYPES is consulted) to normalize case for
custom types too: compute a lowercased key (e.g., requested_browser) and map it
to the actual init function from _CUSTOM_BROWSER_TYPES by using a normalized-key
mapping or by storing/looking-up keys lowercased before calling init_func (refer
to self.browser_type, requested_browser, _CUSTOM_BROWSER_TYPES, and init_func)
so that "MyPlugin" and "myplugin" resolve the same way and still set
self._context/self._browser and self._initialized as before.

In `@tests/tools/browser/test_browser_manager_full.py`:
- Around line 192-195: Replace the test's mock_process.stderr AsyncMock with a
real asyncio.StreamReader so _start_lightpanda_stderr_drain()'s isinstance(...,
asyncio.StreamReader) branch executes; specifically, create an
asyncio.StreamReader() instance and assign it to mock_process.stderr (and if
needed use a helper to attach a dummy transport or call feed_data/feed_eof to
simulate stderr output) for both the mock_process setup at
mock_process.returncode = None / mock_process.stderr and the similar block
around lines 229-235 so the drain task path is exercised.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 660b1bc and ea79c33.

📒 Files selected for processing (2)
  • code_puppy/tools/browser/browser_manager.py
  • tests/tools/browser/test_browser_manager_full.py

@adybag14-cyber
Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 2, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
tests/tools/browser/test_browser_manager_full.py (1)

298-298: Minor: Redundant import inside test function.

asyncio is already imported at the module level (line 3). The import at line 298 is unnecessary.

Proposed fix
     `@pytest.mark.asyncio`
     async def test_connect_lightpanda_over_cdp_attempt_respects_timeout_budget(self):
         """Test CDP connect attempts are bounded by LIGHTPANDA_STARTUP_TIMEOUT."""
-        import asyncio
-
         from code_puppy.tools.browser.browser_manager import BrowserManager
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/tools/browser/test_browser_manager_full.py` at line 298, Remove the
redundant local "import asyncio" inside the test function and rely on the
module-level asyncio import (already present near the top of the file); locate
the inline "import asyncio" statement and delete it so there is only the single
module-level import left.
code_puppy/tools/browser/browser_manager.py (1)

228-233: Minor: Silent floor adjustment after validation may cause confusion.

The validation at line 228 requires timeout > 0, but line 233 then silently bumps values in (0, 1) to 1.0. A user setting LIGHTPANDA_STARTUP_TIMEOUT=0.5 might expect a 500ms timeout but will get 1 second without notice.

Consider either:

  1. Moving the floor check into the validation with a clear error message, or
  2. Documenting this behavior

This is a minor UX quirk, not a bug.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@code_puppy/tools/browser/browser_manager.py` around lines 228 - 233, The
current validation accepts timeout > 0 but then silently returns max(timeout,
1.0), which hides values in (0,1); update the validation in the function that
parses LIGHTPANDA_STARTUP_TIMEOUT (variables: timeout, timeout_raw) to enforce
the minimum floor at validation time (e.g., require timeout >= 1.0) and raise a
RuntimeError with a clear message like "LIGHTPANDA_STARTUP_TIMEOUT must be a
finite number >= 1.0, got: {timeout_raw}"; alternatively, if you prefer
non-breaking behavior, emit a warning or info log when max(timeout, 1.0) is
applied so users know a value <1.0 was bumped to 1.0 — but do not silently
adjust without notification.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@code_puppy/tools/browser/browser_manager.py`:
- Around line 228-233: The current validation accepts timeout > 0 but then
silently returns max(timeout, 1.0), which hides values in (0,1); update the
validation in the function that parses LIGHTPANDA_STARTUP_TIMEOUT (variables:
timeout, timeout_raw) to enforce the minimum floor at validation time (e.g.,
require timeout >= 1.0) and raise a RuntimeError with a clear message like
"LIGHTPANDA_STARTUP_TIMEOUT must be a finite number >= 1.0, got: {timeout_raw}";
alternatively, if you prefer non-breaking behavior, emit a warning or info log
when max(timeout, 1.0) is applied so users know a value <1.0 was bumped to 1.0 —
but do not silently adjust without notification.

In `@tests/tools/browser/test_browser_manager_full.py`:
- Line 298: Remove the redundant local "import asyncio" inside the test function
and rely on the module-level asyncio import (already present near the top of the
file); locate the inline "import asyncio" statement and delete it so there is
only the single module-level import left.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ea79c33 and a8f5cbe.

📒 Files selected for processing (2)
  • code_puppy/tools/browser/browser_manager.py
  • tests/tools/browser/test_browser_manager_full.py

@adybag14-cyber
Copy link
Copy Markdown
Author

okay code rabbit you are just rage baiting now

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants