chore: add ruff lint rules and turn on formatting
This commit is contained in:
@@ -17,5 +17,7 @@ jobs:
|
||||
cache-dependency-glob: "**/pyproject.toml"
|
||||
- name: Install dependencies
|
||||
run: uv sync --dev
|
||||
- name: Run ruff
|
||||
run: uv run ruff check .
|
||||
- name: Run ruff linter
|
||||
run: uv run ruff check --output-format=github .
|
||||
- name: Run ruff format
|
||||
run: uv run ruff format --check .
|
||||
|
||||
@@ -23,10 +23,10 @@ import os
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
from .config import get_effective_config, apply_config_to_env
|
||||
from .prompts import RESEARCHER_INSTRUCTIONS, get_system_prompt
|
||||
from . import paths as _paths_mod
|
||||
from .config import apply_config_to_env, get_effective_config
|
||||
from .paths import set_active_workspace, set_workspace_root
|
||||
from .prompts import RESEARCHER_INSTRUCTIONS, get_system_prompt
|
||||
|
||||
# Suppress noisy warnings from deepagents skill loader (non-string frontmatter fields, etc.)
|
||||
logging.getLogger("deepagents.middleware.skills").setLevel(logging.ERROR)
|
||||
@@ -153,8 +153,8 @@ def _build_prompt_refs() -> dict:
|
||||
|
||||
def _build_base_kwargs(base_backend, base_middleware):
|
||||
"""Build agent kwargs *without* MCP (fast, no subprocess spawning)."""
|
||||
from .tools import skill_manager, tavily_search, think_tool
|
||||
from .utils import load_subagents
|
||||
from .tools import tavily_search, think_tool, skill_manager
|
||||
|
||||
tool_registry = {"think_tool": think_tool}
|
||||
if os.environ.get("TAVILY_API_KEY"):
|
||||
@@ -185,8 +185,8 @@ def load_mcp_and_build_kwargs(base_backend, base_middleware):
|
||||
Re-connects to MCP servers only when the effective MCP config changes.
|
||||
Falls back to base kwargs if no MCP configured.
|
||||
"""
|
||||
from .tools import skill_manager, tavily_search, think_tool
|
||||
from .utils import load_subagents
|
||||
from .tools import tavily_search, think_tool, skill_manager
|
||||
|
||||
mcp_by_agent = _load_mcp_tools_cached()
|
||||
if not mcp_by_agent:
|
||||
@@ -237,7 +237,8 @@ def load_mcp_and_build_kwargs(base_backend, base_middleware):
|
||||
|
||||
def _get_default_backend():
|
||||
"""Build the default composite backend from current paths."""
|
||||
from deepagents.backends import FilesystemBackend, CompositeBackend
|
||||
from deepagents.backends import CompositeBackend, FilesystemBackend
|
||||
|
||||
from .backends import CustomSandboxBackend, MergedReadOnlyBackend
|
||||
|
||||
workspace_dir = str(_paths_mod.WORKSPACE_ROOT)
|
||||
@@ -269,7 +270,7 @@ def _get_default_backend():
|
||||
|
||||
def _get_default_middleware():
|
||||
"""Build the default middleware list."""
|
||||
from .middleware import create_memory_middleware, ToolErrorHandlerMiddleware
|
||||
from .middleware import ToolErrorHandlerMiddleware, create_memory_middleware
|
||||
|
||||
cfg = _ensure_config()
|
||||
memory_dir = str(_paths_mod.MEMORY_DIR)
|
||||
@@ -336,15 +337,18 @@ def create_cli_agent(workspace_dir: str | None = None, checkpointer=None, config
|
||||
import os as _os
|
||||
|
||||
from deepagents import create_deep_agent
|
||||
from deepagents.backends import FilesystemBackend, CompositeBackend
|
||||
from .backends import CustomSandboxBackend, MergedReadOnlyBackend
|
||||
from .middleware import create_memory_middleware, ToolErrorHandlerMiddleware
|
||||
from deepagents.backends import CompositeBackend, FilesystemBackend
|
||||
|
||||
from . import paths as _paths
|
||||
from .backends import CustomSandboxBackend, MergedReadOnlyBackend
|
||||
from .middleware import ToolErrorHandlerMiddleware, create_memory_middleware
|
||||
|
||||
cfg = _ensure_config(config)
|
||||
|
||||
if checkpointer is None:
|
||||
from langgraph.checkpoint.memory import InMemorySaver # type: ignore[import-untyped]
|
||||
from langgraph.checkpoint.memory import (
|
||||
InMemorySaver, # type: ignore[import-untyped]
|
||||
)
|
||||
|
||||
checkpointer = InMemorySaver()
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ from __future__ import annotations
|
||||
|
||||
from importlib import import_module
|
||||
|
||||
|
||||
_EXPORTS: dict[str, tuple[str, str]] = {
|
||||
# Agent graph (lazy to avoid expensive initialization at import time)
|
||||
"EvoScientist_agent": (".EvoScientist", "EvoScientist_agent"),
|
||||
|
||||
@@ -56,8 +56,8 @@ def _is_editable_install() -> bool:
|
||||
project root can shadow the real ``dist-info`` in site-packages.
|
||||
"""
|
||||
try:
|
||||
import json
|
||||
import importlib.metadata as _meta
|
||||
import json
|
||||
|
||||
for dist in _meta.distributions():
|
||||
name = dist.metadata.get("Name", "")
|
||||
|
||||
@@ -4,19 +4,19 @@ This module provides an extensible interface for different messaging channels
|
||||
(iMessage, Telegram, Discord, Slack, WeChat, DingTalk, Feishu, Email, QQ, Signal) to communicate with the EvoScientist agent.
|
||||
"""
|
||||
|
||||
from .base import Channel, RawIncoming, IncomingMessage, OutgoingMessage, chunk_text
|
||||
from .bus import MessageBus, InboundMessage, OutboundMessage
|
||||
from .base import Channel, IncomingMessage, OutgoingMessage, RawIncoming, chunk_text
|
||||
from .bus import InboundMessage, MessageBus, OutboundMessage
|
||||
from .capabilities import ChannelCapabilities
|
||||
from .channel_manager import (
|
||||
ChannelManager,
|
||||
register_channel,
|
||||
create_channel,
|
||||
available_channels,
|
||||
create_channel,
|
||||
register_channel,
|
||||
)
|
||||
from .consumer import InboundConsumer
|
||||
from .formatter import UnifiedFormatter
|
||||
from .middleware import TypingManager
|
||||
from .plugin import ChannelPlugin, ChannelMeta, ReloadPolicy
|
||||
from .plugin import ChannelMeta, ChannelPlugin, ReloadPolicy
|
||||
from .standalone import run_standalone
|
||||
|
||||
# Backward compat: ChannelServer is now Channel itself
|
||||
|
||||
@@ -4,23 +4,23 @@ This module defines the Channel interface that all messaging channels
|
||||
(iMessage, WeChat, etc.) must implement.
|
||||
"""
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
import asyncio
|
||||
import logging
|
||||
import re
|
||||
from abc import ABC, abstractmethod
|
||||
from collections import OrderedDict
|
||||
from collections.abc import Awaitable, Callable as CallableABC
|
||||
from collections.abc import AsyncIterator, Awaitable, Callable
|
||||
from collections.abc import Callable as CallableABC
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Any, AsyncIterator, Callable
|
||||
from typing import Any
|
||||
|
||||
from ..paths import MEDIA_DIR
|
||||
|
||||
from .bus.events import InboundMessage, OutboundMessage
|
||||
from .capabilities import ChannelCapabilities
|
||||
from .formatter import UnifiedFormatter
|
||||
from .plugin import ChannelPlugin, ChannelMeta
|
||||
from .plugin import ChannelMeta, ChannelPlugin
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -55,7 +55,7 @@ def chunk_text(text: str, limit: int) -> list[str]:
|
||||
# Effective limit is reduced if we need to add fences
|
||||
# We reserve ~20 chars for fences (```lang\n and \n```)
|
||||
effective_limit = limit - (20 if in_code_block else 0)
|
||||
|
||||
|
||||
if len(remaining) <= effective_limit:
|
||||
segment = remaining
|
||||
best = len(remaining)
|
||||
@@ -69,13 +69,13 @@ def chunk_text(text: str, limit: int) -> list[str]:
|
||||
pos = segment.rfind("\n\n")
|
||||
if pos > 0:
|
||||
best = pos
|
||||
|
||||
|
||||
# Line
|
||||
if best == -1:
|
||||
pos = segment.rfind("\n")
|
||||
if pos > 0:
|
||||
best = pos
|
||||
|
||||
|
||||
# Word
|
||||
if best == -1:
|
||||
pos = segment.rfind(" ")
|
||||
@@ -91,12 +91,12 @@ def chunk_text(text: str, limit: int) -> list[str]:
|
||||
best = effective_limit
|
||||
|
||||
chunk_raw = remaining[:best].rstrip()
|
||||
|
||||
|
||||
# Track state transitions within this raw segment
|
||||
starts_in_code = in_code_block
|
||||
current_lang = code_block_lang
|
||||
|
||||
# We use a simple count of ``` to toggle state.
|
||||
|
||||
# We use a simple count of ``` to toggle state.
|
||||
# Note: This handles both opening and closing fences.
|
||||
fences = list(re.finditer(r"```(\w*)", chunk_raw))
|
||||
for f in fences:
|
||||
@@ -106,17 +106,17 @@ def chunk_text(text: str, limit: int) -> list[str]:
|
||||
else:
|
||||
in_code_block = False
|
||||
code_block_lang = ""
|
||||
|
||||
|
||||
ends_in_code = in_code_block
|
||||
|
||||
# Build the final chunk with necessary fences
|
||||
prefix = f"```{current_lang}\n" if starts_in_code else ""
|
||||
suffix = "\n```" if ends_in_code else ""
|
||||
|
||||
|
||||
final_chunk = prefix + chunk_raw + suffix
|
||||
if final_chunk.strip():
|
||||
chunks.append(final_chunk)
|
||||
|
||||
|
||||
remaining = remaining[best:].lstrip("\n")
|
||||
|
||||
return chunks
|
||||
@@ -326,7 +326,7 @@ class Channel(ChannelPlugin, ABC):
|
||||
self._message_was_mentioned: dict[str, bool] = {}
|
||||
|
||||
# Retry configuration (auto-resolved from channel name)
|
||||
from .retry import RetryConfig, DEFAULT_RETRY, RETRY_PRESETS
|
||||
from .retry import DEFAULT_RETRY, RETRY_PRESETS, RetryConfig
|
||||
|
||||
self._retry_config: RetryConfig = RETRY_PRESETS.get(self.name, DEFAULT_RETRY)
|
||||
|
||||
@@ -349,11 +349,11 @@ class Channel(ChannelPlugin, ABC):
|
||||
5. MentionGatingMiddleware — filter by mention policy
|
||||
"""
|
||||
from .middleware import (
|
||||
DedupMiddleware,
|
||||
AllowListMiddleware,
|
||||
PairingMiddleware,
|
||||
DedupMiddleware,
|
||||
GroupHistoryMiddleware,
|
||||
MentionGatingMiddleware,
|
||||
PairingMiddleware,
|
||||
)
|
||||
|
||||
middlewares = []
|
||||
@@ -454,7 +454,7 @@ class Channel(ChannelPlugin, ABC):
|
||||
try:
|
||||
msg = await asyncio.wait_for(self._queue.get(), timeout=1.0)
|
||||
yield msg
|
||||
except asyncio.TimeoutError:
|
||||
except TimeoutError:
|
||||
continue
|
||||
|
||||
def _acquire_send_lock(self, chat_id: str) -> asyncio.Lock:
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
from .events import InboundMessage, OutboundMessage
|
||||
from .message_bus import MessageBus
|
||||
|
||||
__all__ = ["MessageBus", "InboundMessage", "OutboundMessage"]
|
||||
__all__ = ["InboundMessage", "MessageBus", "OutboundMessage"]
|
||||
|
||||
@@ -10,7 +10,7 @@ Deduplication is handled at the Channel level (single dedup point).
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
from typing import Callable, Awaitable
|
||||
from collections.abc import Awaitable, Callable
|
||||
|
||||
from .events import InboundMessage, OutboundMessage
|
||||
|
||||
@@ -72,7 +72,7 @@ class MessageBus:
|
||||
self.outbound.get(),
|
||||
timeout=1.0,
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
except TimeoutError:
|
||||
continue
|
||||
subscribers = self._outbound_subscribers.get(msg.channel, [])
|
||||
if not subscribers:
|
||||
|
||||
@@ -16,10 +16,11 @@ import json
|
||||
import logging
|
||||
import pkgutil
|
||||
import time
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Any, Callable
|
||||
from typing import Any
|
||||
|
||||
from .base import Channel, OutboundMessage
|
||||
from .bus import MessageBus
|
||||
@@ -352,7 +353,7 @@ class _HealthServer:
|
||||
self._process_request(reader, writer),
|
||||
timeout=self._CONNECTION_TIMEOUT,
|
||||
)
|
||||
except (asyncio.TimeoutError, ConnectionError, OSError):
|
||||
except (TimeoutError, ConnectionError, OSError):
|
||||
pass
|
||||
finally:
|
||||
try:
|
||||
@@ -448,7 +449,7 @@ def _parse_csv(value: str) -> set[str] | None:
|
||||
if not value or not value.strip():
|
||||
return None
|
||||
items = {s.strip() for s in value.split(",") if s.strip()}
|
||||
return items if items else None
|
||||
return items or None
|
||||
|
||||
|
||||
def register_channel(name: str, factory: ChannelFactory) -> None:
|
||||
@@ -596,7 +597,7 @@ class ChannelManager:
|
||||
self._shared_webhook_server: SharedWebhookServer | None = None
|
||||
|
||||
@classmethod
|
||||
def from_config(cls, config, bus: MessageBus | None = None) -> "ChannelManager":
|
||||
def from_config(cls, config, bus: MessageBus | None = None) -> ChannelManager:
|
||||
"""Create a ChannelManager from application config.
|
||||
|
||||
Parses ``config.channel_enabled`` (comma-separated channel types),
|
||||
@@ -820,7 +821,7 @@ class ChannelManager:
|
||||
self.bus.consume_outbound(),
|
||||
timeout=1.0,
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
except TimeoutError:
|
||||
continue
|
||||
except asyncio.CancelledError:
|
||||
break
|
||||
|
||||
@@ -13,8 +13,9 @@ import asyncio
|
||||
import logging
|
||||
import uuid
|
||||
from collections import OrderedDict
|
||||
from collections.abc import AsyncIterator, Callable
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, AsyncIterator, Callable, TypeVar
|
||||
from typing import Any, TypeVar
|
||||
|
||||
from .base import Channel
|
||||
from .bus import MessageBus
|
||||
@@ -307,7 +308,7 @@ class InboundConsumer:
|
||||
self.bus.consume_inbound(),
|
||||
timeout=1.0,
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
except TimeoutError:
|
||||
continue
|
||||
except asyncio.CancelledError:
|
||||
break
|
||||
@@ -577,7 +578,7 @@ class InboundConsumer:
|
||||
pending.event.wait(),
|
||||
timeout=_HITL_APPROVAL_TIMEOUT,
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
except TimeoutError:
|
||||
# Auto-approve on timeout
|
||||
pending.decision = "approve"
|
||||
finally:
|
||||
@@ -606,7 +607,7 @@ class InboundConsumer:
|
||||
)
|
||||
# continue to next HITL round
|
||||
|
||||
except asyncio.TimeoutError:
|
||||
except TimeoutError:
|
||||
self._metrics.total_timeouts += 1
|
||||
logger.error(
|
||||
f"Inference timeout ({self._inference_timeout}s idle) "
|
||||
@@ -678,7 +679,7 @@ class InboundConsumer:
|
||||
self._pending_ask_user_replies[session_key] = pending
|
||||
try:
|
||||
await asyncio.wait_for(pending.event.wait(), timeout=timeout)
|
||||
except asyncio.TimeoutError:
|
||||
except TimeoutError:
|
||||
pass
|
||||
finally:
|
||||
self._pending_ask_user_replies.pop(session_key, None)
|
||||
|
||||
@@ -9,15 +9,15 @@ Usage in config:
|
||||
dingtalk_client_secret = "your_app_secret"
|
||||
"""
|
||||
|
||||
from ..channel_manager import _parse_csv, register_channel
|
||||
from .channel import DingTalkChannel, DingTalkConfig
|
||||
from ..channel_manager import register_channel, _parse_csv
|
||||
|
||||
__all__ = ["DingTalkChannel", "DingTalkConfig"]
|
||||
|
||||
|
||||
def create_from_config(config) -> DingTalkChannel:
|
||||
allowed = _parse_csv(config.dingtalk_allowed_senders)
|
||||
proxy = config.dingtalk_proxy if config.dingtalk_proxy else None
|
||||
proxy = config.dingtalk_proxy or None
|
||||
return DingTalkChannel(
|
||||
DingTalkConfig(
|
||||
client_id=config.dingtalk_client_id,
|
||||
|
||||
@@ -3,15 +3,15 @@
|
||||
import asyncio
|
||||
import json
|
||||
import logging
|
||||
from urllib.parse import quote_plus
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from urllib.parse import quote_plus
|
||||
|
||||
from ..base import Channel, RawIncoming, ChannelError
|
||||
from ..base import Channel, ChannelError, RawIncoming
|
||||
from ..capabilities import DINGTALK as DINGTALK_CAPS
|
||||
from ..mixins import WebSocketMixin, TokenMixin
|
||||
from ..config import BaseChannelConfig
|
||||
from ..mixins import TokenMixin, WebSocketMixin
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -19,9 +19,9 @@ Examples:
|
||||
import argparse
|
||||
import logging
|
||||
|
||||
from .channel import DingTalkChannel, DingTalkConfig
|
||||
from ..bus import MessageBus
|
||||
from ..standalone import run_standalone
|
||||
from .channel import DingTalkChannel, DingTalkConfig
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.DEBUG,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from ..channel_manager import _parse_csv, register_channel
|
||||
from .channel import DiscordChannel, DiscordConfig
|
||||
from ..channel_manager import register_channel, _parse_csv
|
||||
|
||||
__all__ = ["DiscordChannel", "DiscordConfig"]
|
||||
|
||||
@@ -7,7 +7,7 @@ __all__ = ["DiscordChannel", "DiscordConfig"]
|
||||
def create_from_config(config) -> DiscordChannel:
|
||||
allowed = _parse_csv(config.discord_allowed_senders)
|
||||
channels = _parse_csv(config.discord_allowed_channels)
|
||||
proxy = config.discord_proxy if config.discord_proxy else None
|
||||
proxy = config.discord_proxy or None
|
||||
return DiscordChannel(
|
||||
DiscordConfig(
|
||||
bot_token=config.discord_bot_token,
|
||||
|
||||
@@ -6,7 +6,7 @@ import os
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
|
||||
from ..base import Channel, RawIncoming, ChannelError
|
||||
from ..base import Channel, ChannelError, RawIncoming
|
||||
from ..capabilities import DISCORD as DISCORD_CAPS
|
||||
from ..config import BaseChannelConfig
|
||||
|
||||
@@ -97,7 +97,7 @@ class DiscordChannel(Channel):
|
||||
|
||||
try:
|
||||
await asyncio.wait_for(self._ready.wait(), timeout=60)
|
||||
except asyncio.TimeoutError:
|
||||
except TimeoutError:
|
||||
raise ChannelError(
|
||||
"Discord bot failed to connect within 60s. "
|
||||
"Check network/proxy connectivity to gateway.discord.gg"
|
||||
|
||||
@@ -19,9 +19,9 @@ Examples:
|
||||
import argparse
|
||||
import logging
|
||||
|
||||
from .channel import DiscordChannel, DiscordConfig
|
||||
from ..bus import MessageBus
|
||||
from ..standalone import run_standalone
|
||||
from .channel import DiscordChannel, DiscordConfig
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.DEBUG,
|
||||
|
||||
@@ -9,8 +9,8 @@ Usage in config:
|
||||
...
|
||||
"""
|
||||
|
||||
from ..channel_manager import _parse_csv, register_channel
|
||||
from .channel import EmailChannel, EmailConfig
|
||||
from ..channel_manager import register_channel, _parse_csv
|
||||
|
||||
__all__ = ["EmailChannel", "EmailConfig"]
|
||||
|
||||
|
||||
@@ -21,10 +21,10 @@ from email.mime.text import MIMEText
|
||||
from email.utils import parseaddr
|
||||
from pathlib import Path
|
||||
|
||||
from ..base import Channel, RawIncoming, ChannelError
|
||||
from ..base import Channel, ChannelError, RawIncoming
|
||||
from ..capabilities import EMAIL as EMAIL_CAPS
|
||||
from ..mixins import PollingMixin
|
||||
from ..config import BaseChannelConfig
|
||||
from ..mixins import PollingMixin
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -37,9 +37,9 @@ def _decode_hdr(raw: str) -> str:
|
||||
|
||||
|
||||
def _strip_html(text: str) -> str:
|
||||
text = re.sub(r"<br\s*/?>", "\n", text, flags=re.I)
|
||||
text = re.sub(r"<p[^>]*>", "\n", text, flags=re.I)
|
||||
text = re.sub(r"</p>", "\n", text, flags=re.I)
|
||||
text = re.sub(r"<br\s*/?>", "\n", text, flags=re.IGNORECASE)
|
||||
text = re.sub(r"<p[^>]*>", "\n", text, flags=re.IGNORECASE)
|
||||
text = re.sub(r"</p>", "\n", text, flags=re.IGNORECASE)
|
||||
text = re.sub(r"<[^>]+>", "", text)
|
||||
return html.unescape(text).strip()
|
||||
|
||||
|
||||
@@ -19,9 +19,9 @@ Examples:
|
||||
import argparse
|
||||
import logging
|
||||
|
||||
from .channel import EmailChannel, EmailConfig
|
||||
from ..bus import MessageBus
|
||||
from ..standalone import run_standalone
|
||||
from .channel import EmailChannel, EmailConfig
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.DEBUG,
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
from ..channel_manager import _parse_csv, register_channel
|
||||
from .channel import FeishuChannel, FeishuConfig
|
||||
from ..channel_manager import register_channel, _parse_csv
|
||||
|
||||
__all__ = ["FeishuChannel", "FeishuConfig"]
|
||||
|
||||
|
||||
def create_from_config(config) -> FeishuChannel:
|
||||
allowed = _parse_csv(config.feishu_allowed_senders)
|
||||
proxy = config.feishu_proxy if config.feishu_proxy else None
|
||||
proxy = config.feishu_proxy or None
|
||||
return FeishuChannel(
|
||||
FeishuConfig(
|
||||
app_id=config.feishu_app_id,
|
||||
|
||||
@@ -23,18 +23,18 @@ import hashlib
|
||||
import json
|
||||
import logging
|
||||
import re
|
||||
from typing import Any, TYPE_CHECKING
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from aiohttp import web
|
||||
|
||||
from ..base import Channel, RawIncoming, ChannelError
|
||||
from ..base import Channel, ChannelError, RawIncoming
|
||||
from ..capabilities import FEISHU as FEISHU_CAPS
|
||||
from ..mixins import WebhookMixin, TokenMixin
|
||||
from ..config import BaseChannelConfig
|
||||
from ..mixins import TokenMixin, WebhookMixin
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -294,8 +294,8 @@ class FeishuChannel(Channel, WebhookMixin, TokenMixin):
|
||||
|
||||
async def start(self) -> None:
|
||||
try:
|
||||
from aiohttp import web # noqa: F401
|
||||
import httpx # noqa: F401
|
||||
from aiohttp import web # noqa: F401
|
||||
except ImportError:
|
||||
raise ChannelError(
|
||||
"aiohttp or httpx not installed. "
|
||||
@@ -611,7 +611,7 @@ class FeishuChannel(Channel, WebhookMixin, TokenMixin):
|
||||
|
||||
# ── Webhook event handler ─────────────────────────────────────
|
||||
|
||||
async def _handle_event(self, request) -> "web.Response":
|
||||
async def _handle_event(self, request) -> web.Response:
|
||||
"""Handle POST /webhook/event from Feishu."""
|
||||
from aiohttp import web
|
||||
|
||||
|
||||
@@ -20,9 +20,9 @@ Examples:
|
||||
import argparse
|
||||
import logging
|
||||
|
||||
from .channel import FeishuChannel, FeishuConfig
|
||||
from ..bus import MessageBus
|
||||
from ..standalone import run_standalone
|
||||
from .channel import FeishuChannel, FeishuConfig
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.DEBUG,
|
||||
|
||||
@@ -12,8 +12,7 @@ Channels no longer need per-file format functions — they just declare
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from typing import Callable
|
||||
|
||||
from collections.abc import Callable
|
||||
|
||||
# ═════════════════════════════════════════════════════════════════════
|
||||
# Markdown conversion engine (formerly markdown_utils.py)
|
||||
@@ -290,6 +289,6 @@ class UnifiedFormatter:
|
||||
return convert_markdown(text, **self._profile)
|
||||
|
||||
@classmethod
|
||||
def for_channel(cls, format_type: str) -> "UnifiedFormatter":
|
||||
def for_channel(cls, format_type: str) -> UnifiedFormatter:
|
||||
"""Factory: create a formatter for the given format type."""
|
||||
return cls(format_type)
|
||||
|
||||
@@ -9,28 +9,28 @@ Requirements:
|
||||
- Messages.app logged into iCloud
|
||||
"""
|
||||
|
||||
from ..channel_manager import _parse_csv, register_channel
|
||||
from .channel_rpc import IMessageChannelRpc as IMessageChannel
|
||||
from .channel_rpc import IMessageConfig
|
||||
from .probe import probe_imessage, ProbeResult
|
||||
from .probe import ProbeResult, probe_imessage
|
||||
from .targets import (
|
||||
parse_target,
|
||||
normalize_handle,
|
||||
normalize_e164,
|
||||
IMessageTarget,
|
||||
IMessageService,
|
||||
IMessageTarget,
|
||||
normalize_e164,
|
||||
normalize_handle,
|
||||
parse_target,
|
||||
)
|
||||
from ..channel_manager import register_channel, _parse_csv
|
||||
|
||||
__all__ = [
|
||||
"IMessageChannel",
|
||||
"IMessageConfig",
|
||||
"probe_imessage",
|
||||
"ProbeResult",
|
||||
"parse_target",
|
||||
"normalize_handle",
|
||||
"normalize_e164",
|
||||
"IMessageTarget",
|
||||
"IMessageService",
|
||||
"IMessageTarget",
|
||||
"ProbeResult",
|
||||
"normalize_e164",
|
||||
"normalize_handle",
|
||||
"parse_target",
|
||||
"probe_imessage",
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -10,15 +10,15 @@ from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
from ..base import Channel, RawIncoming, ChannelError
|
||||
from ..base import Channel, ChannelError, RawIncoming
|
||||
from ..config import BaseChannelConfig
|
||||
from .rpc_client import ImsgRpcClient, RpcNotification
|
||||
from .targets import (
|
||||
normalize_handle,
|
||||
parse_target,
|
||||
ChatIdTarget,
|
||||
ChatGuidTarget,
|
||||
ChatIdentifierTarget,
|
||||
ChatIdTarget,
|
||||
normalize_handle,
|
||||
parse_target,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -7,8 +7,9 @@ similar to OpenClaw's approach.
|
||||
import asyncio
|
||||
import json
|
||||
import logging
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Callable
|
||||
from typing import Any
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -96,7 +97,7 @@ class ImsgRpcClient:
|
||||
try:
|
||||
self._process.terminate()
|
||||
await asyncio.wait_for(self._process.wait(), timeout=2.0)
|
||||
except asyncio.TimeoutError:
|
||||
except TimeoutError:
|
||||
self._process.kill()
|
||||
await self._process.wait()
|
||||
|
||||
@@ -150,7 +151,7 @@ class ImsgRpcClient:
|
||||
|
||||
try:
|
||||
return await asyncio.wait_for(future, timeout=timeout)
|
||||
except asyncio.TimeoutError:
|
||||
except TimeoutError:
|
||||
self._pending.pop(request_id, None)
|
||||
raise Exception(f"RPC request timeout: {method}")
|
||||
|
||||
@@ -209,7 +210,7 @@ class ImsgRpcClient:
|
||||
if future is None:
|
||||
return
|
||||
|
||||
if "error" in data and data["error"]:
|
||||
if data.get("error"):
|
||||
error = data["error"]
|
||||
msg = error.get("message", "RPC error")
|
||||
future.set_exception(Exception(msg))
|
||||
|
||||
@@ -19,9 +19,9 @@ Examples:
|
||||
import argparse
|
||||
import logging
|
||||
|
||||
from . import IMessageChannel, IMessageConfig
|
||||
from ..bus import MessageBus
|
||||
from ..standalone import run_standalone
|
||||
from . import IMessageChannel, IMessageConfig
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -16,12 +16,12 @@ import logging
|
||||
import random
|
||||
import time
|
||||
from collections import OrderedDict, deque
|
||||
from collections.abc import Awaitable
|
||||
from collections.abc import Awaitable, Callable
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Callable
|
||||
from typing import Any
|
||||
|
||||
from .bus.events import InboundMessage, OutboundMessage
|
||||
from .base import RawIncoming
|
||||
from .bus.events import InboundMessage, OutboundMessage
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -543,8 +543,8 @@ class FormattingMiddleware(OutboundMiddlewareBase):
|
||||
"""
|
||||
|
||||
def __init__(self, capabilities: Any) -> None:
|
||||
from .formatter import UnifiedFormatter
|
||||
from .capabilities import ChannelCapabilities
|
||||
from .formatter import UnifiedFormatter
|
||||
|
||||
caps: ChannelCapabilities = capabilities
|
||||
self._formatter = UnifiedFormatter.for_channel(caps.format_type)
|
||||
|
||||
@@ -18,7 +18,6 @@ import logging
|
||||
import time
|
||||
from typing import Any
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -192,6 +191,7 @@ class WebSocketMixin:
|
||||
async def _ws_loop(self) -> None:
|
||||
"""Main WebSocket loop with auto-reconnect."""
|
||||
import os
|
||||
|
||||
import aiohttp
|
||||
|
||||
while getattr(self, "_running", False):
|
||||
|
||||
@@ -15,7 +15,6 @@ from typing import Any, Protocol, runtime_checkable
|
||||
|
||||
from .capabilities import ChannelCapabilities
|
||||
|
||||
|
||||
# ── Channel metadata ─────────────────────────────────────────────────
|
||||
|
||||
|
||||
|
||||
@@ -8,8 +8,8 @@ Usage in config:
|
||||
qq_app_secret = "your_app_secret"
|
||||
"""
|
||||
|
||||
from ..channel_manager import _parse_csv, register_channel
|
||||
from .channel import QQChannel, QQConfig
|
||||
from ..channel_manager import register_channel, _parse_csv
|
||||
|
||||
__all__ = ["QQChannel", "QQConfig"]
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ from collections import deque
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
|
||||
from ..base import Channel, RawIncoming, ChannelError
|
||||
from ..base import Channel, ChannelError, RawIncoming
|
||||
from ..capabilities import QQ as QQ_CAPS
|
||||
from ..config import BaseChannelConfig
|
||||
|
||||
@@ -64,7 +64,7 @@ class QQChannel(Channel):
|
||||
|
||||
def __init__(self, config: QQConfig):
|
||||
super().__init__(config)
|
||||
self._client: "botpy.Client | None" = None
|
||||
self._client: botpy.Client | None = None
|
||||
self._bot_task: asyncio.Task | None = None
|
||||
self._processed_ids: deque = deque(maxlen=1000)
|
||||
self._msg_seq: dict[str, int] = {} # msg_id -> next seq number
|
||||
|
||||
@@ -19,9 +19,9 @@ Examples:
|
||||
import argparse
|
||||
import logging
|
||||
|
||||
from .channel import QQChannel, QQConfig
|
||||
from ..bus import MessageBus
|
||||
from ..standalone import run_standalone
|
||||
from .channel import QQChannel, QQConfig
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.DEBUG,
|
||||
|
||||
@@ -105,7 +105,7 @@ async def retry_async(
|
||||
await asyncio.sleep(delay)
|
||||
|
||||
# Should never reach here, but satisfy the type checker.
|
||||
assert last_exc is not None # noqa: S101
|
||||
assert last_exc is not None
|
||||
raise last_exc
|
||||
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@ Usage in config:
|
||||
signal_phone_number = "+1234567890"
|
||||
"""
|
||||
|
||||
from ..channel_manager import _parse_csv, register_channel
|
||||
from .channel import SignalChannel, SignalConfig
|
||||
from ..channel_manager import register_channel, _parse_csv
|
||||
|
||||
__all__ = ["SignalChannel", "SignalConfig"]
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
from ..base import Channel, RawIncoming, ChannelError
|
||||
from ..base import Channel, ChannelError, RawIncoming
|
||||
from ..capabilities import SIGNAL as SIGNAL_CAPS
|
||||
from ..config import BaseChannelConfig
|
||||
|
||||
@@ -102,7 +102,7 @@ class SignalChannel(Channel):
|
||||
await writer.wait_closed()
|
||||
logger.info("signal-cli daemon already running")
|
||||
return
|
||||
except (ConnectionRefusedError, asyncio.TimeoutError, OSError):
|
||||
except (TimeoutError, ConnectionRefusedError, OSError):
|
||||
pass
|
||||
|
||||
# Start daemon
|
||||
@@ -437,7 +437,7 @@ class SignalChannel(Channel):
|
||||
|
||||
try:
|
||||
return await asyncio.wait_for(fut, timeout=timeout)
|
||||
except asyncio.TimeoutError:
|
||||
except TimeoutError:
|
||||
self._pending_rpcs.pop(rpc_id, None)
|
||||
logger.warning(f"Signal RPC '{method}' timed out after {timeout}s")
|
||||
return None
|
||||
|
||||
@@ -19,9 +19,9 @@ Examples:
|
||||
import argparse
|
||||
import logging
|
||||
|
||||
from .channel import SignalChannel, SignalConfig
|
||||
from ..bus import MessageBus
|
||||
from ..standalone import run_standalone
|
||||
from .channel import SignalChannel, SignalConfig
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.DEBUG,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from ..channel_manager import _parse_csv, register_channel
|
||||
from .channel import SlackChannel, SlackConfig
|
||||
from ..channel_manager import register_channel, _parse_csv
|
||||
|
||||
__all__ = ["SlackChannel", "SlackConfig"]
|
||||
|
||||
@@ -7,7 +7,7 @@ __all__ = ["SlackChannel", "SlackConfig"]
|
||||
def create_from_config(config) -> SlackChannel:
|
||||
allowed = _parse_csv(config.slack_allowed_senders)
|
||||
channels = _parse_csv(config.slack_allowed_channels)
|
||||
proxy = config.slack_proxy if config.slack_proxy else None
|
||||
proxy = config.slack_proxy or None
|
||||
return SlackChannel(
|
||||
SlackConfig(
|
||||
bot_token=config.slack_bot_token,
|
||||
|
||||
@@ -5,7 +5,7 @@ import logging
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
|
||||
from ..base import Channel, RawIncoming, ChannelError
|
||||
from ..base import Channel, ChannelError, RawIncoming
|
||||
from ..capabilities import SLACK as SLACK_CAPS
|
||||
from ..config import BaseChannelConfig
|
||||
|
||||
@@ -43,10 +43,10 @@ class SlackChannel(Channel):
|
||||
)
|
||||
|
||||
try:
|
||||
from slack_sdk.web.async_client import AsyncWebClient
|
||||
from slack_sdk.socket_mode.aiohttp import SocketModeClient
|
||||
from slack_sdk.socket_mode.request import SocketModeRequest
|
||||
from slack_sdk.socket_mode.response import SocketModeResponse
|
||||
from slack_sdk.web.async_client import AsyncWebClient
|
||||
except ImportError:
|
||||
raise ChannelError(
|
||||
"slack-sdk or aiohttp not installed. "
|
||||
@@ -65,7 +65,7 @@ class SlackChannel(Channel):
|
||||
timeout=15,
|
||||
)
|
||||
self._bot_user_id = auth["user_id"]
|
||||
except asyncio.TimeoutError:
|
||||
except TimeoutError:
|
||||
raise ChannelError(
|
||||
"Slack auth_test timed out — check network and bot token"
|
||||
)
|
||||
@@ -110,7 +110,7 @@ class SlackChannel(Channel):
|
||||
self._socket_client.connect(),
|
||||
timeout=30,
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
except TimeoutError:
|
||||
raise ChannelError(
|
||||
"Slack Socket Mode connection timed out — "
|
||||
"check app token (must start with xapp-) and "
|
||||
|
||||
@@ -19,9 +19,9 @@ Examples:
|
||||
import argparse
|
||||
import logging
|
||||
|
||||
from .channel import SlackChannel, SlackConfig
|
||||
from ..bus import MessageBus
|
||||
from ..standalone import run_standalone
|
||||
from .channel import SlackChannel, SlackConfig
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.DEBUG,
|
||||
|
||||
@@ -36,7 +36,7 @@ async def standalone_outbound_dispatcher(
|
||||
bus.consume_outbound(),
|
||||
timeout=1.0,
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
except TimeoutError:
|
||||
continue
|
||||
except asyncio.CancelledError:
|
||||
break
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
from ..channel_manager import _parse_csv, register_channel
|
||||
from .channel import TelegramChannel, TelegramConfig
|
||||
from ..channel_manager import register_channel, _parse_csv
|
||||
|
||||
__all__ = ["TelegramChannel", "TelegramConfig"]
|
||||
|
||||
|
||||
def create_from_config(config) -> TelegramChannel:
|
||||
allowed = _parse_csv(config.telegram_allowed_senders)
|
||||
proxy = config.telegram_proxy if config.telegram_proxy else None
|
||||
proxy = config.telegram_proxy or None
|
||||
return TelegramChannel(
|
||||
TelegramConfig(
|
||||
bot_token=config.telegram_bot_token,
|
||||
|
||||
@@ -6,12 +6,12 @@ from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
from ..base import (
|
||||
Channel,
|
||||
RawIncoming,
|
||||
ChannelError,
|
||||
AUDIO_EXTS,
|
||||
IMAGE_EXTS,
|
||||
VIDEO_EXTS,
|
||||
AUDIO_EXTS,
|
||||
Channel,
|
||||
ChannelError,
|
||||
RawIncoming,
|
||||
)
|
||||
from ..capabilities import TELEGRAM as TELEGRAM_CAPS
|
||||
from ..config import BaseChannelConfig
|
||||
|
||||
@@ -19,9 +19,9 @@ Examples:
|
||||
import argparse
|
||||
import logging
|
||||
|
||||
from .channel import TelegramChannel, TelegramConfig
|
||||
from ..bus import MessageBus
|
||||
from ..standalone import run_standalone
|
||||
from .channel import TelegramChannel, TelegramConfig
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.DEBUG,
|
||||
|
||||
@@ -29,16 +29,16 @@ Usage in config:
|
||||
wechat_webhook_port = 9001
|
||||
"""
|
||||
|
||||
from .channel import WeChatChannel, WeComConfig, WeChatMPConfig
|
||||
from ..channel_manager import register_channel, _parse_csv
|
||||
from ..channel_manager import _parse_csv, register_channel
|
||||
from .channel import WeChatChannel, WeChatMPConfig, WeComConfig
|
||||
|
||||
__all__ = ["WeChatChannel", "WeComConfig", "WeChatMPConfig"]
|
||||
__all__ = ["WeChatChannel", "WeChatMPConfig", "WeComConfig"]
|
||||
|
||||
|
||||
def create_from_config(config) -> WeChatChannel:
|
||||
backend = config.wechat_backend or "wecom"
|
||||
allowed = _parse_csv(config.wechat_allowed_senders)
|
||||
proxy = config.wechat_proxy if config.wechat_proxy else None
|
||||
proxy = config.wechat_proxy or None
|
||||
port = int(config.wechat_webhook_port or 9001)
|
||||
|
||||
if backend == "wechatmp":
|
||||
|
||||
@@ -27,15 +27,15 @@ import time
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Any, TYPE_CHECKING
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from aiohttp import web
|
||||
|
||||
from ..mixins import WebhookMixin, TokenMixin
|
||||
from ..base import Channel, RawIncoming, ChannelError
|
||||
from ..base import Channel, ChannelError, RawIncoming
|
||||
from ..capabilities import WECHAT as WECHAT_CAPS
|
||||
from ..config import BaseChannelConfig
|
||||
from ..mixins import TokenMixin, WebhookMixin
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -139,8 +139,8 @@ class WeChatChannel(Channel, WebhookMixin, TokenMixin):
|
||||
|
||||
async def start(self) -> None:
|
||||
try:
|
||||
import httpx
|
||||
from aiohttp import web
|
||||
import httpx # noqa: F401
|
||||
except ImportError:
|
||||
raise ChannelError(
|
||||
"aiohttp or httpx not installed. "
|
||||
@@ -274,7 +274,7 @@ class WeChatChannel(Channel, WebhookMixin, TokenMixin):
|
||||
|
||||
# ── Signature verification (GET callback) ─────────────────────
|
||||
|
||||
async def _handle_verify(self, request) -> "web.Response":
|
||||
async def _handle_verify(self, request) -> web.Response:
|
||||
"""Handle GET /wechat/callback for URL verification.
|
||||
|
||||
WeChat/WeCom sends: msg_signature, timestamp, nonce, echostr
|
||||
@@ -322,9 +322,10 @@ class WeChatChannel(Channel, WebhookMixin, TokenMixin):
|
||||
|
||||
# ── Inbound message handling (POST callback) ──────────────────
|
||||
|
||||
async def _handle_message(self, request) -> "web.Response":
|
||||
async def _handle_message(self, request) -> web.Response:
|
||||
"""Handle POST /wechat/callback for incoming messages."""
|
||||
from aiohttp import web
|
||||
|
||||
from .crypto import parse_xml
|
||||
|
||||
try:
|
||||
|
||||
@@ -30,9 +30,9 @@ Options:
|
||||
import argparse
|
||||
import logging
|
||||
|
||||
from .channel import WeChatChannel, WeComConfig, WeChatMPConfig
|
||||
from ..bus import MessageBus
|
||||
from ..standalone import run_standalone
|
||||
from .channel import WeChatChannel, WeChatMPConfig, WeComConfig
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.DEBUG,
|
||||
|
||||
@@ -89,7 +89,7 @@ class VerifyServer:
|
||||
try:
|
||||
await asyncio.wait_for(self._verified.wait(), timeout=timeout)
|
||||
return True
|
||||
except asyncio.TimeoutError:
|
||||
except TimeoutError:
|
||||
return False
|
||||
|
||||
@property
|
||||
|
||||
@@ -2,27 +2,26 @@
|
||||
|
||||
# Backward-compat re-exports (tests import these from EvoScientist.cli)
|
||||
from ..stream.state import ( # noqa: F401
|
||||
SubAgentState,
|
||||
StreamState,
|
||||
_parse_todo_items,
|
||||
SubAgentState,
|
||||
_build_todo_stats,
|
||||
_parse_todo_items,
|
||||
)
|
||||
from .channel import _channels_is_running, _channels_stop # noqa: F401
|
||||
from .agent import _deduplicate_run_name # noqa: F401
|
||||
|
||||
from ._app import app # noqa: F401
|
||||
from . import commands # noqa: F401 — registers @app.command decorators
|
||||
from ._app import app
|
||||
from ._constants import WELCOME_SLOGANS # noqa: F401
|
||||
from .agent import _deduplicate_run_name # noqa: F401
|
||||
from .channel import _channels_is_running, _channels_stop # noqa: F401
|
||||
|
||||
# UI runtime re-exports (merged from former tui/ package)
|
||||
from .tui_runtime import ( # noqa: F401
|
||||
DEFAULT_UI_BACKEND,
|
||||
SUPPORTED_UI_BACKENDS,
|
||||
get_backend,
|
||||
normalize_ui_backend,
|
||||
resolve_ui_backend,
|
||||
get_backend,
|
||||
run_streaming,
|
||||
)
|
||||
from ._constants import WELCOME_SLOGANS # noqa: F401
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""Shared constants and utilities for CLI and TUI modules."""
|
||||
|
||||
from datetime import datetime, timezone
|
||||
from datetime import UTC, datetime
|
||||
|
||||
from ..sessions import AGENT_NAME
|
||||
|
||||
@@ -35,7 +35,7 @@ def build_metadata(workspace_dir: str | None, model: str | None) -> dict:
|
||||
"""Build metadata dict for LangGraph checkpoint persistence."""
|
||||
return {
|
||||
"agent_name": AGENT_NAME,
|
||||
"updated_at": datetime.now(timezone.utc).isoformat(),
|
||||
"updated_at": datetime.now(UTC).isoformat(),
|
||||
"workspace_dir": workspace_dir or "",
|
||||
"model": model or "",
|
||||
}
|
||||
|
||||
@@ -12,11 +12,11 @@ for the main thread to set a response via ``_set_channel_response()``.
|
||||
import asyncio
|
||||
import logging
|
||||
import queue
|
||||
import time
|
||||
import threading
|
||||
import time
|
||||
import uuid
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Optional
|
||||
from typing import Any
|
||||
|
||||
from rich.panel import Panel
|
||||
from rich.table import Table
|
||||
@@ -263,11 +263,11 @@ def channel_hitl_prompt(
|
||||
|
||||
Returns approval decisions list on approve/auto, or None on reject/timeout.
|
||||
"""
|
||||
from ..channels.bus.events import OutboundMessage
|
||||
from ..channels.consumer import (
|
||||
_format_approval_prompt,
|
||||
_parse_approval_reply,
|
||||
)
|
||||
from ..channels.bus.events import OutboundMessage
|
||||
|
||||
# Check session auto-approve (set by a previous "3" reply)
|
||||
session_key = f"{msg.channel_type}:{msg.chat_id}"
|
||||
@@ -333,11 +333,11 @@ def channel_hitl_prompt(
|
||||
# Module-level channel state (bus mode)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
_manager: Optional[Any] = None # ChannelManager
|
||||
_bus_loop: Optional[asyncio.AbstractEventLoop] = None
|
||||
_bus_thread: Optional[threading.Thread] = None
|
||||
_manager: Any | None = None # ChannelManager
|
||||
_bus_loop: asyncio.AbstractEventLoop | None = None
|
||||
_bus_thread: threading.Thread | None = None
|
||||
_cli_agent: Any = None # shared agent reference (same as CLI)
|
||||
_cli_thread_id: Optional[str] = None # shared thread_id (same conversation)
|
||||
_cli_thread_id: str | None = None # shared thread_id (same conversation)
|
||||
|
||||
|
||||
def _channels_is_running(channel_type: str | None = None) -> bool:
|
||||
@@ -498,7 +498,7 @@ async def _bus_inbound_consumer(bus, manager) -> None:
|
||||
while True:
|
||||
try:
|
||||
msg = await asyncio.wait_for(bus.consume_inbound(), timeout=1.0)
|
||||
except asyncio.TimeoutError:
|
||||
except TimeoutError:
|
||||
continue
|
||||
except asyncio.CancelledError:
|
||||
break
|
||||
|
||||
@@ -7,20 +7,19 @@ import re
|
||||
from datetime import datetime
|
||||
from importlib.metadata import version as _pkg_version
|
||||
from pathlib import Path
|
||||
from typing import Any, Optional
|
||||
from typing import Any
|
||||
|
||||
import typer # type: ignore[import-untyped]
|
||||
from rich.markup import escape
|
||||
from rich.table import Table
|
||||
|
||||
from rich.markup import escape
|
||||
|
||||
from ..stream.display import console
|
||||
from ..paths import ensure_dirs, set_workspace_root
|
||||
from ._app import app, config_app, mcp_app, channel_app
|
||||
from ..stream.display import console
|
||||
from ._app import app, channel_app, config_app, mcp_app
|
||||
from ._constants import build_metadata
|
||||
from .agent import (
|
||||
_deduplicate_run_name,
|
||||
_create_session_workspace,
|
||||
_deduplicate_run_name,
|
||||
_load_agent,
|
||||
_shorten_path,
|
||||
)
|
||||
@@ -33,16 +32,15 @@ from .channel import (
|
||||
channel_ask_user_prompt,
|
||||
channel_hitl_prompt,
|
||||
)
|
||||
from .tui_runtime import run_streaming
|
||||
from .interactive import cmd_interactive, cmd_run
|
||||
from .mcp_ui import (
|
||||
_mcp_list_servers,
|
||||
_mcp_add_server_from_kwargs,
|
||||
_mcp_edit_server_fields,
|
||||
_mcp_list_servers,
|
||||
_mcp_remove_server,
|
||||
_show_mcp_config,
|
||||
)
|
||||
from .interactive import cmd_interactive, cmd_run
|
||||
|
||||
from .tui_runtime import run_streaming
|
||||
|
||||
# =============================================================================
|
||||
# Onboard command
|
||||
@@ -119,15 +117,15 @@ class CompactResult:
|
||||
"""
|
||||
|
||||
__slots__ = (
|
||||
"status",
|
||||
"message",
|
||||
"messages_compacted",
|
||||
"messages_kept",
|
||||
"tokens_before",
|
||||
"pct_decrease",
|
||||
"status",
|
||||
"tokens_after",
|
||||
"tokens_before",
|
||||
"tokens_summarized",
|
||||
"tokens_summary",
|
||||
"pct_decrease",
|
||||
)
|
||||
|
||||
def __init__(
|
||||
@@ -243,13 +241,14 @@ async def compact_conversation(agent: Any, thread_id: str | None) -> CompactResu
|
||||
"noop", "Nothing to compact — no messages in conversation."
|
||||
)
|
||||
|
||||
from ..EvoScientist import _ensure_chat_model, _get_default_backend
|
||||
from deepagents.middleware.summarization import (
|
||||
SummarizationEvent,
|
||||
SummarizationMiddleware,
|
||||
compute_summarization_defaults,
|
||||
)
|
||||
|
||||
from ..EvoScientist import _ensure_chat_model, _get_default_backend
|
||||
|
||||
try:
|
||||
model = _ensure_chat_model()
|
||||
except Exception as exc:
|
||||
@@ -476,7 +475,7 @@ def serve(
|
||||
no_thinking: bool = typer.Option(
|
||||
False, "--no-thinking", help="Disable thinking relay to channels"
|
||||
),
|
||||
workdir: Optional[str] = typer.Option(
|
||||
workdir: str | None = typer.Option(
|
||||
None, "--workdir", help="Override workspace directory"
|
||||
),
|
||||
auto_approve: bool = typer.Option(
|
||||
@@ -499,7 +498,7 @@ def serve(
|
||||
|
||||
nest_asyncio.apply()
|
||||
|
||||
from ..config import get_effective_config, apply_config_to_env
|
||||
from ..config import apply_config_to_env, get_effective_config
|
||||
|
||||
cli_overrides = {}
|
||||
if auto_approve:
|
||||
@@ -594,7 +593,7 @@ def config_callback(ctx: typer.Context):
|
||||
@config_app.command("list")
|
||||
def config_list():
|
||||
"""List all configuration values"""
|
||||
from ..config import list_config, get_config_path
|
||||
from ..config import get_config_path, list_config
|
||||
|
||||
config_data = list_config()
|
||||
|
||||
@@ -658,7 +657,7 @@ def config_reset(
|
||||
yes: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation prompt"),
|
||||
):
|
||||
"""Reset configuration to defaults"""
|
||||
from ..config import reset_config, get_config_path
|
||||
from ..config import get_config_path, reset_config
|
||||
|
||||
config_path = get_config_path()
|
||||
|
||||
@@ -707,7 +706,7 @@ def mcp_list():
|
||||
|
||||
@mcp_app.command("config")
|
||||
def mcp_config(
|
||||
name: Optional[str] = typer.Argument(None, help="Server name (omit to show all)"),
|
||||
name: str | None = typer.Argument(None, help="Server name (omit to show all)"),
|
||||
):
|
||||
"""Show detailed configuration for MCP servers
|
||||
|
||||
@@ -730,28 +729,26 @@ def mcp_config(
|
||||
def mcp_add(
|
||||
name: str = typer.Argument(..., help="Server name"),
|
||||
target: str = typer.Argument(..., help="Command (stdio) or URL (http/sse)"),
|
||||
args: Optional[list[str]] = typer.Argument(
|
||||
None, help="Extra args for stdio command"
|
||||
),
|
||||
transport: Optional[str] = typer.Option(
|
||||
args: list[str] | None = typer.Argument(None, help="Extra args for stdio command"),
|
||||
transport: str | None = typer.Option(
|
||||
None, "--transport", "-T", help="Transport type (default: auto-detect)"
|
||||
),
|
||||
tools: Optional[str] = typer.Option(
|
||||
tools: str | None = typer.Option(
|
||||
None,
|
||||
"--tools",
|
||||
"-t",
|
||||
help="Comma-separated tool allowlist (supports wildcards: *_exa, read_*)",
|
||||
),
|
||||
expose_to: Optional[str] = typer.Option(
|
||||
expose_to: str | None = typer.Option(
|
||||
None, "--expose-to", "-e", help="Comma-separated target agents"
|
||||
),
|
||||
header: Optional[list[str]] = typer.Option(
|
||||
header: list[str] | None = typer.Option(
|
||||
None, "--header", "-H", help="HTTP header as Key:Value (repeatable)"
|
||||
),
|
||||
env: Optional[list[str]] = typer.Option(
|
||||
env: list[str] | None = typer.Option(
|
||||
None, "--env", help="Env var as KEY=VALUE for stdio (repeatable)"
|
||||
),
|
||||
env_ref: Optional[list[str]] = typer.Option(
|
||||
env_ref: list[str] | None = typer.Option(
|
||||
None, "--env-ref", help="Env var name as ${NAME} runtime ref (repeatable)"
|
||||
),
|
||||
):
|
||||
@@ -804,31 +801,27 @@ def mcp_add(
|
||||
@mcp_app.command("edit")
|
||||
def mcp_edit(
|
||||
name: str = typer.Argument(..., help="Server name to edit"),
|
||||
transport: Optional[str] = typer.Option(
|
||||
transport: str | None = typer.Option(
|
||||
None, "--transport", help="New transport type"
|
||||
),
|
||||
command: Optional[str] = typer.Option(
|
||||
None, "--command", help="New command (stdio)"
|
||||
),
|
||||
url: Optional[str] = typer.Option(
|
||||
None, "--url", help="New URL (http/sse/websocket)"
|
||||
),
|
||||
tools: Optional[str] = typer.Option(
|
||||
command: str | None = typer.Option(None, "--command", help="New command (stdio)"),
|
||||
url: str | None = typer.Option(None, "--url", help="New URL (http/sse/websocket)"),
|
||||
tools: str | None = typer.Option(
|
||||
None,
|
||||
"--tools",
|
||||
"-t",
|
||||
help="Comma-separated tool allowlist, supports wildcards ('none' to clear)",
|
||||
),
|
||||
expose_to: Optional[str] = typer.Option(
|
||||
expose_to: str | None = typer.Option(
|
||||
None,
|
||||
"--expose-to",
|
||||
"-e",
|
||||
help="Comma-separated target agents ('none' to clear)",
|
||||
),
|
||||
header: Optional[list[str]] = typer.Option(
|
||||
header: list[str] | None = typer.Option(
|
||||
None, "--header", "-H", help="HTTP header as Key:Value (repeatable)"
|
||||
),
|
||||
env: Optional[list[str]] = typer.Option(
|
||||
env: list[str] | None = typer.Option(
|
||||
None, "--env", help="Env var as KEY=VALUE for stdio (repeatable)"
|
||||
),
|
||||
):
|
||||
@@ -897,7 +890,7 @@ def _version_callback(value: bool):
|
||||
@app.callback(invoke_without_command=True)
|
||||
def _main_callback(
|
||||
ctx: typer.Context,
|
||||
version: Optional[bool] = typer.Option(
|
||||
version: bool | None = typer.Option(
|
||||
None,
|
||||
"-V",
|
||||
"--version",
|
||||
@@ -905,25 +898,25 @@ def _main_callback(
|
||||
is_eager=True,
|
||||
help="Show version and exit.",
|
||||
),
|
||||
mode: Optional[str] = typer.Option(
|
||||
mode: str | None = typer.Option(
|
||||
None,
|
||||
"-m",
|
||||
"--mode",
|
||||
help="Workspace mode: 'daemon' (persistent, default) or 'run' (isolated per-session)",
|
||||
),
|
||||
name: Optional[str] = typer.Option(
|
||||
name: str | None = typer.Option(
|
||||
None,
|
||||
"-n",
|
||||
"--name",
|
||||
help="Name for this run (used as directory name instead of timestamp; requires --mode run)",
|
||||
),
|
||||
prompt: Optional[str] = typer.Option(
|
||||
prompt: str | None = typer.Option(
|
||||
None, "-p", "--prompt", help="Query to execute (single-shot mode)"
|
||||
),
|
||||
thread_id: Optional[str] = typer.Option(
|
||||
thread_id: str | None = typer.Option(
|
||||
None, "--thread-id", help="Thread ID for conversation persistence"
|
||||
),
|
||||
workdir: Optional[str] = typer.Option(
|
||||
workdir: str | None = typer.Option(
|
||||
None, "--workdir", help="Override workspace directory for this session"
|
||||
),
|
||||
use_cwd: bool = typer.Option(
|
||||
@@ -942,12 +935,12 @@ def _main_callback(
|
||||
"--ask-user",
|
||||
help="Enable agent to ask clarifying questions about your research preferences",
|
||||
),
|
||||
auth_mode: Optional[str] = typer.Option(
|
||||
auth_mode: str | None = typer.Option(
|
||||
None,
|
||||
"--auth-mode",
|
||||
help="Auth mode for Anthropic/OpenAI: api_key (default) or oauth (ccproxy).",
|
||||
),
|
||||
ui: Optional[str] = typer.Option(
|
||||
ui: str | None = typer.Option(
|
||||
None,
|
||||
"--ui",
|
||||
help="UI backend: tui (default) or cli.",
|
||||
@@ -959,7 +952,7 @@ def _main_callback(
|
||||
return
|
||||
|
||||
# Load and apply configuration
|
||||
from ..config import get_effective_config, apply_config_to_env
|
||||
from ..config import apply_config_to_env, get_effective_config
|
||||
|
||||
# Build CLI overrides dict
|
||||
cli_overrides = {}
|
||||
@@ -1101,7 +1094,8 @@ def _main_callback(
|
||||
if prompt:
|
||||
# Single-shot mode: wrap in persistent checkpointer
|
||||
import asyncio
|
||||
from ..sessions import get_checkpointer, generate_thread_id
|
||||
|
||||
from ..sessions import generate_thread_id, get_checkpointer
|
||||
|
||||
async def _single_shot():
|
||||
async with get_checkpointer() as checkpointer:
|
||||
|
||||
@@ -9,49 +9,55 @@ from typing import Any
|
||||
|
||||
import typer # type: ignore[import-untyped]
|
||||
from prompt_toolkit import PromptSession # type: ignore[import-untyped]
|
||||
from prompt_toolkit.completion import Completer, Completion # type: ignore[import-untyped]
|
||||
from prompt_toolkit.history import FileHistory # type: ignore[import-untyped]
|
||||
from prompt_toolkit.auto_suggest import AutoSuggestFromHistory # type: ignore[import-untyped]
|
||||
from prompt_toolkit.auto_suggest import (
|
||||
AutoSuggestFromHistory, # type: ignore[import-untyped]
|
||||
)
|
||||
from prompt_toolkit.completion import ( # type: ignore[import-untyped]
|
||||
Completer,
|
||||
Completion,
|
||||
)
|
||||
from prompt_toolkit.formatted_text import HTML # type: ignore[import-untyped]
|
||||
from prompt_toolkit.history import FileHistory # type: ignore[import-untyped]
|
||||
from prompt_toolkit.shortcuts import CompleteStyle # type: ignore[import-untyped]
|
||||
from prompt_toolkit.styles import Style as PtStyle # type: ignore[import-untyped]
|
||||
from rich.markup import escape
|
||||
from rich.table import Table
|
||||
from rich.text import Text
|
||||
|
||||
import EvoScientist.cli.channel as _ch_mod
|
||||
|
||||
from ..sessions import (
|
||||
_format_relative_time,
|
||||
delete_thread,
|
||||
find_similar_threads,
|
||||
generate_thread_id,
|
||||
get_checkpointer,
|
||||
get_thread_messages,
|
||||
get_thread_metadata,
|
||||
list_threads,
|
||||
thread_exists,
|
||||
find_similar_threads,
|
||||
delete_thread,
|
||||
get_thread_metadata,
|
||||
get_thread_messages,
|
||||
_format_relative_time,
|
||||
)
|
||||
from ..stream.display import console
|
||||
from .tui_runtime import run_streaming, resolve_ui_backend
|
||||
from .tui_interactive import run_textual_interactive
|
||||
from ._constants import WELCOME_SLOGANS, LOGO_LINES, LOGO_GRADIENT, build_metadata
|
||||
from .agent import _shorten_path, _create_session_workspace, _load_agent
|
||||
from ._constants import LOGO_GRADIENT, LOGO_LINES, WELCOME_SLOGANS, build_metadata
|
||||
from .agent import _create_session_workspace, _load_agent, _shorten_path
|
||||
from .channel import (
|
||||
ChannelMessage,
|
||||
_auto_start_channel,
|
||||
_channels_is_running,
|
||||
_cmd_channel,
|
||||
_cmd_channel_stop,
|
||||
_auto_start_channel,
|
||||
_message_queue,
|
||||
_set_channel_response,
|
||||
)
|
||||
import EvoScientist.cli.channel as _ch_mod
|
||||
from .mcp_ui import _cmd_mcp
|
||||
from .skills_cmd import (
|
||||
_cmd_list_skills,
|
||||
_cmd_install_skill,
|
||||
_cmd_uninstall_skill,
|
||||
_cmd_install_skills,
|
||||
_cmd_list_skills,
|
||||
_cmd_uninstall_skill,
|
||||
)
|
||||
from .tui_interactive import run_textual_interactive
|
||||
from .tui_runtime import resolve_ui_backend, run_streaming
|
||||
|
||||
_channel_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@ from ..mcp.registry import (
|
||||
)
|
||||
from ..stream.display import console
|
||||
|
||||
|
||||
_PICKER_STYLE = PtStyle.from_dict(
|
||||
{
|
||||
"questionmark": "#888888",
|
||||
@@ -82,9 +81,7 @@ def _browse_and_select(
|
||||
|
||||
if pre_filter_tag:
|
||||
pre_filter_tag = pre_filter_tag.lower()
|
||||
filtered = [
|
||||
e for e in servers if pre_filter_tag in [t.lower() for t in e.tags]
|
||||
]
|
||||
filtered = [e for e in servers if pre_filter_tag in [t.lower() for t in e.tags]]
|
||||
if not filtered:
|
||||
console.print(
|
||||
f"[yellow]No servers found with tag: {pre_filter_tag}[/yellow]"
|
||||
|
||||
@@ -186,6 +186,7 @@ def _show_mcp_config(name: str = "", *, show_blank_line: bool = True) -> str:
|
||||
def _cmd_mcp_add(args_str: str) -> None:
|
||||
"""Handle ``/mcp add ...``."""
|
||||
import shlex
|
||||
|
||||
from ..mcp import parse_mcp_add_args
|
||||
|
||||
if not args_str.strip():
|
||||
@@ -230,6 +231,7 @@ def _cmd_mcp_add(args_str: str) -> None:
|
||||
def _cmd_mcp_edit(args_str: str) -> None:
|
||||
"""Handle ``/mcp edit <name> --field value ...``."""
|
||||
import shlex
|
||||
|
||||
from ..mcp import parse_mcp_edit_args
|
||||
|
||||
if not args_str.strip():
|
||||
|
||||
@@ -8,8 +8,8 @@ from .agent import _shorten_path
|
||||
|
||||
def _cmd_list_skills() -> None:
|
||||
"""List all available skills (user and system)."""
|
||||
from ..tools.skills_manager import list_skills
|
||||
from ..paths import USER_SKILLS_DIR
|
||||
from ..tools.skills_manager import list_skills
|
||||
|
||||
skills = list_skills(include_system=True)
|
||||
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Callable, Protocol
|
||||
from typing import Any, Protocol
|
||||
|
||||
from ..stream.display import _run_streaming
|
||||
|
||||
|
||||
@@ -10,21 +10,17 @@ import asyncio
|
||||
import logging
|
||||
import queue
|
||||
import random
|
||||
from typing import Any, Callable
|
||||
from collections.abc import Callable
|
||||
from typing import Any
|
||||
|
||||
from rich.console import Group
|
||||
from rich.text import Text
|
||||
|
||||
import EvoScientist.cli.channel as _ch_mod
|
||||
from .channel import (
|
||||
ChannelMessage,
|
||||
_channels_is_running,
|
||||
_channels_running_list,
|
||||
_channels_stop,
|
||||
_auto_start_channel,
|
||||
_message_queue,
|
||||
_set_channel_response,
|
||||
)
|
||||
|
||||
from ..commands import CommandContext
|
||||
from ..commands import manager as cmd_manager
|
||||
from ..config.settings import get_config_dir
|
||||
from ..sessions import (
|
||||
find_similar_threads,
|
||||
generate_thread_id,
|
||||
@@ -33,14 +29,20 @@ from ..sessions import (
|
||||
get_thread_metadata,
|
||||
thread_exists,
|
||||
)
|
||||
from ..config.settings import get_config_dir
|
||||
from ..stream.events import stream_agent_events
|
||||
from ..stream.state import StreamState, _INTERNAL_TOOLS
|
||||
from ..stream.state import _INTERNAL_TOOLS, StreamState
|
||||
from ._constants import LOGO_GRADIENT, LOGO_LINES, WELCOME_SLOGANS, build_metadata
|
||||
from .channel import (
|
||||
ChannelMessage,
|
||||
_auto_start_channel,
|
||||
_channels_is_running,
|
||||
_channels_running_list,
|
||||
_channels_stop,
|
||||
_message_queue,
|
||||
_set_channel_response,
|
||||
)
|
||||
from .history_suggester import HistorySuggester
|
||||
|
||||
from ..commands import manager as cmd_manager, CommandContext
|
||||
from ._constants import LOGO_LINES, LOGO_GRADIENT, WELCOME_SLOGANS, build_metadata
|
||||
|
||||
_channel_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -179,16 +181,16 @@ def run_textual_interactive(
|
||||
|
||||
from .clipboard import copy_selection_to_clipboard, get_clipboard_text
|
||||
from .widgets import (
|
||||
LoadingWidget,
|
||||
ThinkingWidget,
|
||||
SummarizationWidget,
|
||||
AssistantMessage,
|
||||
ToolCallWidget,
|
||||
LoadingWidget,
|
||||
SubAgentWidget,
|
||||
TodoWidget,
|
||||
UserMessage,
|
||||
SummarizationWidget,
|
||||
SystemMessage,
|
||||
ThinkingWidget,
|
||||
TodoWidget,
|
||||
ToolCallWidget,
|
||||
UsageWidget,
|
||||
UserMessage,
|
||||
)
|
||||
except Exception as e: # pragma: no cover - runtime fallback path
|
||||
raise RuntimeError(
|
||||
@@ -394,11 +396,11 @@ def run_textual_interactive(
|
||||
_ch_mod._cli_thread_id = self._conversation_tid
|
||||
self._render_welcome()
|
||||
self._render_status()
|
||||
self.append_system(
|
||||
f"New session: {self._conversation_tid}", style="green"
|
||||
)
|
||||
self.append_system(f"New session: {self._conversation_tid}", style="green")
|
||||
|
||||
async def handle_session_resume(self, thread_id: str, workspace_dir: str | None = None) -> None:
|
||||
async def handle_session_resume(
|
||||
self, thread_id: str, workspace_dir: str | None = None
|
||||
) -> None:
|
||||
if workspace_dir:
|
||||
self._workspace_dir = workspace_dir
|
||||
|
||||
@@ -520,7 +522,7 @@ def run_textual_interactive(
|
||||
self._approval_future = asyncio.get_event_loop().create_future()
|
||||
try:
|
||||
return await asyncio.wait_for(self._approval_future, timeout=300)
|
||||
except (asyncio.TimeoutError, asyncio.CancelledError):
|
||||
except (TimeoutError, asyncio.CancelledError):
|
||||
return None
|
||||
finally:
|
||||
self._approval_future = None
|
||||
@@ -542,7 +544,7 @@ def run_textual_interactive(
|
||||
|
||||
try:
|
||||
result = await asyncio.wait_for(self._ask_user_future, timeout=300)
|
||||
except (asyncio.TimeoutError, asyncio.CancelledError):
|
||||
except (TimeoutError, asyncio.CancelledError):
|
||||
ask_w.action_cancel()
|
||||
return {"status": "cancelled"}
|
||||
finally:
|
||||
@@ -564,7 +566,7 @@ def run_textual_interactive(
|
||||
self._picker_future = asyncio.get_event_loop().create_future()
|
||||
try:
|
||||
return await asyncio.wait_for(self._picker_future, timeout=120)
|
||||
except (asyncio.TimeoutError, asyncio.CancelledError):
|
||||
except (TimeoutError, asyncio.CancelledError):
|
||||
return None
|
||||
finally:
|
||||
self._picker_future = None
|
||||
@@ -592,7 +594,7 @@ def run_textual_interactive(
|
||||
self._browser_future = asyncio.get_event_loop().create_future()
|
||||
try:
|
||||
return await asyncio.wait_for(self._browser_future, timeout=300)
|
||||
except (asyncio.TimeoutError, asyncio.CancelledError):
|
||||
except (TimeoutError, asyncio.CancelledError):
|
||||
return None
|
||||
finally:
|
||||
self._browser_future = None
|
||||
@@ -619,7 +621,7 @@ def run_textual_interactive(
|
||||
self._mcp_browser_future = asyncio.get_event_loop().create_future()
|
||||
try:
|
||||
return await asyncio.wait_for(self._mcp_browser_future, timeout=300)
|
||||
except (asyncio.TimeoutError, asyncio.CancelledError):
|
||||
except (TimeoutError, asyncio.CancelledError):
|
||||
return None
|
||||
finally:
|
||||
self._mcp_browser_future = None
|
||||
@@ -1100,7 +1102,9 @@ def run_textual_interactive(
|
||||
except Exception:
|
||||
pass
|
||||
_prompt.disabled = False
|
||||
from langgraph.types import Command # type: ignore[import-untyped]
|
||||
from langgraph.types import (
|
||||
Command, # type: ignore[import-untyped]
|
||||
)
|
||||
|
||||
_stream_input = Command(resume=result)
|
||||
_hitl_resuming = True
|
||||
@@ -1112,7 +1116,9 @@ def run_textual_interactive(
|
||||
|
||||
# HITL: check session auto-approve first
|
||||
if self._hitl_auto_approve:
|
||||
from langgraph.types import Command # type: ignore[import-untyped]
|
||||
from langgraph.types import (
|
||||
Command, # type: ignore[import-untyped]
|
||||
)
|
||||
|
||||
_stream_input = Command(
|
||||
resume={
|
||||
@@ -1135,7 +1141,9 @@ def run_textual_interactive(
|
||||
action_reqs,
|
||||
)
|
||||
if decisions is not None:
|
||||
from langgraph.types import Command # type: ignore[import-untyped]
|
||||
from langgraph.types import (
|
||||
Command, # type: ignore[import-untyped]
|
||||
)
|
||||
|
||||
_stream_input = Command(
|
||||
resume={"decisions": decisions}
|
||||
@@ -1168,7 +1176,9 @@ def run_textual_interactive(
|
||||
if decided_event and decided_event.decisions is not None:
|
||||
if decided_event.auto_approve_session:
|
||||
self._hitl_auto_approve = True
|
||||
from langgraph.types import Command # type: ignore[import-untyped]
|
||||
from langgraph.types import (
|
||||
Command, # type: ignore[import-untyped]
|
||||
)
|
||||
|
||||
_stream_input = Command(
|
||||
resume={"decisions": decided_event.decisions}
|
||||
@@ -1437,7 +1447,7 @@ def run_textual_interactive(
|
||||
return _ch_mod.channel_ask_user_prompt(ask_user_data, msg)
|
||||
|
||||
from ..commands.channel_ui import ChannelCommandUI
|
||||
|
||||
|
||||
# Handle slash commands from channel
|
||||
if msg.content.strip().startswith("/"):
|
||||
ctx = CommandContext(
|
||||
@@ -1457,7 +1467,9 @@ def run_textual_interactive(
|
||||
f"[{msg.channel_type}: Executed command from {msg.sender}]",
|
||||
style="dim",
|
||||
)
|
||||
_set_channel_response(msg.msg_id, f"Command executed: {msg.content}")
|
||||
_set_channel_response(
|
||||
msg.msg_id, f"Command executed: {msg.content}"
|
||||
)
|
||||
self._busy = False
|
||||
self._render_status()
|
||||
prompt_widget.disabled = False
|
||||
@@ -1915,6 +1927,7 @@ def run_textual_interactive(
|
||||
) -> None:
|
||||
"""Check tool calls for media files and forward to channel."""
|
||||
import os
|
||||
|
||||
from ..paths import resolve_virtual_path
|
||||
|
||||
arg_key = "path" if tool_name == "write_file" else "file_path"
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Callable
|
||||
from collections.abc import Callable
|
||||
from typing import Any
|
||||
|
||||
from ..stream.display import console
|
||||
from .tui_backends import RichStreamingBackend, StreamingTUIBackend
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
"""TUI widgets for EvoScientist Textual interface."""
|
||||
|
||||
from .loading_widget import LoadingWidget
|
||||
from .thinking_widget import ThinkingWidget
|
||||
from .summarization_widget import SummarizationWidget
|
||||
from .assistant_message import AssistantMessage
|
||||
from .tool_call_widget import ToolCallWidget
|
||||
from .subagent_widget import SubAgentWidget
|
||||
from .todo_widget import TodoWidget
|
||||
from .user_message import UserMessage
|
||||
from .system_message import SystemMessage
|
||||
from .usage_widget import UsageWidget
|
||||
from .approval_widget import ApprovalWidget
|
||||
from .ask_user_widget import AskUserWidget
|
||||
from .assistant_message import AssistantMessage
|
||||
from .loading_widget import LoadingWidget
|
||||
from .subagent_widget import SubAgentWidget
|
||||
from .summarization_widget import SummarizationWidget
|
||||
from .system_message import SystemMessage
|
||||
from .thinking_widget import ThinkingWidget
|
||||
from .thread_selector import ThreadPickerWidget
|
||||
from .todo_widget import TodoWidget
|
||||
from .tool_call_widget import ToolCallWidget
|
||||
from .usage_widget import UsageWidget
|
||||
from .user_message import UserMessage
|
||||
|
||||
__all__ = [
|
||||
"LoadingWidget",
|
||||
"ThinkingWidget",
|
||||
"SummarizationWidget",
|
||||
"AssistantMessage",
|
||||
"ToolCallWidget",
|
||||
"SubAgentWidget",
|
||||
"TodoWidget",
|
||||
"UserMessage",
|
||||
"SystemMessage",
|
||||
"UsageWidget",
|
||||
"ApprovalWidget",
|
||||
"AskUserWidget",
|
||||
"AssistantMessage",
|
||||
"LoadingWidget",
|
||||
"SubAgentWidget",
|
||||
"SummarizationWidget",
|
||||
"SystemMessage",
|
||||
"ThinkingWidget",
|
||||
"ThreadPickerWidget",
|
||||
"TodoWidget",
|
||||
"ToolCallWidget",
|
||||
"UsageWidget",
|
||||
"UserMessage",
|
||||
]
|
||||
|
||||
@@ -137,9 +137,7 @@ class AskUserWidget(Widget):
|
||||
if total == 1:
|
||||
title = ">>> Quick check-in from EvoScientist <<<"
|
||||
else:
|
||||
title = ">>> Question 1/{} — Quick check-in from EvoScientist <<<".format(
|
||||
total
|
||||
)
|
||||
title = f">>> Question 1/{total} — Quick check-in from EvoScientist <<<"
|
||||
|
||||
self._title_w = Static(title, classes="ask-title")
|
||||
yield self._title_w
|
||||
|
||||
@@ -13,7 +13,6 @@ from __future__ import annotations
|
||||
from typing import TYPE_CHECKING, Any, ClassVar
|
||||
|
||||
from rich.text import Text
|
||||
|
||||
from textual.binding import Binding, BindingType
|
||||
from textual.containers import Container
|
||||
from textual.message import Message
|
||||
@@ -163,7 +162,9 @@ class MCPBrowserWidget(Widget):
|
||||
if self._title_widget:
|
||||
self._title_widget.update("Filter by tag:")
|
||||
if self._help_widget:
|
||||
self._help_widget.update("\u2191/\u2193 navigate \u00b7 Enter select \u00b7 Esc cancel")
|
||||
self._help_widget.update(
|
||||
"\u2191/\u2193 navigate \u00b7 Enter select \u00b7 Esc cancel"
|
||||
)
|
||||
|
||||
for i, widget in enumerate(self._row_widgets):
|
||||
if i < len(self._tag_items):
|
||||
@@ -303,9 +304,7 @@ class MCPBrowserWidget(Widget):
|
||||
self._server_items = list(self._servers)
|
||||
else:
|
||||
self._server_items = [
|
||||
s
|
||||
for s in self._servers
|
||||
if tag in [t.lower() for t in s.tags]
|
||||
s for s in self._servers if tag in [t.lower() for t in s.tags]
|
||||
]
|
||||
self._phase = 2
|
||||
self._selected = 0
|
||||
|
||||
@@ -13,7 +13,6 @@ from __future__ import annotations
|
||||
from typing import TYPE_CHECKING, Any, ClassVar
|
||||
|
||||
from rich.text import Text
|
||||
|
||||
from textual.binding import Binding, BindingType
|
||||
from textual.containers import Container
|
||||
from textual.message import Message
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from rich.text import Text
|
||||
|
||||
from textual.containers import Vertical
|
||||
from textual.widgets import Static
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@ from __future__ import annotations
|
||||
|
||||
from rich.panel import Panel
|
||||
from rich.text import Text
|
||||
|
||||
from textual.events import Click
|
||||
from textual.widgets import Static
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from rich.text import Text
|
||||
|
||||
from textual.widgets import Static
|
||||
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ from __future__ import annotations
|
||||
|
||||
from rich.panel import Panel
|
||||
from rich.text import Text
|
||||
|
||||
from textual.events import Click
|
||||
from textual.widgets import Static
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ from __future__ import annotations
|
||||
from typing import TYPE_CHECKING, Any, ClassVar
|
||||
|
||||
from rich.text import Text
|
||||
|
||||
from textual.binding import Binding, BindingType
|
||||
from textual.containers import Container
|
||||
from textual.message import Message
|
||||
|
||||
@@ -8,7 +8,6 @@ from __future__ import annotations
|
||||
|
||||
from rich.panel import Panel
|
||||
from rich.text import Text
|
||||
|
||||
from textual.widgets import Static
|
||||
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from rich.text import Text
|
||||
|
||||
from textual.containers import Vertical
|
||||
from textual.events import Click
|
||||
from textual.widgets import Static
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from rich.text import Text
|
||||
|
||||
from textual.widgets import Static
|
||||
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from rich.text import Text
|
||||
|
||||
from textual.widgets import Static
|
||||
|
||||
|
||||
|
||||
@@ -7,11 +7,11 @@ from .manager import CommandManager, manager
|
||||
|
||||
__all__ = [
|
||||
"Argument",
|
||||
"ChannelCommandUI",
|
||||
"Command",
|
||||
"CommandContext",
|
||||
"CommandUI",
|
||||
"manager",
|
||||
"CommandManager",
|
||||
"ChannelCommandUI",
|
||||
"CommandUI",
|
||||
"implementation",
|
||||
"manager",
|
||||
]
|
||||
|
||||
@@ -2,7 +2,7 @@ from __future__ import annotations
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Protocol, Type, runtime_checkable
|
||||
from typing import Any, Protocol, runtime_checkable
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -10,7 +10,7 @@ class Argument:
|
||||
"""Definition of a command argument."""
|
||||
|
||||
name: str
|
||||
type: Type
|
||||
type: type
|
||||
description: str
|
||||
required: bool = True
|
||||
|
||||
@@ -61,9 +61,9 @@ class Command(ABC):
|
||||
"""Base class for all EvoScientist slash commands."""
|
||||
|
||||
name: str
|
||||
alias: list[str] = []
|
||||
alias: list[str]
|
||||
description: str
|
||||
arguments: list[Argument] = []
|
||||
arguments: list[Argument]
|
||||
|
||||
@abstractmethod
|
||||
async def execute(self, ctx: CommandContext, args: list[str]) -> None:
|
||||
|
||||
@@ -2,4 +2,4 @@ from __future__ import annotations
|
||||
|
||||
from . import channel, general, mcp, session, skills
|
||||
|
||||
__all__ = ["general", "session", "skills", "mcp", "channel"]
|
||||
__all__ = ["channel", "general", "mcp", "session", "skills"]
|
||||
|
||||
@@ -31,6 +31,7 @@ class MCPCommand(Command):
|
||||
await self._mcp_remove(ctx, subargs[0] if subargs else "")
|
||||
elif subcmd == "install":
|
||||
from .mcp_install import InstallMCPCommand
|
||||
|
||||
await InstallMCPCommand().execute(ctx, subargs)
|
||||
else:
|
||||
ctx.ui.append_system("MCP commands:", style="bold")
|
||||
@@ -48,7 +49,9 @@ class MCPCommand(Command):
|
||||
" /mcp edit ... Edit an existing server", style="dim"
|
||||
)
|
||||
ctx.ui.append_system(" /mcp remove ... Remove a server", style="dim")
|
||||
ctx.ui.append_system(" /mcp install ... Browse and install servers", style="dim")
|
||||
ctx.ui.append_system(
|
||||
" /mcp install ... Browse and install servers", style="dim"
|
||||
)
|
||||
|
||||
async def _mcp_list(self, ctx: CommandContext) -> None:
|
||||
from ...mcp import load_mcp_config
|
||||
|
||||
@@ -55,9 +55,7 @@ class InstallMCPCommand(Command):
|
||||
f"{match.name} is already configured.", style="yellow"
|
||||
)
|
||||
return
|
||||
if install_mcp_server(
|
||||
match, print_fn=ctx.ui.append_system
|
||||
):
|
||||
if install_mcp_server(match, print_fn=ctx.ui.append_system):
|
||||
ctx.ui.append_system(f"Configured: {match.name}", style="green")
|
||||
ctx.ui.append_system("Reload with /new to apply.", style="dim")
|
||||
else:
|
||||
@@ -92,9 +90,7 @@ class InstallMCPCommand(Command):
|
||||
ctx.ui.append_system("No servers selected.", style="dim")
|
||||
return
|
||||
|
||||
count = install_mcp_servers(
|
||||
selected_entries, print_fn=ctx.ui.append_system
|
||||
)
|
||||
count = install_mcp_servers(selected_entries, print_fn=ctx.ui.append_system)
|
||||
if count:
|
||||
ctx.ui.append_system(
|
||||
f"{count} server(s) configured. Reload with /new to apply.",
|
||||
|
||||
@@ -2,7 +2,6 @@ from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import shlex
|
||||
from typing import Dict, List, Tuple
|
||||
|
||||
from .base import Command, CommandContext
|
||||
|
||||
@@ -13,7 +12,7 @@ class CommandManager:
|
||||
"""Manages slash command registration and execution."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._commands: Dict[str, Command] = {}
|
||||
self._commands: dict[str, Command] = {}
|
||||
|
||||
def register(self, command: Command) -> None:
|
||||
"""Register a command and its aliases."""
|
||||
@@ -28,7 +27,7 @@ class CommandManager:
|
||||
"""Lookup a command by name."""
|
||||
return self._commands.get(name.lower())
|
||||
|
||||
def list_commands(self) -> List[Tuple[str, str]]:
|
||||
def list_commands(self) -> list[tuple[str, str]]:
|
||||
"""List all registered command names and descriptions."""
|
||||
seen = set()
|
||||
results = []
|
||||
@@ -38,7 +37,7 @@ class CommandManager:
|
||||
seen.add(cmd)
|
||||
return results
|
||||
|
||||
def get_all_commands(self) -> List[Command]:
|
||||
def get_all_commands(self) -> list[Command]:
|
||||
"""Return all registered command instances."""
|
||||
seen = set()
|
||||
results = []
|
||||
|
||||
@@ -9,17 +9,17 @@ The onboard module is loaded lazily because it pulls in heavy dependencies
|
||||
"""
|
||||
|
||||
from .settings import (
|
||||
EvoScientistConfig,
|
||||
apply_config_to_env,
|
||||
get_config_dir,
|
||||
get_config_path,
|
||||
EvoScientistConfig,
|
||||
load_config,
|
||||
save_config,
|
||||
reset_config,
|
||||
get_config_value,
|
||||
set_config_value,
|
||||
list_config,
|
||||
get_effective_config,
|
||||
apply_config_to_env,
|
||||
list_config,
|
||||
load_config,
|
||||
reset_config,
|
||||
save_config,
|
||||
set_config_value,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
|
||||
@@ -14,19 +14,19 @@ from pathlib import Path
|
||||
import questionary
|
||||
from prompt_toolkit.formatted_text import FormattedText
|
||||
from prompt_toolkit.styles import Style
|
||||
from prompt_toolkit.validation import Validator, ValidationError
|
||||
from prompt_toolkit.validation import ValidationError, Validator
|
||||
from questionary import Choice
|
||||
from rich.console import Console
|
||||
from rich.panel import Panel
|
||||
from rich.text import Text
|
||||
|
||||
from ..llm import get_models_for_provider
|
||||
from .settings import (
|
||||
EvoScientistConfig,
|
||||
get_config_path,
|
||||
load_config,
|
||||
save_config,
|
||||
get_config_path,
|
||||
)
|
||||
from ..llm import get_models_for_provider
|
||||
|
||||
console = Console()
|
||||
|
||||
@@ -779,7 +779,7 @@ def _prompt_and_validate_api_key(
|
||||
valid, msg = validate_fn(key_to_validate)
|
||||
if valid:
|
||||
console.print(f"\r [green]\u2713 {msg}[/green] ")
|
||||
return new_key if new_key else None
|
||||
return new_key or None
|
||||
else:
|
||||
console.print(f"\r [red]\u2717 {msg}[/red] ")
|
||||
if not new_key:
|
||||
@@ -795,7 +795,7 @@ def _prompt_and_validate_api_key(
|
||||
raise KeyboardInterrupt()
|
||||
return new_key if save_anyway else None
|
||||
|
||||
return new_key if new_key else None
|
||||
return new_key or None
|
||||
|
||||
|
||||
def _prompt_ccproxy_port(config: EvoScientistConfig) -> None:
|
||||
@@ -822,7 +822,7 @@ def _prompt_ccproxy_port(config: EvoScientistConfig) -> None:
|
||||
ccproxy_port = current_port
|
||||
console.print(f" [dim]Using default port: {ccproxy_port}[/dim]")
|
||||
|
||||
setattr(config, "ccproxy_port", ccproxy_port)
|
||||
config.ccproxy_port = ccproxy_port
|
||||
console.print(
|
||||
f" [green]✓ ccproxy will run on http://127.0.0.1:{ccproxy_port}[/green]"
|
||||
)
|
||||
@@ -864,7 +864,7 @@ def _step_anthropic_auth_mode(config: EvoScientistConfig) -> str:
|
||||
Returns:
|
||||
Selected auth mode: "api_key", "oauth", or "auto".
|
||||
"""
|
||||
from ..ccproxy_manager import is_ccproxy_available, check_ccproxy_auth
|
||||
from ..ccproxy_manager import check_ccproxy_auth, is_ccproxy_available
|
||||
|
||||
ccproxy_available = is_ccproxy_available()
|
||||
|
||||
@@ -960,7 +960,7 @@ def _step_openai_auth_mode(config: EvoScientistConfig) -> str:
|
||||
Returns:
|
||||
Selected auth mode: "api_key" or "oauth".
|
||||
"""
|
||||
from ..ccproxy_manager import is_ccproxy_available, check_ccproxy_auth
|
||||
from ..ccproxy_manager import check_ccproxy_auth, is_ccproxy_available
|
||||
|
||||
ccproxy_available = is_ccproxy_available()
|
||||
|
||||
@@ -1085,7 +1085,7 @@ def _step_base_url(config: EvoScientistConfig, current_value: str | None = None)
|
||||
"""
|
||||
current = current_value if current_value is not None else ""
|
||||
hint = f"Current: {current}" if current else ""
|
||||
default = current if current else ""
|
||||
default = current or ""
|
||||
|
||||
url = questionary.text(
|
||||
f"Base URL{' (' + hint + ', Enter to keep)' if hint else ''}:",
|
||||
@@ -1111,7 +1111,7 @@ def _step_ollama_base_url(config: EvoScientistConfig) -> tuple[str, list[str]]:
|
||||
Tuple of (base_url, detected_model_names).
|
||||
"""
|
||||
current = config.ollama_base_url or os.environ.get("OLLAMA_BASE_URL", "")
|
||||
default = current if current else "http://localhost:11434"
|
||||
default = current or "http://localhost:11434"
|
||||
|
||||
url = questionary.text(
|
||||
f"Ollama base URL (Enter for {default}):",
|
||||
|
||||
@@ -8,13 +8,12 @@ with the following priority (highest to lowest):
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from dataclasses import dataclass, asdict, fields
|
||||
from dotenv import find_dotenv, load_dotenv
|
||||
from dataclasses import asdict, dataclass, fields
|
||||
from pathlib import Path
|
||||
from typing import Any, Literal
|
||||
|
||||
import yaml
|
||||
|
||||
from dotenv import find_dotenv, load_dotenv
|
||||
|
||||
# =============================================================================
|
||||
# Configuration paths
|
||||
|
||||
@@ -5,19 +5,19 @@ with support for multiple providers.
|
||||
"""
|
||||
|
||||
from .models import (
|
||||
MODELS,
|
||||
DEFAULT_MODEL,
|
||||
MODELS,
|
||||
get_chat_model,
|
||||
get_model_info,
|
||||
get_models_for_provider,
|
||||
list_models,
|
||||
get_model_info,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"MODELS",
|
||||
"DEFAULT_MODEL",
|
||||
"MODELS",
|
||||
"get_chat_model",
|
||||
"get_model_info",
|
||||
"get_models_for_provider",
|
||||
"list_models",
|
||||
"get_model_info",
|
||||
]
|
||||
|
||||
@@ -25,6 +25,7 @@ from langchain.chat_models import init_chat_model
|
||||
def _patch_anthropic_proxy_compat() -> None:
|
||||
try:
|
||||
import types as _types
|
||||
|
||||
from langchain_anthropic.chat_models import ChatAnthropic as _CA
|
||||
|
||||
_orig = _CA._make_message_chunk_from_anthropic_event
|
||||
|
||||
@@ -4,17 +4,17 @@ See mcp/README.md for usage details.
|
||||
"""
|
||||
|
||||
from .client import (
|
||||
load_mcp_config,
|
||||
load_mcp_tools,
|
||||
aload_mcp_tools,
|
||||
VALID_TRANSPORTS,
|
||||
add_mcp_server,
|
||||
edit_mcp_server,
|
||||
remove_mcp_server,
|
||||
parse_mcp_add_args,
|
||||
parse_mcp_edit_args,
|
||||
aload_mcp_tools,
|
||||
build_mcp_add_kwargs,
|
||||
build_mcp_edit_fields,
|
||||
VALID_TRANSPORTS,
|
||||
edit_mcp_server,
|
||||
load_mcp_config,
|
||||
load_mcp_tools,
|
||||
parse_mcp_add_args,
|
||||
parse_mcp_edit_args,
|
||||
remove_mcp_server,
|
||||
)
|
||||
from .registry import (
|
||||
MCPServerEntry,
|
||||
@@ -27,14 +27,9 @@ from .registry import (
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"load_mcp_config",
|
||||
"load_mcp_tools",
|
||||
"aload_mcp_tools",
|
||||
"VALID_TRANSPORTS",
|
||||
"add_mcp_server",
|
||||
"edit_mcp_server",
|
||||
"remove_mcp_server",
|
||||
"parse_mcp_add_args",
|
||||
"parse_mcp_edit_args",
|
||||
"aload_mcp_tools",
|
||||
"build_mcp_add_kwargs",
|
||||
"build_mcp_edit_fields",
|
||||
"VALID_TRANSPORTS",
|
||||
|
||||
@@ -18,7 +18,6 @@ from typing import Any
|
||||
|
||||
import yaml
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# =============================================================================
|
||||
|
||||
@@ -17,9 +17,9 @@ import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
from typing import Callable
|
||||
|
||||
import yaml
|
||||
|
||||
@@ -83,9 +83,7 @@ def install_pip_package(package: str) -> bool:
|
||||
|
||||
for cmd in commands:
|
||||
try:
|
||||
result = subprocess.run(
|
||||
cmd, capture_output=True, text=True, timeout=120
|
||||
)
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
|
||||
if result.returncode == 0:
|
||||
import importlib
|
||||
|
||||
@@ -160,7 +158,9 @@ def _scan_mcp_dir(mcp_root: Path) -> list[MCPServerEntry]:
|
||||
try:
|
||||
entries.append(parse_marketplace_yaml(yaml_file))
|
||||
except Exception as exc:
|
||||
logger.warning("Failed to parse marketplace MCP %s: %s", yaml_file.name, exc)
|
||||
logger.warning(
|
||||
"Failed to parse marketplace MCP %s: %s", yaml_file.name, exc
|
||||
)
|
||||
return entries
|
||||
|
||||
|
||||
@@ -242,9 +242,7 @@ def install_mcp_server(
|
||||
if entry.pip_package:
|
||||
print_fn(f" Installing {entry.pip_package}...", "dim")
|
||||
if not install_pip_package(entry.pip_package):
|
||||
print_fn(
|
||||
f" Failed: {pip_install_hint()} {entry.pip_package}", "red"
|
||||
)
|
||||
print_fn(f" Failed: {pip_install_hint()} {entry.pip_package}", "red")
|
||||
return False
|
||||
|
||||
# Add to mcp.yaml
|
||||
|
||||
@@ -31,9 +31,7 @@ import logging
|
||||
import re
|
||||
from collections.abc import Awaitable, Callable
|
||||
from contextvars import ContextVar
|
||||
from typing import TYPE_CHECKING, Any, Annotated, NotRequired, cast
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import TYPE_CHECKING, Annotated, Any, NotRequired, cast
|
||||
|
||||
from langchain.agents.middleware.types import (
|
||||
AgentMiddleware,
|
||||
@@ -46,10 +44,11 @@ from langchain.tools import ToolRuntime
|
||||
from langchain_core.messages import AnyMessage, HumanMessage, filter_messages
|
||||
from langchain_core.runnables.config import RunnableConfig
|
||||
from langgraph.runtime import Runtime
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from langchain.chat_models import BaseChatModel
|
||||
from deepagents.backends.protocol import BACKEND_TYPES, BackendProtocol
|
||||
from langchain.chat_models import BaseChatModel
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -235,7 +234,7 @@ def _get_thread_id(runtime: Runtime) -> str:
|
||||
thread_id = config.get("configurable", {}).get("thread_id")
|
||||
if thread_id is not None:
|
||||
return str(thread_id)
|
||||
except Exception: # noqa: BLE001
|
||||
except Exception:
|
||||
logger.debug("Failed to resolve thread_id from runtime config")
|
||||
return "default"
|
||||
|
||||
@@ -532,7 +531,7 @@ class EvoMemoryMiddleware(AgentMiddleware):
|
||||
and responses[0].error is None
|
||||
):
|
||||
return responses[0].content.decode("utf-8")
|
||||
except Exception as e: # noqa: BLE001
|
||||
except Exception as e:
|
||||
logger.debug("Failed to read memory at %s: %s", self._memory_path, e)
|
||||
return ""
|
||||
|
||||
@@ -545,7 +544,7 @@ class EvoMemoryMiddleware(AgentMiddleware):
|
||||
and responses[0].error is None
|
||||
):
|
||||
return responses[0].content.decode("utf-8")
|
||||
except Exception as e: # noqa: BLE001
|
||||
except Exception as e:
|
||||
logger.debug("Failed to read memory at %s: %s", self._memory_path, e)
|
||||
return ""
|
||||
|
||||
@@ -560,7 +559,7 @@ class EvoMemoryMiddleware(AgentMiddleware):
|
||||
result = backend.write(self._memory_path, new_content)
|
||||
if result and result.error:
|
||||
logger.warning("Failed to write memory: %s", result.error)
|
||||
except Exception as e: # noqa: BLE001
|
||||
except Exception as e:
|
||||
logger.warning("Exception writing memory: %s", e)
|
||||
|
||||
async def _awrite_memory(
|
||||
@@ -575,7 +574,7 @@ class EvoMemoryMiddleware(AgentMiddleware):
|
||||
result = await backend.awrite(self._memory_path, new_content)
|
||||
if result and result.error:
|
||||
logger.warning("Failed to write memory: %s", result.error)
|
||||
except Exception as e: # noqa: BLE001
|
||||
except Exception as e:
|
||||
logger.warning("Exception writing memory: %s", e)
|
||||
|
||||
# -- threshold check -----------------------------------------------------
|
||||
@@ -650,7 +649,7 @@ class EvoMemoryMiddleware(AgentMiddleware):
|
||||
# field cleared) over bind() which only adds invocation kwargs.
|
||||
try:
|
||||
return model.model_copy(update=updates)
|
||||
except Exception: # noqa: BLE001
|
||||
except Exception:
|
||||
# Fallback for non-Pydantic or unusual model classes
|
||||
return model.bind(**{k: v for k, v in updates.items() if v is not None})
|
||||
|
||||
@@ -667,7 +666,7 @@ class EvoMemoryMiddleware(AgentMiddleware):
|
||||
)
|
||||
result = structured_model.invoke(prompt)
|
||||
return result.model_dump(exclude_none=True)
|
||||
except Exception as e: # noqa: BLE001
|
||||
except Exception as e:
|
||||
logger.warning("Memory extraction failed: %s", e)
|
||||
return {}
|
||||
|
||||
@@ -684,7 +683,7 @@ class EvoMemoryMiddleware(AgentMiddleware):
|
||||
)
|
||||
result = await structured_model.ainvoke(prompt)
|
||||
return result.model_dump(exclude_none=True)
|
||||
except Exception as e: # noqa: BLE001
|
||||
except Exception as e:
|
||||
logger.warning("Memory extraction failed: %s", e)
|
||||
return {}
|
||||
|
||||
@@ -705,7 +704,7 @@ class EvoMemoryMiddleware(AgentMiddleware):
|
||||
backend = self._get_backend(state, request.runtime)
|
||||
memory_content = self._read_memory(backend)
|
||||
_CURRENT_MEMORY.set(memory_content)
|
||||
except Exception as e: # noqa: BLE001
|
||||
except Exception as e:
|
||||
logger.debug("Failed to load memory during modify_request: %s", e)
|
||||
# Use placeholder when memory file doesn't exist yet
|
||||
if not memory_content:
|
||||
@@ -823,6 +822,7 @@ def create_memory_middleware(
|
||||
Configured EvoMemoryMiddleware instance.
|
||||
"""
|
||||
from deepagents.backends import FilesystemBackend
|
||||
|
||||
from ..paths import MEMORY_DIR as _DEFAULT_MEMORY_DIR
|
||||
|
||||
if memory_dir is None:
|
||||
|
||||
@@ -9,7 +9,7 @@ Adapted from upstream ``deepagents_cli/sessions.py``.
|
||||
import uuid
|
||||
from collections.abc import AsyncIterator
|
||||
from contextlib import asynccontextmanager
|
||||
from datetime import datetime, timezone
|
||||
from datetime import UTC, datetime
|
||||
from pathlib import Path
|
||||
|
||||
import aiosqlite
|
||||
@@ -137,8 +137,8 @@ def _format_relative_time(iso_ts: str | None) -> str:
|
||||
try:
|
||||
dt = datetime.fromisoformat(iso_ts)
|
||||
if dt.tzinfo is None:
|
||||
dt = dt.replace(tzinfo=timezone.utc)
|
||||
now = datetime.now(timezone.utc)
|
||||
dt = dt.replace(tzinfo=UTC)
|
||||
now = datetime.now(UTC)
|
||||
delta = now - dt
|
||||
seconds = int(delta.total_seconds())
|
||||
if seconds < 60:
|
||||
|
||||
@@ -11,33 +11,33 @@ Provides:
|
||||
- Display functions: Rich rendering for streaming and final output
|
||||
"""
|
||||
|
||||
from .emitter import StreamEventEmitter, StreamEvent
|
||||
from .tracker import ToolCallTracker, ToolCallInfo
|
||||
from .formatter import ToolResultFormatter, ContentType, FormattedResult
|
||||
from .diff_format import build_edit_diff, format_diff_rich
|
||||
from .display import (
|
||||
_astream_to_console,
|
||||
console,
|
||||
create_streaming_display,
|
||||
display_final_results,
|
||||
format_tool_result_compact,
|
||||
formatter,
|
||||
)
|
||||
from .emitter import StreamEvent, StreamEventEmitter
|
||||
from .events import stream_agent_events
|
||||
from .formatter import ContentType, FormattedResult, ToolResultFormatter
|
||||
from .state import StreamState, SubAgentState, _build_todo_stats, _parse_todo_items
|
||||
from .tracker import ToolCallInfo, ToolCallTracker
|
||||
from .utils import (
|
||||
SUCCESS_PREFIX,
|
||||
FAILURE_PREFIX,
|
||||
ToolStatus,
|
||||
SUCCESS_PREFIX,
|
||||
DisplayLimits,
|
||||
ToolStatus,
|
||||
count_lines,
|
||||
format_tool_compact,
|
||||
format_tree_output,
|
||||
get_status_symbol,
|
||||
has_args,
|
||||
is_success,
|
||||
truncate,
|
||||
format_tool_compact,
|
||||
format_tree_output,
|
||||
count_lines,
|
||||
truncate_with_line_hint,
|
||||
get_status_symbol,
|
||||
)
|
||||
from .state import SubAgentState, StreamState, _parse_todo_items, _build_todo_stats
|
||||
from .events import stream_agent_events
|
||||
from .diff_format import build_edit_diff, format_diff_rich
|
||||
from .display import (
|
||||
console,
|
||||
formatter,
|
||||
format_tool_result_compact,
|
||||
create_streaming_display,
|
||||
display_final_results,
|
||||
_astream_to_console,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
|
||||
@@ -14,7 +14,6 @@ import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Charset detection (simplified from upstream config.py)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@@ -9,7 +9,8 @@ import asyncio
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
from typing import Any, Callable
|
||||
from collections.abc import Callable
|
||||
from typing import Any
|
||||
|
||||
from rich.console import Console, Group # type: ignore[import-untyped]
|
||||
from rich.live import Live # type: ignore[import-untyped]
|
||||
@@ -19,17 +20,17 @@ from rich.spinner import Spinner # type: ignore[import-untyped]
|
||||
from rich.text import Text # type: ignore[import-untyped]
|
||||
|
||||
from ..paths import resolve_virtual_path
|
||||
from .diff_format import build_edit_diff
|
||||
from .events import stream_agent_events
|
||||
from .formatter import ToolResultFormatter
|
||||
from .state import (
|
||||
_INTERNAL_TOOLS,
|
||||
StreamState,
|
||||
SubAgentState,
|
||||
_build_todo_stats,
|
||||
_parse_todo_items,
|
||||
_INTERNAL_TOOLS,
|
||||
)
|
||||
from .utils import DisplayLimits, ToolStatus, format_tool_compact, is_success
|
||||
from .diff_format import build_edit_diff
|
||||
from .events import stream_agent_events
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Shared globals
|
||||
|
||||
@@ -5,7 +5,7 @@ All events contain a type and associated data dict.
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Dict
|
||||
from typing import Any
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -13,7 +13,7 @@ class StreamEvent:
|
||||
"""Unified stream event."""
|
||||
|
||||
type: str
|
||||
data: Dict[str, Any]
|
||||
data: dict[str, Any]
|
||||
|
||||
|
||||
class StreamEventEmitter:
|
||||
@@ -32,7 +32,7 @@ class StreamEventEmitter:
|
||||
return StreamEvent("text", {"type": "text", "content": content})
|
||||
|
||||
@staticmethod
|
||||
def tool_call(name: str, args: Dict[str, Any], tool_id: str = "") -> StreamEvent:
|
||||
def tool_call(name: str, args: dict[str, Any], tool_id: str = "") -> StreamEvent:
|
||||
"""Tool call event."""
|
||||
return StreamEvent(
|
||||
"tool_call",
|
||||
@@ -66,7 +66,7 @@ class StreamEventEmitter:
|
||||
|
||||
@staticmethod
|
||||
def subagent_tool_call(
|
||||
subagent: str, name: str, args: Dict[str, Any], tool_id: str = ""
|
||||
subagent: str, name: str, args: dict[str, Any], tool_id: str = ""
|
||||
) -> StreamEvent:
|
||||
"""Tool call from inside a sub-agent."""
|
||||
return StreamEvent(
|
||||
|
||||
@@ -8,9 +8,13 @@ import asyncio
|
||||
import base64
|
||||
import mimetypes
|
||||
import os
|
||||
from typing import Any, AsyncIterator
|
||||
from collections.abc import AsyncIterator
|
||||
from typing import Any
|
||||
|
||||
from langchain_core.messages import AIMessage, AIMessageChunk # type: ignore[import-untyped]
|
||||
from langchain_core.messages import ( # type: ignore[import-untyped]
|
||||
AIMessage,
|
||||
AIMessageChunk,
|
||||
)
|
||||
|
||||
from .emitter import StreamEventEmitter
|
||||
from .tracker import ToolCallTracker
|
||||
|
||||
@@ -7,15 +7,16 @@ Detects content type (success/error/json/markdown/text) and formats accordingly.
|
||||
import json
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
from typing import Any, List
|
||||
from typing import Any
|
||||
|
||||
from rich.markdown import Markdown
|
||||
from rich.markup import escape
|
||||
from rich.panel import Panel
|
||||
from rich.syntax import Syntax
|
||||
from rich.text import Text
|
||||
from rich.markdown import Markdown
|
||||
|
||||
from .utils import SUCCESS_PREFIX, FAILURE_PREFIX, is_success as _is_success, truncate
|
||||
from .utils import FAILURE_PREFIX, SUCCESS_PREFIX, truncate
|
||||
from .utils import is_success as _is_success
|
||||
|
||||
|
||||
class ContentType(Enum):
|
||||
@@ -33,7 +34,7 @@ class FormattedResult:
|
||||
"""Formatted result container."""
|
||||
|
||||
content_type: ContentType
|
||||
elements: List[Any] # Rich renderable elements
|
||||
elements: list[Any] # Rich renderable elements
|
||||
success: bool = True
|
||||
|
||||
|
||||
@@ -125,7 +126,7 @@ class ToolResultFormatter:
|
||||
md_patterns = ["```", "**", "##", "- **"]
|
||||
return content.startswith("#") or any(p in content for p in md_patterns)
|
||||
|
||||
def _format_success(self, name: str, content: str, max_length: int) -> List[Any]:
|
||||
def _format_success(self, name: str, content: str, max_length: int) -> list[Any]:
|
||||
display = truncate(content, max_length)
|
||||
return [
|
||||
Panel(
|
||||
@@ -135,7 +136,7 @@ class ToolResultFormatter:
|
||||
)
|
||||
]
|
||||
|
||||
def _format_error(self, name: str, content: str, max_length: int) -> List[Any]:
|
||||
def _format_error(self, name: str, content: str, max_length: int) -> list[Any]:
|
||||
display = truncate(content, max_length)
|
||||
return [
|
||||
Panel(
|
||||
@@ -145,7 +146,7 @@ class ToolResultFormatter:
|
||||
)
|
||||
]
|
||||
|
||||
def _format_json(self, name: str, content: str, max_length: int) -> List[Any]:
|
||||
def _format_json(self, name: str, content: str, max_length: int) -> list[Any]:
|
||||
json_content = content
|
||||
if content.startswith(SUCCESS_PREFIX):
|
||||
json_content = self._extract_body(content)
|
||||
@@ -161,7 +162,7 @@ class ToolResultFormatter:
|
||||
except (json.JSONDecodeError, ValueError):
|
||||
return self._format_text(name, content, max_length)
|
||||
|
||||
def _format_markdown(self, name: str, content: str, max_length: int) -> List[Any]:
|
||||
def _format_markdown(self, name: str, content: str, max_length: int) -> list[Any]:
|
||||
display = truncate(content, max_length)
|
||||
return [
|
||||
Panel(
|
||||
@@ -171,7 +172,7 @@ class ToolResultFormatter:
|
||||
)
|
||||
]
|
||||
|
||||
def _format_text(self, name: str, content: str, max_length: int) -> List[Any]:
|
||||
def _format_text(self, name: str, content: str, max_length: int) -> list[Any]:
|
||||
display = truncate(content, max_length)
|
||||
return [
|
||||
Text(f"{name}:", style="cyan bold"),
|
||||
|
||||
@@ -6,7 +6,6 @@ Handles tool_use blocks where arguments arrive in fragments via input_json_delta
|
||||
|
||||
import json
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Dict, Optional
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -15,7 +14,7 @@ class ToolCallInfo:
|
||||
|
||||
id: str
|
||||
name: str
|
||||
args: Dict = field(default_factory=dict)
|
||||
args: dict = field(default_factory=dict)
|
||||
emitted: bool = False
|
||||
args_complete: bool = False
|
||||
_json_buffer: str = ""
|
||||
@@ -35,14 +34,14 @@ class ToolCallTracker:
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self._calls: Dict[str, ToolCallInfo] = {}
|
||||
self._last_tool_id: Optional[str] = None
|
||||
self._calls: dict[str, ToolCallInfo] = {}
|
||||
self._last_tool_id: str | None = None
|
||||
|
||||
def update(
|
||||
self,
|
||||
tool_id: str,
|
||||
name: Optional[str] = None,
|
||||
args: Optional[Dict] = None,
|
||||
name: str | None = None,
|
||||
args: dict | None = None,
|
||||
args_complete: bool = False,
|
||||
) -> None:
|
||||
"""Update tool call info (accumulative)."""
|
||||
@@ -96,7 +95,7 @@ class ToolCallTracker:
|
||||
if tool_id in self._calls:
|
||||
self._calls[tool_id].emitted = True
|
||||
|
||||
def get(self, tool_id: str) -> Optional[ToolCallInfo]:
|
||||
def get(self, tool_id: str) -> ToolCallInfo | None:
|
||||
"""Get tool call info by ID."""
|
||||
return self._calls.get(tool_id)
|
||||
|
||||
|
||||
@@ -6,9 +6,8 @@ adapted for deepagents tool names.
|
||||
"""
|
||||
|
||||
import sys
|
||||
from pathlib import PurePath
|
||||
from enum import Enum
|
||||
|
||||
from pathlib import PurePath
|
||||
|
||||
# === Status marker constants ===
|
||||
SUCCESS_PREFIX = "[OK]"
|
||||
|
||||
@@ -4,13 +4,13 @@ External imports like ``from EvoScientist.tools import tavily_search`` continue
|
||||
to work unchanged thanks to these re-exports.
|
||||
"""
|
||||
|
||||
from .search import tavily_search, fetch_webpage_content
|
||||
from .think import think_tool
|
||||
from .search import fetch_webpage_content, tavily_search
|
||||
from .skill_manager import skill_manager
|
||||
from .think import think_tool
|
||||
|
||||
__all__ = [
|
||||
"tavily_search",
|
||||
"fetch_webpage_content",
|
||||
"think_tool",
|
||||
"skill_manager",
|
||||
"tavily_search",
|
||||
"think_tool",
|
||||
]
|
||||
|
||||
@@ -5,13 +5,12 @@ using Tavily for URL discovery and fetching full webpage content.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
from typing import Literal
|
||||
from typing import Annotated, Literal
|
||||
|
||||
import httpx
|
||||
from langchain_core.tools import InjectedToolArg, tool
|
||||
from markdownify import markdownify
|
||||
from tavily import TavilyClient
|
||||
from typing_extensions import Annotated
|
||||
|
||||
# Lazy initialization - only create client when needed
|
||||
_tavily_client = None
|
||||
@@ -49,7 +48,7 @@ async def fetch_webpage_content(url: str, timeout: float = 10.0) -> str:
|
||||
response.raise_for_status()
|
||||
return markdownify(response.text)
|
||||
except Exception as e:
|
||||
return f"Error fetching content from {url}: {str(e)}"
|
||||
return f"Error fetching content from {url}: {e!s}"
|
||||
|
||||
|
||||
@tool(parse_docstring=True)
|
||||
@@ -109,4 +108,4 @@ async def tavily_search(
|
||||
{"".join(result_texts)}"""
|
||||
|
||||
except Exception as e:
|
||||
return f"Search failed: {str(e)}"
|
||||
return f"Search failed: {e!s}"
|
||||
|
||||
@@ -52,11 +52,11 @@ def skill_manager(
|
||||
Result message
|
||||
"""
|
||||
from .skills_manager import (
|
||||
fetch_remote_skill_index,
|
||||
get_skill_info,
|
||||
install_skill,
|
||||
list_skills,
|
||||
uninstall_skill,
|
||||
get_skill_info,
|
||||
fetch_remote_skill_index,
|
||||
)
|
||||
|
||||
if action == "install":
|
||||
|
||||
@@ -97,3 +97,65 @@ testpaths = ["tests"]
|
||||
filterwarnings = [
|
||||
"ignore::UserWarning:langchain_nvidia_ai_endpoints",
|
||||
]
|
||||
|
||||
[tool.ruff]
|
||||
# Exclude a variety of commonly ignored directories.
|
||||
exclude = [
|
||||
".bzr",
|
||||
".direnv",
|
||||
".eggs",
|
||||
".git",
|
||||
".git-rewrite",
|
||||
".hg",
|
||||
".ipynb_checkpoints",
|
||||
".mypy_cache",
|
||||
".nox",
|
||||
".pants.d",
|
||||
".pyenv",
|
||||
".pytest_cache",
|
||||
".pytype",
|
||||
".ruff_cache",
|
||||
".svn",
|
||||
".tox",
|
||||
".venv",
|
||||
".vscode",
|
||||
"__pypackages__",
|
||||
"_build",
|
||||
"buck-out",
|
||||
"build",
|
||||
"dist",
|
||||
"node_modules",
|
||||
"site-packages",
|
||||
"venv",
|
||||
]
|
||||
|
||||
line-length = 88
|
||||
indent-width = 4
|
||||
target-version = "py311"
|
||||
|
||||
[tool.ruff.lint]
|
||||
select = [
|
||||
"E", "W", # pycodestyle (Error & Warning)
|
||||
"F", # Pyflakes (Logical errors)
|
||||
"I", # isort (Import sorting)
|
||||
"B", # flake8-bugbear (Common bugs)
|
||||
"C4", # flake8-comprehensions (List/Dict perf)
|
||||
"UP", # pyupgrade (Modern Python syntax)
|
||||
"PT", # flake8-pytest-style (If using Pytest)
|
||||
"PLE", # Pylint Error
|
||||
"RUF", # Ruff specific rules
|
||||
"FURB", # Refurb rules
|
||||
]
|
||||
ignore = [
|
||||
"E501", # Formatter takes care of that
|
||||
]
|
||||
fixable = ["ALL"]
|
||||
unfixable = []
|
||||
allowed-confusables = ["–", "❯"]
|
||||
|
||||
[tool.ruff.format]
|
||||
quote-style = "double"
|
||||
indent-style = "space"
|
||||
skip-magic-trailing-comma = false
|
||||
line-ending = "auto"
|
||||
docstring-code-format = false
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user