code-puppy 0.0.341__py3-none-any.whl → 0.0.348__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 (26) hide show
  1. code_puppy/agents/base_agent.py +17 -248
  2. code_puppy/agents/event_stream_handler.py +257 -0
  3. code_puppy/cli_runner.py +4 -3
  4. code_puppy/command_line/add_model_menu.py +8 -9
  5. code_puppy/command_line/mcp/catalog_server_installer.py +5 -6
  6. code_puppy/command_line/mcp/custom_server_form.py +54 -19
  7. code_puppy/command_line/mcp/custom_server_installer.py +8 -9
  8. code_puppy/command_line/mcp/handler.py +0 -2
  9. code_puppy/command_line/mcp/help_command.py +1 -5
  10. code_puppy/command_line/mcp/start_command.py +36 -18
  11. code_puppy/command_line/onboarding_slides.py +0 -1
  12. code_puppy/command_line/prompt_toolkit_completion.py +16 -10
  13. code_puppy/command_line/utils.py +54 -0
  14. code_puppy/mcp_/async_lifecycle.py +35 -4
  15. code_puppy/mcp_/managed_server.py +49 -20
  16. code_puppy/mcp_/manager.py +81 -52
  17. code_puppy/messaging/message_queue.py +11 -23
  18. code_puppy/tools/agent_tools.py +66 -13
  19. {code_puppy-0.0.341.dist-info → code_puppy-0.0.348.dist-info}/METADATA +1 -1
  20. {code_puppy-0.0.341.dist-info → code_puppy-0.0.348.dist-info}/RECORD +25 -25
  21. code_puppy/command_line/mcp/add_command.py +0 -170
  22. {code_puppy-0.0.341.data → code_puppy-0.0.348.data}/data/code_puppy/models.json +0 -0
  23. {code_puppy-0.0.341.data → code_puppy-0.0.348.data}/data/code_puppy/models_dev_api.json +0 -0
  24. {code_puppy-0.0.341.dist-info → code_puppy-0.0.348.dist-info}/WHEEL +0 -0
  25. {code_puppy-0.0.341.dist-info → code_puppy-0.0.348.dist-info}/entry_points.txt +0 -0
  26. {code_puppy-0.0.341.dist-info → code_puppy-0.0.348.dist-info}/licenses/LICENSE +0 -0
@@ -469,41 +469,57 @@ class MCPManager:
469
469
  def start_server_sync(self, server_id: str) -> bool:
470
470
  """
471
471
  Synchronous wrapper for start_server.
472
+
473
+ IMPORTANT: This schedules the server start as a background task.
474
+ The server subprocess will start asynchronously - it may not be
475
+ immediately ready when this function returns.
472
476
  """
473
477
  try:
474
- asyncio.get_running_loop()
475
- # We're in an async context, but we need to wait for completion
476
- # Create a future and schedule the coroutine
478
+ loop = asyncio.get_running_loop()
479
+ # We're in an async context - schedule the server start as a background task
480
+ # DO NOT use blocking time.sleep() here as it freezes the event loop!
481
+
482
+ # First, enable the server immediately so it's recognized as "starting"
483
+ managed_server = self._managed_servers.get(server_id)
484
+ if managed_server:
485
+ managed_server.enable()
486
+ self.status_tracker.set_status(server_id, ServerState.STARTING)
487
+ self.status_tracker.record_start_time(server_id)
477
488
 
478
- # Use run_in_executor to run the async function synchronously
479
- async def run_async():
480
- return await self.start_server(server_id)
489
+ # Schedule the async start_server to run in the background
490
+ # This will properly start the subprocess and lifecycle task
491
+ async def start_server_background():
492
+ try:
493
+ result = await self.start_server(server_id)
494
+ if result:
495
+ logger.info(f"Background server start completed: {server_id}")
496
+ else:
497
+ logger.warning(f"Background server start failed: {server_id}")
498
+ return result
499
+ except Exception as e:
500
+ logger.error(f"Background server start error for {server_id}: {e}")
501
+ self.status_tracker.set_status(server_id, ServerState.ERROR)
502
+ return False
481
503
 
482
- # Schedule the task and wait briefly for it to complete
483
- task = asyncio.create_task(run_async())
504
+ # Create the task - it will run when the event loop gets control
505
+ task = loop.create_task(
506
+ start_server_background(), name=f"start_server_{server_id}"
507
+ )
484
508
 
485
- # Give it a moment to complete - this fixes the race condition
486
- import time
509
+ # Store task reference to prevent garbage collection
510
+ if not hasattr(self, "_pending_start_tasks"):
511
+ self._pending_start_tasks = {}
512
+ self._pending_start_tasks[server_id] = task
487
513
 
488
- time.sleep(0.1) # Small delay to let async tasks progress
514
+ # Add callback to clean up task reference when done
515
+ def cleanup_task(t):
516
+ if hasattr(self, "_pending_start_tasks"):
517
+ self._pending_start_tasks.pop(server_id, None)
489
518
 
490
- # Check if task completed, if not, fall back to sync enable
491
- if task.done():
492
- try:
493
- result = task.result()
494
- return result
495
- except Exception:
496
- pass
519
+ task.add_done_callback(cleanup_task)
497
520
 
498
- # If async didn't complete, enable synchronously
499
- managed_server = self._managed_servers.get(server_id)
500
- if managed_server:
501
- managed_server.enable()
502
- self.status_tracker.set_status(server_id, ServerState.RUNNING)
503
- self.status_tracker.record_start_time(server_id)
504
- logger.info(f"Enabled server synchronously: {server_id}")
505
- return True
506
- return False
521
+ logger.info(f"Scheduled background start for server: {server_id}")
522
+ return True # Return immediately - server will start in background
507
523
 
508
524
  except RuntimeError:
509
525
  # No async loop, just enable the server
@@ -582,39 +598,52 @@ class MCPManager:
582
598
  def stop_server_sync(self, server_id: str) -> bool:
583
599
  """
584
600
  Synchronous wrapper for stop_server.
601
+
602
+ IMPORTANT: This schedules the server stop as a background task.
603
+ The server subprocess will stop asynchronously.
585
604
  """
586
605
  try:
587
- asyncio.get_running_loop()
606
+ loop = asyncio.get_running_loop()
607
+ # We're in an async context - schedule the server stop as a background task
608
+ # DO NOT use blocking time.sleep() here as it freezes the event loop!
588
609
 
589
- # We're in an async context, but we need to wait for completion
590
- async def run_async():
591
- return await self.stop_server(server_id)
610
+ # First, disable the server immediately
611
+ managed_server = self._managed_servers.get(server_id)
612
+ if managed_server:
613
+ managed_server.disable()
614
+ self.status_tracker.set_status(server_id, ServerState.STOPPING)
615
+ self.status_tracker.record_stop_time(server_id)
592
616
 
593
- # Schedule the task and wait briefly for it to complete
594
- task = asyncio.create_task(run_async())
617
+ # Schedule the async stop_server to run in the background
618
+ async def stop_server_background():
619
+ try:
620
+ result = await self.stop_server(server_id)
621
+ if result:
622
+ logger.info(f"Background server stop completed: {server_id}")
623
+ return result
624
+ except Exception as e:
625
+ logger.error(f"Background server stop error for {server_id}: {e}")
626
+ return False
595
627
 
596
- # Give it a moment to complete - this fixes the race condition
597
- import time
628
+ # Create the task - it will run when the event loop gets control
629
+ task = loop.create_task(
630
+ stop_server_background(), name=f"stop_server_{server_id}"
631
+ )
598
632
 
599
- time.sleep(0.1) # Small delay to let async tasks progress
633
+ # Store task reference to prevent garbage collection
634
+ if not hasattr(self, "_pending_stop_tasks"):
635
+ self._pending_stop_tasks = {}
636
+ self._pending_stop_tasks[server_id] = task
600
637
 
601
- # Check if task completed, if not, fall back to sync disable
602
- if task.done():
603
- try:
604
- result = task.result()
605
- return result
606
- except Exception:
607
- pass
638
+ # Add callback to clean up task reference when done
639
+ def cleanup_task(t):
640
+ if hasattr(self, "_pending_stop_tasks"):
641
+ self._pending_stop_tasks.pop(server_id, None)
608
642
 
609
- # If async didn't complete, disable synchronously
610
- managed_server = self._managed_servers.get(server_id)
611
- if managed_server:
612
- managed_server.disable()
613
- self.status_tracker.set_status(server_id, ServerState.STOPPED)
614
- self.status_tracker.record_stop_time(server_id)
615
- logger.info(f"Disabled server synchronously: {server_id}")
616
- return True
617
- return False
643
+ task.add_done_callback(cleanup_task)
644
+
645
+ logger.info(f"Scheduled background stop for server: {server_id}")
646
+ return True # Return immediately - server will stop in background
618
647
 
619
648
  except RuntimeError:
620
649
  # No async loop, just disable the server
@@ -329,31 +329,19 @@ def emit_divider(content: str = "─" * 100 + "\n", **metadata):
329
329
 
330
330
 
331
331
  def emit_prompt(prompt_text: str, timeout: float = None) -> str:
332
- """Emit a human input request and wait for response."""
333
- # TUI mode has been removed, always use interactive mode input
334
- if True:
335
- # Emit the prompt as a message for display
336
- from code_puppy.messaging import emit_info
332
+ """Emit a human input request and wait for response.
337
333
 
338
- emit_info(prompt_text)
334
+ Uses safe_input for cross-platform compatibility, especially on Windows
335
+ where raw input() can fail after prompt_toolkit Applications.
336
+ """
337
+ from code_puppy.command_line.utils import safe_input
338
+ from code_puppy.messaging import emit_info
339
339
 
340
- # Get input directly
341
- try:
342
- # Try to use rich console for better formatting
343
- from rich.console import Console
344
-
345
- console = Console()
346
- response = console.input("[cyan]>>> [/cyan]")
347
- return response
348
- except Exception:
349
- # Fallback to basic input
350
- response = input(">>> ")
351
- return response
352
-
353
- # In TUI mode, use the queue system
354
- queue = get_global_queue()
355
- prompt_id = queue.create_prompt_request(prompt_text)
356
- return queue.wait_for_prompt_response(prompt_id, timeout)
340
+ emit_info(prompt_text)
341
+
342
+ # Use safe_input which resets Windows console state before reading
343
+ response = safe_input(">>> ")
344
+ return response
357
345
 
358
346
 
359
347
  def provide_prompt_response(prompt_id: str, response: str):
@@ -21,6 +21,7 @@ from code_puppy.config import (
21
21
  DATA_DIR,
22
22
  get_message_limit,
23
23
  get_use_dbos,
24
+ get_value,
24
25
  )
25
26
  from code_puppy.messaging import (
26
27
  SubAgentInvocationMessage,
@@ -471,39 +472,90 @@ def register_invoke_agent(agent):
471
472
  subagent_name = f"temp-invoke-agent-{session_id}"
472
473
  model_settings = make_model_settings(model_name)
473
474
 
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
- )
475
+ # Get MCP servers for sub-agents (same as main agent)
476
+ from code_puppy.mcp_ import get_mcp_manager
482
477
 
483
- # Register the tools that the agent needs
484
- from code_puppy.tools import register_tools_for_agent
478
+ mcp_servers = []
479
+ mcp_disabled = get_value("disable_mcp_servers")
480
+ if not (
481
+ mcp_disabled and str(mcp_disabled).lower() in ("1", "true", "yes", "on")
482
+ ):
483
+ manager = get_mcp_manager()
484
+ mcp_servers = manager.get_servers_for_agent()
485
485
 
486
- agent_tools = agent_config.get_available_tools()
487
- register_tools_for_agent(temp_agent, agent_tools)
486
+ # Get the event_stream_handler for streaming output
487
+ from code_puppy.agents.event_stream_handler import event_stream_handler
488
488
 
489
489
  if get_use_dbos():
490
490
  from pydantic_ai.durable_exec.dbos import DBOSAgent
491
491
 
492
- dbos_agent = DBOSAgent(temp_agent, name=subagent_name)
492
+ # For DBOS, create agent without MCP servers (to avoid serialization issues)
493
+ # and add them at runtime
494
+ temp_agent = Agent(
495
+ model=model,
496
+ instructions=instructions,
497
+ output_type=str,
498
+ retries=3,
499
+ toolsets=[], # MCP servers added separately for DBOS
500
+ history_processors=[agent_config.message_history_accumulator],
501
+ model_settings=model_settings,
502
+ )
503
+
504
+ # Register the tools that the agent needs
505
+ from code_puppy.tools import register_tools_for_agent
506
+
507
+ agent_tools = agent_config.get_available_tools()
508
+ register_tools_for_agent(temp_agent, agent_tools)
509
+
510
+ # Wrap with DBOS - pass event_stream_handler for streaming output
511
+ dbos_agent = DBOSAgent(
512
+ temp_agent,
513
+ name=subagent_name,
514
+ event_stream_handler=event_stream_handler,
515
+ )
493
516
  temp_agent = dbos_agent
494
517
 
518
+ # Store MCP servers to add at runtime
519
+ subagent_mcp_servers = mcp_servers
520
+ else:
521
+ # Non-DBOS path - include MCP servers directly in the agent
522
+ temp_agent = Agent(
523
+ model=model,
524
+ instructions=instructions,
525
+ output_type=str,
526
+ retries=3,
527
+ toolsets=mcp_servers,
528
+ history_processors=[agent_config.message_history_accumulator],
529
+ model_settings=model_settings,
530
+ )
531
+
532
+ # Register the tools that the agent needs
533
+ from code_puppy.tools import register_tools_for_agent
534
+
535
+ agent_tools = agent_config.get_available_tools()
536
+ register_tools_for_agent(temp_agent, agent_tools)
537
+
538
+ subagent_mcp_servers = None
539
+
495
540
  # Run the temporary agent with the provided prompt as an asyncio task
496
541
  # Pass the message_history from the session to continue the conversation
497
542
  workflow_id = None # Track for potential cancellation
498
543
  if get_use_dbos():
499
544
  # Generate a unique workflow ID for DBOS - ensures no collisions in back-to-back calls
500
545
  workflow_id = _generate_dbos_workflow_id(group_id)
546
+
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
551
+
501
552
  with SetWorkflowID(workflow_id):
502
553
  task = asyncio.create_task(
503
554
  temp_agent.run(
504
555
  prompt,
505
556
  message_history=message_history,
506
557
  usage_limits=UsageLimits(request_limit=get_message_limit()),
558
+ event_stream_handler=event_stream_handler,
507
559
  )
508
560
  )
509
561
  _active_subagent_tasks.add(task)
@@ -513,6 +565,7 @@ def register_invoke_agent(agent):
513
565
  prompt,
514
566
  message_history=message_history,
515
567
  usage_limits=UsageLimits(request_limit=get_message_limit()),
568
+ event_stream_handler=event_stream_handler,
516
569
  )
517
570
  )
518
571
  _active_subagent_tasks.add(task)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code-puppy
3
- Version: 0.0.341
3
+ Version: 0.0.348
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
@@ -3,7 +3,7 @@ code_puppy/__main__.py,sha256=pDVssJOWP8A83iFkxMLY9YteHYat0EyWDQqMkKHpWp4,203
3
3
  code_puppy/callbacks.py,sha256=hqTV--dNxG5vwWWm3MrEjmb8MZuHFFdmHePl23NXPHk,8621
4
4
  code_puppy/chatgpt_codex_client.py,sha256=Om0ANB_kpHubhCwNzF9ENf8RvKBqs0IYzBLl_SNw0Vk,9833
5
5
  code_puppy/claude_cache_client.py,sha256=MLIRSJP428r9IK_aV6XyCXrCfQnNti32U60psPymLM4,14860
6
- code_puppy/cli_runner.py,sha256=BQu5Sa9y_ueqtgvbmuhWS-Tmd1FAjMbhTrtjFKbVZjM,34919
6
+ code_puppy/cli_runner.py,sha256=w5CLKgQYYaT7My3Cga2StXYol-u6DBxNzzUuhhsfhsA,34952
7
7
  code_puppy/config.py,sha256=RlnrLkyFXm7h2Htf8rQA7vqoAyzLPMrESle417uLmFw,52373
8
8
  code_puppy/error_logging.py,sha256=a80OILCUtJhexI6a9GM-r5LqIdjvSRzggfgPp2jv1X0,3297
9
9
  code_puppy/gemini_code_assist.py,sha256=KGS7sO5OLc83nDF3xxS-QiU6vxW9vcm6hmzilu79Ef8,13867
@@ -40,11 +40,12 @@ code_puppy/agents/agent_qa_expert.py,sha256=5Ikb4U3SZQknUEfwlHZiyZXKqnffnOTQagr_
40
40
  code_puppy/agents/agent_qa_kitten.py,sha256=5PeFFSwCFlTUvP6h5bGntx0xv5NmRwBiw0HnMqY8nLI,9107
41
41
  code_puppy/agents/agent_security_auditor.py,sha256=SpiYNA0XAsIwBj7S2_EQPRslRUmF_-b89pIJyW7DYtY,12022
42
42
  code_puppy/agents/agent_typescript_reviewer.py,sha256=vsnpp98xg6cIoFAEJrRTUM_i4wLEWGm5nJxs6fhHobM,10275
43
- code_puppy/agents/base_agent.py,sha256=QnPmROIw-rs5wopcVTZdBZusP0mGRC6EFZ7y_V00rMI,84825
43
+ code_puppy/agents/base_agent.py,sha256=zX7XPNgveBoBCm-SoRncwabnU0uSyEwtG3q-x8JFkiU,73256
44
+ code_puppy/agents/event_stream_handler.py,sha256=C1TDkp9eTHEFvnTQzaGFh_q9izL1r-EnCRTez9kqO2Y,11438
44
45
  code_puppy/agents/json_agent.py,sha256=lhopDJDoiSGHvD8A6t50hi9ZBoNRKgUywfxd0Po_Dzc,4886
45
46
  code_puppy/agents/prompt_reviewer.py,sha256=JJrJ0m5q0Puxl8vFsyhAbY9ftU9n6c6UxEVdNct1E-Q,5558
46
47
  code_puppy/command_line/__init__.py,sha256=y7WeRemfYppk8KVbCGeAIiTuiOszIURCDjOMZv_YRmU,45
47
- code_puppy/command_line/add_model_menu.py,sha256=caXxSQc6dgx0qQ68RRFrDTsiH-wZjl4nUv2r0javhaM,43262
48
+ code_puppy/command_line/add_model_menu.py,sha256=CpURhxPvUhLHLBV_uwH1ODfJ-WAcGklvlsjEf5Vfvg4,43255
48
49
  code_puppy/command_line/attachments.py,sha256=4Q5I2Es4j0ltnz5wjw2z0QXMsiMJvEfWRkPf_lJeITM,13093
49
50
  code_puppy/command_line/autosave_menu.py,sha256=de7nOmFmEH6x5T7C95U8N8xgxxeF-l5lgaJzGJsF3ZY,19824
50
51
  code_puppy/command_line/clipboard.py,sha256=oe9bfAX5RnT81FiYrDmhvHaePS1tAT-NFG1fSXubSD4,16869
@@ -60,21 +61,20 @@ code_puppy/command_line/mcp_completion.py,sha256=eKzW2O7gun7HoHekOW0XVXhNS5J2xCt
60
61
  code_puppy/command_line/model_picker_completion.py,sha256=nDnlf0qFCG2zAm_mWW2eMYwVC7eROVQrFe92hZqOKa8,6810
61
62
  code_puppy/command_line/model_settings_menu.py,sha256=AI97IusDgMmWoCOp7C0Yrk_Uy6M9cmVhoZfVWgFWwXg,32392
62
63
  code_puppy/command_line/motd.py,sha256=XuIk3UTLawwVFM-NfoaJGU5F2hPLASTFXq84UdDMT0Q,2408
63
- code_puppy/command_line/onboarding_slides.py,sha256=tHob7rB_n32dfjtPH-RSG0WLMjDHhlmNxfsF7WCgcVc,7191
64
+ code_puppy/command_line/onboarding_slides.py,sha256=itqAsuHzjHpD_XNz6FniBIYr6dNyP1AW_XQZQ6SbVek,7125
64
65
  code_puppy/command_line/onboarding_wizard.py,sha256=U5lV_1P3IwDYZUHar0zKgdp121zzkvOwwORvdCZwFcw,10241
65
66
  code_puppy/command_line/pin_command_completion.py,sha256=juSvdqRpk7AdfkPy1DJx5NzfEUU5KYGlChvP0hisM18,11667
66
- code_puppy/command_line/prompt_toolkit_completion.py,sha256=U6rRMU3gNqTf3vz3K5V5PZ1xjE8I2ldoEk2FiT8kHJg,32535
67
+ code_puppy/command_line/prompt_toolkit_completion.py,sha256=49GM3jVE89G1M3XroMZk2LhGgXpOO8XZ0Sg8h4a6LLw,32806
67
68
  code_puppy/command_line/session_commands.py,sha256=Jh8GGfhlfBAEVfucKLbcZjNaXYd0twImiOwq2ZnGdQQ,9902
68
- code_puppy/command_line/utils.py,sha256=7eyxDHjPjPB9wGDJQQcXV_zOsGdYsFgI0SGCetVmTqE,1251
69
+ code_puppy/command_line/utils.py,sha256=_upMrrmDp5hteUjrRiEgVR6SoeNEPNZGXb5lOWYqcUQ,2952
69
70
  code_puppy/command_line/mcp/__init__.py,sha256=0-OQuwjq_pLiTVJ1_NrirVwdRerghyKs_MTZkwPC7YY,315
70
- code_puppy/command_line/mcp/add_command.py,sha256=iWqHReWbVOO3kuPE4NTMs3dv_BluxTBaasDPm9P1lU0,5892
71
71
  code_puppy/command_line/mcp/base.py,sha256=pPeNnSyM0GGqD6mhYN-qA22rAT9bEapxliwH_YiIu3Q,823
72
- code_puppy/command_line/mcp/catalog_server_installer.py,sha256=vY7MAy6O92bs-gRoZOO9jVPx23omr0jpSZucfjVkeOY,6170
73
- code_puppy/command_line/mcp/custom_server_form.py,sha256=iLEe30NI_01PP7xETgckBqwlyVrPzbHmzx7by8QKiVA,22082
74
- code_puppy/command_line/mcp/custom_server_installer.py,sha256=4NhHxf4wGUh4OvdIurZAlC7TrNcmm4j8dWucIKrekWg,5733
72
+ code_puppy/command_line/mcp/catalog_server_installer.py,sha256=G9FvQPTBB4LeJ4cOR9DvIkp82mClKRKI35KELbFLCKU,6181
73
+ code_puppy/command_line/mcp/custom_server_form.py,sha256=z0hsqXY1_ScJoacneyfrFHeVUlU3ZUHYt6CXV1wnJ0Y,23733
74
+ code_puppy/command_line/mcp/custom_server_installer.py,sha256=oLB5j07XKApdTrCDlY97GxE1NHrqupsX6DOCXzFj3TE,5744
75
75
  code_puppy/command_line/mcp/edit_command.py,sha256=_WxxpaTgxo9pbvMogG9yvh2mcLE5SYf0Qbi8a8IpZ0k,4603
76
- code_puppy/command_line/mcp/handler.py,sha256=S8KSgf78w7vL7_ReArdcxTgZRoIi0Z0jCksNuELnCFU,4616
77
- code_puppy/command_line/mcp/help_command.py,sha256=dU3ekOjjNKxRS-RjUXJZ7PBwmJeIe-5MhcMYCiyVu4w,5472
76
+ code_puppy/command_line/mcp/handler.py,sha256=jwhcLi28QQ6IuE6E5IsMbU67jMZChfZW96hG9ezYgN4,4547
77
+ code_puppy/command_line/mcp/help_command.py,sha256=sTWecPqmmq6vmLVgZVNjBXKnziCLoJSKsugHkxiJlTI,5285
78
78
  code_puppy/command_line/mcp/install_command.py,sha256=lmUyMUWtkGuy1SOQRHjQgt8mD3t1agVMQfEL5_TOzTM,8364
79
79
  code_puppy/command_line/mcp/install_menu.py,sha256=GVNR7SJbheGLFc_r9N3CT1AT024ptzsEcj1cRnp4U3g,24769
80
80
  code_puppy/command_line/mcp/list_command.py,sha256=UKQFPlhT9qGMCyG5VKjvnSMzDDtfAhIaKU_eErgZJDg,3181
@@ -83,7 +83,7 @@ code_puppy/command_line/mcp/remove_command.py,sha256=hyU_tKJWfyLnmufrFVLwlF0qFEb
83
83
  code_puppy/command_line/mcp/restart_command.py,sha256=w5EcDac09iCvPBAR0u2M5KSIhASqTu5uZwsjCJ4JLhk,3588
84
84
  code_puppy/command_line/mcp/search_command.py,sha256=mDkSz_KjPbvlO9U7oYUKJlqqY4QM90gWKO2xsH2i3SA,4244
85
85
  code_puppy/command_line/mcp/start_all_command.py,sha256=_TVrjRR_oqJVS6qQaHssS0V_3342av1h9gz-Fxwa-Dw,4667
86
- code_puppy/command_line/mcp/start_command.py,sha256=EWXc_05nZaHCAmGxjXZJngEzlwq1HzBh6SNeki7ufGI,3409
86
+ code_puppy/command_line/mcp/start_command.py,sha256=XhuhyMpuo2Bkp53qxvZBSM8yRGatcwY8m-DXcQBHAZc,4344
87
87
  code_puppy/command_line/mcp/status_command.py,sha256=NCyjFlBMURQ39T3G2dTPbyJdNC6X14FyWFzVh4BBnig,6657
88
88
  code_puppy/command_line/mcp/stop_all_command.py,sha256=33mRvxd2cBbTXE6BSkSzFmdDOT4yCxwGEDrHczqpavw,3864
89
89
  code_puppy/command_line/mcp/stop_command.py,sha256=iMzk9h6NuUDg0hhI5eDLem5VS8IwBC7xg8AU-7jsmBE,2679
@@ -91,7 +91,7 @@ code_puppy/command_line/mcp/test_command.py,sha256=eV8u5KKClRK1M2_os1zA78b9TDuYU
91
91
  code_puppy/command_line/mcp/utils.py,sha256=0Wt4ttYgSlVvtusYmBLKXSkjAjcsDiUxcZQAoFLUNnE,3625
92
92
  code_puppy/command_line/mcp/wizard_utils.py,sha256=M5X8RchkQujKYKORsXJnUq2kJHzmNfutIUrsHmfzi7k,11126
93
93
  code_puppy/mcp_/__init__.py,sha256=P9bmVX5UDmzQDqHMylOxuo5Hi82E30pPSMOYw8lEx7Q,1781
94
- code_puppy/mcp_/async_lifecycle.py,sha256=pTQrwQCVgjad8EJeRTSHtIeSQRgT_8r5BeLv-1SgKog,7772
94
+ code_puppy/mcp_/async_lifecycle.py,sha256=XVB73WOc2_sWi0ySWh_h6n5a-xtt72BGvtajyeoKW20,9014
95
95
  code_puppy/mcp_/blocking_startup.py,sha256=27R2wwLVDu5i19IP90KmCn_zHrvVcHVNU8d9c8EcTeI,14780
96
96
  code_puppy/mcp_/captured_stdio_server.py,sha256=t_mnCjtiopRsyi4Aa97rFzDVxEQmb-u94sWJsj2FP8k,8925
97
97
  code_puppy/mcp_/circuit_breaker.py,sha256=a83YwXux9h4R6zBWBUrCIqtp2ffyl7JZEoK2tErG_0I,8601
@@ -99,8 +99,8 @@ code_puppy/mcp_/config_wizard.py,sha256=JNNpgnSD6PFSyS3pTdEdD164oXd2VKp4VHLSz3To
99
99
  code_puppy/mcp_/dashboard.py,sha256=VtaFxLtPnbM_HL2TXRDAg6IqcM-EcFkoghGgkfhMrKI,9417
100
100
  code_puppy/mcp_/error_isolation.py,sha256=mpPBiH17zTXPsOEAn9WmkbwQwnt4gmgiaWv87JBJbUo,12426
101
101
  code_puppy/mcp_/health_monitor.py,sha256=n5R6EeYOYbUucUFe74qGWCU3g6Mep5UEQbLF0wbT0dU,19688
102
- code_puppy/mcp_/managed_server.py,sha256=APqFKjHtsG8iM4so1dYxvKnb0BTmppHnaY8UJ5DBE9g,14075
103
- code_puppy/mcp_/manager.py,sha256=pJ4cALicTxfwG2JIjJraLLf0Mzes-cEVAKIcUwfOoKA,29172
102
+ code_puppy/mcp_/managed_server.py,sha256=e-DetKb3bVgdLYw7miTptLAbqhRgZ1cNoTGS0fh4Noo,15371
103
+ code_puppy/mcp_/manager.py,sha256=_2ZRTLS8Sf3SMgEpAHl-wXBDYVqg2l-j9uI9EkfCNaE,31062
104
104
  code_puppy/mcp_/mcp_logs.py,sha256=o4pSHwELWIjEjqhfaMMEGrBvb159-VIgUp21E707BPo,6264
105
105
  code_puppy/mcp_/registry.py,sha256=U_t12WQ-En-KGyZoiTYdqlhp9NkDTWafu8g5InvF2NM,15774
106
106
  code_puppy/mcp_/retry_manager.py,sha256=evVxbtrsHNyo8UoI7zpO-NVDegibn82RLlgN8VKewA8,10665
@@ -112,7 +112,7 @@ code_puppy/messaging/__init__.py,sha256=THJQDdRub3jiWIRPqF34VggXem3Y2tuUFAJGdDAL
112
112
  code_puppy/messaging/bus.py,sha256=TbdltJ0D5tqnaE4irq1fcXllDYm-mQ_SiX1IFm-S4sw,21406
113
113
  code_puppy/messaging/commands.py,sha256=77CtKVNaF5KS3Xyzd0ccDAisZWQxL3weVEt3J-SfYxo,5464
114
114
  code_puppy/messaging/markdown_patches.py,sha256=dMIJozzJChuHa8QNMSEz_kC-dyt7kZiDLZ7rjthbcmg,1626
115
- code_puppy/messaging/message_queue.py,sha256=e-viZxacBoNSxRJnCJ4hU4vzsSI3oX_rN58RwhJKFfU,11825
115
+ code_puppy/messaging/message_queue.py,sha256=1-5NFWIes5kpecsKnhuQQJPeT0-X102Xi1-IwXUM5_Y,11430
116
116
  code_puppy/messaging/messages.py,sha256=F7RwMHeQrIk-8kuSSBU76wBq1NGuLb2H5cJrSMTC3XM,16464
117
117
  code_puppy/messaging/queue_console.py,sha256=T0U_V1tdN6hd9DLokp-HCk0mhu8Ivpfajha368CBZrU,9983
118
118
  code_puppy/messaging/renderers.py,sha256=GHVtMnxE1pJ-yrcRjacY81JcjlHRz3UVHzp-ohN-CGE,12058
@@ -159,7 +159,7 @@ code_puppy/plugins/shell_safety/command_cache.py,sha256=adYtSPNVOZfW_6dQdtEihO6E
159
159
  code_puppy/plugins/shell_safety/register_callbacks.py,sha256=W3v664RR48Fdbbbltf_NnX22_Ahw2AvAOtvXvWc7KxQ,7322
160
160
  code_puppy/prompts/codex_system_prompt.md,sha256=hEFTCziroLqZmqNle5kG34A8kvTteOWezCiVrAEKhE0,24400
161
161
  code_puppy/tools/__init__.py,sha256=BVTZ85jLHgDANwOnUSOz3UDlp8VQDq4DoGF23BRlyWw,6032
162
- code_puppy/tools/agent_tools.py,sha256=snBI6FlFtR03CbYKXwu53R48c_fRSuDIwcNdVUruLcA,21020
162
+ code_puppy/tools/agent_tools.py,sha256=pRIzGH8jJjlg1XiMrQn_kn0OzUfsCq4EWTuISD2D6hQ,23393
163
163
  code_puppy/tools/command_runner.py,sha256=3qXVnVTaBPia6y2D29As47_TRKgpyCj82yMFK-8UUYc,44954
164
164
  code_puppy/tools/common.py,sha256=IYf-KOcP5eN2MwTlpULSXNATn7GzloAKl7_M1Uyfe4Y,40360
165
165
  code_puppy/tools/file_modifications.py,sha256=vz9n7R0AGDSdLUArZr_55yJLkyI30M8zreAppxIx02M,29380
@@ -175,10 +175,10 @@ code_puppy/tools/browser/browser_scripts.py,sha256=sNb8eLEyzhasy5hV4B9OjM8yIVMLV
175
175
  code_puppy/tools/browser/browser_workflows.py,sha256=nitW42vCf0ieTX1gLabozTugNQ8phtoFzZbiAhw1V90,6491
176
176
  code_puppy/tools/browser/camoufox_manager.py,sha256=RZjGOEftE5sI_tsercUyXFSZI2wpStXf-q0PdYh2G3I,8680
177
177
  code_puppy/tools/browser/vqa_agent.py,sha256=DBn9HKloILqJSTSdNZzH_PYWT0B2h9VwmY6akFQI_uU,2913
178
- code_puppy-0.0.341.data/data/code_puppy/models.json,sha256=FMQdE_yvP_8y0xxt3K918UkFL9cZMYAqW1SfXcQkU_k,3105
179
- code_puppy-0.0.341.data/data/code_puppy/models_dev_api.json,sha256=wHjkj-IM_fx1oHki6-GqtOoCrRMR0ScK0f-Iz0UEcy8,548187
180
- code_puppy-0.0.341.dist-info/METADATA,sha256=TyW2aMaB5pukc2EP2CV8fjW-qotBUDUms_CeaiUbKv4,27550
181
- code_puppy-0.0.341.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
182
- code_puppy-0.0.341.dist-info/entry_points.txt,sha256=Tp4eQC99WY3HOKd3sdvb22vZODRq0XkZVNpXOag_KdI,91
183
- code_puppy-0.0.341.dist-info/licenses/LICENSE,sha256=31u8x0SPgdOq3izJX41kgFazWsM43zPEF9eskzqbJMY,1075
184
- code_puppy-0.0.341.dist-info/RECORD,,
178
+ code_puppy-0.0.348.data/data/code_puppy/models.json,sha256=FMQdE_yvP_8y0xxt3K918UkFL9cZMYAqW1SfXcQkU_k,3105
179
+ code_puppy-0.0.348.data/data/code_puppy/models_dev_api.json,sha256=wHjkj-IM_fx1oHki6-GqtOoCrRMR0ScK0f-Iz0UEcy8,548187
180
+ code_puppy-0.0.348.dist-info/METADATA,sha256=jDNlXNr6nSsNNf8O2QJCuRlD9rZWGOV-fU-iZPt4kF0,27550
181
+ code_puppy-0.0.348.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
182
+ code_puppy-0.0.348.dist-info/entry_points.txt,sha256=Tp4eQC99WY3HOKd3sdvb22vZODRq0XkZVNpXOag_KdI,91
183
+ code_puppy-0.0.348.dist-info/licenses/LICENSE,sha256=31u8x0SPgdOq3izJX41kgFazWsM43zPEF9eskzqbJMY,1075
184
+ code_puppy-0.0.348.dist-info/RECORD,,
@@ -1,170 +0,0 @@
1
- """
2
- MCP Add Command - Adds new MCP servers from JSON configuration or wizard.
3
- """
4
-
5
- import json
6
- import logging
7
- import os
8
- from typing import List, Optional
9
-
10
- from code_puppy.messaging import emit_error, emit_info
11
-
12
- from .base import MCPCommandBase
13
- from .wizard_utils import run_interactive_install_wizard
14
-
15
- # Configure logging
16
- logger = logging.getLogger(__name__)
17
-
18
-
19
- class AddCommand(MCPCommandBase):
20
- """
21
- Command handler for adding MCP servers.
22
-
23
- Adds new MCP servers from JSON configuration or interactive wizard.
24
- """
25
-
26
- def execute(self, args: List[str], group_id: Optional[str] = None) -> None:
27
- """
28
- Add a new MCP server from JSON configuration or launch wizard.
29
-
30
- Usage:
31
- /mcp add - Launch interactive wizard
32
- /mcp add <json> - Add server from JSON config
33
-
34
- Example JSON:
35
- /mcp add {"name": "test", "type": "stdio", "command": "echo", "args": ["hello"]}
36
-
37
- Args:
38
- args: Command arguments - JSON config or empty for wizard
39
- group_id: Optional message group ID for grouping related messages
40
- """
41
- if group_id is None:
42
- group_id = self.generate_group_id()
43
-
44
- try:
45
- if args:
46
- # Parse JSON from arguments
47
- json_str = " ".join(args)
48
-
49
- try:
50
- config_dict = json.loads(json_str)
51
- except json.JSONDecodeError as e:
52
- emit_info(f"Invalid JSON: {e}", message_group=group_id)
53
- emit_info(
54
- "Usage: /mcp add <json> or /mcp add (for wizard)",
55
- message_group=group_id,
56
- )
57
- emit_info(
58
- 'Example: /mcp add {"name": "test", "type": "stdio", "command": "echo"}',
59
- message_group=group_id,
60
- )
61
- return
62
-
63
- # Validate required fields
64
- if "name" not in config_dict:
65
- emit_info("Missing required field: 'name'", message_group=group_id)
66
- return
67
- if "type" not in config_dict:
68
- emit_info("Missing required field: 'type'", message_group=group_id)
69
- return
70
-
71
- # Add the server
72
- success = self._add_server_from_json(config_dict, group_id)
73
-
74
- if success:
75
- # Reload MCP servers
76
- try:
77
- from code_puppy.agent import reload_mcp_servers
78
-
79
- reload_mcp_servers()
80
- except ImportError:
81
- pass
82
-
83
- emit_info(
84
- "Use '/mcp list' to see all servers", message_group=group_id
85
- )
86
-
87
- else:
88
- # No arguments - launch interactive wizard with server templates
89
- success = run_interactive_install_wizard(self.manager, group_id)
90
-
91
- if success:
92
- # Reload the agent to pick up new server
93
- try:
94
- from code_puppy.agent import reload_mcp_servers
95
-
96
- reload_mcp_servers()
97
- except ImportError:
98
- pass
99
-
100
- except ImportError as e:
101
- logger.error(f"Failed to import: {e}")
102
- emit_info("Required module not available", message_group=group_id)
103
- except Exception as e:
104
- logger.error(f"Error in add command: {e}")
105
- emit_error(f"Error adding server: {e}", message_group=group_id)
106
-
107
- def _add_server_from_json(self, config_dict: dict, group_id: str) -> bool:
108
- """
109
- Add a server from JSON configuration.
110
-
111
- Args:
112
- config_dict: Server configuration dictionary
113
- group_id: Message group ID
114
-
115
- Returns:
116
- True if successful, False otherwise
117
- """
118
- try:
119
- from code_puppy.config import MCP_SERVERS_FILE
120
- from code_puppy.mcp_.managed_server import ServerConfig
121
-
122
- # Extract required fields
123
- name = config_dict.pop("name")
124
- server_type = config_dict.pop("type")
125
- enabled = config_dict.pop("enabled", True)
126
-
127
- # Everything else goes into config
128
- server_config = ServerConfig(
129
- id=f"{name}_{hash(name)}",
130
- name=name,
131
- type=server_type,
132
- enabled=enabled,
133
- config=config_dict, # Remaining fields are server-specific config
134
- )
135
-
136
- # Register the server
137
- server_id = self.manager.register_server(server_config)
138
-
139
- if not server_id:
140
- emit_info(f"Failed to add server '{name}'", message_group=group_id)
141
- return False
142
-
143
- emit_info(
144
- f"✅ Added server '{name}' (ID: {server_id})", message_group=group_id
145
- )
146
-
147
- # Save to mcp_servers.json for persistence
148
- if os.path.exists(MCP_SERVERS_FILE):
149
- with open(MCP_SERVERS_FILE, "r") as f:
150
- data = json.load(f)
151
- servers = data.get("mcp_servers", {})
152
- else:
153
- servers = {}
154
- data = {"mcp_servers": servers}
155
-
156
- # Add new server
157
- servers[name] = config_dict.copy()
158
- servers[name]["type"] = server_type
159
-
160
- # Save back
161
- os.makedirs(os.path.dirname(MCP_SERVERS_FILE), exist_ok=True)
162
- with open(MCP_SERVERS_FILE, "w") as f:
163
- json.dump(data, f, indent=2)
164
-
165
- return True
166
-
167
- except Exception as e:
168
- logger.error(f"Error adding server from JSON: {e}")
169
- emit_error(f"Failed to add server: {e}", message_group=group_id)
170
- return False