omnimancer-cli 0.2.20__tar.gz → 0.2.22__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 (157) hide show
  1. {omnimancer_cli-0.2.20/omnimancer_cli.egg-info → omnimancer_cli-0.2.22}/PKG-INFO +1 -1
  2. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/cli/headless.py +77 -4
  3. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22/omnimancer_cli.egg-info}/PKG-INFO +1 -1
  4. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/pyproject.toml +1 -1
  5. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/LICENSE +0 -0
  6. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/README.md +0 -0
  7. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/__init__.py +0 -0
  8. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/__main__.py +0 -0
  9. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/cli/__init__.py +0 -0
  10. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/cli/agent_loop.py +0 -0
  11. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/cli/approval_formatter.py +0 -0
  12. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/cli/approval_integration.py +0 -0
  13. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/cli/approval_prompt.py +0 -0
  14. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/cli/command_dispatch.py +0 -0
  15. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/cli/commands.py +0 -0
  16. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/cli/completion.py +0 -0
  17. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/cli/display.py +0 -0
  18. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/cli/dynamic_commands.py +0 -0
  19. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/cli/handlers/__init__.py +0 -0
  20. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/cli/interface.py +0 -0
  21. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/cli/subagent.py +0 -0
  22. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/cli/system_prompts.py +0 -0
  23. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/cli/tool_handler.py +0 -0
  24. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/core/__init__.py +0 -0
  25. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/core/agent/__init__.py +0 -0
  26. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/core/agent/approval_context.py +0 -0
  27. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/core/agent/approval_interface.py +0 -0
  28. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/core/agent/approval_manager.py +0 -0
  29. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/core/agent/config.py +0 -0
  30. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/core/agent/file_system_manager.py +0 -0
  31. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/core/agent/program_executor.py +0 -0
  32. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/core/agent/read_before_write_errors.py +0 -0
  33. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/core/agent/status_core.py +0 -0
  34. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/core/agent/status_manager.py +0 -0
  35. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/core/agent/tool_definitions.py +0 -0
  36. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/core/agent/types.py +0 -0
  37. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/core/agent/web_client.py +0 -0
  38. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/core/agent/workflow_orchestrator.py +0 -0
  39. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/core/agent_engine.py +0 -0
  40. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/core/agent_managers.py +0 -0
  41. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/core/agent_mode_manager.py +0 -0
  42. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/core/chat_manager.py +0 -0
  43. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/core/config_manager.py +0 -0
  44. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/core/config_migration.py +0 -0
  45. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/core/config_validator.py +0 -0
  46. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/core/conversation_manager.py +0 -0
  47. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/core/engine.py +0 -0
  48. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/core/env_loader.py +0 -0
  49. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/core/fallback_manager.py +0 -0
  50. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/core/health_monitor.py +0 -0
  51. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/core/history_manager.py +0 -0
  52. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/core/hooks.py +0 -0
  53. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/core/mcp_integration_layer.py +0 -0
  54. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/core/models.py +0 -0
  55. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/core/provider_capabilities.py +0 -0
  56. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/core/provider_initializer.py +0 -0
  57. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/core/provider_registry.py +0 -0
  58. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/core/rate_limit_fallback.py +0 -0
  59. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/core/security/__init__.py +0 -0
  60. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/core/security/approval_workflow.py +0 -0
  61. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/core/security/audit_logger.py +0 -0
  62. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/core/security/permission_controller.py +0 -0
  63. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/core/security/permission_rules.py +0 -0
  64. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/core/security/sandbox_manager.py +0 -0
  65. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/core/security/security_manager.py +0 -0
  66. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/core/signal_handler.py +0 -0
  67. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/mcp/__init__.py +0 -0
  68. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/mcp/client.py +0 -0
  69. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/mcp/manager.py +0 -0
  70. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/providers/__init__.py +0 -0
  71. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/providers/azure.py +0 -0
  72. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/providers/base.py +0 -0
  73. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/providers/bedrock.py +0 -0
  74. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/providers/claude.py +0 -0
  75. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/providers/claude_code.py +0 -0
  76. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/providers/claude_credentials.py +0 -0
  77. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/providers/cohere.py +0 -0
  78. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/providers/digitalocean.py +0 -0
  79. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/providers/factory.py +0 -0
  80. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/providers/gemini.py +0 -0
  81. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/providers/mistral.py +0 -0
  82. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/providers/ollama.py +0 -0
  83. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/providers/openai.py +0 -0
  84. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/providers/openrouter.py +0 -0
  85. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/providers/perplexity.py +0 -0
  86. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/providers/vertex.py +0 -0
  87. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/providers/xai.py +0 -0
  88. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/ui/__init__.py +0 -0
  89. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/ui/cancellation_handler.py +0 -0
  90. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/ui/progress_indicator.py +0 -0
  91. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/ui/streaming_display.py +0 -0
  92. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/utils/__init__.py +0 -0
  93. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/utils/error_handler.py +0 -0
  94. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/utils/errors.py +0 -0
  95. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer/utils/retry.py +0 -0
  96. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer_cli.egg-info/SOURCES.txt +0 -0
  97. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer_cli.egg-info/dependency_links.txt +0 -0
  98. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer_cli.egg-info/entry_points.txt +0 -0
  99. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer_cli.egg-info/requires.txt +0 -0
  100. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/omnimancer_cli.egg-info/top_level.txt +0 -0
  101. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/setup.cfg +0 -0
  102. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/tests/test_approval_integration_compatibility.py +0 -0
  103. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/tests/test_approval_preview_format.py +0 -0
  104. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/tests/test_approval_remember.py +0 -0
  105. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/tests/test_approval_system.py +0 -0
  106. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/tests/test_azure_provider.py +0 -0
  107. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/tests/test_bedrock_provider.py +0 -0
  108. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/tests/test_claude_code_provider.py +0 -0
  109. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/tests/test_claude_credentials.py +0 -0
  110. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/tests/test_claude_provider.py +0 -0
  111. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/tests/test_cohere_provider.py +0 -0
  112. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/tests/test_command_approval_security.py +0 -0
  113. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/tests/test_config_comprehensive.py +0 -0
  114. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/tests/test_conversation_history.py +0 -0
  115. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/tests/test_conversation_manager.py +0 -0
  116. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/tests/test_core_engine.py +0 -0
  117. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/tests/test_digitalocean_provider.py +0 -0
  118. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/tests/test_enhanced_model_info.py +0 -0
  119. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/tests/test_env_loader.py +0 -0
  120. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/tests/test_error_handling_robustness.py +0 -0
  121. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/tests/test_execution_timeout_errors.py +0 -0
  122. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/tests/test_fallback_manager.py +0 -0
  123. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/tests/test_fixtures.py +0 -0
  124. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/tests/test_gemini_provider.py +0 -0
  125. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/tests/test_hooks.py +0 -0
  126. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/tests/test_httpx_compatibility.py +0 -0
  127. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/tests/test_import_validation.py +0 -0
  128. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/tests/test_interface_smoke_tests.py +0 -0
  129. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/tests/test_mcp_integration.py +0 -0
  130. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/tests/test_mcp_integration_layer.py +0 -0
  131. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/tests/test_mcp_management_commands.py +0 -0
  132. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/tests/test_mistral_provider.py +0 -0
  133. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/tests/test_models.py +0 -0
  134. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/tests/test_ollama_provider.py +0 -0
  135. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/tests/test_openai_context_fit.py +0 -0
  136. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/tests/test_openai_provider_tools.py +0 -0
  137. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/tests/test_openai_request_timeout.py +0 -0
  138. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/tests/test_openrouter_provider.py +0 -0
  139. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/tests/test_permission_rules.py +0 -0
  140. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/tests/test_perplexity_provider.py +0 -0
  141. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/tests/test_program_executor.py +0 -0
  142. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/tests/test_provider_base.py +0 -0
  143. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/tests/test_provider_capability_consistency.py +0 -0
  144. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/tests/test_provider_discovery.py +0 -0
  145. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/tests/test_provider_integration.py +0 -0
  146. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/tests/test_provider_registry.py +0 -0
  147. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/tests/test_rate_limit_fallback.py +0 -0
  148. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/tests/test_retry.py +0 -0
  149. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/tests/test_security_validation.py +0 -0
  150. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/tests/test_system_integration.py +0 -0
  151. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/tests/test_system_prompts.py +0 -0
  152. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/tests/test_tool_calling_integration.py +0 -0
  153. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/tests/test_tool_handler.py +0 -0
  154. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/tests/test_vertex_provider.py +0 -0
  155. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/tests/test_web_request_markers.py +0 -0
  156. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/tests/test_workflows_comprehensive.py +0 -0
  157. {omnimancer_cli-0.2.20 → omnimancer_cli-0.2.22}/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.20
3
+ Version: 0.2.22
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
@@ -5,6 +5,8 @@ results to stdout. No Rich console, no readline, no interactive UI.
5
5
  """
6
6
 
7
7
  import json
8
+ import logging
9
+ import os
8
10
  import sys
9
11
  import uuid
10
12
  from enum import Enum
@@ -24,6 +26,53 @@ from .tool_handler import (
24
26
  ToolHandler,
25
27
  )
26
28
 
29
+ logger = logging.getLogger(__name__)
30
+
31
+ # A turn with no tool calls used to end the run immediately. Models that
32
+ # narrate their plan ("Let me look into X...") without attaching a tool call
33
+ # made headless runs "complete" with zero work done, so the runner now nudges
34
+ # the model to either keep working or explicitly declare completion.
35
+ NO_TOOL_CALL_NUDGE = (
36
+ "You did not call any tools. If the task is fully complete, reply with "
37
+ "exactly DONE. Otherwise continue working now by calling tools — do not "
38
+ "describe what you plan to do without doing it."
39
+ )
40
+
41
+ # Consecutive tool-less turns tolerated before the run is ended anyway.
42
+ MAX_NO_TOOL_NUDGES = 2
43
+
44
+
45
+ def _is_done_reply(content: Optional[str]) -> bool:
46
+ """True when the model explicitly declares completion per the nudge."""
47
+ if not content:
48
+ return False
49
+ lines = content.strip().splitlines()
50
+ if not lines:
51
+ return False
52
+ return lines[-1].strip().rstrip(".!").upper() == "DONE"
53
+
54
+
55
+ def _resolve_max_iterations(explicit: Optional[int]) -> int:
56
+ """Resolve the tool-iteration cap: explicit value wins, then the
57
+ OMNIMANCER_MAX_ITERATIONS environment variable, then the default.
58
+
59
+ The default (25) suits interactive use; orchestrators running real
60
+ coding tasks inject a higher cap — exhausting it mid-task previously
61
+ looked like a clean "completed" run with no changes.
62
+ """
63
+ if explicit:
64
+ return explicit
65
+ raw = os.environ.get("OMNIMANCER_MAX_ITERATIONS")
66
+ if raw:
67
+ try:
68
+ value = int(raw)
69
+ if value > 0:
70
+ return value
71
+ logger.warning("Ignoring non-positive OMNIMANCER_MAX_ITERATIONS %r", raw)
72
+ except ValueError:
73
+ logger.warning("Ignoring invalid OMNIMANCER_MAX_ITERATIONS %r", raw)
74
+ return MAX_TOOL_ITERATIONS
75
+
27
76
 
28
77
  class OutputFormat(Enum):
29
78
  TEXT = "text"
@@ -229,7 +278,7 @@ class HeadlessRunner:
229
278
  self._verbose = verbose
230
279
  # Headless runs are unattended, so a cap (not a check-in prompt) is
231
280
  # the safety net; --max-iterations sizes it to the job.
232
- self._max_iterations = max_iterations or MAX_TOOL_ITERATIONS
281
+ self._max_iterations = _resolve_max_iterations(max_iterations)
233
282
  session_id = str(uuid.uuid4())
234
283
  self._emitter = HeadlessOutputEmitter(output_format, session_id, verbose)
235
284
  self._tokens = TokenAccumulator()
@@ -296,6 +345,7 @@ class HeadlessRunner:
296
345
 
297
346
  current_message = f"{agent_prompt}\n\nUser: {prompt}"
298
347
  last_content = ""
348
+ prev_content = ""
299
349
  last_model = model
300
350
  last_stop_reason = "end_turn"
301
351
  # Record of the actions the agent took, surfaced in the JSON result.
@@ -304,6 +354,7 @@ class HeadlessRunner:
304
354
  # Identical repeated calls get skipped with a corrective nudge; the
305
355
  # run is aborted only if the model keeps repeating despite nudges.
306
356
  repeat_tracker = RepeatedCallTracker()
357
+ no_tool_nudges = 0
307
358
 
308
359
  for iteration in range(self._max_iterations):
309
360
  turns = iteration + 1
@@ -322,6 +373,7 @@ class HeadlessRunner:
322
373
  last_stop_reason = response.stop_reason or "end_turn"
323
374
 
324
375
  if response.content:
376
+ prev_content = last_content
325
377
  last_content = response.content
326
378
  self._emitter.emit_assistant(
327
379
  response.content,
@@ -334,9 +386,30 @@ class HeadlessRunner:
334
386
  # those so the run doesn't end with the work undone.
335
387
  if not response.tool_calls:
336
388
  recovered = parse_described_tool_calls(response.content)
337
- if not recovered:
338
- break
339
- response.tool_calls = recovered
389
+ if recovered:
390
+ response.tool_calls = recovered
391
+ else:
392
+ # No tool call: either the final answer or narration from
393
+ # a model that stopped without acting. End the run only on
394
+ # an explicit DONE or after repeated nudges — breaking
395
+ # immediately made agents "complete" with zero changes.
396
+ if (
397
+ _is_done_reply(response.content)
398
+ or no_tool_nudges >= MAX_NO_TOOL_NUDGES
399
+ ):
400
+ # A bare "DONE" acknowledgment must not replace the
401
+ # model's actual final summary in the result payload.
402
+ if (
403
+ response.content
404
+ and response.content.strip().rstrip(".!").upper() == "DONE"
405
+ and prev_content
406
+ ):
407
+ last_content = prev_content
408
+ break
409
+ no_tool_nudges += 1
410
+ current_message = NO_TOOL_CALL_NUDGE
411
+ continue
412
+ no_tool_nudges = 0
340
413
 
341
414
  repeat_tracker.record(response.tool_calls)
342
415
  offender = repeat_tracker.abort_offender(response.tool_calls)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: omnimancer-cli
3
- Version: 0.2.20
3
+ Version: 0.2.22
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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "omnimancer-cli"
7
- version = "0.2.20"
7
+ version = "0.2.22"
8
8
  description = "A unified command-line interface for multiple AI language models"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
File without changes