autobyteus 1.2.1__py3-none-any.whl → 1.2.3__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.
- autobyteus/agent/agent.py +15 -5
- autobyteus/agent/bootstrap_steps/__init__.py +1 -3
- autobyteus/agent/bootstrap_steps/agent_bootstrapper.py +3 -59
- autobyteus/agent/bootstrap_steps/base_bootstrap_step.py +1 -4
- autobyteus/agent/bootstrap_steps/mcp_server_prewarming_step.py +1 -3
- autobyteus/agent/bootstrap_steps/system_prompt_processing_step.py +16 -13
- autobyteus/agent/bootstrap_steps/workspace_context_initialization_step.py +2 -4
- autobyteus/agent/context/agent_config.py +43 -20
- autobyteus/agent/context/agent_context.py +23 -18
- autobyteus/agent/context/agent_runtime_state.py +19 -19
- autobyteus/agent/events/__init__.py +16 -1
- autobyteus/agent/events/agent_events.py +43 -3
- autobyteus/agent/events/agent_input_event_queue_manager.py +79 -26
- autobyteus/agent/events/event_store.py +57 -0
- autobyteus/agent/events/notifiers.py +69 -59
- autobyteus/agent/events/worker_event_dispatcher.py +21 -64
- autobyteus/agent/factory/agent_factory.py +52 -0
- autobyteus/agent/handlers/__init__.py +2 -0
- autobyteus/agent/handlers/approved_tool_invocation_event_handler.py +51 -34
- autobyteus/agent/handlers/bootstrap_event_handler.py +155 -0
- autobyteus/agent/handlers/inter_agent_message_event_handler.py +10 -0
- autobyteus/agent/handlers/lifecycle_event_logger.py +19 -11
- autobyteus/agent/handlers/llm_complete_response_received_event_handler.py +10 -15
- autobyteus/agent/handlers/llm_user_message_ready_event_handler.py +188 -48
- autobyteus/agent/handlers/tool_execution_approval_event_handler.py +0 -10
- autobyteus/agent/handlers/tool_invocation_request_event_handler.py +53 -48
- autobyteus/agent/handlers/tool_result_event_handler.py +7 -8
- autobyteus/agent/handlers/user_input_message_event_handler.py +10 -3
- autobyteus/agent/input_processor/memory_ingest_input_processor.py +40 -0
- autobyteus/agent/lifecycle/__init__.py +12 -0
- autobyteus/agent/lifecycle/base_processor.py +109 -0
- autobyteus/agent/lifecycle/events.py +35 -0
- autobyteus/agent/lifecycle/processor_definition.py +36 -0
- autobyteus/agent/lifecycle/processor_registry.py +106 -0
- autobyteus/agent/llm_request_assembler.py +98 -0
- autobyteus/agent/llm_response_processor/__init__.py +1 -8
- autobyteus/agent/message/context_file_type.py +1 -1
- autobyteus/agent/runtime/agent_runtime.py +29 -21
- autobyteus/agent/runtime/agent_worker.py +98 -19
- autobyteus/agent/shutdown_steps/__init__.py +2 -0
- autobyteus/agent/shutdown_steps/agent_shutdown_orchestrator.py +2 -0
- autobyteus/agent/shutdown_steps/tool_cleanup_step.py +58 -0
- autobyteus/agent/status/__init__.py +14 -0
- autobyteus/agent/status/manager.py +93 -0
- autobyteus/agent/status/status_deriver.py +96 -0
- autobyteus/agent/{phases/phase_enum.py → status/status_enum.py} +16 -16
- autobyteus/agent/status/status_update_utils.py +73 -0
- autobyteus/agent/streaming/__init__.py +52 -5
- autobyteus/agent/streaming/adapters/__init__.py +18 -0
- autobyteus/agent/streaming/adapters/invocation_adapter.py +184 -0
- autobyteus/agent/streaming/adapters/tool_call_parsing.py +163 -0
- autobyteus/agent/streaming/adapters/tool_syntax_registry.py +67 -0
- autobyteus/agent/streaming/agent_event_stream.py +3 -183
- autobyteus/agent/streaming/api_tool_call/__init__.py +16 -0
- autobyteus/agent/streaming/api_tool_call/file_content_streamer.py +56 -0
- autobyteus/agent/streaming/api_tool_call/json_string_field_extractor.py +175 -0
- autobyteus/agent/streaming/api_tool_call_streaming_response_handler.py +4 -0
- autobyteus/agent/streaming/events/__init__.py +6 -0
- autobyteus/agent/streaming/events/stream_event_payloads.py +284 -0
- autobyteus/agent/streaming/events/stream_events.py +141 -0
- autobyteus/agent/streaming/handlers/__init__.py +15 -0
- autobyteus/agent/streaming/handlers/api_tool_call_streaming_response_handler.py +303 -0
- autobyteus/agent/streaming/handlers/parsing_streaming_response_handler.py +107 -0
- autobyteus/agent/streaming/handlers/pass_through_streaming_response_handler.py +107 -0
- autobyteus/agent/streaming/handlers/streaming_handler_factory.py +177 -0
- autobyteus/agent/streaming/handlers/streaming_response_handler.py +58 -0
- autobyteus/agent/streaming/parser/__init__.py +61 -0
- autobyteus/agent/streaming/parser/event_emitter.py +181 -0
- autobyteus/agent/streaming/parser/events.py +4 -0
- autobyteus/agent/streaming/parser/invocation_adapter.py +4 -0
- autobyteus/agent/streaming/parser/json_parsing_strategies/__init__.py +19 -0
- autobyteus/agent/streaming/parser/json_parsing_strategies/base.py +32 -0
- autobyteus/agent/streaming/parser/json_parsing_strategies/default.py +34 -0
- autobyteus/agent/streaming/parser/json_parsing_strategies/gemini.py +31 -0
- autobyteus/agent/streaming/parser/json_parsing_strategies/openai.py +64 -0
- autobyteus/agent/streaming/parser/json_parsing_strategies/registry.py +75 -0
- autobyteus/agent/streaming/parser/parser_context.py +227 -0
- autobyteus/agent/streaming/parser/parser_factory.py +132 -0
- autobyteus/agent/streaming/parser/sentinel_format.py +7 -0
- autobyteus/agent/streaming/parser/state_factory.py +62 -0
- autobyteus/agent/streaming/parser/states/__init__.py +1 -0
- autobyteus/agent/streaming/parser/states/base_state.py +60 -0
- autobyteus/agent/streaming/parser/states/custom_xml_tag_run_bash_parsing_state.py +38 -0
- autobyteus/agent/streaming/parser/states/custom_xml_tag_write_file_parsing_state.py +55 -0
- autobyteus/agent/streaming/parser/states/delimited_content_state.py +146 -0
- autobyteus/agent/streaming/parser/states/json_initialization_state.py +144 -0
- autobyteus/agent/streaming/parser/states/json_tool_parsing_state.py +137 -0
- autobyteus/agent/streaming/parser/states/sentinel_content_state.py +30 -0
- autobyteus/agent/streaming/parser/states/sentinel_initialization_state.py +117 -0
- autobyteus/agent/streaming/parser/states/text_state.py +78 -0
- autobyteus/agent/streaming/parser/states/xml_patch_file_tool_parsing_state.py +328 -0
- autobyteus/agent/streaming/parser/states/xml_run_bash_tool_parsing_state.py +129 -0
- autobyteus/agent/streaming/parser/states/xml_tag_initialization_state.py +151 -0
- autobyteus/agent/streaming/parser/states/xml_tool_parsing_state.py +63 -0
- autobyteus/agent/streaming/parser/states/xml_write_file_tool_parsing_state.py +343 -0
- autobyteus/agent/streaming/parser/strategies/__init__.py +17 -0
- autobyteus/agent/streaming/parser/strategies/base.py +24 -0
- autobyteus/agent/streaming/parser/strategies/json_tool_strategy.py +26 -0
- autobyteus/agent/streaming/parser/strategies/registry.py +28 -0
- autobyteus/agent/streaming/parser/strategies/sentinel_strategy.py +23 -0
- autobyteus/agent/streaming/parser/strategies/xml_tag_strategy.py +21 -0
- autobyteus/agent/streaming/parser/stream_scanner.py +167 -0
- autobyteus/agent/streaming/parser/streaming_parser.py +212 -0
- autobyteus/agent/streaming/parser/tool_call_parsing.py +4 -0
- autobyteus/agent/streaming/parser/tool_constants.py +7 -0
- autobyteus/agent/streaming/parser/tool_syntax_registry.py +4 -0
- autobyteus/agent/streaming/parser/xml_tool_parsing_state_registry.py +55 -0
- autobyteus/agent/streaming/parsing_streaming_response_handler.py +4 -0
- autobyteus/agent/streaming/pass_through_streaming_response_handler.py +4 -0
- autobyteus/agent/streaming/queue_streamer.py +3 -57
- autobyteus/agent/streaming/segments/__init__.py +5 -0
- autobyteus/agent/streaming/segments/segment_events.py +81 -0
- autobyteus/agent/streaming/stream_event_payloads.py +2 -223
- autobyteus/agent/streaming/stream_events.py +3 -140
- autobyteus/agent/streaming/streaming_handler_factory.py +4 -0
- autobyteus/agent/streaming/streaming_response_handler.py +4 -0
- autobyteus/agent/streaming/streams/__init__.py +5 -0
- autobyteus/agent/streaming/streams/agent_event_stream.py +197 -0
- autobyteus/agent/streaming/utils/__init__.py +5 -0
- autobyteus/agent/streaming/utils/queue_streamer.py +59 -0
- autobyteus/agent/system_prompt_processor/__init__.py +2 -0
- autobyteus/agent/system_prompt_processor/available_skills_processor.py +96 -0
- autobyteus/agent/system_prompt_processor/base_processor.py +1 -1
- autobyteus/agent/system_prompt_processor/processor_meta.py +15 -2
- autobyteus/agent/system_prompt_processor/tool_manifest_injector_processor.py +39 -58
- autobyteus/agent/token_budget.py +56 -0
- autobyteus/agent/tool_execution_result_processor/memory_ingest_tool_result_processor.py +29 -0
- autobyteus/agent/tool_invocation.py +16 -40
- autobyteus/agent/tool_invocation_preprocessor/__init__.py +9 -0
- autobyteus/agent/tool_invocation_preprocessor/base_preprocessor.py +45 -0
- autobyteus/agent/tool_invocation_preprocessor/processor_definition.py +15 -0
- autobyteus/agent/tool_invocation_preprocessor/processor_meta.py +33 -0
- autobyteus/agent/tool_invocation_preprocessor/processor_registry.py +60 -0
- autobyteus/agent/utils/wait_for_idle.py +12 -14
- autobyteus/agent/workspace/base_workspace.py +6 -27
- autobyteus/agent_team/agent_team.py +3 -3
- autobyteus/agent_team/agent_team_builder.py +1 -41
- autobyteus/agent_team/bootstrap_steps/__init__.py +0 -4
- autobyteus/agent_team/bootstrap_steps/agent_configuration_preparation_step.py +8 -18
- autobyteus/agent_team/bootstrap_steps/agent_team_bootstrapper.py +4 -16
- autobyteus/agent_team/bootstrap_steps/base_agent_team_bootstrap_step.py +1 -2
- autobyteus/agent_team/bootstrap_steps/coordinator_initialization_step.py +1 -2
- autobyteus/agent_team/bootstrap_steps/task_notifier_initialization_step.py +1 -2
- autobyteus/agent_team/bootstrap_steps/team_context_initialization_step.py +4 -4
- autobyteus/agent_team/context/agent_team_config.py +6 -3
- autobyteus/agent_team/context/agent_team_context.py +25 -3
- autobyteus/agent_team/context/agent_team_runtime_state.py +9 -6
- autobyteus/agent_team/events/__init__.py +11 -0
- autobyteus/agent_team/events/agent_team_event_dispatcher.py +22 -9
- autobyteus/agent_team/events/agent_team_events.py +16 -0
- autobyteus/agent_team/events/event_store.py +57 -0
- autobyteus/agent_team/factory/agent_team_factory.py +8 -0
- autobyteus/agent_team/handlers/inter_agent_message_request_event_handler.py +18 -2
- autobyteus/agent_team/handlers/lifecycle_agent_team_event_handler.py +21 -5
- autobyteus/agent_team/handlers/process_user_message_event_handler.py +17 -8
- autobyteus/agent_team/handlers/tool_approval_team_event_handler.py +19 -4
- autobyteus/agent_team/runtime/agent_team_runtime.py +41 -10
- autobyteus/agent_team/runtime/agent_team_worker.py +69 -5
- autobyteus/agent_team/status/__init__.py +14 -0
- autobyteus/agent_team/status/agent_team_status.py +18 -0
- autobyteus/agent_team/status/agent_team_status_manager.py +33 -0
- autobyteus/agent_team/status/status_deriver.py +62 -0
- autobyteus/agent_team/status/status_update_utils.py +42 -0
- autobyteus/agent_team/streaming/__init__.py +2 -2
- autobyteus/agent_team/streaming/agent_team_event_notifier.py +6 -6
- autobyteus/agent_team/streaming/agent_team_stream_event_payloads.py +4 -4
- autobyteus/agent_team/streaming/agent_team_stream_events.py +3 -3
- autobyteus/agent_team/system_prompt_processor/__init__.py +6 -0
- autobyteus/agent_team/system_prompt_processor/team_manifest_injector_processor.py +76 -0
- autobyteus/agent_team/task_notification/task_notification_mode.py +19 -0
- autobyteus/agent_team/utils/wait_for_idle.py +4 -4
- autobyteus/cli/agent_cli.py +18 -10
- autobyteus/cli/agent_team_tui/app.py +14 -11
- autobyteus/cli/agent_team_tui/state.py +13 -15
- autobyteus/cli/agent_team_tui/widgets/agent_list_sidebar.py +15 -15
- autobyteus/cli/agent_team_tui/widgets/focus_pane.py +143 -36
- autobyteus/cli/agent_team_tui/widgets/renderables.py +1 -1
- autobyteus/cli/agent_team_tui/widgets/shared.py +25 -25
- autobyteus/cli/cli_display.py +193 -44
- autobyteus/cli/workflow_tui/app.py +9 -10
- autobyteus/cli/workflow_tui/state.py +14 -16
- autobyteus/cli/workflow_tui/widgets/agent_list_sidebar.py +15 -15
- autobyteus/cli/workflow_tui/widgets/focus_pane.py +137 -35
- autobyteus/cli/workflow_tui/widgets/renderables.py +1 -1
- autobyteus/cli/workflow_tui/widgets/shared.py +25 -25
- autobyteus/clients/autobyteus_client.py +94 -1
- autobyteus/events/event_types.py +11 -18
- autobyteus/llm/api/autobyteus_llm.py +33 -29
- autobyteus/llm/api/claude_llm.py +142 -36
- autobyteus/llm/api/gemini_llm.py +163 -59
- autobyteus/llm/api/grok_llm.py +1 -1
- autobyteus/llm/api/minimax_llm.py +26 -0
- autobyteus/llm/api/mistral_llm.py +113 -87
- autobyteus/llm/api/ollama_llm.py +9 -42
- autobyteus/llm/api/openai_compatible_llm.py +127 -91
- autobyteus/llm/api/openai_llm.py +3 -3
- autobyteus/llm/api/openai_responses_llm.py +324 -0
- autobyteus/llm/api/zhipu_llm.py +21 -2
- autobyteus/llm/autobyteus_provider.py +70 -60
- autobyteus/llm/base_llm.py +85 -81
- autobyteus/llm/converters/__init__.py +14 -0
- autobyteus/llm/converters/anthropic_tool_call_converter.py +37 -0
- autobyteus/llm/converters/gemini_tool_call_converter.py +57 -0
- autobyteus/llm/converters/mistral_tool_call_converter.py +37 -0
- autobyteus/llm/converters/openai_tool_call_converter.py +38 -0
- autobyteus/llm/extensions/base_extension.py +6 -12
- autobyteus/llm/extensions/token_usage_tracking_extension.py +45 -18
- autobyteus/llm/llm_factory.py +282 -204
- autobyteus/llm/lmstudio_provider.py +60 -49
- autobyteus/llm/models.py +35 -2
- autobyteus/llm/ollama_provider.py +60 -49
- autobyteus/llm/ollama_provider_resolver.py +0 -1
- autobyteus/llm/prompt_renderers/__init__.py +19 -0
- autobyteus/llm/prompt_renderers/anthropic_prompt_renderer.py +104 -0
- autobyteus/llm/prompt_renderers/autobyteus_prompt_renderer.py +19 -0
- autobyteus/llm/prompt_renderers/base_prompt_renderer.py +10 -0
- autobyteus/llm/prompt_renderers/gemini_prompt_renderer.py +63 -0
- autobyteus/llm/prompt_renderers/mistral_prompt_renderer.py +87 -0
- autobyteus/llm/prompt_renderers/ollama_prompt_renderer.py +51 -0
- autobyteus/llm/prompt_renderers/openai_chat_renderer.py +97 -0
- autobyteus/llm/prompt_renderers/openai_responses_renderer.py +101 -0
- autobyteus/llm/providers.py +1 -3
- autobyteus/llm/token_counter/claude_token_counter.py +56 -25
- autobyteus/llm/token_counter/mistral_token_counter.py +12 -8
- autobyteus/llm/token_counter/openai_token_counter.py +24 -5
- autobyteus/llm/token_counter/token_counter_factory.py +12 -5
- autobyteus/llm/utils/llm_config.py +6 -12
- autobyteus/llm/utils/media_payload_formatter.py +27 -20
- autobyteus/llm/utils/messages.py +55 -3
- autobyteus/llm/utils/response_types.py +3 -0
- autobyteus/llm/utils/tool_call_delta.py +31 -0
- autobyteus/memory/__init__.py +32 -0
- autobyteus/memory/active_transcript.py +69 -0
- autobyteus/memory/compaction/__init__.py +9 -0
- autobyteus/memory/compaction/compaction_result.py +8 -0
- autobyteus/memory/compaction/compactor.py +89 -0
- autobyteus/memory/compaction/summarizer.py +11 -0
- autobyteus/memory/compaction_snapshot_builder.py +84 -0
- autobyteus/memory/memory_manager.py +183 -0
- autobyteus/memory/models/__init__.py +14 -0
- autobyteus/memory/models/episodic_item.py +41 -0
- autobyteus/memory/models/memory_types.py +7 -0
- autobyteus/memory/models/raw_trace_item.py +79 -0
- autobyteus/memory/models/semantic_item.py +41 -0
- autobyteus/memory/models/tool_interaction.py +20 -0
- autobyteus/memory/policies/__init__.py +5 -0
- autobyteus/memory/policies/compaction_policy.py +16 -0
- autobyteus/memory/retrieval/__init__.py +7 -0
- autobyteus/memory/retrieval/memory_bundle.py +11 -0
- autobyteus/memory/retrieval/retriever.py +13 -0
- autobyteus/memory/store/__init__.py +7 -0
- autobyteus/memory/store/base_store.py +14 -0
- autobyteus/memory/store/file_store.py +98 -0
- autobyteus/memory/tool_interaction_builder.py +46 -0
- autobyteus/memory/turn_tracker.py +9 -0
- autobyteus/multimedia/audio/api/autobyteus_audio_client.py +19 -5
- autobyteus/multimedia/audio/api/gemini_audio_client.py +108 -16
- autobyteus/multimedia/audio/audio_client_factory.py +47 -9
- autobyteus/multimedia/audio/audio_model.py +2 -1
- autobyteus/multimedia/image/api/autobyteus_image_client.py +19 -5
- autobyteus/multimedia/image/api/gemini_image_client.py +38 -17
- autobyteus/multimedia/image/api/openai_image_client.py +125 -43
- autobyteus/multimedia/image/autobyteus_image_provider.py +2 -1
- autobyteus/multimedia/image/image_client_factory.py +47 -15
- autobyteus/multimedia/image/image_model.py +5 -2
- autobyteus/multimedia/providers.py +3 -2
- autobyteus/skills/loader.py +71 -0
- autobyteus/skills/model.py +11 -0
- autobyteus/skills/registry.py +70 -0
- autobyteus/task_management/tools/todo_tools/add_todo.py +2 -2
- autobyteus/task_management/tools/todo_tools/create_todo_list.py +2 -2
- autobyteus/task_management/tools/todo_tools/update_todo_status.py +2 -2
- autobyteus/tools/__init__.py +34 -47
- autobyteus/tools/base_tool.py +7 -0
- autobyteus/tools/file/__init__.py +2 -6
- autobyteus/tools/file/patch_file.py +149 -0
- autobyteus/tools/file/read_file.py +36 -5
- autobyteus/tools/file/write_file.py +4 -1
- autobyteus/tools/functional_tool.py +43 -6
- autobyteus/tools/mcp/__init__.py +2 -0
- autobyteus/tools/mcp/config_service.py +5 -1
- autobyteus/tools/mcp/server/__init__.py +2 -0
- autobyteus/tools/mcp/server/http_managed_mcp_server.py +1 -1
- autobyteus/tools/mcp/server/websocket_managed_mcp_server.py +141 -0
- autobyteus/tools/mcp/server_instance_manager.py +8 -1
- autobyteus/tools/mcp/types.py +61 -0
- autobyteus/tools/multimedia/audio_tools.py +70 -17
- autobyteus/tools/multimedia/download_media_tool.py +18 -4
- autobyteus/tools/multimedia/image_tools.py +246 -62
- autobyteus/tools/operation_executor/journal_manager.py +107 -0
- autobyteus/tools/operation_executor/operation_event_buffer.py +57 -0
- autobyteus/tools/operation_executor/operation_event_producer.py +29 -0
- autobyteus/tools/operation_executor/operation_executor.py +58 -0
- autobyteus/tools/registry/tool_definition.py +43 -2
- autobyteus/tools/skill/load_skill.py +50 -0
- autobyteus/tools/terminal/__init__.py +45 -0
- autobyteus/tools/terminal/ansi_utils.py +32 -0
- autobyteus/tools/terminal/background_process_manager.py +233 -0
- autobyteus/tools/terminal/output_buffer.py +105 -0
- autobyteus/tools/terminal/prompt_detector.py +63 -0
- autobyteus/tools/terminal/pty_session.py +241 -0
- autobyteus/tools/terminal/session_factory.py +20 -0
- autobyteus/tools/terminal/terminal_session_manager.py +226 -0
- autobyteus/tools/terminal/tools/__init__.py +13 -0
- autobyteus/tools/terminal/tools/get_process_output.py +81 -0
- autobyteus/tools/terminal/tools/run_bash.py +109 -0
- autobyteus/tools/terminal/tools/start_background_process.py +104 -0
- autobyteus/tools/terminal/tools/stop_background_process.py +67 -0
- autobyteus/tools/terminal/types.py +54 -0
- autobyteus/tools/terminal/wsl_tmux_session.py +221 -0
- autobyteus/tools/terminal/wsl_utils.py +156 -0
- autobyteus/tools/transaction_management/backup_handler.py +48 -0
- autobyteus/tools/transaction_management/operation_lifecycle_manager.py +62 -0
- autobyteus/tools/usage/__init__.py +1 -2
- autobyteus/tools/usage/formatters/__init__.py +17 -1
- autobyteus/tools/usage/formatters/base_formatter.py +8 -0
- autobyteus/tools/usage/formatters/default_xml_schema_formatter.py +2 -2
- autobyteus/tools/usage/formatters/mistral_json_schema_formatter.py +18 -0
- autobyteus/tools/usage/formatters/patch_file_xml_example_formatter.py +64 -0
- autobyteus/tools/usage/formatters/patch_file_xml_schema_formatter.py +31 -0
- autobyteus/tools/usage/formatters/run_bash_xml_example_formatter.py +32 -0
- autobyteus/tools/usage/formatters/run_bash_xml_schema_formatter.py +36 -0
- autobyteus/tools/usage/formatters/write_file_xml_example_formatter.py +53 -0
- autobyteus/tools/usage/formatters/write_file_xml_schema_formatter.py +31 -0
- autobyteus/tools/usage/providers/tool_manifest_provider.py +10 -10
- autobyteus/tools/usage/registries/__init__.py +1 -3
- autobyteus/tools/usage/registries/tool_formatting_registry.py +115 -8
- autobyteus/tools/usage/tool_schema_provider.py +51 -0
- autobyteus/tools/web/__init__.py +4 -0
- autobyteus/tools/web/read_url_tool.py +80 -0
- autobyteus/utils/diff_utils.py +271 -0
- autobyteus/utils/download_utils.py +109 -0
- autobyteus/utils/file_utils.py +57 -2
- autobyteus/utils/gemini_helper.py +56 -0
- autobyteus/utils/gemini_model_mapping.py +71 -0
- autobyteus/utils/llm_output_formatter.py +75 -0
- autobyteus/utils/tool_call_format.py +36 -0
- autobyteus/workflow/agentic_workflow.py +3 -3
- autobyteus/workflow/bootstrap_steps/agent_tool_injection_step.py +2 -2
- autobyteus/workflow/bootstrap_steps/base_workflow_bootstrap_step.py +2 -2
- autobyteus/workflow/bootstrap_steps/coordinator_initialization_step.py +2 -2
- autobyteus/workflow/bootstrap_steps/coordinator_prompt_preparation_step.py +3 -9
- autobyteus/workflow/bootstrap_steps/workflow_bootstrapper.py +6 -6
- autobyteus/workflow/bootstrap_steps/workflow_runtime_queue_initialization_step.py +2 -2
- autobyteus/workflow/context/workflow_context.py +3 -3
- autobyteus/workflow/context/workflow_runtime_state.py +5 -5
- autobyteus/workflow/events/workflow_event_dispatcher.py +5 -5
- autobyteus/workflow/handlers/lifecycle_workflow_event_handler.py +3 -3
- autobyteus/workflow/handlers/process_user_message_event_handler.py +5 -5
- autobyteus/workflow/handlers/tool_approval_workflow_event_handler.py +2 -2
- autobyteus/workflow/runtime/workflow_runtime.py +8 -8
- autobyteus/workflow/runtime/workflow_worker.py +3 -3
- autobyteus/workflow/status/__init__.py +11 -0
- autobyteus/workflow/status/workflow_status.py +19 -0
- autobyteus/workflow/status/workflow_status_manager.py +48 -0
- autobyteus/workflow/streaming/__init__.py +2 -2
- autobyteus/workflow/streaming/workflow_event_notifier.py +7 -7
- autobyteus/workflow/streaming/workflow_stream_event_payloads.py +4 -4
- autobyteus/workflow/streaming/workflow_stream_events.py +3 -3
- autobyteus/workflow/utils/wait_for_idle.py +4 -4
- autobyteus-1.2.3.dist-info/METADATA +293 -0
- autobyteus-1.2.3.dist-info/RECORD +600 -0
- {autobyteus-1.2.1.dist-info → autobyteus-1.2.3.dist-info}/WHEEL +1 -1
- {autobyteus-1.2.1.dist-info → autobyteus-1.2.3.dist-info}/top_level.txt +0 -1
- autobyteus/agent/bootstrap_steps/agent_runtime_queue_initialization_step.py +0 -57
- autobyteus/agent/hooks/__init__.py +0 -16
- autobyteus/agent/hooks/base_phase_hook.py +0 -78
- autobyteus/agent/hooks/hook_definition.py +0 -36
- autobyteus/agent/hooks/hook_meta.py +0 -37
- autobyteus/agent/hooks/hook_registry.py +0 -106
- autobyteus/agent/llm_response_processor/provider_aware_tool_usage_processor.py +0 -103
- autobyteus/agent/phases/__init__.py +0 -18
- autobyteus/agent/phases/discover.py +0 -53
- autobyteus/agent/phases/manager.py +0 -265
- autobyteus/agent/phases/transition_decorator.py +0 -40
- autobyteus/agent/phases/transition_info.py +0 -33
- autobyteus/agent/remote_agent.py +0 -244
- autobyteus/agent/workspace/workspace_definition.py +0 -36
- autobyteus/agent/workspace/workspace_meta.py +0 -37
- autobyteus/agent/workspace/workspace_registry.py +0 -72
- autobyteus/agent_team/bootstrap_steps/agent_team_runtime_queue_initialization_step.py +0 -25
- autobyteus/agent_team/bootstrap_steps/coordinator_prompt_preparation_step.py +0 -85
- autobyteus/agent_team/phases/__init__.py +0 -11
- autobyteus/agent_team/phases/agent_team_operational_phase.py +0 -19
- autobyteus/agent_team/phases/agent_team_phase_manager.py +0 -48
- autobyteus/llm/api/bedrock_llm.py +0 -92
- autobyteus/llm/api/groq_llm.py +0 -94
- autobyteus/llm/api/nvidia_llm.py +0 -108
- autobyteus/llm/utils/token_pricing_config.py +0 -87
- autobyteus/rpc/__init__.py +0 -73
- autobyteus/rpc/client/__init__.py +0 -17
- autobyteus/rpc/client/abstract_client_connection.py +0 -124
- autobyteus/rpc/client/client_connection_manager.py +0 -153
- autobyteus/rpc/client/sse_client_connection.py +0 -306
- autobyteus/rpc/client/stdio_client_connection.py +0 -280
- autobyteus/rpc/config/__init__.py +0 -13
- autobyteus/rpc/config/agent_server_config.py +0 -153
- autobyteus/rpc/config/agent_server_registry.py +0 -152
- autobyteus/rpc/hosting.py +0 -244
- autobyteus/rpc/protocol.py +0 -244
- autobyteus/rpc/server/__init__.py +0 -20
- autobyteus/rpc/server/agent_server_endpoint.py +0 -181
- autobyteus/rpc/server/base_method_handler.py +0 -40
- autobyteus/rpc/server/method_handlers.py +0 -259
- autobyteus/rpc/server/sse_server_handler.py +0 -182
- autobyteus/rpc/server/stdio_server_handler.py +0 -151
- autobyteus/rpc/server_main.py +0 -198
- autobyteus/rpc/transport_type.py +0 -13
- autobyteus/tools/bash/__init__.py +0 -2
- autobyteus/tools/bash/bash_executor.py +0 -100
- autobyteus/tools/browser/__init__.py +0 -2
- autobyteus/tools/browser/session_aware/browser_session_aware_navigate_to.py +0 -75
- autobyteus/tools/browser/session_aware/browser_session_aware_tool.py +0 -30
- autobyteus/tools/browser/session_aware/browser_session_aware_web_element_trigger.py +0 -154
- autobyteus/tools/browser/session_aware/browser_session_aware_webpage_reader.py +0 -89
- autobyteus/tools/browser/session_aware/browser_session_aware_webpage_screenshot_taker.py +0 -107
- autobyteus/tools/browser/session_aware/factory/browser_session_aware_web_element_trigger_factory.py +0 -14
- autobyteus/tools/browser/session_aware/factory/browser_session_aware_webpage_reader_factory.py +0 -26
- autobyteus/tools/browser/session_aware/factory/browser_session_aware_webpage_screenshot_taker_factory.py +0 -14
- autobyteus/tools/browser/session_aware/shared_browser_session.py +0 -11
- autobyteus/tools/browser/session_aware/shared_browser_session_manager.py +0 -25
- autobyteus/tools/browser/session_aware/web_element_action.py +0 -20
- autobyteus/tools/browser/standalone/__init__.py +0 -6
- autobyteus/tools/browser/standalone/factory/__init__.py +0 -0
- autobyteus/tools/browser/standalone/factory/webpage_reader_factory.py +0 -25
- autobyteus/tools/browser/standalone/factory/webpage_screenshot_taker_factory.py +0 -14
- autobyteus/tools/browser/standalone/navigate_to.py +0 -84
- autobyteus/tools/browser/standalone/web_page_pdf_generator.py +0 -101
- autobyteus/tools/browser/standalone/webpage_image_downloader.py +0 -169
- autobyteus/tools/browser/standalone/webpage_reader.py +0 -105
- autobyteus/tools/browser/standalone/webpage_screenshot_taker.py +0 -105
- autobyteus/tools/file/edit_file.py +0 -200
- autobyteus/tools/file/list_directory.py +0 -168
- autobyteus/tools/file/search_files.py +0 -188
- autobyteus/tools/timer.py +0 -175
- autobyteus/tools/usage/parsers/__init__.py +0 -22
- autobyteus/tools/usage/parsers/_json_extractor.py +0 -99
- autobyteus/tools/usage/parsers/_string_decoders.py +0 -18
- autobyteus/tools/usage/parsers/anthropic_xml_tool_usage_parser.py +0 -10
- autobyteus/tools/usage/parsers/base_parser.py +0 -41
- autobyteus/tools/usage/parsers/default_json_tool_usage_parser.py +0 -83
- autobyteus/tools/usage/parsers/default_xml_tool_usage_parser.py +0 -316
- autobyteus/tools/usage/parsers/exceptions.py +0 -13
- autobyteus/tools/usage/parsers/gemini_json_tool_usage_parser.py +0 -77
- autobyteus/tools/usage/parsers/openai_json_tool_usage_parser.py +0 -149
- autobyteus/tools/usage/parsers/provider_aware_tool_usage_parser.py +0 -59
- autobyteus/tools/usage/registries/tool_usage_parser_registry.py +0 -62
- autobyteus/workflow/phases/__init__.py +0 -11
- autobyteus/workflow/phases/workflow_operational_phase.py +0 -19
- autobyteus/workflow/phases/workflow_phase_manager.py +0 -48
- autobyteus-1.2.1.dist-info/METADATA +0 -205
- autobyteus-1.2.1.dist-info/RECORD +0 -511
- examples/__init__.py +0 -1
- examples/agent_team/__init__.py +0 -1
- examples/discover_phase_transitions.py +0 -104
- examples/run_agentic_software_engineer.py +0 -239
- examples/run_browser_agent.py +0 -262
- examples/run_google_slides_agent.py +0 -287
- examples/run_mcp_browser_client.py +0 -174
- examples/run_mcp_google_slides_client.py +0 -270
- examples/run_mcp_list_tools.py +0 -189
- examples/run_poem_writer.py +0 -284
- examples/run_sqlite_agent.py +0 -295
- /autobyteus/{tools/browser/session_aware → skills}/__init__.py +0 -0
- /autobyteus/tools/{browser/session_aware/factory → skill}/__init__.py +0 -0
- {autobyteus-1.2.1.dist-info → autobyteus-1.2.3.dist-info}/licenses/LICENSE +0 -0
|
@@ -38,7 +38,8 @@ class ToolDefinition:
|
|
|
38
38
|
config_schema_provider: Callable[[], Optional['ParameterSchema']],
|
|
39
39
|
tool_class: Optional[Type['BaseTool']] = None,
|
|
40
40
|
custom_factory: Optional[Callable[['ToolConfig'], 'BaseTool']] = None,
|
|
41
|
-
metadata: Optional[Dict[str, Any]] = None
|
|
41
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
42
|
+
description_provider: Optional[Callable[[], str]] = None):
|
|
42
43
|
"""
|
|
43
44
|
Initializes the ToolDefinition.
|
|
44
45
|
"""
|
|
@@ -69,6 +70,19 @@ class ToolDefinition:
|
|
|
69
70
|
raise ValueError(f"ToolDefinition '{name}' with origin MCP must provide a 'mcp_server_id' in its metadata.")
|
|
70
71
|
|
|
71
72
|
self._name = name
|
|
73
|
+
# Prefer an explicit description provider, otherwise derive one from the tool class when available.
|
|
74
|
+
if description_provider is not None and not callable(description_provider):
|
|
75
|
+
raise TypeError(f"ToolDefinition '{name}' requires a callable for 'description_provider' if provided.")
|
|
76
|
+
|
|
77
|
+
if description_provider:
|
|
78
|
+
self._description_provider = description_provider
|
|
79
|
+
elif tool_class is not None and hasattr(tool_class, "get_description") and callable(getattr(tool_class, "get_description")):
|
|
80
|
+
# Use the tool class' get_description as a dynamic provider by default.
|
|
81
|
+
self._description_provider = tool_class.get_description
|
|
82
|
+
else:
|
|
83
|
+
# Fall back to a static description provider.
|
|
84
|
+
self._description_provider = lambda: description
|
|
85
|
+
|
|
72
86
|
self._description = description
|
|
73
87
|
self._tool_class = tool_class
|
|
74
88
|
self._custom_factory = custom_factory
|
|
@@ -136,15 +150,42 @@ class ToolDefinition:
|
|
|
136
150
|
def reload_cached_schema(self) -> None:
|
|
137
151
|
"""
|
|
138
152
|
Actively re-generates the schemas from their providers and updates the cache.
|
|
139
|
-
This is an eager operation.
|
|
153
|
+
Also refreshes the description if a provider is available. This is an eager operation.
|
|
140
154
|
"""
|
|
141
155
|
logger.info(f"Eagerly reloading schema cache for tool '{self.name}'.")
|
|
156
|
+
self._reload_description()
|
|
142
157
|
self._cached_argument_schema = _CACHE_NOT_SET
|
|
143
158
|
self._cached_config_schema = _CACHE_NOT_SET
|
|
144
159
|
# The schemas will be regenerated on the next property access.
|
|
145
160
|
# To make it fully eager, we can trigger the access here.
|
|
146
161
|
_ = self.argument_schema
|
|
147
162
|
_ = self.config_schema
|
|
163
|
+
|
|
164
|
+
def _reload_description(self) -> None:
|
|
165
|
+
"""
|
|
166
|
+
Refreshes the cached description using the provider if available.
|
|
167
|
+
"""
|
|
168
|
+
if not self._description_provider:
|
|
169
|
+
return
|
|
170
|
+
try:
|
|
171
|
+
new_description = self._description_provider()
|
|
172
|
+
if isinstance(new_description, str) and new_description:
|
|
173
|
+
if new_description != self._description:
|
|
174
|
+
logger.info(
|
|
175
|
+
f"Description for tool '{self.name}' updated during reload."
|
|
176
|
+
)
|
|
177
|
+
self._description = new_description
|
|
178
|
+
else:
|
|
179
|
+
logger.warning(
|
|
180
|
+
f"Description provider for tool '{self.name}' returned an invalid value. "
|
|
181
|
+
"Keeping existing description."
|
|
182
|
+
)
|
|
183
|
+
except Exception as exc:
|
|
184
|
+
logger.warning(
|
|
185
|
+
f"Failed to refresh description for tool '{self.name}' during reload: {exc}. "
|
|
186
|
+
"Keeping existing description.",
|
|
187
|
+
exc_info=True
|
|
188
|
+
)
|
|
148
189
|
|
|
149
190
|
# --- Convenience Schema/Example Generation API (using default formatters) ---
|
|
150
191
|
def get_usage_xml(self, provider: Optional[LLMProvider] = None) -> str:
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import TYPE_CHECKING
|
|
3
|
+
from autobyteus.tools import tool
|
|
4
|
+
from autobyteus.tools.tool_category import ToolCategory
|
|
5
|
+
from autobyteus.skills.registry import SkillRegistry
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from autobyteus.agent.context import AgentContext
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
@tool(name="load_skill", category=ToolCategory.GENERAL)
|
|
13
|
+
async def load_skill(context: 'AgentContext', skill_name: str) -> str:
|
|
14
|
+
"""
|
|
15
|
+
Loads a skill's entry point (SKILL.md) and provides its root path context.
|
|
16
|
+
Use this to understand a specialized skill's capabilities and internal assets.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
skill_name: The registered name of the skill (e.g., 'java_expert') or a path to a skill directory.
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
A formatted context block containing the skill's map, its absolute root path, and path resolution guidance.
|
|
23
|
+
"""
|
|
24
|
+
logger.debug(f"Tool 'load_skill' called for skill: {skill_name}")
|
|
25
|
+
registry = SkillRegistry()
|
|
26
|
+
skill = registry.get_skill(skill_name)
|
|
27
|
+
|
|
28
|
+
if not skill:
|
|
29
|
+
# Fallback: check if skill_name is actually a path
|
|
30
|
+
logger.debug(f"Skill '{skill_name}' not found in registry. Attempting to register from path.")
|
|
31
|
+
try:
|
|
32
|
+
skill = registry.register_skill_from_path(skill_name)
|
|
33
|
+
except Exception as e:
|
|
34
|
+
error_msg = f"Skill '{skill_name}' not found and is not a valid skill path: {e}"
|
|
35
|
+
logger.error(error_msg)
|
|
36
|
+
raise ValueError(error_msg)
|
|
37
|
+
|
|
38
|
+
logger.info(f"Skill '{skill.name}' successfully loaded.")
|
|
39
|
+
return f"""## Skill: {skill.name}
|
|
40
|
+
Root Path: {skill.root_path}
|
|
41
|
+
|
|
42
|
+
> **CRITICAL: Path Resolution When Using Tools**
|
|
43
|
+
>
|
|
44
|
+
> This skill uses relative paths. When using any tool that requires a file path,
|
|
45
|
+
> you MUST first construct the full absolute path by combining the Root Path above
|
|
46
|
+
> with the relative path from the skill instructions.
|
|
47
|
+
>
|
|
48
|
+
> **Example:** Root Path + `./scripts/format.sh` = `{skill.root_path}/scripts/format.sh`
|
|
49
|
+
|
|
50
|
+
{skill.content}"""
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Terminal tools package.
|
|
3
|
+
|
|
4
|
+
Provides PTY-based terminal operations for agents with stateful
|
|
5
|
+
command execution and background process management.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
|
|
10
|
+
from autobyteus.tools.terminal.types import (
|
|
11
|
+
TerminalResult,
|
|
12
|
+
BackgroundProcessOutput,
|
|
13
|
+
ProcessInfo,
|
|
14
|
+
)
|
|
15
|
+
from autobyteus.tools.terminal.output_buffer import OutputBuffer
|
|
16
|
+
from autobyteus.tools.terminal.prompt_detector import PromptDetector
|
|
17
|
+
from autobyteus.tools.terminal.terminal_session_manager import TerminalSessionManager
|
|
18
|
+
from autobyteus.tools.terminal.background_process_manager import BackgroundProcessManager
|
|
19
|
+
from autobyteus.tools.terminal.session_factory import get_default_session_factory
|
|
20
|
+
|
|
21
|
+
PtySession = None
|
|
22
|
+
WslTmuxSession = None
|
|
23
|
+
|
|
24
|
+
if os.name != "nt":
|
|
25
|
+
from autobyteus.tools.terminal.pty_session import PtySession
|
|
26
|
+
else:
|
|
27
|
+
from autobyteus.tools.terminal.wsl_tmux_session import WslTmuxSession
|
|
28
|
+
|
|
29
|
+
__all__ = [
|
|
30
|
+
# Types
|
|
31
|
+
"TerminalResult",
|
|
32
|
+
"BackgroundProcessOutput",
|
|
33
|
+
"ProcessInfo",
|
|
34
|
+
# Components
|
|
35
|
+
"OutputBuffer",
|
|
36
|
+
"PromptDetector",
|
|
37
|
+
"TerminalSessionManager",
|
|
38
|
+
"BackgroundProcessManager",
|
|
39
|
+
"get_default_session_factory",
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
if PtySession is not None:
|
|
43
|
+
__all__.append("PtySession")
|
|
44
|
+
if WslTmuxSession is not None:
|
|
45
|
+
__all__.append("WslTmuxSession")
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ANSI escape sequence utilities for cleaning terminal output.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
|
|
7
|
+
# Regex pattern for ANSI escape sequences
|
|
8
|
+
# Matches: ESC[...m (colors), ESC[...H (cursor), etc.
|
|
9
|
+
_ANSI_ESCAPE_PATTERN = re.compile(r'\x1b\[[0-9;?]*[a-zA-Z]|\x1b\][^\x07]*\x07|\x1b[=>]')
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def strip_ansi_codes(text: str) -> str:
|
|
13
|
+
"""Remove ANSI escape sequences from text.
|
|
14
|
+
|
|
15
|
+
Strips color codes, cursor control, and other terminal escape
|
|
16
|
+
sequences, leaving only the plain text content.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
text: String potentially containing ANSI escape codes.
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
Clean string with ANSI codes removed.
|
|
23
|
+
|
|
24
|
+
Examples:
|
|
25
|
+
>>> strip_ansi_codes('\x1b[31mRed text\x1b[0m')
|
|
26
|
+
'Red text'
|
|
27
|
+
>>> strip_ansi_codes('Hello\x1b[1;32m World\x1b[0m')
|
|
28
|
+
'Hello World'
|
|
29
|
+
"""
|
|
30
|
+
if not text:
|
|
31
|
+
return text
|
|
32
|
+
return _ANSI_ESCAPE_PATTERN.sub('', text)
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Background Process Manager for long-running processes.
|
|
3
|
+
|
|
4
|
+
Manages multiple PTY sessions for background processes like servers,
|
|
5
|
+
with output buffering and lifecycle management.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import asyncio
|
|
11
|
+
import logging
|
|
12
|
+
import time
|
|
13
|
+
import uuid
|
|
14
|
+
from typing import Callable, Dict, Optional
|
|
15
|
+
|
|
16
|
+
from autobyteus.tools.terminal.output_buffer import OutputBuffer
|
|
17
|
+
from autobyteus.tools.terminal.session_factory import get_default_session_factory
|
|
18
|
+
from autobyteus.tools.terminal.types import BackgroundProcessOutput, ProcessInfo
|
|
19
|
+
from autobyteus.tools.terminal.ansi_utils import strip_ansi_codes
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class BackgroundProcess:
|
|
25
|
+
"""Wrapper for a background process with its PTY and output buffer."""
|
|
26
|
+
|
|
27
|
+
def __init__(
|
|
28
|
+
self,
|
|
29
|
+
process_id: str,
|
|
30
|
+
command: str,
|
|
31
|
+
session: object,
|
|
32
|
+
output_buffer: OutputBuffer
|
|
33
|
+
):
|
|
34
|
+
self.process_id = process_id
|
|
35
|
+
self.command = command
|
|
36
|
+
self.session = session
|
|
37
|
+
self.output_buffer = output_buffer
|
|
38
|
+
self.started_at = time.time()
|
|
39
|
+
self._reader_task: Optional[asyncio.Task] = None
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def is_running(self) -> bool:
|
|
43
|
+
return self.session.is_alive
|
|
44
|
+
|
|
45
|
+
def to_info(self) -> ProcessInfo:
|
|
46
|
+
return ProcessInfo(
|
|
47
|
+
process_id=self.process_id,
|
|
48
|
+
command=self.command,
|
|
49
|
+
started_at=self.started_at,
|
|
50
|
+
is_running=self.is_running
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class BackgroundProcessManager:
|
|
55
|
+
"""Manages background processes (servers, watchers, etc.).
|
|
56
|
+
|
|
57
|
+
Each background process runs in its own PTY session with
|
|
58
|
+
continuous output buffering.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
def __init__(
|
|
62
|
+
self,
|
|
63
|
+
session_factory: Callable[[str], object] = None,
|
|
64
|
+
max_output_bytes: int = 1_000_000
|
|
65
|
+
):
|
|
66
|
+
"""Initialize the background process manager.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
session_factory: Factory function to create PtySession instances.
|
|
70
|
+
max_output_bytes: Maximum bytes to buffer per process.
|
|
71
|
+
"""
|
|
72
|
+
self._session_factory = session_factory or get_default_session_factory()
|
|
73
|
+
self._max_output_bytes = max_output_bytes
|
|
74
|
+
self._processes: Dict[str, BackgroundProcess] = {}
|
|
75
|
+
self._counter = 0
|
|
76
|
+
|
|
77
|
+
def _generate_id(self) -> str:
|
|
78
|
+
"""Generate a unique process ID."""
|
|
79
|
+
self._counter += 1
|
|
80
|
+
return f"bg_{self._counter:03d}"
|
|
81
|
+
|
|
82
|
+
async def start_process(self, command: str, cwd: str) -> str:
|
|
83
|
+
"""Start a long-running process in the background.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
command: The command to run.
|
|
87
|
+
cwd: Working directory for the process.
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
Process ID that can be used to reference this process.
|
|
91
|
+
"""
|
|
92
|
+
process_id = self._generate_id()
|
|
93
|
+
session_id = f"bg-{uuid.uuid4().hex[:8]}"
|
|
94
|
+
|
|
95
|
+
session = self._session_factory(session_id)
|
|
96
|
+
output_buffer = OutputBuffer(max_bytes=self._max_output_bytes)
|
|
97
|
+
|
|
98
|
+
# Start the session
|
|
99
|
+
await session.start(cwd)
|
|
100
|
+
|
|
101
|
+
# Create process wrapper
|
|
102
|
+
bg_process = BackgroundProcess(
|
|
103
|
+
process_id=process_id,
|
|
104
|
+
command=command,
|
|
105
|
+
session=session,
|
|
106
|
+
output_buffer=output_buffer
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
# Write the command
|
|
110
|
+
if not command.endswith('\n'):
|
|
111
|
+
command += '\n'
|
|
112
|
+
await session.write(command.encode('utf-8'))
|
|
113
|
+
|
|
114
|
+
# Start background reader task
|
|
115
|
+
bg_process._reader_task = asyncio.create_task(
|
|
116
|
+
self._read_loop(bg_process)
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
self._processes[process_id] = bg_process
|
|
120
|
+
logger.info(f"Started background process {process_id}: {command.strip()}")
|
|
121
|
+
|
|
122
|
+
return process_id
|
|
123
|
+
|
|
124
|
+
async def _read_loop(self, process: BackgroundProcess) -> None:
|
|
125
|
+
"""Background task that continuously reads output from the PTY.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
process: The background process to read from.
|
|
129
|
+
"""
|
|
130
|
+
try:
|
|
131
|
+
while process.session.is_alive:
|
|
132
|
+
try:
|
|
133
|
+
data = await process.session.read(timeout=0.1)
|
|
134
|
+
if data:
|
|
135
|
+
process.output_buffer.append(data)
|
|
136
|
+
except Exception as e:
|
|
137
|
+
logger.debug(f"Read error for {process.process_id}: {e}")
|
|
138
|
+
break
|
|
139
|
+
|
|
140
|
+
await asyncio.sleep(0.05)
|
|
141
|
+
except asyncio.CancelledError:
|
|
142
|
+
pass
|
|
143
|
+
except Exception as e:
|
|
144
|
+
logger.error(f"Error in read loop for {process.process_id}: {e}")
|
|
145
|
+
|
|
146
|
+
def get_output(
|
|
147
|
+
self,
|
|
148
|
+
process_id: str,
|
|
149
|
+
lines: int = 100
|
|
150
|
+
) -> BackgroundProcessOutput:
|
|
151
|
+
"""Get recent output from a background process.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
process_id: ID of the process.
|
|
155
|
+
lines: Number of recent lines to return.
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
BackgroundProcessOutput with output and status.
|
|
159
|
+
|
|
160
|
+
Raises:
|
|
161
|
+
KeyError: If process_id not found.
|
|
162
|
+
"""
|
|
163
|
+
if process_id not in self._processes:
|
|
164
|
+
raise KeyError(f"Process {process_id} not found")
|
|
165
|
+
|
|
166
|
+
process = self._processes[process_id]
|
|
167
|
+
raw_output = process.output_buffer.get_lines(lines)
|
|
168
|
+
clean_output = strip_ansi_codes(raw_output)
|
|
169
|
+
return BackgroundProcessOutput(
|
|
170
|
+
output=clean_output,
|
|
171
|
+
is_running=process.is_running,
|
|
172
|
+
process_id=process_id
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
async def stop_process(self, process_id: str) -> bool:
|
|
176
|
+
"""Stop a background process.
|
|
177
|
+
|
|
178
|
+
Sends SIGTERM, then SIGKILL if necessary.
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
process_id: ID of the process to stop.
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
True if process was found and stopped, False if not found.
|
|
185
|
+
"""
|
|
186
|
+
if process_id not in self._processes:
|
|
187
|
+
return False
|
|
188
|
+
|
|
189
|
+
process = self._processes.pop(process_id)
|
|
190
|
+
|
|
191
|
+
# Cancel reader task
|
|
192
|
+
if process._reader_task:
|
|
193
|
+
process._reader_task.cancel()
|
|
194
|
+
try:
|
|
195
|
+
await process._reader_task
|
|
196
|
+
except asyncio.CancelledError:
|
|
197
|
+
pass
|
|
198
|
+
|
|
199
|
+
# Close the PTY session
|
|
200
|
+
await process.session.close()
|
|
201
|
+
|
|
202
|
+
logger.info(f"Stopped background process {process_id}")
|
|
203
|
+
return True
|
|
204
|
+
|
|
205
|
+
async def stop_all(self) -> int:
|
|
206
|
+
"""Stop all background processes.
|
|
207
|
+
|
|
208
|
+
Returns:
|
|
209
|
+
Number of processes stopped.
|
|
210
|
+
"""
|
|
211
|
+
count = len(self._processes)
|
|
212
|
+
process_ids = list(self._processes.keys())
|
|
213
|
+
|
|
214
|
+
for process_id in process_ids:
|
|
215
|
+
await self.stop_process(process_id)
|
|
216
|
+
|
|
217
|
+
return count
|
|
218
|
+
|
|
219
|
+
def list_processes(self) -> Dict[str, ProcessInfo]:
|
|
220
|
+
"""List all background processes.
|
|
221
|
+
|
|
222
|
+
Returns:
|
|
223
|
+
Dict mapping process_id to ProcessInfo.
|
|
224
|
+
"""
|
|
225
|
+
return {
|
|
226
|
+
pid: proc.to_info()
|
|
227
|
+
for pid, proc in self._processes.items()
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
@property
|
|
231
|
+
def process_count(self) -> int:
|
|
232
|
+
"""Number of managed processes."""
|
|
233
|
+
return len(self._processes)
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Ring buffer for capturing terminal output.
|
|
3
|
+
|
|
4
|
+
Provides bounded memory storage for command output with
|
|
5
|
+
support for retrieving recent lines.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import threading
|
|
9
|
+
from collections import deque
|
|
10
|
+
from typing import List
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class OutputBuffer:
|
|
14
|
+
"""Ring buffer that stores output with bounded memory.
|
|
15
|
+
|
|
16
|
+
Thread-safe buffer for capturing terminal output. Stores data
|
|
17
|
+
up to a maximum byte limit, discarding oldest data when full.
|
|
18
|
+
|
|
19
|
+
Attributes:
|
|
20
|
+
max_bytes: Maximum bytes to store before discarding old data.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(self, max_bytes: int = 1_000_000):
|
|
24
|
+
"""Initialize the output buffer.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
max_bytes: Maximum bytes to store (default 1MB).
|
|
28
|
+
"""
|
|
29
|
+
self._max_bytes = max_bytes
|
|
30
|
+
self._buffer: deque = deque()
|
|
31
|
+
self._total_bytes = 0
|
|
32
|
+
self._lock = threading.Lock()
|
|
33
|
+
|
|
34
|
+
def append(self, data: bytes) -> None:
|
|
35
|
+
"""Append data to the buffer.
|
|
36
|
+
|
|
37
|
+
If adding data would exceed max_bytes, oldest lines are
|
|
38
|
+
discarded to make room.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
data: Bytes to append to the buffer.
|
|
42
|
+
"""
|
|
43
|
+
if not data:
|
|
44
|
+
return
|
|
45
|
+
|
|
46
|
+
with self._lock:
|
|
47
|
+
# Decode and split by lines, keeping line endings
|
|
48
|
+
try:
|
|
49
|
+
text = data.decode('utf-8', errors='replace')
|
|
50
|
+
except Exception:
|
|
51
|
+
text = str(data)
|
|
52
|
+
|
|
53
|
+
# Split into lines but keep partial lines
|
|
54
|
+
lines = text.splitlines(keepends=True)
|
|
55
|
+
|
|
56
|
+
for line in lines:
|
|
57
|
+
line_bytes = len(line.encode('utf-8'))
|
|
58
|
+
self._buffer.append(line)
|
|
59
|
+
self._total_bytes += line_bytes
|
|
60
|
+
|
|
61
|
+
# Trim oldest lines if over limit
|
|
62
|
+
while self._total_bytes > self._max_bytes and self._buffer:
|
|
63
|
+
removed = self._buffer.popleft()
|
|
64
|
+
self._total_bytes -= len(removed.encode('utf-8'))
|
|
65
|
+
|
|
66
|
+
def get_lines(self, n: int = 100) -> str:
|
|
67
|
+
"""Get the last n lines from the buffer.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
n: Number of lines to retrieve.
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
String containing the last n lines.
|
|
74
|
+
"""
|
|
75
|
+
with self._lock:
|
|
76
|
+
if n >= len(self._buffer):
|
|
77
|
+
return ''.join(self._buffer)
|
|
78
|
+
return ''.join(list(self._buffer)[-n:])
|
|
79
|
+
|
|
80
|
+
def get_all(self) -> str:
|
|
81
|
+
"""Get all content from the buffer.
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
String containing all buffered content.
|
|
85
|
+
"""
|
|
86
|
+
with self._lock:
|
|
87
|
+
return ''.join(self._buffer)
|
|
88
|
+
|
|
89
|
+
def clear(self) -> None:
|
|
90
|
+
"""Clear all content from the buffer."""
|
|
91
|
+
with self._lock:
|
|
92
|
+
self._buffer.clear()
|
|
93
|
+
self._total_bytes = 0
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
def size(self) -> int:
|
|
97
|
+
"""Current size of buffer in bytes."""
|
|
98
|
+
with self._lock:
|
|
99
|
+
return self._total_bytes
|
|
100
|
+
|
|
101
|
+
@property
|
|
102
|
+
def line_count(self) -> int:
|
|
103
|
+
"""Current number of lines in buffer."""
|
|
104
|
+
with self._lock:
|
|
105
|
+
return len(self._buffer)
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Prompt detection for terminal command completion.
|
|
3
|
+
|
|
4
|
+
Detects when a shell prompt has returned, indicating command completion.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import re
|
|
8
|
+
from typing import Optional
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class PromptDetector:
|
|
12
|
+
"""Detects when shell prompt returns after command execution.
|
|
13
|
+
|
|
14
|
+
Uses regex pattern matching to identify when the shell has
|
|
15
|
+
returned to a prompt state, indicating command completion.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
# Default pattern matches common prompts ending with $ or #
|
|
19
|
+
DEFAULT_PATTERN = r'[\$#]\s*$'
|
|
20
|
+
|
|
21
|
+
def __init__(self, prompt_pattern: Optional[str] = None):
|
|
22
|
+
"""Initialize the prompt detector.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
prompt_pattern: Regex pattern to match prompt.
|
|
26
|
+
Defaults to matching $ or # at end of line.
|
|
27
|
+
"""
|
|
28
|
+
self._pattern = prompt_pattern or self.DEFAULT_PATTERN
|
|
29
|
+
self._compiled = re.compile(self._pattern, re.MULTILINE)
|
|
30
|
+
|
|
31
|
+
def check(self, output: str) -> bool:
|
|
32
|
+
"""Check if output ends with a prompt.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
output: Terminal output to check.
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
True if output appears to end with a prompt.
|
|
39
|
+
"""
|
|
40
|
+
if not output:
|
|
41
|
+
return False
|
|
42
|
+
|
|
43
|
+
# Check the last few lines for a prompt
|
|
44
|
+
lines = output.rstrip().split('\n')
|
|
45
|
+
if not lines:
|
|
46
|
+
return False
|
|
47
|
+
|
|
48
|
+
last_line = lines[-1]
|
|
49
|
+
return bool(self._compiled.search(last_line))
|
|
50
|
+
|
|
51
|
+
def set_pattern(self, pattern: str) -> None:
|
|
52
|
+
"""Update the prompt pattern.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
pattern: New regex pattern to use.
|
|
56
|
+
"""
|
|
57
|
+
self._pattern = pattern
|
|
58
|
+
self._compiled = re.compile(self._pattern, re.MULTILINE)
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def pattern(self) -> str:
|
|
62
|
+
"""Current prompt pattern."""
|
|
63
|
+
return self._pattern
|