utim-cli 2.3.13__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.13 → utim_cli-2.3.14}/CHANGELOG.md +7 -0
- {utim_cli-2.3.13/utim_cli.egg-info → utim_cli-2.3.14}/PKG-INFO +1 -1
- {utim_cli-2.3.13 → utim_cli-2.3.14}/pyproject.toml +1 -1
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim/__init__.py +1 -1
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/__init__.py +1 -1
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/_version.py +1 -1
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/ask_helper.py +1 -1
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/auth.py +2 -2
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/brain.py +36 -2
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/context_pruner.py +1 -1
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/orchestrator.py +19 -43
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/orchestrator_llm_transport.py +40 -19
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/reflection.py +33 -4
- utim_cli-2.3.14/utim_cli/server/key_rotator.py +710 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/server/router.py +11 -1
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/server/routes/auth_routes.py +21 -11
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/server/routes/completion_routes.py +136 -98
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/server/routes/credit_routes.py +26 -9
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/server/routes/marketplace_routes.py +17 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/tools.py +6 -19
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/tui/marketplace_dialog.py +8 -3
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/tui/model_dialog.py +17 -7
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/utim.py +84 -67
- {utim_cli-2.3.13 → utim_cli-2.3.14/utim_cli.egg-info}/PKG-INFO +1 -1
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli.egg-info/SOURCES.txt +1 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/LICENSE +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/MANIFEST.in +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/README.md +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/setup.cfg +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/setup.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/_verify_repro.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/_verify_syntax.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/agent.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/backup.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/billing.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/blender_agent.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/bootstrap.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/client_utils.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/config.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/constants.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/crawler/__init__.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/crawler/engine.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/crawler/markdown_distiller.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/dependency_manager.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/doctor.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/evidence_registry.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/harbor.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/knowledge_graph.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/local_db.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/logger.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/mcp_clean_wrapper.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/mcp_client.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/mcp_registry.json +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/models.txt +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/orchestrator_helpers.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/orchestrator_tool_exec.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/orchestrator_turn_history.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/personalities.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/platform_installer.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/plugins/__init__.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/plugins/hooks.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/plugins/manager.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/plugins/registry.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/plugins/schema.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/plugins/tool_loader.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/report.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/scrapy_search.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/sdk/__init__.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/sdk/config.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/sdk/context.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/sdk/events.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/sdk/handlers.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/sdk/session.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/sdk/sidecar.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/server/__init__.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/server/admin_auth.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/server/attribution.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/server/audit_log.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/server/auth.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/server/batch_processor.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/server/captcha.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/server/cli_auth.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/server/concurrency.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/server/db.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/server/docs_md/SECRET_PROVISIONING.md +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/server/docs_md/about.md +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/server/docs_md/changelog.md +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/server/docs_md/docs.md +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/server/docs_md/features.md +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/server/docs_md/license.md +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/server/docs_md/pricing.md +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/server/docs_md/privacy.md +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/server/docs_md/refund.md +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/server/docs_md/support.md +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/server/docs_md/terms.md +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/server/email_utils.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/server/exchange_rate.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/server/firebase.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/server/fix_duplicate_users.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/server/history.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/server/local_llm.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/server/logging_config.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/server/micro_batcher.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/server/model_agent.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/server/models.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/server/pricing_updater.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/server/provision_build.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/server/rate_limit.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/server/rewards_engine.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/server/routes/__init__.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/server/routes/admin_db_routes.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/server/routes/billing_routes.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/server/routes/feedback_routes.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/server/routes/quota_routes.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/server/routes/quota_share_routes.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/server/routes/referral_routes.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/server/routes/rewards_routes.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/server/routes/security_routes.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/server/routes/session_routes.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/server/routes/share_routes.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/server/server.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/server/storage_nodes.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/share.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/share_tui.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/situational_scoring.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/state.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/subagent_manager.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/task_dispatcher.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/task_logger.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/tools_grep.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/tui/__init__.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/tui/feedback_dialog.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/tui/history_dialog.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/tui/marketplace_app_state.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/tui/marketplace_layout_engine.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/tui/mcp_dialog.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/tui/miniagents_dialog.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/tui/personality_dialog.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/tui/plugins_dialog.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/tui/publish_dialog.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/tui/quota_dialog.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/tui/quota_redeem_dialog.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/tui/quota_share_dialog.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/tui/resume_dialog.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/tui/rewards_tui.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/tui/skills_dialog.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/tui/subagents_dialog.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/tui/tasks_dialog.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/tui/thinking_display.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/tui/tools_dialog.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/tui/update_dialog.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/utilities.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/utimmodel.txt +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/vector_memory.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/wheel.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli/workspace.py +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli.egg-info/dependency_links.txt +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli.egg-info/entry_points.txt +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli.egg-info/requires.txt +0 -0
- {utim_cli-2.3.13 → utim_cli-2.3.14}/utim_cli.egg-info/top_level.txt +0 -0
|
@@ -1,5 +1,12 @@
|
|
|
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
|
+
|
|
3
10
|
## [2.3.13] - 2026-08-28
|
|
4
11
|
|
|
5
12
|
### 🐞 Minor Bug Fixes
|
|
@@ -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
|
|
@@ -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
|
|
|
@@ -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()
|
|
@@ -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
|
|
@@ -1010,14 +1010,14 @@ class Orchestrator(LLMTransportMixin, TurnHistoryMixin, ToolExecutionMixin):
|
|
|
1010
1010
|
|
|
1011
1011
|
# If the model response was cut off mid-turn due to length/token limits, nudge it to continue
|
|
1012
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"
|
|
1013
1014
|
_is_truly_token_truncated = (
|
|
1014
1015
|
not msg.get("aborted")
|
|
1015
1016
|
and not tool_calls
|
|
1016
|
-
and
|
|
1017
|
+
and _fr not in ("stop", "end_turn", "tool_calls", "repetition_loop")
|
|
1017
1018
|
and (
|
|
1018
|
-
|
|
1019
|
-
or msg.get("
|
|
1020
|
-
or (msg.get("was_cut_off") and msg.get("finish_reason") not in ("stop", "end_turn"))
|
|
1019
|
+
_fr in ("length", "max_tokens")
|
|
1020
|
+
or bool(msg.get("was_truncated_by_limit"))
|
|
1021
1021
|
)
|
|
1022
1022
|
)
|
|
1023
1023
|
if _is_truly_token_truncated:
|
|
@@ -1067,29 +1067,24 @@ class Orchestrator(LLMTransportMixin, TurnHistoryMixin, ToolExecutionMixin):
|
|
|
1067
1067
|
or (_attempted_tool_call and any(t in _MUTATION_TOOLS for t in _recent_tool_names))
|
|
1068
1068
|
)
|
|
1069
1069
|
|
|
1070
|
-
# If the model stopped with empty OR
|
|
1071
|
-
|
|
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)
|
|
1072
1074
|
_lower_content = content.lower()
|
|
1073
1075
|
_is_lazy_transition = False
|
|
1074
1076
|
is_planning = False
|
|
1075
1077
|
|
|
1076
|
-
# ── Fix
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
# Tool call was attempted but failed to parse or reconstruct — skip lazy logic
|
|
1081
|
-
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
|
|
1082
1082
|
else:
|
|
1083
|
-
_only_inspection_tools = _had_tools_this_turn and all(t in _INSPECTION_TOOLS for t in _recent_tool_names)
|
|
1084
|
-
_content_clean = content.strip()
|
|
1085
|
-
_content_len = len(_content_clean)
|
|
1086
|
-
_ends_with_cliffhanger = _content_clean.endswith(":") or _content_clean.endswith("...")
|
|
1087
|
-
|
|
1088
1083
|
# Tail action promise: only genuine cliffhangers at the end of the text (e.g. "I will now edit foo.py:")
|
|
1089
1084
|
_ACTION_PROMISE_TAIL = [
|
|
1090
|
-
r"(?:i(?:'ll| will)|we(?:'ll| will))\s+(?:now\s+)?(?:update|edit|implement|create|fix|write|build|start|run)\b[
|
|
1091
|
-
r"let me\s+(?:now\s+)?(?:update|edit|implement|create|fix|write|build|start|run|check)\b[
|
|
1092
|
-
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]*[:\.\.\.]?$",
|
|
1093
1088
|
]
|
|
1094
1089
|
_has_tail_action_promise = any(
|
|
1095
1090
|
re.search(pat, _content_clean, re.IGNORECASE)
|
|
@@ -1104,31 +1099,12 @@ class Orchestrator(LLMTransportMixin, TurnHistoryMixin, ToolExecutionMixin):
|
|
|
1104
1099
|
"exit code 0", "exit_code: 0", "passes", "passing", "as requested", "here are the findings",
|
|
1105
1100
|
"here is the result", "here is the breakdown", "here is what i found",
|
|
1106
1101
|
])
|
|
1107
|
-
_model_already_delivered = bool(_successful_mutations_this_turn)
|
|
1108
1102
|
|
|
1109
|
-
#
|
|
1110
|
-
|
|
1111
|
-
|
|
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:
|
|
1112
1107
|
_is_lazy_transition = False
|
|
1113
|
-
elif _had_tools_this_turn:
|
|
1114
|
-
if _only_inspection_tools and not _attempted_mutations:
|
|
1115
|
-
# If the model inspected and returned a short uninformative cliffhanger without answering
|
|
1116
|
-
if _content_len < 40 and not _has_any_completion_marker and (_has_tail_action_promise or _ends_with_cliffhanger):
|
|
1117
|
-
_is_lazy_transition = True
|
|
1118
|
-
else:
|
|
1119
|
-
_is_lazy_transition = False
|
|
1120
|
-
elif is_planning:
|
|
1121
|
-
_is_lazy_transition = _content_len < 20
|
|
1122
|
-
else:
|
|
1123
|
-
# Nudge ONLY on genuine cliffhangers or empty promises
|
|
1124
|
-
_is_lazy_transition = (
|
|
1125
|
-
(_has_tail_action_promise or _ends_with_cliffhanger)
|
|
1126
|
-
or (_content_len < 30 and not _has_any_completion_marker and not _model_already_delivered)
|
|
1127
|
-
)
|
|
1128
|
-
elif not _had_tools_this_turn and not _is_empty and not is_planning and not tool_calls:
|
|
1129
|
-
# Model did not call any tools. Only nudge if it gave a short cliffhanger promising action.
|
|
1130
|
-
if _content_len < 100 and (_has_tail_action_promise or _ends_with_cliffhanger):
|
|
1131
|
-
_is_lazy_transition = True
|
|
1132
1108
|
|
|
1133
1109
|
# ── Fix 3/7: Handle tool-call parse failures explicitly ──────────
|
|
1134
1110
|
# The model emitted a tool call but the JSON was malformed/truncated.
|
|
@@ -1193,9 +1193,11 @@ class LLMTransportMixin:
|
|
|
1193
1193
|
try:
|
|
1194
1194
|
if getattr(self, "_subagent_depth", 0) == 0:
|
|
1195
1195
|
from utim_cli.state import STATE
|
|
1196
|
-
STATE
|
|
1197
|
-
|
|
1198
|
-
|
|
1196
|
+
cur_top = STATE.get("thinking_topic", "")
|
|
1197
|
+
if not str(cur_top).startswith("Request Queued"):
|
|
1198
|
+
STATE["thinking_topic"] = _THINKING_PHASES[_phase_idx % len(_THINKING_PHASES)]
|
|
1199
|
+
_phase_idx += 1
|
|
1200
|
+
_last_phase_time = now
|
|
1199
1201
|
except Exception:
|
|
1200
1202
|
pass
|
|
1201
1203
|
|
|
@@ -1240,7 +1242,23 @@ class LLMTransportMixin:
|
|
|
1240
1242
|
if "type" in chunk:
|
|
1241
1243
|
last_content_time = time.time()
|
|
1242
1244
|
t = chunk["type"]
|
|
1243
|
-
if t == "
|
|
1245
|
+
if t == "queue_status":
|
|
1246
|
+
pos = chunk.get("position", 1)
|
|
1247
|
+
status = chunk.get("status", "queued")
|
|
1248
|
+
if status == "queued":
|
|
1249
|
+
try:
|
|
1250
|
+
from utim_cli.state import STATE
|
|
1251
|
+
STATE["thinking_topic"] = f"Request Queued Position {pos}"
|
|
1252
|
+
except Exception:
|
|
1253
|
+
pass
|
|
1254
|
+
elif status == "processing":
|
|
1255
|
+
try:
|
|
1256
|
+
from utim_cli.state import STATE
|
|
1257
|
+
STATE["thinking_topic"] = "Formulating response..."
|
|
1258
|
+
except Exception:
|
|
1259
|
+
pass
|
|
1260
|
+
continue
|
|
1261
|
+
elif t == "thinking_delta":
|
|
1244
1262
|
t_text = chunk.get("text", "")
|
|
1245
1263
|
if t_text:
|
|
1246
1264
|
if _rep_detector.append_and_check(t_text):
|
|
@@ -1339,6 +1357,10 @@ class LLMTransportMixin:
|
|
|
1339
1357
|
if not silent:
|
|
1340
1358
|
self.console.print(f"\n[bold red]Model Error ({current_model}): {err_msg}[/bold red]\n")
|
|
1341
1359
|
raise RuntimeError(f"Server completion error ({current_model}): {err_msg}")
|
|
1360
|
+
if "finish_reason" in chunk and chunk["finish_reason"]:
|
|
1361
|
+
_stream_finish_reason = chunk["finish_reason"]
|
|
1362
|
+
elif not _stream_finish_reason:
|
|
1363
|
+
_stream_finish_reason = "stop"
|
|
1342
1364
|
final_content = chunk.get("content") or final_content
|
|
1343
1365
|
if not final_content and chunk.get("reasoning"):
|
|
1344
1366
|
final_content = chunk["reasoning"]
|
|
@@ -1347,6 +1369,7 @@ class LLMTransportMixin:
|
|
|
1347
1369
|
if usage:
|
|
1348
1370
|
in_t = usage.get("input_tokens", 0)
|
|
1349
1371
|
out_t = usage.get("output_tokens", 0)
|
|
1372
|
+
_stream_completion_tokens = out_t
|
|
1350
1373
|
from utim_cli.server.models import estimate_cost
|
|
1351
1374
|
cost = estimate_cost(current_model, in_t, out_t)
|
|
1352
1375
|
from utim_cli.client_utils import update_session_usage
|
|
@@ -1679,22 +1702,20 @@ class LLMTransportMixin:
|
|
|
1679
1702
|
except Exception:
|
|
1680
1703
|
pass
|
|
1681
1704
|
|
|
1682
|
-
#
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
and _stream_completion_tokens >= _max_out - 5
|
|
1705
|
+
# If finish_reason is an explicit completion, response is NOT cut off
|
|
1706
|
+
if _stream_finish_reason in ("stop", "end_turn"):
|
|
1707
|
+
was_cut_off = False
|
|
1708
|
+
_was_truncated_by_limit = False
|
|
1709
|
+
else:
|
|
1710
|
+
# Truncation diagnosis: was the stream cut short by output token limit?
|
|
1711
|
+
_max_out = _get_model_max_output(current_model)
|
|
1712
|
+
_was_truncated_by_limit = (
|
|
1713
|
+
_stream_finish_reason in ("length", "max_tokens")
|
|
1714
|
+
or (
|
|
1715
|
+
_stream_completion_tokens > 0
|
|
1716
|
+
and _stream_completion_tokens >= _max_out - 5
|
|
1717
|
+
)
|
|
1696
1718
|
)
|
|
1697
|
-
)
|
|
1698
1719
|
|
|
1699
1720
|
final_msg = {
|
|
1700
1721
|
"role": "assistant",
|
|
@@ -1374,14 +1374,29 @@ def get_buffered_interactions(limit: int = 5) -> List[Dict]:
|
|
|
1374
1374
|
return []
|
|
1375
1375
|
|
|
1376
1376
|
|
|
1377
|
+
_REFLECTION_KEY_ROTATION_IDX: int = 0
|
|
1378
|
+
_REFLECTION_KEY_ROTATION_LOCK = threading.Lock()
|
|
1379
|
+
|
|
1377
1380
|
def _get_reflection_key() -> str:
|
|
1378
1381
|
"""
|
|
1379
|
-
Returns
|
|
1380
|
-
|
|
1382
|
+
Returns an active API key for reflection, rotating among dedicated OPENROUTER_API_KEY2,
|
|
1383
|
+
OPENROUTER_API_KEY3, OPENROUTER_API_KEY4, logged-in UTIM credentials, and ambient OPENROUTER_API_KEY.
|
|
1381
1384
|
"""
|
|
1382
1385
|
try:
|
|
1383
|
-
|
|
1384
|
-
|
|
1386
|
+
# Collect all configured OpenRouter keys for reflection rotation
|
|
1387
|
+
keys = []
|
|
1388
|
+
for var in ["OPENROUTER_API_KEY2", "OPENROUTER_API_KEY3", "OPENROUTER_API_KEY4", "OPENROUTER_API_KEY"]:
|
|
1389
|
+
val = os.getenv(var, "").strip()
|
|
1390
|
+
if val and val not in keys:
|
|
1391
|
+
keys.append(val)
|
|
1392
|
+
|
|
1393
|
+
if keys:
|
|
1394
|
+
global _REFLECTION_KEY_ROTATION_IDX
|
|
1395
|
+
with _REFLECTION_KEY_ROTATION_LOCK:
|
|
1396
|
+
chosen = keys[_REFLECTION_KEY_ROTATION_IDX % len(keys)]
|
|
1397
|
+
_REFLECTION_KEY_ROTATION_IDX = (_REFLECTION_KEY_ROTATION_IDX + 1) % len(keys)
|
|
1398
|
+
return chosen
|
|
1399
|
+
|
|
1385
1400
|
from utim_cli.config import config
|
|
1386
1401
|
api_key = config.get("api_key") or os.getenv("UTIM_API_KEY")
|
|
1387
1402
|
source = config.get("main_model_source")
|
|
@@ -1410,6 +1425,13 @@ def _call_reflection_llm(prompt: str, llm_key: str = None, system: str = "", max
|
|
|
1410
1425
|
"""
|
|
1411
1426
|
global _LAST_REFLECTION_CALL_TIME, _REFLECTION_COOLDOWN_UNTIL
|
|
1412
1427
|
|
|
1428
|
+
try:
|
|
1429
|
+
from utim_cli.tools import is_user_paid
|
|
1430
|
+
if not is_user_paid():
|
|
1431
|
+
return {}
|
|
1432
|
+
except Exception:
|
|
1433
|
+
pass
|
|
1434
|
+
|
|
1413
1435
|
now = time.time()
|
|
1414
1436
|
if now < _REFLECTION_COOLDOWN_UNTIL:
|
|
1415
1437
|
# Under active rate-limit cooldown — skip background reflection gracefully
|
|
@@ -2299,6 +2321,13 @@ class BackgroundReflectionDispatcher:
|
|
|
2299
2321
|
tool_results: List[Dict], elapsed_seconds: int,
|
|
2300
2322
|
iterations: int, hints: Optional[List[str]],
|
|
2301
2323
|
messages_snapshot: Optional[List[Dict]] = None):
|
|
2324
|
+
try:
|
|
2325
|
+
from utim_cli.tools import is_user_paid
|
|
2326
|
+
if not is_user_paid():
|
|
2327
|
+
return
|
|
2328
|
+
except Exception:
|
|
2329
|
+
pass
|
|
2330
|
+
|
|
2302
2331
|
self.start()
|
|
2303
2332
|
while self._task_queue.qsize() >= 3:
|
|
2304
2333
|
try:
|