utim-cli 2.3.12__tar.gz → 2.3.14__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.
- {utim_cli-2.3.12 → utim_cli-2.3.14}/CHANGELOG.md +12 -0
- {utim_cli-2.3.12/utim_cli.egg-info → utim_cli-2.3.14}/PKG-INFO +1 -1
- {utim_cli-2.3.12 → utim_cli-2.3.14}/pyproject.toml +1 -1
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim/__init__.py +1 -1
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/__init__.py +1 -1
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/_version.py +1 -1
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/agent.py +1 -1
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/ask_helper.py +1 -1
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/auth.py +2 -2
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/bootstrap.py +6 -5
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/brain.py +36 -2
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/client_utils.py +66 -7
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/config.py +102 -4
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/context_pruner.py +1 -1
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/doctor.py +1 -1
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/orchestrator.py +50 -41
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/orchestrator_helpers.py +4 -4
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/orchestrator_llm_transport.py +42 -32
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/orchestrator_tool_exec.py +2 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/reflection.py +33 -4
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/server/auth.py +54 -5
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/server/db.py +7 -0
- utim_cli-2.3.14/utim_cli/server/key_rotator.py +710 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/server/router.py +11 -1
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/server/routes/auth_routes.py +21 -11
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/server/routes/completion_routes.py +136 -98
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/server/routes/credit_routes.py +26 -9
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/server/routes/marketplace_routes.py +17 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/server/routes/quota_routes.py +36 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/tools.py +272 -140
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/tui/marketplace_dialog.py +16 -13
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/tui/miniagents_dialog.py +72 -48
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/tui/model_dialog.py +17 -7
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/tui/skills_dialog.py +11 -9
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/tui/subagents_dialog.py +70 -47
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/utim.py +174 -74
- {utim_cli-2.3.12 → utim_cli-2.3.14/utim_cli.egg-info}/PKG-INFO +1 -1
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli.egg-info/SOURCES.txt +1 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/LICENSE +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/MANIFEST.in +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/README.md +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/setup.cfg +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/setup.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/_verify_repro.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/_verify_syntax.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/backup.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/billing.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/blender_agent.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/constants.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/crawler/__init__.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/crawler/engine.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/crawler/markdown_distiller.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/dependency_manager.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/evidence_registry.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/harbor.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/knowledge_graph.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/local_db.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/logger.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/mcp_clean_wrapper.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/mcp_client.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/mcp_registry.json +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/models.txt +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/orchestrator_turn_history.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/personalities.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/platform_installer.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/plugins/__init__.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/plugins/hooks.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/plugins/manager.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/plugins/registry.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/plugins/schema.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/plugins/tool_loader.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/report.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/scrapy_search.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/sdk/__init__.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/sdk/config.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/sdk/context.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/sdk/events.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/sdk/handlers.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/sdk/session.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/sdk/sidecar.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/server/__init__.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/server/admin_auth.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/server/attribution.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/server/audit_log.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/server/batch_processor.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/server/captcha.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/server/cli_auth.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/server/concurrency.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/server/docs_md/SECRET_PROVISIONING.md +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/server/docs_md/about.md +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/server/docs_md/changelog.md +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/server/docs_md/docs.md +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/server/docs_md/features.md +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/server/docs_md/license.md +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/server/docs_md/pricing.md +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/server/docs_md/privacy.md +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/server/docs_md/refund.md +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/server/docs_md/support.md +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/server/docs_md/terms.md +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/server/email_utils.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/server/exchange_rate.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/server/firebase.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/server/fix_duplicate_users.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/server/history.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/server/local_llm.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/server/logging_config.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/server/micro_batcher.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/server/model_agent.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/server/models.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/server/pricing_updater.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/server/provision_build.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/server/rate_limit.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/server/rewards_engine.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/server/routes/__init__.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/server/routes/admin_db_routes.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/server/routes/billing_routes.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/server/routes/feedback_routes.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/server/routes/quota_share_routes.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/server/routes/referral_routes.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/server/routes/rewards_routes.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/server/routes/security_routes.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/server/routes/session_routes.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/server/routes/share_routes.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/server/server.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/server/storage_nodes.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/share.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/share_tui.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/situational_scoring.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/state.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/subagent_manager.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/task_dispatcher.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/task_logger.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/tools_grep.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/tui/__init__.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/tui/feedback_dialog.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/tui/history_dialog.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/tui/marketplace_app_state.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/tui/marketplace_layout_engine.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/tui/mcp_dialog.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/tui/personality_dialog.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/tui/plugins_dialog.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/tui/publish_dialog.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/tui/quota_dialog.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/tui/quota_redeem_dialog.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/tui/quota_share_dialog.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/tui/resume_dialog.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/tui/rewards_tui.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/tui/tasks_dialog.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/tui/thinking_display.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/tui/tools_dialog.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/tui/update_dialog.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/utilities.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/utimmodel.txt +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/vector_memory.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/wheel.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli/workspace.py +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli.egg-info/dependency_links.txt +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli.egg-info/entry_points.txt +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli.egg-info/requires.txt +0 -0
- {utim_cli-2.3.12 → utim_cli-2.3.14}/utim_cli.egg-info/top_level.txt +0 -0
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.3.14] - 2026-08-28
|
|
4
|
+
|
|
5
|
+
### ⚡ Request Queue & Scaled Free Capacity
|
|
6
|
+
- Added request queue visual, Increased global free user request cap from 20RPM - 60RPM
|
|
7
|
+
- Increase per user Rpm cap from 10-30RPM
|
|
8
|
+
- Added a new fun feature "/personality"
|
|
9
|
+
|
|
10
|
+
## [2.3.13] - 2026-08-28
|
|
11
|
+
|
|
12
|
+
### 🐞 Minor Bug Fixes
|
|
13
|
+
- Minor Bug Fixes
|
|
14
|
+
|
|
3
15
|
## [2.3.12] - 2026-08-27
|
|
4
16
|
|
|
5
17
|
### ⚡ Hedged TTFT Fallback Racing & Smart Nudge Improvements
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: utim-cli
|
|
3
|
-
Version: 2.3.
|
|
3
|
+
Version: 2.3.14
|
|
4
4
|
Summary: UTIM – Universal Terminal Intelligence Manager. A powerful agentic AI coding assistant for your terminal.
|
|
5
5
|
License: Emend AI Proprietary EULA
|
|
6
6
|
Project-URL: Homepage, https://utim.dev
|
|
@@ -14,7 +14,7 @@ Quickstart:
|
|
|
14
14
|
try:
|
|
15
15
|
from utim_cli._version import VERSION as __version__
|
|
16
16
|
except ImportError:
|
|
17
|
-
__version__ = "2.3.
|
|
17
|
+
__version__ = "2.3.14"
|
|
18
18
|
|
|
19
19
|
# Developer-friendly core classes (similar to `import genai` / `import openai`)
|
|
20
20
|
from utim_cli.sdk.session import UtimSession as Session
|
|
@@ -288,7 +288,7 @@ class ReActAgent:
|
|
|
288
288
|
|
|
289
289
|
# Provider-safety pass: drop null/empty `tool_calls` keys from
|
|
290
290
|
# history (strict providers 400 on `messages.N.tool_calls: null`).
|
|
291
|
-
kwargs["messages"] = sanitize_messages_for_api(kwargs.get("messages") or [])
|
|
291
|
+
kwargs["messages"] = sanitize_messages_for_api(kwargs.get("messages") or [], model_id=getattr(self, "model", None))
|
|
292
292
|
|
|
293
293
|
# Call the LLM with streaming (retry once on transport errors so a
|
|
294
294
|
# mid-stream failure doesn't silently kill the whole run)
|
|
@@ -321,7 +321,7 @@ class _AuthCallbackHandler(http.server.BaseHTTPRequestHandler):
|
|
|
321
321
|
|
|
322
322
|
# ── Public login() function ───────────────────────────────────────────────────
|
|
323
323
|
|
|
324
|
-
def login(restart: bool =
|
|
324
|
+
def login(restart: bool = False) -> None:
|
|
325
325
|
"""
|
|
326
326
|
Sign in via the Device Authorization Flow.
|
|
327
327
|
|
|
@@ -504,8 +504,8 @@ def login(restart: bool = True) -> None:
|
|
|
504
504
|
raw_print(f" Signed in as {BLUE}{body.get('email', '')}{RESET}")
|
|
505
505
|
if "credits" in body:
|
|
506
506
|
raw_print(f" Credits: {body.get('credits', 0):,.0f} UTIM")
|
|
507
|
-
raw_print(f" {DIM}Restarting UTIM CLI…{RESET}")
|
|
508
507
|
if restart:
|
|
508
|
+
raw_print(f" {DIM}Restarting UTIM CLI…{RESET}")
|
|
509
509
|
restart_process()
|
|
510
510
|
# If restart wasn't called, fall through and let the main loop exit.
|
|
511
511
|
|
|
@@ -150,7 +150,7 @@ def scan_available_skills(force_reload: bool = False):
|
|
|
150
150
|
|
|
151
151
|
import os, re
|
|
152
152
|
from pathlib import Path
|
|
153
|
-
from utim_cli.config import config as _cfg
|
|
153
|
+
from utim_cli.config import config as _cfg, get_all_utim_dirs
|
|
154
154
|
|
|
155
155
|
disabled: list = _cfg.get("disabled_skills") or []
|
|
156
156
|
if not isinstance(disabled, list):
|
|
@@ -163,11 +163,12 @@ def scan_available_skills(force_reload: bool = False):
|
|
|
163
163
|
Path('.agents'),
|
|
164
164
|
Path('.utim/skills'),
|
|
165
165
|
Path('.utim/agentskills'),
|
|
166
|
-
|
|
167
|
-
UTIM_DIR / 'agentskills',
|
|
168
|
-
Path.home() / '.utim' / 'skills',
|
|
169
|
-
Path.home() / '.utim' / 'agentskills',
|
|
166
|
+
Path('skills'),
|
|
170
167
|
]
|
|
168
|
+
for d in get_all_utim_dirs():
|
|
169
|
+
paths_to_check.append(d / 'skills')
|
|
170
|
+
paths_to_check.append(d / 'agentskills')
|
|
171
|
+
paths_to_check.append(d)
|
|
171
172
|
|
|
172
173
|
for base_dir in paths_to_check:
|
|
173
174
|
if not base_dir.exists():
|
|
@@ -353,6 +353,13 @@ def store_brain_memory(
|
|
|
353
353
|
if not content or not content.strip():
|
|
354
354
|
return ""
|
|
355
355
|
|
|
356
|
+
try:
|
|
357
|
+
from utim_cli.tools import is_user_paid
|
|
358
|
+
if not is_user_paid():
|
|
359
|
+
return ""
|
|
360
|
+
except Exception:
|
|
361
|
+
pass
|
|
362
|
+
|
|
356
363
|
pid = project_id or _project_id()
|
|
357
364
|
now = datetime.now().isoformat()
|
|
358
365
|
clean_title = title or content[:60].replace("\n", " ").strip()
|
|
@@ -444,6 +451,13 @@ def retrieve_brain_memories(
|
|
|
444
451
|
if not query:
|
|
445
452
|
return []
|
|
446
453
|
|
|
454
|
+
try:
|
|
455
|
+
from utim_cli.tools import is_user_paid
|
|
456
|
+
if not is_user_paid():
|
|
457
|
+
return []
|
|
458
|
+
except Exception:
|
|
459
|
+
pass
|
|
460
|
+
|
|
447
461
|
pid = project_id or _project_id()
|
|
448
462
|
|
|
449
463
|
# ── Stage 1: Forward pass ─────────────────────────────────────────────
|
|
@@ -696,6 +710,13 @@ def index_project_architecture(workspace: str = "."):
|
|
|
696
710
|
If yes, store an updated architecture memory in the brain.
|
|
697
711
|
Maintains exactly one deterministic architecture memory per project.
|
|
698
712
|
"""
|
|
713
|
+
try:
|
|
714
|
+
from utim_cli.tools import is_user_paid
|
|
715
|
+
if not is_user_paid():
|
|
716
|
+
return
|
|
717
|
+
except Exception:
|
|
718
|
+
pass
|
|
719
|
+
|
|
699
720
|
pid = _project_id(workspace)
|
|
700
721
|
snap = _snapshot_project_architecture(workspace)
|
|
701
722
|
snap_hash = hashlib.sha1(snap.encode()).hexdigest()
|
|
@@ -776,13 +797,19 @@ _watcher_lock = threading.Lock()
|
|
|
776
797
|
|
|
777
798
|
def start_brain_watcher():
|
|
778
799
|
"""
|
|
779
|
-
Start the background
|
|
780
|
-
Safe to call multiple times
|
|
800
|
+
Start the background daemon watcher if not already running.
|
|
801
|
+
Safe to call multiple times.
|
|
781
802
|
"""
|
|
782
803
|
global _watcher_started
|
|
783
804
|
with _watcher_lock:
|
|
784
805
|
if _watcher_started:
|
|
785
806
|
return
|
|
807
|
+
try:
|
|
808
|
+
from utim_cli.tools import is_user_paid
|
|
809
|
+
if not is_user_paid():
|
|
810
|
+
return
|
|
811
|
+
except Exception:
|
|
812
|
+
pass
|
|
786
813
|
_watcher_started = True
|
|
787
814
|
|
|
788
815
|
# One-time cleanup of legacy duplicate records on startup
|
|
@@ -946,6 +973,13 @@ def get_brain_context_prompt(user_prompt: str = "") -> str:
|
|
|
946
973
|
relevant brain project folder matching the project data/query, and injects
|
|
947
974
|
direct clickable paths into the system prompt.
|
|
948
975
|
"""
|
|
976
|
+
try:
|
|
977
|
+
from utim_cli.tools import is_user_paid
|
|
978
|
+
if not is_user_paid():
|
|
979
|
+
return ""
|
|
980
|
+
except Exception:
|
|
981
|
+
pass
|
|
982
|
+
|
|
949
983
|
try:
|
|
950
984
|
pid, proj_dir = find_most_relevant_brain_project(user_prompt)
|
|
951
985
|
brain_root = _brain_root()
|
|
@@ -572,7 +572,7 @@ def proxy_openrouter_request(json_data: dict, stream: bool = False, timeout=None
|
|
|
572
572
|
return requests.post("https://openrouter.ai/api/v1/chat/completions", json=json_data, headers=headers, stream=stream or json_data.get("stream"), timeout=timeout, verify=config.verify_ssl)
|
|
573
573
|
|
|
574
574
|
|
|
575
|
-
def sanitize_messages_for_api(messages):
|
|
575
|
+
def sanitize_messages_for_api(messages, model_id: Optional[str] = None):
|
|
576
576
|
"""Normalize conversation history into a provider-safe shape before sending.
|
|
577
577
|
|
|
578
578
|
Fixes the Liquid/LFM-2.5 (and other strict OpenAI-compatible providers)
|
|
@@ -580,26 +580,85 @@ def sanitize_messages_for_api(messages):
|
|
|
580
580
|
`messages.N.tool_calls`. Internal code paths persist assistant messages
|
|
581
581
|
with `"tool_calls": None` (abort/quota returns, XML-fallback turns,
|
|
582
582
|
session reloads). The OpenAI spec says the key must be omitted or an
|
|
583
|
-
array — never null.
|
|
583
|
+
array — never null.
|
|
584
584
|
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
585
|
+
Also handles shifting between vision and non-vision models:
|
|
586
|
+
If target `model_id` is NOT vision-capable, converts any multimodal `image_url`
|
|
587
|
+
content blocks into text analysis blocks using precomputed background
|
|
588
|
+
analyses (from `analyze_image` / `_IMAGE_ANALYSIS_CACHE`) so non-vision models
|
|
589
|
+
do not trigger "No endpoints found which support images" from OpenRouter/providers.
|
|
590
590
|
|
|
591
591
|
Returns a new list; the input list and its dicts are not mutated.
|
|
592
592
|
"""
|
|
593
593
|
_INTERNAL_KEYS = (
|
|
594
594
|
"reasoning_duration", "was_cut_off", "was_truncated_by_limit",
|
|
595
595
|
"finish_reason", "completion_tokens", "cache_control", "aborted",
|
|
596
|
+
"_image_meta", "_image_path", "_image_analysis", "_image_entry", "_image_paths",
|
|
596
597
|
)
|
|
598
|
+
|
|
599
|
+
# Check if target model supports vision
|
|
600
|
+
is_vision = True
|
|
601
|
+
if model_id:
|
|
602
|
+
try:
|
|
603
|
+
from utim_cli.tools import is_model_vision_capable
|
|
604
|
+
is_vision = is_model_vision_capable(model_id)
|
|
605
|
+
except Exception:
|
|
606
|
+
is_vision = True
|
|
607
|
+
else:
|
|
608
|
+
try:
|
|
609
|
+
from utim_cli.tools import is_model_vision_capable
|
|
610
|
+
is_vision = is_model_vision_capable()
|
|
611
|
+
except Exception:
|
|
612
|
+
is_vision = True
|
|
613
|
+
|
|
597
614
|
clean = []
|
|
598
615
|
for msg in messages:
|
|
599
616
|
if not isinstance(msg, dict):
|
|
600
617
|
continue # never send non-dict junk to the API
|
|
601
618
|
m = dict(msg)
|
|
602
619
|
role = m.get("role")
|
|
620
|
+
content = m.get("content")
|
|
621
|
+
|
|
622
|
+
# Multimodal image handling: if the model is NOT vision capable,
|
|
623
|
+
# convert image_url blocks to text analysis!
|
|
624
|
+
if not is_vision and isinstance(content, list):
|
|
625
|
+
text_parts = []
|
|
626
|
+
for part in content:
|
|
627
|
+
if not isinstance(part, dict):
|
|
628
|
+
text_parts.append(str(part))
|
|
629
|
+
continue
|
|
630
|
+
p_type = part.get("type")
|
|
631
|
+
if p_type == "text":
|
|
632
|
+
t = part.get("text", "")
|
|
633
|
+
if t:
|
|
634
|
+
text_parts.append(t)
|
|
635
|
+
elif p_type == "image_url":
|
|
636
|
+
# Retrieve visual analysis
|
|
637
|
+
analysis = m.get("_image_analysis")
|
|
638
|
+
img_path = m.get("_image_path")
|
|
639
|
+
img_meta = m.get("_image_meta")
|
|
640
|
+
img_entry = m.get("_image_entry")
|
|
641
|
+
if img_entry and isinstance(img_entry, dict) and img_entry.get("analysis"):
|
|
642
|
+
analysis = img_entry.get("analysis")
|
|
643
|
+
if not analysis and img_path:
|
|
644
|
+
try:
|
|
645
|
+
from utim_cli.tools import _IMAGE_ANALYSIS_CACHE
|
|
646
|
+
analysis = _IMAGE_ANALYSIS_CACHE.get(img_path)
|
|
647
|
+
except Exception:
|
|
648
|
+
pass
|
|
649
|
+
if not analysis and img_path and os.path.exists(img_path):
|
|
650
|
+
try:
|
|
651
|
+
from utim_cli.tools import analyze_image, _IMAGE_ANALYSIS_CACHE
|
|
652
|
+
analysis = analyze_image(img_path, "Identify and describe this image in detail.")
|
|
653
|
+
if analysis and not analysis.startswith("Error"):
|
|
654
|
+
_IMAGE_ANALYSIS_CACHE[img_path] = analysis
|
|
655
|
+
except Exception as _ae:
|
|
656
|
+
analysis = f"Image {os.path.basename(img_path)} (Visual analysis unavailable: {_ae})"
|
|
657
|
+
if not analysis:
|
|
658
|
+
analysis = img_meta or "[Image visual content]"
|
|
659
|
+
|
|
660
|
+
text_parts.append(f"[Visual Image Analysis]:\n{analysis}")
|
|
661
|
+
m["content"] = "\n\n".join(p for p in text_parts if p.strip())
|
|
603
662
|
|
|
604
663
|
if role == "assistant":
|
|
605
664
|
tc = m.get("tool_calls")
|
|
@@ -6,7 +6,7 @@ import shutil
|
|
|
6
6
|
import time
|
|
7
7
|
import hashlib
|
|
8
8
|
import hmac
|
|
9
|
-
from typing import Any, Dict, Optional
|
|
9
|
+
from typing import Any, Dict, Optional, List
|
|
10
10
|
|
|
11
11
|
logger = logging.getLogger("utim.config")
|
|
12
12
|
|
|
@@ -199,15 +199,113 @@ def _apply_ssl_session_patch(disable: bool) -> None:
|
|
|
199
199
|
except Exception:
|
|
200
200
|
pass
|
|
201
201
|
|
|
202
|
+
def get_all_utim_dirs() -> List[pathlib.Path]:
|
|
203
|
+
"""Return all valid candidate .utim directories across workspace, environment variables,
|
|
204
|
+
and user profiles (Windows USERPROFILE, APPDATA, HOME, Path.home(), etc.).
|
|
205
|
+
Ordered by priority.
|
|
206
|
+
"""
|
|
207
|
+
candidates: List[pathlib.Path] = []
|
|
208
|
+
seen = set()
|
|
209
|
+
|
|
210
|
+
def _add(p):
|
|
211
|
+
if not p:
|
|
212
|
+
return
|
|
213
|
+
try:
|
|
214
|
+
resolved = pathlib.Path(p).expanduser().resolve()
|
|
215
|
+
r_str = str(resolved).lower()
|
|
216
|
+
if r_str not in seen:
|
|
217
|
+
seen.add(r_str)
|
|
218
|
+
candidates.append(resolved)
|
|
219
|
+
except Exception:
|
|
220
|
+
pass
|
|
221
|
+
|
|
222
|
+
# 1. Explicit environment variables (highest priority)
|
|
223
|
+
for env_var in ("UTIM_HOME", "UTIM_DIR", "UTIM_PATH", "AGY_HOME", "ANTIGRAVITY_HOME"):
|
|
224
|
+
val = os.environ.get(env_var)
|
|
225
|
+
if val:
|
|
226
|
+
_add(val)
|
|
227
|
+
|
|
228
|
+
# 2. PyTest environment check
|
|
229
|
+
import sys
|
|
230
|
+
if "pytest" in sys.argv[0] or "py.test" in sys.argv[0] or "PYTEST_CURRENT_TEST" in os.environ:
|
|
231
|
+
_add(pathlib.Path('.utim'))
|
|
232
|
+
|
|
233
|
+
# 3. Current workspace / project directories
|
|
234
|
+
try:
|
|
235
|
+
cwd = pathlib.Path.cwd()
|
|
236
|
+
_add(cwd / ".utim")
|
|
237
|
+
_add(cwd / ".agents")
|
|
238
|
+
except Exception:
|
|
239
|
+
pass
|
|
240
|
+
|
|
241
|
+
# 4. Standard User Home variants (handles Windows username differences, Git Bash, MSYS, Cygwin)
|
|
242
|
+
user_home_paths = []
|
|
243
|
+
if os.environ.get("USERPROFILE"):
|
|
244
|
+
user_home_paths.append(pathlib.Path(os.environ["USERPROFILE"]))
|
|
245
|
+
if os.environ.get("HOME"):
|
|
246
|
+
user_home_paths.append(pathlib.Path(os.environ["HOME"]))
|
|
247
|
+
if os.environ.get("HOMEDRIVE") and os.environ.get("HOMEPATH"):
|
|
248
|
+
user_home_paths.append(pathlib.Path(os.environ["HOMEDRIVE"] + os.environ["HOMEPATH"]))
|
|
249
|
+
try:
|
|
250
|
+
user_home_paths.append(pathlib.Path.home())
|
|
251
|
+
except Exception:
|
|
252
|
+
pass
|
|
253
|
+
try:
|
|
254
|
+
user_home_paths.append(pathlib.Path(os.path.expanduser("~")))
|
|
255
|
+
except Exception:
|
|
256
|
+
pass
|
|
257
|
+
if os.environ.get("APPDATA"):
|
|
258
|
+
user_home_paths.append(pathlib.Path(os.environ["APPDATA"]))
|
|
259
|
+
if os.environ.get("LOCALAPPDATA"):
|
|
260
|
+
user_home_paths.append(pathlib.Path(os.environ["LOCALAPPDATA"]))
|
|
261
|
+
|
|
262
|
+
for h in user_home_paths:
|
|
263
|
+
_add(h / ".utim")
|
|
264
|
+
_add(h / ".agents")
|
|
265
|
+
|
|
266
|
+
return candidates
|
|
267
|
+
|
|
268
|
+
|
|
202
269
|
def get_utim_dir() -> pathlib.Path:
|
|
203
270
|
"""Return the centralized .utim directory path.
|
|
204
|
-
|
|
205
|
-
|
|
271
|
+
Prioritizes explicit environment variables, existing .utim directories,
|
|
272
|
+
and falls back cleanly to the primary user profile directory.
|
|
206
273
|
"""
|
|
207
274
|
import sys
|
|
208
275
|
if "pytest" in sys.argv[0] or "py.test" in sys.argv[0] or "PYTEST_CURRENT_TEST" in os.environ:
|
|
209
276
|
return pathlib.Path('.utim')
|
|
210
|
-
|
|
277
|
+
|
|
278
|
+
# Check explicit env var
|
|
279
|
+
for env_var in ("UTIM_HOME", "UTIM_DIR", "UTIM_PATH"):
|
|
280
|
+
val = os.environ.get(env_var)
|
|
281
|
+
if val:
|
|
282
|
+
try:
|
|
283
|
+
p = pathlib.Path(val).expanduser().resolve()
|
|
284
|
+
p.mkdir(parents=True, exist_ok=True)
|
|
285
|
+
return p
|
|
286
|
+
except Exception:
|
|
287
|
+
pass
|
|
288
|
+
|
|
289
|
+
# Check existing .utim directories from all candidate paths
|
|
290
|
+
all_dirs = get_all_utim_dirs()
|
|
291
|
+
for d in all_dirs:
|
|
292
|
+
if d.exists() and d.is_dir() and d.name == ".utim":
|
|
293
|
+
return d
|
|
294
|
+
|
|
295
|
+
# Default fallback to primary user home
|
|
296
|
+
try:
|
|
297
|
+
if os.environ.get("USERPROFILE"):
|
|
298
|
+
fallback = pathlib.Path(os.environ["USERPROFILE"]) / ".utim"
|
|
299
|
+
else:
|
|
300
|
+
fallback = pathlib.Path.home() / ".utim"
|
|
301
|
+
except Exception:
|
|
302
|
+
fallback = pathlib.Path(os.path.expanduser("~")) / ".utim"
|
|
303
|
+
|
|
304
|
+
try:
|
|
305
|
+
fallback.mkdir(parents=True, exist_ok=True)
|
|
306
|
+
except Exception:
|
|
307
|
+
pass
|
|
308
|
+
return fallback
|
|
211
309
|
|
|
212
310
|
|
|
213
311
|
class Config:
|
|
@@ -1496,7 +1496,7 @@ def _call_compression_model_with_fallback(messages: List[Dict], llm_key: str, ma
|
|
|
1496
1496
|
},
|
|
1497
1497
|
stream=False,
|
|
1498
1498
|
timeout=(15, 300),
|
|
1499
|
-
is_reflection=
|
|
1499
|
+
is_reflection=False
|
|
1500
1500
|
)
|
|
1501
1501
|
if resp.status_code == 429:
|
|
1502
1502
|
# On 429 rate limit, immediately try fallback without blocking
|
|
@@ -153,7 +153,7 @@ def run_diagnostics(console: Console = None, fix: bool = False, include_heavy: b
|
|
|
153
153
|
from utim_cli.config import get_utim_dir
|
|
154
154
|
utim_dir = get_utim_dir()
|
|
155
155
|
local_utim = os.path.exists(utim_dir)
|
|
156
|
-
console.print(f" * .utim/ directory: {'[green]Exists[/green]' if local_utim else '[yellow]Missing[/yellow]'}")
|
|
156
|
+
console.print(f" * .utim/ directory: {'[green]Exists[/green]' if local_utim else '[yellow]Missing[/yellow]'} [dim]({utim_dir})[/dim]")
|
|
157
157
|
|
|
158
158
|
config_exists = os.path.exists(os.path.join(utim_dir, "config.json"))
|
|
159
159
|
console.print(f" * config.json: {'[green]Exists[/green]' if config_exists else '[yellow]Missing[/yellow]'}")
|
|
@@ -619,7 +619,23 @@ class Orchestrator(LLMTransportMixin, TurnHistoryMixin, ToolExecutionMixin):
|
|
|
619
619
|
"type": "image_url",
|
|
620
620
|
"image_url": {"url": f"data:{mime};base64,{b64}"}
|
|
621
621
|
})
|
|
622
|
-
|
|
622
|
+
# Background analyze image for non-vision fallback
|
|
623
|
+
def _bg_user_img_analyze(_p):
|
|
624
|
+
try:
|
|
625
|
+
from utim_cli.tools import analyze_image, _IMAGE_ANALYSIS_CACHE
|
|
626
|
+
if _p not in _IMAGE_ANALYSIS_CACHE:
|
|
627
|
+
res = analyze_image(_p, "Identify and describe this image in detail.")
|
|
628
|
+
if res and not res.startswith("Error"):
|
|
629
|
+
_IMAGE_ANALYSIS_CACHE[_p] = res
|
|
630
|
+
except Exception:
|
|
631
|
+
pass
|
|
632
|
+
threading.Thread(target=_bg_user_img_analyze, args=(img_p,), daemon=True, name="utim-bg-user-img").start()
|
|
633
|
+
|
|
634
|
+
self.messages.append({
|
|
635
|
+
"role": "user",
|
|
636
|
+
"content": multimodal_content,
|
|
637
|
+
"_image_paths": valid_images,
|
|
638
|
+
})
|
|
623
639
|
else:
|
|
624
640
|
# Text-only model fallback: analyze image to text context
|
|
625
641
|
for img_p in valid_images:
|
|
@@ -993,7 +1009,18 @@ class Orchestrator(LLMTransportMixin, TurnHistoryMixin, ToolExecutionMixin):
|
|
|
993
1009
|
self.console.print(f"\n[bold yellow]Parsed {len(tool_calls)} tool call(s) from assistant text response.[/bold yellow]")
|
|
994
1010
|
|
|
995
1011
|
# If the model response was cut off mid-turn due to length/token limits, nudge it to continue
|
|
996
|
-
|
|
1012
|
+
# ONLY trigger when the response was genuinely truncated by token limits (never when finish_reason is "stop" or "end_turn")
|
|
1013
|
+
_fr = msg.get("finish_reason") or "stop"
|
|
1014
|
+
_is_truly_token_truncated = (
|
|
1015
|
+
not msg.get("aborted")
|
|
1016
|
+
and not tool_calls
|
|
1017
|
+
and _fr not in ("stop", "end_turn", "tool_calls", "repetition_loop")
|
|
1018
|
+
and (
|
|
1019
|
+
_fr in ("length", "max_tokens")
|
|
1020
|
+
or bool(msg.get("was_truncated_by_limit"))
|
|
1021
|
+
)
|
|
1022
|
+
)
|
|
1023
|
+
if _is_truly_token_truncated:
|
|
997
1024
|
self.console.print("\n[bold yellow]Response truncated by token limits. Continuing response...[/bold yellow]\n")
|
|
998
1025
|
self.messages.append(
|
|
999
1026
|
{
|
|
@@ -1040,29 +1067,24 @@ class Orchestrator(LLMTransportMixin, TurnHistoryMixin, ToolExecutionMixin):
|
|
|
1040
1067
|
or (_attempted_tool_call and any(t in _MUTATION_TOOLS for t in _recent_tool_names))
|
|
1041
1068
|
)
|
|
1042
1069
|
|
|
1043
|
-
# If the model stopped with empty OR
|
|
1044
|
-
|
|
1070
|
+
# If the model stopped with empty OR purely an unperformed action promise
|
|
1071
|
+
_content_clean = content.strip()
|
|
1072
|
+
_content_len = len(_content_clean)
|
|
1073
|
+
_is_empty = (_content_len == 0)
|
|
1045
1074
|
_lower_content = content.lower()
|
|
1046
1075
|
_is_lazy_transition = False
|
|
1047
1076
|
is_planning = False
|
|
1048
1077
|
|
|
1049
|
-
# ── Fix
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
# Tool call was attempted but failed to parse or reconstruct — skip lazy logic
|
|
1054
|
-
pass
|
|
1078
|
+
# ── Fix: Never lazy-nudge when a tool call was attempted or already executed ──
|
|
1079
|
+
if _attempted_tool_call or _had_tools_this_turn or tool_calls:
|
|
1080
|
+
# The model already executed tools or attempted tools this turn — it's reporting results
|
|
1081
|
+
_is_lazy_transition = False
|
|
1055
1082
|
else:
|
|
1056
|
-
_only_inspection_tools = _had_tools_this_turn and all(t in _INSPECTION_TOOLS for t in _recent_tool_names)
|
|
1057
|
-
_content_clean = content.strip()
|
|
1058
|
-
_content_len = len(_content_clean)
|
|
1059
|
-
_ends_with_cliffhanger = _content_clean.endswith(":") or _content_clean.endswith("...")
|
|
1060
|
-
|
|
1061
1083
|
# Tail action promise: only genuine cliffhangers at the end of the text (e.g. "I will now edit foo.py:")
|
|
1062
1084
|
_ACTION_PROMISE_TAIL = [
|
|
1063
|
-
r"(?:i(?:'ll| will)|we(?:'ll| will))\s+(?:now\s+)?(?:update|edit|implement|create|fix|write|build|start|run)\b[
|
|
1064
|
-
r"let me\s+(?:now\s+)?(?:update|edit|implement|create|fix|write|build|start|run|check)\b[
|
|
1065
|
-
r"proceeding to\s+(?:implement|create|build|write|update|run)\b[
|
|
1085
|
+
r"^(?:i(?:'ll| will)|we(?:'ll| will))\s+(?:now\s+)?(?:update|edit|implement|create|fix|write|build|start|run)\b[^\n]*[:\.\.\.]?$",
|
|
1086
|
+
r"^let me\s+(?:now\s+)?(?:update|edit|implement|create|fix|write|build|start|run|check)\b[^\n]*[:\.\.\.]?$",
|
|
1087
|
+
r"^proceeding to\s+(?:implement|create|build|write|update|run)\b[^\n]*[:\.\.\.]?$",
|
|
1066
1088
|
]
|
|
1067
1089
|
_has_tail_action_promise = any(
|
|
1068
1090
|
re.search(pat, _content_clean, re.IGNORECASE)
|
|
@@ -1077,31 +1099,12 @@ class Orchestrator(LLMTransportMixin, TurnHistoryMixin, ToolExecutionMixin):
|
|
|
1077
1099
|
"exit code 0", "exit_code: 0", "passes", "passing", "as requested", "here are the findings",
|
|
1078
1100
|
"here is the result", "here is the breakdown", "here is what i found",
|
|
1079
1101
|
])
|
|
1080
|
-
_model_already_delivered = bool(_successful_mutations_this_turn)
|
|
1081
1102
|
|
|
1082
|
-
#
|
|
1083
|
-
|
|
1084
|
-
|
|
1103
|
+
# Only nudge if the ENTIRE message is a short (< 60 chars) unperformed action promise without tools
|
|
1104
|
+
if _content_len < 60 and _has_tail_action_promise and not _has_any_completion_marker and not is_planning:
|
|
1105
|
+
_is_lazy_transition = True
|
|
1106
|
+
else:
|
|
1085
1107
|
_is_lazy_transition = False
|
|
1086
|
-
elif _had_tools_this_turn:
|
|
1087
|
-
if _only_inspection_tools and not _attempted_mutations:
|
|
1088
|
-
# If the model inspected and returned a short uninformative cliffhanger without answering
|
|
1089
|
-
if _content_len < 40 and not _has_any_completion_marker and (_has_tail_action_promise or _ends_with_cliffhanger):
|
|
1090
|
-
_is_lazy_transition = True
|
|
1091
|
-
else:
|
|
1092
|
-
_is_lazy_transition = False
|
|
1093
|
-
elif is_planning:
|
|
1094
|
-
_is_lazy_transition = _content_len < 20
|
|
1095
|
-
else:
|
|
1096
|
-
# Nudge ONLY on genuine cliffhangers or empty promises
|
|
1097
|
-
_is_lazy_transition = (
|
|
1098
|
-
(_has_tail_action_promise or _ends_with_cliffhanger)
|
|
1099
|
-
or (_content_len < 30 and not _has_any_completion_marker and not _model_already_delivered)
|
|
1100
|
-
)
|
|
1101
|
-
elif not _had_tools_this_turn and not _is_empty and not is_planning and not tool_calls:
|
|
1102
|
-
# Model did not call any tools. Only nudge if it gave a short cliffhanger promising action.
|
|
1103
|
-
if _content_len < 100 and (_has_tail_action_promise or _ends_with_cliffhanger):
|
|
1104
|
-
_is_lazy_transition = True
|
|
1105
1108
|
|
|
1106
1109
|
# ── Fix 3/7: Handle tool-call parse failures explicitly ──────────
|
|
1107
1110
|
# The model emitted a tool call but the JSON was malformed/truncated.
|
|
@@ -1587,6 +1590,8 @@ class Orchestrator(LLMTransportMixin, TurnHistoryMixin, ToolExecutionMixin):
|
|
|
1587
1590
|
# Clear any stale vision images from a previous iteration before
|
|
1588
1591
|
# this batch of tools runs (defensive — shouldn't normally be set).
|
|
1589
1592
|
_tools_module._pending_vision_images.clear()
|
|
1593
|
+
_tools_module._cancel_event = self.cancel_event
|
|
1594
|
+
_tools_module._active_model_id = self.model_id
|
|
1590
1595
|
|
|
1591
1596
|
# Execute tools in parallel when beneficial
|
|
1592
1597
|
if len(tool_calls) > 1:
|
|
@@ -1650,6 +1655,10 @@ class Orchestrator(LLMTransportMixin, TurnHistoryMixin, ToolExecutionMixin):
|
|
|
1650
1655
|
{"type": "text", "text": f"[Image from read_file: {_vimg['meta']}]"},
|
|
1651
1656
|
{"type": "image_url", "image_url": {"url": f"data:{_vimg['mime']};base64,{_vimg['b64']}"}},
|
|
1652
1657
|
],
|
|
1658
|
+
"_image_meta": _vimg.get("meta", ""),
|
|
1659
|
+
"_image_path": _vimg.get("filepath", ""),
|
|
1660
|
+
"_image_analysis": _vimg.get("analysis", "") or getattr(_tools_module, "_IMAGE_ANALYSIS_CACHE", {}).get(_vimg.get("filepath", ""), ""),
|
|
1661
|
+
"_image_entry": _vimg,
|
|
1653
1662
|
}
|
|
1654
1663
|
)
|
|
1655
1664
|
_vision_queue.clear()
|
|
@@ -386,7 +386,7 @@ SYSTEM_PROMPT = SYSTEM_PROMPT + _detect_environment()
|
|
|
386
386
|
|
|
387
387
|
COMPRESSED_SYSTEM_PROMPT = f"""<identity>
|
|
388
388
|
You are UTIM, an elite agentic AI coding assistant and CLI pair programmer.
|
|
389
|
-
- Your identity is strictly UTIM (or UTIM AI).
|
|
389
|
+
- Your identity is strictly UTIM (or UTIM AI). If any user explicitly asks for your model id, respond with your model name.
|
|
390
390
|
- When greeted or asked who you are, introduce yourself concisely as UTIM.
|
|
391
391
|
</identity>
|
|
392
392
|
|
|
@@ -1182,7 +1182,7 @@ def get_frozen_system_prompt(user_prompt: str = "", current_iteration: int = 0,
|
|
|
1182
1182
|
return (
|
|
1183
1183
|
"<identity>\n"
|
|
1184
1184
|
"You are UTIM, an elite AI coding assistant and CLI pair programmer.\n"
|
|
1185
|
-
"- Your identity is strictly UTIM (or UTIM AI).
|
|
1185
|
+
"- Your identity is strictly UTIM (or UTIM AI). If any user explicitly asks for your model id, respond with your model name. \n"
|
|
1186
1186
|
"- If introducing yourself, introduce yourself as UTIM.\n"
|
|
1187
1187
|
"</identity>\n\n"
|
|
1188
1188
|
"Respond warmly and concisely in under 2 sentences."
|
|
@@ -1192,7 +1192,7 @@ def get_frozen_system_prompt(user_prompt: str = "", current_iteration: int = 0,
|
|
|
1192
1192
|
identity = (
|
|
1193
1193
|
"<identity>\n"
|
|
1194
1194
|
"You are UTIM, an elite agentic AI coding assistant and CLI pair programmer operating autonomously inside the user's terminal.\n"
|
|
1195
|
-
"- Your identity is strictly UTIM (or UTIM AI).
|
|
1195
|
+
"- Your identity is strictly UTIM (or UTIM AI). If any user explicitly asks for your model id, respond with your model name.\n"
|
|
1196
1196
|
"- When greeted or asked who you are, introduce yourself warmly as UTIM.\n"
|
|
1197
1197
|
"- You are autonomous, highly capable, proactive, and focused on writing correct code and executing tasks.\n"
|
|
1198
1198
|
"</identity>\n\n"
|
|
@@ -1204,7 +1204,7 @@ def get_frozen_system_prompt(user_prompt: str = "", current_iteration: int = 0,
|
|
|
1204
1204
|
identity = (
|
|
1205
1205
|
"<identity>\n"
|
|
1206
1206
|
"You are UTIM, an elite agentic AI coding assistant and CLI pair programmer.\n"
|
|
1207
|
-
"- Your identity is strictly UTIM (or UTIM AI).
|
|
1207
|
+
"- Your identity is strictly UTIM (or UTIM AI). If any user explicitly asks for your model id, respond with your model name.\n"
|
|
1208
1208
|
"- When greeted or asked who you are, introduce yourself concisely as UTIM.\n"
|
|
1209
1209
|
"</identity>\n\n"
|
|
1210
1210
|
)
|