omnimancer-cli 0.2.24__tar.gz → 0.2.26__tar.gz
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.
- {omnimancer_cli-0.2.24/omnimancer_cli.egg-info → omnimancer_cli-0.2.26}/PKG-INFO +16 -3
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/README.md +10 -1
- omnimancer_cli-0.2.26/omn_fleet_hook.py +215 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/cli/agent_loop.py +84 -39
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/cli/display.py +3 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/cli/headless.py +154 -27
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/cli/interface.py +235 -35
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/cli/prompt_input.py +42 -11
- omnimancer_cli-0.2.26/omnimancer/cli/session.py +41 -0
- omnimancer_cli-0.2.26/omnimancer/cli/subagent.py +176 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/cli/tool_handler.py +39 -15
- omnimancer_cli-0.2.26/omnimancer/cli/turn_notify.py +170 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/agent/program_executor.py +17 -5
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/agent/status_core.py +5 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/agent/status_manager.py +42 -10
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/agent_engine.py +162 -5
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/agent_managers.py +12 -1
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/config_migration.py +1 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/engine.py +38 -1
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/hooks.py +4 -1
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/models.py +35 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/security/audit_logger.py +7 -0
- omnimancer_cli-0.2.26/omnimancer/events/__init__.py +1 -0
- omnimancer_cli-0.2.26/omnimancer/events/emitter.py +502 -0
- omnimancer_cli-0.2.26/omnimancer/events/jsonl_writer.py +362 -0
- omnimancer_cli-0.2.26/omnimancer/events/schema.py +143 -0
- omnimancer_cli-0.2.26/omnimancer/tui/__init__.py +5 -0
- omnimancer_cli-0.2.26/omnimancer/tui/fleet/__init__.py +1 -0
- omnimancer_cli-0.2.26/omnimancer/tui/fleet/app.py +882 -0
- omnimancer_cli-0.2.26/omnimancer/tui/fleet/cli.py +146 -0
- omnimancer_cli-0.2.26/omnimancer/tui/fleet/models.py +243 -0
- omnimancer_cli-0.2.26/omnimancer/tui/fleet/sources.py +309 -0
- omnimancer_cli-0.2.26/omnimancer/tui/fleet/widgets.py +222 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/ui/streaming_display.py +30 -7
- omnimancer_cli-0.2.26/omnimancer/ui/turn_activity.py +100 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26/omnimancer_cli.egg-info}/PKG-INFO +16 -3
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer_cli.egg-info/SOURCES.txt +15 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer_cli.egg-info/entry_points.txt +1 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer_cli.egg-info/requires.txt +6 -1
- omnimancer_cli-0.2.26/omnimancer_cli.egg-info/top_level.txt +2 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/pyproject.toml +25 -2
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_config_migration_passthrough.py +7 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_hooks.py +42 -1
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_program_executor.py +4 -3
- omnimancer_cli-0.2.24/omnimancer/cli/subagent.py +0 -125
- omnimancer_cli-0.2.24/omnimancer_cli.egg-info/top_level.txt +0 -1
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/LICENSE +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/__init__.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/__main__.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/cli/__init__.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/cli/approval_formatter.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/cli/approval_integration.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/cli/approval_prompt.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/cli/command_dispatch.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/cli/commands.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/cli/completion.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/cli/dynamic_commands.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/cli/file_mentions.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/cli/handlers/__init__.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/cli/pt_completion.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/cli/system_prompts.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/cli/usage.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/__init__.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/agent/__init__.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/agent/approval_context.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/agent/approval_interface.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/agent/approval_manager.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/agent/config.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/agent/file_system_manager.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/agent/read_before_write_errors.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/agent/tool_definitions.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/agent/types.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/agent/web_client.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/agent/workflow_orchestrator.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/agent_mode_manager.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/chat_manager.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/config_manager.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/config_validator.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/conversation_manager.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/env_loader.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/fallback_manager.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/health_monitor.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/history_manager.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/mcp_integration_layer.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/prompt_enhancer.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/provider_capabilities.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/provider_initializer.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/provider_registry.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/rate_limit_fallback.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/security/__init__.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/security/approval_workflow.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/security/permission_controller.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/security/permission_rules.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/security/sandbox_manager.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/security/security_manager.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/signal_handler.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/mcp/__init__.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/mcp/client.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/mcp/manager.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/providers/__init__.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/providers/azure.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/providers/base.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/providers/bedrock.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/providers/claude.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/providers/claude_code.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/providers/claude_credentials.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/providers/cohere.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/providers/digitalocean.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/providers/factory.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/providers/gemini.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/providers/mistral.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/providers/ollama.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/providers/openai.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/providers/openai_compatible.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/providers/openrouter.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/providers/perplexity.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/providers/vertex.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/providers/xai.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/ui/__init__.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/ui/cancellation_handler.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/ui/progress_indicator.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/utils/__init__.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/utils/error_handler.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/utils/errors.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/utils/retry.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer_cli.egg-info/dependency_links.txt +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/setup.cfg +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_approval_integration_compatibility.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_approval_preview_format.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_approval_remember.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_approval_system.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_azure_provider.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_bedrock_provider.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_claude_code_provider.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_claude_credentials.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_claude_provider.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_cohere_provider.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_command_approval_security.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_config_comprehensive.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_conversation_history.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_conversation_manager.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_core_engine.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_digitalocean_provider.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_enhanced_model_info.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_env_loader.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_error_handling_robustness.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_execution_timeout_errors.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_fallback_manager.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_fixtures.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_gemini_provider.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_httpx_compatibility.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_import_validation.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_interface_smoke_tests.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_mcp_integration.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_mcp_integration_layer.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_mcp_management_commands.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_mistral_provider.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_models.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_ollama_provider.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_openai_compatible_provider.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_openai_context_fit.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_openai_provider_tools.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_openai_request_timeout.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_openai_streaming.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_openrouter_provider.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_permission_rules.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_perplexity_provider.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_provider_aliases.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_provider_base.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_provider_capability_consistency.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_provider_discovery.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_provider_integration.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_provider_registry.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_rate_limit_fallback.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_retry.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_security_validation.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_system_integration.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_system_prompts.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_tool_calling_integration.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_tool_handler.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_vertex_provider.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_web_request_markers.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_workflows_comprehensive.py +0 -0
- {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_xai_provider.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: omnimancer-cli
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.26
|
|
4
4
|
Summary: A unified command-line interface for multiple AI language models
|
|
5
5
|
Author-email: Kellan Strong <kellan@jite.ai>
|
|
6
6
|
License: MIT
|
|
@@ -22,7 +22,7 @@ Requires-Python: >=3.10
|
|
|
22
22
|
Description-Content-Type: text/markdown
|
|
23
23
|
License-File: LICENSE
|
|
24
24
|
Requires-Dist: click>=8.0.0
|
|
25
|
-
Requires-Dist: rich>=
|
|
25
|
+
Requires-Dist: rich>=15.0.0
|
|
26
26
|
Requires-Dist: httpx>=0.24.0
|
|
27
27
|
Requires-Dist: pydantic>=2.0.0
|
|
28
28
|
Requires-Dist: cryptography>=41.0.0
|
|
@@ -35,10 +35,13 @@ Requires-Dist: beautifulsoup4>=4.12.0
|
|
|
35
35
|
Requires-Dist: html2text>=2020.1.16
|
|
36
36
|
Requires-Dist: jsonschema>=4.17.0
|
|
37
37
|
Requires-Dist: readability-lxml>=0.8.1
|
|
38
|
+
Requires-Dist: lxml_html_clean>=0.1.0
|
|
38
39
|
Requires-Dist: PyYAML>=6.0.0
|
|
39
40
|
Requires-Dist: toml>=0.10.2
|
|
40
41
|
Requires-Dist: mcp<2,>=1.0.0
|
|
41
42
|
Requires-Dist: prompt-toolkit<4.0,>=3.0.43
|
|
43
|
+
Provides-Extra: tui
|
|
44
|
+
Requires-Dist: textual>=8.0; extra == "tui"
|
|
42
45
|
Provides-Extra: dev
|
|
43
46
|
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
44
47
|
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
@@ -48,6 +51,7 @@ Requires-Dist: black>=23.0.0; extra == "dev"
|
|
|
48
51
|
Requires-Dist: isort>=5.12.0; extra == "dev"
|
|
49
52
|
Requires-Dist: flake8>=6.0.0; extra == "dev"
|
|
50
53
|
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
54
|
+
Requires-Dist: textual>=8.0; extra == "dev"
|
|
51
55
|
Dynamic: license-file
|
|
52
56
|
|
|
53
57
|
<p align="center">
|
|
@@ -129,7 +133,8 @@ object — including the tool calls the agent made along the way:
|
|
|
129
133
|
],
|
|
130
134
|
"usage": {"input_tokens": 1523, "output_tokens": 892, "total_cost_usd": 0.04},
|
|
131
135
|
"total_cost_usd": 0.04,
|
|
132
|
-
"stop_reason": "end_turn"
|
|
136
|
+
"stop_reason": "end_turn",
|
|
137
|
+
"stop_cause": "done"
|
|
133
138
|
}
|
|
134
139
|
```
|
|
135
140
|
|
|
@@ -139,6 +144,14 @@ On failure it stays valid JSON (stdout), with the error and any tool calls made:
|
|
|
139
144
|
{"type": "result", "subtype": "error", "is_error": true, "error": "…", "tool_calls": [...]}
|
|
140
145
|
```
|
|
141
146
|
|
|
147
|
+
`stop_cause` reports why the run ended: `done` (model declared completion), `nudge_exhausted` (model stopped acting without declaring completion), `max_iterations` (tool-iteration cap hit), or `repeat_abort` (aborted after repeating an identical tool call). Exit codes:
|
|
148
|
+
|
|
149
|
+
| Exit code | Meaning |
|
|
150
|
+
|---|---|
|
|
151
|
+
| 0 | Run completed (`stop_cause` of `done` or `nudge_exhausted`) |
|
|
152
|
+
| 1 | Provider or engine error (`subtype: "error"`) |
|
|
153
|
+
| 3 | Run truncated: `max_iterations` or `repeat_abort` — the result is partial |
|
|
154
|
+
|
|
142
155
|
For a live, line-by-line stream of what the agent is doing (assistant text,
|
|
143
156
|
each `tool_use`, each `tool_result`, then the final `result`), use
|
|
144
157
|
`--output-format stream-json`.
|
|
@@ -77,7 +77,8 @@ object — including the tool calls the agent made along the way:
|
|
|
77
77
|
],
|
|
78
78
|
"usage": {"input_tokens": 1523, "output_tokens": 892, "total_cost_usd": 0.04},
|
|
79
79
|
"total_cost_usd": 0.04,
|
|
80
|
-
"stop_reason": "end_turn"
|
|
80
|
+
"stop_reason": "end_turn",
|
|
81
|
+
"stop_cause": "done"
|
|
81
82
|
}
|
|
82
83
|
```
|
|
83
84
|
|
|
@@ -87,6 +88,14 @@ On failure it stays valid JSON (stdout), with the error and any tool calls made:
|
|
|
87
88
|
{"type": "result", "subtype": "error", "is_error": true, "error": "…", "tool_calls": [...]}
|
|
88
89
|
```
|
|
89
90
|
|
|
91
|
+
`stop_cause` reports why the run ended: `done` (model declared completion), `nudge_exhausted` (model stopped acting without declaring completion), `max_iterations` (tool-iteration cap hit), or `repeat_abort` (aborted after repeating an identical tool call). Exit codes:
|
|
92
|
+
|
|
93
|
+
| Exit code | Meaning |
|
|
94
|
+
|---|---|
|
|
95
|
+
| 0 | Run completed (`stop_cause` of `done` or `nudge_exhausted`) |
|
|
96
|
+
| 1 | Provider or engine error (`subtype: "error"`) |
|
|
97
|
+
| 3 | Run truncated: `max_iterations` or `repeat_abort` — the result is partial |
|
|
98
|
+
|
|
90
99
|
For a live, line-by-line stream of what the agent is doing (assistant text,
|
|
91
100
|
each `tool_use`, each `tool_result`, then the final `result`), use
|
|
92
101
|
`--output-format stream-json`.
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
"""Claude Code hook adapter for the omnimancer fleet event feed.
|
|
2
|
+
|
|
3
|
+
Registered in ``~/.claude/settings.json`` (all events, ``async: true``) as
|
|
4
|
+
the ``omn-fleet-hook`` console script. Each invocation reads ONE hook
|
|
5
|
+
payload from stdin, maps it to an omn.event.v1 line, and appends it to
|
|
6
|
+
``~/.omnimancer/events/omn-<session_id>.jsonl`` — the same files the
|
|
7
|
+
``omn fleet`` dashboard tails, so Claude Code sessions appear on the board
|
|
8
|
+
next to omn and codex agents.
|
|
9
|
+
|
|
10
|
+
DELIBERATELY a top-level, stdlib-only module: importing the omnimancer
|
|
11
|
+
package pulls pydantic and friends, and this process starts once per hook
|
|
12
|
+
event. Schema constants are duplicated from ``omnimancer/events/schema.py``
|
|
13
|
+
(the owner of the omn.event.v1 vocabulary) — keep them in sync.
|
|
14
|
+
|
|
15
|
+
Never blocks, never fails: any error exits 0 silently (a monitoring hook
|
|
16
|
+
must not be able to break a Claude session). ``OMNIMANCER_EVENTS=0``
|
|
17
|
+
disables it; ``OMNIMANCER_EVENTS_DIR`` overrides the output directory.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import json
|
|
23
|
+
import os
|
|
24
|
+
import re
|
|
25
|
+
import sys
|
|
26
|
+
from datetime import datetime, timezone
|
|
27
|
+
from typing import Any, Dict, Optional
|
|
28
|
+
|
|
29
|
+
# Owner of these values: omnimancer/events/schema.py (omn.event.v1).
|
|
30
|
+
SCHEMA_VERSION = 1
|
|
31
|
+
PREVIEW_CHARS = 200
|
|
32
|
+
MESSAGE_PREVIEW_CHARS = 500
|
|
33
|
+
|
|
34
|
+
MAX_STDIN_BYTES = 1_000_000
|
|
35
|
+
# Strict uuid shape: anything looser produces filenames that fall outside
|
|
36
|
+
# SESSION_FILE_RE and would escape the retention/budget sweeps entirely.
|
|
37
|
+
SESSION_ID_RE = re.compile(
|
|
38
|
+
r"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-" r"[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"
|
|
39
|
+
)
|
|
40
|
+
TARGET_KEYS = ("command", "file_path", "path", "url", "pattern")
|
|
41
|
+
|
|
42
|
+
# Claude hook_event_name -> omn.event.v1 event name.
|
|
43
|
+
EVENT_MAP = {
|
|
44
|
+
"SessionStart": "session_start",
|
|
45
|
+
"UserPromptSubmit": "turn_start",
|
|
46
|
+
"PreToolUse": "tool_start",
|
|
47
|
+
"PostToolUse": "tool_end",
|
|
48
|
+
"PostToolUseFailure": "tool_end",
|
|
49
|
+
"Stop": "turn_end",
|
|
50
|
+
"SessionEnd": "session_end",
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _truncate(text: str, limit: int) -> str:
|
|
55
|
+
"""Truncate to limit chars with a single-char ellipsis (schema rule)."""
|
|
56
|
+
if len(text) <= limit:
|
|
57
|
+
return text
|
|
58
|
+
return text[: limit - 1] + "…"
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _utc_now_iso() -> str:
|
|
62
|
+
"""UTC ISO8601 with millisecond precision (schema rule)."""
|
|
63
|
+
return datetime.now(timezone.utc).isoformat(timespec="milliseconds")
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _events_dir() -> str:
|
|
67
|
+
"""Resolve the events directory (env override for tests/relocation)."""
|
|
68
|
+
override = os.environ.get("OMNIMANCER_EVENTS_DIR")
|
|
69
|
+
if override:
|
|
70
|
+
return override
|
|
71
|
+
return os.path.join(os.path.expanduser("~"), ".omnimancer", "events")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _target_from(tool_input: Any) -> Optional[str]:
|
|
75
|
+
"""First actionable string from a tool_input dict, clipped."""
|
|
76
|
+
if not isinstance(tool_input, dict):
|
|
77
|
+
return None
|
|
78
|
+
for key in TARGET_KEYS:
|
|
79
|
+
value = tool_input.get(key)
|
|
80
|
+
if isinstance(value, str) and value:
|
|
81
|
+
return _truncate(value, PREVIEW_CHARS)
|
|
82
|
+
return None
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _build_data(payload: Dict[str, Any], event: str) -> Dict[str, Any]:
|
|
86
|
+
"""Per-event data payload (fields mirror the omn emitters)."""
|
|
87
|
+
hook_event = payload.get("hook_event_name")
|
|
88
|
+
if event == "session_start":
|
|
89
|
+
return {
|
|
90
|
+
"harness": "claude",
|
|
91
|
+
"model": payload.get("model"),
|
|
92
|
+
"source": payload.get("source"),
|
|
93
|
+
"permission_mode": payload.get("permission_mode"),
|
|
94
|
+
}
|
|
95
|
+
if event == "turn_start":
|
|
96
|
+
# Live payloads carry "prompt"; "prompt_text" kept as a fallback
|
|
97
|
+
# for older documented shapes (live-verified 2026-08-01).
|
|
98
|
+
prompt = payload.get("prompt") or payload.get("prompt_text")
|
|
99
|
+
return {
|
|
100
|
+
"prompt_preview": (
|
|
101
|
+
_truncate(prompt, PREVIEW_CHARS) if isinstance(prompt, str) else None
|
|
102
|
+
),
|
|
103
|
+
}
|
|
104
|
+
if event == "tool_start":
|
|
105
|
+
return {
|
|
106
|
+
"tool": payload.get("tool_name"),
|
|
107
|
+
"target": _target_from(payload.get("tool_input")),
|
|
108
|
+
"op_id": payload.get("tool_use_id"),
|
|
109
|
+
"invocation": "claude",
|
|
110
|
+
}
|
|
111
|
+
if event == "tool_end":
|
|
112
|
+
data: Dict[str, Any] = {
|
|
113
|
+
"tool": payload.get("tool_name"),
|
|
114
|
+
"op_id": payload.get("tool_use_id"),
|
|
115
|
+
"success": hook_event == "PostToolUse",
|
|
116
|
+
}
|
|
117
|
+
if hook_event == "PostToolUseFailure":
|
|
118
|
+
# Live payloads carry "error" (+ "is_interrupt" for user
|
|
119
|
+
# cancellation); "tool_output" kept as a fallback for older
|
|
120
|
+
# documented shapes (live-verified 2026-08-01).
|
|
121
|
+
error = payload.get("error") or payload.get("tool_output")
|
|
122
|
+
if isinstance(error, str):
|
|
123
|
+
data["error"] = _truncate(error, MESSAGE_PREVIEW_CHARS)
|
|
124
|
+
if payload.get("is_interrupt"):
|
|
125
|
+
data["was_cancelled"] = True
|
|
126
|
+
return data
|
|
127
|
+
if event == "turn_end":
|
|
128
|
+
message = payload.get("last_assistant_message")
|
|
129
|
+
return {
|
|
130
|
+
"last_message_preview": (
|
|
131
|
+
_truncate(message, MESSAGE_PREVIEW_CHARS)
|
|
132
|
+
if isinstance(message, str)
|
|
133
|
+
else None
|
|
134
|
+
),
|
|
135
|
+
}
|
|
136
|
+
if event == "session_end":
|
|
137
|
+
return {"reason": payload.get("reason")}
|
|
138
|
+
return {}
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _agent_identity(payload: Dict[str, Any]) -> tuple:
|
|
142
|
+
"""(agent_id, parent_id): subagent payloads carry agent_id/agent_type."""
|
|
143
|
+
agent_id = payload.get("agent_id")
|
|
144
|
+
if isinstance(agent_id, str) and agent_id:
|
|
145
|
+
agent_type = payload.get("agent_type") or "agent"
|
|
146
|
+
return f"subagent-{agent_type}-{agent_id[:8]}", "main"
|
|
147
|
+
return "main", None
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def build_line(payload: Dict[str, Any]) -> Optional[str]:
|
|
151
|
+
"""Map one Claude hook payload to an omn.event.v1 JSON line, or None."""
|
|
152
|
+
event = EVENT_MAP.get(str(payload.get("hook_event_name")))
|
|
153
|
+
if event is None:
|
|
154
|
+
return None
|
|
155
|
+
session_id = payload.get("session_id")
|
|
156
|
+
# fullmatch, not match: $ tolerates a trailing newline, which would
|
|
157
|
+
# mint a filename that escapes the retention/budget sweeps.
|
|
158
|
+
if not isinstance(session_id, str) or not SESSION_ID_RE.fullmatch(session_id):
|
|
159
|
+
return None
|
|
160
|
+
agent_id, parent_id = _agent_identity(payload)
|
|
161
|
+
envelope = {
|
|
162
|
+
"v": SCHEMA_VERSION,
|
|
163
|
+
"ts": _utc_now_iso(),
|
|
164
|
+
"event": event,
|
|
165
|
+
"session_id": session_id.lower(),
|
|
166
|
+
"agent_id": agent_id,
|
|
167
|
+
"parent_id": parent_id,
|
|
168
|
+
"pid": os.getpid(),
|
|
169
|
+
# -1: out-of-band producer (no shared counter across hook processes);
|
|
170
|
+
# consumers order by file-append position and ts.
|
|
171
|
+
"seq": -1,
|
|
172
|
+
"mode": "claude",
|
|
173
|
+
"cwd": payload.get("cwd"),
|
|
174
|
+
"data": _build_data(payload, event),
|
|
175
|
+
}
|
|
176
|
+
return json.dumps(envelope, ensure_ascii=False, separators=(",", ":"))
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def main() -> int:
|
|
180
|
+
"""Read one payload from stdin, append one line; always exit 0."""
|
|
181
|
+
try:
|
|
182
|
+
if os.environ.get("OMNIMANCER_EVENTS", "") == "0":
|
|
183
|
+
return 0
|
|
184
|
+
raw = sys.stdin.buffer.read(MAX_STDIN_BYTES)
|
|
185
|
+
payload = json.loads(raw.decode("utf-8", errors="replace"))
|
|
186
|
+
if not isinstance(payload, dict):
|
|
187
|
+
return 0
|
|
188
|
+
line = build_line(payload)
|
|
189
|
+
if line is None:
|
|
190
|
+
return 0
|
|
191
|
+
directory = _events_dir()
|
|
192
|
+
os.makedirs(directory, mode=0o700, exist_ok=True)
|
|
193
|
+
# mode= only applies at creation: repair pre-existing permissive
|
|
194
|
+
# dirs/files too — these paths carry prompt/command previews.
|
|
195
|
+
os.chmod(directory, 0o700)
|
|
196
|
+
path = os.path.join(directory, f"omn-{payload['session_id'].lower()}.jsonl")
|
|
197
|
+
fd = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_APPEND, 0o600)
|
|
198
|
+
try:
|
|
199
|
+
if hasattr(os, "fchmod"):
|
|
200
|
+
os.fchmod(fd, 0o600)
|
|
201
|
+
else:
|
|
202
|
+
# Windows has no fchmod; best-effort by path so the write
|
|
203
|
+
# below still happens instead of dying on AttributeError.
|
|
204
|
+
os.chmod(path, 0o600)
|
|
205
|
+
os.write(fd, (line + "\n").encode("utf-8"))
|
|
206
|
+
finally:
|
|
207
|
+
os.close(fd)
|
|
208
|
+
except Exception:
|
|
209
|
+
# A monitoring hook must never surface an error into the session.
|
|
210
|
+
pass
|
|
211
|
+
return 0
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
if __name__ == "__main__":
|
|
215
|
+
sys.exit(main())
|
|
@@ -10,13 +10,12 @@ import ipaddress
|
|
|
10
10
|
import logging
|
|
11
11
|
import re
|
|
12
12
|
import shlex
|
|
13
|
-
import subprocess
|
|
14
13
|
from difflib import SequenceMatcher
|
|
15
14
|
from pathlib import Path
|
|
16
|
-
from typing import Any, Optional
|
|
15
|
+
from typing import Any, Optional, cast
|
|
17
16
|
from urllib.parse import urlparse, urlunparse
|
|
18
17
|
|
|
19
|
-
from ..core.agent.types import Operation, OperationType
|
|
18
|
+
from ..core.agent.types import Operation, OperationResult, OperationType
|
|
20
19
|
|
|
21
20
|
# ---------------------------------------------------------------------------
|
|
22
21
|
# Web request constants and helpers
|
|
@@ -238,6 +237,7 @@ class AgentLoopMixin:
|
|
|
238
237
|
until complete.
|
|
239
238
|
"""
|
|
240
239
|
current_response = initial_response
|
|
240
|
+
self._turn_final_response = initial_response
|
|
241
241
|
|
|
242
242
|
while True:
|
|
243
243
|
self._show_assistant_message(
|
|
@@ -307,6 +307,8 @@ class AgentLoopMixin:
|
|
|
307
307
|
)
|
|
308
308
|
break
|
|
309
309
|
|
|
310
|
+
self._turn_final_response = next_response
|
|
311
|
+
|
|
310
312
|
done_indicators = [
|
|
311
313
|
"task is complete",
|
|
312
314
|
"analysis complete",
|
|
@@ -447,6 +449,44 @@ class AgentLoopMixin:
|
|
|
447
449
|
|
|
448
450
|
return best_match
|
|
449
451
|
|
|
452
|
+
async def _execute_marker_command(
|
|
453
|
+
self,
|
|
454
|
+
command: str,
|
|
455
|
+
args: list[str],
|
|
456
|
+
description: str,
|
|
457
|
+
) -> OperationResult:
|
|
458
|
+
"""Execute a marker command through the shared permission gate.
|
|
459
|
+
|
|
460
|
+
Args:
|
|
461
|
+
command: Executable name.
|
|
462
|
+
args: Command arguments without shell interpolation.
|
|
463
|
+
description: Human-readable operation description.
|
|
464
|
+
|
|
465
|
+
Returns:
|
|
466
|
+
The gated command result, or a failure if no agent engine exists.
|
|
467
|
+
"""
|
|
468
|
+
agent_engine = getattr(self.engine, "agent_engine", None)
|
|
469
|
+
if agent_engine is None:
|
|
470
|
+
return OperationResult(
|
|
471
|
+
success=False,
|
|
472
|
+
error="Agent engine not available - command execution disabled",
|
|
473
|
+
)
|
|
474
|
+
operation = Operation(
|
|
475
|
+
type=OperationType.COMMAND_EXECUTE,
|
|
476
|
+
description=description,
|
|
477
|
+
data={"command": command, "args": args},
|
|
478
|
+
requires_approval=False,
|
|
479
|
+
)
|
|
480
|
+
result = await agent_engine.execute_with_approval(operation)
|
|
481
|
+
return cast(OperationResult, result)
|
|
482
|
+
|
|
483
|
+
@staticmethod
|
|
484
|
+
def _marker_command_output(result: OperationResult) -> str:
|
|
485
|
+
"""Extract displayable stdout from an operation result."""
|
|
486
|
+
if isinstance(result.data, dict):
|
|
487
|
+
return str(result.data.get("stdout", ""))
|
|
488
|
+
return str(result.data or "")
|
|
489
|
+
|
|
450
490
|
async def _parse_and_execute_operations(self, response_content: str) -> str:
|
|
451
491
|
"""Parse model response for operation markers and execute them."""
|
|
452
492
|
updated_response = response_content
|
|
@@ -608,23 +648,28 @@ class AgentLoopMixin:
|
|
|
608
648
|
pattern = match.group(1).strip()
|
|
609
649
|
|
|
610
650
|
try:
|
|
611
|
-
result =
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
text=True,
|
|
616
|
-
timeout=10,
|
|
651
|
+
result = await self._execute_marker_command(
|
|
652
|
+
"find",
|
|
653
|
+
[".", "-name", pattern, "-type", "f"],
|
|
654
|
+
f"Find files matching: {pattern}",
|
|
617
655
|
)
|
|
618
|
-
|
|
619
|
-
if files:
|
|
656
|
+
if not result.success:
|
|
620
657
|
updated_response = updated_response.replace(
|
|
621
658
|
match.group(0),
|
|
622
|
-
f"
|
|
659
|
+
f"❌ Find failed: {result.error}",
|
|
623
660
|
)
|
|
624
661
|
else:
|
|
662
|
+
files = "\n".join(
|
|
663
|
+
self._marker_command_output(result).splitlines()[:20]
|
|
664
|
+
).strip()
|
|
665
|
+
if files:
|
|
666
|
+
replacement = (
|
|
667
|
+
f"🔍 Found files matching '{pattern}':\n{files}"
|
|
668
|
+
)
|
|
669
|
+
else:
|
|
670
|
+
replacement = f"🔍 No files found matching '{pattern}'"
|
|
625
671
|
updated_response = updated_response.replace(
|
|
626
|
-
match.group(0),
|
|
627
|
-
f"🔍 No files found matching '{pattern}'",
|
|
672
|
+
match.group(0), replacement
|
|
628
673
|
)
|
|
629
674
|
except Exception as e:
|
|
630
675
|
updated_response = updated_response.replace(
|
|
@@ -638,23 +683,26 @@ class AgentLoopMixin:
|
|
|
638
683
|
search_text = match.group(1).strip()
|
|
639
684
|
|
|
640
685
|
try:
|
|
641
|
-
result =
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
text=True,
|
|
646
|
-
timeout=10,
|
|
686
|
+
result = await self._execute_marker_command(
|
|
687
|
+
"grep",
|
|
688
|
+
["-r", "-n", search_text, "."],
|
|
689
|
+
f"Search files for: {search_text}",
|
|
647
690
|
)
|
|
648
|
-
|
|
649
|
-
if matches:
|
|
691
|
+
if not result.success:
|
|
650
692
|
updated_response = updated_response.replace(
|
|
651
693
|
match.group(0),
|
|
652
|
-
f"
|
|
694
|
+
f"❌ Search failed: {result.error}",
|
|
653
695
|
)
|
|
654
696
|
else:
|
|
697
|
+
matches = "\n".join(
|
|
698
|
+
self._marker_command_output(result).splitlines()[:20]
|
|
699
|
+
).strip()
|
|
700
|
+
if matches:
|
|
701
|
+
replacement = f"🔍 Found '{search_text}' in:\n{matches}"
|
|
702
|
+
else:
|
|
703
|
+
replacement = f"🔍 No matches found for '{search_text}'"
|
|
655
704
|
updated_response = updated_response.replace(
|
|
656
|
-
match.group(0),
|
|
657
|
-
f"🔍 No matches found for '{search_text}'",
|
|
705
|
+
match.group(0), replacement
|
|
658
706
|
)
|
|
659
707
|
except Exception as e:
|
|
660
708
|
updated_response = updated_response.replace(
|
|
@@ -707,22 +755,19 @@ class AgentLoopMixin:
|
|
|
707
755
|
|
|
708
756
|
if cmd_base in safe_commands:
|
|
709
757
|
try:
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
timeout=10,
|
|
716
|
-
executable="/bin/bash",
|
|
717
|
-
)
|
|
718
|
-
output = (
|
|
719
|
-
result.stdout.strip()
|
|
720
|
-
if result.returncode == 0
|
|
721
|
-
else f"Error: {result.stderr}"
|
|
758
|
+
parts = shlex.split(command)
|
|
759
|
+
result = await self._execute_marker_command(
|
|
760
|
+
parts[0],
|
|
761
|
+
parts[1:],
|
|
762
|
+
f"Execute safe command: {command[:50]}",
|
|
722
763
|
)
|
|
764
|
+
if result.success:
|
|
765
|
+
output = self._marker_command_output(result).strip()
|
|
766
|
+
replacement = f"✅ `{command}`\n{output}"
|
|
767
|
+
else:
|
|
768
|
+
replacement = f"❌ Failed: {result.error}"
|
|
723
769
|
updated_response = updated_response.replace(
|
|
724
|
-
match.group(0),
|
|
725
|
-
f"✅ `{command}`\n{output}",
|
|
770
|
+
match.group(0), replacement
|
|
726
771
|
)
|
|
727
772
|
except Exception as e:
|
|
728
773
|
updated_response = updated_response.replace(
|
|
@@ -412,6 +412,9 @@ Model available: {'Yes' if model_info else 'No'}"""
|
|
|
412
412
|
self.display_manager.show_message(message, MessageType.WARNING)
|
|
413
413
|
|
|
414
414
|
def _show_token_status(self, response: Any) -> None:
|
|
415
|
+
notifier = getattr(self, "turn_notifier", None)
|
|
416
|
+
if notifier is not None:
|
|
417
|
+
notifier.record_assistant(getattr(response, "content", None), response)
|
|
415
418
|
usage = getattr(self, "usage", None)
|
|
416
419
|
if usage is not None:
|
|
417
420
|
usage.add(response)
|