code-puppy 0.0.202__py3-none-any.whl → 0.0.204__py3-none-any.whl
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.
- code_puppy/agents/agent_manager.py +34 -5
- code_puppy/agents/base_agent.py +2 -2
- code_puppy/main.py +7 -1
- code_puppy/tools/browser/camoufox_manager.py +52 -14
- {code_puppy-0.0.202.dist-info → code_puppy-0.0.204.dist-info}/METADATA +2 -2
- {code_puppy-0.0.202.dist-info → code_puppy-0.0.204.dist-info}/RECORD +10 -10
- {code_puppy-0.0.202.data → code_puppy-0.0.204.data}/data/code_puppy/models.json +0 -0
- {code_puppy-0.0.202.dist-info → code_puppy-0.0.204.dist-info}/WHEEL +0 -0
- {code_puppy-0.0.202.dist-info → code_puppy-0.0.204.dist-info}/entry_points.txt +0 -0
- {code_puppy-0.0.202.dist-info → code_puppy-0.0.204.dist-info}/licenses/LICENSE +0 -0
@@ -51,21 +51,50 @@ def get_terminal_session_id() -> str:
|
|
51
51
|
|
52
52
|
|
53
53
|
def _is_process_alive(pid: int) -> bool:
|
54
|
-
"""Check if a process with the given PID is still alive.
|
54
|
+
"""Check if a process with the given PID is still alive, cross-platform.
|
55
55
|
|
56
56
|
Args:
|
57
57
|
pid: Process ID to check
|
58
58
|
|
59
59
|
Returns:
|
60
|
-
bool: True if process exists, False otherwise
|
60
|
+
bool: True if process likely exists, False otherwise
|
61
61
|
"""
|
62
62
|
try:
|
63
|
-
|
64
|
-
|
65
|
-
|
63
|
+
if os.name == "nt":
|
64
|
+
# Windows: use OpenProcess to probe liveness safely
|
65
|
+
import ctypes
|
66
|
+
from ctypes import wintypes
|
67
|
+
|
68
|
+
PROCESS_QUERY_LIMITED_INFORMATION = 0x1000
|
69
|
+
kernel32 = ctypes.windll.kernel32 # type: ignore[attr-defined]
|
70
|
+
kernel32.OpenProcess.argtypes = [wintypes.DWORD, wintypes.BOOL, wintypes.DWORD]
|
71
|
+
kernel32.OpenProcess.restype = wintypes.HANDLE
|
72
|
+
handle = kernel32.OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, False, int(pid))
|
73
|
+
if handle:
|
74
|
+
kernel32.CloseHandle(handle)
|
75
|
+
return True
|
76
|
+
# If access denied, process likely exists but we can't query it
|
77
|
+
last_error = kernel32.GetLastError()
|
78
|
+
# ERROR_ACCESS_DENIED = 5
|
79
|
+
if last_error == 5:
|
80
|
+
return True
|
81
|
+
return False
|
82
|
+
else:
|
83
|
+
# Unix-like: signal 0 does not deliver a signal but checks existence
|
84
|
+
os.kill(int(pid), 0)
|
85
|
+
return True
|
86
|
+
except PermissionError:
|
87
|
+
# No permission to signal -> process exists
|
66
88
|
return True
|
67
89
|
except (OSError, ProcessLookupError):
|
90
|
+
# Process does not exist
|
91
|
+
return False
|
92
|
+
except ValueError:
|
93
|
+
# Invalid signal or pid format
|
68
94
|
return False
|
95
|
+
except Exception:
|
96
|
+
# Be conservative – don't crash session cleanup due to platform quirks
|
97
|
+
return True
|
69
98
|
|
70
99
|
|
71
100
|
def _cleanup_dead_sessions(sessions: dict[str, str]) -> dict[str, str]:
|
code_puppy/agents/base_agent.py
CHANGED
@@ -13,7 +13,7 @@ import pydantic
|
|
13
13
|
import pydantic_ai.models
|
14
14
|
from pydantic_ai import Agent as PydanticAgent
|
15
15
|
from pydantic_ai import BinaryContent, DocumentUrl, ImageUrl
|
16
|
-
from pydantic_ai import RunContext, UsageLimitExceeded
|
16
|
+
from pydantic_ai import RunContext, UsageLimitExceeded, UsageLimits
|
17
17
|
from pydantic_ai.messages import (
|
18
18
|
ModelMessage,
|
19
19
|
ModelRequest,
|
@@ -950,7 +950,7 @@ class BaseAgent(ABC):
|
|
950
950
|
self.set_message_history(
|
951
951
|
self.prune_interrupted_tool_calls(self.get_message_history())
|
952
952
|
)
|
953
|
-
usage_limits =
|
953
|
+
usage_limits = UsageLimits(request_limit=get_message_limit())
|
954
954
|
result_ = await pydantic_agent.run(
|
955
955
|
prompt_payload,
|
956
956
|
message_history=self.get_message_history(),
|
code_puppy/main.py
CHANGED
@@ -421,7 +421,13 @@ async def interactive_mode(message_renderer, initial_command: str = None) -> Non
|
|
421
421
|
|
422
422
|
# Handle / commands based on cleaned prompt (after stripping attachments)
|
423
423
|
if cleaned_for_commands.startswith("/"):
|
424
|
-
|
424
|
+
try:
|
425
|
+
command_result = handle_command(cleaned_for_commands)
|
426
|
+
except Exception as e:
|
427
|
+
from code_puppy.messaging import emit_error
|
428
|
+
emit_error(f"Command error: {e}")
|
429
|
+
# Continue interactive loop instead of exiting
|
430
|
+
continue
|
425
431
|
if command_result is True:
|
426
432
|
continue
|
427
433
|
elif isinstance(command_result, str):
|
@@ -1,6 +1,7 @@
|
|
1
1
|
"""Camoufox browser manager - privacy-focused Firefox automation."""
|
2
2
|
|
3
|
-
from
|
3
|
+
from pathlib import Path
|
4
|
+
from typing import Optional, TypeAlias
|
4
5
|
|
5
6
|
import camoufox
|
6
7
|
from camoufox.addons import DefaultAddons
|
@@ -9,6 +10,8 @@ from camoufox.locale import ALLOW_GEOIP, download_mmdb
|
|
9
10
|
from camoufox.pkgman import CamoufoxFetcher, camoufox_path
|
10
11
|
from playwright.async_api import Browser, BrowserContext, Page
|
11
12
|
|
13
|
+
_MIN_VIEWPORT_DIMENSION = 640
|
14
|
+
|
12
15
|
from code_puppy.messaging import emit_info
|
13
16
|
|
14
17
|
|
@@ -38,6 +41,9 @@ class CamoufoxManager:
|
|
38
41
|
self.block_webrtc = True # Block WebRTC for privacy
|
39
42
|
self.humanize = True # Add human-like behavior
|
40
43
|
|
44
|
+
# Persistent profile directory for consistent browser state across runs
|
45
|
+
self.profile_dir = self._get_profile_directory()
|
46
|
+
|
41
47
|
@classmethod
|
42
48
|
def get_instance(cls) -> "CamoufoxManager":
|
43
49
|
"""Get the singleton instance."""
|
@@ -45,6 +51,16 @@ class CamoufoxManager:
|
|
45
51
|
cls._instance = cls()
|
46
52
|
return cls._instance
|
47
53
|
|
54
|
+
def _get_profile_directory(self) -> Path:
|
55
|
+
"""Get or create the persistent profile directory.
|
56
|
+
|
57
|
+
Returns a Path object pointing to ~/.code_puppy/camoufox_profile
|
58
|
+
where browser data (cookies, history, bookmarks, etc.) will be stored.
|
59
|
+
"""
|
60
|
+
profile_path = Path.home() / ".code_puppy" / "camoufox_profile"
|
61
|
+
profile_path.mkdir(parents=True, exist_ok=True)
|
62
|
+
return profile_path
|
63
|
+
|
48
64
|
async def async_initialize(self) -> None:
|
49
65
|
"""Initialize Camoufox browser."""
|
50
66
|
if self._initialized:
|
@@ -68,30 +84,40 @@ class CamoufoxManager:
|
|
68
84
|
|
69
85
|
async def _initialize_camoufox(self) -> None:
|
70
86
|
"""Try to start Camoufox with the configured privacy settings."""
|
87
|
+
emit_info(f"[cyan]📁 Using persistent profile: {self.profile_dir}[/cyan]")
|
88
|
+
|
71
89
|
camoufox_instance = camoufox.AsyncCamoufox(
|
72
90
|
headless=self.headless,
|
73
91
|
block_webrtc=self.block_webrtc,
|
74
92
|
humanize=self.humanize,
|
75
93
|
exclude_addons=list(DefaultAddons),
|
94
|
+
persistent_context=True,
|
95
|
+
user_data_dir=str(self.profile_dir),
|
76
96
|
addons=[],
|
77
97
|
)
|
78
|
-
|
79
|
-
self.
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
98
|
+
|
99
|
+
self._browser = camoufox_instance.browser
|
100
|
+
# Use persistent storage directory for browser context
|
101
|
+
# This ensures cookies, localStorage, history, etc. persist across runs
|
102
|
+
if not self._initialized:
|
103
|
+
self._context = await camoufox_instance.start()
|
104
|
+
self._initialized = True
|
105
|
+
# Do not auto-open a page here to avoid duplicate windows/tabs.
|
85
106
|
|
86
107
|
async def get_current_page(self) -> Optional[Page]:
|
87
|
-
"""Get the currently active page."""
|
108
|
+
"""Get the currently active page. Lazily creates one if none exist."""
|
88
109
|
if not self._initialized or not self._context:
|
89
110
|
await self.async_initialize()
|
90
111
|
|
91
|
-
if self._context:
|
92
|
-
|
93
|
-
|
94
|
-
|
112
|
+
if not self._context:
|
113
|
+
return None
|
114
|
+
|
115
|
+
pages = self._context.pages
|
116
|
+
if pages:
|
117
|
+
return pages[0]
|
118
|
+
|
119
|
+
# Lazily create a new blank page without navigation
|
120
|
+
return await self._context.new_page()
|
95
121
|
|
96
122
|
async def new_page(self, url: Optional[str] = None) -> Page:
|
97
123
|
"""Create a new page and optionally navigate to URL."""
|
@@ -140,9 +166,21 @@ class CamoufoxManager:
|
|
140
166
|
return self._context.pages
|
141
167
|
|
142
168
|
async def _cleanup(self) -> None:
|
143
|
-
"""Clean up browser resources."""
|
169
|
+
"""Clean up browser resources and save persistent state."""
|
144
170
|
try:
|
171
|
+
# Save browser state before closing (cookies, localStorage, etc.)
|
145
172
|
if self._context:
|
173
|
+
try:
|
174
|
+
storage_state_path = self.profile_dir / "storage_state.json"
|
175
|
+
await self._context.storage_state(path=str(storage_state_path))
|
176
|
+
emit_info(
|
177
|
+
f"[green]💾 Browser state saved to {storage_state_path}[/green]"
|
178
|
+
)
|
179
|
+
except Exception as e:
|
180
|
+
emit_info(
|
181
|
+
f"[yellow]Warning: Could not save storage state: {e}[/yellow]"
|
182
|
+
)
|
183
|
+
|
146
184
|
await self._context.close()
|
147
185
|
self._context = None
|
148
186
|
if self._browser:
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: code-puppy
|
3
|
-
Version: 0.0.
|
3
|
+
Version: 0.0.204
|
4
4
|
Summary: Code generation agent
|
5
5
|
Project-URL: repository, https://github.com/mpfaffenberger/code_puppy
|
6
6
|
Project-URL: HomePage, https://github.com/mpfaffenberger/code_puppy
|
@@ -26,7 +26,7 @@ Requires-Dist: openai>=1.99.1
|
|
26
26
|
Requires-Dist: pathspec>=0.11.0
|
27
27
|
Requires-Dist: playwright>=1.40.0
|
28
28
|
Requires-Dist: prompt-toolkit>=3.0.52
|
29
|
-
Requires-Dist: pydantic-ai==1.0.
|
29
|
+
Requires-Dist: pydantic-ai==1.0.5
|
30
30
|
Requires-Dist: pydantic>=2.4.0
|
31
31
|
Requires-Dist: pyjwt>=2.8.0
|
32
32
|
Requires-Dist: pytest-cov>=6.1.1
|
@@ -3,7 +3,7 @@ code_puppy/__main__.py,sha256=pDVssJOWP8A83iFkxMLY9YteHYat0EyWDQqMkKHpWp4,203
|
|
3
3
|
code_puppy/callbacks.py,sha256=ukSgVFaEO68o6J09qFwDrnmNanrVv3toTLQhS504Meo,6162
|
4
4
|
code_puppy/config.py,sha256=xT-nU1U4n7u8pyzJPG18-cJZBKv5OZI2CtHLt9DGRzU,26065
|
5
5
|
code_puppy/http_utils.py,sha256=YLd8Y16idbI32JGeBXG8n5rT4o4X_zxk9FgUvK9XFo8,8248
|
6
|
-
code_puppy/main.py,sha256=
|
6
|
+
code_puppy/main.py,sha256=SUh2UNbbEwVWSQwDkz-xBp80Q8qenX7tItsEEopcZfI,24024
|
7
7
|
code_puppy/model_factory.py,sha256=ZbIAJWMNKNdTCEMQK8Ig6TDDZlVNyGO9hOLHoLLPMYw,15397
|
8
8
|
code_puppy/models.json,sha256=dClUciCo2RlVDs0ZAQCIur8MOavZUEAXHEecn0uPa-4,1629
|
9
9
|
code_puppy/reopenable_async_client.py,sha256=4UJRaMp5np8cbef9F0zKQ7TPKOfyf5U-Kv-0zYUWDho,8274
|
@@ -21,13 +21,13 @@ code_puppy/agents/agent_cpp_reviewer.py,sha256=H4INgJo2OJ84QT7bfTkw4s1Ml7luwokhA
|
|
21
21
|
code_puppy/agents/agent_creator_agent.py,sha256=IiwVirB6uoIeGOmtetut9eDv6o055ykND3V-fvyA8Lw,23042
|
22
22
|
code_puppy/agents/agent_golang_reviewer.py,sha256=-OMuT8hkapVf2Oox46Ck9SRHlsfd8ab8uefbVfdW72M,3348
|
23
23
|
code_puppy/agents/agent_javascript_reviewer.py,sha256=5YC4kRSvorcNgObjHjD2Rrgnvf8jlKhPqWdjOMjU9A0,3636
|
24
|
-
code_puppy/agents/agent_manager.py,sha256
|
24
|
+
code_puppy/agents/agent_manager.py,sha256=-q1p3_xHGTguXhDtHvVBWAscvX3ZrSNbXsl382GBeC4,12790
|
25
25
|
code_puppy/agents/agent_python_reviewer.py,sha256=D0M3VA12QKdsyg2zIBI2FECxz0IP2fSIfg24xGzDhw0,3837
|
26
26
|
code_puppy/agents/agent_qa_expert.py,sha256=wCGXzuAVElT5c-QigQVb8JX9Gw0JmViCUQQnADMSbVc,3796
|
27
27
|
code_puppy/agents/agent_qa_kitten.py,sha256=5PeFFSwCFlTUvP6h5bGntx0xv5NmRwBiw0HnMqY8nLI,9107
|
28
28
|
code_puppy/agents/agent_security_auditor.py,sha256=ADafi2x4gqXw6m-Nch5vjiKjO0Urcbj0x4zxHti3gDw,3712
|
29
29
|
code_puppy/agents/agent_typescript_reviewer.py,sha256=EDY1mFkVpuJ1BPXsJFu2wQ2pfAV-90ipc_8w9ymrKPg,4054
|
30
|
-
code_puppy/agents/base_agent.py,sha256=
|
30
|
+
code_puppy/agents/base_agent.py,sha256=4hwqOCXQwL2F6luQsYn_IAGjboz1MTU2YIfHG6IYKnU,41409
|
31
31
|
code_puppy/agents/json_agent.py,sha256=lhopDJDoiSGHvD8A6t50hi9ZBoNRKgUywfxd0Po_Dzc,4886
|
32
32
|
code_puppy/command_line/__init__.py,sha256=y7WeRemfYppk8KVbCGeAIiTuiOszIURCDjOMZv_YRmU,45
|
33
33
|
code_puppy/command_line/attachments.py,sha256=rqBUR52wJj4cP-iTkN4KdoHZ9Oovc6tBp6ayifi9UF0,12022
|
@@ -99,7 +99,7 @@ code_puppy/tools/browser/browser_navigation.py,sha256=Tj_fNcM3KGpkM2UTKcGQX8BpI3
|
|
99
99
|
code_puppy/tools/browser/browser_screenshot.py,sha256=YU4olUqxhqyK3_pBC0BtU6A7_EEtiRlh6saj93nkKAg,8258
|
100
100
|
code_puppy/tools/browser/browser_scripts.py,sha256=MMO5KRjdrhuLOoJGoKGG1jm6UAqhFhUznz02aWqhMAE,15065
|
101
101
|
code_puppy/tools/browser/browser_workflows.py,sha256=jplJ1T60W3G4-dhVJX-CXkm9sskUH_Qzp0Dj-oubvrE,6142
|
102
|
-
code_puppy/tools/browser/camoufox_manager.py,sha256=
|
102
|
+
code_puppy/tools/browser/camoufox_manager.py,sha256=bzeerqgQa3CBKVe0UGPpbVR7pKpOVcHlvun7ASRD-KA,7770
|
103
103
|
code_puppy/tools/browser/vqa_agent.py,sha256=0GMDgJAK728rIuSQxAVytFSNagjo0LCjCUxBTm3w9Po,1952
|
104
104
|
code_puppy/tui/__init__.py,sha256=XesAxIn32zLPOmvpR2wIDxDAnnJr81a5pBJB4cZp1Xs,321
|
105
105
|
code_puppy/tui/app.py,sha256=D-8qHzxYbe-bVgrkBLl2lLBw7HRbUoVqDTRKy1gaE-E,44279
|
@@ -123,9 +123,9 @@ code_puppy/tui/screens/help.py,sha256=eJuPaOOCp7ZSUlecearqsuX6caxWv7NQszUh0tZJjB
|
|
123
123
|
code_puppy/tui/screens/mcp_install_wizard.py,sha256=vObpQwLbXjQsxmSg-WCasoev1usEi0pollKnL0SHu9U,27693
|
124
124
|
code_puppy/tui/screens/settings.py,sha256=EoMxiguyeF0srwV1bj4_MG9rrxkNthh6TdTNsxnXLfE,11460
|
125
125
|
code_puppy/tui/screens/tools.py,sha256=3pr2Xkpa9Js6Yhf1A3_wQVRzFOui-KDB82LwrsdBtyk,1715
|
126
|
-
code_puppy-0.0.
|
127
|
-
code_puppy-0.0.
|
128
|
-
code_puppy-0.0.
|
129
|
-
code_puppy-0.0.
|
130
|
-
code_puppy-0.0.
|
131
|
-
code_puppy-0.0.
|
126
|
+
code_puppy-0.0.204.data/data/code_puppy/models.json,sha256=dClUciCo2RlVDs0ZAQCIur8MOavZUEAXHEecn0uPa-4,1629
|
127
|
+
code_puppy-0.0.204.dist-info/METADATA,sha256=Fcbi95ybiGcekCuITFxE1_3YGaFJjdXoICpjpw4FNPQ,20759
|
128
|
+
code_puppy-0.0.204.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
129
|
+
code_puppy-0.0.204.dist-info/entry_points.txt,sha256=Tp4eQC99WY3HOKd3sdvb22vZODRq0XkZVNpXOag_KdI,91
|
130
|
+
code_puppy-0.0.204.dist-info/licenses/LICENSE,sha256=31u8x0SPgdOq3izJX41kgFazWsM43zPEF9eskzqbJMY,1075
|
131
|
+
code_puppy-0.0.204.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|