Post-merge validation fixes (upstream v0.3.0 + Ai4Sci fork):
- llm/patches.py: restore the two module-level patch calls the merge dropped
(_patch_openai_empty_sse_keepalive, _patch_deepagents_extracted_document_text)
and make _is_ccproxy_codex accept an explicit base_url/api_key so the
invocation plan can classify an endpoint without mutating the process env.
- llm/models.py: an explicit per-call plan now wins over
EVOSCIENTIST_USE_RESPONSES_API (env is only a default), an explicit caller
`reasoning` block survives an explicit use_responses_api=False, and the
third-party (openrouter) default effort stays the fork's fixed `medium`.
- EvoScientist.py: sub-agent stacks pass NO_OP_SINK as `events` instead of None.
- middleware/error_normalization.py: platform-generated diagnostics
(ModelOutputTruncatedError) keep their actionable text while provider SDK
errors still get the canned redacted message.
- pyproject.toml: hold google-genai 1.x (langchain-google-genai>=4.3.7,<4.4)
because llm/gemini_interactions.py drives the 1.x Interactions API; this is
also what deepagents 0.7.13 requires.
- config/settings.py: restore upstream's use_responses_api config field.
`reasoning_effort` stays deleted on purpose — Ai4Sci keeps reasoning an
invocation-plan parameter, never a deployment-env override.
- tests: align upstream tests that encode replaced behaviour (ccproxy
responses-api context, reasoning-effort-overrides-env, fingerprint coverage)
with the fork's contracts.
OpenRouter keys app pages by HTTP-Referer; X-Title only renames that
page. A custom openrouter_app_title on the default referer therefore
renamed the shared EvoScientist app page for everyone. Force the default
title whenever the resolved referer is the default, silently, so usage
keeps being attributed to EvoScientist; a private fork still overrides
both together.
* fix: defer eager observation-index build to first model call
* fix: add mtime-keyed cache to list_observation_documents to avoid re-parsing unchanged files
* fix: return a copy of the cached document list and strengthen the deletion test
* fix: copy cached document list on read and write to prevent caller mutations
* fix: split global and project cache to avoid duplicate parsing and cross-project invalidation
* fix: bump mtime explicitly in cache modification test for Windows NTFS resolution
* fix: bound project observation cache with LRU eviction
* fix: deduplicate path logic and strengthen cache typing
* fix: group cache tests under TestObservationCache with autouse fixture and fix f-string interpolation
* fix: reject non-positive observation cache cap in config validation
* fix: cache resolved observation docs and config cap to avoid repeated work
* fix: use st_mtime_ns and st_size in cache signature for NTFS reliability
* test: clear EVOSCIENTIST_MAX_CACHED_PROJECTS in test env cleanup fixtures
* test: cover per-file observation cache semantics
* fix: replace layered observation caches with per-file parse cache
* fix: serialize observation parse cache transactions
* Add Novita as an LLM provider
Registers Novita (novita.ai) as an OpenAI-routed provider, following the
same pattern as Requesty/Atlas Cloud/SiliconFlow: a base_url + API key env
var entry in _OPENAI_ROUTED_PROVIDERS, a handful of model registry entries
(DeepSeek/Qwen/GLM), onboarding wizard support (constants/steps/wizard/
helpers), a key validator using the auth-preflight sentinel pattern (Novita's
/v1/models endpoint returns the public catalog even for an invalid key, so
auth must be checked via a chat completion instead), and a host-to-provider
mapping entry for error attribution.
* Recommend Novita's current flagship models
The models listed for Novita were older ids that no longer reflect what
the platform leads with. Point the recommendations at the three current
flagships instead, each verified against api.novita.ai:
moonshotai/kimi-k3 1M context, native vision
zai-org/glm-5.2 1M context, long-horizon agentic work
deepseek/deepseek-v4-flash-0731 1M context, cheapest of the three
Context windows, output limits, input modalities and pricing were taken
from the live /openai/v1/models response rather than carried over.
* Keep branch CI workflow files unchanged (no workflow OAuth scope)
Co-authored-by: multica-agent <github@multica.ai>
* ci: restore workflow files to match main
---------
Co-authored-by: jax-novita <jax-novita@users.noreply.github.com>
Co-authored-by: multica-agent <github@multica.ai>
Co-authored-by: Dinos Papakostas <dinospk1999@gmail.com>
Co-authored-by: Xi Zhang <106144707+X-iZhang@users.noreply.github.com>
* feat: add thread metadata index for improved performance in thread listing
- Implemented a new SQLite index on the `checkpoints` table to optimize thread listing queries by indexing relevant metadata fields.
- Updated the `list_threads` function to ensure the index is created if it does not exist.
- Added a test to verify the creation of the metadata index during thread listing.
feat: enhance workspace sidecar management with owner tracking
- Modified the workspace sidecar to include `owner_pids` to track the current process owners.
- Updated tests to validate the new owner tracking functionality and ensure proper behavior when managing workspace sidecars.
chore: introduce model registry for streamlined model management
- Created a new `registry.py` file to maintain a comprehensive model registry, including model names, IDs, providers, and routing tables.
- Added functions to retrieve models by provider and list available models, enhancing the modularity and maintainability of model management.
* feat: enhance workspace sidecar management and improve thread metadata indexing
* fix(tests): ensure sidecar correctly registers owner with original workspace and pid
* refactor: simplify workspace sidecar management by removing owner tracking
* feat(server): add commands to manage background langgraph dev server
- Introduced `server_app` for managing the langgraph dev server with commands to check status and stop the server.
- Enhanced workspace sidecar management to include configuration fingerprint for drift detection.
- Updated deployment functions to handle server configuration and state more effectively.
* feat(server): enhance server status command to display PID with stale record warning
* feat(langgraph_dev): exclusion-set config fingerprint, webui keepalive, unified stop guidance
* fix(cli): platform-specific manual-stop hint; document keepalive endpoint-change limitation
* fix: change default bind host to loopback for security across all components
* fix: update documentation and tests for loopback host configuration and security warnings
* feat: configurable bind host for WebUI and langgraph dev (refs #400)
WebUI mode was only reachable from the machine running it: the front-end
got no bind interface, and `start_langgraph_dev(...)` was called without a
host, so both servers stayed on loopback with no way to widen them.
Adds two config fields with deliberately different defaults:
webui_host = 0.0.0.0 front-end serves the app shell, no secrets
langgraph_dev_host = 127.0.0.1 unauthenticated API, agent can run shell
The design hinges on separating bind address from client address. Only
bind() uses the configured interface; every consumer that *connects*
(health probes, occupancy checks, async sub-agent self-dispatch) goes
through the new `_probe_host`, which maps a wildcard bind back to
loopback and honors a pinned interface verbatim. `_can_bind_port` is the
one exception and binds the literal host, since it must replicate the
bind the server itself will attempt.
- manager.py: `_probe_host`, `_is_loopback_host`, `_format_hostport`;
host kwarg threaded through the probes and `start_langgraph_dev`,
which now emits `--host` and propagates
EVOSCIENTIST_LANGGRAPH_DEV_HOST to the subprocess
- sdk.py: `langgraph_dev_url` tracks host as well as port;
EvoScientist.py reuses it instead of an inline f-string
- server.py: `--host` flag mirroring `--port`, plus a red PUBLIC BIND
banner whenever the bind is not provably loopback
- webui.py: forwards both hosts; the front-end is widened via HOSTNAME
because @evoscientist/webui ships no --host flag — its bin launcher
does `HOSTNAME: process.env.HOSTNAME || "127.0.0.1"`. The warning is
gated on the backend host only, so the shipped front-end default
doesn't print a banner on every launch
Verified end to end against a live server: requesting 0.0.0.0 yields a
socket listening on 0.0.0.0 with the health probe correctly resolved to
127.0.0.1, while the default still binds 127.0.0.1 only.
Note: webui_host defaulting to 0.0.0.0 is a behavior change — upgrading
users will find the front-end reachable from the LAN.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat: default both bind hosts to 0.0.0.0, add --host and wizard host rendering (closes#400)
Completes the remaining items from #400.
- `langgraph_dev_host` now defaults to 0.0.0.0, matching `webui_host`.
Remote WebUI use needs both anyway (the UI reaches the backend from the
browser, not server-side), so a loopback backend default just meant every
remote user hit a silently failing UI. `_DEFAULT_HOST` and sdk's
`DEFAULT_LANGGRAPH_DEV_HOST` follow, so there is one story about where
these servers listen.
SECURITY: this exposes an unauthenticated API whose agent can run shell
commands. The red PUBLIC BIND banner consequently fires on every launch
while exposed — kept deliberately, since the exposure is real and the
escape hatch (`--host 127.0.0.1` / `config set langgraph_dev_host`) is
only discoverable if we say so. READMEs now lead with the warning and
document the SSH-tunnel alternative.
- `EvoSci --host <ip>` on the WebUI launch path, driving both servers. In
WebUI mode they are two halves of one surface; moving only one leaves the
UI loading but unable to reach the agent. Blank values are dropped rather
than written as an empty override that would beat the config file.
- Onboarding wizard no longer prints hard-coded `http://127.0.0.1:{port}` /
`http://localhost:{port}` (steps.py:160, :223) — both render the
configured bind through `_base_url` / `_format_hostport`, so a pinned
interface is reported honestly and a wildcard still shows loopback.
Verified against a live server: with no host argument at all, resolution
through EvoScientistConfig yields a socket listening on 0.0.0.0, a client URL
of http://127.0.0.1, and the warning gate returning True.
Still open and tracked separately: the front-end takes its backend URL from
browser input: `@evoscientist/webui` reads only HOSTNAME, PORT and
EVOSCIENTIST_LANGGRAPH_DEV_PORT, so advertising a backend URL needs a change
in that repo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* ci: bump setup-uv v6 -> v9.0.0 to drop the deprecated node20 runtime
GitHub now warns that setup-uv@v6 targets Node.js 20 and is being forced
onto Node.js 24. v7.0.0 is the release that made that switch, so anything
>= v7 clears the warning; v9.0.0 is current.
Pinned to the full tag deliberately: setup-uv stopped publishing major and
minor tags in v8.0.0 as supply-chain hardening, so `@v9` and `@v8` return
404 and would fail the job outright. Releases are immutable from v8 on, so
the full tag is as tamper-proof as a SHA. Comment left in lint.yml because
"simplifying" this back to `@v9` is an easy and CI-breaking mistake.
actions/checkout@v5 is already node24 and needs no change.
Note: v9.0.0 flips the `prune-cache` default to false (upstream did this to
ease load on PyPI infrastructure). None of these workflows set it, so they
follow the new default and Actions cache usage may grow.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(cli): correct --host help text and warn on public bind in non-WebUI modes
The --host help claimed "WebUI mode only", which is wrong in a way that
matters for security. `--host` writes `langgraph_dev_host` unconditionally,
and `_ensure_async_subagent_server` auto-starts that backend for tui / cli /
serve as well — the langgraph dev server is shared across UI modes. So the
flag narrows or widens the agent API in every mode, and only `webui_host` is
actually WebUI-specific. Reported against cli/commands.py.
The documentation error hid a real gap: the PUBLIC BIND banner lived only in
deploy/server.py and deploy/webui.py, so a plain `EvoSci` session bound
0.0.0.0 with no runtime signal whatsoever — and `--help` is opt-in, so
fixing the text alone would not surface it. Added the same banner to the
shared CLI path, gated on `is_async_subagents_available()`: ensure_langgraph_dev
fails soft (async degrades to in-process delegation), and warning about a
bind that never happened would be worse than staying quiet.
READMEs (EN + zh-CN) get the same correction — the warning block sat inside
the Desktop WebUI section and read as WebUI-scoped.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(deploy): strip the config-derived bind host, not just the CLI one
`deploy()` only stripped the `--host` branch. When the flag was omitted,
`getattr(config, "langgraph_dev_host", ...)` flowed unstripped into
`_is_port_occupied`, `is_langgraph_dev_running`, `start_langgraph_dev` and
the banner. `run_webui` already strips unconditionally; this aligns the two.
Reachable because `deploy()` reads through `getattr` and is routinely handed
duck-typed config objects (tests, embedders) that never run
`EvoScientistConfig.__post_init__`, which is what normally normalizes these
fields.
Worst case was not just a bad bind: `_is_loopback_host(" 127.0.0.1 ")` is
False, so a padded loopback value would print a false PUBLIC BIND warning
while binding a string socket.bind() rejects outright — a security banner
saying the opposite of the truth.
Three regression tests added, each verified to fail against the old code.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* style: apply ruff format to the bind-host changes
The Lint workflow runs both `ruff check` and `ruff format --check`; I had
only been running the former locally, so five files landed unformatted and
failed CI. Whitespace and line-wrapping only — no semantic change.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(security): keep the langgraph dev backend on loopback by default
The backend is an unauthenticated API whose agent can run shell commands,
and it is auto-started in every UI mode (tui/cli/webui/serve/deploy) — so a
0.0.0.0 default put it on the network for users who never asked. Restore
127.0.0.1 as the default and make 0.0.0.0 an explicit opt-in.
webui_host keeps its 0.0.0.0 default: the front-end serves the app shell
only and holds no credentials. run_webui already prints a remote-backend
hint when the front-end is exposed and the backend is not.
Help text and both READMEs are reframed around widening rather than
narrowing; the escape-hatch tests are inverted to assert the public-bind
opt-in survives into argv.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
- Introduced TodoListMiddleware to the middleware stack for better task management.
- Updated HITL interrupt configuration to include 'delete' operations requiring approval.
- Implemented error handling for delete operations in read-only and memory backends.
- Enhanced approval prompt formatting to display file paths for delete actions.
- Added tests to ensure delete operations are correctly blocked or prompted for approval.
- Updated dependencies to use deepagents 0.7.0 and langchain 1.5.3 for improved functionality.
* Add Requesty as an LLM provider
* Address review: Requesty prompt caching, model ordering, key validation
- Declare Anthropic-style prompt caching for Requesty Claude models by
default (mirroring the OpenRouter behavior), with an opt-out flag
EVOSCIENTIST_REQUESTY_ANTHROPIC_PROMPT_CACHE. Requesty is an OpenAI-routed
provider, so the caching check now uses the original provider name.
- Move the Requesty model entries above OpenRouter so Requesty no longer
overrides native/OpenRouter models for names it shares with them
(the MODELS dict is last-entry-wins); drop the outdated gpt-4o-mini entry.
- Fix validate_requesty_key: Requesty's /v1/models returns 200 even for an
invalid/missing key (public catalog), so it cannot validate a key. Use a
minimal authenticated /v1/chat/completions request instead (200 = valid,
403 = invalid), verified against the live endpoint.
- Add tests for Requesty prompt caching (default on, opt-out, non-Anthropic skip).
* Validate Requesty key against auth layer, not a specific model
The onboarding validator probed /v1/chat/completions with a hardcoded
real model (openai/gpt-4o-mini), which tied key validation to that model
staying available upstream. The router resolves auth before the model, so
probe a deliberately nonexistent sentinel model (requesty/auth-preflight)
instead: a valid key yields 404 (model-not-found, auth passed), an invalid
key yields 401/403, and 429/5xx stay inconclusive so a transient outage
does not reject a good key. Add unit tests covering each case.
---------
Co-authored-by: X-iZhang <zacharyzhang2022@gmail.com>
* fix: propagate langgraph dev bind port into subprocess env for self-loop URL
* fix: keep parent env authoritative over workspace .env for mapped keys
* fix: limit .env shadow-guard to EVOSCIENTIST_* keys so API keys keep .env-wins
* fix: snapshot EVOSCIENTIST_* env by prefix instead of filtering _ENV_MAPPINGS
* fix: merge .env via dotenv_values to close empty-value and RMW-race edges
* chore: align docstrings after .env-merge rework
---------
Co-authored-by: Xi Zhang <106144707+X-iZhang@users.noreply.github.com>
* fix(llm): respect reasoning_effort setting on native OpenAI path
The native OpenAI provider path hardcoded reasoning effort to xhigh for
gpt-5.4/5.5/codex models, silently ignoring the user's reasoning_effort
config setting. The OpenRouter path already honors the
EVOSCIENTIST_REASONING_EFFORT env var that settings.py exports from that
setting; this applies the same lookup on the native path, falling back
to the previous defaults when unset.
Adds a regression test and isolates the existing xhigh test from the
env var.
* fix(llm): preserve model reasoning defaults
* fix(llm): preserve GPT-5.6 reasoning default
---------
Co-authored-by: Xi Zhang <106144707+X-iZhang@users.noreply.github.com>
* feat(llm): add OpenRouter app attribution headers (#339)
Attach EvoScientist app-attribution at the shared model-init layer so all
OpenRouter calls are credited to the project. langchain-openrouter maps
app_url/app_title/app_categories -> HTTP-Referer / X-Title /
X-OpenRouter-Categories. Applied only for the openrouter provider, via
setdefault so explicit caller kwargs win. Configurable through new
openrouter_http_referer / openrouter_app_title / openrouter_app_categories
settings and their EVOSCIENTIST_OPENROUTER_* env vars.
Closes#339
* refactor(llm): centralize OpenRouter attribution defaults + cap categories
Address PR #344 review:
- Define the app-attribution default constants once in config/settings.py
(the config fields and llm/models.py both use them) instead of duplicating
the literals across the two modules.
- Reduce the default categories to creative-writing,personal-agent and cap the
sent list to OpenRouter's 2-per-request limit, warning when a configured list
exceeds it, so extras are dropped predictably (and surfaced) here rather than
being silently truncated server-side.
---------
Co-authored-by: Xi Zhang <106144707+X-iZhang@users.noreply.github.com>
* feat: add scheduler functionality with cron-style task management
- Implemented a new scheduler subagent to automate recurring tasks using cron expressions.
- Enhanced the subagent factory to include the skill manager and auxiliary chat model for the scheduler.
- Created a YAML configuration for the scheduler with a detailed system prompt and toolset.
- Updated README files to include documentation on scheduled tasks and usage examples.
- Added tests for the scheduler, including command execution, scheduling tools, and middleware integration.
- Introduced new dependencies for timezone handling and ensured compatibility in the project configuration.
* fix(async-notifier): ensure fallback hint is used for unknown notification kinds
* feat: enhance scheduling functionality and improve system message handling
* feat(dangerous-mode): implement real-filesystem access with safety checks
- Introduced a 'dangerous mode' allowing the agent to operate on the real filesystem.
- Updated command validation to bypass path confinement while enforcing a blocklist for privileged commands.
- Added warnings and guidelines for users when operating in dangerous mode.
- Enhanced configuration to support dangerous mode and ensure it implies auto-approval.
- Updated tests to verify the behavior of commands and configurations in dangerous mode.
* feat(dangerous-mode): enhance logging and environment management for dangerous mode
* feat(dangerous-mode): improve handling of dangerous mode with environment flags and enhance test isolation
* feat(middleware): reposition code interpreter middleware in the stack
* feat(models): add qwen3.7-plus model entry and update context window comment
* feat(models): add qwen3.7-max and qwen3.7-plus model entries for DashScope
* feat(auxiliary): implement auxiliary model support for background tasks and tool selection
- Added auxiliary model configuration to EvoScientistConfig.
- Introduced _ensure_auxiliary_chat_model function to manage auxiliary model instances.
- Updated onboarding steps to include auxiliary model selection.
- Modified middleware to route tool selection to the auxiliary model when applicable.
- Enhanced tests to cover auxiliary model functionality and configuration.
* feat(steps): update UI backend selection options and descriptions
* Refactor code structure for improved readability and maintainability
* feat(patches): implement OpenRouter response reasoning item stripping to prevent multi-turn errors
* feat: update version to v0.1.4 in badges, README, and pyproject.toml; adjust skill counts in steps.py
* feat(config): add auxiliary model and provider environment variables to test setup
* feat(memory): add observation memory lifecycle
Add file-backed observation memory with deterministic markdown records,
structured record_observation tooling, startup indexing, and
profile/observation prompt guidance.
Launch post-turn and post-subagent EvoMemory workers through LangGraph
dev so completed runs can update profile memory, save durable
observations, and write subagent execution summaries without blocking
the active agent.
Wire memory middleware into the main agent, subagents, async graphs, TUI
status reporting, worker activity accounting, and observation-aware
research prompts, with regression coverage for storage, lifecycle
scheduling, graph registration, status display, and stream reset
behavior.
* fix(cli): sync background agent server on resume
Resume flows now need to keep the LangGraph dev background server
aligned with the active workspace even when async subagents are
disabled. EvoMemory workers use that server too, so gating resume-time
sync on enable_async_subagents could leave workers pinned to the launch
workspace after resuming a thread from another workspace.
Run workspace sync unconditionally for Rich CLI and Textual resume
paths, while preserving WorkspaceMismatchError handling so failed sync
aborts the resume before mutating the active thread or workspace.
Propagate aborted resume callbacks through the command UI so
channel-issued /resume commands do not send false success or history
output. Channel slash dispatch now treats CommandManager-caught command
errors as command errors and skips completion hooks for those failed
commands.
Add regression coverage for disabled async subagents, callback aborts,
and channel command error reporting.
* fix(cli): prepare serve resume workspace before adopting
Load the resumed workspace agent and sync the background server as a
single pre-adoption step. Restore the previous active workspace if
preparation fails so serve mode keeps using the old session
consistently.
* fix(memory): untrack abandoned worker status watches
Stop treating watcher shutdown as confirmed worker completion. Terminal
worker statuses still count memory deltas, while poll failures or
watcher setup failures now remove the active run without crediting
partial outputs.
* fix(cli): report channel command failures accurately
Treat command_error as a None sentinel so empty error strings still
fail, and let TUI resumes continue only on non-mismatch
background-server sync failures while reporting degraded mode.
* fix(stream): clear memory counters for resume streams
Reset completed-memory counters for every new agent stream, including
Command-based HITL and resume streams, so saved-memory indicators do not
leak across turns.
* docs(tools): make observation recording guidance conditional
Clarify that agents should call record_observation only when the
observation tool is available, preserving the existing durability and
usefulness criteria.
* feat(config): add controls for profile and observation memory
Add config flags for profile memory, observation memory, observation
writer placement, and background memory workers.
Wire the controls through main agents, subagents, EvoMemory middleware,
and memory lifecycle workers so observation writes can be assigned to
the live agent, subagent worker, both, or neither. Keep turn memory
workers profile-only and make prompts reflect the available observation
read/write paths. Skip langgraph dev startup when neither async
subagents nor memory workers need the background server.
Add coverage for config parsing, prompt gating, middleware wiring, and
worker tool availability.
* test(cli): include memory defaults in serve config stubs
* fix(memory): offload async worker launch blocking calls
Run the langgraph-dev health check and memory-output snapshot in worker
threads from the async EvoMemory launcher so it does not block the event
loop.
* chore(memory): harden turn worker subagent guardrail
* chore(memory): refresh profile context per request
* fix(memory): offload async profile file reads
* fix(memory): offload async worker completion accounting
* feat: add WebUI mode support with related configuration and onboarding steps
* feat: enhance WebUI port configuration to prevent conflicts with backend port
* feat: add support for fresh interactive session detection in WebUI
* feat: implement configurable sandbox execute timeout and enhance recovery instructions
* feat: add background process management tools and middleware for sandbox execution
* feat: enhance background process management with completion notifications and deduplication
* feat: enhance sandbox execution timeout validation and update related messages
* feat: enhance background process management with thread-specific completion notifications and HITL approval handling
* test: assert completion notification waits for process finish timestamp
* feat(middleware): add CodeInterpreterMiddleware with project-specific configuration
chore(config): increase checkpoint retention limit for runaway conversations
fix(tests): update database schema references from 'blob' to 'value'
chore(deps): update deepagents dependency to include quickjs support
* feat(deepagents): update to version 0.6.1 and add optional dependencies for quickjs
* feat(sessions): improve error handling for message deltas and update Overwrite type check
* Enhance PruningCheckpointer with DeltaChannel Awareness
- Introduced a new pruning strategy in `_prune_after_put` to preserve the `_DeltaSnapshot` chain during checkpoint pruning.
- Implemented methods to fetch recent checkpoint IDs and walk to snapshot ancestors, ensuring that necessary checkpoints are retained.
- Updated SQL queries to handle checkpoint and write deletions more efficiently.
- Added comprehensive tests for DeltaChannel-aware pruning, ensuring that the pruning logic correctly handles various checkpoint scenarios, including those with and without snapshot seeds.
- Refactored `_load_checkpoint_messages` to utilize the new saver interface, improving message reconstruction from checkpoints.
* feat(tests): add migration sweep test to preserve snapshot ancestor
* feat(sessions): enhance checkpoint retrieval to prevent transcript leakage in multi-agent scenarios
* feat(middleware): enhance CodeInterpreterMiddleware with configurable timeout and result character limit
feat(config): add CodeInterpreterMiddleware tuning parameters to EvoScientistConfig
feat(sessions): implement inline message delta reducer for improved message handling
* feat(dependencies): update deepagents version to 0.6.2 in pyproject.toml and uv.lock
* feat(wechat): add personal-WeChat (iLink) backend with QR-code login
Adds a third WeChat backend alongside WeCom and Official Account:
``personal`` rides Tencent's iLink Bot long-poll gateway so a personal
WeChat account can act as a bot. Credentials are obtained via QR-code
scan and persisted under ``DATA_DIR/wechat_personal/accounts/``.
- channels/wechat/personal.py: WeixinPersonalChannel + qr_login.
- channels/wechat/crypto.py: aes128_ecb_decrypt + parse_ilink_aes_key
for the iLink CDN media protocol.
- channels/wechat/probe.py: validate_wechat_personal credential probe.
- channels/wechat/serve.py: --backend personal CLI + --qr-login flow.
- channels/wechat/__init__.py: factory dispatch on wechat_backend; pull
in the new dependencies in the docstring.
- config/settings.py: wechat_personal_* fields.
- config/onboard.py: WeChat-backend picker + QR-scan flow in the wizard
+ personal-backend probe in _probe_channel.
- pyproject.toml / uv.lock: add qrcode + certifi to wechat & all-channels
extras (aiohttp was already pulled in transitively).
* fix(wechat): address ruff failures and CodeRabbit review on personal-WeChat PR
- personal.py: drop unused imports (`field`, `PollingMixin`); replace
`asyncio.TimeoutError` with builtin; hold references to background
`asyncio.create_task` results so they aren't GC'd; wire `dm_policy`
through `_process_message` (disabled/allowlist) so `wechat_personal_dm_policy`
actually takes effect for DMs.
- onboard.py: import-check gate now validates the full WeChat dependency
set (aiohttp, qrcode, Crypto, certifi) instead of only aiohttp; mask
`WeCom Secret` and `MP App Secret` prompts via `questionary.password`;
derive the QR-login hint path from `_account_dir()` instead of the
hard-coded `~/.evoscientist/...`; stop copying the QR-login token into
the main config (already persisted per-account on disk — copying broadens
secret exposure and risks staleness).
- pyproject.toml: allow Chinese full-width punctuation in `allowed-confusables`
for user-facing CN messages.
* style(wechat): apply ruff format
`ruff format --check` was failing CI on three files (one pre-existing in
`__init__.py` plus formatter-driven line-merges in the files touched by
the previous fix commit). Ran `ruff format` to bring them in line; both
`ruff check` and `ruff format --check` now pass.
* Refactor sub-agent architecture and introduce async support
- Removed the legacy subagent.yaml file and replaced it with individual YAML files for each sub-agent in the subagents directory.
- Updated the load_subagents function to support both directory and single file layouts for loading sub-agent configurations.
- Added new langgraph_dev module for managing async sub-agent lifecycle and deployment.
- Created graphs for async sub-agents (writing-agent, data-analysis-agent) and updated langgraph.json for deployment.
- Introduced new sub-agent definitions for planner, research, debug, code, and writing agents with appropriate system prompts and configurations.
- Enhanced package data inclusion in pyproject.toml to accommodate new sub-agent YAML files.
* Refactor code for improved readability by consolidating conditional statements and formatting
* feat: enhance async sub-agent support with workspace synchronization and user feedback
- Added console status messages during async sub-agent server startup and workspace synchronization to improve user experience.
- Implemented a new WorkspaceSyncWidget for live feedback during workspace sync operations.
- Updated onboarding to reject occupied ports and ensure proper workspace handling for async sub-agents.
- Introduced locking mechanisms to manage concurrent access to langgraph dev processes and workspace states.
* feat: add async sub-agent configuration and server management functions
* feat: improve port occupation handling and log file management in start_langgraph_dev
* feat: enhance async sub-agent handling and introduce comprehensive tests
- Updated `_maybe_swap_async_subagents` to improve async sub-agent management, ensuring internal flags are stripped before handoff.
- Enhanced port management in `onboard.py` to allow reuse of occupied ports if already running by the same service.
- Introduced file locking in `manager.py` to prevent race conditions during concurrent CLI invocations.
- Added new tests for async sub-agent swapping and langgraph manager functionalities to ensure reliability and correctness.
- Updated dependencies in `pyproject.toml` to include `psutil` and `filelock`.
* fix(docs): clarify sub-agent configuration in README
* test(manager): isolate _PID_DIR + tighten reuse-path assertion
Addresses CodeRabbit review on tests/test_langgraph_manager.py:
- Patch _PID_DIR to tmp_path so the FileLock setup in
ensure_langgraph_dev doesn't mkdir the user's real
~/.config/evoscientist/ dir as a test side-effect.
- Tighten "result is None or hasattr(result, 'poll')" to a strict
"result is None" — the reuse path returns None unconditionally,
so the OR clause was hiding potential regressions.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(manager): clean up stale PID file when unrelated process reuses PID
* feat(tests): add validation tests for async flag in load_subagents
* fix(load_subagents): restrict to .yaml files and clarify configuration handling
* fix(load_subagents): improve error handling for non-dict specifications in YAML
* feat(onboard): add "LangGraph Port" step to onboarding process
* feat(langgraph): add concurrency configuration for langgraph dev workers
* feat(async-subagents): enhance MCP tool routing for async sub-agents
* fix(manager): update exception handling for connection errors and prevent zombie processes
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Implement PruningCheckpointer for efficient checkpoint management and add comprehensive tests
- Introduced `PruningCheckpointer` to manage checkpoint pruning after each `aput()`, ensuring only the latest checkpoints are retained based on a configurable limit.
- Added migration sweep functionality to clean up legacy checkpoints and prevent database bloat.
- Enhanced `get_checkpointer()` to utilize the new `PruningCheckpointer` and trigger migration sweeps when necessary.
- Developed a suite of integration tests for `PruningCheckpointer`, covering various scenarios including pruning behavior, concurrent writes, and retention policies.
- Implemented tests for migration sweep functionality, ensuring proper partitioning and user version management.
- Added diagnostic helper `db_stats` to provide insights into the database state, including thread and checkpoint counts.
* feat(sessions): enhance pruning logic to handle legacy DBs without writes table
* fix(tests): prevent atexit hook leakage in TestMigrationSweep
* feat(tests): enhance TestPruningCheckpointer to validate put+prune serialization
* feat(tests): refactor mock path implementation for get_db_path in test cases
* Add status bar and compact summary widgets with context window resolution
- Implemented a shared status bar for CLI and TUI frontends, including helpers for managing session metrics and context windows.
- Created a `CompactSummaryWidget` for displaying manual summaries in a collapsible format.
- Introduced a `CompactingWidget` to indicate ongoing compacting processes.
- Added a base class `TimedStatusWidget` for widgets that require a timer.
- Developed context window resolution helpers to retrieve context window sizes from various model attributes.
- Enhanced tests for context window resolution and status bar functionalities, ensuring accurate behavior across different scenarios.
- Updated existing tests to cover new features and maintain code quality.
* refactor(Channel): simplify lambda function in _send_with_retry method
* feat: enhance context editing logic and improve error handling in StreamState
* refactor(Channel): streamline lambda function in _send_with_retry method
* feat: rename auto-approve option to auto-mode for unattended execution; update checkpoint queries to filter by agent name; improve compatibility validation logic
* feat: rename auto-approve option to auto-mode; update related logic and tests for improved unattended execution
* fix: correct formatting of console message for MCP server configuration status
* feat: add check for None summary_message in _apply_summarization_event to prevent errors
* feat: enhance _load_checkpoint_messages to validate message format and apply summarization event
* 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>
* 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>
* 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(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>
* 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: 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>
* 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>
Add MiniMax (api.minimax.io/v1) as a first-class third-party provider,
enabling direct API access without routing through NVIDIA/SiliconFlow/
OpenRouter intermediaries. Includes M2.5 and M2.5-highspeed models
with 204K context window.
Changes:
- Register "minimax" in _THIRD_PARTY_PROVIDERS with MINIMAX_API_KEY
- Add MiniMax-M2.5 and MiniMax-M2.5-highspeed model entries
- Add minimax_api_key to config, env mappings, and env export
- Add MiniMax to onboarding wizard with API key validation
- Update .env.example, README.md, README.zh-CN.md
- Add 9 unit tests and 3 integration tests (all passing)
Co-authored-by: PR Bot <pr-bot@minimaxi.com>
Co-authored-by: Xi Zhang <106144707+X-iZhang@users.noreply.github.com>
* fix: add config option for ccproxy port number
* feat: add user prompt for ccproxy port configuration and validation
fix: update is_ccproxy_running to use health check endpoint
test: enhance tests for ccproxy port handling and validation
* fix: streamline ccproxy installation process using _install_pip_package
* fix: auto-patch ccproxy adapter for correct OAuth beta header
---------
Co-authored-by: Xi Zhang <106144707+X-iZhang@users.noreply.github.com>
Co-authored-by: X-iZhang <zacharyzhang2022@gmail.com>
load_dotenv was called in three scattered places: commands.py (main and
serve entry points) and search.py (at module import time). This could
re-override env vars after config resolution.
I moved the single load_dotenv call into get_effective_config() so it
participates in the config priority chain, and removed it from all
other call sites.