patchpal 0.23.2__tar.gz → 0.24.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {patchpal-0.23.2/patchpal.egg-info → patchpal-0.24.1}/PKG-INFO +6 -1
- {patchpal-0.23.2 → patchpal-0.24.1}/README.md +2 -0
- {patchpal-0.23.2 → patchpal-0.24.1}/patchpal/__init__.py +1 -1
- {patchpal-0.23.2 → patchpal-0.24.1}/patchpal/agent/function_calling.py +86 -0
- {patchpal-0.23.2 → patchpal-0.24.1}/patchpal/cli/interactive.py +45 -1
- {patchpal-0.23.2 → patchpal-0.24.1}/patchpal/cli/sandbox.py +38 -7
- {patchpal-0.23.2 → patchpal-0.24.1}/patchpal/config.py +10 -0
- {patchpal-0.23.2 → patchpal-0.24.1}/patchpal/permissions.py +17 -0
- {patchpal-0.23.2 → patchpal-0.24.1}/patchpal/tools/__init__.py +52 -0
- patchpal-0.24.1/patchpal/tools/browser_tools.py +1171 -0
- {patchpal-0.23.2 → patchpal-0.24.1}/patchpal/tools/definitions.py +275 -0
- {patchpal-0.23.2 → patchpal-0.24.1}/patchpal/tools/shell_tools.py +5 -1
- {patchpal-0.23.2 → patchpal-0.24.1}/patchpal/tools/web_tools.py +71 -17
- {patchpal-0.23.2 → patchpal-0.24.1/patchpal.egg-info}/PKG-INFO +6 -1
- {patchpal-0.23.2 → patchpal-0.24.1}/patchpal.egg-info/SOURCES.txt +1 -0
- {patchpal-0.23.2 → patchpal-0.24.1}/patchpal.egg-info/requires.txt +4 -0
- {patchpal-0.23.2 → patchpal-0.24.1}/pyproject.toml +4 -0
- {patchpal-0.23.2 → patchpal-0.24.1}/tests/test_agent.py +40 -6
- {patchpal-0.23.2 → patchpal-0.24.1}/tests/test_permissions.py +28 -0
- {patchpal-0.23.2 → patchpal-0.24.1}/tests/test_tools.py +38 -0
- {patchpal-0.23.2 → patchpal-0.24.1}/LICENSE +0 -0
- {patchpal-0.23.2 → patchpal-0.24.1}/MANIFEST.in +0 -0
- {patchpal-0.23.2 → patchpal-0.24.1}/patchpal/agent/__init__.py +0 -0
- {patchpal-0.23.2 → patchpal-0.24.1}/patchpal/agent/bedrock_profile_utils.py +0 -0
- {patchpal-0.23.2 → patchpal-0.24.1}/patchpal/agent/react.py +0 -0
- {patchpal-0.23.2 → patchpal-0.24.1}/patchpal/cli/__init__.py +0 -0
- {patchpal-0.23.2 → patchpal-0.24.1}/patchpal/cli/autopilot.py +0 -0
- {patchpal-0.23.2 → patchpal-0.24.1}/patchpal/cli/mcp.py +0 -0
- {patchpal-0.23.2 → patchpal-0.24.1}/patchpal/cli/streaming.py +0 -0
- {patchpal-0.23.2 → patchpal-0.24.1}/patchpal/context.py +0 -0
- {patchpal-0.23.2 → patchpal-0.24.1}/patchpal/prompts/react_prompt.md +0 -0
- {patchpal-0.23.2 → patchpal-0.24.1}/patchpal/prompts/system_prompt.md +0 -0
- {patchpal-0.23.2 → patchpal-0.24.1}/patchpal/skills.py +0 -0
- {patchpal-0.23.2 → patchpal-0.24.1}/patchpal/tools/audit.py +0 -0
- {patchpal-0.23.2 → patchpal-0.24.1}/patchpal/tools/code_analysis.py +0 -0
- {patchpal-0.23.2 → patchpal-0.24.1}/patchpal/tools/common.py +0 -0
- {patchpal-0.23.2 → patchpal-0.24.1}/patchpal/tools/file_reading.py +0 -0
- {patchpal-0.23.2 → patchpal-0.24.1}/patchpal/tools/file_writing.py +0 -0
- {patchpal-0.23.2 → patchpal-0.24.1}/patchpal/tools/find_tool.py +0 -0
- {patchpal-0.23.2 → patchpal-0.24.1}/patchpal/tools/grep_tool.py +0 -0
- {patchpal-0.23.2 → patchpal-0.24.1}/patchpal/tools/image_handler.py +0 -0
- {patchpal-0.23.2 → patchpal-0.24.1}/patchpal/tools/mcp.py +0 -0
- {patchpal-0.23.2 → patchpal-0.24.1}/patchpal/tools/repo_map.py +0 -0
- {patchpal-0.23.2 → patchpal-0.24.1}/patchpal/tools/todo_tools.py +0 -0
- {patchpal-0.23.2 → patchpal-0.24.1}/patchpal/tools/tool_schema.py +0 -0
- {patchpal-0.23.2 → patchpal-0.24.1}/patchpal/tools/user_interaction.py +0 -0
- {patchpal-0.23.2 → patchpal-0.24.1}/patchpal.egg-info/dependency_links.txt +0 -0
- {patchpal-0.23.2 → patchpal-0.24.1}/patchpal.egg-info/entry_points.txt +0 -0
- {patchpal-0.23.2 → patchpal-0.24.1}/patchpal.egg-info/top_level.txt +0 -0
- {patchpal-0.23.2 → patchpal-0.24.1}/setup.cfg +0 -0
- {patchpal-0.23.2 → patchpal-0.24.1}/tests/test_cli.py +0 -0
- {patchpal-0.23.2 → patchpal-0.24.1}/tests/test_config_dynamic.py +0 -0
- {patchpal-0.23.2 → patchpal-0.24.1}/tests/test_context.py +0 -0
- {patchpal-0.23.2 → patchpal-0.24.1}/tests/test_custom_tools.py +0 -0
- {patchpal-0.23.2 → patchpal-0.24.1}/tests/test_enabled_tools.py +0 -0
- {patchpal-0.23.2 → patchpal-0.24.1}/tests/test_find_tool.py +0 -0
- {patchpal-0.23.2 → patchpal-0.24.1}/tests/test_guardrails.py +0 -0
- {patchpal-0.23.2 → patchpal-0.24.1}/tests/test_image_blocking.py +0 -0
- {patchpal-0.23.2 → patchpal-0.24.1}/tests/test_maximum_security.py +0 -0
- {patchpal-0.23.2 → patchpal-0.24.1}/tests/test_mcp_config.py +0 -0
- {patchpal-0.23.2 → patchpal-0.24.1}/tests/test_memory.py +0 -0
- {patchpal-0.23.2 → patchpal-0.24.1}/tests/test_memory_locations.py +0 -0
- {patchpal-0.23.2 → patchpal-0.24.1}/tests/test_operational_safety.py +0 -0
- {patchpal-0.23.2 → patchpal-0.24.1}/tests/test_optional_tools.py +0 -0
- {patchpal-0.23.2 → patchpal-0.24.1}/tests/test_react.py +0 -0
- {patchpal-0.23.2 → patchpal-0.24.1}/tests/test_reasoning_content.py +0 -0
- {patchpal-0.23.2 → patchpal-0.24.1}/tests/test_repo_map.py +0 -0
- {patchpal-0.23.2 → patchpal-0.24.1}/tests/test_simplified_prompt.py +0 -0
- {patchpal-0.23.2 → patchpal-0.24.1}/tests/test_skills.py +0 -0
- {patchpal-0.23.2 → patchpal-0.24.1}/tests/test_streaming.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: patchpal
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.24.1
|
|
4
4
|
Summary: An agentic coding and automation assistant, supporting both local and cloud LLMs
|
|
5
5
|
Author: PatchPal Contributors
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -38,6 +38,9 @@ Requires-Dist: ruff==0.14.13; extra == "dev"
|
|
|
38
38
|
Requires-Dist: pre-commit>=3.0.0; extra == "dev"
|
|
39
39
|
Provides-Extra: mcp
|
|
40
40
|
Requires-Dist: mcp>=0.9.0; extra == "mcp"
|
|
41
|
+
Provides-Extra: browser
|
|
42
|
+
Requires-Dist: playwright>=1.40.0; extra == "browser"
|
|
43
|
+
Requires-Dist: nest-asyncio>=1.5.0; extra == "browser"
|
|
41
44
|
Provides-Extra: docs
|
|
42
45
|
Requires-Dist: mkdocs<2.0,>=1.5.0; extra == "docs"
|
|
43
46
|
Requires-Dist: mkdocs-material>=9.5.0; extra == "docs"
|
|
@@ -62,6 +65,7 @@ Most agent frameworks are [built in TypeScript](https://news.ycombinator.com/ite
|
|
|
62
65
|
- [Sandbox Mode](https://amaiya.github.io/patchpal/usage/sandbox/) for secure container isolation
|
|
63
66
|
- [Python SDK](https://amaiya.github.io/patchpal/usage/python-api/) for flexibility and extensibility
|
|
64
67
|
- [Built-In](https://amaiya.github.io/patchpal/features/tools/) and [Custom Tools](https://amaiya.github.io/patchpal/features/custom-tools/)
|
|
68
|
+
- [Browser Automation](https://amaiya.github.io/patchpal/features/tools/#browser-automation-14-tools-optional) for interactive web tasks, form filling, and JavaScript-heavy sites
|
|
65
69
|
- [Skills System](https://amaiya.github.io/patchpal/features/skills/) and [MCP Integration](https://amaiya.github.io/patchpal/features/mcp/)
|
|
66
70
|
- [Autopilot Mode](https://amaiya.github.io/patchpal/usage/autopilot/) using [Ralph Wiggum loops](https://github.com/amaiya/patchpal/tree/main/examples/ralph/)
|
|
67
71
|
- [Project Memory](https://amaiya.github.io/patchpal/features/memory/) automatically loads project context from `MEMORY.md` (repository root or `~/.patchpal/repos/<repo-name>/MEMORY.md`) at startup.
|
|
@@ -108,6 +112,7 @@ docker run -it --rm \
|
|
|
108
112
|
|
|
109
113
|
## Setup
|
|
110
114
|
0. **Install**: `pip install patchpal`
|
|
115
|
+
- **Optional**: For browser automation (13 additional tools): `pip install patchpal[browser] && python -m playwright install chromium`
|
|
111
116
|
1. **Get an API key or a Local LLM Engine**:
|
|
112
117
|
- **[Cloud]** For Anthropic models (default): Sign up at https://console.anthropic.com/
|
|
113
118
|
- **[Cloud]** For OpenAI models: Get a key from https://platform.openai.com/
|
|
@@ -14,6 +14,7 @@ Most agent frameworks are [built in TypeScript](https://news.ycombinator.com/ite
|
|
|
14
14
|
- [Sandbox Mode](https://amaiya.github.io/patchpal/usage/sandbox/) for secure container isolation
|
|
15
15
|
- [Python SDK](https://amaiya.github.io/patchpal/usage/python-api/) for flexibility and extensibility
|
|
16
16
|
- [Built-In](https://amaiya.github.io/patchpal/features/tools/) and [Custom Tools](https://amaiya.github.io/patchpal/features/custom-tools/)
|
|
17
|
+
- [Browser Automation](https://amaiya.github.io/patchpal/features/tools/#browser-automation-14-tools-optional) for interactive web tasks, form filling, and JavaScript-heavy sites
|
|
17
18
|
- [Skills System](https://amaiya.github.io/patchpal/features/skills/) and [MCP Integration](https://amaiya.github.io/patchpal/features/mcp/)
|
|
18
19
|
- [Autopilot Mode](https://amaiya.github.io/patchpal/usage/autopilot/) using [Ralph Wiggum loops](https://github.com/amaiya/patchpal/tree/main/examples/ralph/)
|
|
19
20
|
- [Project Memory](https://amaiya.github.io/patchpal/features/memory/) automatically loads project context from `MEMORY.md` (repository root or `~/.patchpal/repos/<repo-name>/MEMORY.md`) at startup.
|
|
@@ -60,6 +61,7 @@ docker run -it --rm \
|
|
|
60
61
|
|
|
61
62
|
## Setup
|
|
62
63
|
0. **Install**: `pip install patchpal`
|
|
64
|
+
- **Optional**: For browser automation (13 additional tools): `pip install patchpal[browser] && python -m playwright install chromium`
|
|
63
65
|
1. **Get an API key or a Local LLM Engine**:
|
|
64
66
|
- **[Cloud]** For Anthropic models (default): Sign up at https://console.anthropic.com/
|
|
65
67
|
- **[Cloud]** For OpenAI models: Get a key from https://platform.openai.com/
|
|
@@ -1496,6 +1496,92 @@ Repository root takes priority if both exist."""
|
|
|
1496
1496
|
"\033[2m❓ Asking user a question...\033[0m",
|
|
1497
1497
|
flush=True,
|
|
1498
1498
|
)
|
|
1499
|
+
elif tool_name == "browser_navigate":
|
|
1500
|
+
print(
|
|
1501
|
+
f"\033[2m🌐 Browser navigating: {tool_args.get('url', '')}\033[0m",
|
|
1502
|
+
flush=True,
|
|
1503
|
+
)
|
|
1504
|
+
elif tool_name == "browser_click":
|
|
1505
|
+
print(
|
|
1506
|
+
f"\033[2m🖱️ Browser clicking: {tool_args.get('selector', '')}\033[0m",
|
|
1507
|
+
flush=True,
|
|
1508
|
+
)
|
|
1509
|
+
elif tool_name == "browser_fill":
|
|
1510
|
+
print(
|
|
1511
|
+
f"\033[2m⌨️ Browser filling: {tool_args.get('selector', '')}\033[0m",
|
|
1512
|
+
flush=True,
|
|
1513
|
+
)
|
|
1514
|
+
elif tool_name == "browser_screenshot":
|
|
1515
|
+
print(
|
|
1516
|
+
f"\033[2m📸 Browser screenshot: {tool_args.get('path', '') or 'default path'}\033[0m",
|
|
1517
|
+
flush=True,
|
|
1518
|
+
)
|
|
1519
|
+
elif tool_name == "browser_get_text":
|
|
1520
|
+
print(
|
|
1521
|
+
"\033[2m📄 Browser extracting page text...\033[0m",
|
|
1522
|
+
flush=True,
|
|
1523
|
+
)
|
|
1524
|
+
elif tool_name == "browser_get_html":
|
|
1525
|
+
print(
|
|
1526
|
+
"\033[2m📋 Browser extracting HTML source...\033[0m",
|
|
1527
|
+
flush=True,
|
|
1528
|
+
)
|
|
1529
|
+
elif tool_name == "browser_scroll":
|
|
1530
|
+
direction = tool_args.get("direction", "down")
|
|
1531
|
+
selector = tool_args.get("selector", "")
|
|
1532
|
+
if selector:
|
|
1533
|
+
scroll_desc = f"to '{selector}'"
|
|
1534
|
+
else:
|
|
1535
|
+
scroll_desc = direction
|
|
1536
|
+
print(
|
|
1537
|
+
f"\033[2m📜 Browser scrolling {scroll_desc}...\033[0m",
|
|
1538
|
+
flush=True,
|
|
1539
|
+
)
|
|
1540
|
+
elif tool_name == "browser_execute_script":
|
|
1541
|
+
script_preview = tool_args.get("script", "")[:50]
|
|
1542
|
+
if len(tool_args.get("script", "")) > 50:
|
|
1543
|
+
script_preview += "..."
|
|
1544
|
+
print(
|
|
1545
|
+
f"\033[2m⚡ Browser executing script: {script_preview}\033[0m",
|
|
1546
|
+
flush=True,
|
|
1547
|
+
)
|
|
1548
|
+
elif tool_name == "browser_wait":
|
|
1549
|
+
selector = tool_args.get("selector", "")
|
|
1550
|
+
if selector:
|
|
1551
|
+
wait_desc = f"for '{selector}'"
|
|
1552
|
+
else:
|
|
1553
|
+
wait_desc = f"{tool_args.get('milliseconds', 1000)}ms"
|
|
1554
|
+
print(
|
|
1555
|
+
f"\033[2m⏳ Browser waiting {wait_desc}...\033[0m",
|
|
1556
|
+
flush=True,
|
|
1557
|
+
)
|
|
1558
|
+
elif tool_name == "browser_close":
|
|
1559
|
+
print(
|
|
1560
|
+
"\033[2m🚪 Closing browser...\033[0m",
|
|
1561
|
+
flush=True,
|
|
1562
|
+
)
|
|
1563
|
+
elif tool_name == "browser_list_frames":
|
|
1564
|
+
print(
|
|
1565
|
+
"\033[2m🖼️ Browser listing frames...\033[0m",
|
|
1566
|
+
flush=True,
|
|
1567
|
+
)
|
|
1568
|
+
elif tool_name == "browser_switch_frame":
|
|
1569
|
+
frame_info = ""
|
|
1570
|
+
if "index" in tool_args and tool_args["index"] is not None:
|
|
1571
|
+
frame_info = f"index {tool_args['index']}"
|
|
1572
|
+
elif "name" in tool_args and tool_args["name"] is not None:
|
|
1573
|
+
frame_info = f"'{tool_args['name']}'"
|
|
1574
|
+
else:
|
|
1575
|
+
frame_info = "main page"
|
|
1576
|
+
print(
|
|
1577
|
+
f"\033[2m🖼️ Browser switching to frame: {frame_info}\033[0m",
|
|
1578
|
+
flush=True,
|
|
1579
|
+
)
|
|
1580
|
+
elif tool_name == "browser_dismiss_modals":
|
|
1581
|
+
print(
|
|
1582
|
+
"\033[2m🚫 Browser dismissing modals...\033[0m",
|
|
1583
|
+
flush=True,
|
|
1584
|
+
)
|
|
1499
1585
|
else:
|
|
1500
1586
|
# Check if this is an MCP tool by looking for __mcp_server__ attribute
|
|
1501
1587
|
if hasattr(tool_func, "__mcp_server__"):
|
|
@@ -575,6 +575,32 @@ Supported models: Any LiteLLM-supported model
|
|
|
575
575
|
pass # Don't fail if audit logging fails
|
|
576
576
|
|
|
577
577
|
print("\nGoodbye!")
|
|
578
|
+
|
|
579
|
+
# Clean up the browser if it was opened during the session.
|
|
580
|
+
# An open Playwright browser keeps its own asyncio machinery
|
|
581
|
+
# alive; closing it here releases the browser process/loop so
|
|
582
|
+
# they don't linger into interpreter shutdown.
|
|
583
|
+
try:
|
|
584
|
+
from patchpal.tools.browser_tools import _BrowserState
|
|
585
|
+
|
|
586
|
+
if _BrowserState.is_open():
|
|
587
|
+
_BrowserState.close(silent=True)
|
|
588
|
+
except Exception:
|
|
589
|
+
pass # Browser tools optional / already closed
|
|
590
|
+
|
|
591
|
+
# Suppress the benign "Task exception was never retrieved"
|
|
592
|
+
# warning that asyncio emits at shutdown: prompt_toolkit's
|
|
593
|
+
# pending Application.run_async() task gets cancelled with a
|
|
594
|
+
# KeyboardInterrupt during teardown, and nobody retrieves it.
|
|
595
|
+
# This is cosmetic and happens AFTER all work is done.
|
|
596
|
+
try:
|
|
597
|
+
import asyncio
|
|
598
|
+
|
|
599
|
+
_loop = asyncio.get_event_loop()
|
|
600
|
+
_loop.set_exception_handler(lambda loop, context: None)
|
|
601
|
+
except Exception:
|
|
602
|
+
pass
|
|
603
|
+
|
|
578
604
|
break
|
|
579
605
|
|
|
580
606
|
# Handle /help command - show available commands
|
|
@@ -1633,7 +1659,25 @@ Supported models: Any LiteLLM-supported model
|
|
|
1633
1659
|
)
|
|
1634
1660
|
continue
|
|
1635
1661
|
except Exception as e:
|
|
1636
|
-
|
|
1662
|
+
# Debug: Print full exception info to help diagnose asyncio errors
|
|
1663
|
+
import traceback
|
|
1664
|
+
|
|
1665
|
+
error_str = str(e)
|
|
1666
|
+
print(f"\n\033[1;31mError:\033[0m {error_str}")
|
|
1667
|
+
|
|
1668
|
+
# If it's the asyncio error, print more debug info
|
|
1669
|
+
if "asyncio.run() cannot be called from a running event loop" in error_str:
|
|
1670
|
+
print("\n\033[1;33m[DEBUG] AsyncIO Error Details:\033[0m")
|
|
1671
|
+
print(
|
|
1672
|
+
"This error occurs when asyncio.run() is called while an event loop is already running."
|
|
1673
|
+
)
|
|
1674
|
+
print("Stack trace:")
|
|
1675
|
+
traceback.print_exc()
|
|
1676
|
+
print(
|
|
1677
|
+
"\n\033[1;33m[DEBUG] If browser tools were just used, this may be a Playwright cleanup issue.\033[0m"
|
|
1678
|
+
)
|
|
1679
|
+
print("Try: Close any open browser windows and type 'exit' to restart PatchPal.\n")
|
|
1680
|
+
|
|
1637
1681
|
print("Please try again or type 'exit' to quit.")
|
|
1638
1682
|
|
|
1639
1683
|
|
|
@@ -18,11 +18,17 @@ import sys
|
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
def load_env_file(env_file):
|
|
21
|
-
"""Load environment variables from .env file.
|
|
21
|
+
"""Load environment variables from .env file.
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
set: Keys of the environment variables that were loaded from the file.
|
|
25
|
+
"""
|
|
22
26
|
if not os.path.exists(env_file):
|
|
23
27
|
print(f"❌ Error: .env file not found: {env_file}", file=sys.stderr)
|
|
24
28
|
sys.exit(1)
|
|
25
29
|
|
|
30
|
+
loaded_keys = set()
|
|
31
|
+
|
|
26
32
|
with open(env_file, "r") as f:
|
|
27
33
|
for line in f:
|
|
28
34
|
line = line.strip()
|
|
@@ -43,6 +49,9 @@ def load_env_file(env_file):
|
|
|
43
49
|
value = value[1:-1]
|
|
44
50
|
|
|
45
51
|
os.environ[key] = value
|
|
52
|
+
loaded_keys.add(key)
|
|
53
|
+
|
|
54
|
+
return loaded_keys
|
|
46
55
|
|
|
47
56
|
|
|
48
57
|
def detect_runtime():
|
|
@@ -354,8 +363,17 @@ echo ""
|
|
|
354
363
|
return script
|
|
355
364
|
|
|
356
365
|
|
|
357
|
-
def build_container_args(sandbox_args, patchpal_args):
|
|
358
|
-
"""Build container runtime arguments.
|
|
366
|
+
def build_container_args(sandbox_args, patchpal_args, env_file_keys=None):
|
|
367
|
+
"""Build container runtime arguments.
|
|
368
|
+
|
|
369
|
+
Args:
|
|
370
|
+
sandbox_args: Parsed sandbox CLI arguments
|
|
371
|
+
patchpal_args: Arguments to pass through to patchpal
|
|
372
|
+
env_file_keys: Set of env var names loaded from --env-file (if any).
|
|
373
|
+
These are forwarded into the container even though the
|
|
374
|
+
rest of the host environment is not (isolation mode).
|
|
375
|
+
"""
|
|
376
|
+
env_file_keys = env_file_keys or set()
|
|
359
377
|
runtime = detect_runtime()
|
|
360
378
|
if not runtime:
|
|
361
379
|
print("❌ Error: Neither Docker nor Podman found", file=sys.stderr)
|
|
@@ -456,8 +474,12 @@ def build_container_args(sandbox_args, patchpal_args):
|
|
|
456
474
|
if sandbox_args.cpus:
|
|
457
475
|
container_args.extend(["--cpus", str(sandbox_args.cpus)])
|
|
458
476
|
|
|
459
|
-
# Pass through environment variables
|
|
460
|
-
#
|
|
477
|
+
# Pass through environment variables.
|
|
478
|
+
# WITHOUT --env-file: pass through matching vars from the full host environment.
|
|
479
|
+
# WITH --env-file: isolate from the host, but still forward the vars that were
|
|
480
|
+
# explicitly loaded from the file itself (previously these were
|
|
481
|
+
# loaded into os.environ but never forwarded into the container,
|
|
482
|
+
# causing credentials in --env-file to be silently dropped).
|
|
461
483
|
if not sandbox_args.env_file:
|
|
462
484
|
# Pass through PATCHPAL_* environment variables
|
|
463
485
|
for key, value in os.environ.items():
|
|
@@ -496,6 +518,12 @@ def build_container_args(sandbox_args, patchpal_args):
|
|
|
496
518
|
for key, value in os.environ.items():
|
|
497
519
|
if key.startswith("OLLAMA_"):
|
|
498
520
|
container_args.extend(["-e", f"{key}={value}"])
|
|
521
|
+
else:
|
|
522
|
+
# Isolated mode: only forward the specific vars loaded from --env-file
|
|
523
|
+
for key in env_file_keys:
|
|
524
|
+
value = os.environ.get(key)
|
|
525
|
+
if value is not None:
|
|
526
|
+
container_args.extend(["-e", f"{key}={value}"])
|
|
499
527
|
|
|
500
528
|
# Track mounted paths to avoid duplicates
|
|
501
529
|
mounted_paths = set()
|
|
@@ -1002,9 +1030,10 @@ def main():
|
|
|
1002
1030
|
sandbox_args.network = "host"
|
|
1003
1031
|
|
|
1004
1032
|
# Load .env file if specified
|
|
1033
|
+
env_file_keys = set()
|
|
1005
1034
|
if sandbox_args.env_file:
|
|
1006
1035
|
print(f"Loading environment variables from: {sandbox_args.env_file}")
|
|
1007
|
-
load_env_file(sandbox_args.env_file)
|
|
1036
|
+
env_file_keys = load_env_file(sandbox_args.env_file)
|
|
1008
1037
|
|
|
1009
1038
|
# If LITELLM_KWARGS contains AWS params, normalize env var names to uppercase
|
|
1010
1039
|
# This must happen before endpoint detection for AWS Bedrock auto-detection to work
|
|
@@ -1023,6 +1052,7 @@ def main():
|
|
|
1023
1052
|
upper_key = key.upper()
|
|
1024
1053
|
if upper_key not in os.environ:
|
|
1025
1054
|
os.environ[upper_key] = value
|
|
1055
|
+
env_file_keys.add(upper_key)
|
|
1026
1056
|
except (json.JSONDecodeError, ValueError):
|
|
1027
1057
|
# Not JSON, check if individual env vars exist (comma-separated format)
|
|
1028
1058
|
for key, value in list(os.environ.items()):
|
|
@@ -1030,6 +1060,7 @@ def main():
|
|
|
1030
1060
|
upper_key = key.upper()
|
|
1031
1061
|
if upper_key not in os.environ:
|
|
1032
1062
|
os.environ[upper_key] = value
|
|
1063
|
+
env_file_keys.add(upper_key)
|
|
1033
1064
|
|
|
1034
1065
|
# Auto-detect LLM endpoints if network restrictions are enabled
|
|
1035
1066
|
detected_endpoints = []
|
|
@@ -1060,7 +1091,7 @@ def main():
|
|
|
1060
1091
|
)
|
|
1061
1092
|
|
|
1062
1093
|
# Build container command
|
|
1063
|
-
container_args, runtime = build_container_args(sandbox_args, patchpal_argv)
|
|
1094
|
+
container_args, runtime = build_container_args(sandbox_args, patchpal_argv, env_file_keys)
|
|
1064
1095
|
|
|
1065
1096
|
# Show what we're doing
|
|
1066
1097
|
print(f"Using container runtime: {runtime}")
|
|
@@ -79,6 +79,16 @@ class Config:
|
|
|
79
79
|
"""Enable web search and fetch tools (default: true)."""
|
|
80
80
|
return _get_env_bool("PATCHPAL_ENABLE_WEB", "true")
|
|
81
81
|
|
|
82
|
+
@property
|
|
83
|
+
def ENABLE_BROWSER(self) -> bool:
|
|
84
|
+
"""Enable browser automation tools (default: true).
|
|
85
|
+
|
|
86
|
+
Browser tools require web access, so they are only available when
|
|
87
|
+
ENABLE_WEB is also true (and Playwright is installed). Set this to
|
|
88
|
+
false to disable the browser tools while keeping web_search/web_fetch.
|
|
89
|
+
"""
|
|
90
|
+
return _get_env_bool("PATCHPAL_ENABLE_BROWSER", "true")
|
|
91
|
+
|
|
82
92
|
@property
|
|
83
93
|
def ENABLE_MCP(self) -> bool:
|
|
84
94
|
"""Enable Model Context Protocol tools (default: true)."""
|
|
@@ -358,6 +358,23 @@ class PermissionManager:
|
|
|
358
358
|
if self._check_grant_list(self.persistent_grants, tool_name, pattern, full_command):
|
|
359
359
|
return True
|
|
360
360
|
|
|
361
|
+
# Special case: "browser" pattern applies across all browser tools
|
|
362
|
+
# If browser pattern was granted to ANY browser tool, apply to all
|
|
363
|
+
if pattern == "browser":
|
|
364
|
+
browser_tools = [
|
|
365
|
+
"browser_navigate",
|
|
366
|
+
"browser_click",
|
|
367
|
+
"browser_fill",
|
|
368
|
+
"browser_execute_script",
|
|
369
|
+
]
|
|
370
|
+
for browser_tool in browser_tools:
|
|
371
|
+
if self._check_grant_list(self.session_grants, browser_tool, pattern, full_command):
|
|
372
|
+
return True
|
|
373
|
+
if self._check_grant_list(
|
|
374
|
+
self.persistent_grants, browser_tool, pattern, full_command
|
|
375
|
+
):
|
|
376
|
+
return True
|
|
377
|
+
|
|
361
378
|
return False
|
|
362
379
|
|
|
363
380
|
def _grant_permission(
|
|
@@ -67,6 +67,42 @@ from patchpal.tools.web_tools import (
|
|
|
67
67
|
web_search,
|
|
68
68
|
)
|
|
69
69
|
|
|
70
|
+
# Browser tools (optional - only available if Playwright installed)
|
|
71
|
+
try:
|
|
72
|
+
from patchpal.tools.browser_tools import (
|
|
73
|
+
PLAYWRIGHT_AVAILABLE,
|
|
74
|
+
browser_click,
|
|
75
|
+
browser_close,
|
|
76
|
+
browser_dismiss_modals,
|
|
77
|
+
browser_execute_script,
|
|
78
|
+
browser_fill,
|
|
79
|
+
browser_get_html,
|
|
80
|
+
browser_get_text,
|
|
81
|
+
browser_list_frames,
|
|
82
|
+
browser_navigate,
|
|
83
|
+
browser_press_key,
|
|
84
|
+
browser_screenshot,
|
|
85
|
+
browser_scroll,
|
|
86
|
+
browser_switch_frame,
|
|
87
|
+
browser_wait,
|
|
88
|
+
)
|
|
89
|
+
except ImportError:
|
|
90
|
+
PLAYWRIGHT_AVAILABLE = False
|
|
91
|
+
browser_navigate = None
|
|
92
|
+
browser_click = None
|
|
93
|
+
browser_fill = None
|
|
94
|
+
browser_screenshot = None
|
|
95
|
+
browser_get_text = None
|
|
96
|
+
browser_get_html = None
|
|
97
|
+
browser_scroll = None
|
|
98
|
+
browser_execute_script = None
|
|
99
|
+
browser_press_key = None
|
|
100
|
+
browser_wait = None
|
|
101
|
+
browser_close = None
|
|
102
|
+
browser_dismiss_modals = None
|
|
103
|
+
browser_list_frames = None
|
|
104
|
+
browser_switch_frame = None
|
|
105
|
+
|
|
70
106
|
__all__ = [
|
|
71
107
|
# File operations
|
|
72
108
|
"read_file",
|
|
@@ -96,6 +132,22 @@ __all__ = [
|
|
|
96
132
|
"web_fetch",
|
|
97
133
|
"web_search",
|
|
98
134
|
"get_url_tracker",
|
|
135
|
+
# Browser tools (optional)
|
|
136
|
+
"PLAYWRIGHT_AVAILABLE",
|
|
137
|
+
"browser_navigate",
|
|
138
|
+
"browser_click",
|
|
139
|
+
"browser_fill",
|
|
140
|
+
"browser_screenshot",
|
|
141
|
+
"browser_get_text",
|
|
142
|
+
"browser_get_html",
|
|
143
|
+
"browser_scroll",
|
|
144
|
+
"browser_execute_script",
|
|
145
|
+
"browser_press_key",
|
|
146
|
+
"browser_wait",
|
|
147
|
+
"browser_close",
|
|
148
|
+
"browser_dismiss_modals",
|
|
149
|
+
"browser_list_frames",
|
|
150
|
+
"browser_switch_frame",
|
|
99
151
|
# Shell tools
|
|
100
152
|
"run_shell",
|
|
101
153
|
# User interaction
|