* fix(llm): patch _stream/_astream for OpenAI-compatible content flattening
_patch_openai_compat_content() only patched _generate/_agenerate but
EvoSci CLI uses streaming paths. This extends the content flattening
to _stream/_astream so strict OpenAI-compatible relays receive plain
string content during streaming calls.
Closes#142
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(test): use asyncio.run() instead of pytest-asyncio for CI compat
CI does not have pytest-asyncio installed, so async tests must use
asyncio.run() wrapper instead of @pytest.mark.asyncio decorator.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(test): use @pytest.mark.anyio for async tests (CI compat)
CI does not have pytest-asyncio. Use @pytest.mark.anyio consistent
with existing async tests in the project.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add Moonshot and Kimi Coding Plan as LLM providers
Add two new providers for Moonshot AI:
- `moonshot`: OpenAI-compatible direct API (api.moonshot.cn/v1) with
kimi-k2.5, kimi-k2-thinking, moonshot-v1-auto/128k/32k/8k models
- `kimi-coding`: Anthropic-compatible Kimi Coding Plan endpoint
(api.kimi.com/coding/) with User-Agent header for compatibility
Both providers disable thinking to avoid multi-turn tool calling
errors caused by LangChain dropping reasoning_content from history.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update Moonshot thinking comment and add provider assertions
- Add clarifying comment for disabling thinking on all Moonshot models
- Add moonshot and kimi-coding assertions to test_entries_has_all_providers
* fix: exclude Moonshot and Kimi Coding from content patch
Tested and verified both APIs support standard list content format:
- Moonshot (OpenAI-compatible): supports list content, no patch needed
- Kimi Coding (Anthropic-compatible): supports list content, no patch needed
Only apply _patch_openai_compat_content to strict providers like DeepSeek.
* fix: set _original_provider in routed provider branches
Ensure _original_provider is set before provider is reassigned to
'openai' or 'anthropic', so the no-patch exclusion for Moonshot
and Kimi Coding works correctly.
* style: translate Moonshot comments to English
* style: translate comment to English to fix ruff lint error
* merge: resolve conflicts
* chore: revert uv.lock and translate Chinese comments to English
Revert unrelated uv.lock dependency changes and replace Chinese code
comments with English for codebase consistency per review feedback.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* style: fix ruff format for models.py
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: ypd <ypd@ypddeMac-mini.local>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Xi Zhang <106144707+X-iZhang@users.noreply.github.com>
Co-authored-by: Xiaohui Yan <xhcloud@gmail.com>
Co-authored-by: X-iZhang <zacharyzhang2022@gmail.com>
* fix: support XDG_CONFIG_HOME for sessions.db on Windows
Fixes SQLite database opening failure on Windows systems with non-ASCII
usernames (e.g., Chinese characters). The get_db_path() function now
supports the XDG_CONFIG_HOME environment variable, consistent with
get_config_dir() in settings.py.
Closes#101
* fix: auto-resolve Windows Unicode path for sqlite3 via 8.3 short path
Refactor get_db_path() to reuse get_config_dir() (XDG_CONFIG_HOME
support) and add _to_short_path() helper that converts the config
directory to its Windows 8.3 short form via GetShortPathNameW. This
automatically resolves sqlite3 failures on Windows systems with
non-ASCII usernames (e.g., Chinese characters) without requiring
manual environment variable configuration.
The short-path conversion is best-effort: it targets the directory
(which exists after mkdir) rather than the db file, and falls back
gracefully on non-Windows, non-NTFS, or when 8.3 naming is disabled.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Xi Zhang <106144707+X-iZhang@users.noreply.github.com>
Co-authored-by: X-iZhang <zacharyzhang2022@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(cli): add --debug flag for verbose logging in serve mode
* feat(cli): add log_level config field with priority over env var
Replace dead `debug` parameter in `main()` with a proper `log_level`
config field in EvoScientistConfig. Enables `EvoSci config set log_level
debug` with priority: config file > EVOSCIENTIST_LOG_LEVEL env var.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix:Telegram channel start failed #133
* refactor: simplify bus thread and add nest_asyncio warning
- Remove unnecessary _run_as_task() wrapper; run_until_complete()
already creates a Task internally via ensure_future()
- Add comment noting nest_asyncio.apply() is global and irreversible
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Xi Zhang <zacharyzhang2022@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Xi Zhang <106144707+X-iZhang@users.noreply.github.com>
* feat(llm): upgrade OpenAI reasoning effort from high to xhigh
The OpenAI Responses API supports "xhigh" as a reasoning effort level,
which provides deeper reasoning than "high". This is already used by
other CLI tools (e.g., OpenClaw) for OpenAI models.
Only affects the direct API key path; the ccproxy/OAuth path is
unchanged (reasoning is still skipped there).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(llm): limit xhigh reasoning to gpt-5.4+ and codex models
Only gpt-5.4 series and codex models support xhigh reasoning effort.
Older models (gpt-5, gpt-5.1, gpt-5.2, gpt-5.3) fall back to high.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Xi Zhang <zacharyzhang2022@gmail.com>
Co-authored-by: Xi Zhang <106144707+X-iZhang@users.noreply.github.com>
* feat: Add GLM-5.1 support for Zhipu providers
Add GLM-5.1 model entries to both zhipu-code (coding endpoint) and
zhipu (general endpoint) providers, following the existing pattern
for GLM models.
* feat: add glm-5v-turbo support for Zhipu providers
---------
Co-authored-by: Xi Zhang <106144707+X-iZhang@users.noreply.github.com>
Co-authored-by: Xi Zhang <zacharyzhang2022@gmail.com>
* feat: enable reasoning for OpenRouter via extra_body to prevent multi-turn errors
* feat: implement OpenRouter native reasoning support and patch langchain-openrouter bug
* feat: add OpenRouter reasoning effort configuration and update related tests
* feat: add langchain-openrouter dependency for enhanced reasoning support
* fix: correct spacing in reasoning effort choice label
* feat: implement patch for OpenRouter reasoning details to prevent Pydantic errors
* feat: add patches for OpenRouter reasoning and content handling utilities
* feat: prevent multiple patches of OpenRouter reasoning details by using a global flag
* feat: update OpenRouter reasoning patch to ensure single application with global flag
* feat: refine OpenAI responses API handling to apply only for OpenAI provider
* feat: Enhance TUI interaction by updating todo widget positioning and skipping empty tool call chunks
* feat: Update tool selector threshold and adjust logging level for selector failures
* feat: Temporarily disable timestamp toast in tool call widget for UX review
* feat: Re-enable timestamp toast in tool call widget on click
* feat: Add context management middleware for improved error handling and context editing
* feat: Implement LLMToolSelectorMiddleware for enhanced tool selection and tracking
* feat(tests): update test functions to include mock timestamp parameter
* refactor: simplify tool selection state storage and update comments in middleware
* feat: Enhance tool selection handling and suppress structured output for improved event streaming
* refactor: simplify patching in test_create_tool_selector functions
* feat: add model parameter to create_tool_selector_middleware for enhanced flexibility
* feat: enhance tool selection suppression with JSON buffering for improved accuracy
* feat: Upgrade ccproxy to version 0.2.7 and remove deprecated thinking tag handling
* feat: Enhance ccproxy compatibility and strip legacy thinking tags
* fix: use `uv tool install --with` for durable MCP server installs (#121)
When EvoScientist is installed via `uv tool install`, MCP server packages
added during onboarding were installed with `uv pip install`, which is
not tracked by uv. Running `uv tool upgrade evoscientist` would recreate
the venv from scratch and silently wipe the MCP server binaries.
Now `install_pip_package()` detects uv tool environments and uses
`uv tool install <tool> --with <package>`, which records the dependency
in uv-receipt.toml so it survives upgrades. Existing --with packages
are read from the receipt and preserved.
Falls back to the old `uv pip install` path if the durable method fails.
* style: fmt
* fix: preserve requirement specs and normalize dedup in uv tool installs
Address review feedback: _uv_tool_existing_requirements() now returns
a dict mapping bare names to full PEP 508 specs (preserving extras and
version constraints from uv-receipt.toml). Dedup check uses
_bare_package_name() to normalize the incoming package argument before
comparing against receipt entries.
* fix: update OpenRouter API key validation to use /auth/key endpoint and httpx
* Refactor code structure for improved readability and maintainability
* feat: enhance welcome banner to include file commands indication
* feat: update LaTeX setup prompt to use selection UI for better user experience
* feat: update version to v0.0.5 in badges and project configuration
* feat(config): use_responses_api (#98)
langchain-openai auto-switches to the Responses API when reasoning
params are set, which breaks OpenAI-compatible relays that only support
Chat Completions. This adds a user-facing config option to override
that behavior:
evosci config set use_responses_api false
# or EVOSCIENTIST_USE_RESPONSES_API=false
* fix: propagate use_responses_api from config file and add normalization tests
Address PR #105 review comments:
- apply_config_to_env() now sets EVOSCIENTIST_USE_RESPONSES_API so
config file values take effect (not just the env var directly)
- Add parametrized tests for case/whitespace normalization
---------
Co-authored-by: Xi Zhang <106144707+X-iZhang@users.noreply.github.com>
lark_oapi.ws.client captures the main thread's event loop in a
module-level variable at import time. When the WebSocket SDK thread
calls loop.run_until_complete() on that shared loop, nest_asyncio's
global patches cause task-tracking conflicts on Linux/Python 3.12:
- RuntimeError: Leaving task … does not match the current task
- AttributeError: 'NoneType' object has no attribute 'select'
Replace the previous Handle._run monkey-patch (which only suppressed
symptoms) with a proper fix: create a fresh event loop in the SDK
thread and swap the module-level loop variable so the SDK operates
on a fully isolated loop with no cross-thread interaction.
Closes#97
* feat(tui): enhance conversation history rendering and implement two-level thread hierarchy in picker
* feat(tui): improve conversation history display and enhance thread selection UI
* feat(file_mentions): implement @file mention parsing and completion for CLI and TUI
* feat(uv-tool): add compatibility checks and installation helpers for uv tool environments
* feat(dependencies): update package versions in uv.lock for compatibility and improvements
* feat(badges): update PyPI version to v0.0.4 in SVG assets and README files
* feat(tests): format code in TestUvToolCompat for improved readability
* feat(feishu): add WebSocket long connection subscription mode
Add WebSocket (长连接) mode as an alternative to webhook for Feishu
event subscription. This allows running without a public IP, port
forwarding, or tunnel — ideal for local dev and NAT/firewall setups.
- New `feishu_subscription_mode` config: "webhook" (default) or "websocket"
- WebSocket mode uses official `lark-oapi` SDK with thread-safe queue bridge
- Onboard wizard: mode selection, SDK install prompt for websocket
- CLI: `--mode webhook|websocket` for standalone serve
- `pip install evoscientist[feishu]` optional dependency
- 5 new tests covering config, SDK missing error, message bridge, cleanup
- Docs: subscription mode comparison table, prerequisites per mode
* Fix: Ruff
* Fix: small fix
* feat: implement background update check and startup notifications
* feat: enhance user experience with timestamp notifications and UI polish
* feat: implement multi-line chat input with Enter-to-submit and modifier+Enter newline
* update
* update
* v0.0.3
* feat: improve code readability with consistent formatting in TUI and test files
* feat: update PyPI badge version to v0.0.3 in README files
* feat: add docstrings for test classes in test_update_check.py
* fix: subagent summerize
* fix: group subagent text by agent name for parallel fallback
The flat subagent_text_buffer list would interleave text from parallel
sub-agents into incoherent output. Replace with a dict grouped by
agent name so each sub-agent's text stays coherent, with [name]:
attribution when multiple agents contribute.
Add comprehensive tests for the new behavior (24 tests).
* fix: group subagent text by agent name for parallel fallback
The flat subagent_text_buffer list would interleave text from parallel
sub-agents into incoherent output. Replace with a dict grouped by
agent name so each sub-agent's text stays coherent, with [name]:
attribution when multiple agents contribute.
Add comprehensive tests for the new behavior (24 tests).
* fix: group subagent text by agent name for parallel fallback
The flat subagent_text_buffer list would interleave text from parallel
sub-agents into incoherent output. Replace with a dict grouped by
agent name so each sub-agent's text stays coherent, with [name]:
attribution when multiple agents contribute.
Add comprehensive tests for the new behavior (24 tests).
* chore: fix multiple agent
* test: add test
* fix linter
* remove redundant
* feat: add STT voice transcription for all channels
Automatically transcribes audio/voice messages (Telegram, WeChat, Slack,
etc.) into text before the agent sees them. Enabled via config, off by default.
Changes:
- EvoScientist/stt.py: new STT engine using faster-whisper with lazy
model loading and per-language model selection (zh/en/auto)
- EvoScientist/channels/base.py: hook in _enqueue_raw() to transcribe
audio files and prepend transcript to message text; removes the raw
[voice: ...] annotation after successful transcription so the agent
does not attempt further audio processing
- EvoScientist/config/settings.py: stt_enabled (default False),
stt_language (default "auto")
- pyproject.toml: optional [stt] dependency group (faster-whisper>=1.0)
- tests/test_stt.py: unit tests covering all backends and channel integration
Usage:
pip install 'EvoScientist[stt]'
EvoSci config set stt_enabled true
EvoSci config set stt_language zh # zh / en / auto
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: remove unused imports (ruff F401)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: address PR #28 reviewer feedback
Changes per SemiGlassFace review (CHANGES_REQUESTED):
1. Cache config at channel __init__ — no longer calls load_config() on
every incoming message; STT settings stored as instance attributes
(_stt_enabled, _stt_language, _stt_model, _stt_device,
_stt_compute_type) set once during Channel.__init__().
2. Replace deprecated asyncio.get_event_loop() with get_running_loop()
to avoid DeprecationWarning on Python 3.12+.
3. Annotation removal now uses exact path matching instead of substring
search — checks fp == a or a.endswith(f": {fp}]") so only the
correct annotation is removed after transcription.
4. Expose stt_model, stt_device, stt_compute_type as config fields so
users can override the HuggingFace model id, inference device, and
quantisation without touching code. transcribe_file() forwards all
three to the engine.
Also: _engines dict replaced with single _engine + _engine_key tuple
(model_id, device, compute_type) — reuses cached model unless settings
change, simpler than a dict.
Tests: 19 STT-specific tests all pass; total 1105 tests green, ruff clean.
* fix: resolve ruff lint errors (UP037, I001, PT006)
* style: apply ruff format
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add priority binding for TAB to intercept before Textual's focus_next
- Remove duplicate up/down handling in on_key (now handled by priority
bindings from PR #76)
- Update tests to use cmd_manager.list_commands() instead of removed
_TUI_SLASH_COMMANDS
Closes#57
Co-authored-by: X-iZhang <zacharyzhang2022@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add DeepSeek as a recognized third-party provider
Register DeepSeek API (https://api.deepseek.com) with DEEPSEEK_API_KEY
env var and add model short names: deepseek-r1 → deepseek-reasoner,
deepseek-v3 → deepseek-chat.
* feat: add _flatten_message_content utility for list-to-string conversion
Extract text from content block lists while skipping thinking/reasoning
blocks. This handles the case where LangChain stores assistant messages
with content as a list of content blocks instead of a plain string.
* fix: flatten list content to strings for OpenAI-compatible providers
Add _patch_openai_compat_content() that wraps _generate/_agenerate to
sanitize message content before API calls. Apply it for all third-party
OpenAI-compat providers and native OpenAI proxies.
This fixes "invalid type: sequence, expected a string" errors from
strict APIs like DeepSeek that reject list-format content in assistant
messages during multi-turn conversations.
* feat: add DeepSeek API key validation and integrate into onboarding process
test: implement unit tests for content flattening utility in OpenAI-compatible providers
---------
Co-authored-by: Xi Zhang <106144707+X-iZhang@users.noreply.github.com>
Co-authored-by: X-iZhang <zacharyzhang2022@gmail.com>
Comprehensive step-by-step guide covering:
- ccproxy installation from source (patched fork required for Claude 4+)
- ccproxy OAuth login via `ccproxy auth login claude-api`
- EvoScientist install with telegram + stt extras using uv
- STT model pre-download to avoid first-message delay
- launchd plist setup for auto-start on login with KeepAlive
- Full troubleshooting section based on real deployment experience:
- OAuth token not found (.credentials.json location)
- Packages installed in wrong Python environment (conda vs .venv)
- Shell glob eating brackets in pip install 'pkg[extra]'
- Whisper hallucination / VAD filter
- ffmpeg approval prompts → --auto-approve
- heredoc variable expansion gotcha
- External drive mount timing with launchd
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Xi Zhang <106144707+X-iZhang@users.noreply.github.com>
- Fix RUF006: Implement background task tracking in Discord, iMessage, WeChat, and TUI to prevent premature GC of fire-and-forget tasks.
- Fix B904: Add explicit exception chaining (raise ... from) across all exception handlers.
- Fix RUF012: Annotate mutable class attributes with ClassVar for command arguments and media maps.
- Fix B008: Refactor Typer commands in cli/commands.py to use Annotated for argument and option defaults.
- Fix B023/B018: Resolve late-binding issues in lambdas and remove useless expressions.
- Fix syntax errors in retry.py docstrings and models.py lambda parameter ordering.