v2.9.5 (#676)
Co-authored-by: Clément Drouin <clement.drouin@mistral.ai> Co-authored-by: Kim-Adeline Miguel <51720070+kimadeline@users.noreply.github.com> Co-authored-by: Mathias Gesbert <mathias.gesbert@mistral.ai> Co-authored-by: allansimon-mistral <allan.simon@ext.mistral.ai> Co-authored-by: Mistral Vibe <vibe@mistral.ai>
|
|
@ -101,6 +101,11 @@ def _build_env(vibe_home_dir: Path, *, include_api_key: bool) -> dict[str, str]:
|
|||
env["PYTHONUNBUFFERED"] = "1"
|
||||
env["VIBE_HOME"] = str(vibe_home_dir)
|
||||
|
||||
vibe_home_dir.mkdir(parents=True, exist_ok=True)
|
||||
config_file = vibe_home_dir / "config.toml"
|
||||
if not config_file.exists():
|
||||
config_file.write_text("enable_telemetry = false\n")
|
||||
|
||||
if include_api_key:
|
||||
env["MISTRAL_API_KEY"] = "mock"
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ from vibe.acp.acp_agent_loop import VibeAcpAgentLoop
|
|||
class TestCloseSession:
|
||||
@pytest.mark.asyncio
|
||||
async def test_close_session_removes_session_and_closes_resources(
|
||||
self, acp_agent_loop: VibeAcpAgentLoop
|
||||
self, acp_agent_loop: VibeAcpAgentLoop, telemetry_events: list[dict[str, Any]]
|
||||
) -> None:
|
||||
session_response = await acp_agent_loop.new_session(cwd=".", mcp_servers=[])
|
||||
session = acp_agent_loop.sessions[session_response.session_id]
|
||||
|
|
@ -30,6 +30,17 @@ class TestCloseSession:
|
|||
assert session_response.session_id not in acp_agent_loop.sessions
|
||||
backend_close.assert_awaited_once()
|
||||
telemetry_close.assert_awaited_once()
|
||||
session_closed_events = [
|
||||
event
|
||||
for event in telemetry_events
|
||||
if event["event_name"] == "vibe.session_closed"
|
||||
]
|
||||
assert len(session_closed_events) == 1
|
||||
assert (
|
||||
session_closed_events[0]["properties"]["session_id"]
|
||||
== session_response.session_id
|
||||
)
|
||||
assert session_closed_events[0]["properties"]["agent_entrypoint"] == "acp"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_close_session_cancels_active_prompt(
|
||||
|
|
@ -69,6 +80,23 @@ class TestCloseSession:
|
|||
|
||||
assert bg_task.cancelled()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_emit_session_closed_for_active_sessions(
|
||||
self, acp_agent_loop: VibeAcpAgentLoop, telemetry_events: list[dict[str, Any]]
|
||||
) -> None:
|
||||
session1 = await acp_agent_loop.new_session(cwd=".", mcp_servers=[])
|
||||
session2 = await acp_agent_loop.new_session(cwd=".", mcp_servers=[])
|
||||
|
||||
await acp_agent_loop.emit_session_closed_for_active_sessions()
|
||||
|
||||
session_closed_events = [
|
||||
e for e in telemetry_events if e["event_name"] == "vibe.session_closed"
|
||||
]
|
||||
emitted_ids = {e["properties"]["session_id"] for e in session_closed_events}
|
||||
assert len(session_closed_events) == 2
|
||||
assert session1.session_id in emitted_ids
|
||||
assert session2.session_id in emitted_ids
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_close_session_rejects_new_background_tasks(
|
||||
self, acp_agent_loop: VibeAcpAgentLoop
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ from tests.stubs.fake_backend import FakeBackend
|
|||
from tests.stubs.fake_client import FakeClient
|
||||
from vibe.acp.acp_agent_loop import VibeAcpAgentLoop
|
||||
from vibe.core.agent_loop import AgentLoop
|
||||
from vibe.core.session.session_id import shorten_session_id
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
|
@ -79,3 +80,10 @@ class TestCompactEventHandling:
|
|||
assert compact_end.status == "completed"
|
||||
|
||||
assert compact_start.tool_call_id == compact_end.tool_call_id
|
||||
assert compact_end.content is not None
|
||||
compact_end_text = compact_end.content[0].content
|
||||
assert isinstance(compact_end_text, TextContentBlock)
|
||||
assert shorten_session_id(session_response.session_id) in compact_end_text.text
|
||||
assert (
|
||||
shorten_session_id(session.agent_loop.session_id) in compact_end_text.text
|
||||
)
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ class TestACPInitialize:
|
|||
),
|
||||
)
|
||||
assert response.agent_info == Implementation(
|
||||
name="@mistralai/mistral-vibe", title="Mistral Vibe", version="2.9.4"
|
||||
name="@mistralai/mistral-vibe", title="Mistral Vibe", version="2.9.5"
|
||||
)
|
||||
|
||||
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.4"
|
||||
name="@mistralai/mistral-vibe", title="Mistral Vibe", version="2.9.5"
|
||||
)
|
||||
|
||||
assert response.auth_methods is not None
|
||||
|
|
|
|||
|
|
@ -92,20 +92,26 @@ class TestMultiSessionCore:
|
|||
)
|
||||
assert user_message1 is not None
|
||||
assert user_message1.content == "Prompt for session 1"
|
||||
assistant_message1 = next(
|
||||
(msg for msg in session1.agent_loop.messages if msg.role == Role.assistant),
|
||||
None,
|
||||
)
|
||||
assert assistant_message1 is not None
|
||||
assert assistant_message1.content == "Response 1"
|
||||
user_message2 = next(
|
||||
(msg for msg in session2.agent_loop.messages if msg.role == Role.user), None
|
||||
)
|
||||
assert user_message2 is not None
|
||||
assert user_message2.content == "Prompt for session 2"
|
||||
|
||||
# Backend stream order is non-deterministic under asyncio.gather, so
|
||||
# assert that both sessions received distinct responses from the
|
||||
# expected set rather than pinning a specific assignment.
|
||||
assistant_message1 = next(
|
||||
(msg for msg in session1.agent_loop.messages if msg.role == Role.assistant),
|
||||
None,
|
||||
)
|
||||
assistant_message2 = next(
|
||||
(msg for msg in session2.agent_loop.messages if msg.role == Role.assistant),
|
||||
None,
|
||||
)
|
||||
assert assistant_message1 is not None
|
||||
assert assistant_message2 is not None
|
||||
assert assistant_message2.content == "Response 2"
|
||||
assert {assistant_message1.content, assistant_message2.content} == {
|
||||
"Response 1",
|
||||
"Response 2",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -199,6 +199,28 @@ def test_callable_entries_updates_completions_dynamically() -> None:
|
|||
assert suggestions[0].description == "Summarize the conversation"
|
||||
|
||||
|
||||
def test_tab_on_slash_command_with_args_replaces_only_head() -> None:
|
||||
controller, view = make_controller()
|
||||
text = "/compact some args"
|
||||
controller.on_text_changed(text, cursor_index=len(text))
|
||||
|
||||
result = controller.on_key(key_event("tab"), text=text, cursor_index=len(text))
|
||||
|
||||
assert result is CompletionResult.HANDLED
|
||||
assert view.replacements == [Replacement(0, 8, "/compact")]
|
||||
|
||||
|
||||
def test_enter_on_slash_command_with_args_submits_with_head_only_replacement() -> None:
|
||||
controller, view = make_controller()
|
||||
text = "/compact some args"
|
||||
controller.on_text_changed(text, cursor_index=len(text))
|
||||
|
||||
result = controller.on_key(key_event("enter"), text=text, cursor_index=len(text))
|
||||
|
||||
assert result is CompletionResult.SUBMIT
|
||||
assert view.replacements == [Replacement(0, 8, "/compact")]
|
||||
|
||||
|
||||
def test_callable_entries_reflects_enabled_disabled_skills() -> None:
|
||||
"""Test that skill enable/disable changes are reflected in completions.
|
||||
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ async def test_popup_hides_when_input_cleared(vibe_app: VibeApp) -> None:
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_pressing_tab_writes_selected_command_and_hides_popup(
|
||||
async def test_pressing_tab_writes_selected_command_and_leaves_popup_visible(
|
||||
vibe_app: VibeApp,
|
||||
) -> None:
|
||||
async with vibe_app.run_test() as pilot:
|
||||
|
|
@ -53,7 +53,7 @@ async def test_pressing_tab_writes_selected_command_and_hides_popup(
|
|||
await pilot.press("tab")
|
||||
|
||||
assert chat_input.value == "/config"
|
||||
assert popup.styles.display == "none"
|
||||
assert popup.styles.display == "block"
|
||||
|
||||
|
||||
def ensure_selected_command(popup: CompletionPopup, expected_alias: str) -> None:
|
||||
|
|
|
|||
|
|
@ -133,3 +133,13 @@ class TestCommandRegistry:
|
|||
assert result is not None
|
||||
_, cmd, _ = result
|
||||
assert cmd.handler == "_show_data_retention"
|
||||
|
||||
def test_loop_command_registration(self) -> None:
|
||||
registry = CommandRegistry()
|
||||
assert registry.get_command_name("/loop") == "loop"
|
||||
result = registry.parse_command("/loop 30s ping")
|
||||
assert result is not None
|
||||
cmd_name, cmd, cmd_args = result
|
||||
assert cmd_name == "loop"
|
||||
assert cmd.handler == "_loop_command"
|
||||
assert cmd_args == "30s ping"
|
||||
|
|
|
|||
28
tests/cli/test_compact_message.py
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from vibe.cli.textual_ui.widgets.compact import CompactMessage
|
||||
from vibe.core.session.session_id import shorten_session_id
|
||||
|
||||
|
||||
class TestCompactMessage:
|
||||
def test_get_content_includes_session_ids_after_compaction(self) -> None:
|
||||
message = CompactMessage()
|
||||
message.post_message = MagicMock()
|
||||
|
||||
message.set_complete(
|
||||
old_tokens=177_017,
|
||||
new_tokens=23_263,
|
||||
old_session_id="11111111-1111-1111-1111-111111111111",
|
||||
new_session_id="22222222-2222-2222-2222-222222222222",
|
||||
)
|
||||
|
||||
assert message.get_content() == (
|
||||
"Compaction complete: 177,017 → 23,263 tokens (-87.%)\n"
|
||||
"session: "
|
||||
f"{shorten_session_id('11111111-1111-1111-1111-111111111111')} "
|
||||
"(before compaction) → "
|
||||
f"{shorten_session_id('22222222-2222-2222-2222-222222222222')} "
|
||||
"(after compaction)"
|
||||
)
|
||||
55
tests/cli/test_initial_agent_name.py
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
|
||||
from vibe.cli.cli import get_initial_agent_name
|
||||
from vibe.core.agents.models import BuiltinAgentName
|
||||
from vibe.core.config import VibeConfig
|
||||
|
||||
|
||||
def _make_args(*, agent: str | None, prompt: str | None) -> argparse.Namespace:
|
||||
return argparse.Namespace(agent=agent, prompt=prompt)
|
||||
|
||||
|
||||
def test_uses_args_agent_when_provided() -> None:
|
||||
config = VibeConfig.model_construct(default_agent=BuiltinAgentName.PLAN)
|
||||
args = _make_args(agent="accept-edits", prompt=None)
|
||||
|
||||
assert get_initial_agent_name(args, config) == "accept-edits"
|
||||
|
||||
|
||||
def test_falls_back_to_config_default_agent_when_args_agent_is_none() -> None:
|
||||
config = VibeConfig.model_construct(default_agent=BuiltinAgentName.PLAN)
|
||||
args = _make_args(agent=None, prompt=None)
|
||||
|
||||
assert get_initial_agent_name(args, config) == BuiltinAgentName.PLAN
|
||||
|
||||
|
||||
def test_defaults_to_default_when_unset_in_config_and_args() -> None:
|
||||
config = VibeConfig.model_construct()
|
||||
args = _make_args(agent=None, prompt=None)
|
||||
|
||||
assert get_initial_agent_name(args, config) == BuiltinAgentName.DEFAULT
|
||||
|
||||
|
||||
def test_programmatic_mode_promotes_default_to_auto_approve() -> None:
|
||||
config = VibeConfig.model_construct(default_agent=BuiltinAgentName.DEFAULT)
|
||||
args = _make_args(agent=None, prompt="hello")
|
||||
|
||||
assert get_initial_agent_name(args, config) == BuiltinAgentName.AUTO_APPROVE
|
||||
|
||||
|
||||
def test_programmatic_mode_ignores_config_default_agent_when_args_agent_is_none() -> (
|
||||
None
|
||||
):
|
||||
config = VibeConfig.model_construct(default_agent=BuiltinAgentName.PLAN)
|
||||
args = _make_args(agent=None, prompt="hello")
|
||||
|
||||
assert get_initial_agent_name(args, config) == BuiltinAgentName.AUTO_APPROVE
|
||||
|
||||
|
||||
def test_programmatic_mode_keeps_explicit_agent_arg() -> None:
|
||||
config = VibeConfig.model_construct(default_agent=BuiltinAgentName.DEFAULT)
|
||||
args = _make_args(agent="accept-edits", prompt="hello")
|
||||
|
||||
assert get_initial_agent_name(args, config) == "accept-edits"
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
from typing import Any
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
import pytest
|
||||
|
|
@ -10,7 +11,7 @@ from tests.conftest import build_test_vibe_app, build_test_vibe_config
|
|||
from vibe.cli.plan_offer.ports.whoami_gateway import WhoAmIPlanType, WhoAmIResponse
|
||||
from vibe.cli.textual_ui.widgets.chat_input import ChatInputContainer
|
||||
from vibe.core.config import ModelConfig, ProviderConfig, VibeConfig
|
||||
from vibe.core.types import Backend
|
||||
from vibe.core.types import Backend, LLMMessage, Role
|
||||
|
||||
|
||||
def _chat_plan_gateway(*, prompt_switching_to_pro_plan: bool) -> FakeWhoAmIGateway:
|
||||
|
|
@ -36,6 +37,16 @@ async def _wait_until(pause, predicate, timeout: float = 2.0) -> None:
|
|||
raise AssertionError("Condition was not met within the timeout")
|
||||
|
||||
|
||||
def _teleport_failed_events(
|
||||
telemetry_events: list[dict[str, Any]],
|
||||
) -> list[dict[str, Any]]:
|
||||
return [
|
||||
event
|
||||
for event in telemetry_events
|
||||
if event["event_name"] == "vibe.teleport_failed"
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_teleport_command_visible_for_paid_chat_users() -> None:
|
||||
app = build_test_vibe_app(
|
||||
|
|
@ -56,6 +67,81 @@ async def test_teleport_command_visible_for_paid_chat_users() -> None:
|
|||
assert "&" in input_widget.mode_characters
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_teleport_command_without_history_sends_early_failure_telemetry(
|
||||
telemetry_events: list[dict[str, Any]],
|
||||
) -> None:
|
||||
app = build_test_vibe_app(
|
||||
config=_vibe_code_enabled_config(),
|
||||
plan_offer_gateway=_chat_plan_gateway(prompt_switching_to_pro_plan=False),
|
||||
)
|
||||
|
||||
async with app.run_test() as pilot:
|
||||
await _wait_until(
|
||||
pilot.pause,
|
||||
lambda: app.commands.get_command_name("/teleport") == "teleport",
|
||||
)
|
||||
|
||||
await app.on_chat_input_container_submitted(
|
||||
ChatInputContainer.Submitted("/teleport")
|
||||
)
|
||||
|
||||
assert _teleport_failed_events(telemetry_events) == [
|
||||
{
|
||||
"event_name": "vibe.teleport_failed",
|
||||
"properties": {
|
||||
"stage": "no_history",
|
||||
"error_class": "TeleportNoHistoryError",
|
||||
"push_required": False,
|
||||
"github_auth_required": False,
|
||||
"nb_session_messages": 0,
|
||||
"session_id": app.agent_loop.session_id,
|
||||
},
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_teleport_command_in_remote_session_sends_early_failure_telemetry(
|
||||
telemetry_events: list[dict[str, Any]],
|
||||
) -> None:
|
||||
app = build_test_vibe_app(
|
||||
config=_vibe_code_enabled_config(),
|
||||
plan_offer_gateway=_chat_plan_gateway(prompt_switching_to_pro_plan=False),
|
||||
)
|
||||
app.agent_loop.messages.append(LLMMessage(role=Role.user, content="hello"))
|
||||
app.agent_loop.messages.append(LLMMessage(role=Role.assistant, content="hi"))
|
||||
|
||||
async with app.run_test() as pilot:
|
||||
await _wait_until(
|
||||
pilot.pause,
|
||||
lambda: app.commands.get_command_name("/teleport") == "teleport",
|
||||
)
|
||||
|
||||
await app._remote_manager.attach(session_id="remote-session", config=app.config)
|
||||
await app.on_chat_input_container_submitted(
|
||||
ChatInputContainer.Submitted("/teleport")
|
||||
)
|
||||
await _wait_until(
|
||||
pilot.pause, lambda: len(_teleport_failed_events(telemetry_events)) == 1
|
||||
)
|
||||
await app._remote_manager.detach()
|
||||
|
||||
assert _teleport_failed_events(telemetry_events) == [
|
||||
{
|
||||
"event_name": "vibe.teleport_failed",
|
||||
"properties": {
|
||||
"stage": "remote_session",
|
||||
"error_class": "TeleportRemoteSessionError",
|
||||
"push_required": False,
|
||||
"github_auth_required": False,
|
||||
"nb_session_messages": 2,
|
||||
"session_id": app.agent_loop.session_id,
|
||||
},
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_teleport_command_hidden_when_current_key_is_not_eligible() -> None:
|
||||
app = build_test_vibe_app(
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ def get_base_config() -> dict[str, Any]:
|
|||
}
|
||||
],
|
||||
"enable_auto_update": False,
|
||||
"enable_telemetry": False,
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -174,7 +175,8 @@ def telemetry_events(monkeypatch: pytest.MonkeyPatch) -> list[dict[str, Any]]:
|
|||
*,
|
||||
correlation_id: str | None = None,
|
||||
) -> None:
|
||||
event: dict[str, Any] = {"event_name": event_name, "properties": properties}
|
||||
merged = self.build_client_event_metadata() | properties
|
||||
event: dict[str, Any] = {"event_name": event_name, "properties": merged}
|
||||
if correlation_id is not None:
|
||||
event["correlation_id"] = correlation_id
|
||||
events.append(event)
|
||||
|
|
|
|||
|
|
@ -104,3 +104,19 @@ class TestAgentManager:
|
|||
)
|
||||
manager = AgentManager(lambda: config, initial_agent="plan")
|
||||
assert manager.active_profile.name == "plan"
|
||||
|
||||
def test_initial_agent_raises_when_agent_is_disabled(self) -> None:
|
||||
config = build_test_vibe_config(
|
||||
include_project_context=False,
|
||||
include_prompt_detail=False,
|
||||
disabled_agents=["plan"],
|
||||
)
|
||||
with pytest.raises(ValueError, match="not available"):
|
||||
AgentManager(lambda: config, initial_agent="plan")
|
||||
|
||||
def test_initial_agent_raises_when_agent_does_not_exist(self) -> None:
|
||||
config = build_test_vibe_config(
|
||||
include_project_context=False, include_prompt_detail=False
|
||||
)
|
||||
with pytest.raises(ValueError, match="not found"):
|
||||
AgentManager(lambda: config, initial_agent="nonexistent-agent")
|
||||
|
|
|
|||
304
tests/core/test_loop.py
Normal file
|
|
@ -0,0 +1,304 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
from typing import cast
|
||||
|
||||
import pytest
|
||||
|
||||
from vibe.core.loop import (
|
||||
MAX_LOOPS_PER_SESSION,
|
||||
MIN_INTERVAL_SECONDS,
|
||||
LoopError,
|
||||
LoopErrorResult,
|
||||
LoopListResult,
|
||||
LoopManager,
|
||||
LoopOkResult,
|
||||
ScheduledLoop,
|
||||
format_duration,
|
||||
parse_interval,
|
||||
)
|
||||
from vibe.core.session.session_logger import SessionLogger
|
||||
|
||||
|
||||
class FakeMetadata:
|
||||
def __init__(self) -> None:
|
||||
self.loops: list[ScheduledLoop] = []
|
||||
|
||||
|
||||
class FakeSessionLogger:
|
||||
def __init__(self) -> None:
|
||||
self.session_metadata = FakeMetadata()
|
||||
self.persisted: list[list[ScheduledLoop]] = []
|
||||
|
||||
async def persist_loops(self) -> None:
|
||||
self.persisted.append([*self.session_metadata.loops])
|
||||
|
||||
|
||||
class RaisingSessionLogger:
|
||||
def __init__(self) -> None:
|
||||
self.session_metadata = FakeMetadata()
|
||||
|
||||
async def persist_loops(self) -> None:
|
||||
raise RuntimeError("disk on fire")
|
||||
|
||||
|
||||
def _build_manager() -> tuple[LoopManager, FakeSessionLogger]:
|
||||
fake = FakeSessionLogger()
|
||||
return LoopManager(cast(SessionLogger, fake)), fake
|
||||
|
||||
|
||||
class TestParseInterval:
|
||||
def test_parses_supported_units(self) -> None:
|
||||
assert parse_interval("30s") == 30
|
||||
assert parse_interval("5m") == 300
|
||||
assert parse_interval("2h") == 7200
|
||||
assert parse_interval("1d") == 86400
|
||||
|
||||
def test_parses_case_insensitively(self) -> None:
|
||||
assert parse_interval("30S") == 30
|
||||
assert parse_interval("2H") == 7200
|
||||
|
||||
def test_strips_whitespace(self) -> None:
|
||||
assert parse_interval(" 30s ") == 30
|
||||
|
||||
@pytest.mark.parametrize("bad", ["", "5", "5x", "-5m", "5 m", "1.5m", "abc"])
|
||||
def test_rejects_invalid_inputs(self, bad: str) -> None:
|
||||
with pytest.raises(LoopError):
|
||||
parse_interval(bad)
|
||||
|
||||
def test_rejects_below_minimum(self) -> None:
|
||||
with pytest.raises(LoopError, match=f"at least {MIN_INTERVAL_SECONDS}"):
|
||||
parse_interval("29s")
|
||||
|
||||
|
||||
class TestFormatInterval:
|
||||
@pytest.mark.parametrize("text", ["30s", "5m", "2h", "1d"])
|
||||
def test_round_trip_with_parse(self, text: str) -> None:
|
||||
assert format_duration(parse_interval(text)) == text
|
||||
|
||||
def test_formats_non_round_values(self) -> None:
|
||||
assert format_duration(90) == "1m30s"
|
||||
assert format_duration(180) == "3m"
|
||||
assert format_duration(7200) == "2h"
|
||||
assert format_duration(259200) == "3d"
|
||||
|
||||
def test_formats_short_version(self) -> None:
|
||||
assert format_duration(90, short=True) == "1m"
|
||||
assert format_duration(180, short=True) == "3m"
|
||||
assert format_duration(190, short=True) == "3m"
|
||||
assert format_duration(8000, short=True) == "2h"
|
||||
assert format_duration(260000, short=True) == "3d"
|
||||
|
||||
|
||||
class TestLoopManagerHandleCommand:
|
||||
@pytest.mark.asyncio
|
||||
async def test_empty_returns_loops_list(self) -> None:
|
||||
manager, fake = _build_manager()
|
||||
result = await manager.handle_command("")
|
||||
assert isinstance(result, LoopListResult)
|
||||
assert result.loops == []
|
||||
assert fake.persisted == []
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize("verb", ["list", "ls"])
|
||||
async def test_list_alias_returns_loops_list(self, verb: str) -> None:
|
||||
manager, fake = _build_manager()
|
||||
result = await manager.handle_command(verb)
|
||||
assert isinstance(result, LoopListResult)
|
||||
assert result.loops == []
|
||||
assert fake.persisted == []
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_success_persists_and_returns_id(self) -> None:
|
||||
manager, fake = _build_manager()
|
||||
result = await manager.handle_command("1m hello world")
|
||||
assert isinstance(result, LoopOkResult)
|
||||
assert "every 1m" in result.message
|
||||
assert "hello world" in result.message
|
||||
assert len(manager.loops) == 1
|
||||
loop = manager.loops[0]
|
||||
assert loop.interval_seconds == 60
|
||||
assert loop.prompt == "hello world"
|
||||
assert result.message.count(loop.id) >= 1
|
||||
assert len(fake.persisted) == 1
|
||||
assert fake.persisted[0][0].id == loop.id
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_bad_interval_returns_error_no_persist(self) -> None:
|
||||
manager, fake = _build_manager()
|
||||
result = await manager.handle_command("nope hello")
|
||||
assert isinstance(result, LoopErrorResult)
|
||||
assert "Invalid interval" in result.message
|
||||
assert "Usage:" not in result.message
|
||||
assert manager.loops == []
|
||||
assert fake.persisted == []
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_empty_prompt_returns_error_no_persist(self) -> None:
|
||||
manager, fake = _build_manager()
|
||||
result = await manager.handle_command("30s ")
|
||||
assert isinstance(result, LoopErrorResult)
|
||||
assert "Missing prompt" in result.message
|
||||
assert manager.loops == []
|
||||
assert fake.persisted == []
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_prompt_starting_with_slash_returns_error(self) -> None:
|
||||
manager, fake = _build_manager()
|
||||
result = await manager.handle_command("30s /config")
|
||||
assert isinstance(result, LoopErrorResult)
|
||||
assert "cannot start with '/'" in result.message
|
||||
assert manager.loops == []
|
||||
assert fake.persisted == []
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_over_limit_returns_error(self) -> None:
|
||||
manager, _ = _build_manager()
|
||||
for i in range(MAX_LOOPS_PER_SESSION):
|
||||
result = await manager.handle_command(f"30s prompt {i}")
|
||||
assert isinstance(result, LoopOkResult)
|
||||
result = await manager.handle_command("30s overflow")
|
||||
assert isinstance(result, LoopErrorResult)
|
||||
assert "limit" in result.message.lower()
|
||||
assert len(manager.loops) == MAX_LOOPS_PER_SESSION
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_cancel_missing_target_is_error_no_persist(self) -> None:
|
||||
manager, fake = _build_manager()
|
||||
result = await manager.handle_command("cancel")
|
||||
assert isinstance(result, LoopErrorResult)
|
||||
assert "Missing loop id" in result.message
|
||||
assert fake.persisted == []
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_cancel_unknown_id_is_error_no_persist(self) -> None:
|
||||
manager, fake = _build_manager()
|
||||
await manager.handle_command("30s ping")
|
||||
fake.persisted.clear()
|
||||
result = await manager.handle_command("cancel deadbeef")
|
||||
assert isinstance(result, LoopErrorResult)
|
||||
assert "deadbeef" in result.message
|
||||
assert fake.persisted == []
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_cancel_known_id_persists(self) -> None:
|
||||
manager, fake = _build_manager()
|
||||
await manager.handle_command("30s ping")
|
||||
loop_id = manager.loops[0].id
|
||||
fake.persisted.clear()
|
||||
result = await manager.handle_command(f"cancel {loop_id}")
|
||||
assert isinstance(result, LoopOkResult)
|
||||
assert loop_id in result.message
|
||||
assert "ping" in result.message
|
||||
assert manager.loops == []
|
||||
assert fake.persisted == [[]]
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_cancel_all_persists(self) -> None:
|
||||
manager, fake = _build_manager()
|
||||
await manager.handle_command("30s a")
|
||||
await manager.handle_command("30s b")
|
||||
fake.persisted.clear()
|
||||
result = await manager.handle_command("cancel all")
|
||||
assert isinstance(result, LoopOkResult)
|
||||
assert "2 scheduled loop(s)" in result.message
|
||||
assert manager.loops == []
|
||||
assert fake.persisted == [[]]
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize("verb", ["rm", "stop", "delete"])
|
||||
async def test_cancel_alias_verbs_work(self, verb: str) -> None:
|
||||
manager, _ = _build_manager()
|
||||
await manager.handle_command("30s ping")
|
||||
loop_id = manager.loops[0].id
|
||||
result = await manager.handle_command(f"{verb} {loop_id}")
|
||||
assert isinstance(result, LoopOkResult)
|
||||
assert manager.loops == []
|
||||
|
||||
|
||||
class TestLoopManagerPopDue:
|
||||
@pytest.mark.asyncio
|
||||
async def test_returns_none_when_empty(self) -> None:
|
||||
manager, fake = _build_manager()
|
||||
assert await manager.pop_due() is None
|
||||
assert fake.persisted == []
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_returns_none_when_nothing_due(self) -> None:
|
||||
manager, fake = _build_manager()
|
||||
await manager.handle_command("30s ping")
|
||||
fake.persisted.clear()
|
||||
result = await manager.pop_due(now=0.0)
|
||||
assert result is None
|
||||
assert fake.persisted == []
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_returns_due_loop_advances_and_persists(self) -> None:
|
||||
manager, fake = _build_manager()
|
||||
await manager.handle_command("30s ping")
|
||||
fake.persisted.clear()
|
||||
loop = manager.loops[0]
|
||||
original_next = loop.next_fire_at
|
||||
due = await manager.pop_due(now=original_next + 100.0)
|
||||
assert due is not None
|
||||
assert due.id == loop.id
|
||||
assert manager.loops[0].next_fire_at == original_next + 100.0 + 30.0
|
||||
assert len(fake.persisted) == 1
|
||||
|
||||
|
||||
class TestLoopManagerNextDueIn:
|
||||
def test_inf_when_empty(self) -> None:
|
||||
manager, _ = _build_manager()
|
||||
assert manager.next_due_in() == math.inf
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_correct_delta_when_populated(self) -> None:
|
||||
manager, _ = _build_manager()
|
||||
await manager.handle_command("30s ping")
|
||||
loop = manager.loops[0]
|
||||
delta = manager.next_due_in(now=loop.next_fire_at - 7.0)
|
||||
assert delta == pytest.approx(7.0)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_zero_when_overdue(self) -> None:
|
||||
manager, _ = _build_manager()
|
||||
await manager.handle_command("30s ping")
|
||||
loop = manager.loops[0]
|
||||
delta = manager.next_due_in(now=loop.next_fire_at + 100.0)
|
||||
assert delta == 0.0
|
||||
|
||||
|
||||
class TestLoopManagerRestore:
|
||||
def test_replaces_in_memory_list_without_persist(self) -> None:
|
||||
manager, fake = _build_manager()
|
||||
loops = [
|
||||
ScheduledLoop(
|
||||
id="aabbccdd",
|
||||
interval_seconds=30,
|
||||
prompt="x",
|
||||
next_fire_at=1.0,
|
||||
created_at=0.0,
|
||||
),
|
||||
ScheduledLoop(
|
||||
id="11223344",
|
||||
interval_seconds=60,
|
||||
prompt="y",
|
||||
next_fire_at=2.0,
|
||||
created_at=0.0,
|
||||
),
|
||||
]
|
||||
manager.restore(loops)
|
||||
assert [loop.id for loop in manager.loops] == ["aabbccdd", "11223344"]
|
||||
assert fake.persisted == []
|
||||
|
||||
|
||||
class TestLoopManagerPersisterErrors:
|
||||
@pytest.mark.asyncio
|
||||
async def test_persister_exception_does_not_propagate(self) -> None:
|
||||
manager = LoopManager(cast(SessionLogger, RaisingSessionLogger()))
|
||||
result = await manager.handle_command("30s ping")
|
||||
assert isinstance(result, LoopOkResult)
|
||||
assert len(manager.loops) == 1
|
||||
loop = manager.loops[0]
|
||||
due = await manager.pop_due(now=loop.next_fire_at + 1.0)
|
||||
assert due is not None
|
||||
|
|
@ -362,6 +362,48 @@ class TestTelemetryClient:
|
|||
assert telemetry_events[1]["properties"]["command"] == "my_skill"
|
||||
assert telemetry_events[1]["properties"]["command_type"] == "skill"
|
||||
|
||||
def test_send_teleport_completed_payload(
|
||||
self, telemetry_events: list[dict[str, Any]]
|
||||
) -> None:
|
||||
config = build_test_vibe_config(enable_telemetry=True)
|
||||
client = TelemetryClient(config_getter=lambda: config)
|
||||
|
||||
client.send_teleport_completed(
|
||||
push_required=True, github_auth_required=False, nb_session_messages=4
|
||||
)
|
||||
|
||||
assert len(telemetry_events) == 1
|
||||
assert telemetry_events[0]["event_name"] == "vibe.teleport_completed"
|
||||
assert telemetry_events[0]["properties"] == {
|
||||
"push_required": True,
|
||||
"github_auth_required": False,
|
||||
"nb_session_messages": 4,
|
||||
}
|
||||
|
||||
def test_send_teleport_failed_payload(
|
||||
self, telemetry_events: list[dict[str, Any]]
|
||||
) -> None:
|
||||
config = build_test_vibe_config(enable_telemetry=True)
|
||||
client = TelemetryClient(config_getter=lambda: config)
|
||||
|
||||
client.send_teleport_failed(
|
||||
stage="push",
|
||||
error_class="ServiceTeleportError",
|
||||
push_required=True,
|
||||
github_auth_required=False,
|
||||
nb_session_messages=4,
|
||||
)
|
||||
|
||||
assert len(telemetry_events) == 1
|
||||
assert telemetry_events[0]["event_name"] == "vibe.teleport_failed"
|
||||
assert telemetry_events[0]["properties"] == {
|
||||
"stage": "push",
|
||||
"error_class": "ServiceTeleportError",
|
||||
"push_required": True,
|
||||
"github_auth_required": False,
|
||||
"nb_session_messages": 4,
|
||||
}
|
||||
|
||||
def test_send_new_session_payload(
|
||||
self, telemetry_events: list[dict[str, Any]]
|
||||
) -> None:
|
||||
|
|
@ -393,6 +435,55 @@ class TestTelemetryClient:
|
|||
assert properties["terminal_emulator"] == "vscode"
|
||||
assert "version" in properties
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_send_session_closed_payload(
|
||||
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_active_provider().api_key_env_var
|
||||
monkeypatch.setenv(env_key, "sk-test")
|
||||
client = TelemetryClient(
|
||||
config_getter=lambda: config,
|
||||
session_id_getter=lambda: "current-session",
|
||||
parent_session_id_getter=lambda: "current-parent-session",
|
||||
entrypoint_metadata_getter=lambda: EntrypointMetadata(
|
||||
agent_entrypoint="cli",
|
||||
agent_version="1.0.0",
|
||||
client_name="vibe_cli",
|
||||
client_version="1.0.0",
|
||||
),
|
||||
)
|
||||
mock_post = AsyncMock(return_value=MagicMock(status_code=204))
|
||||
client._client = MagicMock()
|
||||
client._client.post = mock_post
|
||||
client._client.aclose = AsyncMock()
|
||||
|
||||
client.send_session_closed()
|
||||
await client.aclose()
|
||||
|
||||
mock_post.assert_called_once_with(
|
||||
"https://api.mistral.ai/v1/datalake/events",
|
||||
json={
|
||||
"event": "vibe.session_closed",
|
||||
"properties": {
|
||||
"agent_entrypoint": "cli",
|
||||
"agent_version": "1.0.0",
|
||||
"client_name": "vibe_cli",
|
||||
"client_version": "1.0.0",
|
||||
"session_id": "current-session",
|
||||
"parent_session_id": "current-parent-session",
|
||||
},
|
||||
},
|
||||
headers={
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": "Bearer sk-test",
|
||||
"User-Agent": get_user_agent(Backend.MISTRAL),
|
||||
},
|
||||
)
|
||||
|
||||
def test_build_base_metadata_includes_entrypoint_and_session(self) -> None:
|
||||
metadata = build_base_metadata(
|
||||
entrypoint_metadata=EntrypointMetadata(
|
||||
|
|
|
|||
231
tests/core/test_teleport_telemetry.py
Normal file
|
|
@ -0,0 +1,231 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from collections.abc import AsyncGenerator
|
||||
from typing import Any, cast
|
||||
|
||||
import pytest
|
||||
|
||||
from vibe.core.agent_loop import AgentLoop, TeleportError
|
||||
from vibe.core.teleport.errors import ServiceTeleportError
|
||||
from vibe.core.teleport.teleport import TeleportService
|
||||
from vibe.core.teleport.types import (
|
||||
TeleportAuthRequiredEvent,
|
||||
TeleportCheckingGitEvent,
|
||||
TeleportCompleteEvent,
|
||||
TeleportFetchingUrlEvent,
|
||||
TeleportPushingEvent,
|
||||
TeleportPushRequiredEvent,
|
||||
TeleportPushResponseEvent,
|
||||
TeleportStartingWorkflowEvent,
|
||||
TeleportWaitingForGitHubEvent,
|
||||
)
|
||||
from vibe.core.types import LLMMessage, Role
|
||||
|
||||
|
||||
def _set_teleport_service(agent_loop: AgentLoop, service: object) -> None:
|
||||
agent_loop._teleport_service = cast(TeleportService, service)
|
||||
|
||||
|
||||
class TestTeleportAgentLoopTelemetry:
|
||||
@pytest.mark.asyncio
|
||||
async def test_teleport_to_vibe_code_sends_completed_success(
|
||||
self, agent_loop: AgentLoop, telemetry_events: list[dict[str, Any]]
|
||||
) -> None:
|
||||
class FakeTeleportService:
|
||||
async def __aenter__(self) -> FakeTeleportService:
|
||||
return self
|
||||
|
||||
async def __aexit__(self, *_args: object) -> None:
|
||||
return None
|
||||
|
||||
async def execute(
|
||||
self, *_args: object, **_kwargs: object
|
||||
) -> AsyncGenerator[object, object]:
|
||||
yield TeleportCheckingGitEvent()
|
||||
yield TeleportPushRequiredEvent()
|
||||
yield TeleportPushingEvent()
|
||||
yield TeleportStartingWorkflowEvent()
|
||||
yield TeleportWaitingForGitHubEvent()
|
||||
yield TeleportAuthRequiredEvent(oauth_url="https://github.com/auth")
|
||||
yield TeleportFetchingUrlEvent()
|
||||
yield TeleportCompleteEvent(url="https://chat.example.com/123")
|
||||
|
||||
agent_loop.messages.append(LLMMessage(role=Role.user, content="hello"))
|
||||
_set_teleport_service(agent_loop, FakeTeleportService())
|
||||
|
||||
gen = agent_loop.teleport_to_vibe_code(None)
|
||||
response = None
|
||||
events = []
|
||||
while True:
|
||||
try:
|
||||
event = await gen.asend(response)
|
||||
except StopAsyncIteration:
|
||||
break
|
||||
events.append(event)
|
||||
response = (
|
||||
TeleportPushResponseEvent(approved=True)
|
||||
if isinstance(event, TeleportPushRequiredEvent)
|
||||
else None
|
||||
)
|
||||
|
||||
assert isinstance(events[-1], TeleportCompleteEvent)
|
||||
assert telemetry_events[-1]["event_name"] == "vibe.teleport_completed"
|
||||
assert telemetry_events[-1]["properties"] == {
|
||||
"push_required": True,
|
||||
"github_auth_required": True,
|
||||
"nb_session_messages": 1,
|
||||
"session_id": agent_loop.session_id,
|
||||
}
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_teleport_to_vibe_code_sends_failed_stage(
|
||||
self, agent_loop: AgentLoop, telemetry_events: list[dict[str, Any]]
|
||||
) -> None:
|
||||
class FakeTeleportService:
|
||||
async def __aenter__(self) -> FakeTeleportService:
|
||||
return self
|
||||
|
||||
async def __aexit__(self, *_args: object) -> None:
|
||||
return None
|
||||
|
||||
async def execute(
|
||||
self, *_args: object, **_kwargs: object
|
||||
) -> AsyncGenerator[object, object]:
|
||||
yield TeleportCheckingGitEvent()
|
||||
yield TeleportStartingWorkflowEvent()
|
||||
raise ServiceTeleportError("Workflow api-key-123 could not be started.")
|
||||
|
||||
agent_loop.messages.append(LLMMessage(role=Role.user, content="hello"))
|
||||
_set_teleport_service(agent_loop, FakeTeleportService())
|
||||
|
||||
gen = agent_loop.teleport_to_vibe_code(None)
|
||||
with pytest.raises(TeleportError, match="api-key-123"):
|
||||
async for _ in gen:
|
||||
pass
|
||||
|
||||
assert telemetry_events[-1]["event_name"] == "vibe.teleport_failed"
|
||||
assert telemetry_events[-1]["properties"] == {
|
||||
"stage": "workflow_start",
|
||||
"error_class": "ServiceTeleportError",
|
||||
"push_required": False,
|
||||
"github_auth_required": False,
|
||||
"nb_session_messages": 1,
|
||||
"session_id": agent_loop.session_id,
|
||||
}
|
||||
assert "api-key-123" not in str(telemetry_events[-1]["properties"])
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_teleport_to_vibe_code_sends_failed_cancelled(
|
||||
self, agent_loop: AgentLoop, telemetry_events: list[dict[str, Any]]
|
||||
) -> None:
|
||||
class FakeTeleportService:
|
||||
async def __aenter__(self) -> FakeTeleportService:
|
||||
return self
|
||||
|
||||
async def __aexit__(self, *_args: object) -> None:
|
||||
return None
|
||||
|
||||
async def execute(
|
||||
self, *_args: object, **_kwargs: object
|
||||
) -> AsyncGenerator[object, object]:
|
||||
yield TeleportCheckingGitEvent()
|
||||
response = yield TeleportPushRequiredEvent()
|
||||
if (
|
||||
not isinstance(response, TeleportPushResponseEvent)
|
||||
or not response.approved
|
||||
):
|
||||
raise ServiceTeleportError(
|
||||
"Teleport cancelled: changes not pushed."
|
||||
)
|
||||
|
||||
agent_loop.messages.append(LLMMessage(role=Role.user, content="hello"))
|
||||
_set_teleport_service(agent_loop, FakeTeleportService())
|
||||
|
||||
gen = agent_loop.teleport_to_vibe_code(None)
|
||||
assert isinstance(await gen.asend(None), TeleportCheckingGitEvent)
|
||||
assert isinstance(await gen.asend(None), TeleportPushRequiredEvent)
|
||||
|
||||
with pytest.raises(TeleportError, match="Teleport cancelled"):
|
||||
await gen.asend(TeleportPushResponseEvent(approved=False))
|
||||
|
||||
assert telemetry_events[-1]["event_name"] == "vibe.teleport_failed"
|
||||
assert telemetry_events[-1]["properties"] == {
|
||||
"stage": "cancelled",
|
||||
"error_class": "ServiceTeleportError",
|
||||
"push_required": True,
|
||||
"github_auth_required": False,
|
||||
"nb_session_messages": 1,
|
||||
"session_id": agent_loop.session_id,
|
||||
}
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_teleport_to_vibe_code_sends_failed_when_task_cancelled(
|
||||
self, agent_loop: AgentLoop, telemetry_events: list[dict[str, Any]]
|
||||
) -> None:
|
||||
class FakeTeleportService:
|
||||
async def __aenter__(self) -> FakeTeleportService:
|
||||
return self
|
||||
|
||||
async def __aexit__(self, *_args: object) -> None:
|
||||
return None
|
||||
|
||||
async def execute(
|
||||
self, *_args: object, **_kwargs: object
|
||||
) -> AsyncGenerator[object, object]:
|
||||
yield TeleportCheckingGitEvent()
|
||||
raise asyncio.CancelledError
|
||||
|
||||
agent_loop.messages.append(LLMMessage(role=Role.user, content="hello"))
|
||||
_set_teleport_service(agent_loop, FakeTeleportService())
|
||||
|
||||
gen = agent_loop.teleport_to_vibe_code(None)
|
||||
assert isinstance(await gen.asend(None), TeleportCheckingGitEvent)
|
||||
|
||||
with pytest.raises(asyncio.CancelledError):
|
||||
await gen.asend(None)
|
||||
|
||||
assert telemetry_events[-1]["event_name"] == "vibe.teleport_failed"
|
||||
assert telemetry_events[-1]["properties"] == {
|
||||
"stage": "cancelled",
|
||||
"error_class": "CancelledError",
|
||||
"push_required": False,
|
||||
"github_auth_required": False,
|
||||
"nb_session_messages": 1,
|
||||
"session_id": agent_loop.session_id,
|
||||
}
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_teleport_to_vibe_code_sends_failed_when_consumer_closes_generator(
|
||||
self, agent_loop: AgentLoop, telemetry_events: list[dict[str, Any]]
|
||||
) -> None:
|
||||
class FakeTeleportService:
|
||||
async def __aenter__(self) -> FakeTeleportService:
|
||||
return self
|
||||
|
||||
async def __aexit__(self, *_args: object) -> None:
|
||||
return None
|
||||
|
||||
async def execute(
|
||||
self, *_args: object, **_kwargs: object
|
||||
) -> AsyncGenerator[object, object]:
|
||||
yield TeleportCheckingGitEvent()
|
||||
yield TeleportPushRequiredEvent()
|
||||
|
||||
agent_loop.messages.append(LLMMessage(role=Role.user, content="hello"))
|
||||
_set_teleport_service(agent_loop, FakeTeleportService())
|
||||
|
||||
gen = agent_loop.teleport_to_vibe_code(None)
|
||||
assert isinstance(await gen.asend(None), TeleportCheckingGitEvent)
|
||||
|
||||
await gen.aclose()
|
||||
|
||||
assert telemetry_events[-1]["event_name"] == "vibe.teleport_failed"
|
||||
assert telemetry_events[-1]["properties"] == {
|
||||
"stage": "cancelled",
|
||||
"error_class": "CancelledError",
|
||||
"push_required": False,
|
||||
"github_auth_required": False,
|
||||
"nb_session_messages": 1,
|
||||
"session_id": agent_loop.session_id,
|
||||
}
|
||||
|
|
@ -4,7 +4,7 @@ from pathlib import Path
|
|||
|
||||
import pytest
|
||||
|
||||
from vibe.core.utils import get_server_url_from_api_base
|
||||
from vibe.core.utils import compact_reduction_display, get_server_url_from_api_base
|
||||
import vibe.core.utils.io as io_utils
|
||||
from vibe.core.utils.io import decode_safe, read_safe, read_safe_async
|
||||
|
||||
|
|
@ -23,6 +23,24 @@ def test_get_server_url_from_api_base(api_base, expected):
|
|||
assert get_server_url_from_api_base(api_base) == expected
|
||||
|
||||
|
||||
class TestCompactReductionDisplay:
|
||||
def test_includes_session_ids_when_available(self) -> None:
|
||||
assert compact_reduction_display(
|
||||
177_017,
|
||||
23_263,
|
||||
old_session_id="11111111-1111-1111-1111-111111111111",
|
||||
new_session_id="22222222-2222-2222-2222-222222222222",
|
||||
) == (
|
||||
"Compaction complete: 177,017 → 23,263 tokens (-87.%)\n"
|
||||
"session: 11111111 (before compaction) → 22222222 (after compaction)"
|
||||
)
|
||||
|
||||
def test_returns_base_message_without_session_ids(self) -> None:
|
||||
assert compact_reduction_display(177_017, 23_263) == (
|
||||
"Compaction complete: 177,017 → 23,263 tokens (-87.%)"
|
||||
)
|
||||
|
||||
|
||||
class TestReadSafe:
|
||||
def test_reads_utf8(self, tmp_path: Path) -> None:
|
||||
f = tmp_path / "hello.txt"
|
||||
|
|
|
|||
|
|
@ -60,7 +60,62 @@ class TestReadFileExecution:
|
|||
|
||||
assert result.content == "line 50\nline 51\n"
|
||||
assert result.lines_read == 2
|
||||
assert result.was_truncated
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_run_marks_truncated_when_max_read_bytes_exceeded(
|
||||
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
monkeypatch.chdir(tmp_path)
|
||||
test_file = tmp_path / "big.txt"
|
||||
test_file.write_text(
|
||||
"".join(f"line {i}\n" for i in range(100)), encoding="utf-8"
|
||||
)
|
||||
tool = ReadFile(
|
||||
config_getter=lambda: ReadFileToolConfig(max_read_bytes=20),
|
||||
state=ReadFileState(),
|
||||
)
|
||||
|
||||
result = await collect_result(tool.run(ReadFileArgs(path=str(test_file))))
|
||||
|
||||
assert result.was_truncated
|
||||
assert result.lines_read > 0
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_run_not_truncated_when_eof_reached(
|
||||
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
monkeypatch.chdir(tmp_path)
|
||||
test_file = tmp_path / "small.txt"
|
||||
test_file.write_text("a\nb\nc\n", encoding="utf-8")
|
||||
tool = ReadFile(
|
||||
config_getter=lambda: ReadFileToolConfig(max_read_bytes=1024),
|
||||
state=ReadFileState(),
|
||||
)
|
||||
|
||||
result = await collect_result(tool.run(ReadFileArgs(path=str(test_file))))
|
||||
|
||||
assert not result.was_truncated
|
||||
assert result.lines_read == 3
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_run_not_truncated_when_limit_matches_remaining_lines(
|
||||
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
monkeypatch.chdir(tmp_path)
|
||||
test_file = tmp_path / "exact.txt"
|
||||
test_file.write_text("a\nb\nc\n", encoding="utf-8")
|
||||
tool = ReadFile(
|
||||
config_getter=lambda: ReadFileToolConfig(max_read_bytes=1024),
|
||||
state=ReadFileState(),
|
||||
)
|
||||
|
||||
result = await collect_result(
|
||||
tool.run(ReadFileArgs(path=str(test_file), limit=10))
|
||||
)
|
||||
|
||||
assert not result.was_truncated
|
||||
assert result.lines_read == 3
|
||||
|
||||
|
||||
class TestGetResultExtra:
|
||||
|
|
|
|||
|
|
@ -5,33 +5,41 @@ from unittest.mock import AsyncMock, MagicMock, patch
|
|||
import pytest
|
||||
|
||||
from vibe.core.session.resume_sessions import (
|
||||
SHORT_SESSION_ID_LEN,
|
||||
list_remote_resume_sessions,
|
||||
short_session_id,
|
||||
)
|
||||
from vibe.core.session.session_id import shorten_session_id
|
||||
|
||||
|
||||
class TestShortenSessionId:
|
||||
def test_shortens_to_first_8_chars(self) -> None:
|
||||
sid = "abcdef1234567890"
|
||||
assert shorten_session_id(sid) == "abcdef12"
|
||||
|
||||
def test_from_end_shortens_to_last_8_chars(self) -> None:
|
||||
sid = "abcdef1234567890"
|
||||
assert shorten_session_id(sid, from_end=True) == "34567890"
|
||||
|
||||
def test_returns_full_id_when_shorter_than_limit(self) -> None:
|
||||
sid = "abc"
|
||||
assert shorten_session_id(sid) == "abc"
|
||||
assert shorten_session_id(sid, from_end=True) == "abc"
|
||||
|
||||
|
||||
class TestShortSessionId:
|
||||
def test_local_shortens_to_first_chars(self) -> None:
|
||||
def test_local_delegates_to_shorten(self) -> None:
|
||||
sid = "abcdef1234567890"
|
||||
result = short_session_id(sid)
|
||||
assert result == sid[:SHORT_SESSION_ID_LEN]
|
||||
assert len(result) == SHORT_SESSION_ID_LEN
|
||||
assert short_session_id(sid) == shorten_session_id(sid)
|
||||
|
||||
def test_local_is_default(self) -> None:
|
||||
sid = "abcdef1234567890"
|
||||
assert short_session_id(sid) == short_session_id(sid, source="local")
|
||||
|
||||
def test_remote_shortens_to_last_chars(self) -> None:
|
||||
def test_remote_delegates_to_shorten_from_end(self) -> None:
|
||||
sid = "abcdef1234567890"
|
||||
result = short_session_id(sid, source="remote")
|
||||
assert result == sid[-SHORT_SESSION_ID_LEN:]
|
||||
assert len(result) == SHORT_SESSION_ID_LEN
|
||||
|
||||
def test_returns_full_id_when_shorter_than_limit(self) -> None:
|
||||
sid = "abc"
|
||||
assert short_session_id(sid) == "abc"
|
||||
assert short_session_id(sid, source="remote") == "abc"
|
||||
assert short_session_id(sid, source="remote") == shorten_session_id(
|
||||
sid, from_end=True
|
||||
)
|
||||
|
||||
def test_empty_string(self) -> None:
|
||||
assert short_session_id("") == ""
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import pytest
|
|||
from tests.conftest import build_test_vibe_config
|
||||
from vibe.core.agents.models import AgentProfile, AgentSafety
|
||||
from vibe.core.config import SessionLoggingConfig, VibeConfig
|
||||
from vibe.core.loop import ScheduledLoop
|
||||
from vibe.core.session.session_logger import SessionLogger
|
||||
from vibe.core.tools.manager import ToolManager
|
||||
from vibe.core.types import AgentStats, LLMMessage, Role, SessionMetadata
|
||||
|
|
@ -868,3 +869,136 @@ class TestSessionLoggerCleanupTmpFiles:
|
|||
logger.maybe_cleanup_tmp_files()
|
||||
|
||||
assert cleanup_spy.call_count == 2
|
||||
|
||||
|
||||
class TestPersistLoops:
|
||||
@pytest.mark.asyncio
|
||||
async def test_writes_into_existing_metadata(
|
||||
self,
|
||||
session_config: SessionLoggingConfig,
|
||||
mock_vibe_config: VibeConfig,
|
||||
mock_tool_manager: ToolManager,
|
||||
mock_agent_profile: AgentProfile,
|
||||
) -> None:
|
||||
logger = SessionLogger(session_config, "test-session-loops")
|
||||
await logger.save_interaction(
|
||||
messages=[
|
||||
LLMMessage(role=Role.system, content="System prompt"),
|
||||
LLMMessage(role=Role.user, content="Hello"),
|
||||
LLMMessage(role=Role.assistant, content="Hi there!"),
|
||||
],
|
||||
stats=AgentStats(steps=1),
|
||||
base_config=mock_vibe_config,
|
||||
tool_manager=mock_tool_manager,
|
||||
agent_profile=mock_agent_profile,
|
||||
)
|
||||
|
||||
assert logger.session_metadata is not None
|
||||
logger.session_metadata.loops = [
|
||||
ScheduledLoop(
|
||||
id="aabbccdd",
|
||||
interval_seconds=30,
|
||||
prompt="ping",
|
||||
next_fire_at=12345.0,
|
||||
created_at=12000.0,
|
||||
)
|
||||
]
|
||||
await logger.persist_loops()
|
||||
|
||||
assert logger.session_dir is not None
|
||||
with open(logger.session_dir / "meta.json") as f:
|
||||
metadata = json.load(f)
|
||||
|
||||
assert metadata["session_id"] == "test-session-loops"
|
||||
assert metadata["total_messages"] == 2
|
||||
assert metadata["loops"] == [
|
||||
{
|
||||
"id": "aabbccdd",
|
||||
"interval_seconds": 30,
|
||||
"prompt": "ping",
|
||||
"next_fire_at": 12345.0,
|
||||
"created_at": 12000.0,
|
||||
}
|
||||
]
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_noop_when_metadata_file_missing(
|
||||
self, session_config: SessionLoggingConfig
|
||||
) -> None:
|
||||
logger = SessionLogger(session_config, "no-meta-session")
|
||||
assert logger.session_dir is not None
|
||||
# No save_interaction was called -> meta.json does not exist
|
||||
assert not (logger.session_dir / "meta.json").exists()
|
||||
|
||||
assert logger.session_metadata is not None
|
||||
logger.session_metadata.loops = [
|
||||
ScheduledLoop(
|
||||
id="aabbccdd",
|
||||
interval_seconds=30,
|
||||
prompt="ping",
|
||||
next_fire_at=1.0,
|
||||
created_at=0.0,
|
||||
)
|
||||
]
|
||||
await logger.persist_loops()
|
||||
|
||||
assert not (logger.session_dir / "meta.json").exists()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_noop_when_logging_disabled(
|
||||
self, disabled_session_config: SessionLoggingConfig
|
||||
) -> None:
|
||||
logger = SessionLogger(disabled_session_config, "ignored")
|
||||
# Should not raise even though session_metadata is None
|
||||
await logger.persist_loops()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_subsequent_save_interaction_preserves_loops(
|
||||
self,
|
||||
session_config: SessionLoggingConfig,
|
||||
mock_vibe_config: VibeConfig,
|
||||
mock_tool_manager: ToolManager,
|
||||
mock_agent_profile: AgentProfile,
|
||||
) -> None:
|
||||
logger = SessionLogger(session_config, "loops-vs-save")
|
||||
messages = [
|
||||
LLMMessage(role=Role.system, content="System prompt"),
|
||||
LLMMessage(role=Role.user, content="Hello"),
|
||||
LLMMessage(role=Role.assistant, content="Hi there!"),
|
||||
]
|
||||
await logger.save_interaction(
|
||||
messages=messages,
|
||||
stats=AgentStats(steps=1),
|
||||
base_config=mock_vibe_config,
|
||||
tool_manager=mock_tool_manager,
|
||||
agent_profile=mock_agent_profile,
|
||||
)
|
||||
|
||||
assert logger.session_metadata is not None
|
||||
logger.session_metadata.loops = [
|
||||
ScheduledLoop(
|
||||
id="aabbccdd",
|
||||
interval_seconds=30,
|
||||
prompt="ping",
|
||||
next_fire_at=12345.0,
|
||||
created_at=12000.0,
|
||||
)
|
||||
]
|
||||
await logger.persist_loops()
|
||||
|
||||
# A subsequent save (e.g. user sends another message) must not
|
||||
# overwrite the on-disk loops with the stale in-memory value.
|
||||
more_messages = [*messages, LLMMessage(role=Role.user, content="Again")]
|
||||
await logger.save_interaction(
|
||||
messages=more_messages,
|
||||
stats=AgentStats(steps=2),
|
||||
base_config=mock_vibe_config,
|
||||
tool_manager=mock_tool_manager,
|
||||
agent_profile=mock_agent_profile,
|
||||
)
|
||||
|
||||
assert logger.session_dir is not None
|
||||
with open(logger.session_dir / "meta.json") as f:
|
||||
metadata = json.load(f)
|
||||
assert len(metadata["loops"]) == 1
|
||||
assert metadata["loops"][0]["id"] == "aabbccdd"
|
||||
|
|
|
|||
|
|
@ -191,7 +191,7 @@
|
|||
</text><text class="terminal-r1" x="1464" y="654.4" textLength="12.2" clip-path="url(#terminal-line-26)">
|
||||
</text><text class="terminal-r1" x="0" y="678.8" textLength="268.4" clip-path="url(#terminal-line-27)">Hello! I can help you.</text><text class="terminal-r1" x="1464" y="678.8" textLength="12.2" clip-path="url(#terminal-line-27)">
|
||||
</text><text class="terminal-r1" x="1464" y="703.2" textLength="12.2" clip-path="url(#terminal-line-28)">
|
||||
</text><text class="terminal-r5" x="12.2" y="727.6" textLength="36.6" clip-path="url(#terminal-line-29)">▂▅▇</text><text class="terminal-r1" x="48.8" y="727.6" textLength="122" clip-path="url(#terminal-line-29)"> speaking </text><text class="terminal-r6" x="170.8" y="727.6" textLength="219.6" clip-path="url(#terminal-line-29)">Esc/Ctrl+C to stop</text><text class="terminal-r1" x="1464" y="727.6" textLength="12.2" clip-path="url(#terminal-line-29)">
|
||||
</text><text class="terminal-r5" x="0" y="727.6" textLength="36.6" clip-path="url(#terminal-line-29)">▂▅▇</text><text class="terminal-r1" x="36.6" y="727.6" textLength="122" clip-path="url(#terminal-line-29)"> speaking </text><text class="terminal-r6" x="158.6" y="727.6" textLength="219.6" clip-path="url(#terminal-line-29)">Esc/Ctrl+C to stop</text><text class="terminal-r1" x="1464" y="727.6" textLength="12.2" clip-path="url(#terminal-line-29)">
|
||||
</text><text class="terminal-r7" x="0" y="752" textLength="1464" clip-path="url(#terminal-line-30)">┌──────────────────────────────────────────────────────────────────────────────────────────────────────────── default ─┐</text><text class="terminal-r1" x="1464" y="752" textLength="12.2" clip-path="url(#terminal-line-30)">
|
||||
</text><text class="terminal-r7" x="0" y="776.4" textLength="12.2" clip-path="url(#terminal-line-31)">│</text><text class="terminal-r2" x="24.4" y="776.4" textLength="12.2" clip-path="url(#terminal-line-31)">></text><text class="terminal-r7" x="1451.8" y="776.4" textLength="12.2" clip-path="url(#terminal-line-31)">│</text><text class="terminal-r1" x="1464" y="776.4" textLength="12.2" clip-path="url(#terminal-line-31)">
|
||||
</text><text class="terminal-r7" x="0" y="800.8" textLength="12.2" clip-path="url(#terminal-line-32)">│</text><text class="terminal-r7" x="1451.8" y="800.8" textLength="12.2" clip-path="url(#terminal-line-32)">│</text><text class="terminal-r1" x="1464" y="800.8" textLength="12.2" clip-path="url(#terminal-line-32)">
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
|
@ -191,7 +191,7 @@
|
|||
</text><text class="terminal-r1" x="1464" y="654.4" textLength="12.2" clip-path="url(#terminal-line-26)">
|
||||
</text><text class="terminal-r1" x="0" y="678.8" textLength="268.4" clip-path="url(#terminal-line-27)">Hello! I can help you.</text><text class="terminal-r1" x="1464" y="678.8" textLength="12.2" clip-path="url(#terminal-line-27)">
|
||||
</text><text class="terminal-r1" x="1464" y="703.2" textLength="12.2" clip-path="url(#terminal-line-28)">
|
||||
</text><text class="terminal-r5" x="12.2" y="727.6" textLength="12.2" clip-path="url(#terminal-line-29)">█</text><text class="terminal-r1" x="24.4" y="727.6" textLength="158.6" clip-path="url(#terminal-line-29)"> summarizing </text><text class="terminal-r6" x="183" y="727.6" textLength="219.6" clip-path="url(#terminal-line-29)">Esc/Ctrl+C to stop</text><text class="terminal-r1" x="1464" y="727.6" textLength="12.2" clip-path="url(#terminal-line-29)">
|
||||
</text><text class="terminal-r5" x="0" y="727.6" textLength="12.2" clip-path="url(#terminal-line-29)">█</text><text class="terminal-r1" x="12.2" y="727.6" textLength="158.6" clip-path="url(#terminal-line-29)"> summarizing </text><text class="terminal-r6" x="170.8" y="727.6" textLength="219.6" clip-path="url(#terminal-line-29)">Esc/Ctrl+C to stop</text><text class="terminal-r1" x="1464" y="727.6" textLength="12.2" clip-path="url(#terminal-line-29)">
|
||||
</text><text class="terminal-r7" x="0" y="752" textLength="1464" clip-path="url(#terminal-line-30)">┌──────────────────────────────────────────────────────────────────────────────────────────────────────────── default ─┐</text><text class="terminal-r1" x="1464" y="752" textLength="12.2" clip-path="url(#terminal-line-30)">
|
||||
</text><text class="terminal-r7" x="0" y="776.4" textLength="12.2" clip-path="url(#terminal-line-31)">│</text><text class="terminal-r2" x="24.4" y="776.4" textLength="12.2" clip-path="url(#terminal-line-31)">></text><text class="terminal-r7" x="1451.8" y="776.4" textLength="12.2" clip-path="url(#terminal-line-31)">│</text><text class="terminal-r1" x="1464" y="776.4" textLength="12.2" clip-path="url(#terminal-line-31)">
|
||||
</text><text class="terminal-r7" x="0" y="800.8" textLength="12.2" clip-path="url(#terminal-line-32)">│</text><text class="terminal-r7" x="1451.8" y="800.8" textLength="12.2" clip-path="url(#terminal-line-32)">│</text><text class="terminal-r1" x="1464" y="800.8" textLength="12.2" clip-path="url(#terminal-line-32)">
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
|
@ -33,13 +33,12 @@
|
|||
}
|
||||
|
||||
.terminal-r1 { fill: #c5c8c6 }
|
||||
.terminal-r2 { fill: #68a0b3 }
|
||||
.terminal-r3 { fill: #4b4e55 }
|
||||
.terminal-r4 { fill: #9a9b99 }
|
||||
.terminal-r5 { fill: #608ab1;font-weight: bold }
|
||||
.terminal-r2 { fill: #9a9b99 }
|
||||
.terminal-r3 { fill: #608ab1;font-weight: bold }
|
||||
.terminal-r4 { fill: #98a84b;font-weight: bold }
|
||||
.terminal-r5 { fill: #4b4e55 }
|
||||
.terminal-r6 { fill: #292929 }
|
||||
.terminal-r7 { fill: #98a84b;font-weight: bold }
|
||||
.terminal-r8 { fill: #ff8205;font-weight: bold }
|
||||
.terminal-r7 { fill: #ff8205;font-weight: bold }
|
||||
</style>
|
||||
|
||||
<defs>
|
||||
|
|
@ -197,56 +196,56 @@
|
|||
</g>
|
||||
|
||||
<g transform="translate(9, 41)" clip-path="url(#terminal-clip-terminal)">
|
||||
<rect fill="#4b4e55" x="1451.8" y="50.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="74.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="99.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="123.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="147.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="172.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="196.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="221.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="245.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="269.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="294.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="318.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="343.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="367.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="391.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="416.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="440.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="465.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="489.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="513.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="538.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="562.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="587.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="611.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="635.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="660.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="684.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="709.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="733.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="757.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="782.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="806.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="831.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="855.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="879.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="904.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="928.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="953.1" width="12.2" height="24.65" shape-rendering="crispEdges"/>
|
||||
<rect fill="#4b4e55" x="1451.8" y="74.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="99.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="123.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="147.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="172.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="196.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="221.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="245.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="269.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="294.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="318.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="343.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="367.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="391.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="416.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="440.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="465.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="489.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="513.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="538.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="562.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="587.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="611.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="635.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="660.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="684.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="709.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="733.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="757.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="782.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="806.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="831.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="855.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="879.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="904.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="928.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="953.1" width="12.2" height="24.65" shape-rendering="crispEdges"/>
|
||||
<g class="terminal-matrix">
|
||||
<text class="terminal-r1" x="0" y="20" textLength="134.2" clip-path="url(#terminal-line-0)">  ⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="170.8" y="20" textLength="61" clip-path="url(#terminal-line-0)">Type </text><text class="terminal-r2" x="231.8" y="20" textLength="61" clip-path="url(#terminal-line-0)">/help</text><text class="terminal-r1" x="292.8" y="20" textLength="256.2" clip-path="url(#terminal-line-0)"> for more information</text><text class="terminal-r1" x="1464" y="20" textLength="12.2" clip-path="url(#terminal-line-0)">
|
||||
</text><text class="terminal-r3" x="1451.8" y="44.4" textLength="12.2" clip-path="url(#terminal-line-1)">▁</text><text class="terminal-r1" x="1464" y="44.4" textLength="12.2" clip-path="url(#terminal-line-1)">
|
||||
</text><text class="terminal-r4" x="24.4" y="68.8" textLength="12.2" clip-path="url(#terminal-line-2)">⎢</text><text class="terminal-r5" x="48.8" y="68.8" textLength="219.6" clip-path="url(#terminal-line-2)">Keyboard Shortcuts</text><text class="terminal-r1" x="1464" y="68.8" textLength="12.2" clip-path="url(#terminal-line-2)">
|
||||
</text><text class="terminal-r4" x="24.4" y="93.2" textLength="12.2" clip-path="url(#terminal-line-3)">⎢</text><text class="terminal-r1" x="48.8" y="93.2" textLength="24.4" clip-path="url(#terminal-line-3)">• </text><text class="terminal-r7" x="73.2" y="93.2" textLength="61" clip-path="url(#terminal-line-3)">Enter</text><text class="terminal-r1" x="134.2" y="93.2" textLength="183" clip-path="url(#terminal-line-3)"> Submit message</text><text class="terminal-r1" x="1464" y="93.2" textLength="12.2" clip-path="url(#terminal-line-3)">
|
||||
</text><text class="terminal-r4" x="24.4" y="117.6" textLength="12.2" clip-path="url(#terminal-line-4)">⎢</text><text class="terminal-r1" x="48.8" y="117.6" textLength="24.4" clip-path="url(#terminal-line-4)">• </text><text class="terminal-r7" x="73.2" y="117.6" textLength="73.2" clip-path="url(#terminal-line-4)">Ctrl+J</text><text class="terminal-r1" x="146.4" y="117.6" textLength="36.6" clip-path="url(#terminal-line-4)"> / </text><text class="terminal-r7" x="183" y="117.6" textLength="134.2" clip-path="url(#terminal-line-4)">Shift+Enter</text><text class="terminal-r1" x="317.2" y="117.6" textLength="183" clip-path="url(#terminal-line-4)"> Insert newline</text><text class="terminal-r1" x="1464" y="117.6" textLength="12.2" clip-path="url(#terminal-line-4)">
|
||||
</text><text class="terminal-r4" x="24.4" y="142" textLength="12.2" clip-path="url(#terminal-line-5)">⎢</text><text class="terminal-r1" x="48.8" y="142" textLength="24.4" clip-path="url(#terminal-line-5)">• </text><text class="terminal-r7" x="73.2" y="142" textLength="73.2" clip-path="url(#terminal-line-5)">Escape</text><text class="terminal-r1" x="146.4" y="142" textLength="402.6" clip-path="url(#terminal-line-5)"> Interrupt agent or close dialogs</text><text class="terminal-r1" x="1464" y="142" textLength="12.2" clip-path="url(#terminal-line-5)">
|
||||
</text><text class="terminal-r4" x="24.4" y="166.4" textLength="12.2" clip-path="url(#terminal-line-6)">⎢</text><text class="terminal-r1" x="48.8" y="166.4" textLength="24.4" clip-path="url(#terminal-line-6)">• </text><text class="terminal-r7" x="73.2" y="166.4" textLength="73.2" clip-path="url(#terminal-line-6)">Ctrl+C</text><text class="terminal-r1" x="146.4" y="166.4" textLength="463.6" clip-path="url(#terminal-line-6)"> Quit (or clear input if text present)</text><text class="terminal-r1" x="1464" y="166.4" textLength="12.2" clip-path="url(#terminal-line-6)">
|
||||
</text><text class="terminal-r4" x="24.4" y="190.8" textLength="12.2" clip-path="url(#terminal-line-7)">⎢</text><text class="terminal-r1" x="48.8" y="190.8" textLength="24.4" clip-path="url(#terminal-line-7)">• </text><text class="terminal-r7" x="73.2" y="190.8" textLength="73.2" clip-path="url(#terminal-line-7)">Ctrl+G</text><text class="terminal-r1" x="146.4" y="190.8" textLength="366" clip-path="url(#terminal-line-7)"> Edit input in external editor</text><text class="terminal-r1" x="1464" y="190.8" textLength="12.2" clip-path="url(#terminal-line-7)">
|
||||
</text><text class="terminal-r4" x="24.4" y="215.2" textLength="12.2" clip-path="url(#terminal-line-8)">⎢</text><text class="terminal-r1" x="48.8" y="215.2" textLength="24.4" clip-path="url(#terminal-line-8)">• </text><text class="terminal-r7" x="73.2" y="215.2" textLength="73.2" clip-path="url(#terminal-line-8)">Ctrl+O</text><text class="terminal-r1" x="146.4" y="215.2" textLength="292.8" clip-path="url(#terminal-line-8)"> Toggle tool output view</text><text class="terminal-r1" x="1464" y="215.2" textLength="12.2" clip-path="url(#terminal-line-8)">
|
||||
</text><text class="terminal-r4" x="24.4" y="239.6" textLength="12.2" clip-path="url(#terminal-line-9)">⎢</text><text class="terminal-r1" x="48.8" y="239.6" textLength="24.4" clip-path="url(#terminal-line-9)">• </text><text class="terminal-r7" x="73.2" y="239.6" textLength="109.8" clip-path="url(#terminal-line-9)">Shift+Tab</text><text class="terminal-r1" x="183" y="239.6" textLength="512.4" clip-path="url(#terminal-line-9)"> Cycle through agents (default, plan, ...)</text><text class="terminal-r1" x="1464" y="239.6" textLength="12.2" clip-path="url(#terminal-line-9)">
|
||||
</text><text class="terminal-r4" x="24.4" y="264" textLength="12.2" clip-path="url(#terminal-line-10)">⎢</text><text class="terminal-r1" x="48.8" y="264" textLength="24.4" clip-path="url(#terminal-line-10)">• </text><text class="terminal-r7" x="73.2" y="264" textLength="73.2" clip-path="url(#terminal-line-10)">Alt+↑↓</text><text class="terminal-r1" x="146.4" y="264" textLength="36.6" clip-path="url(#terminal-line-10)"> / </text><text class="terminal-r7" x="183" y="264" textLength="97.6" clip-path="url(#terminal-line-10)">Ctrl+P/N</text><text class="terminal-r1" x="280.6" y="264" textLength="390.4" clip-path="url(#terminal-line-10)"> Rewind to previous/next message</text><text class="terminal-r1" x="1464" y="264" textLength="12.2" clip-path="url(#terminal-line-10)">
|
||||
</text><text class="terminal-r4" x="24.4" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)">⎢</text><text class="terminal-r1" x="1464" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)">
|
||||
</text><text class="terminal-r4" x="24.4" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)">⎢</text><text class="terminal-r5" x="48.8" y="312.8" textLength="195.2" clip-path="url(#terminal-line-12)">Special Features</text><text class="terminal-r1" x="1464" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)">
|
||||
</text><text class="terminal-r4" x="24.4" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)">⎢</text><text class="terminal-r1" x="1464" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)">
|
||||
</text><text class="terminal-r4" x="24.4" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)">⎢</text><text class="terminal-r1" x="48.8" y="361.6" textLength="24.4" clip-path="url(#terminal-line-14)">• </text><text class="terminal-r7" x="73.2" y="361.6" textLength="122" clip-path="url(#terminal-line-14)">!<command></text><text class="terminal-r1" x="195.2" y="361.6" textLength="366" clip-path="url(#terminal-line-14)"> Execute bash command directly</text><text class="terminal-r1" x="1464" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)">
|
||||
</text><text class="terminal-r4" x="24.4" y="386" textLength="12.2" clip-path="url(#terminal-line-15)">⎢</text><text class="terminal-r1" x="48.8" y="386" textLength="24.4" clip-path="url(#terminal-line-15)">• </text><text class="terminal-r7" x="73.2" y="386" textLength="170.8" clip-path="url(#terminal-line-15)">@path/to/file/</text><text class="terminal-r1" x="244" y="386" textLength="305" clip-path="url(#terminal-line-15)"> Autocompletes file paths</text><text class="terminal-r1" x="1464" y="386" textLength="12.2" clip-path="url(#terminal-line-15)">
|
||||
</text><text class="terminal-r4" x="24.4" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)">⎢</text><text class="terminal-r1" x="1464" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)">
|
||||
</text><text class="terminal-r4" x="24.4" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)">⎢</text><text class="terminal-r5" x="48.8" y="434.8" textLength="97.6" clip-path="url(#terminal-line-17)">Commands</text><text class="terminal-r1" x="1464" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)">
|
||||
</text><text class="terminal-r4" x="24.4" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)">⎢</text><text class="terminal-r1" x="1464" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)">
|
||||
</text><text class="terminal-r4" x="24.4" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)">⎢</text><text class="terminal-r1" x="48.8" y="483.6" textLength="24.4" clip-path="url(#terminal-line-19)">• </text><text class="terminal-r7" x="73.2" y="483.6" textLength="61" clip-path="url(#terminal-line-19)">/help</text><text class="terminal-r1" x="134.2" y="483.6" textLength="231.8" clip-path="url(#terminal-line-19)">: Show help message</text><text class="terminal-r1" x="1464" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)">
|
||||
</text><text class="terminal-r4" x="24.4" y="508" textLength="12.2" clip-path="url(#terminal-line-20)">⎢</text><text class="terminal-r1" x="48.8" y="508" textLength="24.4" clip-path="url(#terminal-line-20)">• </text><text class="terminal-r7" x="73.2" y="508" textLength="85.4" clip-path="url(#terminal-line-20)">/config</text><text class="terminal-r1" x="158.6" y="508" textLength="268.4" clip-path="url(#terminal-line-20)">: Edit config settings</text><text class="terminal-r1" x="1464" y="508" textLength="12.2" clip-path="url(#terminal-line-20)">
|
||||
</text><text class="terminal-r4" x="24.4" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)">⎢</text><text class="terminal-r1" x="48.8" y="532.4" textLength="24.4" clip-path="url(#terminal-line-21)">• </text><text class="terminal-r7" x="73.2" y="532.4" textLength="73.2" clip-path="url(#terminal-line-21)">/model</text><text class="terminal-r1" x="146.4" y="532.4" textLength="256.2" clip-path="url(#terminal-line-21)">: Select active model</text><text class="terminal-r1" x="1464" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)">
|
||||
</text><text class="terminal-r4" x="24.4" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)">⎢</text><text class="terminal-r1" x="48.8" y="556.8" textLength="24.4" clip-path="url(#terminal-line-22)">• </text><text class="terminal-r7" x="73.2" y="556.8" textLength="109.8" clip-path="url(#terminal-line-22)">/thinking</text><text class="terminal-r1" x="183" y="556.8" textLength="280.6" clip-path="url(#terminal-line-22)">: Select thinking level</text><text class="terminal-r1" x="1464" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)">
|
||||
</text><text class="terminal-r4" x="24.4" y="581.2" textLength="12.2" clip-path="url(#terminal-line-23)">⎢</text><text class="terminal-r1" x="48.8" y="581.2" textLength="24.4" clip-path="url(#terminal-line-23)">• </text><text class="terminal-r7" x="73.2" y="581.2" textLength="85.4" clip-path="url(#terminal-line-23)">/reload</text><text class="terminal-r1" x="158.6" y="581.2" textLength="780.8" clip-path="url(#terminal-line-23)">: Reload configuration, agent instructions, and skills from disk</text><text class="terminal-r1" x="1464" y="581.2" textLength="12.2" clip-path="url(#terminal-line-23)">
|
||||
</text><text class="terminal-r4" x="24.4" y="605.6" textLength="12.2" clip-path="url(#terminal-line-24)">⎢</text><text class="terminal-r1" x="48.8" y="605.6" textLength="24.4" clip-path="url(#terminal-line-24)">• </text><text class="terminal-r7" x="73.2" y="605.6" textLength="73.2" clip-path="url(#terminal-line-24)">/clear</text><text class="terminal-r1" x="146.4" y="605.6" textLength="341.6" clip-path="url(#terminal-line-24)">: Clear conversation history</text><text class="terminal-r1" x="1464" y="605.6" textLength="12.2" clip-path="url(#terminal-line-24)">
|
||||
</text><text class="terminal-r4" x="24.4" y="630" textLength="12.2" clip-path="url(#terminal-line-25)">⎢</text><text class="terminal-r1" x="48.8" y="630" textLength="24.4" clip-path="url(#terminal-line-25)">• </text><text class="terminal-r7" x="73.2" y="630" textLength="61" clip-path="url(#terminal-line-25)">/copy</text><text class="terminal-r1" x="134.2" y="630" textLength="561.2" clip-path="url(#terminal-line-25)">: Copy the last agent message to the clipboard</text><text class="terminal-r1" x="1464" y="630" textLength="12.2" clip-path="url(#terminal-line-25)">
|
||||
</text><text class="terminal-r4" x="24.4" y="654.4" textLength="12.2" clip-path="url(#terminal-line-26)">⎢</text><text class="terminal-r1" x="48.8" y="654.4" textLength="24.4" clip-path="url(#terminal-line-26)">• </text><text class="terminal-r7" x="73.2" y="654.4" textLength="48.8" clip-path="url(#terminal-line-26)">/log</text><text class="terminal-r1" x="122" y="654.4" textLength="524.6" clip-path="url(#terminal-line-26)">: Show path to current interaction log file</text><text class="terminal-r1" x="1464" y="654.4" textLength="12.2" clip-path="url(#terminal-line-26)">
|
||||
</text><text class="terminal-r4" x="24.4" y="678.8" textLength="12.2" clip-path="url(#terminal-line-27)">⎢</text><text class="terminal-r1" x="48.8" y="678.8" textLength="24.4" clip-path="url(#terminal-line-27)">• </text><text class="terminal-r7" x="73.2" y="678.8" textLength="73.2" clip-path="url(#terminal-line-27)">/debug</text><text class="terminal-r1" x="146.4" y="678.8" textLength="268.4" clip-path="url(#terminal-line-27)">: Toggle debug console</text><text class="terminal-r1" x="1464" y="678.8" textLength="12.2" clip-path="url(#terminal-line-27)">
|
||||
</text><text class="terminal-r4" x="24.4" y="703.2" textLength="12.2" clip-path="url(#terminal-line-28)">⎢</text><text class="terminal-r1" x="48.8" y="703.2" textLength="24.4" clip-path="url(#terminal-line-28)">• </text><text class="terminal-r7" x="73.2" y="703.2" textLength="97.6" clip-path="url(#terminal-line-28)">/compact</text><text class="terminal-r1" x="170.8" y="703.2" textLength="1171.2" clip-path="url(#terminal-line-28)">: Compact conversation history by summarizing. Optionally pass instructions to guide the summary</text><text class="terminal-r1" x="1464" y="703.2" textLength="12.2" clip-path="url(#terminal-line-28)">
|
||||
</text><text class="terminal-r4" x="24.4" y="727.6" textLength="12.2" clip-path="url(#terminal-line-29)">⎢</text><text class="terminal-r1" x="48.8" y="727.6" textLength="24.4" clip-path="url(#terminal-line-29)">• </text><text class="terminal-r7" x="73.2" y="727.6" textLength="61" clip-path="url(#terminal-line-29)">/exit</text><text class="terminal-r1" x="134.2" y="727.6" textLength="268.4" clip-path="url(#terminal-line-29)">: Exit the application</text><text class="terminal-r1" x="1464" y="727.6" textLength="12.2" clip-path="url(#terminal-line-29)">
|
||||
</text><text class="terminal-r4" x="24.4" y="752" textLength="12.2" clip-path="url(#terminal-line-30)">⎢</text><text class="terminal-r1" x="48.8" y="752" textLength="24.4" clip-path="url(#terminal-line-30)">• </text><text class="terminal-r7" x="73.2" y="752" textLength="85.4" clip-path="url(#terminal-line-30)">/status</text><text class="terminal-r1" x="158.6" y="752" textLength="317.2" clip-path="url(#terminal-line-30)">: Display agent statistics</text><text class="terminal-r1" x="1464" y="752" textLength="12.2" clip-path="url(#terminal-line-30)">
|
||||
</text><text class="terminal-r4" x="24.4" y="776.4" textLength="12.2" clip-path="url(#terminal-line-31)">⎢</text><text class="terminal-r1" x="48.8" y="776.4" textLength="24.4" clip-path="url(#terminal-line-31)">• </text><text class="terminal-r7" x="73.2" y="776.4" textLength="146.4" clip-path="url(#terminal-line-31)">/proxy-setup</text><text class="terminal-r1" x="219.6" y="776.4" textLength="561.2" clip-path="url(#terminal-line-31)">: Configure proxy and SSL certificate settings</text><text class="terminal-r1" x="1464" y="776.4" textLength="12.2" clip-path="url(#terminal-line-31)">
|
||||
</text><text class="terminal-r4" x="24.4" y="800.8" textLength="12.2" clip-path="url(#terminal-line-32)">⎢</text><text class="terminal-r1" x="48.8" y="800.8" textLength="24.4" clip-path="url(#terminal-line-32)">• </text><text class="terminal-r7" x="73.2" y="800.8" textLength="109.8" clip-path="url(#terminal-line-32)">/continue</text><text class="terminal-r1" x="183" y="800.8" textLength="24.4" clip-path="url(#terminal-line-32)">, </text><text class="terminal-r7" x="207.4" y="800.8" textLength="85.4" clip-path="url(#terminal-line-32)">/resume</text><text class="terminal-r1" x="292.8" y="800.8" textLength="402.6" clip-path="url(#terminal-line-32)">: Browse and resume past sessions</text><text class="terminal-r1" x="1464" y="800.8" textLength="12.2" clip-path="url(#terminal-line-32)">
|
||||
</text><text class="terminal-r4" x="24.4" y="825.2" textLength="12.2" clip-path="url(#terminal-line-33)">⎢</text><text class="terminal-r1" x="48.8" y="825.2" textLength="24.4" clip-path="url(#terminal-line-33)">• </text><text class="terminal-r7" x="73.2" y="825.2" textLength="85.4" clip-path="url(#terminal-line-33)">/rename</text><text class="terminal-r1" x="158.6" y="825.2" textLength="341.6" clip-path="url(#terminal-line-33)">: Rename the current session</text><text class="terminal-r1" x="1464" y="825.2" textLength="12.2" clip-path="url(#terminal-line-33)">
|
||||
</text><text class="terminal-r4" x="24.4" y="849.6" textLength="12.2" clip-path="url(#terminal-line-34)">⎢</text><text class="terminal-r1" x="48.8" y="849.6" textLength="24.4" clip-path="url(#terminal-line-34)">• </text><text class="terminal-r7" x="73.2" y="849.6" textLength="134.2" clip-path="url(#terminal-line-34)">/connectors</text><text class="terminal-r1" x="207.4" y="849.6" textLength="24.4" clip-path="url(#terminal-line-34)">, </text><text class="terminal-r7" x="231.8" y="849.6" textLength="48.8" clip-path="url(#terminal-line-34)">/mcp</text><text class="terminal-r1" x="280.6" y="849.6" textLength="939.4" clip-path="url(#terminal-line-34)">: Display available MCP servers and connectors. Pass a name to list its tools</text><text class="terminal-r1" x="1464" y="849.6" textLength="12.2" clip-path="url(#terminal-line-34)">
|
||||
</text><text class="terminal-r4" x="24.4" y="874" textLength="12.2" clip-path="url(#terminal-line-35)">⎢</text><text class="terminal-r1" x="48.8" y="874" textLength="24.4" clip-path="url(#terminal-line-35)">• </text><text class="terminal-r7" x="73.2" y="874" textLength="73.2" clip-path="url(#terminal-line-35)">/voice</text><text class="terminal-r1" x="146.4" y="874" textLength="317.2" clip-path="url(#terminal-line-35)">: Configure voice settings</text><text class="terminal-r1" x="1464" y="874" textLength="12.2" clip-path="url(#terminal-line-35)">
|
||||
</text><text class="terminal-r4" x="24.4" y="898.4" textLength="12.2" clip-path="url(#terminal-line-36)">⎢</text><text class="terminal-r1" x="48.8" y="898.4" textLength="24.4" clip-path="url(#terminal-line-36)">• </text><text class="terminal-r7" x="73.2" y="898.4" textLength="122" clip-path="url(#terminal-line-36)">/leanstall</text><text class="terminal-r1" x="195.2" y="898.4" textLength="463.6" clip-path="url(#terminal-line-36)">: Install the Lean 4 agent (leanstral)</text><text class="terminal-r1" x="1464" y="898.4" textLength="12.2" clip-path="url(#terminal-line-36)">
|
||||
</text><text class="terminal-r4" x="24.4" y="922.8" textLength="12.2" clip-path="url(#terminal-line-37)">⎢</text><text class="terminal-r1" x="48.8" y="922.8" textLength="24.4" clip-path="url(#terminal-line-37)">• </text><text class="terminal-r7" x="73.2" y="922.8" textLength="146.4" clip-path="url(#terminal-line-37)">/unleanstall</text><text class="terminal-r1" x="219.6" y="922.8" textLength="341.6" clip-path="url(#terminal-line-37)">: Uninstall the Lean 4 agent</text><text class="terminal-r1" x="1464" y="922.8" textLength="12.2" clip-path="url(#terminal-line-37)">
|
||||
</text><text class="terminal-r4" x="24.4" y="947.2" textLength="12.2" clip-path="url(#terminal-line-38)">⎢</text><text class="terminal-r1" x="48.8" y="947.2" textLength="24.4" clip-path="url(#terminal-line-38)">• </text><text class="terminal-r7" x="73.2" y="947.2" textLength="85.4" clip-path="url(#terminal-line-38)">/rewind</text><text class="terminal-r1" x="158.6" y="947.2" textLength="366" clip-path="url(#terminal-line-38)">: Rewind to a previous message</text><text class="terminal-r1" x="1464" y="947.2" textLength="12.2" clip-path="url(#terminal-line-38)">
|
||||
</text><text class="terminal-r4" x="24.4" y="971.6" textLength="12.2" clip-path="url(#terminal-line-39)">⎣</text><text class="terminal-r1" x="48.8" y="971.6" textLength="24.4" clip-path="url(#terminal-line-39)">• </text><text class="terminal-r7" x="73.2" y="971.6" textLength="183" clip-path="url(#terminal-line-39)">/data-retention</text><text class="terminal-r1" x="256.2" y="971.6" textLength="402.6" clip-path="url(#terminal-line-39)">: Show data retention information</text><text class="terminal-r1" x="1464" y="971.6" textLength="12.2" clip-path="url(#terminal-line-39)">
|
||||
<text class="terminal-r1" x="1464" y="20" textLength="12.2" clip-path="url(#terminal-line-0)">
|
||||
</text><text class="terminal-r2" x="24.4" y="44.4" textLength="12.2" clip-path="url(#terminal-line-1)">⎢</text><text class="terminal-r3" x="48.8" y="44.4" textLength="219.6" clip-path="url(#terminal-line-1)">Keyboard Shortcuts</text><text class="terminal-r1" x="1464" y="44.4" textLength="12.2" clip-path="url(#terminal-line-1)">
|
||||
</text><text class="terminal-r2" x="24.4" y="68.8" textLength="12.2" clip-path="url(#terminal-line-2)">⎢</text><text class="terminal-r1" x="48.8" y="68.8" textLength="24.4" clip-path="url(#terminal-line-2)">• </text><text class="terminal-r4" x="73.2" y="68.8" textLength="61" clip-path="url(#terminal-line-2)">Enter</text><text class="terminal-r1" x="134.2" y="68.8" textLength="183" clip-path="url(#terminal-line-2)"> Submit message</text><text class="terminal-r5" x="1451.8" y="68.8" textLength="12.2" clip-path="url(#terminal-line-2)">▂</text><text class="terminal-r1" x="1464" y="68.8" textLength="12.2" clip-path="url(#terminal-line-2)">
|
||||
</text><text class="terminal-r2" x="24.4" y="93.2" textLength="12.2" clip-path="url(#terminal-line-3)">⎢</text><text class="terminal-r1" x="48.8" y="93.2" textLength="24.4" clip-path="url(#terminal-line-3)">• </text><text class="terminal-r4" x="73.2" y="93.2" textLength="73.2" clip-path="url(#terminal-line-3)">Ctrl+J</text><text class="terminal-r1" x="146.4" y="93.2" textLength="36.6" clip-path="url(#terminal-line-3)"> / </text><text class="terminal-r4" x="183" y="93.2" textLength="134.2" clip-path="url(#terminal-line-3)">Shift+Enter</text><text class="terminal-r1" x="317.2" y="93.2" textLength="183" clip-path="url(#terminal-line-3)"> Insert newline</text><text class="terminal-r1" x="1464" y="93.2" textLength="12.2" clip-path="url(#terminal-line-3)">
|
||||
</text><text class="terminal-r2" x="24.4" y="117.6" textLength="12.2" clip-path="url(#terminal-line-4)">⎢</text><text class="terminal-r1" x="48.8" y="117.6" textLength="24.4" clip-path="url(#terminal-line-4)">• </text><text class="terminal-r4" x="73.2" y="117.6" textLength="73.2" clip-path="url(#terminal-line-4)">Escape</text><text class="terminal-r1" x="146.4" y="117.6" textLength="402.6" clip-path="url(#terminal-line-4)"> Interrupt agent or close dialogs</text><text class="terminal-r1" x="1464" y="117.6" textLength="12.2" clip-path="url(#terminal-line-4)">
|
||||
</text><text class="terminal-r2" x="24.4" y="142" textLength="12.2" clip-path="url(#terminal-line-5)">⎢</text><text class="terminal-r1" x="48.8" y="142" textLength="24.4" clip-path="url(#terminal-line-5)">• </text><text class="terminal-r4" x="73.2" y="142" textLength="73.2" clip-path="url(#terminal-line-5)">Ctrl+C</text><text class="terminal-r1" x="146.4" y="142" textLength="463.6" clip-path="url(#terminal-line-5)"> Quit (or clear input if text present)</text><text class="terminal-r1" x="1464" y="142" textLength="12.2" clip-path="url(#terminal-line-5)">
|
||||
</text><text class="terminal-r2" x="24.4" y="166.4" textLength="12.2" clip-path="url(#terminal-line-6)">⎢</text><text class="terminal-r1" x="48.8" y="166.4" textLength="24.4" clip-path="url(#terminal-line-6)">• </text><text class="terminal-r4" x="73.2" y="166.4" textLength="73.2" clip-path="url(#terminal-line-6)">Ctrl+G</text><text class="terminal-r1" x="146.4" y="166.4" textLength="366" clip-path="url(#terminal-line-6)"> Edit input in external editor</text><text class="terminal-r1" x="1464" y="166.4" textLength="12.2" clip-path="url(#terminal-line-6)">
|
||||
</text><text class="terminal-r2" x="24.4" y="190.8" textLength="12.2" clip-path="url(#terminal-line-7)">⎢</text><text class="terminal-r1" x="48.8" y="190.8" textLength="24.4" clip-path="url(#terminal-line-7)">• </text><text class="terminal-r4" x="73.2" y="190.8" textLength="73.2" clip-path="url(#terminal-line-7)">Ctrl+O</text><text class="terminal-r1" x="146.4" y="190.8" textLength="292.8" clip-path="url(#terminal-line-7)"> Toggle tool output view</text><text class="terminal-r1" x="1464" y="190.8" textLength="12.2" clip-path="url(#terminal-line-7)">
|
||||
</text><text class="terminal-r2" x="24.4" y="215.2" textLength="12.2" clip-path="url(#terminal-line-8)">⎢</text><text class="terminal-r1" x="48.8" y="215.2" textLength="24.4" clip-path="url(#terminal-line-8)">• </text><text class="terminal-r4" x="73.2" y="215.2" textLength="109.8" clip-path="url(#terminal-line-8)">Shift+Tab</text><text class="terminal-r1" x="183" y="215.2" textLength="512.4" clip-path="url(#terminal-line-8)"> Cycle through agents (default, plan, ...)</text><text class="terminal-r1" x="1464" y="215.2" textLength="12.2" clip-path="url(#terminal-line-8)">
|
||||
</text><text class="terminal-r2" x="24.4" y="239.6" textLength="12.2" clip-path="url(#terminal-line-9)">⎢</text><text class="terminal-r1" x="48.8" y="239.6" textLength="24.4" clip-path="url(#terminal-line-9)">• </text><text class="terminal-r4" x="73.2" y="239.6" textLength="73.2" clip-path="url(#terminal-line-9)">Alt+↑↓</text><text class="terminal-r1" x="146.4" y="239.6" textLength="36.6" clip-path="url(#terminal-line-9)"> / </text><text class="terminal-r4" x="183" y="239.6" textLength="97.6" clip-path="url(#terminal-line-9)">Ctrl+P/N</text><text class="terminal-r1" x="280.6" y="239.6" textLength="390.4" clip-path="url(#terminal-line-9)"> Rewind to previous/next message</text><text class="terminal-r1" x="1464" y="239.6" textLength="12.2" clip-path="url(#terminal-line-9)">
|
||||
</text><text class="terminal-r2" x="24.4" y="264" textLength="12.2" clip-path="url(#terminal-line-10)">⎢</text><text class="terminal-r1" x="1464" y="264" textLength="12.2" clip-path="url(#terminal-line-10)">
|
||||
</text><text class="terminal-r2" x="24.4" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)">⎢</text><text class="terminal-r3" x="48.8" y="288.4" textLength="195.2" clip-path="url(#terminal-line-11)">Special Features</text><text class="terminal-r1" x="1464" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)">
|
||||
</text><text class="terminal-r2" x="24.4" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)">⎢</text><text class="terminal-r1" x="1464" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)">
|
||||
</text><text class="terminal-r2" x="24.4" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)">⎢</text><text class="terminal-r1" x="48.8" y="337.2" textLength="24.4" clip-path="url(#terminal-line-13)">• </text><text class="terminal-r4" x="73.2" y="337.2" textLength="122" clip-path="url(#terminal-line-13)">!<command></text><text class="terminal-r1" x="195.2" y="337.2" textLength="366" clip-path="url(#terminal-line-13)"> Execute bash command directly</text><text class="terminal-r1" x="1464" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)">
|
||||
</text><text class="terminal-r2" x="24.4" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)">⎢</text><text class="terminal-r1" x="48.8" y="361.6" textLength="24.4" clip-path="url(#terminal-line-14)">• </text><text class="terminal-r4" x="73.2" y="361.6" textLength="170.8" clip-path="url(#terminal-line-14)">@path/to/file/</text><text class="terminal-r1" x="244" y="361.6" textLength="305" clip-path="url(#terminal-line-14)"> Autocompletes file paths</text><text class="terminal-r1" x="1464" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)">
|
||||
</text><text class="terminal-r2" x="24.4" y="386" textLength="12.2" clip-path="url(#terminal-line-15)">⎢</text><text class="terminal-r1" x="1464" y="386" textLength="12.2" clip-path="url(#terminal-line-15)">
|
||||
</text><text class="terminal-r2" x="24.4" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)">⎢</text><text class="terminal-r3" x="48.8" y="410.4" textLength="97.6" clip-path="url(#terminal-line-16)">Commands</text><text class="terminal-r1" x="1464" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)">
|
||||
</text><text class="terminal-r2" x="24.4" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)">⎢</text><text class="terminal-r1" x="1464" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)">
|
||||
</text><text class="terminal-r2" x="24.4" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)">⎢</text><text class="terminal-r1" x="48.8" y="459.2" textLength="24.4" clip-path="url(#terminal-line-18)">• </text><text class="terminal-r4" x="73.2" y="459.2" textLength="61" clip-path="url(#terminal-line-18)">/help</text><text class="terminal-r1" x="134.2" y="459.2" textLength="231.8" clip-path="url(#terminal-line-18)">: Show help message</text><text class="terminal-r1" x="1464" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)">
|
||||
</text><text class="terminal-r2" x="24.4" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)">⎢</text><text class="terminal-r1" x="48.8" y="483.6" textLength="24.4" clip-path="url(#terminal-line-19)">• </text><text class="terminal-r4" x="73.2" y="483.6" textLength="85.4" clip-path="url(#terminal-line-19)">/config</text><text class="terminal-r1" x="158.6" y="483.6" textLength="268.4" clip-path="url(#terminal-line-19)">: Edit config settings</text><text class="terminal-r1" x="1464" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)">
|
||||
</text><text class="terminal-r2" x="24.4" y="508" textLength="12.2" clip-path="url(#terminal-line-20)">⎢</text><text class="terminal-r1" x="48.8" y="508" textLength="24.4" clip-path="url(#terminal-line-20)">• </text><text class="terminal-r4" x="73.2" y="508" textLength="73.2" clip-path="url(#terminal-line-20)">/model</text><text class="terminal-r1" x="146.4" y="508" textLength="256.2" clip-path="url(#terminal-line-20)">: Select active model</text><text class="terminal-r1" x="1464" y="508" textLength="12.2" clip-path="url(#terminal-line-20)">
|
||||
</text><text class="terminal-r2" x="24.4" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)">⎢</text><text class="terminal-r1" x="48.8" y="532.4" textLength="24.4" clip-path="url(#terminal-line-21)">• </text><text class="terminal-r4" x="73.2" y="532.4" textLength="109.8" clip-path="url(#terminal-line-21)">/thinking</text><text class="terminal-r1" x="183" y="532.4" textLength="280.6" clip-path="url(#terminal-line-21)">: Select thinking level</text><text class="terminal-r1" x="1464" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)">
|
||||
</text><text class="terminal-r2" x="24.4" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)">⎢</text><text class="terminal-r1" x="48.8" y="556.8" textLength="24.4" clip-path="url(#terminal-line-22)">• </text><text class="terminal-r4" x="73.2" y="556.8" textLength="85.4" clip-path="url(#terminal-line-22)">/reload</text><text class="terminal-r1" x="158.6" y="556.8" textLength="780.8" clip-path="url(#terminal-line-22)">: Reload configuration, agent instructions, and skills from disk</text><text class="terminal-r1" x="1464" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)">
|
||||
</text><text class="terminal-r2" x="24.4" y="581.2" textLength="12.2" clip-path="url(#terminal-line-23)">⎢</text><text class="terminal-r1" x="48.8" y="581.2" textLength="24.4" clip-path="url(#terminal-line-23)">• </text><text class="terminal-r4" x="73.2" y="581.2" textLength="73.2" clip-path="url(#terminal-line-23)">/clear</text><text class="terminal-r1" x="146.4" y="581.2" textLength="341.6" clip-path="url(#terminal-line-23)">: Clear conversation history</text><text class="terminal-r1" x="1464" y="581.2" textLength="12.2" clip-path="url(#terminal-line-23)">
|
||||
</text><text class="terminal-r2" x="24.4" y="605.6" textLength="12.2" clip-path="url(#terminal-line-24)">⎢</text><text class="terminal-r1" x="48.8" y="605.6" textLength="24.4" clip-path="url(#terminal-line-24)">• </text><text class="terminal-r4" x="73.2" y="605.6" textLength="61" clip-path="url(#terminal-line-24)">/copy</text><text class="terminal-r1" x="134.2" y="605.6" textLength="561.2" clip-path="url(#terminal-line-24)">: Copy the last agent message to the clipboard</text><text class="terminal-r1" x="1464" y="605.6" textLength="12.2" clip-path="url(#terminal-line-24)">
|
||||
</text><text class="terminal-r2" x="24.4" y="630" textLength="12.2" clip-path="url(#terminal-line-25)">⎢</text><text class="terminal-r1" x="48.8" y="630" textLength="24.4" clip-path="url(#terminal-line-25)">• </text><text class="terminal-r4" x="73.2" y="630" textLength="48.8" clip-path="url(#terminal-line-25)">/log</text><text class="terminal-r1" x="122" y="630" textLength="524.6" clip-path="url(#terminal-line-25)">: Show path to current interaction log file</text><text class="terminal-r1" x="1464" y="630" textLength="12.2" clip-path="url(#terminal-line-25)">
|
||||
</text><text class="terminal-r2" x="24.4" y="654.4" textLength="12.2" clip-path="url(#terminal-line-26)">⎢</text><text class="terminal-r1" x="48.8" y="654.4" textLength="24.4" clip-path="url(#terminal-line-26)">• </text><text class="terminal-r4" x="73.2" y="654.4" textLength="73.2" clip-path="url(#terminal-line-26)">/debug</text><text class="terminal-r1" x="146.4" y="654.4" textLength="268.4" clip-path="url(#terminal-line-26)">: Toggle debug console</text><text class="terminal-r1" x="1464" y="654.4" textLength="12.2" clip-path="url(#terminal-line-26)">
|
||||
</text><text class="terminal-r2" x="24.4" y="678.8" textLength="12.2" clip-path="url(#terminal-line-27)">⎢</text><text class="terminal-r1" x="48.8" y="678.8" textLength="24.4" clip-path="url(#terminal-line-27)">• </text><text class="terminal-r4" x="73.2" y="678.8" textLength="97.6" clip-path="url(#terminal-line-27)">/compact</text><text class="terminal-r1" x="170.8" y="678.8" textLength="1171.2" clip-path="url(#terminal-line-27)">: Compact conversation history by summarizing. Optionally pass instructions to guide the summary</text><text class="terminal-r1" x="1464" y="678.8" textLength="12.2" clip-path="url(#terminal-line-27)">
|
||||
</text><text class="terminal-r2" x="24.4" y="703.2" textLength="12.2" clip-path="url(#terminal-line-28)">⎢</text><text class="terminal-r1" x="48.8" y="703.2" textLength="24.4" clip-path="url(#terminal-line-28)">• </text><text class="terminal-r4" x="73.2" y="703.2" textLength="61" clip-path="url(#terminal-line-28)">/exit</text><text class="terminal-r1" x="134.2" y="703.2" textLength="268.4" clip-path="url(#terminal-line-28)">: Exit the application</text><text class="terminal-r1" x="1464" y="703.2" textLength="12.2" clip-path="url(#terminal-line-28)">
|
||||
</text><text class="terminal-r2" x="24.4" y="727.6" textLength="12.2" clip-path="url(#terminal-line-29)">⎢</text><text class="terminal-r1" x="48.8" y="727.6" textLength="24.4" clip-path="url(#terminal-line-29)">• </text><text class="terminal-r4" x="73.2" y="727.6" textLength="85.4" clip-path="url(#terminal-line-29)">/status</text><text class="terminal-r1" x="158.6" y="727.6" textLength="317.2" clip-path="url(#terminal-line-29)">: Display agent statistics</text><text class="terminal-r1" x="1464" y="727.6" textLength="12.2" clip-path="url(#terminal-line-29)">
|
||||
</text><text class="terminal-r2" x="24.4" y="752" textLength="12.2" clip-path="url(#terminal-line-30)">⎢</text><text class="terminal-r1" x="48.8" y="752" textLength="24.4" clip-path="url(#terminal-line-30)">• </text><text class="terminal-r4" x="73.2" y="752" textLength="146.4" clip-path="url(#terminal-line-30)">/proxy-setup</text><text class="terminal-r1" x="219.6" y="752" textLength="561.2" clip-path="url(#terminal-line-30)">: Configure proxy and SSL certificate settings</text><text class="terminal-r1" x="1464" y="752" textLength="12.2" clip-path="url(#terminal-line-30)">
|
||||
</text><text class="terminal-r2" x="24.4" y="776.4" textLength="12.2" clip-path="url(#terminal-line-31)">⎢</text><text class="terminal-r1" x="48.8" y="776.4" textLength="24.4" clip-path="url(#terminal-line-31)">• </text><text class="terminal-r4" x="73.2" y="776.4" textLength="109.8" clip-path="url(#terminal-line-31)">/continue</text><text class="terminal-r1" x="183" y="776.4" textLength="24.4" clip-path="url(#terminal-line-31)">, </text><text class="terminal-r4" x="207.4" y="776.4" textLength="85.4" clip-path="url(#terminal-line-31)">/resume</text><text class="terminal-r1" x="292.8" y="776.4" textLength="402.6" clip-path="url(#terminal-line-31)">: Browse and resume past sessions</text><text class="terminal-r1" x="1464" y="776.4" textLength="12.2" clip-path="url(#terminal-line-31)">
|
||||
</text><text class="terminal-r2" x="24.4" y="800.8" textLength="12.2" clip-path="url(#terminal-line-32)">⎢</text><text class="terminal-r1" x="48.8" y="800.8" textLength="24.4" clip-path="url(#terminal-line-32)">• </text><text class="terminal-r4" x="73.2" y="800.8" textLength="85.4" clip-path="url(#terminal-line-32)">/rename</text><text class="terminal-r1" x="158.6" y="800.8" textLength="341.6" clip-path="url(#terminal-line-32)">: Rename the current session</text><text class="terminal-r1" x="1464" y="800.8" textLength="12.2" clip-path="url(#terminal-line-32)">
|
||||
</text><text class="terminal-r2" x="24.4" y="825.2" textLength="12.2" clip-path="url(#terminal-line-33)">⎢</text><text class="terminal-r1" x="48.8" y="825.2" textLength="24.4" clip-path="url(#terminal-line-33)">• </text><text class="terminal-r4" x="73.2" y="825.2" textLength="134.2" clip-path="url(#terminal-line-33)">/connectors</text><text class="terminal-r1" x="207.4" y="825.2" textLength="24.4" clip-path="url(#terminal-line-33)">, </text><text class="terminal-r4" x="231.8" y="825.2" textLength="48.8" clip-path="url(#terminal-line-33)">/mcp</text><text class="terminal-r1" x="280.6" y="825.2" textLength="939.4" clip-path="url(#terminal-line-33)">: Display available MCP servers and connectors. Pass a name to list its tools</text><text class="terminal-r1" x="1464" y="825.2" textLength="12.2" clip-path="url(#terminal-line-33)">
|
||||
</text><text class="terminal-r2" x="24.4" y="849.6" textLength="12.2" clip-path="url(#terminal-line-34)">⎢</text><text class="terminal-r1" x="48.8" y="849.6" textLength="24.4" clip-path="url(#terminal-line-34)">• </text><text class="terminal-r4" x="73.2" y="849.6" textLength="73.2" clip-path="url(#terminal-line-34)">/voice</text><text class="terminal-r1" x="146.4" y="849.6" textLength="317.2" clip-path="url(#terminal-line-34)">: Configure voice settings</text><text class="terminal-r1" x="1464" y="849.6" textLength="12.2" clip-path="url(#terminal-line-34)">
|
||||
</text><text class="terminal-r2" x="24.4" y="874" textLength="12.2" clip-path="url(#terminal-line-35)">⎢</text><text class="terminal-r1" x="48.8" y="874" textLength="24.4" clip-path="url(#terminal-line-35)">• </text><text class="terminal-r4" x="73.2" y="874" textLength="122" clip-path="url(#terminal-line-35)">/leanstall</text><text class="terminal-r1" x="195.2" y="874" textLength="463.6" clip-path="url(#terminal-line-35)">: Install the Lean 4 agent (leanstral)</text><text class="terminal-r1" x="1464" y="874" textLength="12.2" clip-path="url(#terminal-line-35)">
|
||||
</text><text class="terminal-r2" x="24.4" y="898.4" textLength="12.2" clip-path="url(#terminal-line-36)">⎢</text><text class="terminal-r1" x="48.8" y="898.4" textLength="24.4" clip-path="url(#terminal-line-36)">• </text><text class="terminal-r4" x="73.2" y="898.4" textLength="146.4" clip-path="url(#terminal-line-36)">/unleanstall</text><text class="terminal-r1" x="219.6" y="898.4" textLength="341.6" clip-path="url(#terminal-line-36)">: Uninstall the Lean 4 agent</text><text class="terminal-r1" x="1464" y="898.4" textLength="12.2" clip-path="url(#terminal-line-36)">
|
||||
</text><text class="terminal-r2" x="24.4" y="922.8" textLength="12.2" clip-path="url(#terminal-line-37)">⎢</text><text class="terminal-r1" x="48.8" y="922.8" textLength="24.4" clip-path="url(#terminal-line-37)">• </text><text class="terminal-r4" x="73.2" y="922.8" textLength="85.4" clip-path="url(#terminal-line-37)">/rewind</text><text class="terminal-r1" x="158.6" y="922.8" textLength="366" clip-path="url(#terminal-line-37)">: Rewind to a previous message</text><text class="terminal-r1" x="1464" y="922.8" textLength="12.2" clip-path="url(#terminal-line-37)">
|
||||
</text><text class="terminal-r2" x="24.4" y="947.2" textLength="12.2" clip-path="url(#terminal-line-38)">⎢</text><text class="terminal-r1" x="48.8" y="947.2" textLength="24.4" clip-path="url(#terminal-line-38)">• </text><text class="terminal-r4" x="73.2" y="947.2" textLength="61" clip-path="url(#terminal-line-38)">/loop</text><text class="terminal-r1" x="134.2" y="947.2" textLength="427" clip-path="url(#terminal-line-38)">: Schedule a recurring prompt. Use </text><text class="terminal-r4" x="561.2" y="947.2" textLength="305" clip-path="url(#terminal-line-38)">/loop <interval> <prompt></text><text class="terminal-r1" x="866.2" y="947.2" textLength="24.4" clip-path="url(#terminal-line-38)">, </text><text class="terminal-r4" x="890.6" y="947.2" textLength="122" clip-path="url(#terminal-line-38)">/loop list</text><text class="terminal-r1" x="1012.6" y="947.2" textLength="61" clip-path="url(#terminal-line-38)">, or </text><text class="terminal-r4" x="1073.6" y="947.2" textLength="256.2" clip-path="url(#terminal-line-38)">/loop cancel <id|all></text><text class="terminal-r1" x="1464" y="947.2" textLength="12.2" clip-path="url(#terminal-line-38)">
|
||||
</text><text class="terminal-r2" x="24.4" y="971.6" textLength="12.2" clip-path="url(#terminal-line-39)">⎣</text><text class="terminal-r1" x="48.8" y="971.6" textLength="24.4" clip-path="url(#terminal-line-39)">• </text><text class="terminal-r4" x="73.2" y="971.6" textLength="183" clip-path="url(#terminal-line-39)">/data-retention</text><text class="terminal-r1" x="256.2" y="971.6" textLength="402.6" clip-path="url(#terminal-line-39)">: Show data retention information</text><text class="terminal-r1" x="1464" y="971.6" textLength="12.2" clip-path="url(#terminal-line-39)">
|
||||
</text><text class="terminal-r1" x="1464" y="996" textLength="12.2" clip-path="url(#terminal-line-40)">
|
||||
</text><text class="terminal-r1" x="1464" y="1020.4" textLength="12.2" clip-path="url(#terminal-line-41)">
|
||||
</text><text class="terminal-r4" x="0" y="1044.8" textLength="1464" clip-path="url(#terminal-line-42)">┌──────────────────────────────────────────────────────────────────────────────────────────────────────────── default ─┐</text><text class="terminal-r1" x="1464" y="1044.8" textLength="12.2" clip-path="url(#terminal-line-42)">
|
||||
</text><text class="terminal-r4" x="0" y="1069.2" textLength="12.2" clip-path="url(#terminal-line-43)">│</text><text class="terminal-r8" x="24.4" y="1069.2" textLength="12.2" clip-path="url(#terminal-line-43)">></text><text class="terminal-r4" x="1451.8" y="1069.2" textLength="12.2" clip-path="url(#terminal-line-43)">│</text><text class="terminal-r1" x="1464" y="1069.2" textLength="12.2" clip-path="url(#terminal-line-43)">
|
||||
</text><text class="terminal-r4" x="0" y="1093.6" textLength="12.2" clip-path="url(#terminal-line-44)">│</text><text class="terminal-r4" x="1451.8" y="1093.6" textLength="12.2" clip-path="url(#terminal-line-44)">│</text><text class="terminal-r1" x="1464" y="1093.6" textLength="12.2" clip-path="url(#terminal-line-44)">
|
||||
</text><text class="terminal-r4" x="0" y="1118" textLength="12.2" clip-path="url(#terminal-line-45)">│</text><text class="terminal-r4" x="1451.8" y="1118" textLength="12.2" clip-path="url(#terminal-line-45)">│</text><text class="terminal-r1" x="1464" y="1118" textLength="12.2" clip-path="url(#terminal-line-45)">
|
||||
</text><text class="terminal-r4" x="0" y="1142.4" textLength="1464" clip-path="url(#terminal-line-46)">└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘</text><text class="terminal-r1" x="1464" y="1142.4" textLength="12.2" clip-path="url(#terminal-line-46)">
|
||||
</text><text class="terminal-r4" x="0" y="1166.8" textLength="158.6" clip-path="url(#terminal-line-47)">/test/workdir</text><text class="terminal-r4" x="1256.6" y="1166.8" textLength="207.4" clip-path="url(#terminal-line-47)">0% of 200k tokens</text>
|
||||
</text><text class="terminal-r2" x="0" y="1044.8" textLength="1464" clip-path="url(#terminal-line-42)">┌──────────────────────────────────────────────────────────────────────────────────────────────────────────── default ─┐</text><text class="terminal-r1" x="1464" y="1044.8" textLength="12.2" clip-path="url(#terminal-line-42)">
|
||||
</text><text class="terminal-r2" x="0" y="1069.2" textLength="12.2" clip-path="url(#terminal-line-43)">│</text><text class="terminal-r7" x="24.4" y="1069.2" textLength="12.2" clip-path="url(#terminal-line-43)">></text><text class="terminal-r2" x="1451.8" y="1069.2" textLength="12.2" clip-path="url(#terminal-line-43)">│</text><text class="terminal-r1" x="1464" y="1069.2" textLength="12.2" clip-path="url(#terminal-line-43)">
|
||||
</text><text class="terminal-r2" x="0" y="1093.6" textLength="12.2" clip-path="url(#terminal-line-44)">│</text><text class="terminal-r2" x="1451.8" y="1093.6" textLength="12.2" clip-path="url(#terminal-line-44)">│</text><text class="terminal-r1" x="1464" y="1093.6" textLength="12.2" clip-path="url(#terminal-line-44)">
|
||||
</text><text class="terminal-r2" x="0" y="1118" textLength="12.2" clip-path="url(#terminal-line-45)">│</text><text class="terminal-r2" x="1451.8" y="1118" textLength="12.2" clip-path="url(#terminal-line-45)">│</text><text class="terminal-r1" x="1464" y="1118" textLength="12.2" clip-path="url(#terminal-line-45)">
|
||||
</text><text class="terminal-r2" x="0" y="1142.4" textLength="1464" clip-path="url(#terminal-line-46)">└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘</text><text class="terminal-r1" x="1464" y="1142.4" textLength="12.2" clip-path="url(#terminal-line-46)">
|
||||
</text><text class="terminal-r2" x="0" y="1166.8" textLength="158.6" clip-path="url(#terminal-line-47)">/test/workdir</text><text class="terminal-r2" x="1256.6" y="1166.8" textLength="207.4" clip-path="url(#terminal-line-47)">0% of 200k tokens</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
|
|
@ -196,47 +196,47 @@
|
|||
</g>
|
||||
|
||||
<g transform="translate(9, 41)" clip-path="url(#terminal-clip-terminal)">
|
||||
<rect fill="#4b4e55" x="1451.8" y="74.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="99.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="123.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="147.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="172.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="196.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="221.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="245.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="269.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="294.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="318.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="343.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="367.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="391.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="416.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="440.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="465.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="489.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="513.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="538.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="562.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="587.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="611.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="635.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="660.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="684.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="709.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="733.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="757.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="782.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="806.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="831.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="855.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="879.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="904.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="928.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="953.1" width="12.2" height="24.65" shape-rendering="crispEdges"/>
|
||||
<rect fill="#4b4e55" x="1451.8" y="99.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="123.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="147.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="172.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="196.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="221.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="245.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="269.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="294.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="318.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="343.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="367.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="391.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="416.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="440.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="465.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="489.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="513.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="538.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="562.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="587.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="611.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="635.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="660.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="684.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="709.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="733.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="757.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="782.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="806.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="831.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="855.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="879.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="904.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="928.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="953.1" width="12.2" height="24.65" shape-rendering="crispEdges"/>
|
||||
<g class="terminal-matrix">
|
||||
<text class="terminal-r1" x="1464" y="20" textLength="12.2" clip-path="url(#terminal-line-0)">
|
||||
</text><text class="terminal-r2" x="24.4" y="44.4" textLength="12.2" clip-path="url(#terminal-line-1)">⎢</text><text class="terminal-r3" x="48.8" y="44.4" textLength="219.6" clip-path="url(#terminal-line-1)">Keyboard Shortcuts</text><text class="terminal-r1" x="1464" y="44.4" textLength="12.2" clip-path="url(#terminal-line-1)">
|
||||
</text><text class="terminal-r2" x="24.4" y="68.8" textLength="12.2" clip-path="url(#terminal-line-2)">⎢</text><text class="terminal-r1" x="48.8" y="68.8" textLength="24.4" clip-path="url(#terminal-line-2)">• </text><text class="terminal-r4" x="73.2" y="68.8" textLength="61" clip-path="url(#terminal-line-2)">Enter</text><text class="terminal-r1" x="134.2" y="68.8" textLength="183" clip-path="url(#terminal-line-2)"> Submit message</text><text class="terminal-r5" x="1451.8" y="68.8" textLength="12.2" clip-path="url(#terminal-line-2)">▂</text><text class="terminal-r1" x="1464" y="68.8" textLength="12.2" clip-path="url(#terminal-line-2)">
|
||||
</text><text class="terminal-r2" x="24.4" y="93.2" textLength="12.2" clip-path="url(#terminal-line-3)">⎢</text><text class="terminal-r1" x="48.8" y="93.2" textLength="24.4" clip-path="url(#terminal-line-3)">• </text><text class="terminal-r4" x="73.2" y="93.2" textLength="73.2" clip-path="url(#terminal-line-3)">Ctrl+J</text><text class="terminal-r1" x="146.4" y="93.2" textLength="36.6" clip-path="url(#terminal-line-3)"> / </text><text class="terminal-r4" x="183" y="93.2" textLength="134.2" clip-path="url(#terminal-line-3)">Shift+Enter</text><text class="terminal-r1" x="317.2" y="93.2" textLength="183" clip-path="url(#terminal-line-3)"> Insert newline</text><text class="terminal-r1" x="1464" y="93.2" textLength="12.2" clip-path="url(#terminal-line-3)">
|
||||
</text><text class="terminal-r2" x="24.4" y="117.6" textLength="12.2" clip-path="url(#terminal-line-4)">⎢</text><text class="terminal-r1" x="48.8" y="117.6" textLength="24.4" clip-path="url(#terminal-line-4)">• </text><text class="terminal-r4" x="73.2" y="117.6" textLength="73.2" clip-path="url(#terminal-line-4)">Escape</text><text class="terminal-r1" x="146.4" y="117.6" textLength="402.6" clip-path="url(#terminal-line-4)"> Interrupt agent or close dialogs</text><text class="terminal-r1" x="1464" y="117.6" textLength="12.2" clip-path="url(#terminal-line-4)">
|
||||
</text><text class="terminal-r2" x="24.4" y="142" textLength="12.2" clip-path="url(#terminal-line-5)">⎢</text><text class="terminal-r1" x="48.8" y="142" textLength="24.4" clip-path="url(#terminal-line-5)">• </text><text class="terminal-r4" x="73.2" y="142" textLength="73.2" clip-path="url(#terminal-line-5)">Ctrl+C</text><text class="terminal-r1" x="146.4" y="142" textLength="463.6" clip-path="url(#terminal-line-5)"> Quit (or clear input if text present)</text><text class="terminal-r1" x="1464" y="142" textLength="12.2" clip-path="url(#terminal-line-5)">
|
||||
</text><text class="terminal-r2" x="24.4" y="166.4" textLength="12.2" clip-path="url(#terminal-line-6)">⎢</text><text class="terminal-r1" x="48.8" y="166.4" textLength="24.4" clip-path="url(#terminal-line-6)">• </text><text class="terminal-r4" x="73.2" y="166.4" textLength="73.2" clip-path="url(#terminal-line-6)">Ctrl+G</text><text class="terminal-r1" x="146.4" y="166.4" textLength="366" clip-path="url(#terminal-line-6)"> Edit input in external editor</text><text class="terminal-r1" x="1464" y="166.4" textLength="12.2" clip-path="url(#terminal-line-6)">
|
||||
</text><text class="terminal-r2" x="24.4" y="190.8" textLength="12.2" clip-path="url(#terminal-line-7)">⎢</text><text class="terminal-r1" x="48.8" y="190.8" textLength="24.4" clip-path="url(#terminal-line-7)">• </text><text class="terminal-r4" x="73.2" y="190.8" textLength="73.2" clip-path="url(#terminal-line-7)">Ctrl+O</text><text class="terminal-r1" x="146.4" y="190.8" textLength="292.8" clip-path="url(#terminal-line-7)"> Toggle tool output view</text><text class="terminal-r1" x="1464" y="190.8" textLength="12.2" clip-path="url(#terminal-line-7)">
|
||||
</text><text class="terminal-r2" x="24.4" y="215.2" textLength="12.2" clip-path="url(#terminal-line-8)">⎢</text><text class="terminal-r1" x="48.8" y="215.2" textLength="24.4" clip-path="url(#terminal-line-8)">• </text><text class="terminal-r4" x="73.2" y="215.2" textLength="109.8" clip-path="url(#terminal-line-8)">Shift+Tab</text><text class="terminal-r1" x="183" y="215.2" textLength="512.4" clip-path="url(#terminal-line-8)"> Cycle through agents (default, plan, ...)</text><text class="terminal-r1" x="1464" y="215.2" textLength="12.2" clip-path="url(#terminal-line-8)">
|
||||
</text><text class="terminal-r2" x="24.4" y="239.6" textLength="12.2" clip-path="url(#terminal-line-9)">⎢</text><text class="terminal-r1" x="48.8" y="239.6" textLength="24.4" clip-path="url(#terminal-line-9)">• </text><text class="terminal-r4" x="73.2" y="239.6" textLength="73.2" clip-path="url(#terminal-line-9)">Alt+↑↓</text><text class="terminal-r1" x="146.4" y="239.6" textLength="36.6" clip-path="url(#terminal-line-9)"> / </text><text class="terminal-r4" x="183" y="239.6" textLength="97.6" clip-path="url(#terminal-line-9)">Ctrl+P/N</text><text class="terminal-r1" x="280.6" y="239.6" textLength="390.4" clip-path="url(#terminal-line-9)"> Rewind to previous/next message</text><text class="terminal-r1" x="1464" y="239.6" textLength="12.2" clip-path="url(#terminal-line-9)">
|
||||
</text><text class="terminal-r2" x="24.4" y="264" textLength="12.2" clip-path="url(#terminal-line-10)">⎢</text><text class="terminal-r1" x="1464" y="264" textLength="12.2" clip-path="url(#terminal-line-10)">
|
||||
</text><text class="terminal-r2" x="24.4" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)">⎢</text><text class="terminal-r3" x="48.8" y="288.4" textLength="195.2" clip-path="url(#terminal-line-11)">Special Features</text><text class="terminal-r1" x="1464" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)">
|
||||
</text><text class="terminal-r2" x="24.4" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)">⎢</text><text class="terminal-r1" x="1464" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)">
|
||||
</text><text class="terminal-r2" x="24.4" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)">⎢</text><text class="terminal-r1" x="48.8" y="337.2" textLength="24.4" clip-path="url(#terminal-line-13)">• </text><text class="terminal-r4" x="73.2" y="337.2" textLength="122" clip-path="url(#terminal-line-13)">!<command></text><text class="terminal-r1" x="195.2" y="337.2" textLength="366" clip-path="url(#terminal-line-13)"> Execute bash command directly</text><text class="terminal-r1" x="1464" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)">
|
||||
</text><text class="terminal-r2" x="24.4" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)">⎢</text><text class="terminal-r1" x="48.8" y="361.6" textLength="24.4" clip-path="url(#terminal-line-14)">• </text><text class="terminal-r4" x="73.2" y="361.6" textLength="170.8" clip-path="url(#terminal-line-14)">@path/to/file/</text><text class="terminal-r1" x="244" y="361.6" textLength="305" clip-path="url(#terminal-line-14)"> Autocompletes file paths</text><text class="terminal-r1" x="1464" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)">
|
||||
</text><text class="terminal-r2" x="24.4" y="386" textLength="12.2" clip-path="url(#terminal-line-15)">⎢</text><text class="terminal-r1" x="1464" y="386" textLength="12.2" clip-path="url(#terminal-line-15)">
|
||||
</text><text class="terminal-r2" x="24.4" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)">⎢</text><text class="terminal-r3" x="48.8" y="410.4" textLength="97.6" clip-path="url(#terminal-line-16)">Commands</text><text class="terminal-r1" x="1464" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)">
|
||||
</text><text class="terminal-r2" x="24.4" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)">⎢</text><text class="terminal-r1" x="1464" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)">
|
||||
</text><text class="terminal-r2" x="24.4" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)">⎢</text><text class="terminal-r1" x="48.8" y="459.2" textLength="24.4" clip-path="url(#terminal-line-18)">• </text><text class="terminal-r4" x="73.2" y="459.2" textLength="61" clip-path="url(#terminal-line-18)">/help</text><text class="terminal-r1" x="134.2" y="459.2" textLength="231.8" clip-path="url(#terminal-line-18)">: Show help message</text><text class="terminal-r1" x="1464" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)">
|
||||
</text><text class="terminal-r2" x="24.4" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)">⎢</text><text class="terminal-r1" x="48.8" y="483.6" textLength="24.4" clip-path="url(#terminal-line-19)">• </text><text class="terminal-r4" x="73.2" y="483.6" textLength="85.4" clip-path="url(#terminal-line-19)">/config</text><text class="terminal-r1" x="158.6" y="483.6" textLength="268.4" clip-path="url(#terminal-line-19)">: Edit config settings</text><text class="terminal-r1" x="1464" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)">
|
||||
</text><text class="terminal-r2" x="24.4" y="508" textLength="12.2" clip-path="url(#terminal-line-20)">⎢</text><text class="terminal-r1" x="48.8" y="508" textLength="24.4" clip-path="url(#terminal-line-20)">• </text><text class="terminal-r4" x="73.2" y="508" textLength="73.2" clip-path="url(#terminal-line-20)">/model</text><text class="terminal-r1" x="146.4" y="508" textLength="256.2" clip-path="url(#terminal-line-20)">: Select active model</text><text class="terminal-r1" x="1464" y="508" textLength="12.2" clip-path="url(#terminal-line-20)">
|
||||
</text><text class="terminal-r2" x="24.4" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)">⎢</text><text class="terminal-r1" x="48.8" y="532.4" textLength="24.4" clip-path="url(#terminal-line-21)">• </text><text class="terminal-r4" x="73.2" y="532.4" textLength="109.8" clip-path="url(#terminal-line-21)">/thinking</text><text class="terminal-r1" x="183" y="532.4" textLength="280.6" clip-path="url(#terminal-line-21)">: Select thinking level</text><text class="terminal-r1" x="1464" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)">
|
||||
</text><text class="terminal-r2" x="24.4" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)">⎢</text><text class="terminal-r1" x="48.8" y="556.8" textLength="24.4" clip-path="url(#terminal-line-22)">• </text><text class="terminal-r4" x="73.2" y="556.8" textLength="85.4" clip-path="url(#terminal-line-22)">/reload</text><text class="terminal-r1" x="158.6" y="556.8" textLength="780.8" clip-path="url(#terminal-line-22)">: Reload configuration, agent instructions, and skills from disk</text><text class="terminal-r1" x="1464" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)">
|
||||
</text><text class="terminal-r2" x="24.4" y="581.2" textLength="12.2" clip-path="url(#terminal-line-23)">⎢</text><text class="terminal-r1" x="48.8" y="581.2" textLength="24.4" clip-path="url(#terminal-line-23)">• </text><text class="terminal-r4" x="73.2" y="581.2" textLength="73.2" clip-path="url(#terminal-line-23)">/clear</text><text class="terminal-r1" x="146.4" y="581.2" textLength="341.6" clip-path="url(#terminal-line-23)">: Clear conversation history</text><text class="terminal-r1" x="1464" y="581.2" textLength="12.2" clip-path="url(#terminal-line-23)">
|
||||
</text><text class="terminal-r2" x="24.4" y="605.6" textLength="12.2" clip-path="url(#terminal-line-24)">⎢</text><text class="terminal-r1" x="48.8" y="605.6" textLength="24.4" clip-path="url(#terminal-line-24)">• </text><text class="terminal-r4" x="73.2" y="605.6" textLength="61" clip-path="url(#terminal-line-24)">/copy</text><text class="terminal-r1" x="134.2" y="605.6" textLength="561.2" clip-path="url(#terminal-line-24)">: Copy the last agent message to the clipboard</text><text class="terminal-r1" x="1464" y="605.6" textLength="12.2" clip-path="url(#terminal-line-24)">
|
||||
</text><text class="terminal-r2" x="24.4" y="630" textLength="12.2" clip-path="url(#terminal-line-25)">⎢</text><text class="terminal-r1" x="48.8" y="630" textLength="24.4" clip-path="url(#terminal-line-25)">• </text><text class="terminal-r4" x="73.2" y="630" textLength="48.8" clip-path="url(#terminal-line-25)">/log</text><text class="terminal-r1" x="122" y="630" textLength="524.6" clip-path="url(#terminal-line-25)">: Show path to current interaction log file</text><text class="terminal-r1" x="1464" y="630" textLength="12.2" clip-path="url(#terminal-line-25)">
|
||||
</text><text class="terminal-r2" x="24.4" y="654.4" textLength="12.2" clip-path="url(#terminal-line-26)">⎢</text><text class="terminal-r1" x="48.8" y="654.4" textLength="24.4" clip-path="url(#terminal-line-26)">• </text><text class="terminal-r4" x="73.2" y="654.4" textLength="73.2" clip-path="url(#terminal-line-26)">/debug</text><text class="terminal-r1" x="146.4" y="654.4" textLength="268.4" clip-path="url(#terminal-line-26)">: Toggle debug console</text><text class="terminal-r1" x="1464" y="654.4" textLength="12.2" clip-path="url(#terminal-line-26)">
|
||||
</text><text class="terminal-r2" x="24.4" y="678.8" textLength="12.2" clip-path="url(#terminal-line-27)">⎢</text><text class="terminal-r1" x="48.8" y="678.8" textLength="24.4" clip-path="url(#terminal-line-27)">• </text><text class="terminal-r4" x="73.2" y="678.8" textLength="97.6" clip-path="url(#terminal-line-27)">/compact</text><text class="terminal-r1" x="170.8" y="678.8" textLength="1171.2" clip-path="url(#terminal-line-27)">: Compact conversation history by summarizing. Optionally pass instructions to guide the summary</text><text class="terminal-r1" x="1464" y="678.8" textLength="12.2" clip-path="url(#terminal-line-27)">
|
||||
</text><text class="terminal-r2" x="24.4" y="703.2" textLength="12.2" clip-path="url(#terminal-line-28)">⎢</text><text class="terminal-r1" x="48.8" y="703.2" textLength="24.4" clip-path="url(#terminal-line-28)">• </text><text class="terminal-r4" x="73.2" y="703.2" textLength="61" clip-path="url(#terminal-line-28)">/exit</text><text class="terminal-r1" x="134.2" y="703.2" textLength="268.4" clip-path="url(#terminal-line-28)">: Exit the application</text><text class="terminal-r1" x="1464" y="703.2" textLength="12.2" clip-path="url(#terminal-line-28)">
|
||||
</text><text class="terminal-r2" x="24.4" y="727.6" textLength="12.2" clip-path="url(#terminal-line-29)">⎢</text><text class="terminal-r1" x="48.8" y="727.6" textLength="24.4" clip-path="url(#terminal-line-29)">• </text><text class="terminal-r4" x="73.2" y="727.6" textLength="85.4" clip-path="url(#terminal-line-29)">/status</text><text class="terminal-r1" x="158.6" y="727.6" textLength="317.2" clip-path="url(#terminal-line-29)">: Display agent statistics</text><text class="terminal-r1" x="1464" y="727.6" textLength="12.2" clip-path="url(#terminal-line-29)">
|
||||
</text><text class="terminal-r2" x="24.4" y="752" textLength="12.2" clip-path="url(#terminal-line-30)">⎢</text><text class="terminal-r1" x="48.8" y="752" textLength="24.4" clip-path="url(#terminal-line-30)">• </text><text class="terminal-r4" x="73.2" y="752" textLength="109.8" clip-path="url(#terminal-line-30)">/teleport</text><text class="terminal-r1" x="183" y="752" textLength="378.2" clip-path="url(#terminal-line-30)">: Teleport session to Vibe Code</text><text class="terminal-r1" x="1464" y="752" textLength="12.2" clip-path="url(#terminal-line-30)">
|
||||
</text><text class="terminal-r2" x="24.4" y="776.4" textLength="12.2" clip-path="url(#terminal-line-31)">⎢</text><text class="terminal-r1" x="48.8" y="776.4" textLength="24.4" clip-path="url(#terminal-line-31)">• </text><text class="terminal-r4" x="73.2" y="776.4" textLength="146.4" clip-path="url(#terminal-line-31)">/proxy-setup</text><text class="terminal-r1" x="219.6" y="776.4" textLength="561.2" clip-path="url(#terminal-line-31)">: Configure proxy and SSL certificate settings</text><text class="terminal-r1" x="1464" y="776.4" textLength="12.2" clip-path="url(#terminal-line-31)">
|
||||
</text><text class="terminal-r2" x="24.4" y="800.8" textLength="12.2" clip-path="url(#terminal-line-32)">⎢</text><text class="terminal-r1" x="48.8" y="800.8" textLength="24.4" clip-path="url(#terminal-line-32)">• </text><text class="terminal-r4" x="73.2" y="800.8" textLength="109.8" clip-path="url(#terminal-line-32)">/continue</text><text class="terminal-r1" x="183" y="800.8" textLength="24.4" clip-path="url(#terminal-line-32)">, </text><text class="terminal-r4" x="207.4" y="800.8" textLength="85.4" clip-path="url(#terminal-line-32)">/resume</text><text class="terminal-r1" x="292.8" y="800.8" textLength="402.6" clip-path="url(#terminal-line-32)">: Browse and resume past sessions</text><text class="terminal-r1" x="1464" y="800.8" textLength="12.2" clip-path="url(#terminal-line-32)">
|
||||
</text><text class="terminal-r2" x="24.4" y="825.2" textLength="12.2" clip-path="url(#terminal-line-33)">⎢</text><text class="terminal-r1" x="48.8" y="825.2" textLength="24.4" clip-path="url(#terminal-line-33)">• </text><text class="terminal-r4" x="73.2" y="825.2" textLength="85.4" clip-path="url(#terminal-line-33)">/rename</text><text class="terminal-r1" x="158.6" y="825.2" textLength="341.6" clip-path="url(#terminal-line-33)">: Rename the current session</text><text class="terminal-r1" x="1464" y="825.2" textLength="12.2" clip-path="url(#terminal-line-33)">
|
||||
</text><text class="terminal-r2" x="24.4" y="849.6" textLength="12.2" clip-path="url(#terminal-line-34)">⎢</text><text class="terminal-r1" x="48.8" y="849.6" textLength="24.4" clip-path="url(#terminal-line-34)">• </text><text class="terminal-r4" x="73.2" y="849.6" textLength="134.2" clip-path="url(#terminal-line-34)">/connectors</text><text class="terminal-r1" x="207.4" y="849.6" textLength="24.4" clip-path="url(#terminal-line-34)">, </text><text class="terminal-r4" x="231.8" y="849.6" textLength="48.8" clip-path="url(#terminal-line-34)">/mcp</text><text class="terminal-r1" x="280.6" y="849.6" textLength="939.4" clip-path="url(#terminal-line-34)">: Display available MCP servers and connectors. Pass a name to list its tools</text><text class="terminal-r1" x="1464" y="849.6" textLength="12.2" clip-path="url(#terminal-line-34)">
|
||||
</text><text class="terminal-r2" x="24.4" y="874" textLength="12.2" clip-path="url(#terminal-line-35)">⎢</text><text class="terminal-r1" x="48.8" y="874" textLength="24.4" clip-path="url(#terminal-line-35)">• </text><text class="terminal-r4" x="73.2" y="874" textLength="73.2" clip-path="url(#terminal-line-35)">/voice</text><text class="terminal-r1" x="146.4" y="874" textLength="317.2" clip-path="url(#terminal-line-35)">: Configure voice settings</text><text class="terminal-r1" x="1464" y="874" textLength="12.2" clip-path="url(#terminal-line-35)">
|
||||
</text><text class="terminal-r2" x="24.4" y="898.4" textLength="12.2" clip-path="url(#terminal-line-36)">⎢</text><text class="terminal-r1" x="48.8" y="898.4" textLength="24.4" clip-path="url(#terminal-line-36)">• </text><text class="terminal-r4" x="73.2" y="898.4" textLength="122" clip-path="url(#terminal-line-36)">/leanstall</text><text class="terminal-r1" x="195.2" y="898.4" textLength="463.6" clip-path="url(#terminal-line-36)">: Install the Lean 4 agent (leanstral)</text><text class="terminal-r1" x="1464" y="898.4" textLength="12.2" clip-path="url(#terminal-line-36)">
|
||||
</text><text class="terminal-r2" x="24.4" y="922.8" textLength="12.2" clip-path="url(#terminal-line-37)">⎢</text><text class="terminal-r1" x="48.8" y="922.8" textLength="24.4" clip-path="url(#terminal-line-37)">• </text><text class="terminal-r4" x="73.2" y="922.8" textLength="146.4" clip-path="url(#terminal-line-37)">/unleanstall</text><text class="terminal-r1" x="219.6" y="922.8" textLength="341.6" clip-path="url(#terminal-line-37)">: Uninstall the Lean 4 agent</text><text class="terminal-r1" x="1464" y="922.8" textLength="12.2" clip-path="url(#terminal-line-37)">
|
||||
</text><text class="terminal-r2" x="24.4" y="947.2" textLength="12.2" clip-path="url(#terminal-line-38)">⎢</text><text class="terminal-r1" x="48.8" y="947.2" textLength="24.4" clip-path="url(#terminal-line-38)">• </text><text class="terminal-r4" x="73.2" y="947.2" textLength="85.4" clip-path="url(#terminal-line-38)">/rewind</text><text class="terminal-r1" x="158.6" y="947.2" textLength="366" clip-path="url(#terminal-line-38)">: Rewind to a previous message</text><text class="terminal-r1" x="1464" y="947.2" textLength="12.2" clip-path="url(#terminal-line-38)">
|
||||
<text class="terminal-r2" x="24.4" y="20" textLength="12.2" clip-path="url(#terminal-line-0)">⎢</text><text class="terminal-r3" x="48.8" y="20" textLength="219.6" clip-path="url(#terminal-line-0)">Keyboard Shortcuts</text><text class="terminal-r1" x="1464" y="20" textLength="12.2" clip-path="url(#terminal-line-0)">
|
||||
</text><text class="terminal-r2" x="24.4" y="44.4" textLength="12.2" clip-path="url(#terminal-line-1)">⎢</text><text class="terminal-r1" x="48.8" y="44.4" textLength="24.4" clip-path="url(#terminal-line-1)">• </text><text class="terminal-r4" x="73.2" y="44.4" textLength="61" clip-path="url(#terminal-line-1)">Enter</text><text class="terminal-r1" x="134.2" y="44.4" textLength="183" clip-path="url(#terminal-line-1)"> Submit message</text><text class="terminal-r1" x="1464" y="44.4" textLength="12.2" clip-path="url(#terminal-line-1)">
|
||||
</text><text class="terminal-r2" x="24.4" y="68.8" textLength="12.2" clip-path="url(#terminal-line-2)">⎢</text><text class="terminal-r1" x="48.8" y="68.8" textLength="24.4" clip-path="url(#terminal-line-2)">• </text><text class="terminal-r4" x="73.2" y="68.8" textLength="73.2" clip-path="url(#terminal-line-2)">Ctrl+J</text><text class="terminal-r1" x="146.4" y="68.8" textLength="36.6" clip-path="url(#terminal-line-2)"> / </text><text class="terminal-r4" x="183" y="68.8" textLength="134.2" clip-path="url(#terminal-line-2)">Shift+Enter</text><text class="terminal-r1" x="317.2" y="68.8" textLength="183" clip-path="url(#terminal-line-2)"> Insert newline</text><text class="terminal-r1" x="1464" y="68.8" textLength="12.2" clip-path="url(#terminal-line-2)">
|
||||
</text><text class="terminal-r2" x="24.4" y="93.2" textLength="12.2" clip-path="url(#terminal-line-3)">⎢</text><text class="terminal-r1" x="48.8" y="93.2" textLength="24.4" clip-path="url(#terminal-line-3)">• </text><text class="terminal-r4" x="73.2" y="93.2" textLength="73.2" clip-path="url(#terminal-line-3)">Escape</text><text class="terminal-r1" x="146.4" y="93.2" textLength="402.6" clip-path="url(#terminal-line-3)"> Interrupt agent or close dialogs</text><text class="terminal-r5" x="1451.8" y="93.2" textLength="12.2" clip-path="url(#terminal-line-3)">▃</text><text class="terminal-r1" x="1464" y="93.2" textLength="12.2" clip-path="url(#terminal-line-3)">
|
||||
</text><text class="terminal-r2" x="24.4" y="117.6" textLength="12.2" clip-path="url(#terminal-line-4)">⎢</text><text class="terminal-r1" x="48.8" y="117.6" textLength="24.4" clip-path="url(#terminal-line-4)">• </text><text class="terminal-r4" x="73.2" y="117.6" textLength="73.2" clip-path="url(#terminal-line-4)">Ctrl+C</text><text class="terminal-r1" x="146.4" y="117.6" textLength="463.6" clip-path="url(#terminal-line-4)"> Quit (or clear input if text present)</text><text class="terminal-r1" x="1464" y="117.6" textLength="12.2" clip-path="url(#terminal-line-4)">
|
||||
</text><text class="terminal-r2" x="24.4" y="142" textLength="12.2" clip-path="url(#terminal-line-5)">⎢</text><text class="terminal-r1" x="48.8" y="142" textLength="24.4" clip-path="url(#terminal-line-5)">• </text><text class="terminal-r4" x="73.2" y="142" textLength="73.2" clip-path="url(#terminal-line-5)">Ctrl+G</text><text class="terminal-r1" x="146.4" y="142" textLength="366" clip-path="url(#terminal-line-5)"> Edit input in external editor</text><text class="terminal-r1" x="1464" y="142" textLength="12.2" clip-path="url(#terminal-line-5)">
|
||||
</text><text class="terminal-r2" x="24.4" y="166.4" textLength="12.2" clip-path="url(#terminal-line-6)">⎢</text><text class="terminal-r1" x="48.8" y="166.4" textLength="24.4" clip-path="url(#terminal-line-6)">• </text><text class="terminal-r4" x="73.2" y="166.4" textLength="73.2" clip-path="url(#terminal-line-6)">Ctrl+O</text><text class="terminal-r1" x="146.4" y="166.4" textLength="292.8" clip-path="url(#terminal-line-6)"> Toggle tool output view</text><text class="terminal-r1" x="1464" y="166.4" textLength="12.2" clip-path="url(#terminal-line-6)">
|
||||
</text><text class="terminal-r2" x="24.4" y="190.8" textLength="12.2" clip-path="url(#terminal-line-7)">⎢</text><text class="terminal-r1" x="48.8" y="190.8" textLength="24.4" clip-path="url(#terminal-line-7)">• </text><text class="terminal-r4" x="73.2" y="190.8" textLength="109.8" clip-path="url(#terminal-line-7)">Shift+Tab</text><text class="terminal-r1" x="183" y="190.8" textLength="512.4" clip-path="url(#terminal-line-7)"> Cycle through agents (default, plan, ...)</text><text class="terminal-r1" x="1464" y="190.8" textLength="12.2" clip-path="url(#terminal-line-7)">
|
||||
</text><text class="terminal-r2" x="24.4" y="215.2" textLength="12.2" clip-path="url(#terminal-line-8)">⎢</text><text class="terminal-r1" x="48.8" y="215.2" textLength="24.4" clip-path="url(#terminal-line-8)">• </text><text class="terminal-r4" x="73.2" y="215.2" textLength="73.2" clip-path="url(#terminal-line-8)">Alt+↑↓</text><text class="terminal-r1" x="146.4" y="215.2" textLength="36.6" clip-path="url(#terminal-line-8)"> / </text><text class="terminal-r4" x="183" y="215.2" textLength="97.6" clip-path="url(#terminal-line-8)">Ctrl+P/N</text><text class="terminal-r1" x="280.6" y="215.2" textLength="390.4" clip-path="url(#terminal-line-8)"> Rewind to previous/next message</text><text class="terminal-r1" x="1464" y="215.2" textLength="12.2" clip-path="url(#terminal-line-8)">
|
||||
</text><text class="terminal-r2" x="24.4" y="239.6" textLength="12.2" clip-path="url(#terminal-line-9)">⎢</text><text class="terminal-r1" x="1464" y="239.6" textLength="12.2" clip-path="url(#terminal-line-9)">
|
||||
</text><text class="terminal-r2" x="24.4" y="264" textLength="12.2" clip-path="url(#terminal-line-10)">⎢</text><text class="terminal-r3" x="48.8" y="264" textLength="195.2" clip-path="url(#terminal-line-10)">Special Features</text><text class="terminal-r1" x="1464" y="264" textLength="12.2" clip-path="url(#terminal-line-10)">
|
||||
</text><text class="terminal-r2" x="24.4" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)">⎢</text><text class="terminal-r1" x="1464" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)">
|
||||
</text><text class="terminal-r2" x="24.4" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)">⎢</text><text class="terminal-r1" x="48.8" y="312.8" textLength="24.4" clip-path="url(#terminal-line-12)">• </text><text class="terminal-r4" x="73.2" y="312.8" textLength="122" clip-path="url(#terminal-line-12)">!<command></text><text class="terminal-r1" x="195.2" y="312.8" textLength="366" clip-path="url(#terminal-line-12)"> Execute bash command directly</text><text class="terminal-r1" x="1464" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)">
|
||||
</text><text class="terminal-r2" x="24.4" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)">⎢</text><text class="terminal-r1" x="48.8" y="337.2" textLength="24.4" clip-path="url(#terminal-line-13)">• </text><text class="terminal-r4" x="73.2" y="337.2" textLength="170.8" clip-path="url(#terminal-line-13)">@path/to/file/</text><text class="terminal-r1" x="244" y="337.2" textLength="305" clip-path="url(#terminal-line-13)"> Autocompletes file paths</text><text class="terminal-r1" x="1464" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)">
|
||||
</text><text class="terminal-r2" x="24.4" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)">⎢</text><text class="terminal-r1" x="1464" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)">
|
||||
</text><text class="terminal-r2" x="24.4" y="386" textLength="12.2" clip-path="url(#terminal-line-15)">⎢</text><text class="terminal-r3" x="48.8" y="386" textLength="97.6" clip-path="url(#terminal-line-15)">Commands</text><text class="terminal-r1" x="1464" y="386" textLength="12.2" clip-path="url(#terminal-line-15)">
|
||||
</text><text class="terminal-r2" x="24.4" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)">⎢</text><text class="terminal-r1" x="1464" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)">
|
||||
</text><text class="terminal-r2" x="24.4" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)">⎢</text><text class="terminal-r1" x="48.8" y="434.8" textLength="24.4" clip-path="url(#terminal-line-17)">• </text><text class="terminal-r4" x="73.2" y="434.8" textLength="61" clip-path="url(#terminal-line-17)">/help</text><text class="terminal-r1" x="134.2" y="434.8" textLength="231.8" clip-path="url(#terminal-line-17)">: Show help message</text><text class="terminal-r1" x="1464" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)">
|
||||
</text><text class="terminal-r2" x="24.4" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)">⎢</text><text class="terminal-r1" x="48.8" y="459.2" textLength="24.4" clip-path="url(#terminal-line-18)">• </text><text class="terminal-r4" x="73.2" y="459.2" textLength="85.4" clip-path="url(#terminal-line-18)">/config</text><text class="terminal-r1" x="158.6" y="459.2" textLength="268.4" clip-path="url(#terminal-line-18)">: Edit config settings</text><text class="terminal-r1" x="1464" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)">
|
||||
</text><text class="terminal-r2" x="24.4" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)">⎢</text><text class="terminal-r1" x="48.8" y="483.6" textLength="24.4" clip-path="url(#terminal-line-19)">• </text><text class="terminal-r4" x="73.2" y="483.6" textLength="73.2" clip-path="url(#terminal-line-19)">/model</text><text class="terminal-r1" x="146.4" y="483.6" textLength="256.2" clip-path="url(#terminal-line-19)">: Select active model</text><text class="terminal-r1" x="1464" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)">
|
||||
</text><text class="terminal-r2" x="24.4" y="508" textLength="12.2" clip-path="url(#terminal-line-20)">⎢</text><text class="terminal-r1" x="48.8" y="508" textLength="24.4" clip-path="url(#terminal-line-20)">• </text><text class="terminal-r4" x="73.2" y="508" textLength="109.8" clip-path="url(#terminal-line-20)">/thinking</text><text class="terminal-r1" x="183" y="508" textLength="280.6" clip-path="url(#terminal-line-20)">: Select thinking level</text><text class="terminal-r1" x="1464" y="508" textLength="12.2" clip-path="url(#terminal-line-20)">
|
||||
</text><text class="terminal-r2" x="24.4" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)">⎢</text><text class="terminal-r1" x="48.8" y="532.4" textLength="24.4" clip-path="url(#terminal-line-21)">• </text><text class="terminal-r4" x="73.2" y="532.4" textLength="85.4" clip-path="url(#terminal-line-21)">/reload</text><text class="terminal-r1" x="158.6" y="532.4" textLength="780.8" clip-path="url(#terminal-line-21)">: Reload configuration, agent instructions, and skills from disk</text><text class="terminal-r1" x="1464" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)">
|
||||
</text><text class="terminal-r2" x="24.4" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)">⎢</text><text class="terminal-r1" x="48.8" y="556.8" textLength="24.4" clip-path="url(#terminal-line-22)">• </text><text class="terminal-r4" x="73.2" y="556.8" textLength="73.2" clip-path="url(#terminal-line-22)">/clear</text><text class="terminal-r1" x="146.4" y="556.8" textLength="341.6" clip-path="url(#terminal-line-22)">: Clear conversation history</text><text class="terminal-r1" x="1464" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)">
|
||||
</text><text class="terminal-r2" x="24.4" y="581.2" textLength="12.2" clip-path="url(#terminal-line-23)">⎢</text><text class="terminal-r1" x="48.8" y="581.2" textLength="24.4" clip-path="url(#terminal-line-23)">• </text><text class="terminal-r4" x="73.2" y="581.2" textLength="61" clip-path="url(#terminal-line-23)">/copy</text><text class="terminal-r1" x="134.2" y="581.2" textLength="561.2" clip-path="url(#terminal-line-23)">: Copy the last agent message to the clipboard</text><text class="terminal-r1" x="1464" y="581.2" textLength="12.2" clip-path="url(#terminal-line-23)">
|
||||
</text><text class="terminal-r2" x="24.4" y="605.6" textLength="12.2" clip-path="url(#terminal-line-24)">⎢</text><text class="terminal-r1" x="48.8" y="605.6" textLength="24.4" clip-path="url(#terminal-line-24)">• </text><text class="terminal-r4" x="73.2" y="605.6" textLength="48.8" clip-path="url(#terminal-line-24)">/log</text><text class="terminal-r1" x="122" y="605.6" textLength="524.6" clip-path="url(#terminal-line-24)">: Show path to current interaction log file</text><text class="terminal-r1" x="1464" y="605.6" textLength="12.2" clip-path="url(#terminal-line-24)">
|
||||
</text><text class="terminal-r2" x="24.4" y="630" textLength="12.2" clip-path="url(#terminal-line-25)">⎢</text><text class="terminal-r1" x="48.8" y="630" textLength="24.4" clip-path="url(#terminal-line-25)">• </text><text class="terminal-r4" x="73.2" y="630" textLength="73.2" clip-path="url(#terminal-line-25)">/debug</text><text class="terminal-r1" x="146.4" y="630" textLength="268.4" clip-path="url(#terminal-line-25)">: Toggle debug console</text><text class="terminal-r1" x="1464" y="630" textLength="12.2" clip-path="url(#terminal-line-25)">
|
||||
</text><text class="terminal-r2" x="24.4" y="654.4" textLength="12.2" clip-path="url(#terminal-line-26)">⎢</text><text class="terminal-r1" x="48.8" y="654.4" textLength="24.4" clip-path="url(#terminal-line-26)">• </text><text class="terminal-r4" x="73.2" y="654.4" textLength="97.6" clip-path="url(#terminal-line-26)">/compact</text><text class="terminal-r1" x="170.8" y="654.4" textLength="1171.2" clip-path="url(#terminal-line-26)">: Compact conversation history by summarizing. Optionally pass instructions to guide the summary</text><text class="terminal-r1" x="1464" y="654.4" textLength="12.2" clip-path="url(#terminal-line-26)">
|
||||
</text><text class="terminal-r2" x="24.4" y="678.8" textLength="12.2" clip-path="url(#terminal-line-27)">⎢</text><text class="terminal-r1" x="48.8" y="678.8" textLength="24.4" clip-path="url(#terminal-line-27)">• </text><text class="terminal-r4" x="73.2" y="678.8" textLength="61" clip-path="url(#terminal-line-27)">/exit</text><text class="terminal-r1" x="134.2" y="678.8" textLength="268.4" clip-path="url(#terminal-line-27)">: Exit the application</text><text class="terminal-r1" x="1464" y="678.8" textLength="12.2" clip-path="url(#terminal-line-27)">
|
||||
</text><text class="terminal-r2" x="24.4" y="703.2" textLength="12.2" clip-path="url(#terminal-line-28)">⎢</text><text class="terminal-r1" x="48.8" y="703.2" textLength="24.4" clip-path="url(#terminal-line-28)">• </text><text class="terminal-r4" x="73.2" y="703.2" textLength="85.4" clip-path="url(#terminal-line-28)">/status</text><text class="terminal-r1" x="158.6" y="703.2" textLength="317.2" clip-path="url(#terminal-line-28)">: Display agent statistics</text><text class="terminal-r1" x="1464" y="703.2" textLength="12.2" clip-path="url(#terminal-line-28)">
|
||||
</text><text class="terminal-r2" x="24.4" y="727.6" textLength="12.2" clip-path="url(#terminal-line-29)">⎢</text><text class="terminal-r1" x="48.8" y="727.6" textLength="24.4" clip-path="url(#terminal-line-29)">• </text><text class="terminal-r4" x="73.2" y="727.6" textLength="109.8" clip-path="url(#terminal-line-29)">/teleport</text><text class="terminal-r1" x="183" y="727.6" textLength="378.2" clip-path="url(#terminal-line-29)">: Teleport session to Vibe Code</text><text class="terminal-r1" x="1464" y="727.6" textLength="12.2" clip-path="url(#terminal-line-29)">
|
||||
</text><text class="terminal-r2" x="24.4" y="752" textLength="12.2" clip-path="url(#terminal-line-30)">⎢</text><text class="terminal-r1" x="48.8" y="752" textLength="24.4" clip-path="url(#terminal-line-30)">• </text><text class="terminal-r4" x="73.2" y="752" textLength="146.4" clip-path="url(#terminal-line-30)">/proxy-setup</text><text class="terminal-r1" x="219.6" y="752" textLength="561.2" clip-path="url(#terminal-line-30)">: Configure proxy and SSL certificate settings</text><text class="terminal-r1" x="1464" y="752" textLength="12.2" clip-path="url(#terminal-line-30)">
|
||||
</text><text class="terminal-r2" x="24.4" y="776.4" textLength="12.2" clip-path="url(#terminal-line-31)">⎢</text><text class="terminal-r1" x="48.8" y="776.4" textLength="24.4" clip-path="url(#terminal-line-31)">• </text><text class="terminal-r4" x="73.2" y="776.4" textLength="109.8" clip-path="url(#terminal-line-31)">/continue</text><text class="terminal-r1" x="183" y="776.4" textLength="24.4" clip-path="url(#terminal-line-31)">, </text><text class="terminal-r4" x="207.4" y="776.4" textLength="85.4" clip-path="url(#terminal-line-31)">/resume</text><text class="terminal-r1" x="292.8" y="776.4" textLength="402.6" clip-path="url(#terminal-line-31)">: Browse and resume past sessions</text><text class="terminal-r1" x="1464" y="776.4" textLength="12.2" clip-path="url(#terminal-line-31)">
|
||||
</text><text class="terminal-r2" x="24.4" y="800.8" textLength="12.2" clip-path="url(#terminal-line-32)">⎢</text><text class="terminal-r1" x="48.8" y="800.8" textLength="24.4" clip-path="url(#terminal-line-32)">• </text><text class="terminal-r4" x="73.2" y="800.8" textLength="85.4" clip-path="url(#terminal-line-32)">/rename</text><text class="terminal-r1" x="158.6" y="800.8" textLength="341.6" clip-path="url(#terminal-line-32)">: Rename the current session</text><text class="terminal-r1" x="1464" y="800.8" textLength="12.2" clip-path="url(#terminal-line-32)">
|
||||
</text><text class="terminal-r2" x="24.4" y="825.2" textLength="12.2" clip-path="url(#terminal-line-33)">⎢</text><text class="terminal-r1" x="48.8" y="825.2" textLength="24.4" clip-path="url(#terminal-line-33)">• </text><text class="terminal-r4" x="73.2" y="825.2" textLength="134.2" clip-path="url(#terminal-line-33)">/connectors</text><text class="terminal-r1" x="207.4" y="825.2" textLength="24.4" clip-path="url(#terminal-line-33)">, </text><text class="terminal-r4" x="231.8" y="825.2" textLength="48.8" clip-path="url(#terminal-line-33)">/mcp</text><text class="terminal-r1" x="280.6" y="825.2" textLength="939.4" clip-path="url(#terminal-line-33)">: Display available MCP servers and connectors. Pass a name to list its tools</text><text class="terminal-r1" x="1464" y="825.2" textLength="12.2" clip-path="url(#terminal-line-33)">
|
||||
</text><text class="terminal-r2" x="24.4" y="849.6" textLength="12.2" clip-path="url(#terminal-line-34)">⎢</text><text class="terminal-r1" x="48.8" y="849.6" textLength="24.4" clip-path="url(#terminal-line-34)">• </text><text class="terminal-r4" x="73.2" y="849.6" textLength="73.2" clip-path="url(#terminal-line-34)">/voice</text><text class="terminal-r1" x="146.4" y="849.6" textLength="317.2" clip-path="url(#terminal-line-34)">: Configure voice settings</text><text class="terminal-r1" x="1464" y="849.6" textLength="12.2" clip-path="url(#terminal-line-34)">
|
||||
</text><text class="terminal-r2" x="24.4" y="874" textLength="12.2" clip-path="url(#terminal-line-35)">⎢</text><text class="terminal-r1" x="48.8" y="874" textLength="24.4" clip-path="url(#terminal-line-35)">• </text><text class="terminal-r4" x="73.2" y="874" textLength="122" clip-path="url(#terminal-line-35)">/leanstall</text><text class="terminal-r1" x="195.2" y="874" textLength="463.6" clip-path="url(#terminal-line-35)">: Install the Lean 4 agent (leanstral)</text><text class="terminal-r1" x="1464" y="874" textLength="12.2" clip-path="url(#terminal-line-35)">
|
||||
</text><text class="terminal-r2" x="24.4" y="898.4" textLength="12.2" clip-path="url(#terminal-line-36)">⎢</text><text class="terminal-r1" x="48.8" y="898.4" textLength="24.4" clip-path="url(#terminal-line-36)">• </text><text class="terminal-r4" x="73.2" y="898.4" textLength="146.4" clip-path="url(#terminal-line-36)">/unleanstall</text><text class="terminal-r1" x="219.6" y="898.4" textLength="341.6" clip-path="url(#terminal-line-36)">: Uninstall the Lean 4 agent</text><text class="terminal-r1" x="1464" y="898.4" textLength="12.2" clip-path="url(#terminal-line-36)">
|
||||
</text><text class="terminal-r2" x="24.4" y="922.8" textLength="12.2" clip-path="url(#terminal-line-37)">⎢</text><text class="terminal-r1" x="48.8" y="922.8" textLength="24.4" clip-path="url(#terminal-line-37)">• </text><text class="terminal-r4" x="73.2" y="922.8" textLength="85.4" clip-path="url(#terminal-line-37)">/rewind</text><text class="terminal-r1" x="158.6" y="922.8" textLength="366" clip-path="url(#terminal-line-37)">: Rewind to a previous message</text><text class="terminal-r1" x="1464" y="922.8" textLength="12.2" clip-path="url(#terminal-line-37)">
|
||||
</text><text class="terminal-r2" x="24.4" y="947.2" textLength="12.2" clip-path="url(#terminal-line-38)">⎢</text><text class="terminal-r1" x="48.8" y="947.2" textLength="24.4" clip-path="url(#terminal-line-38)">• </text><text class="terminal-r4" x="73.2" y="947.2" textLength="61" clip-path="url(#terminal-line-38)">/loop</text><text class="terminal-r1" x="134.2" y="947.2" textLength="427" clip-path="url(#terminal-line-38)">: Schedule a recurring prompt. Use </text><text class="terminal-r4" x="561.2" y="947.2" textLength="305" clip-path="url(#terminal-line-38)">/loop <interval> <prompt></text><text class="terminal-r1" x="866.2" y="947.2" textLength="24.4" clip-path="url(#terminal-line-38)">, </text><text class="terminal-r4" x="890.6" y="947.2" textLength="122" clip-path="url(#terminal-line-38)">/loop list</text><text class="terminal-r1" x="1012.6" y="947.2" textLength="61" clip-path="url(#terminal-line-38)">, or </text><text class="terminal-r4" x="1073.6" y="947.2" textLength="256.2" clip-path="url(#terminal-line-38)">/loop cancel <id|all></text><text class="terminal-r1" x="1464" y="947.2" textLength="12.2" clip-path="url(#terminal-line-38)">
|
||||
</text><text class="terminal-r2" x="24.4" y="971.6" textLength="12.2" clip-path="url(#terminal-line-39)">⎣</text><text class="terminal-r1" x="48.8" y="971.6" textLength="24.4" clip-path="url(#terminal-line-39)">• </text><text class="terminal-r4" x="73.2" y="971.6" textLength="183" clip-path="url(#terminal-line-39)">/data-retention</text><text class="terminal-r1" x="256.2" y="971.6" textLength="402.6" clip-path="url(#terminal-line-39)">: Show data retention information</text><text class="terminal-r1" x="1464" y="971.6" textLength="12.2" clip-path="url(#terminal-line-39)">
|
||||
</text><text class="terminal-r1" x="1464" y="996" textLength="12.2" clip-path="url(#terminal-line-40)">
|
||||
</text><text class="terminal-r1" x="1464" y="1020.4" textLength="12.2" clip-path="url(#terminal-line-41)">
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 36 KiB |
|
|
@ -11,6 +11,11 @@ def _pin_timezone(monkeypatch: pytest.MonkeyPatch) -> None:
|
|||
time.tzset()
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _pin_snapshot_colors(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.delenv("NO_COLOR", raising=False)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _pin_banner_version(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.setattr(
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ from tests.mock.utils import mock_llm_chunk
|
|||
from tests.snapshots.base_snapshot_test_app import BaseSnapshotTestApp
|
||||
from tests.snapshots.snap_compare import SnapCompare
|
||||
from tests.stubs.fake_backend import FakeBackend
|
||||
from vibe.core.telemetry.send import TelemetryClient
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
|
|
@ -20,6 +21,7 @@ def _enable_feedback_bar(monkeypatch: pytest.MonkeyPatch) -> None:
|
|||
"vibe.cli.textual_ui.widgets.feedback_bar_manager.MIN_USER_MESSAGES_FOR_FEEDBACK",
|
||||
1,
|
||||
)
|
||||
monkeypatch.setattr(TelemetryClient, "is_active", lambda self: True)
|
||||
|
||||
|
||||
class FeedbackBarSnapshotApp(BaseSnapshotTestApp):
|
||||
|
|
|
|||
|
|
@ -581,6 +581,60 @@ async def test_non_retryable_passes_through_non_streaming(observer_capture) -> N
|
|||
assert agent.session_logger.save_interaction.await_count == 1
|
||||
|
||||
|
||||
def _wrap_with_cause(message: str, cause: BaseException) -> RuntimeError:
|
||||
# Mirrors how Temporal raises ``ActivityError`` on the workflow side with
|
||||
# the original ``ApplicationError`` chained as ``__cause__`` — except we
|
||||
# don't import temporalio. The wrap-site check has to traverse the chain
|
||||
# to find ``non_retryable`` regardless of how deep it is.
|
||||
error = RuntimeError(message)
|
||||
error.__cause__ = cause
|
||||
return error
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_non_retryable_via_cause_chain_streaming(observer_capture) -> None:
|
||||
observed, observer = observer_capture
|
||||
wrapped = _wrap_with_cause(
|
||||
"Activity task failed", _NonRetryableError("auth failed")
|
||||
)
|
||||
backend = FakeBackend(exception_to_raise=wrapped)
|
||||
agent = build_test_agent_loop(
|
||||
config=make_config(),
|
||||
backend=backend,
|
||||
message_observer=observer,
|
||||
enable_streaming=True,
|
||||
)
|
||||
agent.session_logger.save_interaction = AsyncMock(return_value=None)
|
||||
|
||||
with pytest.raises(RuntimeError, match="Activity task failed"):
|
||||
[_ async for _ in agent.act("Trigger non-retryable via cause chain")]
|
||||
|
||||
assert [role for role, _ in observed] == [Role.system, Role.user]
|
||||
assert agent.session_logger.save_interaction.await_count == 1
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_non_retryable_via_cause_chain_non_streaming(observer_capture) -> None:
|
||||
observed, observer = observer_capture
|
||||
wrapped = _wrap_with_cause(
|
||||
"Activity task failed", _NonRetryableError("auth failed")
|
||||
)
|
||||
backend = FakeBackend(exception_to_raise=wrapped)
|
||||
agent = build_test_agent_loop(
|
||||
config=make_config(),
|
||||
backend=backend,
|
||||
message_observer=observer,
|
||||
enable_streaming=False,
|
||||
)
|
||||
agent.session_logger.save_interaction = AsyncMock(return_value=None)
|
||||
|
||||
with pytest.raises(RuntimeError, match="Activity task failed"):
|
||||
[_ async for _ in agent.act("Trigger non-retryable via cause chain")]
|
||||
|
||||
assert [role for role, _ in observed] == [Role.system, Role.user]
|
||||
assert agent.session_logger.save_interaction.await_count == 1
|
||||
|
||||
|
||||
def _snapshot_events(events: list) -> list[tuple[str, str]]:
|
||||
return [
|
||||
(type(e).__name__, e.content)
|
||||
|
|
|
|||
|
|
@ -80,8 +80,15 @@ def test_run_programmatic_preload_streaming_is_batched(
|
|||
new_session = [
|
||||
e for e in telemetry_events if e.get("event_name") == "vibe.new_session"
|
||||
]
|
||||
|
||||
assert len(new_session) == 0
|
||||
|
||||
session_closed = [
|
||||
e for e in telemetry_events if e.get("event_name") == "vibe.session_closed"
|
||||
]
|
||||
assert len(session_closed) == 1
|
||||
assert session_closed[0]["properties"]["agent_entrypoint"] == "programmatic"
|
||||
|
||||
assert (
|
||||
spy.emitted[0][1] == "You are Vibe, a super useful programming assistant."
|
||||
)
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ from __future__ import annotations
|
|||
import json
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from vibe.cli.history_manager import HistoryManager
|
||||
|
||||
|
||||
|
|
@ -74,6 +76,45 @@ def test_history_manager_stores_slash_prefixed_entries(tmp_path: Path) -> None:
|
|||
assert reloaded.get_previous(current_input="") is None
|
||||
|
||||
|
||||
def test_history_manager_keeps_entries_when_reload_read_fails(
|
||||
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
history_file = tmp_path / "history.jsonl"
|
||||
manager = HistoryManager(history_file)
|
||||
manager.add("first")
|
||||
|
||||
def raise_os_error(*args: object, **kwargs: object) -> None:
|
||||
raise OSError
|
||||
|
||||
monkeypatch.setattr("vibe.cli.history_manager.read_safe", raise_os_error)
|
||||
|
||||
manager.add("second")
|
||||
|
||||
assert manager.get_previous(current_input="") == "second"
|
||||
assert manager.get_previous(current_input="") == "first"
|
||||
|
||||
|
||||
def test_history_manager_resets_navigation_when_add_is_duplicate(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
history_file = tmp_path / "history.jsonl"
|
||||
manager = HistoryManager(history_file, max_entries=2)
|
||||
manager.add("a")
|
||||
manager.add("b")
|
||||
|
||||
other = HistoryManager(history_file, max_entries=10)
|
||||
other.add("c")
|
||||
other.add("d")
|
||||
other.add("e")
|
||||
|
||||
assert manager.get_previous(current_input="") == "b"
|
||||
manager.add("e")
|
||||
|
||||
assert manager.get_previous(current_input="") == "e"
|
||||
assert manager.get_previous(current_input="") == "d"
|
||||
assert manager.get_previous(current_input="") is None
|
||||
|
||||
|
||||
def test_history_manager_allows_navigation_round_trip(tmp_path: Path) -> None:
|
||||
history_file = tmp_path / "history.jsonl"
|
||||
manager = HistoryManager(history_file)
|
||||
|
|
|
|||
|
|
@ -48,19 +48,28 @@ def _otel_provider(monkeypatch: pytest.MonkeyPatch):
|
|||
|
||||
class TestSetupTracing:
|
||||
def test_noop_when_disabled(self) -> None:
|
||||
config = MagicMock(enable_otel=False)
|
||||
config = MagicMock(enable_telemetry=True, enable_otel=False)
|
||||
with patch("vibe.core.tracing.trace.set_tracer_provider") as mock_set:
|
||||
setup_tracing(config)
|
||||
mock_set.assert_not_called()
|
||||
|
||||
def test_noop_when_telemetry_disabled(self) -> None:
|
||||
config = MagicMock(enable_telemetry=False, enable_otel=True)
|
||||
with patch("vibe.core.tracing.trace.set_tracer_provider") as mock_set:
|
||||
setup_tracing(config)
|
||||
mock_set.assert_not_called()
|
||||
|
||||
def test_noop_when_exporter_config_is_none(self) -> None:
|
||||
config = MagicMock(enable_otel=True, otel_span_exporter_config=None)
|
||||
config = MagicMock(
|
||||
enable_telemetry=True, enable_otel=True, otel_span_exporter_config=None
|
||||
)
|
||||
with patch("vibe.core.tracing.trace.set_tracer_provider") as mock_set:
|
||||
setup_tracing(config)
|
||||
mock_set.assert_not_called()
|
||||
|
||||
def test_configures_provider_from_exporter_config(self) -> None:
|
||||
config = MagicMock(
|
||||
enable_telemetry=True,
|
||||
enable_otel=True,
|
||||
otel_span_exporter_config=OtelSpanExporterConfig(
|
||||
endpoint="https://customer.mistral.ai/telemetry/v1/traces",
|
||||
|
|
@ -85,6 +94,7 @@ class TestSetupTracing:
|
|||
|
||||
def test_custom_endpoint_has_no_auth_headers(self) -> None:
|
||||
config = MagicMock(
|
||||
enable_telemetry=True,
|
||||
enable_otel=True,
|
||||
otel_span_exporter_config=OtelSpanExporterConfig(
|
||||
endpoint="https://my-collector:4318/v1/traces"
|
||||
|
|
|
|||