utim-cli 2.3.4__tar.gz → 2.3.5__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.4 → utim_cli-2.3.5}/CHANGELOG.md +8 -3
- {utim_cli-2.3.4/utim_cli.egg-info → utim_cli-2.3.5}/PKG-INFO +1 -1
- {utim_cli-2.3.4 → utim_cli-2.3.5}/pyproject.toml +1 -1
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/__init__.py +1 -1
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/_version.py +1 -1
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/agent.py +89 -32
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/ask_helper.py +5 -1
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/bootstrap.py +39 -15
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/brain.py +992 -909
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/client_utils.py +208 -11
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/config.py +5 -4
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/constants.py +1 -1
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/context_pruner.py +674 -49
- utim_cli-2.3.5/utim_cli/crawler/__init__.py +13 -0
- utim_cli-2.3.5/utim_cli/crawler/engine.py +344 -0
- utim_cli-2.3.5/utim_cli/crawler/markdown_distiller.py +251 -0
- utim_cli-2.3.5/utim_cli/evidence_registry.py +498 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/orchestrator.py +139 -90
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/orchestrator_helpers.py +437 -198
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/orchestrator_llm_transport.py +121 -68
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/orchestrator_tool_exec.py +67 -2
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/orchestrator_turn_history.py +100 -73
- utim_cli-2.3.5/utim_cli/plugins/hooks.py +168 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/plugins/manager.py +537 -465
- utim_cli-2.3.5/utim_cli/plugins/tool_loader.py +129 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/reflection.py +570 -81
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/server/db.py +3 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/server/micro_batcher.py +8 -3
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/server/models.py +37 -23
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/server/pricing_updater.py +31 -6
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/server/router.py +131 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/server/routes/completion_routes.py +211 -94
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/server/routes/rewards_routes.py +8 -2
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/subagent_manager.py +285 -17
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/task_dispatcher.py +652 -499
- utim_cli-2.3.5/utim_cli/task_logger.py +204 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/tools.py +517 -267
- utim_cli-2.3.5/utim_cli/tools_grep.py +442 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/tui/model_dialog.py +138 -54
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/utim.py +629 -128
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/vector_memory.py +35 -7
- {utim_cli-2.3.4 → utim_cli-2.3.5/utim_cli.egg-info}/PKG-INFO +1 -1
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli.egg-info/SOURCES.txt +8 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/LICENSE +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/MANIFEST.in +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/README.md +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/setup.cfg +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/setup.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim/__init__.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/auth.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/backup.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/billing.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/blender_agent.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/doctor.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/harbor.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/knowledge_graph.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/local_db.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/logger.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/mcp_clean_wrapper.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/mcp_client.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/mcp_registry.json +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/models.txt +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/plugins/__init__.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/plugins/registry.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/plugins/schema.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/report.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/scrapy_search.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/sdk/__init__.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/sdk/config.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/sdk/context.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/sdk/events.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/sdk/handlers.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/sdk/session.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/sdk/sidecar.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/server/__init__.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/server/admin_auth.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/server/attribution.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/server/audit_log.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/server/auth.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/server/batch_processor.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/server/captcha.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/server/cli_auth.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/server/docs_md/SECRET_PROVISIONING.md +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/server/docs_md/about.md +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/server/docs_md/changelog.md +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/server/docs_md/docs.md +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/server/docs_md/features.md +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/server/docs_md/license.md +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/server/docs_md/pricing.md +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/server/docs_md/privacy.md +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/server/docs_md/refund.md +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/server/docs_md/support.md +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/server/docs_md/terms.md +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/server/email_utils.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/server/exchange_rate.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/server/firebase.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/server/fix_duplicate_users.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/server/history.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/server/local_llm.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/server/logging_config.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/server/model_agent.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/server/provision_build.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/server/rate_limit.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/server/rewards_engine.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/server/routes/__init__.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/server/routes/admin_db_routes.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/server/routes/auth_routes.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/server/routes/billing_routes.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/server/routes/credit_routes.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/server/routes/feedback_routes.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/server/routes/marketplace_routes.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/server/routes/quota_routes.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/server/routes/quota_share_routes.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/server/routes/referral_routes.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/server/routes/security_routes.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/server/routes/session_routes.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/server/routes/share_routes.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/server/server.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/server/storage_nodes.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/share.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/share_tui.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/situational_scoring.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/state.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/tui/__init__.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/tui/feedback_dialog.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/tui/history_dialog.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/tui/marketplace_app_state.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/tui/marketplace_dialog.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/tui/marketplace_layout_engine.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/tui/mcp_dialog.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/tui/miniagents_dialog.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/tui/plugins_dialog.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/tui/publish_dialog.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/tui/quota_dialog.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/tui/quota_redeem_dialog.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/tui/quota_share_dialog.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/tui/resume_dialog.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/tui/rewards_tui.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/tui/skills_dialog.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/tui/subagents_dialog.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/tui/tasks_dialog.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/tui/thinking_display.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/tui/tools_dialog.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/tui/update_dialog.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/utilities.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/utimmodel.txt +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/wheel.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli/workspace.py +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli.egg-info/dependency_links.txt +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli.egg-info/entry_points.txt +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli.egg-info/requires.txt +0 -0
- {utim_cli-2.3.4 → utim_cli-2.3.5}/utim_cli.egg-info/top_level.txt +0 -0
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.3.5] - 2026-08-25
|
|
4
|
+
|
|
5
|
+
### ⚡ Performance & Fixes
|
|
6
|
+
- **Latency Optimisation**: Micro-batching threshold tuning, zero-delay streaming, and asynchronous background reflection pipeline dispatch.
|
|
7
|
+
- **Model Hallucination & Token Bloat Fix**: Multi-level periodic pattern and stream repetition interceptor that truncates duplicate reasoning and loop hallucinations in real-time, preventing context bloat.
|
|
8
|
+
- Minor bug fixes
|
|
9
|
+
|
|
3
10
|
## [2.3.4] - 2026-08-22
|
|
4
11
|
|
|
5
|
-
### ⚡ Performance
|
|
12
|
+
### ⚡ Performance
|
|
6
13
|
- Latency optimizations: Zero-buffer stream reading, asynchronous background experience caching, parallel multi-tool file inspection execution, and micro-batching bypass for interactive sessions.
|
|
7
|
-
- Dynamic fallback model resolution from server model registry with `openrouter/free` priority.
|
|
8
|
-
- Configured reasoning capability toggling across server admin interface and CLI model selector.
|
|
9
14
|
|
|
10
15
|
## [2.3.3] - 2026-08-20
|
|
11
16
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: utim-cli
|
|
3
|
-
Version: 2.3.
|
|
3
|
+
Version: 2.3.5
|
|
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
|
|
@@ -286,45 +286,73 @@ class ReActAgent:
|
|
|
286
286
|
if all_tools:
|
|
287
287
|
kwargs["tools"] = all_tools
|
|
288
288
|
|
|
289
|
-
#
|
|
290
|
-
|
|
289
|
+
# Provider-safety pass: drop null/empty `tool_calls` keys from
|
|
290
|
+
# history (strict providers 400 on `messages.N.tool_calls: null`).
|
|
291
|
+
kwargs["messages"] = sanitize_messages_for_api(kwargs.get("messages") or [])
|
|
292
|
+
|
|
293
|
+
# Call the LLM with streaming (retry once on transport errors so a
|
|
294
|
+
# mid-stream failure doesn't silently kill the whole run)
|
|
295
|
+
try:
|
|
296
|
+
stream = self.client.chat.completions.create(**kwargs)
|
|
297
|
+
except Exception as e:
|
|
298
|
+
if getattr(self, "_stream_retries", 0) >= 2:
|
|
299
|
+
self.console.print(f"[red]Stream failed repeatedly: {e}[/red]")
|
|
300
|
+
break
|
|
301
|
+
self._stream_retries = getattr(self, "_stream_retries", 0) + 1
|
|
302
|
+
self.console.print(f"[yellow]Stream error ({e}) — retrying...[/yellow]")
|
|
303
|
+
time.sleep(1.5)
|
|
304
|
+
continue
|
|
291
305
|
|
|
292
306
|
# Collect streaming chunks
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
307
|
+
finish_reason = ""
|
|
308
|
+
empty_stream = True
|
|
309
|
+
try:
|
|
310
|
+
for chunk in stream:
|
|
311
|
+
if not chunk.choices:
|
|
312
|
+
continue
|
|
313
|
+
empty_stream = False
|
|
314
|
+
delta = chunk.choices[0].delta
|
|
315
|
+
|
|
316
|
+
if delta.content:
|
|
317
|
+
assistant_content += delta.content
|
|
318
|
+
|
|
319
|
+
if delta.tool_calls:
|
|
320
|
+
for tc in delta.tool_calls:
|
|
321
|
+
tc_index = tc.index if tc.index is not None else 0
|
|
322
|
+
if current_tool_call is None or tc_index != current_tool_call.get("index"):
|
|
323
|
+
# Flush the previous buffered tool call (check index, not id)
|
|
324
|
+
if current_tool_call is not None:
|
|
325
|
+
tool_calls.append(current_tool_call)
|
|
326
|
+
current_tool_call = {
|
|
327
|
+
"index": tc_index,
|
|
328
|
+
"id": tc.id or "",
|
|
329
|
+
"type": tc.type or "function",
|
|
330
|
+
"function": {
|
|
331
|
+
"name": tc.function.name if tc.function else "",
|
|
332
|
+
"arguments": tc.function.arguments if tc.function else "",
|
|
333
|
+
},
|
|
334
|
+
}
|
|
335
|
+
else:
|
|
336
|
+
# Same tool call — accumulate the argument chunks
|
|
337
|
+
if tc.id and not current_tool_call["id"]:
|
|
338
|
+
current_tool_call["id"] = tc.id
|
|
339
|
+
if tc.function and tc.function.arguments:
|
|
340
|
+
current_tool_call["function"]["arguments"] += tc.function.arguments
|
|
341
|
+
|
|
342
|
+
fr = chunk.choices[0].finish_reason
|
|
343
|
+
if fr:
|
|
344
|
+
finish_reason = fr
|
|
345
|
+
except Exception as e:
|
|
346
|
+
self.console.print(f"[yellow]Stream interrupted mid-response ({e}).[/yellow]")
|
|
347
|
+
finish_reason = finish_reason or "aborted"
|
|
348
|
+
self._stream_retries = 0
|
|
321
349
|
|
|
322
350
|
# Flush the last buffered tool call
|
|
323
351
|
if current_tool_call is not None:
|
|
324
352
|
tool_calls.append(current_tool_call)
|
|
325
353
|
|
|
326
354
|
# Parse XML-style tool calls if present in the text content
|
|
327
|
-
from .client_utils import parse_xml_tool_calls
|
|
355
|
+
from .client_utils import parse_xml_tool_calls, sanitize_messages_for_api
|
|
328
356
|
parsed_content, parsed_tool_calls = parse_xml_tool_calls(assistant_content)
|
|
329
357
|
if parsed_tool_calls:
|
|
330
358
|
assistant_content = parsed_content or ""
|
|
@@ -338,8 +366,37 @@ class ReActAgent:
|
|
|
338
366
|
if not tool_calls:
|
|
339
367
|
self.console.print()
|
|
340
368
|
|
|
341
|
-
# If no tool calls, we are done
|
|
369
|
+
# If no tool calls, normally we are done — but check WHY the stream
|
|
370
|
+
# ended, because some models stop abruptly on token budget
|
|
371
|
+
# (finish_reason="length") or return a transiently empty response.
|
|
342
372
|
if not tool_calls:
|
|
373
|
+
if finish_reason == "stop":
|
|
374
|
+
# Clean, intentional completion from the model — this is
|
|
375
|
+
# the normal exit path.
|
|
376
|
+
self._empty_retries = 0
|
|
377
|
+
break
|
|
378
|
+
|
|
379
|
+
if finish_reason in ("length", "max_tokens"):
|
|
380
|
+
# Model hit its output cap mid-task. Keep it going instead
|
|
381
|
+
# of silently ending the turn.
|
|
382
|
+
self.console.print("[dim](output limit hit — continuing automatically)[/dim]")
|
|
383
|
+
self.messages.append({
|
|
384
|
+
"role": "assistant",
|
|
385
|
+
"content": assistant_content or "",
|
|
386
|
+
})
|
|
387
|
+
self.messages.append({
|
|
388
|
+
"role": "user",
|
|
389
|
+
"content": "[system] Your previous response was cut off by the output token limit. Continue exactly where you left off without repeating what you already said.",
|
|
390
|
+
})
|
|
391
|
+
continue
|
|
392
|
+
|
|
393
|
+
if (not assistant_content.strip()) and empty_stream:
|
|
394
|
+
# Provider glitch: completely empty completion. Retry once.
|
|
395
|
+
if getattr(self, "_empty_retries", 0) < 1:
|
|
396
|
+
self._empty_retries = 1
|
|
397
|
+
self.console.print("[dim](empty response from model — retrying)[/dim]")
|
|
398
|
+
continue
|
|
399
|
+
self._empty_retries = 0
|
|
343
400
|
break
|
|
344
401
|
|
|
345
402
|
# Save the assistant message with tool_calls
|
|
@@ -31,7 +31,11 @@ PRIMARY_HELPER_MODEL = "openrouter/free"
|
|
|
31
31
|
|
|
32
32
|
|
|
33
33
|
|
|
34
|
-
HELPER_SYSTEM_PROMPT = """
|
|
34
|
+
HELPER_SYSTEM_PROMPT = """<identity>
|
|
35
|
+
You are the UTIM Sidecar Assistant, an embedded technical companion running alongside the active UTIM CLI session.
|
|
36
|
+
- Your identity is strictly the UTIM Sidecar Assistant. Never identify as ox-alpha, ChatGPT, Claude, Qwen, DeepSeek, or other base models.
|
|
37
|
+
</identity>
|
|
38
|
+
|
|
35
39
|
Your mission is to provide real-time architectural insights, explain active agent operations, assess codebase states, and answer developer questions clearly and factually.
|
|
36
40
|
|
|
37
41
|
### OPERATIONAL DIRECTIVES:
|
|
@@ -131,16 +131,25 @@ def _write_utim_md():
|
|
|
131
131
|
from utim_cli.logger import log_error
|
|
132
132
|
log_error("bootstrap", f"Failed to create UTIM.md at {md_path}", e)
|
|
133
133
|
|
|
134
|
-
|
|
134
|
+
_SKILLS_CACHE = {}
|
|
135
|
+
_SKILLS_CACHE_TS = 0.0
|
|
136
|
+
_SKILLS_CACHE_TTL = 300.0 # seconds
|
|
137
|
+
|
|
138
|
+
def scan_available_skills(force_reload: bool = False):
|
|
135
139
|
"""
|
|
136
140
|
Scans ~/.utim/skills, .utim/skills, and .agents/skills for directories containing SKILL.md.
|
|
137
141
|
Skills listed in config['disabled_skills'] are excluded.
|
|
138
142
|
Returns:
|
|
139
143
|
dict: A dictionary mapping skill_name to dict containing path, name, description, and keywords.
|
|
140
144
|
"""
|
|
141
|
-
|
|
145
|
+
global _SKILLS_CACHE, _SKILLS_CACHE_TS
|
|
146
|
+
import time
|
|
147
|
+
now = time.time()
|
|
148
|
+
if not force_reload and _SKILLS_CACHE and (now - _SKILLS_CACHE_TS) < _SKILLS_CACHE_TTL:
|
|
149
|
+
return _SKILLS_CACHE
|
|
150
|
+
|
|
151
|
+
import os, re
|
|
142
152
|
from pathlib import Path
|
|
143
|
-
import yaml
|
|
144
153
|
from utim_cli.config import config as _cfg
|
|
145
154
|
|
|
146
155
|
disabled: list = _cfg.get("disabled_skills") or []
|
|
@@ -163,16 +172,17 @@ def scan_available_skills():
|
|
|
163
172
|
for base_dir in paths_to_check:
|
|
164
173
|
if not base_dir.exists():
|
|
165
174
|
continue
|
|
166
|
-
|
|
175
|
+
try:
|
|
176
|
+
entries = os.listdir(base_dir)
|
|
177
|
+
except Exception:
|
|
178
|
+
continue
|
|
179
|
+
for name in entries:
|
|
167
180
|
skill_dir = base_dir / name
|
|
168
181
|
if skill_dir.is_dir():
|
|
169
182
|
skill_md = skill_dir / 'SKILL.md'
|
|
170
183
|
if skill_md.exists():
|
|
171
184
|
# Avoid duplicates
|
|
172
|
-
if name in skills:
|
|
173
|
-
continue
|
|
174
|
-
# Skip disabled skills
|
|
175
|
-
if name in disabled:
|
|
185
|
+
if name in skills or name in disabled:
|
|
176
186
|
continue
|
|
177
187
|
|
|
178
188
|
description = ""
|
|
@@ -180,14 +190,17 @@ def scan_available_skills():
|
|
|
180
190
|
content = ""
|
|
181
191
|
try:
|
|
182
192
|
with open(skill_md, 'r', encoding='utf-8') as f:
|
|
183
|
-
content = f.read()
|
|
193
|
+
content = f.read(2048) # Fast bounded read for frontmatter
|
|
184
194
|
if content.startswith('---'):
|
|
185
195
|
parts = content.split('---', 2)
|
|
186
196
|
if len(parts) >= 3:
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
197
|
+
fm = parts[1]
|
|
198
|
+
desc_m = re.search(r'^description:\s*(.+)$', fm, re.MULTILINE)
|
|
199
|
+
if desc_m:
|
|
200
|
+
description = desc_m.group(1).strip().strip('"\'')
|
|
201
|
+
name_m = re.search(r'^name:\s*(.+)$', fm, re.MULTILINE)
|
|
202
|
+
if name_m:
|
|
203
|
+
frontmatter_name = name_m.group(1).strip().strip('"\'')
|
|
191
204
|
except Exception:
|
|
192
205
|
pass
|
|
193
206
|
|
|
@@ -212,13 +225,24 @@ def scan_available_skills():
|
|
|
212
225
|
if len(w) > 2 and w not in stop_words:
|
|
213
226
|
words.add(w)
|
|
214
227
|
|
|
228
|
+
size_bytes = 0
|
|
229
|
+
try:
|
|
230
|
+
size_bytes = skill_md.stat().st_size
|
|
231
|
+
except Exception:
|
|
232
|
+
size_bytes = len(content.encode('utf-8')) if content else 0
|
|
233
|
+
|
|
215
234
|
skills[name] = {
|
|
216
235
|
"path": skill_md,
|
|
217
|
-
"name":
|
|
236
|
+
"name": name,
|
|
237
|
+
"folder_name": name,
|
|
238
|
+
"frontmatter_name": frontmatter_name,
|
|
218
239
|
"description": description,
|
|
219
240
|
"keywords": list(words),
|
|
220
|
-
"content": content
|
|
241
|
+
"content": content,
|
|
242
|
+
"size_bytes": size_bytes,
|
|
221
243
|
}
|
|
244
|
+
_SKILLS_CACHE = skills
|
|
245
|
+
_SKILLS_CACHE_TS = now
|
|
222
246
|
return skills
|
|
223
247
|
|
|
224
248
|
|