autobyteus 1.1.4__py3-none-any.whl → 1.1.5__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 (167) hide show
  1. autobyteus/agent/context/__init__.py +4 -2
  2. autobyteus/agent/context/agent_config.py +0 -4
  3. autobyteus/agent/context/agent_context_registry.py +73 -0
  4. autobyteus/agent/events/notifiers.py +4 -0
  5. autobyteus/agent/handlers/inter_agent_message_event_handler.py +7 -2
  6. autobyteus/agent/handlers/llm_complete_response_received_event_handler.py +19 -19
  7. autobyteus/agent/handlers/user_input_message_event_handler.py +15 -0
  8. autobyteus/agent/message/send_message_to.py +29 -23
  9. autobyteus/agent/runtime/agent_runtime.py +10 -2
  10. autobyteus/agent/sender_type.py +15 -0
  11. autobyteus/agent/streaming/agent_event_stream.py +6 -0
  12. autobyteus/agent/streaming/stream_event_payloads.py +12 -0
  13. autobyteus/agent/streaming/stream_events.py +3 -0
  14. autobyteus/agent/system_prompt_processor/tool_manifest_injector_processor.py +7 -4
  15. autobyteus/agent_team/__init__.py +1 -0
  16. autobyteus/agent_team/agent_team.py +93 -0
  17. autobyteus/agent_team/agent_team_builder.py +184 -0
  18. autobyteus/agent_team/base_agent_team.py +86 -0
  19. autobyteus/agent_team/bootstrap_steps/__init__.py +24 -0
  20. autobyteus/agent_team/bootstrap_steps/agent_configuration_preparation_step.py +73 -0
  21. autobyteus/agent_team/bootstrap_steps/agent_team_bootstrapper.py +54 -0
  22. autobyteus/agent_team/bootstrap_steps/agent_team_runtime_queue_initialization_step.py +25 -0
  23. autobyteus/agent_team/bootstrap_steps/base_agent_team_bootstrap_step.py +23 -0
  24. autobyteus/agent_team/bootstrap_steps/coordinator_initialization_step.py +41 -0
  25. autobyteus/agent_team/bootstrap_steps/coordinator_prompt_preparation_step.py +85 -0
  26. autobyteus/agent_team/bootstrap_steps/task_notifier_initialization_step.py +51 -0
  27. autobyteus/agent_team/bootstrap_steps/team_context_initialization_step.py +45 -0
  28. autobyteus/agent_team/context/__init__.py +17 -0
  29. autobyteus/agent_team/context/agent_team_config.py +33 -0
  30. autobyteus/agent_team/context/agent_team_context.py +61 -0
  31. autobyteus/agent_team/context/agent_team_runtime_state.py +56 -0
  32. autobyteus/agent_team/context/team_manager.py +147 -0
  33. autobyteus/agent_team/context/team_node_config.py +76 -0
  34. autobyteus/agent_team/events/__init__.py +29 -0
  35. autobyteus/agent_team/events/agent_team_event_dispatcher.py +39 -0
  36. autobyteus/agent_team/events/agent_team_events.py +53 -0
  37. autobyteus/agent_team/events/agent_team_input_event_queue_manager.py +21 -0
  38. autobyteus/agent_team/exceptions.py +8 -0
  39. autobyteus/agent_team/factory/__init__.py +9 -0
  40. autobyteus/agent_team/factory/agent_team_factory.py +99 -0
  41. autobyteus/agent_team/handlers/__init__.py +19 -0
  42. autobyteus/agent_team/handlers/agent_team_event_handler_registry.py +23 -0
  43. autobyteus/agent_team/handlers/base_agent_team_event_handler.py +16 -0
  44. autobyteus/agent_team/handlers/inter_agent_message_request_event_handler.py +61 -0
  45. autobyteus/agent_team/handlers/lifecycle_agent_team_event_handler.py +27 -0
  46. autobyteus/agent_team/handlers/process_user_message_event_handler.py +46 -0
  47. autobyteus/agent_team/handlers/tool_approval_team_event_handler.py +48 -0
  48. autobyteus/agent_team/phases/__init__.py +11 -0
  49. autobyteus/agent_team/phases/agent_team_operational_phase.py +19 -0
  50. autobyteus/agent_team/phases/agent_team_phase_manager.py +48 -0
  51. autobyteus/agent_team/runtime/__init__.py +13 -0
  52. autobyteus/agent_team/runtime/agent_team_runtime.py +82 -0
  53. autobyteus/agent_team/runtime/agent_team_worker.py +117 -0
  54. autobyteus/agent_team/shutdown_steps/__init__.py +17 -0
  55. autobyteus/agent_team/shutdown_steps/agent_team_shutdown_orchestrator.py +35 -0
  56. autobyteus/agent_team/shutdown_steps/agent_team_shutdown_step.py +42 -0
  57. autobyteus/agent_team/shutdown_steps/base_agent_team_shutdown_step.py +16 -0
  58. autobyteus/agent_team/shutdown_steps/bridge_cleanup_step.py +28 -0
  59. autobyteus/agent_team/shutdown_steps/sub_team_shutdown_step.py +41 -0
  60. autobyteus/agent_team/streaming/__init__.py +26 -0
  61. autobyteus/agent_team/streaming/agent_event_bridge.py +48 -0
  62. autobyteus/agent_team/streaming/agent_event_multiplexer.py +70 -0
  63. autobyteus/agent_team/streaming/agent_team_event_notifier.py +64 -0
  64. autobyteus/agent_team/streaming/agent_team_event_stream.py +33 -0
  65. autobyteus/agent_team/streaming/agent_team_stream_event_payloads.py +32 -0
  66. autobyteus/agent_team/streaming/agent_team_stream_events.py +56 -0
  67. autobyteus/agent_team/streaming/team_event_bridge.py +50 -0
  68. autobyteus/agent_team/task_notification/__init__.py +11 -0
  69. autobyteus/agent_team/task_notification/system_event_driven_agent_task_notifier.py +164 -0
  70. autobyteus/agent_team/task_notification/task_notification_mode.py +24 -0
  71. autobyteus/agent_team/utils/__init__.py +9 -0
  72. autobyteus/agent_team/utils/wait_for_idle.py +46 -0
  73. autobyteus/cli/agent_team_tui/__init__.py +4 -0
  74. autobyteus/cli/agent_team_tui/app.py +210 -0
  75. autobyteus/cli/agent_team_tui/state.py +180 -0
  76. autobyteus/cli/agent_team_tui/widgets/__init__.py +6 -0
  77. autobyteus/cli/agent_team_tui/widgets/agent_list_sidebar.py +149 -0
  78. autobyteus/cli/agent_team_tui/widgets/focus_pane.py +320 -0
  79. autobyteus/cli/agent_team_tui/widgets/logo.py +20 -0
  80. autobyteus/cli/agent_team_tui/widgets/renderables.py +77 -0
  81. autobyteus/cli/agent_team_tui/widgets/shared.py +60 -0
  82. autobyteus/cli/agent_team_tui/widgets/status_bar.py +14 -0
  83. autobyteus/cli/agent_team_tui/widgets/task_board_panel.py +82 -0
  84. autobyteus/events/event_types.py +7 -2
  85. autobyteus/llm/api/autobyteus_llm.py +11 -12
  86. autobyteus/llm/api/lmstudio_llm.py +10 -13
  87. autobyteus/llm/api/ollama_llm.py +8 -13
  88. autobyteus/llm/autobyteus_provider.py +73 -46
  89. autobyteus/llm/llm_factory.py +102 -140
  90. autobyteus/llm/lmstudio_provider.py +63 -48
  91. autobyteus/llm/models.py +83 -53
  92. autobyteus/llm/ollama_provider.py +69 -61
  93. autobyteus/llm/ollama_provider_resolver.py +1 -0
  94. autobyteus/llm/providers.py +13 -13
  95. autobyteus/llm/runtimes.py +11 -0
  96. autobyteus/task_management/__init__.py +43 -0
  97. autobyteus/task_management/base_task_board.py +68 -0
  98. autobyteus/task_management/converters/__init__.py +11 -0
  99. autobyteus/task_management/converters/task_board_converter.py +64 -0
  100. autobyteus/task_management/converters/task_plan_converter.py +48 -0
  101. autobyteus/task_management/deliverable.py +16 -0
  102. autobyteus/task_management/deliverables/__init__.py +8 -0
  103. autobyteus/task_management/deliverables/file_deliverable.py +15 -0
  104. autobyteus/task_management/events.py +27 -0
  105. autobyteus/task_management/in_memory_task_board.py +126 -0
  106. autobyteus/task_management/schemas/__init__.py +15 -0
  107. autobyteus/task_management/schemas/deliverable_schema.py +13 -0
  108. autobyteus/task_management/schemas/plan_definition.py +35 -0
  109. autobyteus/task_management/schemas/task_status_report.py +27 -0
  110. autobyteus/task_management/task_plan.py +110 -0
  111. autobyteus/task_management/tools/__init__.py +14 -0
  112. autobyteus/task_management/tools/get_task_board_status.py +68 -0
  113. autobyteus/task_management/tools/publish_task_plan.py +113 -0
  114. autobyteus/task_management/tools/update_task_status.py +135 -0
  115. autobyteus/tools/bash/bash_executor.py +59 -14
  116. autobyteus/tools/mcp/config_service.py +63 -58
  117. autobyteus/tools/mcp/server/http_managed_mcp_server.py +14 -2
  118. autobyteus/tools/mcp/server/stdio_managed_mcp_server.py +14 -2
  119. autobyteus/tools/mcp/server_instance_manager.py +30 -4
  120. autobyteus/tools/mcp/tool_registrar.py +103 -50
  121. autobyteus/tools/parameter_schema.py +17 -11
  122. autobyteus/tools/registry/tool_definition.py +24 -29
  123. autobyteus/tools/tool_category.py +1 -0
  124. autobyteus/tools/usage/formatters/default_json_example_formatter.py +78 -3
  125. autobyteus/tools/usage/formatters/default_xml_example_formatter.py +23 -3
  126. autobyteus/tools/usage/formatters/gemini_json_example_formatter.py +6 -0
  127. autobyteus/tools/usage/formatters/google_json_example_formatter.py +7 -0
  128. autobyteus/tools/usage/formatters/openai_json_example_formatter.py +6 -4
  129. autobyteus/tools/usage/parsers/gemini_json_tool_usage_parser.py +23 -7
  130. autobyteus/tools/usage/parsers/provider_aware_tool_usage_parser.py +14 -25
  131. autobyteus/tools/usage/providers/__init__.py +2 -12
  132. autobyteus/tools/usage/providers/tool_manifest_provider.py +36 -29
  133. autobyteus/tools/usage/registries/__init__.py +7 -12
  134. autobyteus/tools/usage/registries/tool_formatter_pair.py +15 -0
  135. autobyteus/tools/usage/registries/tool_formatting_registry.py +58 -0
  136. autobyteus/tools/usage/registries/tool_usage_parser_registry.py +55 -0
  137. {autobyteus-1.1.4.dist-info → autobyteus-1.1.5.dist-info}/METADATA +3 -3
  138. {autobyteus-1.1.4.dist-info → autobyteus-1.1.5.dist-info}/RECORD +146 -72
  139. examples/agent_team/__init__.py +1 -0
  140. examples/run_browser_agent.py +17 -15
  141. examples/run_google_slides_agent.py +17 -16
  142. examples/run_poem_writer.py +22 -12
  143. examples/run_sqlite_agent.py +17 -15
  144. autobyteus/tools/mcp/call_handlers/__init__.py +0 -16
  145. autobyteus/tools/mcp/call_handlers/base_handler.py +0 -40
  146. autobyteus/tools/mcp/call_handlers/stdio_handler.py +0 -76
  147. autobyteus/tools/mcp/call_handlers/streamable_http_handler.py +0 -55
  148. autobyteus/tools/usage/providers/json_example_provider.py +0 -32
  149. autobyteus/tools/usage/providers/json_schema_provider.py +0 -35
  150. autobyteus/tools/usage/providers/json_tool_usage_parser_provider.py +0 -28
  151. autobyteus/tools/usage/providers/xml_example_provider.py +0 -28
  152. autobyteus/tools/usage/providers/xml_schema_provider.py +0 -29
  153. autobyteus/tools/usage/providers/xml_tool_usage_parser_provider.py +0 -26
  154. autobyteus/tools/usage/registries/json_example_formatter_registry.py +0 -51
  155. autobyteus/tools/usage/registries/json_schema_formatter_registry.py +0 -51
  156. autobyteus/tools/usage/registries/json_tool_usage_parser_registry.py +0 -42
  157. autobyteus/tools/usage/registries/xml_example_formatter_registry.py +0 -30
  158. autobyteus/tools/usage/registries/xml_schema_formatter_registry.py +0 -33
  159. autobyteus/tools/usage/registries/xml_tool_usage_parser_registry.py +0 -30
  160. examples/workflow/__init__.py +0 -1
  161. examples/workflow/run_basic_research_workflow.py +0 -189
  162. examples/workflow/run_code_review_workflow.py +0 -269
  163. examples/workflow/run_debate_workflow.py +0 -212
  164. examples/workflow/run_workflow_with_tui.py +0 -153
  165. {autobyteus-1.1.4.dist-info → autobyteus-1.1.5.dist-info}/WHEEL +0 -0
  166. {autobyteus-1.1.4.dist-info → autobyteus-1.1.5.dist-info}/licenses/LICENSE +0 -0
  167. {autobyteus-1.1.4.dist-info → autobyteus-1.1.5.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,149 @@
1
+ # file: autobyteus/autobyteus/cli/agent_team_tui/widgets/agent_list_sidebar.py
2
+ """
3
+ Defines the sidebar widget that lists all nodes in the team hierarchy.
4
+ """
5
+ import logging
6
+ from typing import Dict, Any, Optional
7
+
8
+ from textual.message import Message
9
+ from textual.widgets import Static, Tree
10
+ from textual.widgets.tree import TreeNode
11
+ from textual.containers import Vertical
12
+
13
+ from autobyteus.agent.phases import AgentOperationalPhase
14
+ from autobyteus.agent_team.phases import AgentTeamOperationalPhase
15
+ from .shared import (
16
+ AGENT_PHASE_ICONS, TEAM_PHASE_ICONS, SUB_TEAM_ICON,
17
+ TEAM_ICON, SPEAKING_ICON, DEFAULT_ICON
18
+ )
19
+ from .logo import Logo
20
+
21
+ logger = logging.getLogger(__name__)
22
+
23
+ class AgentListSidebar(Static):
24
+ """A widget to display the hierarchical list of team nodes. This is a dumb
25
+ rendering component driven by the TUIStateStore."""
26
+
27
+ class NodeSelected(Message):
28
+ """Posted when any node is selected in the tree."""
29
+ def __init__(self, node_data: Dict[str, Any]) -> None:
30
+ self.node_data = node_data
31
+ super().__init__()
32
+
33
+ def __init__(self, *args, **kwargs) -> None:
34
+ super().__init__(*args, **kwargs)
35
+ self._node_map: Dict[str, TreeNode] = {} # Maps node names to TreeNode objects
36
+
37
+ def compose(self):
38
+ with Vertical():
39
+ yield Tree("Agent Team", id="agent-tree")
40
+ yield Logo()
41
+
42
+ def on_tree_node_selected(self, event: Tree.NodeSelected) -> None:
43
+ """Handle node selection from the tree."""
44
+ if event.node.data:
45
+ self.post_message(self.NodeSelected(event.node.data))
46
+ event.stop()
47
+
48
+ def _build_label(self, name: str, node_data: Dict, agent_phases: Dict, team_phases: Dict, speaking_agents: Dict) -> str:
49
+ """Constructs the display label for a tree node."""
50
+ node_type = node_data["type"]
51
+ icon = DEFAULT_ICON
52
+
53
+ if node_type == "agent":
54
+ phase = agent_phases.get(name, AgentOperationalPhase.UNINITIALIZED)
55
+ icon = SPEAKING_ICON if speaking_agents.get(name) else AGENT_PHASE_ICONS.get(phase, DEFAULT_ICON)
56
+ label = f"{icon} {name}"
57
+ elif node_type in ["team", "subteam"]:
58
+ phase = team_phases.get(name, AgentTeamOperationalPhase.UNINITIALIZED)
59
+ default_icon = TEAM_ICON if node_type == "team" else SUB_TEAM_ICON
60
+ icon = TEAM_PHASE_ICONS.get(phase, default_icon)
61
+ role = node_data.get("role")
62
+ label = f"{icon} {role or name}"
63
+ if role and role != name:
64
+ label += f" ({name})"
65
+ else:
66
+ label = f"{icon} {name}"
67
+
68
+ return label
69
+
70
+ def update_tree(self, tree_data: Dict, agent_phases: Dict[str, AgentOperationalPhase], team_phases: Dict[str, AgentTeamOperationalPhase], speaking_agents: Dict[str, bool]):
71
+ """
72
+ Performs an in-place update of the tree to reflect the new state,
73
+ avoiding a full rebuild for better performance and preserving UI state like expansion.
74
+ """
75
+ tree = self.query_one(Tree)
76
+
77
+ if not tree_data:
78
+ tree.root.set_label("Initializing agent team...")
79
+ return
80
+
81
+ root_name = list(tree_data.keys())[0]
82
+ root_node_data = tree_data[root_name]
83
+
84
+ # Kick off the recursive update from the root.
85
+ self._update_node_recursively(tree.root, root_node_data, agent_phases, team_phases, speaking_agents)
86
+
87
+ # Ensure the root is expanded on the first run.
88
+ if not tree.root.is_expanded:
89
+ tree.root.expand()
90
+
91
+ def _update_node_recursively(self, ui_node: TreeNode, node_data: Dict, agent_phases: Dict, team_phases: Dict, speaking_agents: Dict):
92
+ """Recursively updates a node and reconciles its children."""
93
+ # 1. Update the current node's label and data
94
+ name = node_data['name']
95
+ label = self._build_label(name, node_data, agent_phases, team_phases, speaking_agents)
96
+ ui_node.set_label(label)
97
+ ui_node.data = node_data
98
+ self._node_map[name] = ui_node # Ensure map is always up-to-date
99
+
100
+ # 2. Reconcile children
101
+ new_children_data = node_data.get("children", {})
102
+ existing_ui_children_by_name = {child.data['name']: child for child in ui_node.children if child.data}
103
+
104
+ # Add new nodes and update existing ones
105
+ for child_name, child_data in new_children_data.items():
106
+ if child_name in existing_ui_children_by_name:
107
+ # Node exists, so we recursively update it
108
+ child_ui_node = existing_ui_children_by_name[child_name]
109
+ self._update_node_recursively(child_ui_node, child_data, agent_phases, team_phases, speaking_agents)
110
+ else:
111
+ # Node is new, so we add it
112
+ new_child_label = self._build_label(child_name, child_data, agent_phases, team_phases, speaking_agents)
113
+ is_leaf = child_data.get("children", {}) == {} and child_data['type'] == 'agent'
114
+
115
+ if is_leaf:
116
+ new_ui_node = ui_node.add_leaf(new_child_label, data=child_data)
117
+ else:
118
+ new_ui_node = ui_node.add(new_child_label, data=child_data)
119
+ # Since this is a new branch, we must build its children too
120
+ self._update_node_recursively(new_ui_node, child_data, agent_phases, team_phases, speaking_agents)
121
+
122
+ self._node_map[child_name] = new_ui_node
123
+
124
+ # Remove old nodes that no longer exist in the new data
125
+ nodes_to_remove = []
126
+ for existing_child_name, existing_child_node in existing_ui_children_by_name.items():
127
+ if existing_child_name not in new_children_data:
128
+ nodes_to_remove.append(existing_child_node)
129
+ if existing_child_name in self._node_map:
130
+ del self._node_map[existing_child_name]
131
+
132
+ for node in nodes_to_remove:
133
+ node.remove()
134
+
135
+ def update_selection(self, node_name: Optional[str]):
136
+ """Updates the tree's selection and expands parents to make it visible."""
137
+ if not node_name or node_name not in self._node_map:
138
+ return
139
+
140
+ tree = self.query_one(Tree)
141
+ node_to_select = self._node_map[node_name]
142
+
143
+ parent = node_to_select.parent
144
+ while parent:
145
+ parent.expand()
146
+ parent = parent.parent
147
+
148
+ tree.select_node(node_to_select)
149
+ tree.scroll_to_node(node_to_select)
@@ -0,0 +1,320 @@
1
+ """
2
+ Defines the main focus pane widget for displaying detailed logs or summaries.
3
+ """
4
+ import logging
5
+ import json
6
+ from typing import Optional, List, Any, Dict
7
+
8
+ from rich.text import Text
9
+ from rich.panel import Panel
10
+ from rich.syntax import Syntax
11
+ from textual.message import Message
12
+ from textual.widgets import Input, Static, Button
13
+ from textual.containers import VerticalScroll, Horizontal
14
+
15
+ from autobyteus.agent.phases import AgentOperationalPhase
16
+ from autobyteus.agent_team.phases import AgentTeamOperationalPhase
17
+ from autobyteus.task_management.base_task_board import TaskStatus
18
+ from autobyteus.task_management.task_plan import Task
19
+ from autobyteus.agent.streaming.stream_events import StreamEvent as AgentStreamEvent, StreamEventType as AgentStreamEventType
20
+ from autobyteus.agent.streaming.stream_event_payloads import (
21
+ AgentOperationalPhaseTransitionData, AssistantChunkData, AssistantCompleteResponseData,
22
+ ErrorEventData, ToolInteractionLogEntryData, ToolInvocationApprovalRequestedData, ToolInvocationAutoExecutingData,
23
+ SystemTaskNotificationData
24
+ )
25
+ from .shared import (
26
+ AGENT_PHASE_ICONS, TEAM_PHASE_ICONS, SUB_TEAM_ICON, DEFAULT_ICON,
27
+ USER_ICON, ASSISTANT_ICON, TEAM_ICON, AGENT_ICON, SYSTEM_TASK_ICON
28
+ )
29
+ from . import renderables
30
+ from .task_board_panel import TaskBoardPanel
31
+
32
+ logger = logging.getLogger(__name__)
33
+
34
+ class FocusPane(Static):
35
+ """
36
+ A widget to display detailed logs for agents or high-level dashboards for teams.
37
+ This is a dumb rendering component driven by the TUIStateStore.
38
+ """
39
+
40
+ class MessageSubmitted(Message):
41
+ def __init__(self, text: str, agent_name: str) -> None:
42
+ self.text = text
43
+ self.agent_name = agent_name
44
+ super().__init__()
45
+
46
+ class ApprovalSubmitted(Message):
47
+ def __init__(self, agent_name: str, invocation_id: str, is_approved: bool, reason: Optional[str]) -> None:
48
+ self.agent_name = agent_name
49
+ self.invocation_id = invocation_id
50
+ self.is_approved = is_approved
51
+ self.reason = reason
52
+ super().__init__()
53
+
54
+ def __init__(self, *args, **kwargs) -> None:
55
+ super().__init__(*args, **kwargs)
56
+ self._focused_node_data: Optional[Dict[str, Any]] = None
57
+ self._pending_approval_data: Optional[ToolInvocationApprovalRequestedData] = None
58
+
59
+ # State variables for streaming
60
+ self._thinking_widget: Optional[Static] = None
61
+ self._thinking_text: Optional[Text] = None
62
+ self._assistant_content_widget: Optional[Static] = None
63
+ self._assistant_content_text: Optional[Text] = None
64
+
65
+ # Buffers for batched UI updates to improve performance
66
+ self._reasoning_buffer: str = ""
67
+ self._content_buffer: str = ""
68
+
69
+ def compose(self):
70
+ yield Static("Select a node from the sidebar", id="focus-pane-title")
71
+ yield VerticalScroll(id="focus-pane-log-container")
72
+ yield Horizontal(id="approval-buttons")
73
+ yield Input(placeholder="Select an agent to send messages...", id="focus-pane-input", disabled=True)
74
+
75
+ async def on_input_submitted(self, event: Input.Submitted) -> None:
76
+ if event.value and self._focused_node_data and self._focused_node_data.get("type") == 'agent':
77
+ log_container = self.query_one("#focus-pane-log-container")
78
+ user_message_text = Text(f"{USER_ICON} You: {event.value}", style="bright_blue")
79
+ await log_container.mount(Static(""))
80
+ await log_container.mount(Static(user_message_text))
81
+ log_container.scroll_end(animate=False)
82
+
83
+ self.post_message(self.MessageSubmitted(event.value, self._focused_node_data['name']))
84
+ self.query_one(Input).clear()
85
+ event.stop()
86
+
87
+ async def on_button_pressed(self, event: Button.Pressed) -> None:
88
+ if not self._pending_approval_data or not self._focused_node_data:
89
+ return
90
+
91
+ is_approved = event.button.id == "approve-btn"
92
+ reason = "User approved via TUI." if is_approved else "User denied via TUI."
93
+
94
+ log_container = self.query_one("#focus-pane-log-container")
95
+ approval_text = "APPROVED" if is_approved else "DENIED"
96
+ display_text = Text(f"{USER_ICON} You: {approval_text} (Reason: {reason})", style="bright_cyan")
97
+ await log_container.mount(Static(""))
98
+ await log_container.mount(Static(display_text))
99
+ log_container.scroll_end(animate=False)
100
+
101
+ self.post_message(self.ApprovalSubmitted(
102
+ agent_name=self._focused_node_data['name'],
103
+ invocation_id=self._pending_approval_data.invocation_id,
104
+ is_approved=is_approved, reason=reason
105
+ ))
106
+ await self._clear_approval_ui()
107
+ event.stop()
108
+
109
+ async def _clear_approval_ui(self):
110
+ self._pending_approval_data = None
111
+ await self.query_one("#approval-buttons").remove_children()
112
+ input_widget = self.query_one(Input)
113
+ if self._focused_node_data and self._focused_node_data.get("type") == "agent":
114
+ input_widget.disabled = False
115
+ input_widget.placeholder = f"Send a message to {self._focused_node_data['name']}..."
116
+ input_widget.focus()
117
+ else:
118
+ input_widget.disabled = True
119
+ input_widget.placeholder = "Select an agent to send messages..."
120
+
121
+ async def _show_approval_prompt(self):
122
+ if not self._pending_approval_data: return
123
+ input_widget = self.query_one(Input)
124
+ input_widget.placeholder = "Please approve or deny the tool call..."
125
+ input_widget.disabled = True
126
+ button_container = self.query_one("#approval-buttons")
127
+ await button_container.remove_children()
128
+ await button_container.mount(
129
+ Button("Approve", variant="success", id="approve-btn"),
130
+ Button("Deny", variant="error", id="deny-btn")
131
+ )
132
+
133
+ def _update_title(self, agent_phases: Dict[str, AgentOperationalPhase], team_phases: Dict[str, AgentTeamOperationalPhase]):
134
+ """Renders the title of the focus pane with the node's current status."""
135
+ if not self._focused_node_data:
136
+ self.query_one("#focus-pane-title").update("Select a node from the sidebar")
137
+ return
138
+
139
+ node_name = self._focused_node_data.get("name", "Unknown")
140
+ node_type = self._focused_node_data.get("type", "node")
141
+ node_type_str = node_type.replace("_", " ").capitalize()
142
+
143
+ title_icon = DEFAULT_ICON
144
+ phase_str = ""
145
+
146
+ if node_type == 'agent':
147
+ title_icon = AGENT_ICON
148
+ phase = agent_phases.get(node_name, AgentOperationalPhase.UNINITIALIZED)
149
+ phase_str = f" (Status: {phase.value})"
150
+ elif node_type == 'subteam':
151
+ title_icon = SUB_TEAM_ICON
152
+ phase = team_phases.get(node_name, AgentTeamOperationalPhase.UNINITIALIZED)
153
+ phase_str = f" (Status: {phase.value})"
154
+ elif node_type == 'team':
155
+ title_icon = TEAM_ICON
156
+ phase = team_phases.get(node_name, AgentTeamOperationalPhase.UNINITIALIZED)
157
+ phase_str = f" (Status: {phase.value})"
158
+
159
+ self.query_one("#focus-pane-title").update(f"{title_icon} {node_type_str}: [bold]{node_name}[/bold]{phase_str}")
160
+
161
+ def update_current_node_status(self, all_agent_phases: Dict, all_team_phases: Dict):
162
+ """A lightweight method to only update the title with the latest status."""
163
+ self._update_title(all_agent_phases, all_team_phases)
164
+
165
+ async def update_content(self, node_data: Dict[str, Any], history: List[Any],
166
+ pending_approval: Optional[ToolInvocationApprovalRequestedData],
167
+ all_agent_phases: Dict[str, AgentOperationalPhase],
168
+ all_team_phases: Dict[str, AgentTeamOperationalPhase],
169
+ task_plan: Optional[List[Task]],
170
+ task_statuses: Optional[Dict[str, TaskStatus]]):
171
+ """The main method to update the entire pane based on new state."""
172
+ self.flush_stream_buffers()
173
+
174
+ self._focused_node_data = node_data
175
+ self._pending_approval_data = pending_approval
176
+
177
+ self._update_title(all_agent_phases, all_team_phases)
178
+
179
+ log_container = self.query_one("#focus-pane-log-container")
180
+ await log_container.remove_children()
181
+
182
+ self._thinking_widget = None
183
+ self._thinking_text = None
184
+ self._assistant_content_widget = None
185
+ self._assistant_content_text = None
186
+
187
+ await self._clear_approval_ui()
188
+
189
+ if self._focused_node_data.get("type") == 'agent':
190
+ for event in history:
191
+ await self.add_agent_event(event)
192
+ if self._pending_approval_data:
193
+ await self._show_approval_prompt()
194
+ elif self._focused_node_data.get("type") in ['team', 'subteam']:
195
+ await self._render_team_dashboard(node_data, all_agent_phases, all_team_phases, task_plan, task_statuses)
196
+
197
+ async def _render_team_dashboard(self, node_data: Dict[str, Any],
198
+ all_agent_phases: Dict[str, AgentOperationalPhase],
199
+ all_team_phases: Dict[str, AgentTeamOperationalPhase],
200
+ task_plan: Optional[List[Task]],
201
+ task_statuses: Optional[Dict[str, TaskStatus]]):
202
+ """Renders a static summary dashboard for a team or sub-team."""
203
+ log_container = self.query_one("#focus-pane-log-container")
204
+
205
+ phase = all_team_phases.get(node_data['name'], AgentTeamOperationalPhase.UNINITIALIZED)
206
+ phase_icon = TEAM_PHASE_ICONS.get(phase, DEFAULT_ICON)
207
+ info_text = Text()
208
+ info_text.append(f"Name: {node_data['name']}\n", style="bold")
209
+ if node_data.get('role'):
210
+ info_text.append(f"Role: {node_data['role']}\n")
211
+ info_text.append(f"Status: {phase_icon} {phase.value}")
212
+ await log_container.mount(Static(Panel(info_text, title="Team Info", border_style="green", title_align="left")))
213
+
214
+ await log_container.mount(TaskBoardPanel(tasks=task_plan, statuses=task_statuses, team_name=node_data['name']))
215
+
216
+ children_data = node_data.get("children", {})
217
+ if children_data:
218
+ team_text = Text()
219
+ for name, child_node in children_data.items():
220
+ if child_node['type'] == 'agent':
221
+ agent_phase = all_agent_phases.get(name, AgentOperationalPhase.UNINITIALIZED)
222
+ agent_icon = AGENT_PHASE_ICONS.get(agent_phase, DEFAULT_ICON)
223
+ team_text.append(f" ▪ {agent_icon} {name} (Agent): {agent_phase.value}\n")
224
+ elif child_node['type'] == 'subteam':
225
+ wf_phase = all_team_phases.get(name, AgentTeamOperationalPhase.UNINITIALIZED)
226
+ wf_icon = TEAM_PHASE_ICONS.get(wf_phase, SUB_TEAM_ICON)
227
+ team_text.append(f" ▪ {wf_icon} {name} (Sub-Team): {wf_phase.value}\n")
228
+ await log_container.mount(Static(Panel(team_text, title="Team Status", border_style="blue", title_align="left")))
229
+
230
+ async def _close_thinking_block(self, scroll: bool = True):
231
+ if self._thinking_widget and self._thinking_text:
232
+ self.flush_stream_buffers()
233
+ self._thinking_text.append("\n</Thinking>", style="dim italic cyan")
234
+ self._thinking_widget.update(self._thinking_text)
235
+ if scroll:
236
+ self.query_one("#focus-pane-log-container").scroll_end(animate=False)
237
+ self._thinking_widget = None
238
+ self._thinking_text = None
239
+
240
+ def flush_stream_buffers(self):
241
+ scrolled = False
242
+ if self._reasoning_buffer and self._thinking_widget and self._thinking_text:
243
+ self._thinking_text.append(self._reasoning_buffer)
244
+ self._thinking_widget.update(self._thinking_text)
245
+ self._reasoning_buffer = ""
246
+ scrolled = True
247
+ if self._content_buffer and self._assistant_content_widget and self._assistant_content_text:
248
+ self._assistant_content_text.append(self._content_buffer)
249
+ self._assistant_content_widget.update(self._assistant_content_text)
250
+ self._content_buffer = ""
251
+ scrolled = True
252
+ if scrolled:
253
+ self.query_one("#focus-pane-log-container").scroll_end(animate=False)
254
+
255
+ async def add_agent_event(self, event: AgentStreamEvent):
256
+ log_container = self.query_one("#focus-pane-log-container")
257
+ event_type = event.event_type
258
+
259
+ if event_type == AgentStreamEventType.ASSISTANT_CHUNK:
260
+ data: AssistantChunkData = event.data
261
+ if data.reasoning:
262
+ if self._thinking_widget is None:
263
+ self.flush_stream_buffers()
264
+ await log_container.mount(Static(""))
265
+ self._thinking_text = Text("<Thinking>\n", style="dim italic cyan")
266
+ self._thinking_widget = Static(self._thinking_text)
267
+ await log_container.mount(self._thinking_widget)
268
+ self._reasoning_buffer += data.reasoning
269
+ if data.content:
270
+ if self._thinking_widget: await self._close_thinking_block()
271
+ if self._assistant_content_widget is None:
272
+ await log_container.mount(Static(""))
273
+ self._assistant_content_text = Text(f"{ASSISTANT_ICON} assistant: ", style="bold green")
274
+ self._assistant_content_widget = Static(self._assistant_content_text)
275
+ await log_container.mount(self._assistant_content_widget)
276
+ self._content_buffer += data.content
277
+ return
278
+
279
+ if event_type == AgentStreamEventType.ASSISTANT_COMPLETE_RESPONSE:
280
+ was_streaming_content = self._assistant_content_widget is not None
281
+ self.flush_stream_buffers()
282
+ await self._close_thinking_block()
283
+ self._assistant_content_widget = None
284
+ self._assistant_content_text = None
285
+ if not was_streaming_content:
286
+ renderables_list = renderables.render_assistant_complete_response(event.data)
287
+ if renderables_list:
288
+ await log_container.mount(Static(""))
289
+ for item in renderables_list: await log_container.mount(Static(item))
290
+ log_container.scroll_end(animate=False)
291
+ return
292
+
293
+ is_stream_breaking_event = event_type in [
294
+ AgentStreamEventType.TOOL_INTERACTION_LOG_ENTRY,
295
+ AgentStreamEventType.TOOL_INVOCATION_AUTO_EXECUTING,
296
+ AgentStreamEventType.TOOL_INVOCATION_APPROVAL_REQUESTED,
297
+ AgentStreamEventType.ERROR_EVENT,
298
+ AgentStreamEventType.SYSTEM_TASK_NOTIFICATION, # NEW
299
+ ]
300
+ if is_stream_breaking_event:
301
+ self.flush_stream_buffers()
302
+ await self._close_thinking_block()
303
+ self._assistant_content_widget = None
304
+ self._assistant_content_text = None
305
+
306
+ renderable = None
307
+ if event_type == AgentStreamEventType.TOOL_INTERACTION_LOG_ENTRY: renderable = renderables.render_tool_interaction_log(event.data)
308
+ elif event_type == AgentStreamEventType.TOOL_INVOCATION_AUTO_EXECUTING: renderable = renderables.render_tool_auto_executing(event.data)
309
+ elif event_type == AgentStreamEventType.TOOL_INVOCATION_APPROVAL_REQUESTED:
310
+ renderable = renderables.render_tool_approval_request(event.data)
311
+ self._pending_approval_data = event.data
312
+ await self._show_approval_prompt()
313
+ elif event_type == AgentStreamEventType.ERROR_EVENT: renderable = renderables.render_error(event.data)
314
+ elif event_type == AgentStreamEventType.SYSTEM_TASK_NOTIFICATION: renderable = renderables.render_system_task_notification(event.data) # NEW
315
+
316
+ if renderable:
317
+ await log_container.mount(Static(""))
318
+ await log_container.mount(Static(renderable))
319
+
320
+ log_container.scroll_end(animate=False)
@@ -0,0 +1,20 @@
1
+ # file: autobyteus/autobyteus/cli/agent_team_tui/widgets/logo.py
2
+ """
3
+ Defines a widget to display the AutoByteus ASCII art logo and tagline.
4
+ """
5
+ from rich.text import Text
6
+ from textual.widgets import Static
7
+ from textual.containers import Vertical
8
+
9
+ class Logo(Vertical):
10
+ """A widget to display the AutoByteus logo and tagline."""
11
+
12
+ def compose(self) -> None:
13
+ # A simple, clean, single-line text logo that is more readable
14
+ # and respects that "AutoByteus" is one word.
15
+ logo_text = Text(justify="center")
16
+ logo_text.append("Auto", style="bold cyan")
17
+ logo_text.append("Byteus", style="bold magenta")
18
+
19
+ yield Static(logo_text, classes="logo-art")
20
+ yield Static("Orchestrating AI Agent Teams.", classes="logo-tagline")
@@ -0,0 +1,77 @@
1
+ # file: autobyteus/autobyteus/cli/agent_team_tui/widgets/renderables.py
2
+ """
3
+ Contains pure functions that convert agent event data into Rich renderables for the FocusPane.
4
+ This separates presentation logic from the view logic of the widget itself.
5
+ """
6
+ import json
7
+ from typing import Optional
8
+
9
+ from rich.text import Text
10
+ from rich.panel import Panel
11
+
12
+ from autobyteus.agent.streaming.stream_event_payloads import (
13
+ AgentOperationalPhaseTransitionData, AssistantCompleteResponseData,
14
+ ErrorEventData, ToolInteractionLogEntryData, ToolInvocationApprovalRequestedData,
15
+ ToolInvocationAutoExecutingData, SystemTaskNotificationData
16
+ )
17
+ from .shared import ASSISTANT_ICON, TOOL_ICON, PROMPT_ICON, ERROR_ICON, LOG_ICON, SYSTEM_TASK_ICON
18
+
19
+ def render_assistant_complete_response(data: AssistantCompleteResponseData) -> list[Text | Panel]:
20
+ """Renders a complete, pre-aggregated assistant response."""
21
+ renderables = []
22
+ if data.reasoning:
23
+ reasoning_text = Text("<Thinking>\n", style="dim italic cyan")
24
+ reasoning_text.append(data.reasoning)
25
+ reasoning_text.append("\n</Thinking>", style="dim italic cyan")
26
+ renderables.append(reasoning_text)
27
+
28
+ if data.content:
29
+ content_text = Text()
30
+ content_text.append(f"{ASSISTANT_ICON} assistant: ", style="bold green")
31
+ content_text.append(data.content)
32
+ renderables.append(content_text)
33
+
34
+ return renderables
35
+
36
+ def render_tool_interaction_log(data: ToolInteractionLogEntryData) -> Text:
37
+ """Renders a tool interaction log entry."""
38
+ return Text(f"{LOG_ICON} [tool-log] {data.log_entry}", style="dim")
39
+
40
+ def render_tool_auto_executing(data: ToolInvocationAutoExecutingData) -> Text:
41
+ """Renders a notification that a tool is being executed automatically."""
42
+ try:
43
+ args_str = json.dumps(data.arguments, indent=2)
44
+ except (TypeError, OverflowError):
45
+ args_str = str(data.arguments)
46
+
47
+ text_content = Text(f"{TOOL_ICON} Executing tool '", style="default")
48
+ text_content.append(f"{data.tool_name}", style="bold yellow")
49
+ text_content.append("' with arguments:\n", style="default")
50
+ text_content.append(args_str, style="yellow")
51
+ return text_content
52
+
53
+ def render_tool_approval_request(data: ToolInvocationApprovalRequestedData) -> Text:
54
+ """Renders a prompt for the user to approve a tool call."""
55
+ try:
56
+ args_str = json.dumps(data.arguments, indent=2)
57
+ except (TypeError, OverflowError):
58
+ args_str = str(data.arguments)
59
+
60
+ text_content = Text(f"{PROMPT_ICON} Requesting approval for tool '", style="default")
61
+ text_content.append(f"{data.tool_name}", style="bold yellow")
62
+ text_content.append("' with arguments:\n", style="default")
63
+ text_content.append(args_str, style="yellow")
64
+ return text_content
65
+
66
+ def render_error(data: ErrorEventData) -> Text:
67
+ """Renders an error event."""
68
+ error_text = f"Error from {data.source}: {data.message}"
69
+ if data.details:
70
+ error_text += f"\nDetails: {data.details}"
71
+ return Text(f"{ERROR_ICON} {error_text}", style="bold red")
72
+
73
+ def render_system_task_notification(data: SystemTaskNotificationData) -> Text:
74
+ """Renders a system-generated task notification."""
75
+ text_content = Text(f"{SYSTEM_TASK_ICON} System Task Notification: ", style="bold magenta")
76
+ text_content.append(data.content, style="magenta")
77
+ return text_content
@@ -0,0 +1,60 @@
1
+ """
2
+ Shared constants and data for TUI widgets.
3
+ """
4
+ from typing import Dict
5
+ from autobyteus.agent.phases import AgentOperationalPhase
6
+ from autobyteus.agent_team.phases import AgentTeamOperationalPhase
7
+ from autobyteus.task_management.base_task_board import TaskStatus
8
+
9
+ AGENT_PHASE_ICONS: Dict[AgentOperationalPhase, str] = {
10
+ AgentOperationalPhase.UNINITIALIZED: "⚪",
11
+ AgentOperationalPhase.BOOTSTRAPPING: "⏳",
12
+ AgentOperationalPhase.IDLE: "🟢",
13
+ AgentOperationalPhase.PROCESSING_USER_INPUT: "💭",
14
+ AgentOperationalPhase.AWAITING_LLM_RESPONSE: "💭",
15
+ AgentOperationalPhase.ANALYZING_LLM_RESPONSE: "🤔",
16
+ AgentOperationalPhase.AWAITING_TOOL_APPROVAL: "❓",
17
+ AgentOperationalPhase.TOOL_DENIED: "❌",
18
+ AgentOperationalPhase.EXECUTING_TOOL: "🛠️",
19
+ AgentOperationalPhase.PROCESSING_TOOL_RESULT: "⚙️",
20
+ AgentOperationalPhase.SHUTTING_DOWN: "🌙",
21
+ AgentOperationalPhase.SHUTDOWN_COMPLETE: "⚫",
22
+ AgentOperationalPhase.ERROR: "❗",
23
+ }
24
+
25
+ TEAM_PHASE_ICONS: Dict[AgentTeamOperationalPhase, str] = {
26
+ AgentTeamOperationalPhase.UNINITIALIZED: "⚪",
27
+ AgentTeamOperationalPhase.BOOTSTRAPPING: "⏳",
28
+ AgentTeamOperationalPhase.IDLE: "🟢",
29
+ AgentTeamOperationalPhase.PROCESSING: "⚙️",
30
+ AgentTeamOperationalPhase.SHUTTING_DOWN: "🌙",
31
+ AgentTeamOperationalPhase.SHUTDOWN_COMPLETE: "⚫",
32
+ AgentTeamOperationalPhase.ERROR: "❗",
33
+ }
34
+
35
+ TASK_STATUS_ICONS: Dict[TaskStatus, str] = {
36
+ TaskStatus.NOT_STARTED: "⚪",
37
+ TaskStatus.IN_PROGRESS: "⏳",
38
+ TaskStatus.COMPLETED: "✅",
39
+ TaskStatus.FAILED: "❌",
40
+ TaskStatus.BLOCKED: "🔒",
41
+ }
42
+
43
+ # Main component icons
44
+ SUB_TEAM_ICON = "📂"
45
+ TEAM_ICON = "🏁"
46
+ AGENT_ICON = "🤖"
47
+
48
+ # General UI icons
49
+ SPEAKING_ICON = "🔊"
50
+ DEFAULT_ICON = "❓"
51
+
52
+ # Semantic icons for log entries
53
+ USER_ICON = "👤"
54
+ ASSISTANT_ICON = "🤖"
55
+ TOOL_ICON = "🛠️"
56
+ PROMPT_ICON = "❓"
57
+ ERROR_ICON = "💥"
58
+ PHASE_ICON = "🔄"
59
+ LOG_ICON = "📄"
60
+ SYSTEM_TASK_ICON = "📥" # NEW
@@ -0,0 +1,14 @@
1
+ # file: autobyteus/autobyteus/cli/agent_team_tui/widgets/status_bar.py
2
+ """
3
+ Defines the status bar widget for the TUI.
4
+ """
5
+
6
+ from textual.widgets import Footer
7
+
8
+ class StatusBar(Footer):
9
+ """A simple footer widget that displays key bindings."""
10
+
11
+ def __init__(self) -> None:
12
+ super().__init__()
13
+ # This will be automatically populated by Textual's binding system.
14
+ # You can add more status information here if needed in the future.