code-puppy 0.0.341__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 (86) 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 +34 -252
  7. code_puppy/agents/event_stream_handler.py +350 -0
  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/cli_runner.py +4 -3
  30. code_puppy/command_line/add_model_menu.py +8 -9
  31. code_puppy/command_line/core_commands.py +85 -0
  32. code_puppy/command_line/mcp/catalog_server_installer.py +5 -6
  33. code_puppy/command_line/mcp/custom_server_form.py +54 -19
  34. code_puppy/command_line/mcp/custom_server_installer.py +8 -9
  35. code_puppy/command_line/mcp/handler.py +0 -2
  36. code_puppy/command_line/mcp/help_command.py +1 -5
  37. code_puppy/command_line/mcp/start_command.py +36 -18
  38. code_puppy/command_line/onboarding_slides.py +0 -1
  39. code_puppy/command_line/prompt_toolkit_completion.py +16 -10
  40. code_puppy/command_line/utils.py +54 -0
  41. code_puppy/config.py +66 -62
  42. code_puppy/mcp_/async_lifecycle.py +35 -4
  43. code_puppy/mcp_/managed_server.py +49 -20
  44. code_puppy/mcp_/manager.py +81 -52
  45. code_puppy/messaging/__init__.py +15 -0
  46. code_puppy/messaging/message_queue.py +11 -23
  47. code_puppy/messaging/messages.py +27 -0
  48. code_puppy/messaging/queue_console.py +1 -1
  49. code_puppy/messaging/rich_renderer.py +36 -1
  50. code_puppy/messaging/spinner/__init__.py +20 -2
  51. code_puppy/messaging/subagent_console.py +461 -0
  52. code_puppy/model_utils.py +54 -0
  53. code_puppy/plugins/antigravity_oauth/antigravity_model.py +90 -19
  54. code_puppy/plugins/antigravity_oauth/transport.py +1 -0
  55. code_puppy/plugins/frontend_emitter/__init__.py +25 -0
  56. code_puppy/plugins/frontend_emitter/emitter.py +121 -0
  57. code_puppy/plugins/frontend_emitter/register_callbacks.py +261 -0
  58. code_puppy/prompts/antigravity_system_prompt.md +1 -0
  59. code_puppy/status_display.py +6 -2
  60. code_puppy/tools/__init__.py +37 -1
  61. code_puppy/tools/agent_tools.py +139 -36
  62. code_puppy/tools/browser/__init__.py +37 -0
  63. code_puppy/tools/browser/browser_control.py +6 -6
  64. code_puppy/tools/browser/browser_interactions.py +21 -20
  65. code_puppy/tools/browser/browser_locators.py +9 -9
  66. code_puppy/tools/browser/browser_navigation.py +7 -7
  67. code_puppy/tools/browser/browser_screenshot.py +78 -140
  68. code_puppy/tools/browser/browser_scripts.py +15 -13
  69. code_puppy/tools/browser/camoufox_manager.py +226 -64
  70. code_puppy/tools/browser/chromium_terminal_manager.py +259 -0
  71. code_puppy/tools/browser/terminal_command_tools.py +521 -0
  72. code_puppy/tools/browser/terminal_screenshot_tools.py +556 -0
  73. code_puppy/tools/browser/terminal_tools.py +525 -0
  74. code_puppy/tools/command_runner.py +292 -101
  75. code_puppy/tools/common.py +176 -1
  76. code_puppy/tools/display.py +84 -0
  77. code_puppy/tools/subagent_context.py +158 -0
  78. {code_puppy-0.0.341.dist-info → code_puppy-0.0.361.dist-info}/METADATA +13 -11
  79. {code_puppy-0.0.341.dist-info → code_puppy-0.0.361.dist-info}/RECORD +84 -53
  80. code_puppy/command_line/mcp/add_command.py +0 -170
  81. code_puppy/tools/browser/vqa_agent.py +0 -90
  82. {code_puppy-0.0.341.data → code_puppy-0.0.361.data}/data/code_puppy/models.json +0 -0
  83. {code_puppy-0.0.341.data → code_puppy-0.0.361.data}/data/code_puppy/models_dev_api.json +0 -0
  84. {code_puppy-0.0.341.dist-info → code_puppy-0.0.361.dist-info}/WHEEL +0 -0
  85. {code_puppy-0.0.341.dist-info → code_puppy-0.0.361.dist-info}/entry_points.txt +0 -0
  86. {code_puppy-0.0.341.dist-info → code_puppy-0.0.361.dist-info}/licenses/LICENSE +0 -0
@@ -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
 
@@ -21,18 +22,20 @@ from code_puppy.config import (
21
22
  DATA_DIR,
22
23
  get_message_limit,
23
24
  get_use_dbos,
25
+ get_value,
24
26
  )
25
27
  from code_puppy.messaging import (
26
28
  SubAgentInvocationMessage,
27
29
  SubAgentResponseMessage,
28
30
  emit_error,
29
31
  emit_info,
32
+ emit_success,
30
33
  get_message_bus,
31
34
  get_session_context,
32
35
  set_session_context,
33
36
  )
34
- from code_puppy.model_factory import ModelFactory, make_model_settings
35
37
  from code_puppy.tools.common import generate_group_id
38
+ from code_puppy.tools.subagent_context import subagent_context
36
39
 
37
40
  # Set to track active subagent invocation tasks
38
41
  _active_subagent_tasks: Set[asyncio.Task] = set()
@@ -412,6 +415,9 @@ def register_invoke_agent(agent):
412
415
  session_id = f"{session_id}-{hash_suffix}"
413
416
  # else: continuing existing session, use session_id as-is
414
417
 
418
+ # Lazy imports to avoid circular dependency
419
+ from code_puppy.agents.subagent_stream_handler import subagent_stream_handler
420
+
415
421
  # Emit structured invocation message via MessageBus
416
422
  bus = get_message_bus()
417
423
  bus.emit(
@@ -428,7 +434,27 @@ def register_invoke_agent(agent):
428
434
  previous_session_id = get_session_context()
429
435
  set_session_context(session_id)
430
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
+
431
454
  try:
455
+ # Lazy import to break circular dependency with messaging module
456
+ from code_puppy.model_factory import ModelFactory, make_model_settings
457
+
432
458
  # Load the specified agent config
433
459
  agent_config = load_agent(agent_name)
434
460
 
@@ -471,59 +497,118 @@ def register_invoke_agent(agent):
471
497
  subagent_name = f"temp-invoke-agent-{session_id}"
472
498
  model_settings = make_model_settings(model_name)
473
499
 
474
- temp_agent = Agent(
475
- model=model,
476
- instructions=instructions,
477
- output_type=str,
478
- retries=3,
479
- history_processors=[agent_config.message_history_accumulator],
480
- model_settings=model_settings,
481
- )
482
-
483
- # Register the tools that the agent needs
484
- from code_puppy.tools import register_tools_for_agent
500
+ # Get MCP servers for sub-agents (same as main agent)
501
+ from code_puppy.mcp_ import get_mcp_manager
485
502
 
486
- agent_tools = agent_config.get_available_tools()
487
- register_tools_for_agent(temp_agent, agent_tools)
503
+ mcp_servers = []
504
+ mcp_disabled = get_value("disable_mcp_servers")
505
+ if not (
506
+ mcp_disabled and str(mcp_disabled).lower() in ("1", "true", "yes", "on")
507
+ ):
508
+ manager = get_mcp_manager()
509
+ mcp_servers = manager.get_servers_for_agent()
488
510
 
489
511
  if get_use_dbos():
490
512
  from pydantic_ai.durable_exec.dbos import DBOSAgent
491
513
 
492
- dbos_agent = DBOSAgent(temp_agent, name=subagent_name)
514
+ # For DBOS, create agent without MCP servers (to avoid serialization issues)
515
+ # and add them at runtime
516
+ temp_agent = Agent(
517
+ model=model,
518
+ instructions=instructions,
519
+ output_type=str,
520
+ retries=3,
521
+ toolsets=[], # MCP servers added separately for DBOS
522
+ history_processors=[agent_config.message_history_accumulator],
523
+ model_settings=model_settings,
524
+ )
525
+
526
+ # Register the tools that the agent needs
527
+ from code_puppy.tools import register_tools_for_agent
528
+
529
+ agent_tools = agent_config.get_available_tools()
530
+ register_tools_for_agent(temp_agent, agent_tools)
531
+
532
+ # Wrap with DBOS - no streaming for sub-agents
533
+ dbos_agent = DBOSAgent(
534
+ temp_agent,
535
+ name=subagent_name,
536
+ )
493
537
  temp_agent = dbos_agent
494
538
 
539
+ # Store MCP servers to add at runtime
540
+ subagent_mcp_servers = mcp_servers
541
+ else:
542
+ # Non-DBOS path - include MCP servers directly in the agent
543
+ temp_agent = Agent(
544
+ model=model,
545
+ instructions=instructions,
546
+ output_type=str,
547
+ retries=3,
548
+ toolsets=mcp_servers,
549
+ history_processors=[agent_config.message_history_accumulator],
550
+ model_settings=model_settings,
551
+ )
552
+
553
+ # Register the tools that the agent needs
554
+ from code_puppy.tools import register_tools_for_agent
555
+
556
+ agent_tools = agent_config.get_available_tools()
557
+ register_tools_for_agent(temp_agent, agent_tools)
558
+
559
+ subagent_mcp_servers = None
560
+
495
561
  # Run the temporary agent with the provided prompt as an asyncio task
496
562
  # Pass the message_history from the session to continue the conversation
497
563
  workflow_id = None # Track for potential cancellation
498
- if get_use_dbos():
499
- # Generate a unique workflow ID for DBOS - ensures no collisions in back-to-back calls
500
- workflow_id = _generate_dbos_workflow_id(group_id)
501
- with SetWorkflowID(workflow_id):
564
+
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
+ )
581
+
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:
502
595
  task = asyncio.create_task(
503
596
  temp_agent.run(
504
597
  prompt,
505
598
  message_history=message_history,
506
599
  usage_limits=UsageLimits(request_limit=get_message_limit()),
600
+ event_stream_handler=stream_handler,
507
601
  )
508
602
  )
509
603
  _active_subagent_tasks.add(task)
510
- else:
511
- task = asyncio.create_task(
512
- temp_agent.run(
513
- prompt,
514
- message_history=message_history,
515
- usage_limits=UsageLimits(request_limit=get_message_limit()),
516
- )
517
- )
518
- _active_subagent_tasks.add(task)
519
604
 
520
- try:
521
- result = await task
522
- finally:
523
- _active_subagent_tasks.discard(task)
524
- if task.cancelled():
525
- if get_use_dbos() and workflow_id:
526
- 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)
527
612
 
528
613
  # Extract the response from the result
529
614
  response = result.output
@@ -550,13 +635,23 @@ def register_invoke_agent(agent):
550
635
  )
551
636
  )
552
637
 
638
+ # Emit clean completion summary
639
+ emit_success(
640
+ f"✓ {agent_name} completed successfully", message_group=group_id
641
+ )
642
+
553
643
  return AgentInvokeOutput(
554
644
  response=response, agent_name=agent_name, session_id=session_id
555
645
  )
556
646
 
557
- 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
558
652
  error_msg = f"Error invoking agent '{agent_name}': {traceback.format_exc()}"
559
653
  emit_error(error_msg, message_group=group_id)
654
+
560
655
  return AgentInvokeOutput(
561
656
  response=None,
562
657
  agent_name=agent_name,
@@ -567,5 +662,13 @@ def register_invoke_agent(agent):
567
662
  finally:
568
663
  # Restore the previous session context
569
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)
570
673
 
571
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: