Replace the boolean wecom_bot_callback with a wecom_bot_mode field
("websocket" | "webhook"), consistent with the Feishu channel's
feishu_event_mode. Update startup() and send() to branch on the mode.
Also fix a temp-file leak in _load_image_base64: downloads, format
conversions and compressions wrote to /tmp but were never removed. Track
only the temp files created here and delete them in a finally block,
leaving the caller's original local file untouched.
Co-authored-by: Cursor <cursoragent@cursor.com>
Support receiving WeCom smart-bot messages via encrypted HTTP callback in
addition to the existing WebSocket long connection. Disabled by default
(wecom_bot_callback=false).
- Add wecom_bot_callback / wecom_bot_token / wecom_bot_encoding_aes_key /
wecom_bot_port config keys
- Add WXBizJsonMsgCrypt-based crypto module for URL verification, callback
decryption and passive-reply encryption (receive_id empty for internal bots)
- Reply asynchronously via the official stream-refresh polling: register a
stream id on first reply, accumulate agent output into per-stream state, and
serve the latest content (text + image) on each poll until finish
- Fall back to EncodingAESKey for media decryption when callback bodies carry
no per-message aeskey
- Degrade unsupported passive replies (file/voice/video) to a text notice
- Expose the new fields in the Web console channel config
Co-authored-by: Cursor <cursoragent@cursor.com>
Rework the multi custom-provider design per maintainer review:
1. Data model: use server-generated uuid4 short id as primary key;
'name' is now a pure display label that can be freely renamed.
2. Routing: drop 'custom_active_provider'; activate a provider by
setting bot_type to 'custom:<id>'. Single source of truth — no
pointer drift between bot_type and a separate active selector.
3. Security: drag_sensitive() now recursively masks api_key/secret in
nested structures (custom_providers list); previously only top-level
string fields were masked.
4. Per-provider model: the provider's 'model' field now takes effect on
the main chat path and agent path (was silently ignored before).
5. XSS fix: replace all inline onclick handlers in custom-provider UI
with data-* attributes + event delegation. Provider names never
appear in executable HTML contexts.
Legacy compatibility: bot_type='custom' (no colon) still reads the flat
custom_api_key/custom_api_base fields byte-for-byte identically.
Closes: consolidates #2876 into this PR as requested.
Ref: #2838
Adds first-class support for configuring more than one custom
OpenAI-compatible provider (e.g. SiliconFlow, DeepSeek, local vLLM)
and switching the active one from the web console, addressing #2838.
Backend:
- config: new `custom_providers` (list) and `custom_active_provider`
fields, fully backward compatible with the legacy single
`open_ai_api_base`/`model` fields (used as fallback).
- models/custom_provider.py: centralized resolver
`resolve_custom_credentials()` returning (api_key, api_base, model),
with active-provider selection and graceful fallback.
- chat_gpt_bot.py wired to use the resolver.
- web_channel.py: `_provider_overview` expands `custom_providers` into
one card per provider (id `custom:<name>`, active flag, masked key);
new POST actions `set_custom_provider`, `delete_custom_provider`,
`set_active_custom_provider` with hermetic persistence + bridge reset.
Frontend:
- console.js: dedicated "Custom providers" section with add / edit /
delete / set-active actions, masked-key keep-existing handling, and
~20 new zh/en i18n strings.
- chat.html: custom provider modal.
Tests:
- tests/test_custom_provider.py (11) - resolver/config behavior.
- tests/test_custom_provider_handlers.py (18) - write-side handlers and
overview expansion, including duplicate-name rejection.
All 29 unit tests pass.
Add a self-evolution subsystem that reviews idle conversations in an
isolated agent and durably learns from them — patching/creating skills,
finishing unfinished tasks, and backfilling missed memory.
- Trigger: background idle scan, fires when a session is idle >= N min AND
(>= N turns OR context usage > 80%). In-memory cursor reviews only new
messages so a session never re-learns old content.
- Isolated review agent: same model, restricted toolset, hard write-guard
confining edits to the workspace (built-in skills are protected).
- Safety: file-level backup before edits + evolution_undo tool; notify the
user ONLY when a workspace file actually changed (no-nag rule); capped
concurrency.
- Records to memory/evolution/<date>.md, surfaced in the memory UI's
renamed "Self-Evolution" tab (merged with dream diaries).
- Hide internal [SCHEDULED]/[EVOLUTION]/backup_id markers from chat history
display (also fixes scheduler marker leakage) while keeping them in stored
content for undo.
- Flat config: self_evolution_enabled (default off until release),
self_evolution_idle_minutes (15), self_evolution_min_turns (6).
- Tests: tests/test_evolution.py (stub + real model modes, 7 scenarios).
Move the sync_msg cursor file from the project-local tmp/ dir to ~/.wechat_kf_cursors.json so it survives tmp/ cleanups and cwd changes across restarts. Aligns with the weixin channel's credentials file convention.
- add wechat_kf_cursor_path config (default ~/.wechat_kf_cursors.json)
- expand ~ via os.path.expanduser in the channel init
- chmod the cursor file to 0o600 after each flush (no-op on Windows)
Rename the WeCom customer-service channel and give it its own corp_id
field so users no longer have to share `wechatcom_corp_id` with the
self-built WeCom app channel.
Renames (channel-side):
- channel type / const: wechatcom_kf -> wechat_kf
- package dir: channel/wechatcom_kf/ -> channel/wechat_kf/
- python files / classes: WechatComKf* -> WechatKf*
- config keys: wechatcom_kf_{secret,token,aes_key,port} ->
wechat_kf_{secret,token,aes_key,port}; new wechat_kf_corp_id
- env vars: WECHATCOM_KF_* -> WECHAT_KF_*; new WECHAT_KF_CORP_ID
- log prefix / cursor file: [wechatcom_kf] -> [wechat_kf]
- web console CHANNEL_DEFS key + startup log line
Renames (docs):
- docs/channels/wecom-kf.mdx -> docs/channels/wechat-kf.mdx (zh/en/ja)
- update docs.json sidebar entries and all field names inside the docs
In addition, the Web Console "微信客服" entry now exposes its own
Corp ID field instead of reusing the wechatcom_app one, and includes
the screenshot of the visual config in the channel guide.
Web Console onboarding section is added (Tabs: Web Console / config
file) and the local URL `http://127.0.0.1:9899/` parenthetical is
dropped for consistency with other channel docs.
Co-authored-by: Cursor <cursoragent@cursor.com>
- Clarify Secret retrieval (must tap "查看" on admin's phone, not copy)
- Update WeCom customer-service binding section to point to the
"接入链接" UI (copy link / generate QR code)
- Drop developer-only asides (wechatcomapp_secret / port collision
notes, internal sections about cursor persistence, channel runtime
differences, multi-kf-account support)
- Stop exposing `wechatcom_kf_cursor_dir` as a user config; cursor file
is now fixed under `tmp/`, which is an internal implementation detail.
Co-authored-by: Cursor <cursoragent@cursor.com>
- Switch from the local `WechatComAppClient` (whose `fetch_access_token`
may return the raw response dict and whose background refresh loop
re-fetches every 60s) to the stock `wechatpy.enterprise.WeChatClient`.
- Use `client.access_token` (string property) when building sync_msg /
send_msg URLs; the previous `client.fetch_access_token()` call could
interpolate a dict into the URL and yield errcode 40014.
- Always skip historical messages on first start; drop the
`wechatcom_kf_skip_history_on_first_start` config — there is no real
case for replaying up to 14 days of history.
- Change default callback port from 9899 to 9888.
Co-authored-by: Cursor <cursoragent@cursor.com>
Introduce a new channel that integrates with WeCom Customer Service
(微信客服), separate from the existing self-built WeCom app channel.
- Register channel type `wechatcom_kf` in factory, app loader and const
- Add config keys for token / secret / aes_key / port / cursor dir and
the first-start history-skip switch; also expose corresponding env vars
- Implement channel, message and cursor store under channel/wechatcom_kf/
Co-authored-by: Cursor <cursoragent@cursor.com>
Add embedding_provider config knob with native support for
openai / dashscope / doubao / zhipu / linkai, plus an in-chat
/memory status and /memory rebuild-index workflow for switching
vendors safely.
Boot MCP servers (npx/uvx) on a background thread instead of blocking
agent init. Built-in tools serve traffic immediately while MCP comes
online; each new agent reads whatever is ready at creation time.
Idempotent via _mcp_loaded flag — concurrent sessions never re-fork
subprocesses. Per-server failures are isolated and warmup is triggered
in app.py so loading overlaps with channel startup.
Allows CowAgent to dynamically load tools from any MCP server at startup,
extending the agent from a fixed toolset to an open, extensible tool ecosystem.
## What's added
- `agent/tools/mcp/mcp_client.py`: lightweight JSON-RPC client supporting both
stdio (subprocess) and SSE (HTTP) transports — zero extra dependencies
- `agent/tools/mcp/mcp_tool.py`: `McpTool` wraps a single MCP tool as a
`BaseTool`, with dynamic name/description/params set at instance level
- `agent/tools/tool_manager.py`: new `_load_mcp_tools()` loads MCP servers at
startup via `McpClientRegistry`; falls back gracefully on any error; no-op
when `mcp_servers` is not configured
- `config.py`: registers `mcp_servers` in `available_setting` with inline docs
## Design
- No new dependencies — JSON-RPC implemented from scratch using stdlib only
- MCP clients are long-lived (initialized once, shared across tool calls)
- `McpClientRegistry` holds all subprocess handles and shuts them down cleanly
- Server init failures are non-fatal: logged as warnings, agent continues normally
- Zero overhead when `mcp_servers` is absent from config
## Config example
```json
"mcp_servers": [
{
"name": "filesystem",
"type": "stdio",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
}
]
```
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The translate module previously only supported Baidu translation, and the
factory raised a bare RuntimeError for any other type. This change adds
Youdao Translation as a second provider and improves the factory's error
message.
Implementation details:
- New YoudaoTranslator class in translate/youdao/youdao_translate.py
- Implements Youdao's v3 SHA-256 signature scheme, including the
truncate-input rule for queries longer than 20 characters
- Maps ISO 639-1 language codes to Youdao-specific codes
(zh -> zh-CHS, zh-TW -> zh-CHT, others pass through)
- Differentiates network errors, API error codes, and empty translations
- factory.create_translator now lists the supported types in its
RuntimeError message instead of failing silently
- Default config exposes youdao_translate_app_key and
youdao_translate_app_secret
Adds 17 unit tests covering signature correctness, language code mapping,
input truncation edge cases, the full request/response flow, and factory
dispatch. All tests pass under Python 3.11.
- rewrite streaming reply to official cardkit v2.0 API (default on, auto-fallback)
- fix Whisper hallucination: bump ASR sample rate to 16k, pass language=zh
- fix lock-over-IO and tmp file cleanup from #2791
- drop deprecated feishu_bot_name; quiet unknown-key warnings
- docs: cardkit permission and feishu_stream_reply usage