From 9421fbc08ebef15edb705d558a0dc7709e1b04b3 Mon Sep 17 00:00:00 2001 From: Mathias Gesbert Date: Thu, 12 Mar 2026 15:57:44 +0100 Subject: [PATCH] v2.4.2 (#482) Co-authored-by: Kim-Adeline Miguel Co-authored-by: Clement Sirieix Co-authored-by: laurens --- .vscode/launch.json | 2 +- CHANGELOG.md | 14 ++ distribution/zed/extension.toml | 14 +- pyproject.toml | 2 +- tests/acp/test_initialize.py | 4 +- tests/acp/test_search_replace.py | 5 - tests/acp/test_write_file.py | 2 - tests/cli/test_ui_skill_dispatch.py | 152 +++++++++++++++++++++ tests/core/test_config_resolution.py | 39 ++++++ tests/core/test_telemetry_send.py | 100 ++++++++++++++ uv.lock | 2 +- vibe/__init__.py | 2 +- vibe/cli/textual_ui/app.py | 11 +- vibe/core/agent_loop.py | 5 +- vibe/core/config/_settings.py | 14 ++ vibe/core/telemetry/send.py | 12 +- vibe/core/teleport/nuage.py | 7 +- vibe/core/teleport/teleport.py | 4 + vibe/core/tools/builtins/search_replace.py | 2 - vibe/core/tools/builtins/write_file.py | 10 -- vibe/whats_new.md | 7 +- 21 files changed, 370 insertions(+), 40 deletions(-) create mode 100644 tests/cli/test_ui_skill_dispatch.py diff --git a/.vscode/launch.json b/.vscode/launch.json index b63761b..d6fc1ff 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,5 +1,5 @@ { - "version": "2.4.1", + "version": "2.4.2", "configurations": [ { "name": "ACP Server", diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c9bef8..92a2cca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,20 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2.4.2] - 2026-03-12 + +### Added + +- Session ID included in telemetry events for better tracing + +### Changed + +- Skills now extract arguments when invoked, improving parameter handling +- Auto-compact threshold falls back to global setting when not defined at model level +- Update notification toast no longer times out, ensuring the user sees the restart prompt +- Removed `file_content_before` from Vibe Code, reducing payload size + + ## [2.4.1] - 2026-03-10 ### Added diff --git a/distribution/zed/extension.toml b/distribution/zed/extension.toml index 3437b4e..21ddf11 100644 --- a/distribution/zed/extension.toml +++ b/distribution/zed/extension.toml @@ -1,7 +1,7 @@ id = "mistral-vibe" name = "Mistral Vibe" description = "Mistral's open-source coding assistant" -version = "2.4.1" +version = "2.4.2" schema_version = 1 authors = ["Mistral AI"] repository = "https://github.com/mistralai/mistral-vibe" @@ -11,25 +11,25 @@ name = "Mistral Vibe" icon = "./icons/mistral_vibe.svg" [agent_servers.mistral-vibe.targets.darwin-aarch64] -archive = "https://github.com/mistralai/mistral-vibe/releases/download/v2.4.1/vibe-acp-darwin-aarch64-2.4.1.zip" +archive = "https://github.com/mistralai/mistral-vibe/releases/download/v2.4.2/vibe-acp-darwin-aarch64-2.4.2.zip" cmd = "./vibe-acp" [agent_servers.mistral-vibe.targets.darwin-x86_64] -archive = "https://github.com/mistralai/mistral-vibe/releases/download/v2.4.1/vibe-acp-darwin-x86_64-2.4.1.zip" +archive = "https://github.com/mistralai/mistral-vibe/releases/download/v2.4.2/vibe-acp-darwin-x86_64-2.4.2.zip" cmd = "./vibe-acp" [agent_servers.mistral-vibe.targets.linux-aarch64] -archive = "https://github.com/mistralai/mistral-vibe/releases/download/v2.4.1/vibe-acp-linux-aarch64-2.4.1.zip" +archive = "https://github.com/mistralai/mistral-vibe/releases/download/v2.4.2/vibe-acp-linux-aarch64-2.4.2.zip" cmd = "./vibe-acp" [agent_servers.mistral-vibe.targets.linux-x86_64] -archive = "https://github.com/mistralai/mistral-vibe/releases/download/v2.4.1/vibe-acp-linux-x86_64-2.4.1.zip" +archive = "https://github.com/mistralai/mistral-vibe/releases/download/v2.4.2/vibe-acp-linux-x86_64-2.4.2.zip" cmd = "./vibe-acp" [agent_servers.mistral-vibe.targets.windows-aarch64] -archive = "https://github.com/mistralai/mistral-vibe/releases/download/v2.4.1/vibe-acp-windows-aarch64-2.4.1.zip" +archive = "https://github.com/mistralai/mistral-vibe/releases/download/v2.4.2/vibe-acp-windows-aarch64-2.4.2.zip" cmd = "./vibe-acp.exe" [agent_servers.mistral-vibe.targets.windows-x86_64] -archive = "https://github.com/mistralai/mistral-vibe/releases/download/v2.4.1/vibe-acp-windows-x86_64-2.4.1.zip" +archive = "https://github.com/mistralai/mistral-vibe/releases/download/v2.4.2/vibe-acp-windows-x86_64-2.4.2.zip" cmd = "./vibe-acp.exe" diff --git a/pyproject.toml b/pyproject.toml index a31b6d4..f6cfa70 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "mistral-vibe" -version = "2.4.1" +version = "2.4.2" description = "Minimal CLI coding agent by Mistral" readme = "README.md" requires-python = ">=3.12" diff --git a/tests/acp/test_initialize.py b/tests/acp/test_initialize.py index b938066..c414873 100644 --- a/tests/acp/test_initialize.py +++ b/tests/acp/test_initialize.py @@ -28,7 +28,7 @@ class TestACPInitialize: session_capabilities=SessionCapabilities(list=SessionListCapabilities()), ) assert response.agent_info == Implementation( - name="@mistralai/mistral-vibe", title="Mistral Vibe", version="2.4.1" + name="@mistralai/mistral-vibe", title="Mistral Vibe", version="2.4.2" ) assert response.auth_methods == [] @@ -52,7 +52,7 @@ class TestACPInitialize: session_capabilities=SessionCapabilities(list=SessionListCapabilities()), ) assert response.agent_info == Implementation( - name="@mistralai/mistral-vibe", title="Mistral Vibe", version="2.4.1" + name="@mistralai/mistral-vibe", title="Mistral Vibe", version="2.4.2" ) assert response.auth_methods is not None diff --git a/tests/acp/test_search_replace.py b/tests/acp/test_search_replace.py index f2fd5da..da56457 100644 --- a/tests/acp/test_search_replace.py +++ b/tests/acp/test_search_replace.py @@ -114,10 +114,6 @@ class TestAcpSearchReplaceExecution: assert isinstance(result, SearchReplaceResult) assert result.file == str(test_file) assert result.blocks_applied == 1 - assert ( - result.file_content_before - == "original line 1\noriginal line 2\noriginal line 3" - ) assert mock_client._read_text_file_called assert mock_client._write_text_file_called assert mock_client._session_update_called @@ -318,7 +314,6 @@ class TestAcpSearchReplaceSessionUpdates: lines_changed=1, content=search_replace_content, warnings=[], - file_content_before="old text", ) event = ToolResultEvent( diff --git a/tests/acp/test_write_file.py b/tests/acp/test_write_file.py index 799b1a8..7badbe6 100644 --- a/tests/acp/test_write_file.py +++ b/tests/acp/test_write_file.py @@ -80,7 +80,6 @@ class TestAcpWriteFileExecution: assert result.content == "Hello, world!" assert result.bytes_written == len(b"Hello, world!") assert result.file_existed is False - assert result.file_content_before is None assert mock_client._write_text_file_called assert mock_client._session_update_called @@ -114,7 +113,6 @@ class TestAcpWriteFileExecution: assert result.content == "New content" assert result.bytes_written == len(b"New content") assert result.file_existed is True - assert result.file_content_before == "" assert mock_client._write_text_file_called assert mock_client._session_update_called diff --git a/tests/cli/test_ui_skill_dispatch.py b/tests/cli/test_ui_skill_dispatch.py new file mode 100644 index 0000000..025e12d --- /dev/null +++ b/tests/cli/test_ui_skill_dispatch.py @@ -0,0 +1,152 @@ +from __future__ import annotations + +from pathlib import Path +import time + +import pytest + +from tests.conftest import build_test_vibe_app, build_test_vibe_config +from tests.skills.conftest import create_skill +from vibe.cli.textual_ui.app import VibeApp +from vibe.cli.textual_ui.widgets.chat_input.container import ChatInputContainer +from vibe.cli.textual_ui.widgets.messages import ErrorMessage, UserMessage + +SKILL_BODY = "## Instructions\n\nDo the thing." + + +@pytest.fixture +def vibe_app_with_skills(tmp_path: Path) -> VibeApp: + skills_dir = tmp_path / "skills" + skills_dir.mkdir() + create_skill(skills_dir, "my-skill", body=SKILL_BODY) + return build_test_vibe_app(config=build_test_vibe_config(skill_paths=[skills_dir])) + + +async def _wait_for_user_message_containing( + vibe_app: VibeApp, pilot, text: str, timeout: float = 1.0 +) -> UserMessage: + deadline = time.monotonic() + timeout + while time.monotonic() < deadline: + for message in vibe_app.query(UserMessage): + if text in message._content: + return message + await pilot.pause(0.05) + raise TimeoutError( + f"UserMessage containing {text!r} did not appear within {timeout}s" + ) + + +async def _wait_for_error_message_containing( + vibe_app: VibeApp, pilot, text: str, timeout: float = 1.0 +) -> ErrorMessage: + deadline = time.monotonic() + timeout + while time.monotonic() < deadline: + for error in vibe_app.query(ErrorMessage): + if text in error._error: + return error + await pilot.pause(0.05) + raise TimeoutError( + f"ErrorMessage containing {text!r} did not appear within {timeout}s" + ) + + +@pytest.mark.asyncio +async def test_skill_without_args_sends_skill_content( + vibe_app_with_skills: VibeApp, +) -> None: + async with vibe_app_with_skills.run_test() as pilot: + await pilot.pause(0.1) + chat_input = vibe_app_with_skills.query_one(ChatInputContainer) + chat_input.post_message(ChatInputContainer.Submitted("/my-skill")) + await pilot.pause(0.1) + + message = await _wait_for_user_message_containing( + vibe_app_with_skills, pilot, "Do the thing." + ) + assert "Do the thing." in message._content + + +@pytest.mark.asyncio +async def test_skill_with_args_prepends_invocation_line( + vibe_app_with_skills: VibeApp, +) -> None: + async with vibe_app_with_skills.run_test() as pilot: + await pilot.pause(0.1) + chat_input = vibe_app_with_skills.query_one(ChatInputContainer) + chat_input.post_message(ChatInputContainer.Submitted("/my-skill foo bar")) + await pilot.pause(0.1) + + message = await _wait_for_user_message_containing( + vibe_app_with_skills, pilot, "Do the thing." + ) + assert "/my-skill foo bar" in message._content + assert "Do the thing." in message._content + + +@pytest.mark.asyncio +async def test_unknown_skill_falls_through_to_agent( + vibe_app_with_skills: VibeApp, +) -> None: + async with vibe_app_with_skills.run_test() as pilot: + await pilot.pause(0.1) + chat_input = vibe_app_with_skills.query_one(ChatInputContainer) + chat_input.post_message(ChatInputContainer.Submitted("/nonexistent-skill")) + await pilot.pause(0.2) + + skill_errors = [ + e + for e in vibe_app_with_skills.query(ErrorMessage) + if "skill" in str(getattr(e, "_error", "")).lower() + ] + assert not skill_errors + + +@pytest.mark.asyncio +async def test_bare_slash_falls_through(vibe_app_with_skills: VibeApp) -> None: + async with vibe_app_with_skills.run_test() as pilot: + await pilot.pause(0.1) + chat_input = vibe_app_with_skills.query_one(ChatInputContainer) + chat_input.post_message(ChatInputContainer.Submitted("/")) + await pilot.pause(0.2) + + assert not any( + "Do the thing." in m._content + for m in vibe_app_with_skills.query(UserMessage) + ) + + +@pytest.mark.asyncio +async def test_skill_without_args_does_not_prepend_invocation_line( + vibe_app_with_skills: VibeApp, +) -> None: + async with vibe_app_with_skills.run_test() as pilot: + await pilot.pause(0.1) + chat_input = vibe_app_with_skills.query_one(ChatInputContainer) + chat_input.post_message(ChatInputContainer.Submitted("/my-skill")) + await pilot.pause(0.1) + + message = await _wait_for_user_message_containing( + vibe_app_with_skills, pilot, "Do the thing." + ) + assert "/my-skill" not in message._content + + +@pytest.mark.asyncio +async def test_skill_with_missing_file_shows_error(tmp_path: Path) -> None: + skills_dir = tmp_path / "skills" + skills_dir.mkdir() + skill_dir = create_skill(skills_dir, "my-skill", body=SKILL_BODY) + vibe_app = build_test_vibe_app( + config=build_test_vibe_config(skill_paths=[skills_dir]) + ) + (skill_dir / "SKILL.md").unlink() + + async with vibe_app.run_test() as pilot: + await pilot.pause(0.1) + chat_input = vibe_app.query_one(ChatInputContainer) + chat_input.post_message(ChatInputContainer.Submitted("/my-skill")) + + error = await _wait_for_error_message_containing( + vibe_app, pilot, "Failed to read skill file" + ) + assert "Failed to read skill file" in error._error diff --git a/tests/core/test_config_resolution.py b/tests/core/test_config_resolution.py index 41680a9..4d1e633 100644 --- a/tests/core/test_config_resolution.py +++ b/tests/core/test_config_resolution.py @@ -4,6 +4,8 @@ from pathlib import Path import pytest +from tests.conftest import build_test_vibe_config +from vibe.core.config import ModelConfig from vibe.core.config.harness_files import ( HarnessFilesManager, init_harness_files_manager, @@ -80,3 +82,40 @@ class TestResolveConfigFile: def test_user_only_returns_global_config(self) -> None: mgr = HarnessFilesManager(sources=("user",)) assert mgr.config_file == VIBE_HOME.path / "config.toml" + + +class TestAutoCompactThresholdFallback: + def test_model_without_explicit_threshold_inherits_global(self) -> None: + model = ModelConfig(name="m", provider="p", alias="m") + cfg = build_test_vibe_config( + auto_compact_threshold=42_000, models=[model], active_model="m" + ) + assert cfg.get_active_model().auto_compact_threshold == 42_000 + + def test_model_with_explicit_threshold_keeps_own_value(self) -> None: + model = ModelConfig( + name="m", provider="p", alias="m", auto_compact_threshold=99_000 + ) + cfg = build_test_vibe_config( + auto_compact_threshold=42_000, models=[model], active_model="m" + ) + assert cfg.get_active_model().auto_compact_threshold == 99_000 + + def test_default_global_threshold_used_when_nothing_set(self) -> None: + model = ModelConfig(name="m", provider="p", alias="m") + cfg = build_test_vibe_config(models=[model], active_model="m") + assert cfg.get_active_model().auto_compact_threshold == 200_000 + + def test_changed_global_threshold_propagates_on_reload(self) -> None: + model = ModelConfig(name="m", provider="p", alias="m") + + cfg1 = build_test_vibe_config( + auto_compact_threshold=50_000, models=[model], active_model="m" + ) + assert cfg1.get_active_model().auto_compact_threshold == 50_000 + + # Simulate config reload with a different global threshold + cfg2 = build_test_vibe_config( + auto_compact_threshold=75_000, models=[model], active_model="m" + ) + assert cfg2.get_active_model().auto_compact_threshold == 75_000 diff --git a/tests/core/test_telemetry_send.py b/tests/core/test_telemetry_send.py index 2e72ca5..e2e0cc6 100644 --- a/tests/core/test_telemetry_send.py +++ b/tests/core/test_telemetry_send.py @@ -272,3 +272,103 @@ class TestTelemetryClient: assert properties["entrypoint"] == "cli" assert properties["terminal_emulator"] == "vscode" assert "version" in properties + + @pytest.mark.asyncio + async def test_session_id_added_when_getter_provided( + self, monkeypatch: pytest.MonkeyPatch + ) -> None: + monkeypatch.setattr( + TelemetryClient, "send_telemetry_event", _original_send_telemetry_event + ) + config = build_test_vibe_config(enable_telemetry=True) + env_key = config.get_provider_for_model( + config.get_active_model() + ).api_key_env_var + monkeypatch.setenv(env_key, "sk-test") + session_id = "test-session-uuid" + client = TelemetryClient( + config_getter=lambda: config, session_id_getter=lambda: session_id + ) + mock_post = AsyncMock(return_value=MagicMock(status_code=204)) + client._client = MagicMock() + client._client.post = mock_post + client._client.aclose = AsyncMock() + + client.send_telemetry_event("vibe.test_event", {"key": "value"}) + await client.aclose() + + mock_post.assert_called_once_with( + DATALAKE_EVENTS_URL, + json={ + "event": "vibe.test_event", + "properties": {"session_id": session_id, "key": "value"}, + }, + headers={ + "Content-Type": "application/json", + "Authorization": "Bearer sk-test", + "User-Agent": get_user_agent(Backend.MISTRAL), + }, + ) + + @pytest.mark.asyncio + async def test_session_id_absent_when_no_getter( + self, monkeypatch: pytest.MonkeyPatch + ) -> None: + monkeypatch.setattr( + TelemetryClient, "send_telemetry_event", _original_send_telemetry_event + ) + config = build_test_vibe_config(enable_telemetry=True) + env_key = config.get_provider_for_model( + config.get_active_model() + ).api_key_env_var + monkeypatch.setenv(env_key, "sk-test") + client = TelemetryClient(config_getter=lambda: config) + mock_post = AsyncMock(return_value=MagicMock(status_code=204)) + client._client = MagicMock() + client._client.post = mock_post + client._client.aclose = AsyncMock() + + client.send_telemetry_event("vibe.test_event", {"key": "value"}) + await client.aclose() + + mock_post.assert_called_once_with( + DATALAKE_EVENTS_URL, + json={"event": "vibe.test_event", "properties": {"key": "value"}}, + headers={ + "Content-Type": "application/json", + "Authorization": "Bearer sk-test", + "User-Agent": get_user_agent(Backend.MISTRAL), + }, + ) + + @pytest.mark.asyncio + async def test_session_id_getter_reflects_latest_value( + self, monkeypatch: pytest.MonkeyPatch + ) -> None: + monkeypatch.setattr( + TelemetryClient, "send_telemetry_event", _original_send_telemetry_event + ) + config = build_test_vibe_config(enable_telemetry=True) + env_key = config.get_provider_for_model( + config.get_active_model() + ).api_key_env_var + monkeypatch.setenv(env_key, "sk-test") + current_id = "first-session-id" + client = TelemetryClient( + config_getter=lambda: config, session_id_getter=lambda: current_id + ) + mock_post = AsyncMock(return_value=MagicMock(status_code=204)) + client._client = MagicMock() + client._client.post = mock_post + client._client.aclose = AsyncMock() + + client.send_telemetry_event("vibe.test_event", {}) + current_id = "second-session-id" + client.send_telemetry_event("vibe.test_event", {}) + await client.aclose() + + calls = mock_post.call_args_list + assert calls[0].kwargs["json"]["properties"]["session_id"] == "first-session-id" + assert ( + calls[1].kwargs["json"]["properties"]["session_id"] == "second-session-id" + ) diff --git a/uv.lock b/uv.lock index fc48d63..bb03376 100644 --- a/uv.lock +++ b/uv.lock @@ -779,7 +779,7 @@ wheels = [ [[package]] name = "mistral-vibe" -version = "2.4.1" +version = "2.4.2" source = { editable = "." } dependencies = [ { name = "agent-client-protocol" }, diff --git a/vibe/__init__.py b/vibe/__init__.py index b71cb46..aab0ddc 100644 --- a/vibe/__init__.py +++ b/vibe/__init__.py @@ -3,4 +3,4 @@ from __future__ import annotations from pathlib import Path VIBE_ROOT = Path(__file__).parent -__version__ = "2.4.1" +__version__ = "2.4.2" diff --git a/vibe/cli/textual_ui/app.py b/vibe/cli/textual_ui/app.py index 7589601..5d6bcc5 100644 --- a/vibe/cli/textual_ui/app.py +++ b/vibe/cli/textual_ui/app.py @@ -537,7 +537,11 @@ class VibeApp(App): # noqa: PLR0904 if not self.agent_loop: return False - skill_name = user_input[1:].strip().lower() + parts = user_input[1:].strip().split(None, 1) + if not parts: + return False + skill_name = parts[0].lower() + skill_info = self.agent_loop.skill_manager.get_skill(skill_name) if not skill_info: return False @@ -554,6 +558,9 @@ class VibeApp(App): # noqa: PLR0904 ) return True + if len(parts) > 1: + skill_content = f"{user_input}\n\n{skill_content}" + await self._handle_user_message(skill_content) return True @@ -1599,7 +1606,7 @@ class VibeApp(App): # noqa: PLR0904 f"{update_message_prefix}\nVibe was updated successfully. Please restart to use the new version.", title="Update successful", severity="information", - timeout=10, + timeout=float("inf"), ) return diff --git a/vibe/core/agent_loop.py b/vibe/core/agent_loop.py index 56d3ad5..110e8fa 100644 --- a/vibe/core/agent_loop.py +++ b/vibe/core/agent_loop.py @@ -199,7 +199,9 @@ class AgentLoop: self.session_id = str(uuid4()) self._current_user_message_id: str | None = None - self.telemetry_client = TelemetryClient(config_getter=lambda: self.config) + self.telemetry_client = TelemetryClient( + config_getter=lambda: self.config, session_id_getter=lambda: self.session_id + ) self.session_logger = SessionLogger(config.session_logging, self.session_id) self._teleport_service: TeleportService | None = None @@ -297,6 +299,7 @@ class AgentLoop: nuage_base_url=self.config.nuage_base_url, nuage_workflow_id=self.config.nuage_workflow_id, nuage_api_key=self.config.nuage_api_key, + nuage_task_queue=self.config.nuage_task_queue, ) return self._teleport_service diff --git a/vibe/core/config/_settings.py b/vibe/core/config/_settings.py index 87521e6..7013fff 100644 --- a/vibe/core/config/_settings.py +++ b/vibe/core/config/_settings.py @@ -309,11 +309,13 @@ class VibeConfig(BaseSettings): enable_auto_update: bool = True enable_notifications: bool = True api_timeout: float = 720.0 + auto_compact_threshold: int = 200_000 # TODO(vibe-nuage): remove exclude=True once the feature is publicly available nuage_enabled: bool = Field(default=False, exclude=True) nuage_base_url: str = Field(default="https://api.globalaegis.net", exclude=True) nuage_workflow_id: str = Field(default="__shared-nuage-workflow", exclude=True) + nuage_task_queue: str | None = Field(default="shared-vibe-nuage", exclude=True) # TODO(vibe-nuage): change default value to MISTRAL_API_KEY once prod has shared vibe-nuage workers nuage_api_key_env_var: str = Field(default="STAGING_MISTRAL_API_KEY", exclude=True) @@ -466,6 +468,18 @@ class VibeConfig(BaseSettings): file_secret_settings, ) + @model_validator(mode="after") + def _apply_global_auto_compact_threshold(self) -> VibeConfig: + self.models = [ + model + if "auto_compact_threshold" in model.model_fields_set + else model.model_copy( + update={"auto_compact_threshold": self.auto_compact_threshold} + ) + for model in self.models + ] + return self + @model_validator(mode="after") def _check_api_key(self) -> VibeConfig: try: diff --git a/vibe/core/telemetry/send.py b/vibe/core/telemetry/send.py index 386f206..47faa34 100644 --- a/vibe/core/telemetry/send.py +++ b/vibe/core/telemetry/send.py @@ -19,8 +19,13 @@ DATALAKE_EVENTS_URL = "https://codestral.mistral.ai/v1/datalake/events" class TelemetryClient: - def __init__(self, config_getter: Callable[[], VibeConfig]) -> None: + def __init__( + self, + config_getter: Callable[[], VibeConfig], + session_id_getter: Callable[[], str | None] | None = None, + ) -> None: self._config_getter = config_getter + self._session_id_getter = session_id_getter self._client: httpx.AsyncClient | None = None self._pending_tasks: set[asyncio.Task[Any]] = set() @@ -71,6 +76,11 @@ class TelemetryClient: if mistral_api_key is None or not self._is_enabled(): return user_agent = self._get_telemetry_user_agent() + if ( + self._session_id_getter is not None + and (session_id := self._session_id_getter()) is not None + ): + properties = {**properties, "session_id": session_id} async def _send() -> None: try: diff --git a/vibe/core/teleport/nuage.py b/vibe/core/teleport/nuage.py index 7bae56a..1944966 100644 --- a/vibe/core/teleport/nuage.py +++ b/vibe/core/teleport/nuage.py @@ -71,12 +71,14 @@ class NuageClient: api_key: str, workflow_id: str, *, + task_queue: str | None = None, client: httpx.AsyncClient | None = None, timeout: float = 60.0, ) -> None: self._base_url = base_url.rstrip("/") self._api_key = api_key self._workflow_id = workflow_id + self._task_queue = task_queue self._client = client self._owns_client = client is None self._timeout = timeout @@ -113,7 +115,10 @@ class NuageClient: response = await self._http_client.post( f"{self._base_url}/v1/workflows/{self._workflow_id}/execute", headers=self._headers(), - json={"input": params.model_dump(mode="json")}, + json={ + "input": params.model_dump(mode="json"), + "task_queue": self._task_queue, + }, ) if not response.is_success: error_msg = f"Nuage workflow trigger failed: {response.text}" diff --git a/vibe/core/teleport/teleport.py b/vibe/core/teleport/teleport.py index 8da8538..374b3a3 100644 --- a/vibe/core/teleport/teleport.py +++ b/vibe/core/teleport/teleport.py @@ -48,6 +48,7 @@ class TeleportService: nuage_api_key: str, workdir: Path | None = None, *, + nuage_task_queue: str | None = None, client: httpx.AsyncClient | None = None, timeout: float = 60.0, ) -> None: @@ -55,6 +56,7 @@ class TeleportService: self._nuage_base_url = nuage_base_url self._nuage_workflow_id = nuage_workflow_id self._nuage_api_key = nuage_api_key + self._nuage_task_queue = nuage_task_queue self._git = GitRepository(workdir) self._client = client self._owns_client = client is None @@ -70,6 +72,7 @@ class TeleportService: self._nuage_base_url, self._nuage_api_key, self._nuage_workflow_id, + task_queue=self._nuage_task_queue, client=self._client, ) await self._git.__aenter__() @@ -106,6 +109,7 @@ class TeleportService: self._nuage_base_url, self._nuage_api_key, self._nuage_workflow_id, + task_queue=self._nuage_task_queue, client=self._http_client, ) return self._nuage diff --git a/vibe/core/tools/builtins/search_replace.py b/vibe/core/tools/builtins/search_replace.py index eef4275..9c730eb 100644 --- a/vibe/core/tools/builtins/search_replace.py +++ b/vibe/core/tools/builtins/search_replace.py @@ -62,7 +62,6 @@ class SearchReplaceResult(BaseModel): lines_changed: int content: str warnings: list[str] = Field(default_factory=list) - file_content_before: str class SearchReplaceConfig(BaseToolConfig): @@ -163,7 +162,6 @@ class SearchReplace( lines_changed=lines_changed, warnings=block_result.warnings, content=args.content, - file_content_before=original_content, ) @final diff --git a/vibe/core/tools/builtins/write_file.py b/vibe/core/tools/builtins/write_file.py index 9d263e8..3078125 100644 --- a/vibe/core/tools/builtins/write_file.py +++ b/vibe/core/tools/builtins/write_file.py @@ -33,7 +33,6 @@ class WriteFileResult(BaseModel): bytes_written: int file_existed: bool content: str - file_content_before: str | None = None class WriteFileConfig(BaseToolConfig): @@ -85,14 +84,6 @@ class WriteFile( ) -> AsyncGenerator[ToolStreamEvent | WriteFileResult, None]: file_path, file_existed, content_bytes = self._prepare_and_validate_path(args) - file_content_before: str | None = None - if file_existed and args.overwrite: - try: - async with await anyio.Path(file_path).open(encoding="utf-8") as f: - file_content_before = await f.read(524_288) # 512kb - except Exception: - pass - await self._write_file(args, file_path) yield WriteFileResult( @@ -100,7 +91,6 @@ class WriteFile( bytes_written=content_bytes, file_existed=file_existed, content=args.content, - file_content_before=file_content_before, ) def _prepare_and_validate_path(self, args: WriteFileArgs) -> tuple[Path, bool, int]: diff --git a/vibe/whats_new.md b/vibe/whats_new.md index 6d3fd33..c45e484 100644 --- a/vibe/whats_new.md +++ b/vibe/whats_new.md @@ -1,3 +1,4 @@ -# What's new in v2.4.1 -- **Prompt mode**: Disabled interactive questions in prompt mode for smoother non-interactive usage -- **VS Code terminal fix**: Space key now works correctly in all input widgets (question prompts, proxy setup) +# What's new in v2.4.2 + +- **Skill arguments**: Skills now extract arguments when invoked, allowing you to pass arguments +- **Auto-compact fallback**: Auto-compact threshold falls back to the global setting when not defined at model level