omnimancer-cli 0.3.4__tar.gz → 0.3.6__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.
Files changed (200) hide show
  1. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/PKG-INFO +1 -1
  2. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/cli/h2l_headless.py +17 -1
  3. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/cli/subagent.py +73 -22
  4. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/engine.py +56 -25
  5. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/env_loader.py +40 -5
  6. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/prompt_enhancer.py +12 -11
  7. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/provider_initializer.py +31 -1
  8. omnimancer_cli-0.3.6/omnimancer/core/provider_resolver.py +226 -0
  9. omnimancer_cli-0.3.6/omnimancer/h2l/refs.py +86 -0
  10. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/providers/bedrock.py +33 -12
  11. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer_cli.egg-info/PKG-INFO +1 -1
  12. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer_cli.egg-info/SOURCES.txt +1 -0
  13. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/pyproject.toml +1 -1
  14. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_bedrock_provider.py +103 -0
  15. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_env_loader.py +38 -1
  16. omnimancer_cli-0.3.4/omnimancer/h2l/refs.py +0 -135
  17. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/LICENSE +0 -0
  18. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/README.md +0 -0
  19. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omn_fleet_hook.py +0 -0
  20. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/__init__.py +0 -0
  21. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/__main__.py +0 -0
  22. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/cli/__init__.py +0 -0
  23. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/cli/agent_loop.py +0 -0
  24. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/cli/approval_formatter.py +0 -0
  25. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/cli/approval_integration.py +0 -0
  26. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/cli/approval_prompt.py +0 -0
  27. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/cli/command_dispatch.py +0 -0
  28. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/cli/commands.py +0 -0
  29. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/cli/completion.py +0 -0
  30. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/cli/display.py +0 -0
  31. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/cli/dynamic_commands.py +0 -0
  32. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/cli/file_mentions.py +0 -0
  33. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/cli/h2l_command.py +0 -0
  34. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/cli/handlers/__init__.py +0 -0
  35. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/cli/headless.py +0 -0
  36. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/cli/headless_checkpoint.py +0 -0
  37. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/cli/interface.py +0 -0
  38. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/cli/prompt_input.py +0 -0
  39. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/cli/pt_completion.py +0 -0
  40. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/cli/session.py +0 -0
  41. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/cli/system_prompts.py +0 -0
  42. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/cli/tool_handler.py +0 -0
  43. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/cli/turn_notify.py +0 -0
  44. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/cli/usage.py +0 -0
  45. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/__init__.py +0 -0
  46. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/agent/__init__.py +0 -0
  47. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/agent/approval_context.py +0 -0
  48. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/agent/approval_interface.py +0 -0
  49. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/agent/approval_manager.py +0 -0
  50. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/agent/config.py +0 -0
  51. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/agent/file_system_manager.py +0 -0
  52. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/agent/program_executor.py +0 -0
  53. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/agent/read_before_write_errors.py +0 -0
  54. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/agent/status_core.py +0 -0
  55. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/agent/status_manager.py +0 -0
  56. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/agent/tool_definitions.py +0 -0
  57. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/agent/types.py +0 -0
  58. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/agent/web_client.py +0 -0
  59. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/agent/workflow_orchestrator.py +0 -0
  60. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/agent_engine.py +0 -0
  61. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/agent_managers.py +0 -0
  62. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/agent_mode_manager.py +0 -0
  63. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/chat_manager.py +0 -0
  64. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/config_manager.py +0 -0
  65. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/config_migration.py +0 -0
  66. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/config_validator.py +0 -0
  67. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/conversation_manager.py +0 -0
  68. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/fallback_manager.py +0 -0
  69. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/health_monitor.py +0 -0
  70. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/history_manager.py +0 -0
  71. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/hooks.py +0 -0
  72. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/mcp_integration_layer.py +0 -0
  73. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/models.py +0 -0
  74. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/provider_capabilities.py +0 -0
  75. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/provider_registry.py +0 -0
  76. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/rate_limit_fallback.py +0 -0
  77. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/security/__init__.py +0 -0
  78. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/security/approval_workflow.py +0 -0
  79. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/security/audit_logger.py +0 -0
  80. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/security/permission_controller.py +0 -0
  81. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/security/permission_rules.py +0 -0
  82. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/security/sandbox_manager.py +0 -0
  83. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/security/security_manager.py +0 -0
  84. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/core/signal_handler.py +0 -0
  85. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/events/__init__.py +0 -0
  86. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/events/emitter.py +0 -0
  87. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/events/jsonl_writer.py +0 -0
  88. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/events/schema.py +0 -0
  89. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/h2l/__init__.py +0 -0
  90. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/h2l/judge.py +0 -0
  91. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/h2l/loop.py +0 -0
  92. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/h2l/models.py +0 -0
  93. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/h2l/orchestrator.py +0 -0
  94. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/h2l/planner.py +0 -0
  95. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/h2l/prompts.py +0 -0
  96. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/h2l/render.py +0 -0
  97. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/h2l/worker.py +0 -0
  98. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/mcp/__init__.py +0 -0
  99. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/mcp/client.py +0 -0
  100. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/mcp/manager.py +0 -0
  101. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/providers/__init__.py +0 -0
  102. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/providers/azure.py +0 -0
  103. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/providers/base.py +0 -0
  104. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/providers/cache_tokens.py +0 -0
  105. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/providers/claude.py +0 -0
  106. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/providers/claude_code.py +0 -0
  107. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/providers/claude_credentials.py +0 -0
  108. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/providers/cohere.py +0 -0
  109. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/providers/digitalocean.py +0 -0
  110. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/providers/factory.py +0 -0
  111. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/providers/gemini.py +0 -0
  112. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/providers/mistral.py +0 -0
  113. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/providers/ollama.py +0 -0
  114. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/providers/openai.py +0 -0
  115. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/providers/openai_compatible.py +0 -0
  116. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/providers/openrouter.py +0 -0
  117. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/providers/perplexity.py +0 -0
  118. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/providers/vertex.py +0 -0
  119. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/providers/xai.py +0 -0
  120. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/tui/__init__.py +0 -0
  121. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/tui/fleet/__init__.py +0 -0
  122. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/tui/fleet/app.py +0 -0
  123. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/tui/fleet/cli.py +0 -0
  124. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/tui/fleet/models.py +0 -0
  125. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/tui/fleet/sources.py +0 -0
  126. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/tui/fleet/widgets.py +0 -0
  127. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/ui/__init__.py +0 -0
  128. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/ui/cancellation_handler.py +0 -0
  129. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/ui/progress_indicator.py +0 -0
  130. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/ui/streaming_display.py +0 -0
  131. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/ui/turn_activity.py +0 -0
  132. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/utils/__init__.py +0 -0
  133. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/utils/error_handler.py +0 -0
  134. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/utils/errors.py +0 -0
  135. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer/utils/retry.py +0 -0
  136. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer_cli.egg-info/dependency_links.txt +0 -0
  137. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer_cli.egg-info/entry_points.txt +0 -0
  138. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer_cli.egg-info/requires.txt +0 -0
  139. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/omnimancer_cli.egg-info/top_level.txt +0 -0
  140. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/setup.cfg +0 -0
  141. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_approval_integration_compatibility.py +0 -0
  142. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_approval_preview_format.py +0 -0
  143. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_approval_remember.py +0 -0
  144. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_approval_system.py +0 -0
  145. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_azure_provider.py +0 -0
  146. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_claude_code_provider.py +0 -0
  147. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_claude_credentials.py +0 -0
  148. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_claude_prompt_cache.py +0 -0
  149. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_claude_provider.py +0 -0
  150. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_cohere_provider.py +0 -0
  151. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_command_approval_security.py +0 -0
  152. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_config_comprehensive.py +0 -0
  153. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_config_migration_passthrough.py +0 -0
  154. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_conversation_history.py +0 -0
  155. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_conversation_manager.py +0 -0
  156. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_core_engine.py +0 -0
  157. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_digitalocean_provider.py +0 -0
  158. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_enhanced_model_info.py +0 -0
  159. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_error_handling_robustness.py +0 -0
  160. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_execution_timeout_errors.py +0 -0
  161. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_fallback_manager.py +0 -0
  162. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_fixtures.py +0 -0
  163. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_gemini_provider.py +0 -0
  164. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_hooks.py +0 -0
  165. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_httpx_compatibility.py +0 -0
  166. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_import_validation.py +0 -0
  167. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_interface_smoke_tests.py +0 -0
  168. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_mcp_integration.py +0 -0
  169. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_mcp_integration_layer.py +0 -0
  170. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_mcp_management_commands.py +0 -0
  171. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_mistral_provider.py +0 -0
  172. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_models.py +0 -0
  173. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_ollama_provider.py +0 -0
  174. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_openai_compatible_provider.py +0 -0
  175. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_openai_context_fit.py +0 -0
  176. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_openai_provider_tools.py +0 -0
  177. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_openai_request_timeout.py +0 -0
  178. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_openai_streaming.py +0 -0
  179. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_openrouter_provider.py +0 -0
  180. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_permission_rules.py +0 -0
  181. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_perplexity_provider.py +0 -0
  182. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_program_executor.py +0 -0
  183. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_provider_aliases.py +0 -0
  184. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_provider_base.py +0 -0
  185. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_provider_cache_tokens.py +0 -0
  186. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_provider_capability_consistency.py +0 -0
  187. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_provider_discovery.py +0 -0
  188. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_provider_integration.py +0 -0
  189. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_provider_registry.py +0 -0
  190. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_rate_limit_fallback.py +0 -0
  191. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_retry.py +0 -0
  192. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_security_validation.py +0 -0
  193. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_system_integration.py +0 -0
  194. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_system_prompts.py +0 -0
  195. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_tool_calling_integration.py +0 -0
  196. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_tool_handler.py +0 -0
  197. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_vertex_provider.py +0 -0
  198. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_web_request_markers.py +0 -0
  199. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/tests/test_workflows_comprehensive.py +0 -0
  200. {omnimancer_cli-0.3.4 → omnimancer_cli-0.3.6}/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.3.4
3
+ Version: 0.3.6
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
@@ -13,6 +13,7 @@ from dataclasses import dataclass, field
13
13
  from pathlib import Path
14
14
  from typing import Any, Dict, List, Optional
15
15
 
16
+ from ..core.env_loader import apply_env_overrides
16
17
  from ..core.models import ChatResponse, Config
17
18
  from ..h2l.models import (
18
19
  H2LConfig,
@@ -48,13 +49,28 @@ class H2LOptions:
48
49
  plan_iterations: Optional[int] = None
49
50
 
50
51
 
52
+ def _effective_default_provider(config: Config) -> str:
53
+ """The default provider a bare ref should use.
54
+
55
+ Environment beats stored config everywhere else, so a bare ``--high
56
+ kimi-k2`` under OMNIMANCER_DEFAULT_PROVIDER=bedrock must resolve to
57
+ bedrock rather than to the stale entry on disk.
58
+ """
59
+ try:
60
+ effective = apply_env_overrides(config)
61
+ except Exception as exc: # overrides must never block a run
62
+ logger.debug("H2L: env overrides unavailable: %s", exc)
63
+ effective = config
64
+ return getattr(effective, "default_provider", "") or ""
65
+
66
+
51
67
  def resolve_h2l_config(config: Config, options: H2LOptions) -> H2LConfig:
52
68
  """Flags override the config block field by field; never persisted."""
53
69
  base: Dict[str, Any] = {}
54
70
  block = getattr(config, "h2l", None)
55
71
  if block is not None:
56
72
  base = block.model_dump()
57
- default_provider = getattr(config, "default_provider", "") or ""
73
+ default_provider = _effective_default_provider(config)
58
74
 
59
75
  if options.high:
60
76
  base["high"] = parse_model_ref(options.high, default_provider)
@@ -2,9 +2,12 @@
2
2
 
3
3
  A subagent (see :class:`~omnimancer.core.models.SubAgentDefinition`) handles a
4
4
  focused task with its own system prompt, a restricted tool allowlist, and an
5
- optional model override. It runs an isolated loop against the current provider
6
- using its *own* conversation context, so the parent conversation is never
7
- touched. This is the lightweight counterpart to Claude Code's AgentTool.
5
+ optional model override. It runs an isolated loop using its *own* conversation
6
+ context, so the parent conversation is never touched. A model override also
7
+ gets its own provider instance from
8
+ :mod:`omnimancer.core.provider_resolver` rather than reassigning the shared
9
+ session provider's model. This is the lightweight counterpart to Claude
10
+ Code's AgentTool.
8
11
  """
9
12
 
10
13
  import logging
@@ -13,7 +16,12 @@ from dataclasses import dataclass, field
13
16
  from typing import Any, List, Optional
14
17
 
15
18
  from ..core.agent.status_core import EventType
16
- from ..core.models import ChatContext, SubAgentDefinition
19
+ from ..core.models import ChatContext, ProviderConfig, SubAgentDefinition
20
+ from ..core.provider_resolver import (
21
+ ProviderResolutionError,
22
+ resolve_entry,
23
+ resolve_provider,
24
+ )
17
25
  from ..events import emitter as fleet_events
18
26
  from .tool_handler import ToolHandler
19
27
 
@@ -48,6 +56,50 @@ class SubAgentRunner:
48
56
  allowed = set(definition.tools)
49
57
  return [t for t in all_tools if t.name in allowed]
50
58
 
59
+ def _provider_name(self, provider: Any) -> str:
60
+ """The config-entry name for the running provider, if we can tell."""
61
+ providers = getattr(self.engine, "providers", None)
62
+ if isinstance(providers, dict):
63
+ for name, instance in providers.items():
64
+ if instance is provider:
65
+ return str(name)
66
+ getter = getattr(provider, "get_provider_name", None)
67
+ if callable(getter):
68
+ raw_name = getter()
69
+ if isinstance(raw_name, str):
70
+ return str(raw_name)
71
+ return ""
72
+
73
+ def _isolated_provider(self, provider_name: str, model: str) -> Optional[Any]:
74
+ """A provider instance of our own for a model override.
75
+
76
+ Returns None when the entry cannot be resolved, so the caller falls
77
+ back to the legacy mutate-and-restore behaviour rather than failing
78
+ a subagent over a provider-resolution detail.
79
+ """
80
+ config_manager = getattr(self.engine, "config_manager", None)
81
+ if not provider_name or config_manager is None:
82
+ return None
83
+ try:
84
+ # Only a real entry is safe to build from: anything else means we
85
+ # cannot tell what we would be constructing.
86
+ entry = resolve_entry(provider_name, config_manager, self.engine, model)
87
+ if not isinstance(entry, ProviderConfig):
88
+ return None
89
+ return resolve_provider(provider_name, config_manager, model, self.engine)
90
+ except ProviderResolutionError as exc:
91
+ logger.debug(
92
+ "Subagent model override falling back to the shared provider: %s",
93
+ exc,
94
+ )
95
+ return None
96
+ except Exception as exc:
97
+ logger.debug(
98
+ "Subagent model override falling back to the shared provider: %s",
99
+ exc,
100
+ )
101
+ return None
102
+
51
103
  async def run(self, definition: SubAgentDefinition, task: str) -> SubAgentResult:
52
104
  """Execute the subagent's isolated tool-calling loop for ``task``."""
53
105
  provider = getattr(self.engine, "current_provider", None)
@@ -62,26 +114,24 @@ class SubAgentRunner:
62
114
  tool_handler = ToolHandler(agent_engine)
63
115
  tools = self._scoped_tools(tool_handler, definition)
64
116
 
117
+ # Identity is read before any override so the entry name is the
118
+ # session's, not something derived from a swapped-in instance.
119
+ provider_name = self._provider_name(provider)
120
+
121
+ shared_provider = provider
65
122
  original_model = getattr(provider, "model", None)
123
+ restore_model = False
66
124
  if definition.model:
67
- provider.model = definition.model
125
+ isolated = self._isolated_provider(provider_name, definition.model)
126
+ if isolated is not None:
127
+ provider = isolated
128
+ else:
129
+ # Last resort: the shared object, restored in the finally.
130
+ provider.model = definition.model
131
+ restore_model = True
68
132
 
69
- # Compute the provider/model identity for fleet lifecycle events.
70
133
  raw_model = getattr(provider, "model", "")
71
134
  model_used: str = raw_model if isinstance(raw_model, str) else ""
72
- provider_name = ""
73
- providers = getattr(self.engine, "providers", None)
74
- if isinstance(providers, dict):
75
- for name, instance in providers.items():
76
- if instance is provider:
77
- provider_name = name
78
- break
79
- if not provider_name:
80
- getter = getattr(provider, "get_provider_name", None)
81
- if callable(getter):
82
- raw_name = getter()
83
- if isinstance(raw_name, str):
84
- provider_name = raw_name
85
135
 
86
136
  # Isolated context — the parent conversation is never modified. The
87
137
  # run id is unique per invocation so two runs of the same subagent
@@ -164,9 +214,10 @@ class SubAgentRunner:
164
214
  finally:
165
215
  # Restore the shared provider BEFORE any await: a cancellation
166
216
  # during the emit must never leave the parent conversation
167
- # running on the subagent's model.
168
- if definition.model and original_model is not None:
169
- provider.model = original_model
217
+ # running on the subagent's model. Only the fallback path
218
+ # touched it; a resolved instance leaves it alone entirely.
219
+ if restore_model and original_model is not None:
220
+ shared_provider.model = original_model
170
221
  try:
171
222
  await fleet_events.emit_event(
172
223
  EventType.SESSION_END,
@@ -136,17 +136,13 @@ class CoreEngine:
136
136
  async def initialize_providers(self) -> None:
137
137
  """Initialize all configured providers."""
138
138
  try:
139
- config = self.config_manager.get_config()
139
+ # The effective configuration: stored config plus ephemeral
140
+ # environment overrides (API keys, base_url, model, default
141
+ # provider), never persisted. Every other surface resolves
142
+ # against this same view via core.provider_resolver.
143
+ from .provider_resolver import effective_config
140
144
 
141
- # Apply ephemeral environment-variable overrides (API keys,
142
- # base_url, model, default provider). Never persisted to disk.
143
- # Overrides must never prevent startup, so fall back on any error.
144
- try:
145
- from .env_loader import apply_env_overrides
146
-
147
- config = apply_env_overrides(config)
148
- except Exception as e:
149
- logger.warning(f"Skipping environment overrides: {e}")
145
+ config = effective_config(self.config_manager)
150
146
 
151
147
  # Use the optimized provider initializer with
152
148
  # config_manager for API key decryption
@@ -186,11 +182,12 @@ class CoreEngine:
186
182
  # DigitalOcean request). Leave it unset so the user gets a
187
183
  # clear error naming the provider.
188
184
  self._unavailable_default_provider = config.default_provider
185
+ # One accurate line instead of a guess; the full
186
+ # explanation is built on demand by _no_provider_error.
189
187
  logger.error(
190
- "Default provider '%s' is configured but failed to "
191
- "initialize (missing or invalid API key?). Not falling "
192
- "back to another provider.",
188
+ "Default provider '%s' is not available: %s",
193
189
  config.default_provider,
190
+ self._no_provider_error(),
194
191
  )
195
192
  elif self.providers:
196
193
  # No default configured — use first available provider.
@@ -232,11 +229,13 @@ class CoreEngine:
232
229
  break
233
230
  if not provider_name or not model:
234
231
  try:
235
- config = self.config_manager.get_config()
232
+ from .provider_resolver import effective_config
233
+
234
+ config = effective_config(self.config_manager)
236
235
  if not provider_name:
237
236
  provider_name = config.default_provider or "unknown"
238
237
  if not model:
239
- entry = config.providers.get(config.default_provider)
238
+ entry = (config.providers or {}).get(config.default_provider)
240
239
  raw = getattr(entry, "model", "")
241
240
  model = raw if isinstance(raw, str) else ""
242
241
  except Exception as e: # identity is best-effort diagnostics
@@ -308,14 +307,44 @@ class CoreEngine:
308
307
  missing, rather than a generic "no provider" message.
309
308
  """
310
309
  unavailable = getattr(self, "_unavailable_default_provider", None)
311
- if unavailable:
310
+ if not unavailable:
311
+ return "No provider available. Please configure a provider first."
312
+
313
+ env_prefix = "OMNIMANCER_" + unavailable.upper().replace("-", "_")
314
+ try:
315
+ from .provider_resolver import effective_providers
316
+
317
+ entry = effective_providers(self.config_manager).get(unavailable)
318
+ except Exception as exc: # diagnostics must not raise
319
+ logger.debug(f"Could not inspect entry for {unavailable}: {exc}")
320
+ entry = None
321
+
322
+ if entry is None:
312
323
  return (
313
- f"Provider '{unavailable}' is selected but failed to initialize "
314
- f"— most likely a missing or invalid API key. Configure it (e.g. "
315
- f"'/config set-provider {unavailable}' or the provider's API-key "
316
- f"environment variable) and try again."
324
+ f"Provider '{unavailable}' is selected as the default but is "
325
+ f"not configured: there is no entry for it and no "
326
+ f"{env_prefix}_API_KEY in the environment. Add it with "
327
+ f"'/config set-provider {unavailable}', or pick a different "
328
+ f"default with '/switch'."
317
329
  )
318
- return "No provider available. Please configure a provider first."
330
+
331
+ if not (getattr(entry, "model", "") or "").strip():
332
+ return (
333
+ f"Provider '{unavailable}' has no model configured, so it was "
334
+ f"not started. Set {env_prefix}_MODEL, or run '/model' to pick "
335
+ f"one."
336
+ )
337
+
338
+ from .provider_initializer import ProviderInitializer
339
+
340
+ reason = ProviderInitializer.get_last_init_errors().get(unavailable)
341
+ if reason:
342
+ return f"Provider '{unavailable}' did not start: {reason}"
343
+ return (
344
+ f"Provider '{unavailable}' is configured but did not start. Check "
345
+ f"its credentials ('/config set-provider {unavailable}' or "
346
+ f"{env_prefix}_API_KEY) and try again."
347
+ )
319
348
 
320
349
  # ------------------------------------------------------------------
321
350
  # Fallback wiring
@@ -945,11 +974,13 @@ class CoreEngine:
945
974
  Dictionary with health status information
946
975
  """
947
976
  try:
948
- config = self.config_manager.get_config()
977
+ from .provider_resolver import effective_providers
978
+
979
+ providers = effective_providers(self.config_manager)
949
980
 
950
981
  if provider_name:
951
982
  # Check specific provider
952
- if provider_name not in config.providers:
983
+ if provider_name not in providers:
953
984
  return {
954
985
  provider_name: {
955
986
  "status": "error",
@@ -959,7 +990,7 @@ class CoreEngine:
959
990
  }
960
991
  }
961
992
 
962
- provider_config = config.providers[provider_name]
993
+ provider_config = providers[provider_name]
963
994
  status = await self.health_monitor.check_provider_health(
964
995
  provider_name, provider_config, force=force
965
996
  )
@@ -967,7 +998,7 @@ class CoreEngine:
967
998
  else:
968
999
  # Check all providers
969
1000
  return await self.health_monitor.check_all_providers_health(
970
- config.providers, force=force
1001
+ providers, force=force
971
1002
  )
972
1003
 
973
1004
  except Exception as e:
@@ -7,13 +7,16 @@ and inject them into provider configurations.
7
7
 
8
8
  import logging
9
9
  import os
10
- from typing import TYPE_CHECKING, Dict, Optional
10
+ from typing import TYPE_CHECKING, Dict, Optional, Set
11
11
 
12
12
  if TYPE_CHECKING:
13
13
  from .models import Config
14
14
 
15
15
  logger = logging.getLogger(__name__)
16
16
 
17
+ # Namespaced env-var suffixes recognized for every provider.
18
+ ENV_VAR_SUFFIXES = ("API_KEY", "BASE_URL", "MODEL")
19
+
17
20
  # Mapping of provider names to (conventional) environment variable names for API keys.
18
21
  ENV_VAR_MAPPING = {
19
22
  "claude": "ANTHROPIC_API_KEY",
@@ -37,6 +40,31 @@ def _omnimancer_env_prefix(provider_name: str) -> str:
37
40
  return "OMNIMANCER_" + provider_name.upper().replace("-", "_")
38
41
 
39
42
 
43
+ def env_configured_provider_names() -> Set[str]:
44
+ """Registered provider names that have OMNIMANCER_<PROVIDER>_* vars set.
45
+
46
+ ``ENV_VAR_MAPPING`` only lists providers that have a conventional
47
+ single-key variable, so without this a namespaced key for anything else
48
+ (bedrock, vertex, openai-compatible) was never even considered as a
49
+ candidate and was silently ignored. The registry is the source of names so
50
+ the underscore-to-hyphen mapping stays unambiguous.
51
+ """
52
+ try:
53
+ from ..providers.factory import ProviderFactory
54
+
55
+ registered = set(ProviderFactory.get_available_providers())
56
+ except Exception as exc: # discovery must never block config loading
57
+ logger.debug(f"Provider registry unavailable for env discovery: {exc}")
58
+ return set()
59
+
60
+ names: Set[str] = set()
61
+ for name in registered:
62
+ prefix = _omnimancer_env_prefix(name)
63
+ if any(os.environ.get(f"{prefix}_{suffix}") for suffix in ENV_VAR_SUFFIXES):
64
+ names.add(name)
65
+ return names
66
+
67
+
40
68
  def load_provider_env_overrides(provider_name: str) -> Dict[str, str]:
41
69
  """
42
70
  Collect per-provider overrides from environment variables.
@@ -176,8 +204,13 @@ def apply_env_overrides(config: "Config") -> "Config":
176
204
  effective = config.model_copy(deep=True)
177
205
 
178
206
  # Consider providers already configured, those with a recognized API-key
179
- # env var, plus any provider named by OMNIMANCER_DEFAULT_PROVIDER.
180
- candidates = set(effective.providers.keys()) | set(ENV_VAR_MAPPING.keys())
207
+ # env var or a namespaced OMNIMANCER_<PROVIDER>_* var, plus any provider
208
+ # named by OMNIMANCER_DEFAULT_PROVIDER.
209
+ candidates = (
210
+ set(effective.providers.keys())
211
+ | set(ENV_VAR_MAPPING.keys())
212
+ | env_configured_provider_names()
213
+ )
181
214
  default_override = os.environ.get(DEFAULT_PROVIDER_ENV_VAR)
182
215
  if default_override:
183
216
  candidates.add(default_override)
@@ -197,7 +230,7 @@ def apply_env_overrides(config: "Config") -> "Config":
197
230
  model=overrides.get("model", ""),
198
231
  )
199
232
  effective.providers[name] = provider_config
200
- logger.info(f"Created provider '{name}' from environment overrides")
233
+ logger.debug(f"Created provider '{name}' from environment overrides")
201
234
 
202
235
  if "api_key" in overrides:
203
236
  provider_config.api_key = overrides["api_key"]
@@ -208,6 +241,8 @@ def apply_env_overrides(config: "Config") -> "Config":
208
241
 
209
242
  if default_override:
210
243
  effective.default_provider = default_override
211
- logger.info(f"Default provider overridden from environment: {default_override}")
244
+ logger.debug(
245
+ f"Default provider overridden from environment: {default_override}"
246
+ )
212
247
 
213
248
  return effective
@@ -264,23 +264,24 @@ async def enhance(
264
264
  if fallback_model is not None and fallback_model not in candidates:
265
265
  candidates.append(fallback_model)
266
266
 
267
- from ..providers.factory import ProviderFactory
267
+ from .provider_resolver import ProviderResolutionError, resolve_provider
268
268
 
269
269
  for provider_name, model in candidates:
270
- provider_config = config.providers.get(provider_name)
271
- if provider_config is None:
270
+ try:
271
+ # The shared resolver so an entry materialized from
272
+ # OMNIMANCER_<PROVIDER>_* is as usable here as in the engine.
273
+ provider = resolve_provider(
274
+ provider_name,
275
+ config_manager,
276
+ model,
277
+ overrides={"temperature": temperature},
278
+ )
279
+ except ProviderResolutionError as exc:
272
280
  logger.warning(
273
- "Enhancement provider %r is not configured", provider_name
281
+ "Enhancement provider %r unusable: %s", provider_name, exc
274
282
  )
275
283
  continue
276
284
  try:
277
- effective = provider_config.model_copy(
278
- update={"model": model, "temperature": temperature}
279
- )
280
- provider = ProviderFactory.create_provider(
281
- provider_name, effective, config_manager
282
- )
283
-
284
285
  # Isolated context: the session conversation never leaks
285
286
  # into the rewrite, and the rewrite never lands in session
286
287
  # history.
@@ -44,6 +44,8 @@ class ProviderInitializer:
44
44
  # Configuration
45
45
  _lazy_loading_enabled: bool = True
46
46
  _cache_enabled: bool = True
47
+ # provider name -> why it failed, from the most recent initialization
48
+ _last_init_errors: Dict[str, str] = {}
47
49
 
48
50
  @classmethod
49
51
  def get_provider_class(cls, provider_name: str) -> Type[BaseProvider]:
@@ -447,14 +449,35 @@ class ProviderInitializer:
447
449
  provider_configs: Dictionary of provider configurations
448
450
  config_manager: Optional config manager for API key decryption
449
451
 
452
+ An entry with no model id is *incomplete*, not broken: environment
453
+ materialized entries (``OMNIMANCER_<PROVIDER>_API_KEY`` with no
454
+ ``_MODEL``) are legitimate when the model comes from somewhere else,
455
+ such as an H2L ``--high <entry>:<model>`` ref. Those are skipped
456
+ without an error; real failures are recorded so callers can report
457
+ the actual cause instead of guessing at one.
458
+
450
459
  Returns:
451
460
  Dictionary of initialized provider instances
452
461
  """
453
462
  from ..providers.factory import ProviderFactory
454
463
 
455
464
  providers = {}
465
+ errors: Dict[str, str] = {}
456
466
 
457
467
  for provider_name, config in provider_configs.items():
468
+ if not (getattr(config, "model", "") or "").strip():
469
+ env_var = (
470
+ "OMNIMANCER_" + provider_name.upper().replace("-", "_") + "_MODEL"
471
+ )
472
+ logger.info(
473
+ "Provider '%s' has no model configured; not started. "
474
+ "Set %s or '/config set-provider %s'. Model refs that "
475
+ "carry their own model (H2L tiers) still work.",
476
+ provider_name,
477
+ env_var,
478
+ provider_name,
479
+ )
480
+ continue
458
481
  try:
459
482
  # Use ProviderFactory instead of direct class loading
460
483
  provider = ProviderFactory.create_provider(
@@ -463,12 +486,19 @@ class ProviderInitializer:
463
486
  providers[provider_name] = provider
464
487
  logger.info(f"Initialized provider: {provider_name}")
465
488
  except Exception as e:
466
- logger.error(f"Failed to initialize provider {provider_name}: {e}")
489
+ errors[provider_name] = str(e)
490
+ logger.error(f"Provider '{provider_name}' did not start: {e}")
467
491
  # Continue with other providers instead of failing completely
468
492
  continue
469
493
 
494
+ cls._last_init_errors = errors
470
495
  return providers
471
496
 
497
+ @classmethod
498
+ def get_last_init_errors(cls) -> Dict[str, str]:
499
+ """Why each provider failed during the most recent initialization."""
500
+ return dict(getattr(cls, "_last_init_errors", {}) or {})
501
+
472
502
  @classmethod
473
503
  def preload_model_info(cls, provider_names: List[str]) -> None:
474
504
  """