waldiez 0.6.0__py3-none-any.whl → 0.6.1__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.

Potentially problematic release.


This version of waldiez might be problematic. Click here for more details.

Files changed (188) hide show
  1. waldiez/__init__.py +1 -1
  2. waldiez/_version.py +1 -1
  3. waldiez/cli.py +18 -7
  4. waldiez/cli_extras/jupyter.py +3 -0
  5. waldiez/cli_extras/runner.py +3 -1
  6. waldiez/cli_extras/studio.py +3 -1
  7. waldiez/exporter.py +9 -3
  8. waldiez/exporting/agent/exporter.py +9 -10
  9. waldiez/exporting/agent/extras/captain_agent_extras.py +6 -6
  10. waldiez/exporting/agent/extras/doc_agent_extras.py +6 -6
  11. waldiez/exporting/agent/extras/group_manager_agent_extas.py +34 -23
  12. waldiez/exporting/agent/extras/group_member_extras.py +6 -5
  13. waldiez/exporting/agent/extras/handoffs/after_work.py +1 -1
  14. waldiez/exporting/agent/extras/handoffs/available.py +1 -1
  15. waldiez/exporting/agent/extras/handoffs/condition.py +3 -2
  16. waldiez/exporting/agent/extras/handoffs/handoff.py +1 -1
  17. waldiez/exporting/agent/extras/handoffs/target.py +6 -4
  18. waldiez/exporting/agent/extras/rag/chroma_extras.py +27 -19
  19. waldiez/exporting/agent/extras/rag/mongo_extras.py +8 -8
  20. waldiez/exporting/agent/extras/rag/pgvector_extras.py +5 -5
  21. waldiez/exporting/agent/extras/rag/qdrant_extras.py +5 -4
  22. waldiez/exporting/agent/extras/rag/vector_db_extras.py +1 -1
  23. waldiez/exporting/agent/extras/rag_user_proxy_agent_extras.py +5 -7
  24. waldiez/exporting/agent/extras/reasoning_agent_extras.py +3 -5
  25. waldiez/exporting/chats/exporter.py +4 -4
  26. waldiez/exporting/chats/processor.py +1 -2
  27. waldiez/exporting/chats/utils/common.py +89 -48
  28. waldiez/exporting/chats/utils/group.py +9 -9
  29. waldiez/exporting/chats/utils/nested.py +7 -7
  30. waldiez/exporting/chats/utils/sequential.py +1 -1
  31. waldiez/exporting/chats/utils/single.py +2 -2
  32. waldiez/exporting/core/content.py +7 -7
  33. waldiez/exporting/core/context.py +5 -3
  34. waldiez/exporting/core/exporter.py +5 -3
  35. waldiez/exporting/core/exporters.py +2 -2
  36. waldiez/exporting/core/extras/agent_extras/captain_extras.py +2 -2
  37. waldiez/exporting/core/extras/agent_extras/group_manager_extras.py +2 -2
  38. waldiez/exporting/core/extras/agent_extras/rag_user_extras.py +2 -2
  39. waldiez/exporting/core/extras/agent_extras/standard_extras.py +3 -8
  40. waldiez/exporting/core/extras/base.py +7 -5
  41. waldiez/exporting/core/extras/flow_extras.py +4 -5
  42. waldiez/exporting/core/extras/model_extras.py +2 -2
  43. waldiez/exporting/core/extras/path_resolver.py +1 -2
  44. waldiez/exporting/core/extras/serializer.py +2 -2
  45. waldiez/exporting/core/protocols.py +6 -5
  46. waldiez/exporting/core/result.py +25 -28
  47. waldiez/exporting/core/types.py +10 -10
  48. waldiez/exporting/core/utils/llm_config.py +2 -2
  49. waldiez/exporting/core/validation.py +10 -11
  50. waldiez/exporting/flow/execution_generator.py +98 -10
  51. waldiez/exporting/flow/exporter.py +2 -2
  52. waldiez/exporting/flow/factory.py +2 -2
  53. waldiez/exporting/flow/file_generator.py +4 -2
  54. waldiez/exporting/flow/merger.py +5 -3
  55. waldiez/exporting/flow/orchestrator.py +72 -2
  56. waldiez/exporting/flow/utils/common.py +5 -5
  57. waldiez/exporting/flow/utils/importing.py +6 -7
  58. waldiez/exporting/flow/utils/linting.py +25 -9
  59. waldiez/exporting/flow/utils/logging.py +2 -2
  60. waldiez/exporting/models/exporter.py +8 -8
  61. waldiez/exporting/models/processor.py +5 -5
  62. waldiez/exporting/tools/exporter.py +2 -2
  63. waldiez/exporting/tools/processor.py +7 -4
  64. waldiez/io/__init__.py +8 -4
  65. waldiez/io/_ws.py +10 -6
  66. waldiez/io/models/constants.py +10 -10
  67. waldiez/io/models/content/audio.py +1 -0
  68. waldiez/io/models/content/base.py +20 -18
  69. waldiez/io/models/content/file.py +1 -0
  70. waldiez/io/models/content/image.py +1 -0
  71. waldiez/io/models/content/text.py +1 -0
  72. waldiez/io/models/content/video.py +1 -0
  73. waldiez/io/models/user_input.py +10 -5
  74. waldiez/io/models/user_response.py +17 -16
  75. waldiez/io/mqtt.py +18 -31
  76. waldiez/io/redis.py +18 -22
  77. waldiez/io/structured.py +52 -53
  78. waldiez/io/utils.py +3 -0
  79. waldiez/io/ws.py +5 -1
  80. waldiez/logger.py +16 -3
  81. waldiez/models/agents/__init__.py +3 -0
  82. waldiez/models/agents/agent/agent.py +23 -16
  83. waldiez/models/agents/agent/agent_data.py +25 -22
  84. waldiez/models/agents/agent/code_execution.py +9 -11
  85. waldiez/models/agents/agent/termination_message.py +10 -12
  86. waldiez/models/agents/agent/update_system_message.py +2 -4
  87. waldiez/models/agents/agents.py +8 -8
  88. waldiez/models/agents/assistant/assistant.py +6 -3
  89. waldiez/models/agents/assistant/assistant_data.py +2 -2
  90. waldiez/models/agents/captain/captain_agent.py +7 -4
  91. waldiez/models/agents/captain/captain_agent_data.py +5 -7
  92. waldiez/models/agents/doc_agent/doc_agent.py +7 -4
  93. waldiez/models/agents/doc_agent/doc_agent_data.py +9 -10
  94. waldiez/models/agents/doc_agent/rag_query_engine.py +10 -12
  95. waldiez/models/agents/extra_requirements.py +3 -3
  96. waldiez/models/agents/group_manager/group_manager.py +12 -7
  97. waldiez/models/agents/group_manager/group_manager_data.py +13 -12
  98. waldiez/models/agents/group_manager/speakers.py +17 -19
  99. waldiez/models/agents/rag_user_proxy/rag_user_proxy.py +7 -4
  100. waldiez/models/agents/rag_user_proxy/rag_user_proxy_data.py +4 -1
  101. waldiez/models/agents/rag_user_proxy/retrieve_config.py +69 -63
  102. waldiez/models/agents/rag_user_proxy/vector_db_config.py +19 -19
  103. waldiez/models/agents/reasoning/reasoning_agent.py +7 -4
  104. waldiez/models/agents/reasoning/reasoning_agent_data.py +3 -2
  105. waldiez/models/agents/reasoning/reasoning_agent_reason_config.py +8 -8
  106. waldiez/models/agents/user_proxy/user_proxy.py +6 -3
  107. waldiez/models/agents/user_proxy/user_proxy_data.py +1 -1
  108. waldiez/models/chat/chat.py +27 -20
  109. waldiez/models/chat/chat_data.py +22 -19
  110. waldiez/models/chat/chat_message.py +9 -9
  111. waldiez/models/chat/chat_nested.py +9 -9
  112. waldiez/models/chat/chat_summary.py +6 -6
  113. waldiez/models/common/__init__.py +2 -0
  114. waldiez/models/common/ag2_version.py +2 -0
  115. waldiez/models/common/dict_utils.py +8 -6
  116. waldiez/models/common/handoff.py +18 -17
  117. waldiez/models/common/method_utils.py +7 -7
  118. waldiez/models/common/naming.py +49 -0
  119. waldiez/models/flow/flow.py +11 -6
  120. waldiez/models/flow/flow_data.py +23 -17
  121. waldiez/models/flow/info.py +3 -3
  122. waldiez/models/flow/naming.py +2 -1
  123. waldiez/models/model/_aws.py +11 -13
  124. waldiez/models/model/_llm.py +5 -0
  125. waldiez/models/model/_price.py +2 -4
  126. waldiez/models/model/extra_requirements.py +1 -3
  127. waldiez/models/model/model.py +2 -2
  128. waldiez/models/model/model_data.py +21 -21
  129. waldiez/models/tool/extra_requirements.py +2 -4
  130. waldiez/models/tool/predefined/_duckduckgo.py +1 -0
  131. waldiez/models/tool/predefined/_email.py +1 -0
  132. waldiez/models/tool/predefined/_google.py +1 -0
  133. waldiez/models/tool/predefined/_perplexity.py +1 -0
  134. waldiez/models/tool/predefined/_searxng.py +1 -0
  135. waldiez/models/tool/predefined/_tavily.py +1 -0
  136. waldiez/models/tool/predefined/_wikipedia.py +1 -0
  137. waldiez/models/tool/predefined/_youtube.py +1 -0
  138. waldiez/models/tool/tool.py +8 -5
  139. waldiez/models/tool/tool_data.py +2 -2
  140. waldiez/models/waldiez.py +152 -4
  141. waldiez/runner.py +11 -5
  142. waldiez/running/async_utils.py +192 -0
  143. waldiez/running/base_runner.py +117 -264
  144. waldiez/running/dir_utils.py +52 -0
  145. waldiez/running/environment.py +10 -44
  146. waldiez/running/events_mixin.py +252 -0
  147. waldiez/running/exceptions.py +20 -0
  148. waldiez/running/gen_seq_diagram.py +18 -15
  149. waldiez/running/io_utils.py +216 -0
  150. waldiez/running/protocol.py +11 -5
  151. waldiez/running/requirements_mixin.py +65 -0
  152. waldiez/running/results_mixin.py +926 -0
  153. waldiez/running/standard_runner.py +22 -25
  154. waldiez/running/step_by_step/breakpoints_mixin.py +192 -60
  155. waldiez/running/step_by_step/command_handler.py +3 -0
  156. waldiez/running/step_by_step/events_processor.py +194 -14
  157. waldiez/running/step_by_step/step_by_step_models.py +110 -43
  158. waldiez/running/step_by_step/step_by_step_runner.py +107 -57
  159. waldiez/running/subprocess_runner/__base__.py +9 -1
  160. waldiez/running/subprocess_runner/_async_runner.py +5 -3
  161. waldiez/running/subprocess_runner/_sync_runner.py +6 -2
  162. waldiez/running/subprocess_runner/runner.py +39 -23
  163. waldiez/running/timeline_processor.py +1 -1
  164. waldiez/utils/__init__.py +2 -0
  165. waldiez/utils/conflict_checker.py +4 -4
  166. waldiez/utils/python_manager.py +415 -0
  167. waldiez/ws/_file_handler.py +18 -18
  168. waldiez/ws/_mock.py +2 -1
  169. waldiez/ws/cli.py +36 -12
  170. waldiez/ws/client_manager.py +35 -27
  171. waldiez/ws/errors.py +3 -0
  172. waldiez/ws/models.py +43 -52
  173. waldiez/ws/reloader.py +12 -4
  174. waldiez/ws/server.py +85 -55
  175. waldiez/ws/session_manager.py +8 -9
  176. waldiez/ws/session_stats.py +1 -1
  177. waldiez/ws/utils.py +4 -1
  178. {waldiez-0.6.0.dist-info → waldiez-0.6.1.dist-info}/METADATA +82 -93
  179. waldiez-0.6.1.dist-info/RECORD +254 -0
  180. waldiez/running/post_run.py +0 -186
  181. waldiez/running/pre_run.py +0 -281
  182. waldiez/running/run_results.py +0 -14
  183. waldiez/running/utils.py +0 -625
  184. waldiez-0.6.0.dist-info/RECORD +0 -251
  185. {waldiez-0.6.0.dist-info → waldiez-0.6.1.dist-info}/WHEEL +0 -0
  186. {waldiez-0.6.0.dist-info → waldiez-0.6.1.dist-info}/entry_points.txt +0 -0
  187. {waldiez-0.6.0.dist-info → waldiez-0.6.1.dist-info}/licenses/LICENSE +0 -0
  188. {waldiez-0.6.0.dist-info → waldiez-0.6.1.dist-info}/licenses/NOTICE.md +0 -0
@@ -1,10 +1,11 @@
1
1
  # SPDX-License-Identifier: Apache-2.0.
2
2
  # Copyright (c) 2024 - 2025 Waldiez and contributors.
3
3
 
4
- # pylint: disable=line-too-long
4
+ # pylint: disable=duplicate-code,line-too-long
5
5
  # pyright: reportUnknownMemberType=false, reportAttributeAccessIssue=false
6
6
  # pyright: reportUnknownArgumentType=false, reportOptionalMemberAccess=false
7
- # pylint: disable=duplicate-code
7
+ # pyright: reportDeprecated=false, reportMissingTypeStubs=false
8
+ # pyright: reportUnsafeMultipleInheritance=false
8
9
  # flake8: noqa: E501
9
10
 
10
11
  """Step-by-step Waldiez runner with user interaction capabilities."""
@@ -12,20 +13,23 @@
12
13
  import asyncio
13
14
  import threading
14
15
  import traceback
16
+ import uuid
15
17
  from collections import deque
18
+ from collections.abc import Iterable
16
19
  from pathlib import Path
17
- from typing import TYPE_CHECKING, Any, Iterable, Union
20
+ from typing import TYPE_CHECKING, Any, Union
18
21
 
19
22
  from pydantic import ValidationError
23
+ from typing_extensions import override
20
24
 
21
- from waldiez.io.utils import DEBUG_INPUT_PROMPT, gen_id
22
25
  from waldiez.models.waldiez import Waldiez
23
26
  from waldiez.running.step_by_step.command_handler import CommandHandler
24
27
  from waldiez.running.step_by_step.events_processor import EventProcessor
25
28
 
26
29
  from ..base_runner import WaldiezBaseRunner
30
+ from ..events_mixin import EventsMixin
27
31
  from ..exceptions import StopRunningException
28
- from ..run_results import WaldiezRunResults
32
+ from ..results_mixin import WaldiezRunResults
29
33
  from .breakpoints_mixin import BreakpointsMixin
30
34
  from .step_by_step_models import (
31
35
  VALID_CONTROL_COMMANDS,
@@ -40,10 +44,10 @@ from .step_by_step_models import (
40
44
  )
41
45
 
42
46
  if TYPE_CHECKING:
47
+ from autogen.agentchat import ConversableAgent # type: ignore
43
48
  from autogen.events import BaseEvent # type: ignore
44
49
  from autogen.messages import BaseMessage # type: ignore
45
50
 
46
-
47
51
  MESSAGES = {
48
52
  "workflow_starting": "<Waldiez step-by-step> - Starting workflow...",
49
53
  "workflow_finished": "<Waldiez step-by-step> - Workflow finished",
@@ -52,6 +56,21 @@ MESSAGES = {
52
56
  "<Waldiez step-by-step> - Workflow execution failed: {error}"
53
57
  ),
54
58
  }
59
+ DEBUG_INPUT_PROMPT = (
60
+ # cspell: disable-next-line
61
+ "[Step] (c)ontinue, (r)un, (q)uit, (i)nfo, (h)elp, (st)ats: "
62
+ )
63
+
64
+
65
+ def gen_id() -> str:
66
+ """Generate a new id.
67
+
68
+ Returns
69
+ -------
70
+ str
71
+ The new id.
72
+ """
73
+ return str(uuid.uuid4())
55
74
 
56
75
 
57
76
  # pylint: disable=too-many-instance-attributes
@@ -67,7 +86,7 @@ class WaldiezStepByStepRunner(WaldiezBaseRunner, BreakpointsMixin):
67
86
  structured_io: bool = False,
68
87
  dot_env: str | Path | None = None,
69
88
  auto_continue: bool = False,
70
- breakpoints: Iterable[str] | None = None,
89
+ breakpoints: Iterable[Any] | None = None,
71
90
  config: WaldiezDebugConfig | None = None,
72
91
  **kwargs: Any,
73
92
  ) -> None:
@@ -80,7 +99,8 @@ class WaldiezStepByStepRunner(WaldiezBaseRunner, BreakpointsMixin):
80
99
  dot_env=dot_env,
81
100
  **kwargs,
82
101
  )
83
- BreakpointsMixin.__init__(self)
102
+ BreakpointsMixin.__init__(self, config=config)
103
+ self.set_agent_id_to_name(waldiez.flow.unique_names["agent_names"])
84
104
 
85
105
  # Configuration
86
106
  self._config = config or WaldiezDebugConfig()
@@ -112,6 +132,8 @@ class WaldiezStepByStepRunner(WaldiezBaseRunner, BreakpointsMixin):
112
132
  # Command handling
113
133
  self._command_handler = CommandHandler(self)
114
134
  self._event_processor = EventProcessor(self)
135
+ auto_run = self.is_auto_run()
136
+ self._config.auto_continue = auto_run
115
137
 
116
138
  @property
117
139
  def auto_continue(self) -> bool:
@@ -207,19 +229,6 @@ class WaldiezStepByStepRunner(WaldiezBaseRunner, BreakpointsMixin):
207
229
  """Get the maximum event history size."""
208
230
  return self._config.max_event_history
209
231
 
210
- @staticmethod
211
- def print(*args: Any, **kwargs: Any) -> None:
212
- """Print method.
213
-
214
- Parameters
215
- ----------
216
- *args : Any
217
- Positional arguments to print.
218
- **kwargs : Any
219
- Keyword arguments to print.
220
- """
221
- WaldiezBaseRunner.print(*args, **kwargs)
222
-
223
232
  def add_to_history(self, event_info: dict[str, Any]) -> None:
224
233
  """Add an event to the history.
225
234
 
@@ -259,6 +268,7 @@ class WaldiezStepByStepRunner(WaldiezBaseRunner, BreakpointsMixin):
259
268
  self.emit(WaldiezDebugEventInfo(event=event_info))
260
269
 
261
270
  # noinspection PyTypeHints
271
+ @override
262
272
  def emit(self, message: WaldiezDebugMessage) -> None:
263
273
  """Emit a debug message.
264
274
 
@@ -463,16 +473,25 @@ class WaldiezStepByStepRunner(WaldiezBaseRunner, BreakpointsMixin):
463
473
 
464
474
  return self._command_handler.handle_command(user_input or "")
465
475
 
466
- def _get_user_action(self) -> WaldiezDebugStepAction:
467
- """Get user action with timeout support."""
476
+ def _get_user_action(self, force: bool) -> WaldiezDebugStepAction:
477
+ """Get user action with timeout support.
478
+
479
+ Parameters
480
+ ----------
481
+ force : bool
482
+ Force getting the user's action, even if in auto-run mode.
483
+ """
468
484
  if self._config.auto_continue:
469
485
  self.step_mode = True
470
- return WaldiezDebugStepAction.CONTINUE
471
-
486
+ if force:
487
+ self._config.auto_continue = False
488
+ else:
489
+ return WaldiezDebugStepAction.CONTINUE
472
490
  while True:
473
491
  request_id = gen_id()
474
492
  try:
475
493
  if not self.structured_io:
494
+ # if structured, we already do this (print the prompt)
476
495
  self.emit(
477
496
  WaldiezDebugInputRequest(
478
497
  prompt=DEBUG_INPUT_PROMPT, request_id=request_id
@@ -481,8 +500,9 @@ class WaldiezStepByStepRunner(WaldiezBaseRunner, BreakpointsMixin):
481
500
  except Exception as e: # pylint: disable=broad-exception-caught
482
501
  self.log.warning("Failed to emit input request: %s", e)
483
502
  try:
484
- user_input = WaldiezBaseRunner.get_user_input(
485
- DEBUG_INPUT_PROMPT
503
+ user_input = EventsMixin.get_user_input(
504
+ DEBUG_INPUT_PROMPT,
505
+ request_id=request_id,
486
506
  ).strip()
487
507
  return self._parse_user_action(
488
508
  user_input, request_id=request_id
@@ -492,11 +512,14 @@ class WaldiezStepByStepRunner(WaldiezBaseRunner, BreakpointsMixin):
492
512
  self._stop_requested.set()
493
513
  return WaldiezDebugStepAction.QUIT
494
514
 
495
- async def _a_get_user_action(self) -> WaldiezDebugStepAction:
515
+ async def _a_get_user_action(self, force: bool) -> WaldiezDebugStepAction:
496
516
  """Get user action asynchronously."""
497
517
  if self._config.auto_continue:
498
518
  self.step_mode = True
499
- return WaldiezDebugStepAction.CONTINUE
519
+ if force:
520
+ self._config.auto_continue = False
521
+ else:
522
+ return WaldiezDebugStepAction.CONTINUE
500
523
 
501
524
  while True:
502
525
  request_id = gen_id()
@@ -508,7 +531,7 @@ class WaldiezStepByStepRunner(WaldiezBaseRunner, BreakpointsMixin):
508
531
  )
509
532
  )
510
533
 
511
- user_input = await WaldiezBaseRunner.a_get_user_input(
534
+ user_input = await EventsMixin.a_get_user_input(
512
535
  DEBUG_INPUT_PROMPT
513
536
  )
514
537
  user_input = user_input.strip()
@@ -519,36 +542,45 @@ class WaldiezStepByStepRunner(WaldiezBaseRunner, BreakpointsMixin):
519
542
  except (KeyboardInterrupt, EOFError):
520
543
  return WaldiezDebugStepAction.QUIT
521
544
 
522
- def _handle_step_interaction(self) -> bool:
523
- """Handle step-by-step user interaction."""
545
+ def _handle_step_interaction(self, force: bool) -> bool:
546
+ """Handle step-by-step user interaction.
547
+
548
+ Parameters
549
+ ----------
550
+ force : bool
551
+ Force getting the user's action, even if in auto-run mode.
552
+ """
524
553
  while True:
525
- action = self._get_user_action()
554
+ action = self._get_user_action(force)
526
555
  if action in (
527
556
  WaldiezDebugStepAction.CONTINUE,
528
557
  WaldiezDebugStepAction.STEP,
529
558
  ):
530
559
  return True
531
560
  if action == WaldiezDebugStepAction.RUN:
561
+ self._config.auto_continue = True
532
562
  return True
533
563
  if action == WaldiezDebugStepAction.QUIT:
534
564
  return False
535
565
  # For other actions (info, help, etc.), continue the loop
536
566
 
537
- async def _a_handle_step_interaction(self) -> bool:
567
+ async def _a_handle_step_interaction(self, force: bool) -> bool:
538
568
  """Handle step-by-step user interaction asynchronously."""
539
569
  while True:
540
- action = await self._a_get_user_action()
570
+ action = await self._a_get_user_action(force)
541
571
  if action in (
542
572
  WaldiezDebugStepAction.CONTINUE,
543
573
  WaldiezDebugStepAction.STEP,
544
574
  ):
545
575
  return True
546
576
  if action == WaldiezDebugStepAction.RUN:
577
+ self._config.auto_continue = True
547
578
  return True
548
579
  if action == WaldiezDebugStepAction.QUIT:
549
580
  return False
550
581
  # For other actions (info, help, etc.), continue the loop
551
582
 
583
+ @override
552
584
  def _run(
553
585
  self,
554
586
  temp_dir: Path,
@@ -586,9 +618,9 @@ class WaldiezStepByStepRunner(WaldiezBaseRunner, BreakpointsMixin):
586
618
  else:
587
619
  stream = IOStream.get_default()
588
620
 
589
- WaldiezBaseRunner._print = stream.print
590
- WaldiezBaseRunner._input = stream.input
591
- WaldiezBaseRunner._send = stream.send
621
+ EventsMixin.set_print_function(stream.print)
622
+ EventsMixin.set_input_function(stream.input)
623
+ EventsMixin.set_send_function(stream.send)
592
624
 
593
625
  self.print(MESSAGES["workflow_starting"])
594
626
  self.print(self.waldiez.info.model_dump_json())
@@ -608,30 +640,44 @@ class WaldiezStepByStepRunner(WaldiezBaseRunner, BreakpointsMixin):
608
640
 
609
641
  return results_container["results"]
610
642
 
611
- def _on_event(self, event: Union["BaseEvent", "BaseMessage"]) -> bool:
643
+ def _re_emit_if_needed(self, event_info: dict[str, Any]) -> None:
644
+ # emit again if type is text, swapping the sender and without recipient
645
+ if event_info.get("type", "") == "text":
646
+ event_info["sender"] = event_info["recipient"]
647
+ event_info["recipient"] = None
648
+ event_info["agents"]["sender"] = event_info["agents"]["recipient"]
649
+ event_info["agents"]["recipient"] = None
650
+ self.emit_event(event_info)
651
+
652
+ def _on_event(
653
+ self,
654
+ event: Union["BaseEvent", "BaseMessage"],
655
+ agents: list["ConversableAgent"],
656
+ ) -> bool:
612
657
  """Process an event with step-by-step debugging."""
613
658
  # pylint: disable=too-many-try-statements,broad-exception-caught
614
659
  try:
615
660
  # Use the event processor for core logic
616
- result = self._event_processor.process_event(event)
661
+ result = self._event_processor.process_event(event, agents)
617
662
 
618
663
  if result["action"] == "stop":
619
664
  self.log.debug(
620
665
  "Step-by-step execution stopped before event processing"
621
666
  )
622
667
  return False
623
- self.emit_event(result["event_info"])
668
+ event_info = result["event_info"]
669
+ self.emit_event(event_info)
624
670
  # Handle breakpoint logic
625
- if result["should_break"]:
626
- if not self._handle_step_interaction():
671
+ if result["action"] == "break":
672
+ if not self._handle_step_interaction(force=True):
627
673
  self._stop_requested.set()
628
674
  if hasattr(event, "type") and event.type == "input_request":
629
675
  event.content.respond("exit")
630
676
  return True
631
677
  raise StopRunningException(StopRunningException.reason)
632
-
678
+ self._re_emit_if_needed(event_info)
633
679
  # Process the actual event
634
- WaldiezBaseRunner.process_event(event, skip_send=True)
680
+ EventsMixin.process_event(event, agents, skip_send=True)
635
681
  self._processed_events += 1
636
682
 
637
683
  except Exception as e:
@@ -644,6 +690,7 @@ class WaldiezStepByStepRunner(WaldiezBaseRunner, BreakpointsMixin):
644
690
  return not self._stop_requested.is_set()
645
691
 
646
692
  # pylint: disable=too-complex
693
+ @override
647
694
  async def _a_run(
648
695
  self,
649
696
  temp_dir: Path,
@@ -657,7 +704,7 @@ class WaldiezStepByStepRunner(WaldiezBaseRunner, BreakpointsMixin):
657
704
 
658
705
  async def _execute_workflow() -> list[dict[str, Any]]:
659
706
  # pylint: disable=import-outside-toplevel
660
- from autogen.io import IOStream # pyright: ignore
707
+ from autogen.io import IOStream
661
708
 
662
709
  from waldiez.io import StructuredIOStream
663
710
 
@@ -677,9 +724,9 @@ class WaldiezStepByStepRunner(WaldiezBaseRunner, BreakpointsMixin):
677
724
  else:
678
725
  stream = IOStream.get_default()
679
726
 
680
- WaldiezBaseRunner._print = stream.print
681
- WaldiezBaseRunner._input = stream.input
682
- WaldiezBaseRunner._send = stream.send
727
+ EventsMixin.set_print_function(stream.print)
728
+ EventsMixin.set_input_function(stream.input)
729
+ EventsMixin.set_send_function(stream.send)
683
730
 
684
731
  self.print(MESSAGES["workflow_starting"])
685
732
  self.print(self.waldiez.info.model_dump_json())
@@ -713,31 +760,34 @@ class WaldiezStepByStepRunner(WaldiezBaseRunner, BreakpointsMixin):
713
760
  return []
714
761
 
715
762
  async def _a_on_event(
716
- self, event: Union["BaseEvent", "BaseMessage"]
763
+ self,
764
+ event: Union["BaseEvent", "BaseMessage"],
765
+ agents: list["ConversableAgent"],
717
766
  ) -> bool:
718
767
  """Process an event with step-by-step debugging asynchronously."""
719
768
  # pylint: disable=too-many-try-statements,broad-exception-caught
720
769
  try:
721
770
  # Use the event processor for core logic
722
- result = self._event_processor.process_event(event)
771
+ result = self._event_processor.process_event(event, agents)
723
772
 
724
773
  if result["action"] == "stop":
725
774
  self.log.debug(
726
775
  "Async step-by-step execution stopped before event processing"
727
776
  )
728
777
  return False
729
- self.emit_event(result["event_info"])
778
+ event_info = result["event_info"]
779
+ self.emit_event(event_info)
730
780
  # Handle breakpoint logic
731
- if result["should_break"]:
732
- if not await self._a_handle_step_interaction():
781
+ if result["action"] == "break":
782
+ if not await self._a_handle_step_interaction(force=True):
733
783
  self._stop_requested.set()
734
784
  if hasattr(event, "type") and event.type == "input_request":
735
- event.content.respond("exit")
785
+ await event.content.respond("exit")
736
786
  return True
737
787
  raise StopRunningException(StopRunningException.reason)
738
-
788
+ self._re_emit_if_needed(event_info)
739
789
  # Process the actual event
740
- await WaldiezBaseRunner.a_process_event(event, skip_send=True)
790
+ await EventsMixin.a_process_event(event, agents, skip_send=True)
741
791
  self._processed_events += 1
742
792
 
743
793
  except Exception as e:
@@ -50,6 +50,10 @@ class BaseSubprocessRunner:
50
50
  self.dot_env = dot_env
51
51
  self.logger = logger or logging.getLogger(self.__class__.__name__)
52
52
  self.waiting_for_input = False
53
+ breakpoints = kwargs.get("breakpoints", [])
54
+ if not isinstance(breakpoints, list):
55
+ breakpoints = []
56
+ self.breakpoints: list[str] = breakpoints
53
57
 
54
58
  def build_command(
55
59
  self,
@@ -110,6 +114,10 @@ class BaseSubprocessRunner:
110
114
  if self.dot_env:
111
115
  cmd.extend(["--dot-env", str(self.dot_env)])
112
116
 
117
+ if self.breakpoints:
118
+ for entry in self.breakpoints:
119
+ cmd.extend(["--breakpoints", entry])
120
+ self.logger.debug("Runner command: %s", " ".join(cmd))
113
121
  return cmd
114
122
 
115
123
  def parse_output(
@@ -138,7 +146,7 @@ class BaseSubprocessRunner:
138
146
  try:
139
147
  data = json.loads(line)
140
148
  if isinstance(data, dict):
141
- return data # pyright: ignore
149
+ return data # pyright: ignore[reportUnknownVariableType]
142
150
  except json.JSONDecodeError:
143
151
  return self.create_output_message(stream=stream, content=line)
144
152
  return self.create_output_message(
@@ -10,8 +10,9 @@ import logging
10
10
 
11
11
  # noinspection PyProtectedMember
12
12
  from asyncio.subprocess import Process as AsyncProcess
13
+ from collections.abc import Coroutine
13
14
  from pathlib import Path
14
- from typing import Any, Callable, Coroutine, Literal, Optional
15
+ from typing import Any, Callable, Literal
15
16
 
16
17
  from .__base__ import BaseSubprocessRunner
17
18
 
@@ -57,7 +58,7 @@ class AsyncSubprocessRunner(BaseSubprocessRunner):
57
58
  )
58
59
  self.on_output = on_output
59
60
  self.on_input_request = on_input_request
60
- self.process: Optional[AsyncProcess] = None
61
+ self.process: AsyncProcess | None = None
61
62
  self.input_queue: asyncio.Queue[str] = asyncio.Queue()
62
63
  self.output_queue: asyncio.Queue[dict[str, Any]] = asyncio.Queue()
63
64
  self._monitor_tasks: list[asyncio.Task[Any]] = []
@@ -158,7 +159,8 @@ class AsyncSubprocessRunner(BaseSubprocessRunner):
158
159
  await self.process.wait()
159
160
 
160
161
  except Exception as e:
161
- self.logger.error(f"Error stopping subprocess: {e}")
162
+ if not isinstance(e, AttributeError):
163
+ self.logger.error(f"Error stopping subprocess: {e}")
162
164
  self.process = None
163
165
 
164
166
  async def _start_monitoring(self) -> None:
@@ -93,6 +93,7 @@ class SyncSubprocessRunner(BaseSubprocessRunner):
93
93
  stdin=subprocess.PIPE,
94
94
  stdout=subprocess.PIPE,
95
95
  stderr=subprocess.PIPE,
96
+ encoding="utf-8",
96
97
  text=True,
97
98
  bufsize=1, # Line buffered
98
99
  )
@@ -351,8 +352,9 @@ class SyncSubprocessRunner(BaseSubprocessRunner):
351
352
  f"Thread {thread.name} did not stop gracefully"
352
353
  )
353
354
 
355
+ # pylint: disable=too-complex
354
356
  # noinspection TryExceptPass,PyBroadException
355
- def _cleanup_process(self) -> None:
357
+ def _cleanup_process(self) -> None: # noqa: C901
356
358
  """Cleanup process resources."""
357
359
  if self.process:
358
360
  if self.process.stdin:
@@ -381,7 +383,9 @@ class SyncSubprocessRunner(BaseSubprocessRunner):
381
383
  self.process.kill()
382
384
  self.process.wait()
383
385
  except BaseException as e:
384
- self.logger.error(f"Error stopping subprocess: {e}")
386
+ if not isinstance(e, AttributeError):
387
+ # already "None"
388
+ self.logger.error(f"Error stopping subprocess: {e}")
385
389
  finally:
386
390
  self.process = None
387
391
 
@@ -1,5 +1,7 @@
1
1
  # SPDX-License-Identifier: Apache-2.0.
2
2
  # Copyright (c) 2024 - 2025 Waldiez and contributors.
3
+
4
+ # pyright: reportAttributeAccessIssue=false,reportUnknownArgumentType=false
3
5
  # flake8: noqa: G004
4
6
  """Waldiez subprocess runner that inherits from BaseRunner."""
5
7
 
@@ -8,15 +10,15 @@ import re
8
10
  from pathlib import Path
9
11
  from typing import Any, Callable, Literal
10
12
 
13
+ from typing_extensions import override
14
+
11
15
  from waldiez.models import Waldiez
12
16
 
13
17
  from ..base_runner import WaldiezBaseRunner
18
+ from ..step_by_step.breakpoints_mixin import BreakpointsMixin
14
19
  from ._async_runner import AsyncSubprocessRunner
15
20
  from ._sync_runner import SyncSubprocessRunner
16
21
 
17
- # TODO: check output directory and return the results from the JSON logs
18
- # in self._run and self._a_run
19
-
20
22
 
21
23
  # noinspection PyUnusedLocal
22
24
  class WaldiezSubprocessRunner(WaldiezBaseRunner):
@@ -71,6 +73,7 @@ class WaldiezSubprocessRunner(WaldiezBaseRunner):
71
73
  dot_env=dot_env,
72
74
  **kwargs,
73
75
  )
76
+ self.breakpoints = self._parse_breakpoints(**kwargs)
74
77
 
75
78
  # Store callbacks
76
79
  self.sync_on_output = on_output or self._default_sync_output
@@ -106,17 +109,27 @@ class WaldiezSubprocessRunner(WaldiezBaseRunner):
106
109
  # noinspection RegExpRedundantEscape
107
110
  file_name = re.sub(r"[^a-zA-Z0-9_\-\.]", "_", file_name)[:30]
108
111
  file_name = f"{file_name}.waldiez"
109
- with open(file_name, "w", encoding="utf-8") as f:
112
+ with open(file_name, "w", encoding="utf-8", newline="\n") as f:
110
113
  f.write(self.waldiez.model_dump_json())
111
114
  return Path(file_name).resolve()
112
115
 
116
+ @staticmethod
117
+ def _parse_breakpoints(**kwargs: Any) -> list[str]:
118
+ initial_breakpoints = kwargs.get("breakpoints")
119
+ if isinstance(initial_breakpoints, (list, set, tuple)):
120
+ breakpoints = BreakpointsMixin.get_initial_breakpoints(
121
+ initial_breakpoints
122
+ )
123
+ return [str(item) for item in breakpoints]
124
+ return []
125
+
113
126
  def _default_sync_output(self, data: dict[str, Any]) -> None:
114
127
  """Get the default sync output handler."""
115
128
  if data.get("type") == "error":
116
129
  self.log.error(data.get("data", ""))
117
130
  else:
118
131
  content = data.get("data", data)
119
- self._print(content)
132
+ self.print(content)
120
133
 
121
134
  def _default_sync_input_request(self, prompt: str) -> None:
122
135
  """Get the default sync input request handler."""
@@ -141,6 +154,7 @@ class WaldiezSubprocessRunner(WaldiezBaseRunner):
141
154
  uploads_root=self.uploads_root,
142
155
  dot_env=self.dot_env_path,
143
156
  logger=self.log,
157
+ breakpoints=self.breakpoints,
144
158
  )
145
159
  return self.async_runner
146
160
 
@@ -153,9 +167,11 @@ class WaldiezSubprocessRunner(WaldiezBaseRunner):
153
167
  uploads_root=self.uploads_root,
154
168
  dot_env=self.dot_env_path,
155
169
  logger=self.log,
170
+ breakpoints=self.breakpoints,
156
171
  )
157
172
  return self.sync_runner
158
173
 
174
+ @override
159
175
  def run(
160
176
  self,
161
177
  output_path: str | Path | None = None,
@@ -220,6 +236,7 @@ class WaldiezSubprocessRunner(WaldiezBaseRunner):
220
236
  return _output_path / f"{filename}.py"
221
237
  return self._waldiez_file.with_suffix(".py")
222
238
 
239
+ @override
223
240
  def _run(
224
241
  self,
225
242
  temp_dir: Path,
@@ -242,14 +259,8 @@ class WaldiezSubprocessRunner(WaldiezBaseRunner):
242
259
  runner = self._create_sync_subprocess_runner()
243
260
 
244
261
  # Run subprocess
245
- success = runner.run_subprocess(self._waldiez_file, mode=self.mode)
246
- return [
247
- {
248
- "success": success,
249
- "runner": "sync_subprocess",
250
- "mode": self.mode,
251
- }
252
- ]
262
+ runner.run_subprocess(self._waldiez_file, mode=self.mode)
263
+ return self.read_from_output(output_file.parent)
253
264
 
254
265
  except Exception as e:
255
266
  self.log.error("Error in sync subprocess execution: %s", e)
@@ -261,6 +272,7 @@ class WaldiezSubprocessRunner(WaldiezBaseRunner):
261
272
  }
262
273
  ]
263
274
 
275
+ @override
264
276
  async def a_run(
265
277
  self,
266
278
  output_path: str | Path | None = None,
@@ -311,6 +323,7 @@ class WaldiezSubprocessRunner(WaldiezBaseRunner):
311
323
  **kwargs,
312
324
  )
313
325
 
326
+ @override
314
327
  async def _a_run(
315
328
  self,
316
329
  temp_dir: Path,
@@ -356,17 +369,11 @@ class WaldiezSubprocessRunner(WaldiezBaseRunner):
356
369
  runner = self._create_async_subprocess_runner()
357
370
 
358
371
  # Run subprocess
359
- success = await runner.run_subprocess(
372
+ await runner.run_subprocess(
360
373
  self._waldiez_file,
361
374
  mode=self.mode,
362
375
  )
363
- return [
364
- {
365
- "success": success,
366
- "runner": "async_subprocess",
367
- "mode": self.mode,
368
- }
369
- ]
376
+ return await self.a_read_from_output(output_file.parent)
370
377
 
371
378
  except Exception as e:
372
379
  self.log.error("Error in async subprocess execution: %s", e)
@@ -408,6 +415,7 @@ class WaldiezSubprocessRunner(WaldiezBaseRunner):
408
415
  self.sync_runner.provide_user_input, user_input
409
416
  )
410
417
 
418
+ @override
411
419
  def stop(self) -> None:
412
420
  """Stop the workflow execution."""
413
421
  super().stop() # Set the base runner stop flag
@@ -461,9 +469,11 @@ class WaldiezSubprocessRunner(WaldiezBaseRunner):
461
469
  self.sync_runner.stop()
462
470
  self.sync_runner = None
463
471
 
472
+ @override
464
473
  def _after_run(
465
474
  self,
466
475
  results: list[dict[str, Any]],
476
+ error: BaseException | None,
467
477
  output_file: Path,
468
478
  waldiez_file: Path,
469
479
  uploads_root: Path | None,
@@ -476,7 +486,9 @@ class WaldiezSubprocessRunner(WaldiezBaseRunner):
476
486
  Parameters
477
487
  ----------
478
488
  results : list[dict[str, Any]]
479
- Results from the workflow execution
489
+ Results from the workflow execution.
490
+ error : BaseException | None
491
+ Optional error during the run.
480
492
  output_file : Path
481
493
  Output file path
482
494
  waldiez_file : Path
@@ -493,9 +505,11 @@ class WaldiezSubprocessRunner(WaldiezBaseRunner):
493
505
  # Cleanup subprocess runners
494
506
  self._cleanup_subprocess_runners()
495
507
 
508
+ @override
496
509
  async def _a_after_run(
497
510
  self,
498
511
  results: list[dict[str, Any]],
512
+ error: BaseException | None,
499
513
  output_file: Path,
500
514
  waldiez_file: Path,
501
515
  uploads_root: Path | None,
@@ -508,7 +522,9 @@ class WaldiezSubprocessRunner(WaldiezBaseRunner):
508
522
  Parameters
509
523
  ----------
510
524
  results : list[dict[str, Any]]
511
- Results from the workflow execution
525
+ Results from the workflow execution.
526
+ error : BaseException | None
527
+ Optional error during the run.
512
528
  output_file : Path
513
529
  Output file path
514
530
  waldiez_file : Path
@@ -93,7 +93,7 @@ class TimelineProcessor:
93
93
  bool
94
94
  True if the value is missing, NaN, or empty; False otherwise.
95
95
  """
96
- if pd.isna(value): # pyright: ignore
96
+ if pd.isna(value):
97
97
  return True
98
98
  if isinstance(value, str) and (
99
99
  value.strip() == "" or value.lower() == "nan"