diff --git a/CHANGELOG.md b/CHANGELOG.md index c162d09..3dc5c8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ 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.9.2] - 2026-04-29 + +### Fixed + +- Teleport surfaces the latest GitHub connection status while polling + + ## [2.9.1] - 2026-04-29 ### Added diff --git a/distribution/zed/extension.toml b/distribution/zed/extension.toml index f1dbb2d..a69c173 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.9.1" +version = "2.9.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.9.1/vibe-acp-darwin-aarch64-2.9.1.zip" +archive = "https://github.com/mistralai/mistral-vibe/releases/download/v2.9.2/vibe-acp-darwin-aarch64-2.9.2.zip" cmd = "./vibe-acp" [agent_servers.mistral-vibe.targets.darwin-x86_64] -archive = "https://github.com/mistralai/mistral-vibe/releases/download/v2.9.1/vibe-acp-darwin-x86_64-2.9.1.zip" +archive = "https://github.com/mistralai/mistral-vibe/releases/download/v2.9.2/vibe-acp-darwin-x86_64-2.9.2.zip" cmd = "./vibe-acp" [agent_servers.mistral-vibe.targets.linux-aarch64] -archive = "https://github.com/mistralai/mistral-vibe/releases/download/v2.9.1/vibe-acp-linux-aarch64-2.9.1.zip" +archive = "https://github.com/mistralai/mistral-vibe/releases/download/v2.9.2/vibe-acp-linux-aarch64-2.9.2.zip" cmd = "./vibe-acp" [agent_servers.mistral-vibe.targets.linux-x86_64] -archive = "https://github.com/mistralai/mistral-vibe/releases/download/v2.9.1/vibe-acp-linux-x86_64-2.9.1.zip" +archive = "https://github.com/mistralai/mistral-vibe/releases/download/v2.9.2/vibe-acp-linux-x86_64-2.9.2.zip" cmd = "./vibe-acp" [agent_servers.mistral-vibe.targets.windows-aarch64] -archive = "https://github.com/mistralai/mistral-vibe/releases/download/v2.9.1/vibe-acp-windows-aarch64-2.9.1.zip" +archive = "https://github.com/mistralai/mistral-vibe/releases/download/v2.9.2/vibe-acp-windows-aarch64-2.9.2.zip" cmd = "./vibe-acp.exe" [agent_servers.mistral-vibe.targets.windows-x86_64] -archive = "https://github.com/mistralai/mistral-vibe/releases/download/v2.9.1/vibe-acp-windows-x86_64-2.9.1.zip" +archive = "https://github.com/mistralai/mistral-vibe/releases/download/v2.9.2/vibe-acp-windows-x86_64-2.9.2.zip" cmd = "./vibe-acp.exe" diff --git a/pyproject.toml b/pyproject.toml index a4fd1d3..142946d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "mistral-vibe" -version = "2.9.1" +version = "2.9.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 bb9401e..0c75aac 100644 --- a/tests/acp/test_initialize.py +++ b/tests/acp/test_initialize.py @@ -34,7 +34,7 @@ class TestACPInitialize: ), ) assert response.agent_info == Implementation( - name="@mistralai/mistral-vibe", title="Mistral Vibe", version="2.9.1" + name="@mistralai/mistral-vibe", title="Mistral Vibe", version="2.9.2" ) assert response.auth_methods == [] @@ -62,7 +62,7 @@ class TestACPInitialize: ), ) assert response.agent_info == Implementation( - name="@mistralai/mistral-vibe", title="Mistral Vibe", version="2.9.1" + name="@mistralai/mistral-vibe", title="Mistral Vibe", version="2.9.2" ) assert response.auth_methods is not None diff --git a/tests/core/test_config_schema.py b/tests/core/test_config_schema.py new file mode 100644 index 0000000..f8cf3ee --- /dev/null +++ b/tests/core/test_config_schema.py @@ -0,0 +1,128 @@ +from __future__ import annotations + +from typing import Annotated + +from pydantic import BaseModel, Field +import pytest + +from vibe.core.config import ( + ConfigDefinitionError, + ConfigFragment, + ConfigSchema, + WithConcatMerge, + WithReplaceMerge, + WithShallowMerge, + WithUnionMerge, +) + + +class ToolSettings(BaseModel): + enabled: bool = True + + +class ModelDefinition(BaseModel): + alias: str + provider: str + + +class ToolsFragment(ConfigFragment): + disabled_tools: Annotated[list[str], WithConcatMerge()] = Field( + default_factory=list + ) + tool_settings: Annotated[dict[str, ToolSettings], WithShallowMerge()] = Field( + default_factory=dict + ) + + +class ModelsFragment(ConfigFragment): + default_model: Annotated[str, WithReplaceMerge()] = "devstral-small" + available_models: Annotated[ + list[ModelDefinition], WithUnionMerge(merge_key="alias") + ] = Field(default_factory=list) + + +class MiniVibeConfigSchema(ConfigSchema): + active_model: Annotated[str, WithReplaceMerge()] = "devstral-2" + models: ModelsFragment = Field(default_factory=ModelsFragment) + tools: ToolsFragment = Field(default_factory=ToolsFragment) + + +def test_config_fragment_accepts_merge_aware_fields() -> None: + fragment = ToolsFragment( + disabled_tools=["bash"], tool_settings={"search": ToolSettings(enabled=False)} + ) + + assert fragment.disabled_tools == ["bash"] + assert fragment.tool_settings["search"].enabled is False + + +def test_config_fragment_rejects_plain_fields() -> None: + with pytest.raises( + ConfigDefinitionError, match="BrokenFragment.name must declare merge metadata" + ): + + class BrokenFragment(ConfigFragment): + name: str = "bash" + + +def test_scenario_mini_vibe_config_schema() -> None: + config = MiniVibeConfigSchema( + active_model="codestral-latest", + models=ModelsFragment( + available_models=[ + ModelDefinition(alias="devstral-small", provider="mistral"), + ModelDefinition(alias="codestral-latest", provider="mistral"), + ] + ), + tools=ToolsFragment(disabled_tools=["bash"]), + ) + + assert config.active_model == "codestral-latest" + assert config.models.default_model == "devstral-small" + assert [model.alias for model in config.models.available_models] == [ + "devstral-small", + "codestral-latest", + ] + assert config.tools.disabled_tools == ["bash"] + + +def test_config_schema_rejects_plain_top_level_fields() -> None: + with pytest.raises( + ConfigDefinitionError, + match="BrokenSchema.active_model must declare merge metadata", + ): + + class BrokenSchema(ConfigSchema): + active_model: str = "devstral-2" + + +def test_config_schema_rejects_plain_nested_models() -> None: + with pytest.raises( + ConfigDefinitionError, + match="BrokenSchema.tools must declare merge metadata or use a ConfigFragment subclass", + ): + + class BrokenSchema(ConfigSchema): + tools: ToolSettings = Field(default_factory=ToolSettings) + + +def test_config_schema_rejects_merge_metadata_on_fragments() -> None: + with pytest.raises( + ConfigDefinitionError, + match="BrokenSchema.tools is a ConfigFragment field and must not declare merge metadata", + ): + + class BrokenSchema(ConfigSchema): + tools: Annotated[ToolsFragment, WithReplaceMerge()] = Field( + default_factory=ToolsFragment + ) + + +def test_config_schema_rejects_optional_fragments() -> None: + with pytest.raises( + ConfigDefinitionError, + match="BrokenSchema.tools must declare merge metadata or use a ConfigFragment subclass", + ): + + class BrokenSchema(ConfigSchema): + tools: ToolsFragment | None = None diff --git a/tests/core/test_nuage_client.py b/tests/core/test_nuage_client.py index c6f6db8..3d232dd 100644 --- a/tests/core/test_nuage_client.py +++ b/tests/core/test_nuage_client.py @@ -23,9 +23,10 @@ class TestWaitForGithubConnection: connected = GitHubPublicData(status=GitHubStatus.CONNECTED) client.get_github_integration = AsyncMock(return_value=connected) - result = await client.wait_for_github_connection("exec-1") + results = [data async for data in client.wait_for_github_connection("exec-1")] - assert result.connected is True + assert len(results) == 1 + assert results[0].connected is True client.get_github_integration.assert_called_once_with("exec-1") @pytest.mark.asyncio @@ -40,9 +41,12 @@ class TestWaitForGithubConnection: ) with patch("vibe.core.teleport.nuage.asyncio.sleep", new_callable=AsyncMock): - result = await client.wait_for_github_connection("exec-1") + results = [ + data async for data in client.wait_for_github_connection("exec-1") + ] - assert result.connected is True + assert len(results) == 3 + assert results[-1].connected is True assert client.get_github_integration.call_count == 3 @pytest.mark.asyncio @@ -53,7 +57,8 @@ class TestWaitForGithubConnection: client.get_github_integration = AsyncMock(return_value=error_data) with pytest.raises(ServiceTeleportError, match="App not installed"): - await client.wait_for_github_connection("exec-1") + async for _ in client.wait_for_github_connection("exec-1"): + pass @pytest.mark.asyncio async def test_raises_on_oauth_timeout(self, client: NuageClient) -> None: @@ -61,7 +66,8 @@ class TestWaitForGithubConnection: client.get_github_integration = AsyncMock(return_value=timeout_data) with pytest.raises(ServiceTeleportError, match="oauth_timeout"): - await client.wait_for_github_connection("exec-1") + async for _ in client.wait_for_github_connection("exec-1"): + pass @pytest.mark.asyncio async def test_raises_on_timeout(self, client: NuageClient) -> None: @@ -77,7 +83,8 @@ class TestWaitForGithubConnection: patch("vibe.core.teleport.nuage.asyncio.sleep", new_callable=AsyncMock), pytest.raises(ServiceTeleportError, match="timed out"), ): - await client.wait_for_github_connection("exec-1", timeout=600.0) + async for _ in client.wait_for_github_connection("exec-1", timeout=600.0): + pass @pytest.mark.asyncio async def test_sleeps_with_correct_interval(self, client: NuageClient) -> None: @@ -88,8 +95,31 @@ class TestWaitForGithubConnection: with patch( "vibe.core.teleport.nuage.asyncio.sleep", new_callable=AsyncMock ) as mock_sleep: - await client.wait_for_github_connection("exec-1", interval=5.0) + async for _ in client.wait_for_github_connection("exec-1", interval=5.0): + pass mock_sleep.assert_called_once() sleep_duration = mock_sleep.call_args[0][0] assert sleep_duration <= 5.0 + + @pytest.mark.asyncio + async def test_yields_each_poll_result(self, client: NuageClient) -> None: + pending = GitHubPublicData( + status=GitHubStatus.WAITING_FOR_OAUTH, + error="Please connect GitHub", + oauth_url="https://github.com/auth", + ) + connected = GitHubPublicData(status=GitHubStatus.CONNECTED) + client.get_github_integration = AsyncMock( + side_effect=[pending, pending, connected] + ) + + with patch("vibe.core.teleport.nuage.asyncio.sleep", new_callable=AsyncMock): + results = [ + data async for data in client.wait_for_github_connection("exec-1") + ] + + assert len(results) == 3 + assert results[0].error == "Please connect GitHub" + assert results[1].error == "Please connect GitHub" + assert results[2].connected is True diff --git a/tests/core/test_teleport_service.py b/tests/core/test_teleport_service.py index a8e1998..fe3f2ff 100644 --- a/tests/core/test_teleport_service.py +++ b/tests/core/test_teleport_service.py @@ -260,11 +260,12 @@ class TestTeleportServiceExecute: service._git.get_info = AsyncMock(return_value=git_info) service._git.is_commit_pushed = AsyncMock(return_value=True) + async def _connected_gen(*_a: object, **_kw: object): # type: ignore[no-untyped-def] + yield mock_github_connected + mock_nuage = MagicMock() mock_nuage.start_workflow = AsyncMock(return_value="exec-123") - mock_nuage.get_github_integration = AsyncMock( - return_value=mock_github_connected - ) + mock_nuage.wait_for_github_connection = _connected_gen mock_nuage.get_chat_assistant_url = AsyncMock( return_value="https://chat.example.com/123" ) @@ -279,9 +280,10 @@ class TestTeleportServiceExecute: assert isinstance(events[0], TeleportCheckingGitEvent) assert isinstance(events[1], TeleportStartingWorkflowEvent) assert isinstance(events[2], TeleportWaitingForGitHubEvent) - assert isinstance(events[3], TeleportFetchingUrlEvent) - assert isinstance(events[4], TeleportCompleteEvent) - assert events[4].url == "https://chat.example.com/123" + assert isinstance(events[3], TeleportAuthCompleteEvent) + assert isinstance(events[4], TeleportFetchingUrlEvent) + assert isinstance(events[5], TeleportCompleteEvent) + assert events[5].url == "https://chat.example.com/123" workflow_params = mock_nuage.start_workflow.call_args.args[0] assert workflow_params.integrations.chat_assistant is not None assert workflow_params.integrations.chat_assistant.project_name is None @@ -298,11 +300,12 @@ class TestTeleportServiceExecute: service._git.get_unpushed_commit_count = AsyncMock(return_value=3) service._git.push_current_branch = AsyncMock(return_value=True) + async def _connected_gen(*_a: object, **_kw: object): # type: ignore[no-untyped-def] + yield mock_github_connected + mock_nuage = MagicMock() mock_nuage.start_workflow = AsyncMock(return_value="exec-123") - mock_nuage.get_github_integration = AsyncMock( - return_value=mock_github_connected - ) + mock_nuage.wait_for_github_connection = _connected_gen mock_nuage.get_chat_assistant_url = AsyncMock( return_value="https://chat.example.com/123" ) @@ -357,18 +360,22 @@ class TestTeleportServiceExecute: github_pending = MagicMock() github_pending.connected = False github_pending.oauth_url = "https://github.com/login/oauth" - github_pending.error = None + github_pending.error = "Please connect GitHub" github_pending.status = GitHubStatus.WAITING_FOR_OAUTH github_connected = MagicMock() github_connected.connected = True github_connected.oauth_url = None + github_connected.error = None github_connected.status = GitHubStatus.CONNECTED + async def _oauth_gen(*_a: object, **_kw: object): # type: ignore[no-untyped-def] + yield github_pending + yield github_connected + mock_nuage = MagicMock() mock_nuage.start_workflow = AsyncMock(return_value="exec-123") - mock_nuage.get_github_integration = AsyncMock(return_value=github_pending) - mock_nuage.wait_for_github_connection = AsyncMock(return_value=github_connected) + mock_nuage.wait_for_github_connection = _oauth_gen mock_nuage.get_chat_assistant_url = AsyncMock( return_value="https://chat.example.com/123" ) @@ -383,9 +390,12 @@ class TestTeleportServiceExecute: assert isinstance(events[0], TeleportCheckingGitEvent) assert isinstance(events[1], TeleportStartingWorkflowEvent) assert isinstance(events[2], TeleportWaitingForGitHubEvent) + assert events[2].message is None assert isinstance(events[3], TeleportAuthRequiredEvent) assert events[3].oauth_url == "https://github.com/login/oauth" - assert isinstance(events[4], TeleportAuthCompleteEvent) + assert isinstance(events[4], TeleportWaitingForGitHubEvent) + assert events[4].message == "Please connect GitHub" + assert isinstance(events[5], TeleportAuthCompleteEvent) assert isinstance(events[-1], TeleportCompleteEvent) @pytest.mark.asyncio @@ -398,11 +408,12 @@ class TestTeleportServiceExecute: service._git.get_info = AsyncMock(return_value=git_info) service._git.is_commit_pushed = AsyncMock(return_value=True) + async def _connected_gen(*_a: object, **_kw: object): # type: ignore[no-untyped-def] + yield mock_github_connected + mock_nuage = MagicMock() mock_nuage.start_workflow = AsyncMock(return_value="exec-123") - mock_nuage.get_github_integration = AsyncMock( - return_value=mock_github_connected - ) + mock_nuage.wait_for_github_connection = _connected_gen mock_nuage.get_chat_assistant_url = AsyncMock(return_value=None) service._nuage_client_instance = mock_nuage @@ -423,11 +434,12 @@ class TestTeleportServiceExecute: service._git.get_info = AsyncMock(return_value=git_info) service._git.is_commit_pushed = AsyncMock(return_value=True) + async def _connected_gen(*_a: object, **_kw: object): # type: ignore[no-untyped-def] + yield mock_github_connected + mock_nuage = MagicMock() mock_nuage.start_workflow = AsyncMock(return_value="exec-123") - mock_nuage.get_github_integration = AsyncMock( - return_value=mock_github_connected - ) + mock_nuage.wait_for_github_connection = _connected_gen mock_nuage.get_chat_assistant_url = AsyncMock( return_value="https://chat.example.com/123" ) diff --git a/uv.lock b/uv.lock index 38c1221..284e544 100644 --- a/uv.lock +++ b/uv.lock @@ -3,7 +3,7 @@ revision = 3 requires-python = ">=3.12" [options] -exclude-newer = "2026-04-22T13:12:26.10035Z" +exclude-newer = "2026-04-22T17:04:46.06461Z" exclude-newer-span = "P7D" [options.exclude-newer-package] @@ -808,7 +808,7 @@ wheels = [ [[package]] name = "mistral-vibe" -version = "2.9.1" +version = "2.9.2" source = { editable = "." } dependencies = [ { name = "agent-client-protocol" }, diff --git a/vibe/__init__.py b/vibe/__init__.py index db20725..bf451e2 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.9.1" +__version__ = "2.9.2" diff --git a/vibe/cli/textual_ui/app.py b/vibe/cli/textual_ui/app.py index 9d43f7b..a6d9c06 100644 --- a/vibe/cli/textual_ui/app.py +++ b/vibe/cli/textual_ui/app.py @@ -1357,8 +1357,8 @@ class VibeApp(App): # noqa: PLR0904 teleport_msg.set_status("Syncing with remote...") case TeleportStartingWorkflowEvent(): teleport_msg.set_status("Teleporting...") - case TeleportWaitingForGitHubEvent(): - teleport_msg.set_status("Connecting to GitHub...") + case TeleportWaitingForGitHubEvent(message=msg): + teleport_msg.set_status(msg or "Connecting to GitHub...") case TeleportAuthRequiredEvent(oauth_url=url, message=msg): webbrowser.open(url) teleport_msg.set_status(msg or "Authorizing GitHub...") diff --git a/vibe/core/config/__init__.py b/vibe/core/config/__init__.py index 7877bdf..9861059 100644 --- a/vibe/core/config/__init__.py +++ b/vibe/core/config/__init__.py @@ -49,6 +49,9 @@ from vibe.core.config.patch import ( SetField, ) from vibe.core.config.schema import ( + ConfigDefinitionError, + ConfigFragment, + ConfigSchema, DuplicateMergeMetadataError, MergeFieldMetadata, WithConcatMerge, @@ -68,8 +71,11 @@ __all__ = [ "DEFAULT_TTS_PROVIDERS", "THINKING_LEVELS", "AppendToList", + "ConfigDefinitionError", + "ConfigFragment", "ConfigLayer", "ConfigLayerError", + "ConfigSchema", "ConnectorConfig", "DeleteField", "DuplicateMergeMetadataError", diff --git a/vibe/core/config/schema.py b/vibe/core/config/schema.py index 185acf0..10479d0 100644 --- a/vibe/core/config/schema.py +++ b/vibe/core/config/schema.py @@ -2,6 +2,7 @@ from __future__ import annotations from dataclasses import dataclass, field +from pydantic import BaseModel from pydantic.fields import FieldInfo from vibe.core.utils.merge import MergeStrategy @@ -11,6 +12,61 @@ class DuplicateMergeMetadataError(TypeError): """Raised when a field declares more than one MergeFieldMetadata marker.""" +class ConfigDefinitionError(TypeError): + """Raised when a config schema or fragment is declared with invalid fields.""" + + +class ConfigSchema(BaseModel): + """Base for composite config schemas composed of fragments and merge-aware fields.""" + + @classmethod + def __pydantic_on_complete__(cls) -> None: + super().__pydantic_on_complete__() + + if cls.__name__ == "ConfigSchema" and cls.__module__ == __name__: + return + + for field_name, field_info in cls.model_fields.items(): + has_merge_metadata = MergeFieldMetadata.from_field(field_info) is not None + is_fragment = isinstance(field_info.annotation, type) and issubclass( + field_info.annotation, ConfigFragment + ) + + if is_fragment and has_merge_metadata: + raise ConfigDefinitionError( + f"{cls.__name__}.{field_name} is a ConfigFragment field and " + "must not declare merge metadata" + ) + + if is_fragment or has_merge_metadata: + continue + + raise ConfigDefinitionError( + f"{cls.__name__}.{field_name} must declare merge metadata or use a " + "ConfigFragment subclass" + ) + + +class ConfigFragment(BaseModel): + """Base for domain config groups with merge-aware top-level fields.""" + + @classmethod + def __pydantic_on_complete__(cls) -> None: + super().__pydantic_on_complete__() + + if cls.__name__ == "ConfigFragment" and cls.__module__ == __name__: + return + + for field_name, field_info in cls.model_fields.items(): + if MergeFieldMetadata.from_field(field_info) is not None: + continue + + raise ConfigDefinitionError( + f"{cls.__name__}.{field_name} must declare merge metadata via " + "MergeFieldMetadata" + ) + + @dataclass(frozen=True) class MergeFieldMetadata: """Base Pydantic Annotated marker that declares how a config field merges across layers. diff --git a/vibe/core/output_formatters.py b/vibe/core/output_formatters.py index c91a407..737cac7 100644 --- a/vibe/core/output_formatters.py +++ b/vibe/core/output_formatters.py @@ -62,8 +62,8 @@ class TextOutputFormatter(OutputFormatter): self._print("Syncing with remote...") case TeleportStartingWorkflowEvent(): self._print("Teleporting...") - case TeleportWaitingForGitHubEvent(): - self._print("Connecting to GitHub...") + case TeleportWaitingForGitHubEvent(message=msg): + self._print(msg or "Connecting to GitHub...") case TeleportAuthRequiredEvent(oauth_url=url, message=msg): self._print(msg or f"Open to authorize GitHub: {url}") case TeleportAuthCompleteEvent(): diff --git a/vibe/core/teleport/nuage.py b/vibe/core/teleport/nuage.py index fa4eea8..c5fb219 100644 --- a/vibe/core/teleport/nuage.py +++ b/vibe/core/teleport/nuage.py @@ -1,6 +1,7 @@ from __future__ import annotations import asyncio +from collections.abc import AsyncGenerator from enum import StrEnum, auto import time import types @@ -184,12 +185,13 @@ class NuageClient: async def wait_for_github_connection( self, execution_id: str, timeout: float = 600.0, interval: float = 2.0 - ) -> GitHubPublicData: + ) -> AsyncGenerator[GitHubPublicData, None]: deadline = time.monotonic() + timeout while time.monotonic() < deadline: github_data = await self.get_github_integration(execution_id) + yield github_data if github_data.connected: - return github_data + return if github_data.is_error: raise ServiceTeleportError( github_data.error diff --git a/vibe/core/teleport/teleport.py b/vibe/core/teleport/teleport.py index 5dffaa1..165092d 100644 --- a/vibe/core/teleport/teleport.py +++ b/vibe/core/teleport/teleport.py @@ -184,15 +184,22 @@ class TeleportService: ) yield TeleportWaitingForGitHubEvent() - github_data = await self._nuage_client.get_github_integration(execution_id) - if not github_data.connected: - if github_data.oauth_url: + auth_event_sent = False + async for github_data in self._nuage_client.wait_for_github_connection( + execution_id + ): + if github_data.connected: + break + if not auth_event_sent and github_data.oauth_url: yield TeleportAuthRequiredEvent( oauth_url=github_data.oauth_url, message=github_data.error ) - await self._nuage_client.wait_for_github_connection(execution_id) - yield TeleportAuthCompleteEvent() + auth_event_sent = True + if github_data.error: + yield TeleportWaitingForGitHubEvent(message=github_data.error) + + yield TeleportAuthCompleteEvent() yield TeleportFetchingUrlEvent() chat_url = await self._nuage_client.get_chat_assistant_url(execution_id) diff --git a/vibe/core/teleport/types.py b/vibe/core/teleport/types.py index 28e9b13..1c41ea0 100644 --- a/vibe/core/teleport/types.py +++ b/vibe/core/teleport/types.py @@ -34,7 +34,7 @@ class TeleportPushingEvent(BaseEvent): class TeleportWaitingForGitHubEvent(BaseEvent): - pass + message: str | None = None class TeleportFetchingUrlEvent(BaseEvent):