utim-cli 1.48.2__tar.gz → 1.48.4__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-1.48.2 → utim_cli-1.48.4}/CHANGELOG.md +11 -1
- {utim_cli-1.48.2/utim_cli.egg-info → utim_cli-1.48.4}/PKG-INFO +1 -1
- {utim_cli-1.48.2 → utim_cli-1.48.4}/pyproject.toml +1 -1
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/__init__.py +1 -1
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/_version.py +1 -1
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/blender_agent.py +27 -134
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/client_utils.py +5 -2
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/orchestrator.py +87 -25
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/server/auth.py +5 -0
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/server/db.py +9 -21
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/server/routes/completion_routes.py +406 -129
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/server/routes/quota_routes.py +1 -1
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/server/routes/quota_share_routes.py +2 -1
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/server/routes/share_routes.py +0 -2
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/tools.py +395 -25
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/tui/quota_redeem_dialog.py +20 -3
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/tui/quota_share_dialog.py +22 -4
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/tui/tools_dialog.py +5 -26
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/utim.py +34 -3
- {utim_cli-1.48.2 → utim_cli-1.48.4/utim_cli.egg-info}/PKG-INFO +1 -1
- {utim_cli-1.48.2 → utim_cli-1.48.4}/LICENSE +0 -0
- {utim_cli-1.48.2 → utim_cli-1.48.4}/MANIFEST.in +0 -0
- {utim_cli-1.48.2 → utim_cli-1.48.4}/README.md +0 -0
- {utim_cli-1.48.2 → utim_cli-1.48.4}/setup.cfg +0 -0
- {utim_cli-1.48.2 → utim_cli-1.48.4}/setup.py +0 -0
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/agent.py +0 -0
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/auth.py +0 -0
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/backup.py +0 -0
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/bootstrap.py +0 -0
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/config.py +0 -0
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/constants.py +0 -0
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/context_pruner.py +0 -0
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/doctor.py +0 -0
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/knowledge_graph.py +0 -0
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/local_db.py +0 -0
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/logger.py +0 -0
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/mcp_clean_wrapper.py +0 -0
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/mcp_client.py +0 -0
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/mcp_registry.json +0 -0
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/reflection.py +0 -0
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/report.py +0 -0
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/scrapy_search.py +0 -0
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/server/__init__.py +0 -0
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/server/docs_md/about.md +0 -0
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/server/docs_md/changelog.md +0 -0
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/server/docs_md/docs.md +0 -0
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/server/docs_md/features.md +0 -0
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/server/docs_md/license.md +0 -0
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/server/docs_md/pricing.md +0 -0
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/server/docs_md/privacy.md +0 -0
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/server/docs_md/refund.md +0 -0
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/server/docs_md/support.md +0 -0
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/server/docs_md/terms.md +0 -0
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/server/email_utils.py +0 -0
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/server/exchange_rate.py +0 -0
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/server/firebase.py +0 -0
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/server/history.py +0 -0
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/server/logging_config.py +0 -0
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/server/models.py +0 -0
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/server/pricing_updater.py +0 -0
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/server/rate_limit.py +0 -0
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/server/router.py +0 -0
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/server/routes/__init__.py +0 -0
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/server/routes/auth_routes.py +0 -0
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/server/routes/credit_routes.py +0 -0
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/server/routes/feedback_routes.py +0 -0
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/server/routes/referral_routes.py +0 -0
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/server/routes/session_routes.py +0 -0
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/server/server.py +0 -0
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/share.py +0 -0
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/share_tui.py +0 -0
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/situational_scoring.py +0 -0
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/state.py +0 -0
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/tui/__init__.py +0 -0
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/tui/feedback_dialog.py +0 -0
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/tui/history_dialog.py +0 -0
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/tui/mcp_dialog.py +0 -0
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/tui/model_dialog.py +0 -0
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/tui/quota_dialog.py +0 -0
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/tui/resume_dialog.py +0 -0
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/tui/skills_dialog.py +0 -0
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/tui/update_dialog.py +0 -0
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/utilities.py +0 -0
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/vector_memory.py +0 -0
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/workspace.py +0 -0
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli.egg-info/SOURCES.txt +0 -0
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli.egg-info/dependency_links.txt +0 -0
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli.egg-info/entry_points.txt +0 -0
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli.egg-info/requires.txt +0 -0
- {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli.egg-info/top_level.txt +0 -0
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## [1.48.
|
|
3
|
+
## [1.48.4] - 2026-07-25
|
|
4
|
+
|
|
5
|
+
- **Minor Bug Fixes**: Under-the-hood optimization and resolution of minor dependency issues.
|
|
6
|
+
|
|
7
|
+
## [1.48.3] - 2026-07-25
|
|
4
8
|
|
|
5
9
|
### Fixed
|
|
10
|
+
- **Blender 3D Tools**: Fully restored normal functionality and re-enabled the local Blender asset generation workflow and agent scripts.
|
|
11
|
+
- **Windows Shell Sanitization**: Resolved issues with Unix commands like `mkdir -p` and `rm -rf` failing on native Windows PowerShell by introducing automatic translation to `New-Item` and `Remove-Item`.
|
|
12
|
+
- **Command Redirection Safeguards**: Automatically blocks Bash-style here-docs (`<<`) on Windows with a helpful prompt to guide models to use proper file-write tools.
|
|
13
|
+
- **UI Abort and Confirmation Fixes**: Removed the 30-second background timeout on confirmation dialogs to prevent thread synchronization freeze states. Fixed signal-handling bugs so pressing Ctrl+C or Esc immediately cancels active HTTP streams and releases the CLI layout.
|
|
14
|
+
|
|
15
|
+
## [1.48.2] - 2026-07-23
|
|
6
16
|
- **Minor Bug Fixes**: Under-the-hood optimization and resolution of minor dependency issues.
|
|
7
17
|
|
|
8
18
|
## [1.48.1] - 2026-07-23
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "utim-cli"
|
|
7
|
-
version = "1.48.
|
|
7
|
+
version = "1.48.4"
|
|
8
8
|
description = "UTIM – Universal Terminal Intelligence Manager. A powerful agentic AI coding assistant for your terminal."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
@@ -1079,143 +1079,36 @@ def _phase3_execute(
|
|
|
1079
1079
|
f"Last error:\n{last_error}"
|
|
1080
1080
|
)
|
|
1081
1081
|
|
|
1082
|
-
|
|
1083
|
-
# ---------------------------------------------------------------------------
|
|
1084
|
-
# Public entry point
|
|
1085
|
-
# ---------------------------------------------------------------------------
|
|
1086
|
-
|
|
1087
1082
|
def blender_agent_create_from_image(
|
|
1088
|
-
image_path: str,
|
|
1089
|
-
|
|
1083
|
+
image_path: Optional[str] = None,
|
|
1084
|
+
prompt: Optional[str] = None,
|
|
1085
|
+
name: Optional[str] = None,
|
|
1090
1086
|
output_path: Optional[str] = None,
|
|
1091
|
-
output_format: str = "
|
|
1087
|
+
output_format: str = "glb",
|
|
1092
1088
|
) -> str:
|
|
1093
|
-
"""Create a detailed 3-D model from an image using the
|
|
1094
|
-
|
|
1095
|
-
Parameters
|
|
1096
|
-
----------
|
|
1097
|
-
image_path:
|
|
1098
|
-
Absolute or relative path to the source image (PNG, JPG, WEBP, BMP).
|
|
1099
|
-
name:
|
|
1100
|
-
Base name for the Blender object and the exported file (no extension).
|
|
1101
|
-
output_path:
|
|
1102
|
-
Directory where the exported file will be saved.
|
|
1103
|
-
Defaults to ``blender_assets/`` in the current working directory.
|
|
1104
|
-
output_format:
|
|
1105
|
-
``"blend"``, ``"obj"``, ``"glb"``, or ``"fbx"``. Defaults to ``"blend"``.
|
|
1106
|
-
|
|
1107
|
-
Returns
|
|
1108
|
-
-------
|
|
1109
|
-
str
|
|
1110
|
-
A human-readable progress log including the path to the exported
|
|
1111
|
-
file, or an error description.
|
|
1112
|
-
"""
|
|
1113
|
-
return "Error: The Blender 3D tool is currently Under Maintenance."
|
|
1114
|
-
|
|
1115
|
-
output_dir = output_path or os.path.join(os.getcwd(), ".utim_tmp", "blender_assets")
|
|
1116
|
-
assets_dir = pathlib.Path(output_dir).absolute()
|
|
1117
|
-
assets_dir.mkdir(parents=True, exist_ok=True)
|
|
1118
|
-
|
|
1119
|
-
safe_name = re.sub(r"[^\w\-]", "_", name)
|
|
1120
|
-
export_filename = f"{safe_name}_{uuid.uuid4().hex[:8]}.{output_format}"
|
|
1121
|
-
export_path = str(assets_dir / export_filename)
|
|
1122
|
-
|
|
1123
|
-
tmp_dir = pathlib.Path(".utim_tmp/blender")
|
|
1124
|
-
tmp_dir.mkdir(parents=True, exist_ok=True)
|
|
1125
|
-
|
|
1126
|
-
log_lines: List[str] = []
|
|
1127
|
-
|
|
1128
|
-
def _log(msg: str) -> None:
|
|
1129
|
-
log_lines.append(msg)
|
|
1130
|
-
|
|
1131
|
-
# Time tracking
|
|
1132
|
-
timings = {}
|
|
1133
|
-
overall_start = time.time()
|
|
1134
|
-
|
|
1135
|
-
def _time_log(phase: str) -> float:
|
|
1136
|
-
elapsed = time.time() - overall_start
|
|
1137
|
-
timings[phase] = elapsed
|
|
1138
|
-
return elapsed
|
|
1139
|
-
|
|
1140
|
-
# ── Phase 0: Local image analysis ────────────────────────────────────────
|
|
1141
|
-
_log("[Blender Agent] Phase 0: Local image analysis...")
|
|
1142
|
-
phase0_start = time.time()
|
|
1143
|
-
scene_brief = _phase0_local_analysis(image_path)
|
|
1144
|
-
_time_log("Phase 0")
|
|
1145
|
-
_log(
|
|
1146
|
-
f" Resolution : {scene_brief.get('width')}x{scene_brief.get('height')} px\n"
|
|
1147
|
-
f" Brightness : {scene_brief.get('brightness')}\n"
|
|
1148
|
-
f" Dom. colours: {len(scene_brief.get('dominant_colors', []))} found"
|
|
1149
|
-
)
|
|
1089
|
+
"""Create a detailed 3-D model from an image or text prompt using the direct Tripo API.
|
|
1150
1090
|
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
_time_log("Phase 1")
|
|
1158
|
-
return "\n".join(log_lines) + f"\n[Blender Agent] Phase 1 FAILED.\nReason: {exc}"
|
|
1159
|
-
_time_log("Phase 1")
|
|
1160
|
-
|
|
1161
|
-
part_count = len(scene_data.get("parts", []))
|
|
1162
|
-
tattoos = scene_data.get("face_details", {}).get("tattoos", [])
|
|
1163
|
-
_log(
|
|
1164
|
-
f" Scene type : {scene_data.get('scene_type', 'unknown')}\n"
|
|
1165
|
-
f" Subject : {scene_data.get('subject_label', 'unknown')}\n"
|
|
1166
|
-
f" Description : {scene_data.get('description', '')[:120]}...\n"
|
|
1167
|
-
f" Parts found : {part_count}\n"
|
|
1168
|
-
f" Hair style : {scene_data.get('hair', {}).get('style', 'n/a')}\n"
|
|
1169
|
-
f" Has tattoos : {len(tattoos) if isinstance(tattoos, list) else bool(tattoos)}\n"
|
|
1170
|
-
f" Clothing : {len(scene_data.get('clothing', []))} item(s)\n"
|
|
1171
|
-
f" Objects : {len(scene_data.get('objects', []))} background objects"
|
|
1172
|
-
)
|
|
1091
|
+
Saves the resulting model to the blender_assets/ folder inside .utim_tmp/ directory.
|
|
1092
|
+
"""
|
|
1093
|
+
from utim_cli.tools import generate_3d_model
|
|
1094
|
+
import os
|
|
1095
|
+
import re
|
|
1096
|
+
import uuid
|
|
1173
1097
|
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
json_path.write_text(json.dumps(scene_data, indent=2), encoding="utf-8")
|
|
1177
|
-
_log(f" Scene JSON : {json_path}")
|
|
1098
|
+
if not image_path and not prompt:
|
|
1099
|
+
return "Error: Please provide either an image_path or a text prompt for 3D model generation."
|
|
1178
1100
|
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
return "\n".join(log_lines) + f"\n[Blender Agent] Phase 2 FAILED.\nReason: {exc}"
|
|
1194
|
-
_time_log("Phase 2")
|
|
1195
|
-
|
|
1196
|
-
script_preview_path = tmp_dir / f"gen_{safe_name}_preview.py"
|
|
1197
|
-
script_preview_path.write_text(script, encoding="utf-8")
|
|
1198
|
-
_log(f" Script saved: {script_preview_path}")
|
|
1199
|
-
|
|
1200
|
-
# ── Phase 3: Execute & validate ───────────────────────────────────────────
|
|
1201
|
-
_log("[Blender Agent] Phase 3: Running Blender headless...")
|
|
1202
|
-
phase3_start = time.time()
|
|
1203
|
-
try:
|
|
1204
|
-
final_path = _phase3_execute(
|
|
1205
|
-
script=script,
|
|
1206
|
-
scene_data=scene_data,
|
|
1207
|
-
image_path=image_path,
|
|
1208
|
-
name=safe_name,
|
|
1209
|
-
export_path=export_path,
|
|
1210
|
-
export_format=output_format,
|
|
1211
|
-
tmp_dir=tmp_dir,
|
|
1212
|
-
)
|
|
1213
|
-
except Exception as exc:
|
|
1214
|
-
_time_log("Phase 3")
|
|
1215
|
-
return "\n".join(log_lines) + f"\n[Blender Agent] Phase 3 FAILED.\nReason: {exc}"
|
|
1216
|
-
_time_log("Phase 3")
|
|
1217
|
-
|
|
1218
|
-
total_elapsed = time.time() - overall_start
|
|
1219
|
-
_log(f"[Blender Agent] SUCCESS - exported to: {final_path}")
|
|
1220
|
-
_log(f"[Blender Agent] Total elapsed time: {total_elapsed:.1f}s")
|
|
1221
|
-
return "\n".join(log_lines)
|
|
1101
|
+
task_type = "image_to_model" if image_path else "text_to_model"
|
|
1102
|
+
safe_name = name or f"blender_{uuid.uuid4().hex[:8]}"
|
|
1103
|
+
|
|
1104
|
+
# Map 'blend' or other formats to glb if needed
|
|
1105
|
+
tripo_format = output_format if output_format in ("glb", "obj") else "glb"
|
|
1106
|
+
res = generate_3d_model(
|
|
1107
|
+
type=task_type,
|
|
1108
|
+
prompt=prompt,
|
|
1109
|
+
image_path=image_path,
|
|
1110
|
+
name=safe_name,
|
|
1111
|
+
output_format=tripo_format
|
|
1112
|
+
)
|
|
1113
|
+
|
|
1114
|
+
return f"[Blender Agent] Tripo direct API generation response:\n{res}"
|
|
@@ -258,7 +258,7 @@ def parse_xml_tool_calls(content: str) -> tuple[str | None, list[dict]]:
|
|
|
258
258
|
|
|
259
259
|
args = {}
|
|
260
260
|
# Pattern 1: <arg_key>key</arg_key>\s*<arg_value>value</arg_value>
|
|
261
|
-
arg_pattern = re.compile(r'<arg_key>([^<]+)</arg_key>\s*<arg_value>([
|
|
261
|
+
arg_pattern = re.compile(r'<arg_key>([^<]+)</arg_key>\s*<arg_value>([\s\S]*?)</arg_value>')
|
|
262
262
|
for arg_match in arg_pattern.finditer(block):
|
|
263
263
|
args[arg_match.group(1).strip()] = arg_match.group(2).strip()
|
|
264
264
|
|
|
@@ -269,9 +269,12 @@ def parse_xml_tool_calls(content: str) -> tuple[str | None, list[dict]]:
|
|
|
269
269
|
|
|
270
270
|
# Standardize argument name for run_command
|
|
271
271
|
if tool_name == "run_command":
|
|
272
|
-
for wrong_key in ["CommandLine", "cmd", "shell", "script"]:
|
|
272
|
+
for wrong_key in ["CommandLine", "commandLine", "command_line", "cmd_line", "cmd", "shell", "script"]:
|
|
273
273
|
if wrong_key in args and "command" not in args:
|
|
274
274
|
args["command"] = args.pop(wrong_key)
|
|
275
|
+
for path_key in ["cwd", "directory", "folder", "path"]:
|
|
276
|
+
if path_key in args and "dir_path" not in args:
|
|
277
|
+
args["dir_path"] = args.pop(path_key)
|
|
275
278
|
for bg_key in ["background", "is_background", "bg"]:
|
|
276
279
|
if bg_key in args:
|
|
277
280
|
val = args.pop(bg_key)
|
|
@@ -714,18 +714,18 @@ def _get_dynamic_directives(is_compressed: bool, messages: Optional[List[Dict]]
|
|
|
714
714
|
directives.append("2. **Planning/Autonomous**: No tools are currently enabled. Do NOT attempt to call any tools.")
|
|
715
715
|
|
|
716
716
|
if has_plan_project and has_run_command:
|
|
717
|
-
directives.append("3. **Think-Create-Verify**:
|
|
717
|
+
directives.append("3. **Think-Create-Verify**: Start executing right away. You do NOT need to write a plan or preamble in text before calling tools. Output tool calls immediately, then explain results or check tests. Verify with `run_command` (build/test/run), do not just read.")
|
|
718
718
|
elif has_plan_project:
|
|
719
|
-
directives.append("3. **Think-Create-Verify**:
|
|
719
|
+
directives.append("3. **Think-Create-Verify**: Start executing right away. You do NOT need to write a plan or preamble in text before calling tools. Output tool calls immediately, then explain results or check tests.")
|
|
720
720
|
elif has_run_command:
|
|
721
|
-
directives.append("3. **Think-Create-Verify**:
|
|
721
|
+
directives.append("3. **Think-Create-Verify**: Start executing right away. You do NOT need to write a plan or preamble in text before calling tools. Output tool calls immediately, then explain results or check tests. Verify with `run_command` (build/test/run), do not just read.")
|
|
722
722
|
else:
|
|
723
|
-
directives.append("3. **Think-Create-Verify**:
|
|
723
|
+
directives.append("3. **Think-Create-Verify**: Start executing right away. You do NOT need to write a plan or preamble in text before calling tools. Output tool calls immediately, then explain results or check tests.")
|
|
724
724
|
|
|
725
725
|
directives.append(f"4. **Manifesto**: Safety and coding standards are in {_posix}/UTIM.md.")
|
|
726
726
|
directives.append("5. **Output**: Be concise and professional. Summarize all changes and test results when complete. Speak colloquially and warmly. If you notice other code smells, potential bugs, or logical issues in the code snippets you read during execution, list them as proposed improvements in your final summary for the user instead of fixing them immediately.")
|
|
727
|
-
directives.append("6. **Execution**:
|
|
728
|
-
directives.append("7. **Tool Calling**: Use native function-calling.
|
|
727
|
+
directives.append("6. **Execution**: Avoid announcing what you are going to do or writing text-only planning steps. Just invoke the appropriate tool call directly. You may explain your reasoning in the same message alongside the tool call if needed, but the tool call MUST be present if there is work to do.")
|
|
728
|
+
directives.append("7. **Tool Calling**: Use native function-calling. Avoid conversational preamble. Do not output raw JSON, `<think>` tags, or raw tool markup in text responses.")
|
|
729
729
|
directives.append(f"8. **Premium Web Design**: Style web UIs per `{_posix}/DESIGN.md` (premium typography, HSL colors, glassmorphism, animations).")
|
|
730
730
|
directives.append("9. **Hint Guidance**: Follow any `[Secret Hint Guidance: <instruction>]` prefix in the user prompt as a high-priority system directive.")
|
|
731
731
|
directives.append("10. **Speed & Safety Balance**: Trust your edits. Do NOT waste tokens/time calling `read_file` or `view_file` immediately after writing or editing just to verify. Instead, verify safety instantly by running a compilation, build, or test command via `run_command`. Only re-read files if compilation/tests fail or return errors.")
|
|
@@ -750,18 +750,18 @@ def _get_dynamic_directives(is_compressed: bool, messages: Optional[List[Dict]]
|
|
|
750
750
|
directives.append("2. **Planning/Autonomous Tooling**: No tools are currently enabled. Do NOT attempt to use any tools.")
|
|
751
751
|
|
|
752
752
|
if has_plan_project and has_run_command:
|
|
753
|
-
directives.append("3. **Think-Create-Verify**:
|
|
753
|
+
directives.append("3. **Think-Create-Verify**: Start executing right away. You do NOT need to write a detailed plan or preamble in text before calling tools. Output tool calls immediately, then explain results or check tests. Verify with execution (`run_command` -> build/test/run) rather than just reading files.")
|
|
754
754
|
elif has_plan_project:
|
|
755
|
-
directives.append("3. **Think-Create-Verify**:
|
|
755
|
+
directives.append("3. **Think-Create-Verify**: Start executing right away. You do NOT need to write a detailed plan or preamble in text before calling tools. Output tool calls immediately, then explain results or check tests.")
|
|
756
756
|
elif has_run_command:
|
|
757
|
-
directives.append("3. **Think-Create-Verify**:
|
|
757
|
+
directives.append("3. **Think-Create-Verify**: Start executing right away. You do NOT need to write a detailed plan or preamble in text before calling tools. Output tool calls immediately, then explain results or check tests. Verify with execution (`run_command` -> build/test/run) rather than just reading files.")
|
|
758
758
|
else:
|
|
759
|
-
directives.append("3. **Think-Create-Verify**:
|
|
759
|
+
directives.append("3. **Think-Create-Verify**: Start executing right away. You do NOT need to write a detailed plan or preamble in text before calling tools. Output tool calls immediately, then explain results or check tests.")
|
|
760
760
|
|
|
761
761
|
directives.append(f"4. **Manifesto Reference**: Detailed engineering rules, coding standards, and safety/sandbox instructions are in {_posix}/UTIM.md. Read/reference it only when specific guidance is needed.")
|
|
762
762
|
directives.append("5. **Output**: Be concise and professional. Summarize all changes and test results when complete. Speak colloquially and warmly in the user's language. If you notice other code smells, potential bugs, or logical issues in the code snippets you read during execution, list them as proposed improvements in your final summary for the user instead of fixing them immediately.")
|
|
763
|
-
directives.append("6. **Execution**:
|
|
764
|
-
directives.append("7. **Tool Calling format**: You MUST use the native JSON-schema function-calling mechanism provided by the API to invoke tools. NEVER output raw JSON, <think to=...> tags, <|message|> tags, or other raw tool-call markup in your text response. Any tool usage must be strictly structured via the API.")
|
|
763
|
+
directives.append("6. **Execution**: Avoid announcing what you are going to do or writing text-only planning steps. Just invoke the appropriate tool call directly. You may explain your reasoning in the same message alongside the tool call if needed, but the tool call MUST be present if there is work to do.")
|
|
764
|
+
directives.append("7. **Tool Calling format**: You MUST use the native JSON-schema function-calling mechanism provided by the API to invoke tools. Avoid conversational preamble. NEVER output raw JSON, <think to=...> tags, <|message|> tags, or other raw tool-call markup in your text response. Any tool usage must be strictly structured via the API.")
|
|
765
765
|
directives.append(f"8. **Premium Web Design**: When creating, updating, or styling web user interfaces, read/reference `{_posix}/DESIGN.md` to apply modern visual design conventions (such as premium typography, HSL color themes, glassmorphism, and smooth animations) instead of default browser styles or raw colors.")
|
|
766
766
|
directives.append("9. **Secret Hint Guidance**: If a user message begins with `[Secret Hint Guidance: <instruction>]`, you MUST treat that instruction as a high-priority system-level override directive. Customize your reasoning, tool selection, explanation detail, and output formatting to strictly adhere to that instruction throughout the entire task execution.")
|
|
767
767
|
directives.append("10. **Speed & Safety Balance**: Trust your edits. Do NOT waste tokens/time calling `read_file` or `view_file` immediately after writing or editing just to verify. Instead, verify safety instantly by running a compilation, build, or test command via `run_command`. Only re-read files if compilation/tests fail or return errors.")
|
|
@@ -2040,6 +2040,15 @@ class Orchestrator:
|
|
|
2040
2040
|
finally:
|
|
2041
2041
|
self.active_response = None
|
|
2042
2042
|
|
|
2043
|
+
if self.cancel_event.is_set():
|
|
2044
|
+
return {
|
|
2045
|
+
"role": "assistant",
|
|
2046
|
+
"content": "[Aborted by user]",
|
|
2047
|
+
"tool_calls": None,
|
|
2048
|
+
"was_cut_off": True,
|
|
2049
|
+
"aborted": True,
|
|
2050
|
+
}, False
|
|
2051
|
+
|
|
2043
2052
|
# Flush any remaining text in _line_buf and table_buf
|
|
2044
2053
|
flush_table()
|
|
2045
2054
|
if _line_buf and not silent:
|
|
@@ -3084,10 +3093,21 @@ class Orchestrator:
|
|
|
3084
3093
|
("edit_file", "path"): "filepath",
|
|
3085
3094
|
("edit_file", "file"): "filepath",
|
|
3086
3095
|
("edit_file", "filename"): "filepath",
|
|
3087
|
-
# run_command: model sometimes uses 'cmd'
|
|
3088
|
-
("run_command", "cmd"):
|
|
3089
|
-
("run_command", "shell"):
|
|
3090
|
-
("run_command", "script"):
|
|
3096
|
+
# run_command: model sometimes uses 'cmd' or 'CommandLine'
|
|
3097
|
+
("run_command", "cmd"): "command",
|
|
3098
|
+
("run_command", "shell"): "command",
|
|
3099
|
+
("run_command", "script"): "command",
|
|
3100
|
+
("run_command", "CommandLine"): "command",
|
|
3101
|
+
("run_command", "commandLine"): "command",
|
|
3102
|
+
("run_command", "command_line"): "command",
|
|
3103
|
+
("run_command", "cmd_line"): "command",
|
|
3104
|
+
("run_command", "cwd"): "dir_path",
|
|
3105
|
+
("run_command", "directory"): "dir_path",
|
|
3106
|
+
("run_command", "folder"): "dir_path",
|
|
3107
|
+
("run_command", "path"): "dir_path",
|
|
3108
|
+
("run_command", "background"): "is_background",
|
|
3109
|
+
("run_command", "bg"): "is_background",
|
|
3110
|
+
("run_command", "run_in_background"):"is_background",
|
|
3091
3111
|
# web_search: model sometimes uses 'prompt'
|
|
3092
3112
|
("web_search", "prompt"): "query",
|
|
3093
3113
|
("web_search", "text"): "query",
|
|
@@ -4166,10 +4186,12 @@ class Orchestrator:
|
|
|
4166
4186
|
|
|
4167
4187
|
|
|
4168
4188
|
|
|
4169
|
-
def run_task(self, user_message: str, max_iterations: int =
|
|
4189
|
+
def run_task(self, user_message: str, max_iterations: int = 35) -> None:
|
|
4170
4190
|
"""Append user_message to history and run the full ReAct loop until the
|
|
4171
4191
|
model stops issuing tool calls or we hit max_iterations.
|
|
4172
4192
|
"""
|
|
4193
|
+
from utim_cli.config import config
|
|
4194
|
+
max_iterations = config.get("max_iterations", max_iterations)
|
|
4173
4195
|
self.turn_step_timings = []
|
|
4174
4196
|
self._tool_failure_counts = {}
|
|
4175
4197
|
|
|
@@ -4611,27 +4633,43 @@ class Orchestrator:
|
|
|
4611
4633
|
)
|
|
4612
4634
|
break
|
|
4613
4635
|
|
|
4636
|
+
# To prevent history pollution and break repeating patterns, check if the last
|
|
4637
|
+
# message in history was a previous nudge. If so, remove the previous nudge-and-response pair
|
|
4638
|
+
# so they don't multiply in the LLM's context.
|
|
4639
|
+
if len(self.messages) >= 2:
|
|
4640
|
+
last_user = self.messages[-1]
|
|
4641
|
+
if last_user.get("role") == "user" and any(marker in (last_user.get("content") or "") for marker in [
|
|
4642
|
+
"Stop announcing your intentions",
|
|
4643
|
+
"Stop describing plans",
|
|
4644
|
+
"You described what you were going to do",
|
|
4645
|
+
"You just executed tool(s)",
|
|
4646
|
+
"You stopped without writing anything"
|
|
4647
|
+
]):
|
|
4648
|
+
self.messages.pop() # Pop the previous user nudge
|
|
4649
|
+
if self.messages and self.messages[-1].get("role") == "assistant":
|
|
4650
|
+
self.messages.pop() # Pop the previous lazy assistant response
|
|
4651
|
+
|
|
4614
4652
|
# Build a context-aware continuation nudge
|
|
4615
4653
|
if _had_tools_this_turn:
|
|
4616
4654
|
tool_list = ", ".join(dict.fromkeys(reversed(_recent_tool_names))) # dedupe, preserve order
|
|
4617
4655
|
nudge = (
|
|
4618
4656
|
f"You just executed tool(s) [{tool_list}]. You then wrote a short response without "
|
|
4619
|
-
f"calling any further tools.
|
|
4620
|
-
f"
|
|
4657
|
+
f"calling any further tools. Stop describing plans or announcing intentions. "
|
|
4658
|
+
f"If the task is incomplete, you MUST output the required JSON tool calls to DO IT. "
|
|
4621
4659
|
f"If the task is truly complete, provide a comprehensive final summary."
|
|
4622
4660
|
)
|
|
4623
4661
|
elif _is_lazy_transition:
|
|
4624
4662
|
# First-step lazy: model announced what it would do but didn't call any tools
|
|
4625
4663
|
nudge = (
|
|
4626
|
-
"
|
|
4627
|
-
"
|
|
4628
|
-
"Do NOT write
|
|
4629
|
-
"
|
|
4664
|
+
"Stop describing plans, listing todos, or announcing your intentions in text. "
|
|
4665
|
+
"You MUST immediately execute the appropriate tool call(s) right now. "
|
|
4666
|
+
"Do NOT write conversational filler, explanations, or 'I will now...' statements. "
|
|
4667
|
+
"Output the tool call JSON directly."
|
|
4630
4668
|
)
|
|
4631
4669
|
else:
|
|
4632
4670
|
nudge = (
|
|
4633
4671
|
"You stopped without writing anything or taking action. The user's request was: "
|
|
4634
|
-
f"\"{user_message[:200]}\".
|
|
4672
|
+
f"\"{user_message[:200]}\". Stop describing plans — output the tool call JSON directly."
|
|
4635
4673
|
)
|
|
4636
4674
|
|
|
4637
4675
|
self.console.print(f"\n[dim yellow]⚠ Model provided lazy response without tools. Auto-nudging (attempt {_empty_response_streak}/3)...[/dim yellow]")
|
|
@@ -4894,9 +4932,33 @@ class Orchestrator:
|
|
|
4894
4932
|
"content": warning_text
|
|
4895
4933
|
})
|
|
4896
4934
|
self.console.print(f"\n[bold red]⚠️ Tool Loop Intercepted: Nudging agent to prevent repeated `{func_name}` failures.[/bold red]")
|
|
4935
|
+
|
|
4936
|
+
# Also track general failures by tool name to catch cases where the model
|
|
4937
|
+
# changes arguments slightly but keeps failing the same tool type.
|
|
4938
|
+
if not hasattr(self, "_tool_name_failure_counts"):
|
|
4939
|
+
self._tool_name_failure_counts = {}
|
|
4940
|
+
|
|
4941
|
+
name_count = self._tool_name_failure_counts.get(func_name, 0) + 1
|
|
4942
|
+
self._tool_name_failure_counts[func_name] = name_count
|
|
4943
|
+
|
|
4944
|
+
if name_count >= 4:
|
|
4945
|
+
self._tool_name_failure_counts[func_name] = 0
|
|
4946
|
+
warning_text = (
|
|
4947
|
+
f"⚠️ [SYSTEM NOTICE] The tool `{func_name}` has failed 4 times with different arguments in this turn. "
|
|
4948
|
+
f"Your general approach with `{func_name}` is failing. Please check if your file paths are absolute, "
|
|
4949
|
+
f"if directories exist, or if command flags are correct. You MUST alter your strategy or verify "
|
|
4950
|
+
f"path structure before calling `{func_name}` again."
|
|
4951
|
+
)
|
|
4952
|
+
self.messages.append({
|
|
4953
|
+
"role": "user",
|
|
4954
|
+
"content": warning_text
|
|
4955
|
+
})
|
|
4956
|
+
self.console.print(f"\n[bold red]⚠️ Tool Type Failure Loop Intercepted: Nudging agent for `{func_name}` general failures.[/bold red]")
|
|
4897
4957
|
else:
|
|
4898
|
-
# Successful tool run clears the failure
|
|
4958
|
+
# Successful tool run clears the failure counts
|
|
4899
4959
|
self._tool_failure_counts.pop(fingerprint, None)
|
|
4960
|
+
if hasattr(self, "_tool_name_failure_counts"):
|
|
4961
|
+
self._tool_name_failure_counts.pop(func_name, None)
|
|
4900
4962
|
|
|
4901
4963
|
# Attempt to compress context if requested or if token limit is breached.
|
|
4902
4964
|
# turn_msg_start is passed so that after compression rewrites
|
|
@@ -163,6 +163,11 @@ def process_user_refills(db: Session, user_id: str) -> None:
|
|
|
163
163
|
unused_credits = max(0.0, cycle_allowance - current_cycle_used)
|
|
164
164
|
if due > 1:
|
|
165
165
|
unused_credits += cycle_allowance * (due - 1)
|
|
166
|
+
|
|
167
|
+
# Cap refills to remaining monthly plan credits after direct deductions
|
|
168
|
+
unallocated_deducted = getattr(sub, "unallocated_deducted", 0.0) or 0.0
|
|
169
|
+
remaining_pool = max(0.0, plan.credits_per_month - (sub.refills_processed * cycle_allowance) - unallocated_deducted)
|
|
170
|
+
unused_credits = min(unused_credits, remaining_pool)
|
|
166
171
|
|
|
167
172
|
credit = db.query(Credit).filter(Credit.user_id == user_id).first()
|
|
168
173
|
if not credit:
|
|
@@ -88,7 +88,7 @@ class Plan(Base):
|
|
|
88
88
|
__tablename__ = "plans"
|
|
89
89
|
|
|
90
90
|
id = Column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
|
|
91
|
-
name = Column(String(50), unique=True, nullable=False) # "free", "hobby", "pro", "
|
|
91
|
+
name = Column(String(50), unique=True, nullable=False) # "free", "hobby", "pro", "max", "ultimate"
|
|
92
92
|
display_name = Column(String(100), nullable=False)
|
|
93
93
|
price_inr = Column(Integer, default=0, nullable=False)
|
|
94
94
|
credits_per_month = Column(Integer, nullable=False)
|
|
@@ -118,6 +118,7 @@ class UserSubscription(Base):
|
|
|
118
118
|
last_refill_at = Column(DateTime, nullable=True)
|
|
119
119
|
# Overrides the default per-cycle credit allocation after a quota-share deduction
|
|
120
120
|
refill_rate_override = Column(Float, nullable=True)
|
|
121
|
+
unallocated_deducted = Column(Float, default=0.0, nullable=False)
|
|
121
122
|
created_at = Column(DateTime, default=datetime.datetime.utcnow, nullable=False)
|
|
122
123
|
updated_at = Column(DateTime, default=datetime.datetime.utcnow, onupdate=datetime.datetime.utcnow, nullable=False)
|
|
123
124
|
|
|
@@ -630,12 +631,14 @@ def init_db(silent: bool = True) -> None:
|
|
|
630
631
|
_run_migration("users", "referral_code", "ALTER TABLE users ADD COLUMN IF NOT EXISTS referral_code VARCHAR(50)", "postgres add referral_code")
|
|
631
632
|
_run_migration("feedbacks", "chat_history", "ALTER TABLE feedbacks ADD COLUMN IF NOT EXISTS chat_history TEXT", "postgres add chat_history")
|
|
632
633
|
_run_migration("user_subscriptions", "refill_rate_override", "ALTER TABLE user_subscriptions ADD COLUMN IF NOT EXISTS refill_rate_override FLOAT", "postgres add refill_rate_override")
|
|
634
|
+
_run_migration("user_subscriptions", "unallocated_deducted", "ALTER TABLE user_subscriptions ADD COLUMN IF NOT EXISTS unallocated_deducted FLOAT DEFAULT 0.0 NOT NULL", "postgres add unallocated_deducted")
|
|
633
635
|
else:
|
|
634
636
|
# SQLite: no IF NOT EXISTS on ADD COLUMN, swallow duplicate errors
|
|
635
637
|
_run_migration("user_subscriptions", "refills_processed", "ALTER TABLE user_subscriptions ADD COLUMN refills_processed INTEGER DEFAULT 0 NOT NULL", "sqlite add refills_processed")
|
|
636
638
|
_run_migration("user_subscriptions", "last_refill_at", "ALTER TABLE user_subscriptions ADD COLUMN last_refill_at TIMESTAMP", "sqlite add last_refill_at")
|
|
637
639
|
_run_migration("user_subscriptions", "current_cycle_used", "ALTER TABLE user_subscriptions ADD COLUMN current_cycle_used FLOAT DEFAULT 0.0 NOT NULL", "sqlite add current_cycle_used")
|
|
638
640
|
_run_migration("user_subscriptions", "refill_rate_override", "ALTER TABLE user_subscriptions ADD COLUMN refill_rate_override FLOAT", "sqlite add refill_rate_override")
|
|
641
|
+
_run_migration("user_subscriptions", "unallocated_deducted", "ALTER TABLE user_subscriptions ADD COLUMN unallocated_deducted FLOAT DEFAULT 0.0 NOT NULL", "sqlite add unallocated_deducted")
|
|
639
642
|
_run_migration("credits", "bonus_balance", "ALTER TABLE credits ADD COLUMN bonus_balance FLOAT DEFAULT 0.0 NOT NULL", "sqlite add bonus_balance")
|
|
640
643
|
_run_migration("credits", "bonus_limit", "ALTER TABLE credits ADD COLUMN bonus_limit FLOAT DEFAULT 0.0 NOT NULL", "sqlite add bonus_limit")
|
|
641
644
|
_run_migration("credits", "free_monthly_used", "ALTER TABLE credits ADD COLUMN free_monthly_used FLOAT DEFAULT 0.0 NOT NULL", "sqlite add free_monthly_used")
|
|
@@ -693,24 +696,6 @@ def init_db(silent: bool = True) -> None:
|
|
|
693
696
|
allowed_models="all",
|
|
694
697
|
max_context_k=1024
|
|
695
698
|
),
|
|
696
|
-
Plan(
|
|
697
|
-
id="team",
|
|
698
|
-
name="team",
|
|
699
|
-
display_name="Team",
|
|
700
|
-
price_inr=3500,
|
|
701
|
-
credits_per_month=45000,
|
|
702
|
-
allowed_models="all",
|
|
703
|
-
max_context_k=1024
|
|
704
|
-
),
|
|
705
|
-
Plan(
|
|
706
|
-
id="enterprise",
|
|
707
|
-
name="enterprise",
|
|
708
|
-
display_name="Enterprise",
|
|
709
|
-
price_inr=5000,
|
|
710
|
-
credits_per_month=80000,
|
|
711
|
-
allowed_models="all",
|
|
712
|
-
max_context_k=1024
|
|
713
|
-
),
|
|
714
699
|
Plan(
|
|
715
700
|
id="ultimate",
|
|
716
701
|
name="ultimate",
|
|
@@ -724,6 +709,9 @@ def init_db(silent: bool = True) -> None:
|
|
|
724
709
|
db.add_all(plans)
|
|
725
710
|
db.commit()
|
|
726
711
|
else:
|
|
712
|
+
# Delete legacy plans if present to prevent confusion
|
|
713
|
+
db.query(Plan).filter(Plan.id.in_(["team", "enterprise"])).delete(synchronize_session=False)
|
|
714
|
+
|
|
727
715
|
# Update existing plan details
|
|
728
716
|
pro_plan = db.query(Plan).filter(Plan.id == "pro").first()
|
|
729
717
|
if pro_plan:
|
|
@@ -817,8 +805,8 @@ def get_max_bonus_limit(plan_id: str) -> float:
|
|
|
817
805
|
return 50000.0
|
|
818
806
|
elif pid == "pro":
|
|
819
807
|
return 1500000.0
|
|
820
|
-
elif pid
|
|
808
|
+
elif pid == "max":
|
|
821
809
|
return 3000000.0
|
|
822
|
-
elif pid
|
|
810
|
+
elif pid == "ultimate":
|
|
823
811
|
return 4500000.0
|
|
824
812
|
return 20000.0
|