code-puppy 0.0.348__py3-none-any.whl → 0.0.361__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.
Files changed (70) hide show
  1. code_puppy/agents/__init__.py +2 -0
  2. code_puppy/agents/agent_manager.py +49 -0
  3. code_puppy/agents/agent_pack_leader.py +383 -0
  4. code_puppy/agents/agent_qa_kitten.py +12 -7
  5. code_puppy/agents/agent_terminal_qa.py +323 -0
  6. code_puppy/agents/base_agent.py +17 -4
  7. code_puppy/agents/event_stream_handler.py +101 -8
  8. code_puppy/agents/pack/__init__.py +34 -0
  9. code_puppy/agents/pack/bloodhound.py +304 -0
  10. code_puppy/agents/pack/husky.py +321 -0
  11. code_puppy/agents/pack/retriever.py +393 -0
  12. code_puppy/agents/pack/shepherd.py +348 -0
  13. code_puppy/agents/pack/terrier.py +287 -0
  14. code_puppy/agents/pack/watchdog.py +367 -0
  15. code_puppy/agents/subagent_stream_handler.py +276 -0
  16. code_puppy/api/__init__.py +13 -0
  17. code_puppy/api/app.py +169 -0
  18. code_puppy/api/main.py +21 -0
  19. code_puppy/api/pty_manager.py +446 -0
  20. code_puppy/api/routers/__init__.py +12 -0
  21. code_puppy/api/routers/agents.py +36 -0
  22. code_puppy/api/routers/commands.py +217 -0
  23. code_puppy/api/routers/config.py +74 -0
  24. code_puppy/api/routers/sessions.py +232 -0
  25. code_puppy/api/templates/terminal.html +361 -0
  26. code_puppy/api/websocket.py +154 -0
  27. code_puppy/callbacks.py +73 -0
  28. code_puppy/claude_cache_client.py +249 -34
  29. code_puppy/command_line/core_commands.py +85 -0
  30. code_puppy/config.py +66 -62
  31. code_puppy/messaging/__init__.py +15 -0
  32. code_puppy/messaging/messages.py +27 -0
  33. code_puppy/messaging/queue_console.py +1 -1
  34. code_puppy/messaging/rich_renderer.py +36 -1
  35. code_puppy/messaging/spinner/__init__.py +20 -2
  36. code_puppy/messaging/subagent_console.py +461 -0
  37. code_puppy/model_utils.py +54 -0
  38. code_puppy/plugins/antigravity_oauth/antigravity_model.py +90 -19
  39. code_puppy/plugins/antigravity_oauth/transport.py +1 -0
  40. code_puppy/plugins/frontend_emitter/__init__.py +25 -0
  41. code_puppy/plugins/frontend_emitter/emitter.py +121 -0
  42. code_puppy/plugins/frontend_emitter/register_callbacks.py +261 -0
  43. code_puppy/prompts/antigravity_system_prompt.md +1 -0
  44. code_puppy/status_display.py +6 -2
  45. code_puppy/tools/__init__.py +37 -1
  46. code_puppy/tools/agent_tools.py +83 -33
  47. code_puppy/tools/browser/__init__.py +37 -0
  48. code_puppy/tools/browser/browser_control.py +6 -6
  49. code_puppy/tools/browser/browser_interactions.py +21 -20
  50. code_puppy/tools/browser/browser_locators.py +9 -9
  51. code_puppy/tools/browser/browser_navigation.py +7 -7
  52. code_puppy/tools/browser/browser_screenshot.py +78 -140
  53. code_puppy/tools/browser/browser_scripts.py +15 -13
  54. code_puppy/tools/browser/camoufox_manager.py +226 -64
  55. code_puppy/tools/browser/chromium_terminal_manager.py +259 -0
  56. code_puppy/tools/browser/terminal_command_tools.py +521 -0
  57. code_puppy/tools/browser/terminal_screenshot_tools.py +556 -0
  58. code_puppy/tools/browser/terminal_tools.py +525 -0
  59. code_puppy/tools/command_runner.py +292 -101
  60. code_puppy/tools/common.py +176 -1
  61. code_puppy/tools/display.py +84 -0
  62. code_puppy/tools/subagent_context.py +158 -0
  63. {code_puppy-0.0.348.dist-info → code_puppy-0.0.361.dist-info}/METADATA +13 -11
  64. {code_puppy-0.0.348.dist-info → code_puppy-0.0.361.dist-info}/RECORD +69 -38
  65. code_puppy/tools/browser/vqa_agent.py +0 -90
  66. {code_puppy-0.0.348.data → code_puppy-0.0.361.data}/data/code_puppy/models.json +0 -0
  67. {code_puppy-0.0.348.data → code_puppy-0.0.361.data}/data/code_puppy/models_dev_api.json +0 -0
  68. {code_puppy-0.0.348.dist-info → code_puppy-0.0.361.dist-info}/WHEEL +0 -0
  69. {code_puppy-0.0.348.dist-info → code_puppy-0.0.361.dist-info}/entry_points.txt +0 -0
  70. {code_puppy-0.0.348.dist-info → code_puppy-0.0.361.dist-info}/licenses/LICENSE +0 -0
@@ -55,10 +55,32 @@ from code_puppy.tools.browser.browser_workflows import (
55
55
  register_read_workflow,
56
56
  register_save_workflow,
57
57
  )
58
+ from code_puppy.tools.browser.terminal_command_tools import (
59
+ register_run_terminal_command,
60
+ register_send_terminal_keys,
61
+ register_wait_terminal_output,
62
+ )
63
+ from code_puppy.tools.browser.terminal_screenshot_tools import (
64
+ register_load_image,
65
+ register_terminal_compare_mockup,
66
+ register_terminal_read_output,
67
+ register_terminal_screenshot,
68
+ )
69
+
70
+ # Terminal automation tools
71
+ from code_puppy.tools.browser.terminal_tools import (
72
+ register_check_terminal_server,
73
+ register_close_terminal,
74
+ register_open_terminal,
75
+ register_start_api_server,
76
+ )
58
77
  from code_puppy.tools.command_runner import (
59
78
  register_agent_run_shell_command,
60
79
  register_agent_share_your_reasoning,
61
80
  )
81
+ from code_puppy.tools.display import (
82
+ display_non_streamed_result as display_non_streamed_result,
83
+ )
62
84
  from code_puppy.tools.file_modifications import register_delete_file, register_edit_file
63
85
  from code_puppy.tools.file_operations import (
64
86
  register_grep,
@@ -121,12 +143,26 @@ TOOL_REGISTRY = {
121
143
  "browser_wait_for_element": register_wait_for_element,
122
144
  "browser_highlight_element": register_browser_highlight_element,
123
145
  "browser_clear_highlights": register_browser_clear_highlights,
124
- # Browser Screenshots and VQA
146
+ # Browser Screenshots
125
147
  "browser_screenshot_analyze": register_take_screenshot_and_analyze,
126
148
  # Browser Workflows
127
149
  "browser_save_workflow": register_save_workflow,
128
150
  "browser_list_workflows": register_list_workflows,
129
151
  "browser_read_workflow": register_read_workflow,
152
+ # Terminal Connection Tools
153
+ "terminal_check_server": register_check_terminal_server,
154
+ "terminal_open": register_open_terminal,
155
+ "terminal_close": register_close_terminal,
156
+ "start_api_server": register_start_api_server,
157
+ # Terminal Command Execution Tools
158
+ "terminal_run_command": register_run_terminal_command,
159
+ "terminal_send_keys": register_send_terminal_keys,
160
+ "terminal_wait_output": register_wait_terminal_output,
161
+ # Terminal Screenshot Tools
162
+ "terminal_screenshot_analyze": register_terminal_screenshot,
163
+ "terminal_read_output": register_terminal_read_output,
164
+ "terminal_compare_mockup": register_terminal_compare_mockup,
165
+ "load_image_for_analysis": register_load_image,
130
166
  }
131
167
 
132
168
 
@@ -7,6 +7,7 @@ import pickle
7
7
  import re
8
8
  import traceback
9
9
  from datetime import datetime
10
+ from functools import partial
10
11
  from pathlib import Path
11
12
  from typing import List, Set
12
13
 
@@ -28,12 +29,13 @@ from code_puppy.messaging import (
28
29
  SubAgentResponseMessage,
29
30
  emit_error,
30
31
  emit_info,
32
+ emit_success,
31
33
  get_message_bus,
32
34
  get_session_context,
33
35
  set_session_context,
34
36
  )
35
- from code_puppy.model_factory import ModelFactory, make_model_settings
36
37
  from code_puppy.tools.common import generate_group_id
38
+ from code_puppy.tools.subagent_context import subagent_context
37
39
 
38
40
  # Set to track active subagent invocation tasks
39
41
  _active_subagent_tasks: Set[asyncio.Task] = set()
@@ -413,6 +415,9 @@ def register_invoke_agent(agent):
413
415
  session_id = f"{session_id}-{hash_suffix}"
414
416
  # else: continuing existing session, use session_id as-is
415
417
 
418
+ # Lazy imports to avoid circular dependency
419
+ from code_puppy.agents.subagent_stream_handler import subagent_stream_handler
420
+
416
421
  # Emit structured invocation message via MessageBus
417
422
  bus = get_message_bus()
418
423
  bus.emit(
@@ -429,7 +434,27 @@ def register_invoke_agent(agent):
429
434
  previous_session_id = get_session_context()
430
435
  set_session_context(session_id)
431
436
 
437
+ # Set terminal session for browser-based terminal tools
438
+ # This uses contextvars which properly propagate through async tasks
439
+ from code_puppy.tools.browser.terminal_tools import (
440
+ _terminal_session_var,
441
+ set_terminal_session,
442
+ )
443
+
444
+ terminal_session_token = set_terminal_session(f"terminal-{session_id}")
445
+
446
+ # Set browser session for Camoufox browser tools (qa-kitten, etc.)
447
+ # This allows parallel agent invocations to each have their own browser
448
+ from code_puppy.tools.browser.camoufox_manager import (
449
+ set_browser_session,
450
+ )
451
+
452
+ browser_session_token = set_browser_session(f"browser-{session_id}")
453
+
432
454
  try:
455
+ # Lazy import to break circular dependency with messaging module
456
+ from code_puppy.model_factory import ModelFactory, make_model_settings
457
+
433
458
  # Load the specified agent config
434
459
  agent_config = load_agent(agent_name)
435
460
 
@@ -483,9 +508,6 @@ def register_invoke_agent(agent):
483
508
  manager = get_mcp_manager()
484
509
  mcp_servers = manager.get_servers_for_agent()
485
510
 
486
- # Get the event_stream_handler for streaming output
487
- from code_puppy.agents.event_stream_handler import event_stream_handler
488
-
489
511
  if get_use_dbos():
490
512
  from pydantic_ai.durable_exec.dbos import DBOSAgent
491
513
 
@@ -507,11 +529,10 @@ def register_invoke_agent(agent):
507
529
  agent_tools = agent_config.get_available_tools()
508
530
  register_tools_for_agent(temp_agent, agent_tools)
509
531
 
510
- # Wrap with DBOS - pass event_stream_handler for streaming output
532
+ # Wrap with DBOS - no streaming for sub-agents
511
533
  dbos_agent = DBOSAgent(
512
534
  temp_agent,
513
535
  name=subagent_name,
514
- event_stream_handler=event_stream_handler,
515
536
  )
516
537
  temp_agent = dbos_agent
517
538
 
@@ -540,43 +561,54 @@ def register_invoke_agent(agent):
540
561
  # Run the temporary agent with the provided prompt as an asyncio task
541
562
  # Pass the message_history from the session to continue the conversation
542
563
  workflow_id = None # Track for potential cancellation
543
- if get_use_dbos():
544
- # Generate a unique workflow ID for DBOS - ensures no collisions in back-to-back calls
545
- workflow_id = _generate_dbos_workflow_id(group_id)
546
564
 
547
- # Add MCP servers to the DBOS agent's toolsets
548
- # (temp_agent is discarded after this invocation, so no need to restore)
549
- if subagent_mcp_servers:
550
- temp_agent._toolsets = temp_agent._toolsets + subagent_mcp_servers
565
+ # Always use subagent_stream_handler to silence output and update console manager
566
+ # This ensures all sub-agent output goes through the aggregated dashboard
567
+ stream_handler = partial(subagent_stream_handler, session_id=session_id)
568
+
569
+ # Wrap the agent run in subagent context for tracking
570
+ with subagent_context(agent_name):
571
+ if get_use_dbos():
572
+ # Generate a unique workflow ID for DBOS - ensures no collisions in back-to-back calls
573
+ workflow_id = _generate_dbos_workflow_id(group_id)
574
+
575
+ # Add MCP servers to the DBOS agent's toolsets
576
+ # (temp_agent is discarded after this invocation, so no need to restore)
577
+ if subagent_mcp_servers:
578
+ temp_agent._toolsets = (
579
+ temp_agent._toolsets + subagent_mcp_servers
580
+ )
551
581
 
552
- with SetWorkflowID(workflow_id):
582
+ with SetWorkflowID(workflow_id):
583
+ task = asyncio.create_task(
584
+ temp_agent.run(
585
+ prompt,
586
+ message_history=message_history,
587
+ usage_limits=UsageLimits(
588
+ request_limit=get_message_limit()
589
+ ),
590
+ event_stream_handler=stream_handler,
591
+ )
592
+ )
593
+ _active_subagent_tasks.add(task)
594
+ else:
553
595
  task = asyncio.create_task(
554
596
  temp_agent.run(
555
597
  prompt,
556
598
  message_history=message_history,
557
599
  usage_limits=UsageLimits(request_limit=get_message_limit()),
558
- event_stream_handler=event_stream_handler,
600
+ event_stream_handler=stream_handler,
559
601
  )
560
602
  )
561
603
  _active_subagent_tasks.add(task)
562
- else:
563
- task = asyncio.create_task(
564
- temp_agent.run(
565
- prompt,
566
- message_history=message_history,
567
- usage_limits=UsageLimits(request_limit=get_message_limit()),
568
- event_stream_handler=event_stream_handler,
569
- )
570
- )
571
- _active_subagent_tasks.add(task)
572
604
 
573
- try:
574
- result = await task
575
- finally:
576
- _active_subagent_tasks.discard(task)
577
- if task.cancelled():
578
- if get_use_dbos() and workflow_id:
579
- DBOS.cancel_workflow(workflow_id)
605
+ try:
606
+ result = await task
607
+ finally:
608
+ _active_subagent_tasks.discard(task)
609
+ if task.cancelled():
610
+ if get_use_dbos() and workflow_id:
611
+ DBOS.cancel_workflow(workflow_id)
580
612
 
581
613
  # Extract the response from the result
582
614
  response = result.output
@@ -603,13 +635,23 @@ def register_invoke_agent(agent):
603
635
  )
604
636
  )
605
637
 
638
+ # Emit clean completion summary
639
+ emit_success(
640
+ f"✓ {agent_name} completed successfully", message_group=group_id
641
+ )
642
+
606
643
  return AgentInvokeOutput(
607
644
  response=response, agent_name=agent_name, session_id=session_id
608
645
  )
609
646
 
610
- except Exception:
647
+ except Exception as e:
648
+ # Emit clean failure summary
649
+ emit_error(f"✗ {agent_name} failed: {str(e)}", message_group=group_id)
650
+
651
+ # Full traceback for debugging
611
652
  error_msg = f"Error invoking agent '{agent_name}': {traceback.format_exc()}"
612
653
  emit_error(error_msg, message_group=group_id)
654
+
613
655
  return AgentInvokeOutput(
614
656
  response=None,
615
657
  agent_name=agent_name,
@@ -620,5 +662,13 @@ def register_invoke_agent(agent):
620
662
  finally:
621
663
  # Restore the previous session context
622
664
  set_session_context(previous_session_id)
665
+ # Reset terminal session context
666
+ _terminal_session_var.reset(terminal_session_token)
667
+ # Reset browser session context
668
+ from code_puppy.tools.browser.camoufox_manager import (
669
+ _browser_session_var,
670
+ )
671
+
672
+ _browser_session_var.reset(browser_session_token)
623
673
 
624
674
  return invoke_agent
@@ -0,0 +1,37 @@
1
+ """Browser tools for terminal automation.
2
+
3
+ This module provides browser-based terminal automation tools.
4
+ """
5
+
6
+ from code_puppy.config import get_banner_color
7
+
8
+ from .camoufox_manager import (
9
+ cleanup_all_browsers,
10
+ get_browser_session,
11
+ get_session_browser_manager,
12
+ set_browser_session,
13
+ )
14
+
15
+
16
+ def format_terminal_banner(text: str) -> str:
17
+ """Format a terminal tool banner with the configured terminal_tool color.
18
+
19
+ Returns Rich markup string that can be used with Text.from_markup().
20
+
21
+ Args:
22
+ text: The banner text (e.g., "TERMINAL OPEN 🖥️ localhost:8765")
23
+
24
+ Returns:
25
+ Rich markup formatted string
26
+ """
27
+ color = get_banner_color("terminal_tool")
28
+ return f"[bold white on {color}] {text} [/bold white on {color}]"
29
+
30
+
31
+ __all__ = [
32
+ "format_terminal_banner",
33
+ "cleanup_all_browsers",
34
+ "get_browser_session",
35
+ "get_session_browser_manager",
36
+ "set_browser_session",
37
+ ]
@@ -7,7 +7,7 @@ from pydantic_ai import RunContext
7
7
  from code_puppy.messaging import emit_error, emit_info, emit_success, emit_warning
8
8
  from code_puppy.tools.common import generate_group_id
9
9
 
10
- from .camoufox_manager import get_camoufox_manager
10
+ from .camoufox_manager import get_session_browser_manager
11
11
 
12
12
 
13
13
  async def initialize_browser(
@@ -22,7 +22,7 @@ async def initialize_browser(
22
22
  message_group=group_id,
23
23
  )
24
24
  try:
25
- browser_manager = get_camoufox_manager()
25
+ browser_manager = get_session_browser_manager()
26
26
 
27
27
  # Configure browser settings
28
28
  browser_manager.headless = headless
@@ -75,7 +75,7 @@ async def close_browser() -> Dict[str, Any]:
75
75
  message_group=group_id,
76
76
  )
77
77
  try:
78
- browser_manager = get_camoufox_manager()
78
+ browser_manager = get_session_browser_manager()
79
79
  await browser_manager.close()
80
80
 
81
81
  emit_warning("Browser closed successfully", message_group=group_id)
@@ -94,7 +94,7 @@ async def get_browser_status() -> Dict[str, Any]:
94
94
  message_group=group_id,
95
95
  )
96
96
  try:
97
- browser_manager = get_camoufox_manager()
97
+ browser_manager = get_session_browser_manager()
98
98
 
99
99
  if not browser_manager._initialized:
100
100
  return {
@@ -139,7 +139,7 @@ async def create_new_page(url: Optional[str] = None) -> Dict[str, Any]:
139
139
  message_group=group_id,
140
140
  )
141
141
  try:
142
- browser_manager = get_camoufox_manager()
142
+ browser_manager = get_session_browser_manager()
143
143
 
144
144
  if not browser_manager._initialized:
145
145
  return {
@@ -168,7 +168,7 @@ async def list_pages() -> Dict[str, Any]:
168
168
  message_group=group_id,
169
169
  )
170
170
  try:
171
- browser_manager = get_camoufox_manager()
171
+ browser_manager = get_session_browser_manager()
172
172
 
173
173
  if not browser_manager._initialized:
174
174
  return {"success": False, "error": "Browser not initialized"}
@@ -7,7 +7,7 @@ from pydantic_ai import RunContext
7
7
  from code_puppy.messaging import emit_error, emit_info, emit_success
8
8
  from code_puppy.tools.common import generate_group_id
9
9
 
10
- from .camoufox_manager import get_camoufox_manager
10
+ from .camoufox_manager import get_session_browser_manager
11
11
 
12
12
 
13
13
  async def click_element(
@@ -24,14 +24,15 @@ async def click_element(
24
24
  message_group=group_id,
25
25
  )
26
26
  try:
27
- browser_manager = get_camoufox_manager()
27
+ browser_manager = get_session_browser_manager()
28
28
  page = await browser_manager.get_current_page()
29
29
 
30
30
  if not page:
31
31
  return {"success": False, "error": "No active browser page available"}
32
32
 
33
- # Find element
34
- element = page.locator(selector)
33
+ # Find element - use .first to handle cases where selector matches multiple elements
34
+ # This avoids Playwright's strict mode violation errors
35
+ element = page.locator(selector).first
35
36
 
36
37
  # Wait for element to be visible and enabled
37
38
  await element.wait_for(state="visible", timeout=timeout)
@@ -69,13 +70,13 @@ async def double_click_element(
69
70
  message_group=group_id,
70
71
  )
71
72
  try:
72
- browser_manager = get_camoufox_manager()
73
+ browser_manager = get_session_browser_manager()
73
74
  page = await browser_manager.get_current_page()
74
75
 
75
76
  if not page:
76
77
  return {"success": False, "error": "No active browser page available"}
77
78
 
78
- element = page.locator(selector)
79
+ element = page.locator(selector).first
79
80
  await element.wait_for(state="visible", timeout=timeout)
80
81
  await element.dblclick(force=force, timeout=timeout)
81
82
 
@@ -99,13 +100,13 @@ async def hover_element(
99
100
  message_group=group_id,
100
101
  )
101
102
  try:
102
- browser_manager = get_camoufox_manager()
103
+ browser_manager = get_session_browser_manager()
103
104
  page = await browser_manager.get_current_page()
104
105
 
105
106
  if not page:
106
107
  return {"success": False, "error": "No active browser page available"}
107
108
 
108
- element = page.locator(selector)
109
+ element = page.locator(selector).first
109
110
  await element.wait_for(state="visible", timeout=timeout)
110
111
  await element.hover(force=force, timeout=timeout)
111
112
 
@@ -130,13 +131,13 @@ async def set_element_text(
130
131
  message_group=group_id,
131
132
  )
132
133
  try:
133
- browser_manager = get_camoufox_manager()
134
+ browser_manager = get_session_browser_manager()
134
135
  page = await browser_manager.get_current_page()
135
136
 
136
137
  if not page:
137
138
  return {"success": False, "error": "No active browser page available"}
138
139
 
139
- element = page.locator(selector)
140
+ element = page.locator(selector).first
140
141
  await element.wait_for(state="visible", timeout=timeout)
141
142
 
142
143
  if clear_first:
@@ -169,13 +170,13 @@ async def get_element_text(
169
170
  message_group=group_id,
170
171
  )
171
172
  try:
172
- browser_manager = get_camoufox_manager()
173
+ browser_manager = get_session_browser_manager()
173
174
  page = await browser_manager.get_current_page()
174
175
 
175
176
  if not page:
176
177
  return {"success": False, "error": "No active browser page available"}
177
178
 
178
- element = page.locator(selector)
179
+ element = page.locator(selector).first
179
180
  await element.wait_for(state="visible", timeout=timeout)
180
181
 
181
182
  text = await element.text_content()
@@ -197,13 +198,13 @@ async def get_element_value(
197
198
  message_group=group_id,
198
199
  )
199
200
  try:
200
- browser_manager = get_camoufox_manager()
201
+ browser_manager = get_session_browser_manager()
201
202
  page = await browser_manager.get_current_page()
202
203
 
203
204
  if not page:
204
205
  return {"success": False, "error": "No active browser page available"}
205
206
 
206
- element = page.locator(selector)
207
+ element = page.locator(selector).first
207
208
  await element.wait_for(state="visible", timeout=timeout)
208
209
 
209
210
  value = await element.input_value()
@@ -231,13 +232,13 @@ async def select_option(
231
232
  message_group=group_id,
232
233
  )
233
234
  try:
234
- browser_manager = get_camoufox_manager()
235
+ browser_manager = get_session_browser_manager()
235
236
  page = await browser_manager.get_current_page()
236
237
 
237
238
  if not page:
238
239
  return {"success": False, "error": "No active browser page available"}
239
240
 
240
- element = page.locator(selector)
241
+ element = page.locator(selector).first
241
242
  await element.wait_for(state="visible", timeout=timeout)
242
243
 
243
244
  if value is not None:
@@ -278,13 +279,13 @@ async def check_element(
278
279
  message_group=group_id,
279
280
  )
280
281
  try:
281
- browser_manager = get_camoufox_manager()
282
+ browser_manager = get_session_browser_manager()
282
283
  page = await browser_manager.get_current_page()
283
284
 
284
285
  if not page:
285
286
  return {"success": False, "error": "No active browser page available"}
286
287
 
287
- element = page.locator(selector)
288
+ element = page.locator(selector).first
288
289
  await element.wait_for(state="visible", timeout=timeout)
289
290
  await element.check(timeout=timeout)
290
291
 
@@ -307,13 +308,13 @@ async def uncheck_element(
307
308
  message_group=group_id,
308
309
  )
309
310
  try:
310
- browser_manager = get_camoufox_manager()
311
+ browser_manager = get_session_browser_manager()
311
312
  page = await browser_manager.get_current_page()
312
313
 
313
314
  if not page:
314
315
  return {"success": False, "error": "No active browser page available"}
315
316
 
316
- element = page.locator(selector)
317
+ element = page.locator(selector).first
317
318
  await element.wait_for(state="visible", timeout=timeout)
318
319
  await element.uncheck(timeout=timeout)
319
320
 
@@ -7,7 +7,7 @@ from pydantic_ai import RunContext
7
7
  from code_puppy.messaging import emit_info, emit_success
8
8
  from code_puppy.tools.common import generate_group_id
9
9
 
10
- from .camoufox_manager import get_camoufox_manager
10
+ from .camoufox_manager import get_session_browser_manager
11
11
 
12
12
 
13
13
  async def find_by_role(
@@ -23,7 +23,7 @@ async def find_by_role(
23
23
  message_group=group_id,
24
24
  )
25
25
  try:
26
- browser_manager = get_camoufox_manager()
26
+ browser_manager = get_session_browser_manager()
27
27
  page = await browser_manager.get_current_page()
28
28
 
29
29
  if not page:
@@ -75,7 +75,7 @@ async def find_by_text(
75
75
  message_group=group_id,
76
76
  )
77
77
  try:
78
- browser_manager = get_camoufox_manager()
78
+ browser_manager = get_session_browser_manager()
79
79
  page = await browser_manager.get_current_page()
80
80
 
81
81
  if not page:
@@ -127,7 +127,7 @@ async def find_by_label(
127
127
  message_group=group_id,
128
128
  )
129
129
  try:
130
- browser_manager = get_camoufox_manager()
130
+ browser_manager = get_session_browser_manager()
131
131
  page = await browser_manager.get_current_page()
132
132
 
133
133
  if not page:
@@ -190,7 +190,7 @@ async def find_by_placeholder(
190
190
  message_group=group_id,
191
191
  )
192
192
  try:
193
- browser_manager = get_camoufox_manager()
193
+ browser_manager = get_session_browser_manager()
194
194
  page = await browser_manager.get_current_page()
195
195
 
196
196
  if not page:
@@ -248,7 +248,7 @@ async def find_by_test_id(
248
248
  message_group=group_id,
249
249
  )
250
250
  try:
251
- browser_manager = get_camoufox_manager()
251
+ browser_manager = get_session_browser_manager()
252
252
  page = await browser_manager.get_current_page()
253
253
 
254
254
  if not page:
@@ -304,7 +304,7 @@ async def run_xpath_query(
304
304
  message_group=group_id,
305
305
  )
306
306
  try:
307
- browser_manager = get_camoufox_manager()
307
+ browser_manager = get_session_browser_manager()
308
308
  page = await browser_manager.get_current_page()
309
309
 
310
310
  if not page:
@@ -359,7 +359,7 @@ async def find_buttons(
359
359
  message_group=group_id,
360
360
  )
361
361
  try:
362
- browser_manager = get_camoufox_manager()
362
+ browser_manager = get_session_browser_manager()
363
363
  page = await browser_manager.get_current_page()
364
364
 
365
365
  if not page:
@@ -410,7 +410,7 @@ async def find_links(
410
410
  message_group=group_id,
411
411
  )
412
412
  try:
413
- browser_manager = get_camoufox_manager()
413
+ browser_manager = get_session_browser_manager()
414
414
  page = await browser_manager.get_current_page()
415
415
 
416
416
  if not page:
@@ -7,7 +7,7 @@ from pydantic_ai import RunContext
7
7
  from code_puppy.messaging import emit_error, emit_info, emit_success
8
8
  from code_puppy.tools.common import generate_group_id
9
9
 
10
- from .camoufox_manager import get_camoufox_manager
10
+ from .camoufox_manager import get_session_browser_manager
11
11
 
12
12
 
13
13
  async def navigate_to_url(url: str) -> Dict[str, Any]:
@@ -18,7 +18,7 @@ async def navigate_to_url(url: str) -> Dict[str, Any]:
18
18
  message_group=group_id,
19
19
  )
20
20
  try:
21
- browser_manager = get_camoufox_manager()
21
+ browser_manager = get_session_browser_manager()
22
22
  page = await browser_manager.get_current_page()
23
23
 
24
24
  if not page:
@@ -48,7 +48,7 @@ async def get_page_info() -> Dict[str, Any]:
48
48
  message_group=group_id,
49
49
  )
50
50
  try:
51
- browser_manager = get_camoufox_manager()
51
+ browser_manager = get_session_browser_manager()
52
52
  page = await browser_manager.get_current_page()
53
53
 
54
54
  if not page:
@@ -71,7 +71,7 @@ async def go_back() -> Dict[str, Any]:
71
71
  message_group=group_id,
72
72
  )
73
73
  try:
74
- browser_manager = get_camoufox_manager()
74
+ browser_manager = get_session_browser_manager()
75
75
  page = await browser_manager.get_current_page()
76
76
 
77
77
  if not page:
@@ -93,7 +93,7 @@ async def go_forward() -> Dict[str, Any]:
93
93
  message_group=group_id,
94
94
  )
95
95
  try:
96
- browser_manager = get_camoufox_manager()
96
+ browser_manager = get_session_browser_manager()
97
97
  page = await browser_manager.get_current_page()
98
98
 
99
99
  if not page:
@@ -115,7 +115,7 @@ async def reload_page(wait_until: str = "domcontentloaded") -> Dict[str, Any]:
115
115
  message_group=group_id,
116
116
  )
117
117
  try:
118
- browser_manager = get_camoufox_manager()
118
+ browser_manager = get_session_browser_manager()
119
119
  page = await browser_manager.get_current_page()
120
120
 
121
121
  if not page:
@@ -139,7 +139,7 @@ async def wait_for_load_state(
139
139
  message_group=group_id,
140
140
  )
141
141
  try:
142
- browser_manager = get_camoufox_manager()
142
+ browser_manager = get_session_browser_manager()
143
143
  page = await browser_manager.get_current_page()
144
144
 
145
145
  if not page: