mirror of
https://github.com/zhayujie/chatgpt-on-wechat.git
synced 2026-07-17 19:27:11 +08:00
Compare commits
14 Commits
feat-self-
...
2.1.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8cb53e6129 | ||
|
|
12c0383dc8 | ||
|
|
83b53039f3 | ||
|
|
7e6a309935 | ||
|
|
33c03e30d9 | ||
|
|
1f1abdd7b6 | ||
|
|
16134bd150 | ||
|
|
c887fc71ad | ||
|
|
9fc39f648f | ||
|
|
ec9557e3d8 | ||
|
|
7cf0f7d42d | ||
|
|
b7aa64279d | ||
|
|
26300a8d43 | ||
|
|
8dd21ddb83 |
@@ -15,7 +15,7 @@
|
|||||||
[English] | [<a href="docs/zh/README.md">中文</a>] | [<a href="docs/ja/README.md">日本語</a>]
|
[English] | [<a href="docs/zh/README.md">中文</a>] | [<a href="docs/ja/README.md">日本語</a>]
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
**CowAgent** is an open-source super AI assistant that proactively plans tasks, controls your computer and external services, creates and runs Skills, and grows alongside you through a personal knowledge base and long-term memory — a reference implementation of Agent Harness engineering.
|
**CowAgent** is an open-source super AI assistant that proactively plans tasks, controls your computer and external services, creates and runs Skills, builds a personal knowledge base and long-term memory, and grows alongside you through self-evolution — a reference implementation of Agent Harness engineering.
|
||||||
|
|
||||||
CowAgent is lightweight, easy to deploy, and built to extend. Plug in any major LLM provider and run it 24/7 on a personal computer or server, across the web and all major IM platforms.
|
CowAgent is lightweight, easy to deploy, and built to extend. Plug in any major LLM provider and run it 24/7 on a personal computer or server, across the web and all major IM platforms.
|
||||||
|
|
||||||
@@ -36,6 +36,7 @@ CowAgent is lightweight, easy to deploy, and built to extend. Plug in any major
|
|||||||
| [Planning](https://docs.cowagent.ai/intro/architecture) | Decomposes complex tasks and executes them step by step, looping over tools until the goal is reached |
|
| [Planning](https://docs.cowagent.ai/intro/architecture) | Decomposes complex tasks and executes them step by step, looping over tools until the goal is reached |
|
||||||
| [Memory](https://docs.cowagent.ai/memory/index) | Three-tier architecture (context → daily → core), automatic Deep Dream distillation, hybrid keyword + vector retrieval |
|
| [Memory](https://docs.cowagent.ai/memory/index) | Three-tier architecture (context → daily → core), automatic Deep Dream distillation, hybrid keyword + vector retrieval |
|
||||||
| [Knowledge](https://docs.cowagent.ai/knowledge/index) | Auto-curates structured knowledge into a Markdown wiki, builds an evolving knowledge graph with visual browsing |
|
| [Knowledge](https://docs.cowagent.ai/knowledge/index) | Auto-curates structured knowledge into a Markdown wiki, builds an evolving knowledge graph with visual browsing |
|
||||||
|
| [Evolution](https://docs.cowagent.ai/memory/self-evolution) | Self-Evolution reviews conversations automatically to improve skills, follow up on unfinished tasks, and consolidate memory and knowledge, growing through everyday use |
|
||||||
| [Skills](https://docs.cowagent.ai/skills/index) | One-click install from [Skill Hub](https://skills.cowagent.ai/), GitHub, ClawHub; or create custom skills via natural-language conversation |
|
| [Skills](https://docs.cowagent.ai/skills/index) | One-click install from [Skill Hub](https://skills.cowagent.ai/), GitHub, ClawHub; or create custom skills via natural-language conversation |
|
||||||
| [Tools](https://docs.cowagent.ai/tools/index) | Built-in file I/O, terminal, browser, scheduler, memory retrieval, web search, and 10+ more tools — with native MCP integration |
|
| [Tools](https://docs.cowagent.ai/tools/index) | Built-in file I/O, terminal, browser, scheduler, memory retrieval, web search, and 10+ more tools — with native MCP integration |
|
||||||
| [Channels](https://docs.cowagent.ai/channels/index) | Integrates with Web, WeChat, Feishu, DingTalk, WeCom, QQ, Official Accounts, Telegram, and Slack |
|
| [Channels](https://docs.cowagent.ai/channels/index) | Integrates with Web, WeChat, Feishu, DingTalk, WeCom, QQ, Official Accounts, Telegram, and Slack |
|
||||||
@@ -198,6 +199,8 @@ Learn more: [Skills overview](https://docs.cowagent.ai/skills/index) · [Creatin
|
|||||||
|
|
||||||
## 🏷 Changelog
|
## 🏷 Changelog
|
||||||
|
|
||||||
|
> **2026.06.09:** [v2.1.1](https://github.com/zhayujie/CowAgent/releases/tag/2.1.1) — Self-Evolution, Web console upgrades (message management, parallel sessions), cross-platform MCP enhancements with concurrent calls, new models (MiniMax-M3, qwen3.7-plus), Python 3.13 support.
|
||||||
|
|
||||||
> **2026.06.01:** [v2.1.0](https://github.com/zhayujie/CowAgent/releases/tag/2.1.0) — Internationalization, new channels (Telegram, Discord, Slack, WeChat Customer Service), CLI interaction upgrades, streamlined one-line install, MCP Streamable HTTP support, new models (claude-opus-4-8, MiMo).
|
> **2026.06.01:** [v2.1.0](https://github.com/zhayujie/CowAgent/releases/tag/2.1.0) — Internationalization, new channels (Telegram, Discord, Slack, WeChat Customer Service), CLI interaction upgrades, streamlined one-line install, MCP Streamable HTTP support, new models (claude-opus-4-8, MiMo).
|
||||||
|
|
||||||
> **2026.05.22:** [v2.0.9](https://github.com/zhayujie/CowAgent/releases/tag/2.0.9) — Model management, MCP protocol support, persistent browser sessions, new models (gpt-5.5, gemini-3.5-flash, qwen3.7-max), deployment hardening.
|
> **2026.05.22:** [v2.0.9](https://github.com/zhayujie/CowAgent/releases/tag/2.0.9) — Model management, MCP protocol support, persistent browser sessions, new models (gpt-5.5, gemini-3.5-flash, qwen3.7-max), deployment hardening.
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ from typing import Any
|
|||||||
# until release; enable via ``self_evolution_enabled``.
|
# until release; enable via ``self_evolution_enabled``.
|
||||||
DEFAULT_ENABLED = False
|
DEFAULT_ENABLED = False
|
||||||
DEFAULT_IDLE_MINUTES = 15
|
DEFAULT_IDLE_MINUTES = 15
|
||||||
DEFAULT_MIN_TURNS = 6
|
DEFAULT_MIN_TURNS = 8
|
||||||
# Max review steps for the isolated evolution agent. Kept small (not exposed as
|
# Max review steps for the isolated evolution agent. Kept small (not exposed as
|
||||||
# config): the review is meant to be cheap and focused, not a long autonomous run.
|
# config): the review is meant to be cheap and focused, not a long autonomous run.
|
||||||
DEFAULT_MAX_STEPS = 12
|
DEFAULT_MAX_STEPS = 12
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ remember_scheduled_output, channel_factory) rather than introducing a fork.
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import re
|
||||||
import threading
|
import threading
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -37,8 +38,10 @@ from agent.evolution.prompts import (
|
|||||||
from agent.evolution.record import append_session_evolution
|
from agent.evolution.record import append_session_evolution
|
||||||
|
|
||||||
# Tools the isolated evolution agent is allowed to use. Everything else is
|
# Tools the isolated evolution agent is allowed to use. Everything else is
|
||||||
# withheld so a review pass can only read context and edit memory/skill files.
|
# withheld so a review pass can only read context, run workspace scripts, and
|
||||||
_ALLOWED_TOOLS = {"read", "write", "edit", "ls", "memory_search", "memory_get"}
|
# edit memory/skill files. bash is needed by skill-creator's init script and is
|
||||||
|
# confined to the workspace by _BashWorkspaceGuard.
|
||||||
|
_ALLOWED_TOOLS = {"read", "write", "edit", "ls", "bash", "memory_search", "memory_get"}
|
||||||
|
|
||||||
# Cap concurrent evolution passes so a burst of idle sessions can't spawn many
|
# Cap concurrent evolution passes so a burst of idle sessions can't spawn many
|
||||||
# background model runs at once. Extra sessions simply wait for the next scan.
|
# background model runs at once. Extra sessions simply wait for the next scan.
|
||||||
@@ -159,12 +162,81 @@ class _WorkspaceWriteGuard:
|
|||||||
return self._inner.execute(args)
|
return self._inner.execute(args)
|
||||||
|
|
||||||
|
|
||||||
|
class _BashWorkspaceGuard:
|
||||||
|
"""Wraps the bash tool so evolution can only run commands inside the
|
||||||
|
workspace.
|
||||||
|
|
||||||
|
Evolution needs bash for skill-creator's init script, but it runs
|
||||||
|
unattended in the background, so a raw shell is too broad. This guard:
|
||||||
|
- forces the command to execute with cwd = workspace,
|
||||||
|
- rejects commands that reference an absolute path or ``..`` segment
|
||||||
|
pointing OUTSIDE the workspace (the common ways to escape it).
|
||||||
|
It is a coarse textual check, not a sandbox — paired with the model's
|
||||||
|
instruction to only run skill-creator scripts, it keeps writes local.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, inner, workspace_dir: str):
|
||||||
|
self._inner = inner
|
||||||
|
self._ws = Path(workspace_dir).resolve()
|
||||||
|
# Pin the shell's working directory to the workspace.
|
||||||
|
try:
|
||||||
|
self._inner.cwd = str(self._ws)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
self.name = inner.name
|
||||||
|
self.description = inner.description
|
||||||
|
self.params = inner.params
|
||||||
|
|
||||||
|
def __getattr__(self, item):
|
||||||
|
return getattr(self._inner, item)
|
||||||
|
|
||||||
|
def execute_tool(self, params):
|
||||||
|
try:
|
||||||
|
return self.execute(params)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"[Evolution] guarded bash error: {e}")
|
||||||
|
from agent.tools.base_tool import ToolResult
|
||||||
|
return ToolResult.fail(f"Error: {e}")
|
||||||
|
|
||||||
|
def _escapes_workspace(self, command: str) -> bool:
|
||||||
|
# Absolute paths that are not under the workspace.
|
||||||
|
for tok in re.findall(r'(?:^|\s)(/[^\s\'";|&]+)', command):
|
||||||
|
try:
|
||||||
|
resolved = Path(tok).resolve()
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
if self._ws != resolved and self._ws not in resolved.parents:
|
||||||
|
return True
|
||||||
|
# Parent-dir traversal that climbs above the workspace.
|
||||||
|
for tok in re.findall(r'[^\s\'";|&]*\.\.[^\s\'";|&]*', command):
|
||||||
|
try:
|
||||||
|
resolved = (self._ws / tok).resolve()
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
if self._ws != resolved and self._ws not in resolved.parents:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def execute(self, args):
|
||||||
|
from agent.tools.base_tool import ToolResult
|
||||||
|
command = (args.get("command") or "").strip()
|
||||||
|
if command and self._escapes_workspace(command):
|
||||||
|
return ToolResult.fail(
|
||||||
|
"Error: evolution may only run commands inside the workspace; "
|
||||||
|
"this command references a path outside it and was blocked."
|
||||||
|
)
|
||||||
|
return self._inner.execute(args)
|
||||||
|
|
||||||
|
|
||||||
def _guard_tools(tools: list, workspace_dir: str) -> list:
|
def _guard_tools(tools: list, workspace_dir: str) -> list:
|
||||||
"""Wrap write/edit tools with the workspace guard; leave others as-is."""
|
"""Wrap write/edit/bash tools with workspace guards; leave others as-is."""
|
||||||
guarded = []
|
guarded = []
|
||||||
for t in tools:
|
for t in tools:
|
||||||
if getattr(t, "name", None) in _WRITE_TOOLS:
|
name = getattr(t, "name", None)
|
||||||
|
if name in _WRITE_TOOLS:
|
||||||
guarded.append(_WorkspaceWriteGuard(t, workspace_dir))
|
guarded.append(_WorkspaceWriteGuard(t, workspace_dir))
|
||||||
|
elif name == "bash":
|
||||||
|
guarded.append(_BashWorkspaceGuard(t, workspace_dir))
|
||||||
else:
|
else:
|
||||||
guarded.append(t)
|
guarded.append(t)
|
||||||
return guarded
|
return guarded
|
||||||
@@ -175,6 +247,9 @@ _WATCH_SUBDIRS = ("MEMORY.md", "skills", "knowledge", "output")
|
|||||||
# Subpaths under memory/ to ignore: evolution's own bookkeeping + the nightly
|
# Subpaths under memory/ to ignore: evolution's own bookkeeping + the nightly
|
||||||
# dream diary, none of which count as a user-facing change signal.
|
# dream diary, none of which count as a user-facing change signal.
|
||||||
_MEMORY_IGNORE = (".evolution_backups", "dreams", "evolution")
|
_MEMORY_IGNORE = (".evolution_backups", "dreams", "evolution")
|
||||||
|
# Files the skill subsystem maintains automatically (the enable/disable index).
|
||||||
|
# Not an evolution result, so a rewrite must not count as a change signal.
|
||||||
|
_WATCH_IGNORE_NAMES = ("skills_config.json",)
|
||||||
|
|
||||||
|
|
||||||
def _workspace_snapshot(workspace_dir) -> dict:
|
def _workspace_snapshot(workspace_dir) -> dict:
|
||||||
@@ -195,6 +270,8 @@ def _workspace_snapshot(workspace_dir) -> dict:
|
|||||||
for p in root.rglob("*"):
|
for p in root.rglob("*"):
|
||||||
if not p.is_file():
|
if not p.is_file():
|
||||||
continue
|
continue
|
||||||
|
if p.name in _WATCH_IGNORE_NAMES:
|
||||||
|
continue
|
||||||
try:
|
try:
|
||||||
st = p.stat()
|
st = p.stat()
|
||||||
snap[str(p.relative_to(ws))] = (st.st_mtime, st.st_size)
|
snap[str(p.relative_to(ws))] = (st.st_mtime, st.st_size)
|
||||||
@@ -269,8 +346,8 @@ def run_evolution_for_session(
|
|||||||
new_messages = all_messages[done:]
|
new_messages = all_messages[done:]
|
||||||
transcript = _build_transcript(new_messages)
|
transcript = _build_transcript(new_messages)
|
||||||
if not transcript.strip():
|
if not transcript.strip():
|
||||||
logger.info(f"[Evolution] session={session_id}: no new messages, skip")
|
# Routine no-op: the per-minute scan hits every idle session. Advance
|
||||||
# Advance the cursor anyway so we don't re-scan the same tail.
|
# the cursor so we don't re-scan the same tail; no log (pure noise).
|
||||||
agent._evo_done_msg_count = total_msgs
|
agent._evo_done_msg_count = total_msgs
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -332,17 +409,23 @@ def run_evolution_for_session(
|
|||||||
str(workspace_dir),
|
str(workspace_dir),
|
||||||
)
|
)
|
||||||
review_agent = agent_bridge.create_agent(
|
review_agent = agent_bridge.create_agent(
|
||||||
system_prompt=EVOLUTION_SYSTEM_PROMPT,
|
system_prompt="",
|
||||||
tools=review_tools,
|
tools=review_tools,
|
||||||
description="Self-evolution review agent",
|
description="Self-evolution review agent",
|
||||||
max_steps=cfg.max_steps,
|
max_steps=cfg.max_steps,
|
||||||
workspace_dir=str(workspace_dir),
|
workspace_dir=str(workspace_dir),
|
||||||
skill_manager=getattr(agent, "skill_manager", None),
|
skill_manager=getattr(agent, "skill_manager", None),
|
||||||
memory_manager=getattr(agent, "memory_manager", None),
|
memory_manager=getattr(agent, "memory_manager", None),
|
||||||
enable_skills=False,
|
enable_skills=True,
|
||||||
|
runtime_info=getattr(agent, "runtime_info", None),
|
||||||
)
|
)
|
||||||
# Reuse the live model so it follows the user's configured model.
|
# Reuse the live model so it follows the user's configured model.
|
||||||
review_agent.model = agent.model
|
review_agent.model = agent.model
|
||||||
|
# Inject the evolution task brief AFTER the full system prompt: the agent
|
||||||
|
# gets the full context (tools, workspace, user preferences, memory, time)
|
||||||
|
# AND its evolution-specific instructions on top, instead of one
|
||||||
|
# overwriting the other.
|
||||||
|
review_agent.extra_system_suffix = EVOLUTION_SYSTEM_PROMPT
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
f"[Evolution] backup {backup_id} ({_backup_n} files) → running review agent"
|
f"[Evolution] backup {backup_id} ({_backup_n} files) → running review agent"
|
||||||
@@ -355,26 +438,40 @@ def run_evolution_for_session(
|
|||||||
# only looks at messages added after this point (silent or not).
|
# only looks at messages added after this point (silent or not).
|
||||||
agent._evo_done_msg_count = total_msgs
|
agent._evo_done_msg_count = total_msgs
|
||||||
|
|
||||||
if not result or SILENT_TOKEN in result:
|
# Respect an explicit silent verdict: empty, exactly [SILENT], or text
|
||||||
|
# that STARTS with [SILENT] means the model chose to stay quiet.
|
||||||
|
if not result or result.startswith(SILENT_TOKEN):
|
||||||
logger.info(f"[Evolution] ✗ No change for session={session_id} ([SILENT])")
|
logger.info(f"[Evolution] ✗ No change for session={session_id} ([SILENT])")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Hard gate: an evolution only counts (and only notifies) if a workspace
|
# Anti-nag backstop: if the model wrote a summary but actually changed no
|
||||||
# file ACTUALLY changed. If the model did real work (wrote memory /
|
# watched file, stay silent — never notify about work that didn't happen.
|
||||||
# patched a skill / finished a task) the user is told; if it merely
|
|
||||||
# produced text without changing anything, we stay silent. This is the
|
|
||||||
# key anti-nag rule — no notification unless something was actually done.
|
|
||||||
if not _workspace_changed(workspace_dir, pre_snapshot):
|
if not _workspace_changed(workspace_dir, pre_snapshot):
|
||||||
logger.info(
|
logger.info(
|
||||||
f"[Evolution] ✗ session={session_id}: model produced text but "
|
f"[Evolution] ✗ session={session_id}: text produced but no file "
|
||||||
f"changed no file — treating as silent"
|
f"changed — staying silent"
|
||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
# The model produced a real summary. Strip any stray [SILENT] tokens it
|
||||||
|
# left mid-text, then notify.
|
||||||
|
result = result.replace(SILENT_TOKEN, "").strip()
|
||||||
|
if not result:
|
||||||
|
logger.info(f"[Evolution] ✗ No change for session={session_id} ([SILENT])")
|
||||||
|
return False
|
||||||
|
|
||||||
logger.info(f"[Evolution] ✓ session={session_id} evolved:\n{result}")
|
logger.info(f"[Evolution] ✓ session={session_id} evolved:\n{result}")
|
||||||
append_session_evolution(workspace_dir, result, backup_id=backup_id, user_id=user_id)
|
append_session_evolution(workspace_dir, result, backup_id=backup_id, user_id=user_id)
|
||||||
# Inject an [EVOLUTION] note so the main agent can honor "undo".
|
# Inject an [EVOLUTION] note so the main agent can honor "undo".
|
||||||
_inject_evolution_record(agent_bridge, session_id, channel_type, result, backup_id)
|
_inject_evolution_record(agent_bridge, session_id, channel_type, result, backup_id)
|
||||||
|
# The injection appended its own messages ([SCHEDULED]/[EVOLUTION]).
|
||||||
|
# Advance the cursor past them so the next scan does not treat
|
||||||
|
# evolution's own bookkeeping as new user content and re-trigger.
|
||||||
|
try:
|
||||||
|
with agent.messages_lock:
|
||||||
|
agent._evo_done_msg_count = len(agent.messages)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
# Push the summary to the user's channel. The "did a file actually
|
# Push the summary to the user's channel. The "did a file actually
|
||||||
# change" gate above is the only throttle we need: real evolutions are
|
# change" gate above is the only throttle we need: real evolutions are
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ language (instructed at the end of the prompt).
|
|||||||
|
|
||||||
Design goals (see ref/hermes-agent background_review for inspiration):
|
Design goals (see ref/hermes-agent background_review for inspiration):
|
||||||
- Default to doing NOTHING. Evolution is the exception, not the rule.
|
- Default to doing NOTHING. Evolution is the exception, not the rule.
|
||||||
- Three signal types: memory, skill, unfinished task.
|
- Signal types: skill, unfinished task, memory, knowledge.
|
||||||
- An explicit "do NOT capture" list to avoid self-poisoning over time.
|
- An explicit "do NOT capture" list to avoid self-poisoning over time.
|
||||||
- Generic examples only — never bake in domain-specific business terms.
|
- Generic examples only — never bake in domain-specific business terms.
|
||||||
"""
|
"""
|
||||||
@@ -52,11 +52,13 @@ them. When their signal is clear, act; do not be shy here.
|
|||||||
the relevant skill file under the skills directory and make a small
|
the relevant skill file under the skills directory and make a small
|
||||||
incremental edit so it never recurs.
|
incremental edit so it never recurs.
|
||||||
b) CREATE a new skill: a clearly reusable, repeatable workflow emerged that
|
b) CREATE a new skill: a clearly reusable, repeatable workflow emerged that
|
||||||
no existing skill covers and the user is likely to want again. To create
|
no existing skill covers and the user is likely to want again. Follow the
|
||||||
one, follow the `skill-creator` skill's conventions (read its SKILL.md for
|
`skill-creator` skill's conventions (read its SKILL.md for the required
|
||||||
the required structure) and write the new skill under the workspace
|
structure), then create `skills/<name>/SKILL.md` by WRITING the file
|
||||||
`skills/` directory. Only create when the workflow is genuinely reusable —
|
directly with the write tool — this is the simplest reliable path. (bash
|
||||||
not for a one-off task.
|
is available and confined to the workspace if a helper script is truly
|
||||||
|
needed, but a direct write is preferred.) Only create when the workflow is
|
||||||
|
genuinely reusable — not for a one-off task.
|
||||||
|
|
||||||
CRITICAL — fix the SOURCE, do not just remember the symptom: when the root
|
CRITICAL — fix the SOURCE, do not just remember the symptom: when the root
|
||||||
cause of a problem lives IN a skill file itself (its instructions, content,
|
cause of a problem lives IN a skill file itself (its instructions, content,
|
||||||
@@ -72,25 +74,31 @@ them. When their signal is clear, act; do not be shy here.
|
|||||||
reply/decision, do NOTHING and stay [SILENT] — do not nag or ping the user.
|
reply/decision, do NOTHING and stay [SILENT] — do not nag or ping the user.
|
||||||
You only ever notify the user as a side effect of having actually done work.
|
You only ever notify the user as a side effect of having actually done work.
|
||||||
|
|
||||||
3. MEMORY — LAST resort, and you are only a SAFETY NET here, not the primary
|
3. MEMORY — RARE, last resort. Default to writing NOTHING here. The main
|
||||||
writer. The main assistant already writes memory DURING the conversation, and
|
assistant already writes memory during the chat, and a nightly pass plus
|
||||||
a nightly pass consolidates daily notes into long-term memory. Prefer fixing
|
context-overflow saves are dedicated safety nets — so memory is almost always
|
||||||
a skill (above) over writing memory whenever the fact belongs in a skill.
|
already covered without you. Skip unless the main assistant clearly missed a
|
||||||
Act ONLY on something the main assistant clearly MISSED that does not belong
|
durable fact that belongs in no skill AND would visibly change future replies.
|
||||||
in any skill.
|
|
||||||
- MEMORY.md is the curated long-term index, auto-loaded into EVERY future
|
- MEMORY.md is the curated long-term index, auto-loaded into EVERY future
|
||||||
conversation. Treat it as precious: writing here is RARE and reserved for
|
conversation. Treat it as precious: edit it in place to CORRECT a wrong
|
||||||
CORRECTING a wrong fact already in MEMORY.md (edit that line in place).
|
fact, or append a new durable preference/decision/lesson — but do so
|
||||||
Do NOT append new entries to MEMORY.md — that is the nightly pass's job.
|
SPARINGLY (a lasting fact, not a passing detail; the nightly pass handles
|
||||||
- For a genuinely important NEW durable fact the chat missed, append ONE
|
routine consolidation).
|
||||||
short bullet to today's `memory/YYYY-MM-DD.md` (not MEMORY.md). When unsure,
|
- For a NEW fact that is important but not yet clearly lasting, append ONE
|
||||||
the daily file is the safe place — but first ask whether this really
|
short bullet to today's `memory/YYYY-MM-DD.md` instead. When unsure, the
|
||||||
belongs in a skill instead.
|
daily file is the safe place — but first ask whether this really belongs
|
||||||
|
in a skill.
|
||||||
- Keep it to ONE short bullet. Never write paragraphs, never re-summarize the
|
- Keep it to ONE short bullet. Never write paragraphs, never re-summarize the
|
||||||
conversation, never copy what the main assistant already recorded.
|
conversation, never copy what the main assistant already recorded.
|
||||||
- If it is already captured anywhere (check MEMORY.md AND the daily file
|
- If it is already captured anywhere (check MEMORY.md AND the daily file
|
||||||
first), do NOTHING.
|
first), do NOTHING.
|
||||||
|
|
||||||
|
4. KNOWLEDGE — only if the conversation produced durable, reusable reference
|
||||||
|
knowledge on a topic (the kind worth looking up again) that the main
|
||||||
|
assistant did NOT already save to `knowledge/`. Add or update the relevant
|
||||||
|
file there. Like memory, this is the exception: skip routine Q&A, and if the
|
||||||
|
topic is already covered in `knowledge/`, do NOTHING rather than duplicate.
|
||||||
|
|
||||||
# Do NOT capture (these poison future behavior)
|
# Do NOT capture (these poison future behavior)
|
||||||
|
|
||||||
- Environment failures: missing binaries, unset credentials, uninstalled
|
- Environment failures: missing binaries, unset credentials, uninstalled
|
||||||
@@ -119,11 +127,12 @@ them. When their signal is clear, act; do not be shy here.
|
|||||||
|
|
||||||
- Nothing worth evolving -> output exactly `[SILENT]` and nothing else.
|
- Nothing worth evolving -> output exactly `[SILENT]` and nothing else.
|
||||||
- Otherwise, after performing the edits, output a short user-facing summary in
|
- Otherwise, after performing the edits, output a short user-facing summary in
|
||||||
the SAME LANGUAGE the user used in the conversation. Tell the user, briefly:
|
the SAME LANGUAGE the user speaks in the conversation. Write it for an ordinary user, in plain
|
||||||
|
everyday words — NOT a developer report. No need to expose internal details
|
||||||
|
(file names/paths, system mechanics, etc.). Tell the user, briefly:
|
||||||
1) that you just did a self-learning pass,
|
1) that you just did a self-learning pass,
|
||||||
2) what you learned and what you changed (in plain terms — no need to cite
|
2) what you learned and what you changed in THIS pass ("remembered X" /
|
||||||
exact file paths; "remembered X" / "improved the weekly-report skill" is
|
"improved the <name> skill" / "finished <task>").
|
||||||
enough).
|
|
||||||
Keep it to 1-3 lines. Generic shape (do not copy domain words):
|
Keep it to 1-3 lines. Generic shape (do not copy domain words):
|
||||||
"I just did a self-learning pass.
|
"I just did a self-learning pass.
|
||||||
- Learned: <what you learned>
|
- Learned: <what you learned>
|
||||||
@@ -138,9 +147,6 @@ def build_review_user_message(transcript: str, protected_skills: list = None) ->
|
|||||||
``protected_skills`` lists skill names that must never be edited (built-in
|
``protected_skills`` lists skill names that must never be edited (built-in
|
||||||
skills shipped with the product). Surfaced so the agent avoids them.
|
skills shipped with the product). Surfaced so the agent avoids them.
|
||||||
"""
|
"""
|
||||||
from datetime import datetime
|
|
||||||
today = datetime.now().strftime("%Y-%m-%d")
|
|
||||||
|
|
||||||
protected_note = ""
|
protected_note = ""
|
||||||
if protected_skills:
|
if protected_skills:
|
||||||
names = ", ".join(sorted(protected_skills))
|
names = ", ".join(sorted(protected_skills))
|
||||||
@@ -150,12 +156,10 @@ def build_review_user_message(transcript: str, protected_skills: list = None) ->
|
|||||||
)
|
)
|
||||||
return (
|
return (
|
||||||
"Here is the conversation transcript that just went idle. Review it per "
|
"Here is the conversation transcript that just went idle. Review it per "
|
||||||
"your instructions and act on any clear signal. Prefer fixing a skill at "
|
"your instructions. Acting is the exception: the main value is fixing or "
|
||||||
"its source over writing memory whenever the fact belongs in a skill.\n"
|
"creating a skill and finishing promised work. Memory and knowledge are "
|
||||||
f"Today is {today}. Only if a fact genuinely belongs in memory (and not "
|
"rare last resorts — stay [SILENT] unless there is a clear, durable signal "
|
||||||
f"in a skill): append one short bullet to the daily file "
|
"not already covered."
|
||||||
f"`memory/{today}.md` for a new fact, or edit MEMORY.md in place to "
|
|
||||||
f"correct an existing wrong fact."
|
|
||||||
f"{protected_note}\n"
|
f"{protected_note}\n"
|
||||||
"<transcript>\n"
|
"<transcript>\n"
|
||||||
f"{transcript}\n"
|
f"{transcript}\n"
|
||||||
|
|||||||
@@ -124,6 +124,11 @@ def _is_internal_user_marker(text: str) -> bool:
|
|||||||
return any(t.startswith(m) for m in _SCHEDULED_DISPLAY_MARKERS)
|
return any(t.startswith(m) for m in _SCHEDULED_DISPLAY_MARKERS)
|
||||||
|
|
||||||
|
|
||||||
|
def _is_evolution_text(text: str) -> bool:
|
||||||
|
"""True if assistant text is a self-evolution summary (before cleaning)."""
|
||||||
|
return (text or "").lstrip().startswith(_EVOLUTION_DISPLAY_MARKER)
|
||||||
|
|
||||||
|
|
||||||
def _clean_display_text(text: str) -> str:
|
def _clean_display_text(text: str) -> str:
|
||||||
"""Strip internal markers from assistant text for user-facing display.
|
"""Strip internal markers from assistant text for user-facing display.
|
||||||
|
|
||||||
@@ -306,6 +311,10 @@ def _group_into_display_turns(
|
|||||||
step["result"] = tr.get("result", "")
|
step["result"] = tr.get("result", "")
|
||||||
step["is_error"] = tr.get("is_error", False)
|
step["is_error"] = tr.get("is_error", False)
|
||||||
|
|
||||||
|
# Detect a self-evolution bubble BEFORE cleaning the marker away, so the
|
||||||
|
# UI can flag it even though the visible text stays clean.
|
||||||
|
is_evolution = _is_evolution_text(final_text)
|
||||||
|
|
||||||
# Clean internal markers from the user-facing assistant text. Applies to
|
# Clean internal markers from the user-facing assistant text. Applies to
|
||||||
# both the final content and the mirrored content step so the rendered
|
# both the final content and the mirrored content step so the rendered
|
||||||
# bubble shows clean text while the stored message keeps the markers.
|
# bubble shows clean text while the stored message keeps the markers.
|
||||||
@@ -321,6 +330,8 @@ def _group_into_display_turns(
|
|||||||
"steps": steps,
|
"steps": steps,
|
||||||
"created_at": final_ts or (user_row[1] if user_row else 0),
|
"created_at": final_ts or (user_row[1] if user_row else 0),
|
||||||
}
|
}
|
||||||
|
if is_evolution:
|
||||||
|
turn["kind"] = "evolution"
|
||||||
if merged_extras:
|
if merged_extras:
|
||||||
turn["extras"] = merged_extras
|
turn["extras"] = merged_extras
|
||||||
turns.append(turn)
|
turns.append(turn)
|
||||||
|
|||||||
@@ -52,6 +52,11 @@ class Agent:
|
|||||||
self.workspace_dir = workspace_dir # Workspace directory
|
self.workspace_dir = workspace_dir # Workspace directory
|
||||||
self.enable_skills = enable_skills # Skills enabled flag
|
self.enable_skills = enable_skills # Skills enabled flag
|
||||||
self.runtime_info = runtime_info # Runtime info for dynamic time update
|
self.runtime_info = runtime_info # Runtime info for dynamic time update
|
||||||
|
# Optional extra instructions appended AFTER the rebuilt full system
|
||||||
|
# prompt. Used by the self-evolution review agent to add its task brief
|
||||||
|
# on top of the full context (tools, workspace, user preferences, time)
|
||||||
|
# so it both follows the user's preferences and knows its evolution job.
|
||||||
|
self.extra_system_suffix = None
|
||||||
|
|
||||||
# Initialize skill manager
|
# Initialize skill manager
|
||||||
self.skill_manager = None
|
self.skill_manager = None
|
||||||
@@ -120,15 +125,20 @@ class Agent:
|
|||||||
except Exception:
|
except Exception:
|
||||||
lang = "zh"
|
lang = "zh"
|
||||||
builder = PromptBuilder(workspace_dir=self.workspace_dir or "", language=lang)
|
builder = PromptBuilder(workspace_dir=self.workspace_dir or "", language=lang)
|
||||||
return builder.build(
|
full = builder.build(
|
||||||
tools=self.tools,
|
tools=self.tools,
|
||||||
context_files=context_files,
|
context_files=context_files,
|
||||||
skill_manager=self.skill_manager,
|
skill_manager=self.skill_manager,
|
||||||
memory_manager=self.memory_manager,
|
memory_manager=self.memory_manager,
|
||||||
runtime_info=self.runtime_info,
|
runtime_info=self.runtime_info,
|
||||||
)
|
)
|
||||||
|
if self.extra_system_suffix:
|
||||||
|
full = f"{full}\n\n{self.extra_system_suffix}"
|
||||||
|
return full
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"Failed to rebuild system prompt, using cached version: {e}")
|
logger.warning(f"Failed to rebuild system prompt, using cached version: {e}")
|
||||||
|
if self.extra_system_suffix:
|
||||||
|
return f"{self.system_prompt}\n\n{self.extra_system_suffix}"
|
||||||
return self.system_prompt
|
return self.system_prompt
|
||||||
|
|
||||||
def refresh_skills(self):
|
def refresh_skills(self):
|
||||||
|
|||||||
@@ -515,6 +515,15 @@ class AgentBridge:
|
|||||||
)
|
)
|
||||||
self._trim_in_memory_to_turns(agent, scheduler_keep_turns)
|
self._trim_in_memory_to_turns(agent, scheduler_keep_turns)
|
||||||
|
|
||||||
|
# Eagerly persist the user message BEFORE running the agent so the
|
||||||
|
# session and the user's bubble are immediately visible — even if
|
||||||
|
# the user switches away or refreshes before the reply finishes.
|
||||||
|
# The reply (assistant/tool messages) is appended once the run
|
||||||
|
# completes; the final persist skips this already-stored user turn.
|
||||||
|
pre_persisted = self._pre_persist_user_message(
|
||||||
|
session_id, query, context, clear_history
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Use agent's run_stream method with event handler
|
# Use agent's run_stream method with event handler
|
||||||
response = agent.run_stream(
|
response = agent.run_stream(
|
||||||
@@ -541,7 +550,11 @@ class AgentBridge:
|
|||||||
# Persist new messages generated during this run
|
# Persist new messages generated during this run
|
||||||
if session_id:
|
if session_id:
|
||||||
channel_type = (context.get("channel_type") or "") if context else ""
|
channel_type = (context.get("channel_type") or "") if context else ""
|
||||||
new_messages = getattr(agent, '_last_run_new_messages', [])
|
new_messages = list(getattr(agent, '_last_run_new_messages', []))
|
||||||
|
# The leading user turn was already persisted eagerly above;
|
||||||
|
# drop it here so it isn't stored twice.
|
||||||
|
if pre_persisted and new_messages and new_messages[0].get("role") == "user":
|
||||||
|
new_messages = new_messages[1:]
|
||||||
if new_messages:
|
if new_messages:
|
||||||
self._persist_messages(session_id, list(new_messages), channel_type)
|
self._persist_messages(session_id, list(new_messages), channel_type)
|
||||||
else:
|
else:
|
||||||
@@ -757,6 +770,48 @@ class AgentBridge:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"[AgentBridge] Failed to sync API keys: {e}")
|
logger.warning(f"[AgentBridge] Failed to sync API keys: {e}")
|
||||||
|
|
||||||
|
def _pre_persist_user_message(
|
||||||
|
self, session_id: str, query: str, context: Context, clear_history: bool
|
||||||
|
) -> bool:
|
||||||
|
"""Persist the user's message before the agent runs.
|
||||||
|
|
||||||
|
This makes a brand-new session (and the user's bubble) visible even if
|
||||||
|
the reply hasn't finished — switching away or refreshing no longer
|
||||||
|
loses the in-flight session. Returns True when the user turn was
|
||||||
|
stored, so the caller can skip it in the post-run persist.
|
||||||
|
|
||||||
|
Best-effort: any failure is swallowed and reported as not-persisted.
|
||||||
|
"""
|
||||||
|
if not session_id or not query:
|
||||||
|
return False
|
||||||
|
# Only real user turns: skip scheduler-injected / scheduled-task runs.
|
||||||
|
if session_id.startswith("scheduler_") or (
|
||||||
|
context and context.get("is_scheduled_task")
|
||||||
|
):
|
||||||
|
return False
|
||||||
|
try:
|
||||||
|
from config import conf
|
||||||
|
if not conf().get("conversation_persistence", True):
|
||||||
|
return False
|
||||||
|
from agent.memory import get_conversation_store
|
||||||
|
store = get_conversation_store()
|
||||||
|
# clear_history starts a fresh transcript: wipe the store first so
|
||||||
|
# the eager user turn becomes seq 0, matching in-memory state.
|
||||||
|
if clear_history:
|
||||||
|
store.clear_session(session_id)
|
||||||
|
channel_type = (context.get("channel_type") or "") if context else ""
|
||||||
|
user_msg = {
|
||||||
|
"role": "user",
|
||||||
|
"content": [{"type": "text", "text": query}],
|
||||||
|
}
|
||||||
|
store.append_messages(session_id, [user_msg], channel_type=channel_type)
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(
|
||||||
|
f"[AgentBridge] Failed to pre-persist user message for session={session_id}: {e}"
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
def _persist_messages(
|
def _persist_messages(
|
||||||
self, session_id: str, new_messages: list, channel_type: str = ""
|
self, session_id: str, new_messages: list, channel_type: str = ""
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|||||||
@@ -524,6 +524,14 @@ class AgentInitializer:
|
|||||||
logger.debug("[AgentInitializer] WebSearch skipped - no search provider configured")
|
logger.debug("[AgentInitializer] WebSearch skipped - no search provider configured")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# Skip evolution_undo when self-evolution is disabled: with no
|
||||||
|
# evolution there is nothing to roll back, so the tool is dead weight.
|
||||||
|
if tool_name == "evolution_undo":
|
||||||
|
from agent.evolution.config import get_evolution_config
|
||||||
|
if not get_evolution_config().enabled:
|
||||||
|
logger.debug("[AgentInitializer] evolution_undo skipped - self-evolution disabled")
|
||||||
|
continue
|
||||||
|
|
||||||
# Special handling for EnvConfig tool
|
# Special handling for EnvConfig tool
|
||||||
if tool_name == "env_config":
|
if tool_name == "env_config":
|
||||||
from agent.tools import EnvConfig
|
from agent.tools import EnvConfig
|
||||||
|
|||||||
@@ -124,6 +124,7 @@ const I18N = {
|
|||||||
config_max_steps: '最大执行步数', config_max_steps_hint: '单次对话中 Agent 最多调用工具的次数',
|
config_max_steps: '最大执行步数', config_max_steps_hint: '单次对话中 Agent 最多调用工具的次数',
|
||||||
config_enable_thinking: '深度思考', config_enable_thinking_hint: '是否启用深度思考模式',
|
config_enable_thinking: '深度思考', config_enable_thinking_hint: '是否启用深度思考模式',
|
||||||
config_self_evolution: '自主进化', config_self_evolution_hint: '会话空闲后自动复盘,沉淀记忆、优化技能、处理未完成事项',
|
config_self_evolution: '自主进化', config_self_evolution_hint: '会话空闲后自动复盘,沉淀记忆、优化技能、处理未完成事项',
|
||||||
|
evolution_badge: '自主学习',
|
||||||
config_channel_type: '通道类型',
|
config_channel_type: '通道类型',
|
||||||
config_provider: '模型厂商', config_model_name: '模型',
|
config_provider: '模型厂商', config_model_name: '模型',
|
||||||
config_custom_model_hint: '输入自定义模型名称',
|
config_custom_model_hint: '输入自定义模型名称',
|
||||||
@@ -327,6 +328,7 @@ const I18N = {
|
|||||||
config_max_steps: 'Max Steps', config_max_steps_hint: 'Max tool calls the Agent can make in a single conversation',
|
config_max_steps: 'Max Steps', config_max_steps_hint: 'Max tool calls the Agent can make in a single conversation',
|
||||||
config_enable_thinking: 'Deep Thinking', config_enable_thinking_hint: 'Enable deep thinking mode',
|
config_enable_thinking: 'Deep Thinking', config_enable_thinking_hint: 'Enable deep thinking mode',
|
||||||
config_self_evolution: 'Self-Evolution', config_self_evolution_hint: 'Auto-review idle conversations to consolidate memory, improve skills, and follow up on unfinished tasks',
|
config_self_evolution: 'Self-Evolution', config_self_evolution_hint: 'Auto-review idle conversations to consolidate memory, improve skills, and follow up on unfinished tasks',
|
||||||
|
evolution_badge: 'Self-learned',
|
||||||
config_channel_type: 'Channel Type',
|
config_channel_type: 'Channel Type',
|
||||||
config_provider: 'Provider', config_model_name: 'Model',
|
config_provider: 'Provider', config_model_name: 'Model',
|
||||||
config_custom_model_hint: 'Enter custom model name',
|
config_custom_model_hint: 'Enter custom model name',
|
||||||
@@ -881,6 +883,8 @@ let isPolling = false;
|
|||||||
let pollGeneration = 0; // incremented on each restart to cancel stale poll loops
|
let pollGeneration = 0; // incremented on each restart to cancel stale poll loops
|
||||||
let loadingContainers = {};
|
let loadingContainers = {};
|
||||||
let activeStreams = {}; // request_id -> EventSource
|
let activeStreams = {}; // request_id -> EventSource
|
||||||
|
let sessionActiveRequest = {}; // session_id -> request_id (in-flight stream per session)
|
||||||
|
let streamBuffers = {}; // request_id -> { items: [event...], timestamp } for re-attach replay
|
||||||
let isComposing = false;
|
let isComposing = false;
|
||||||
let appConfig = { use_agent: false, title: 'CowAgent', subtitle: '', providers: {}, api_bases: {} };
|
let appConfig = { use_agent: false, title: 'CowAgent', subtitle: '', providers: {}, api_bases: {} };
|
||||||
|
|
||||||
@@ -1295,8 +1299,15 @@ sendBtn.addEventListener('click', () => {
|
|||||||
|
|
||||||
function updateSendBtnState() {
|
function updateSendBtnState() {
|
||||||
if (sendBtnMode === 'cancel') {
|
if (sendBtnMode === 'cancel') {
|
||||||
// Don't downgrade a Cancel button on input edits.
|
// Self-heal a stuck Cancel button: if there's no live stream backing
|
||||||
return;
|
// the current request, the cancel state leaked (e.g. a stream ended
|
||||||
|
// without resetting). Recover to Send so the input isn't blocked.
|
||||||
|
if (!activeRequestId || !activeStreams[activeRequestId]) {
|
||||||
|
resetSendBtnSendMode();
|
||||||
|
} else {
|
||||||
|
// Don't downgrade a genuinely active Cancel button on input edits.
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
sendBtn.disabled = uploadingCount > 0 || (!chatInput.value.trim() && pendingAttachments.length === 0);
|
sendBtn.disabled = uploadingCount > 0 || (!chatInput.value.trim() && pendingAttachments.length === 0);
|
||||||
}
|
}
|
||||||
@@ -2228,7 +2239,7 @@ function sendMessage() {
|
|||||||
postWithRetry(0);
|
postWithRetry(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
function startSSE(requestId, loadingEl, timestamp, titleInfo) {
|
function startSSE(requestId, loadingEl, timestamp, titleInfo, replayItems) {
|
||||||
let botEl = null;
|
let botEl = null;
|
||||||
let stepsEl = null; // .agent-steps (thinking summaries + tool indicators)
|
let stepsEl = null; // .agent-steps (thinking summaries + tool indicators)
|
||||||
let contentEl = null; // .answer-content (final streaming answer)
|
let contentEl = null; // .answer-content (final streaming answer)
|
||||||
@@ -2240,6 +2251,25 @@ function startSSE(requestId, loadingEl, timestamp, titleInfo) {
|
|||||||
let reasoningStartTime = 0;
|
let reasoningStartTime = 0;
|
||||||
let done = false;
|
let done = false;
|
||||||
|
|
||||||
|
// The session this stream belongs to. Sessions run in parallel: the user
|
||||||
|
// may switch to another session while this one is still streaming. The
|
||||||
|
// stream keeps running in the background (so the reply still completes and
|
||||||
|
// persists); when foreign it does not touch the view but still records
|
||||||
|
// every event into a buffer, so returning to the session can rebuild the
|
||||||
|
// bubble by replaying the buffer and then resume live rendering.
|
||||||
|
const ownerSession = sessionId;
|
||||||
|
const isActive = () => ownerSession === sessionId;
|
||||||
|
sessionActiveRequest[ownerSession] = requestId;
|
||||||
|
// Per-request event buffer used to rebuild the bubble on re-attach.
|
||||||
|
const buffer = streamBuffers[requestId] || { items: [], timestamp };
|
||||||
|
streamBuffers[requestId] = buffer;
|
||||||
|
const clearOwnerRequest = () => {
|
||||||
|
if (sessionActiveRequest[ownerSession] === requestId) {
|
||||||
|
delete sessionActiveRequest[ownerSession];
|
||||||
|
}
|
||||||
|
delete streamBuffers[requestId];
|
||||||
|
};
|
||||||
|
|
||||||
const MAX_RECONNECTS = 10;
|
const MAX_RECONNECTS = 10;
|
||||||
const RECONNECT_BASE_MS = 1000;
|
const RECONNECT_BASE_MS = 1000;
|
||||||
let reconnectCount = 0;
|
let reconnectCount = 0;
|
||||||
@@ -2281,17 +2311,13 @@ function startSSE(requestId, loadingEl, timestamp, titleInfo) {
|
|||||||
mediaEl = botEl.querySelector('.media-content');
|
mediaEl = botEl.querySelector('.media-content');
|
||||||
}
|
}
|
||||||
|
|
||||||
function connect() {
|
// Holds the live EventSource so terminal events (done/voice_attach/error)
|
||||||
const es = new EventSource(`/stream?request_id=${encodeURIComponent(requestId)}`);
|
// can close it. During replay there is no live connection (null).
|
||||||
activeStreams[requestId] = es;
|
let currentEs = null;
|
||||||
|
|
||||||
es.onmessage = function(e) {
|
|
||||||
let item;
|
|
||||||
try { item = JSON.parse(e.data); } catch (_) { return; }
|
|
||||||
|
|
||||||
// Successful data received, reset reconnect counter
|
|
||||||
reconnectCount = 0;
|
|
||||||
|
|
||||||
|
// Render one SSE event into the bubble. Used by the live handler and by
|
||||||
|
// re-attach replay alike, so both paths produce identical UI.
|
||||||
|
function processSSEItem(item) {
|
||||||
if (item.type === 'reasoning') {
|
if (item.type === 'reasoning') {
|
||||||
ensureBotEl();
|
ensureBotEl();
|
||||||
reasoningText += item.content;
|
reasoningText += item.content;
|
||||||
@@ -2505,6 +2531,7 @@ function startSSE(requestId, loadingEl, timestamp, titleInfo) {
|
|||||||
// TTS audio (`voice_attach`). It will close the stream on
|
// TTS audio (`voice_attach`). It will close the stream on
|
||||||
// its own via onerror once the tail expires.
|
// its own via onerror once the tail expires.
|
||||||
done = true;
|
done = true;
|
||||||
|
clearOwnerRequest();
|
||||||
resetSendBtnSendMode();
|
resetSendBtnSendMode();
|
||||||
|
|
||||||
const finalTextRaw = item.content || accumulatedText;
|
const finalTextRaw = item.content || accumulatedText;
|
||||||
@@ -2560,17 +2587,52 @@ function startSSE(requestId, loadingEl, timestamp, titleInfo) {
|
|||||||
if (botEl && item.url) {
|
if (botEl && item.url) {
|
||||||
attachAudioToBotBubble(botEl, item.url, { autoplay: true });
|
attachAudioToBotBubble(botEl, item.url, { autoplay: true });
|
||||||
}
|
}
|
||||||
es.close();
|
if (currentEs) { currentEs.close(); }
|
||||||
delete activeStreams[requestId];
|
delete activeStreams[requestId];
|
||||||
|
clearOwnerRequest();
|
||||||
|
|
||||||
} else if (item.type === 'error') {
|
} else if (item.type === 'error') {
|
||||||
done = true;
|
done = true;
|
||||||
es.close();
|
if (currentEs) { currentEs.close(); }
|
||||||
delete activeStreams[requestId];
|
delete activeStreams[requestId];
|
||||||
|
clearOwnerRequest();
|
||||||
if (loadingEl) { loadingEl.remove(); loadingEl = null; }
|
if (loadingEl) { loadingEl.remove(); loadingEl = null; }
|
||||||
addBotMessage(t('error_send'), new Date());
|
addBotMessage(t('error_send'), new Date());
|
||||||
resetSendBtnSendMode();
|
resetSendBtnSendMode();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function connect() {
|
||||||
|
const es = new EventSource(`/stream?request_id=${encodeURIComponent(requestId)}`);
|
||||||
|
currentEs = es;
|
||||||
|
activeStreams[requestId] = es;
|
||||||
|
|
||||||
|
es.onmessage = function(e) {
|
||||||
|
let item;
|
||||||
|
try { item = JSON.parse(e.data); } catch (_) { return; }
|
||||||
|
|
||||||
|
// Successful data received, reset reconnect counter
|
||||||
|
reconnectCount = 0;
|
||||||
|
|
||||||
|
// Record every event for re-attach replay (capped to avoid
|
||||||
|
// unbounded growth on very long streams).
|
||||||
|
if (buffer.items.length < 5000) buffer.items.push(item);
|
||||||
|
|
||||||
|
// Background session: keep the stream alive so the reply finishes
|
||||||
|
// and persists, but skip rendering into the now-foreign view. The
|
||||||
|
// buffer above still grows so returning to the session can rebuild
|
||||||
|
// the bubble and resume live rendering.
|
||||||
|
if (ownerSession !== sessionId) {
|
||||||
|
if (item.type === 'done' || item.type === 'error' || item.type === 'voice_attach') {
|
||||||
|
done = true;
|
||||||
|
es.close();
|
||||||
|
delete activeStreams[requestId];
|
||||||
|
clearOwnerRequest();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
processSSEItem(item);
|
||||||
};
|
};
|
||||||
|
|
||||||
es.onerror = function() {
|
es.onerror = function() {
|
||||||
@@ -2596,7 +2658,10 @@ function startSSE(requestId, loadingEl, timestamp, titleInfo) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exhausted retries, show whatever we have
|
// Exhausted retries. Only surface the failure in the owning view —
|
||||||
|
// a background session must not mutate the currently shown chat.
|
||||||
|
clearOwnerRequest();
|
||||||
|
if (!isActive()) return;
|
||||||
if (loadingEl) { loadingEl.remove(); loadingEl = null; }
|
if (loadingEl) { loadingEl.remove(); loadingEl = null; }
|
||||||
if (!botEl) {
|
if (!botEl) {
|
||||||
addBotMessage(t('error_send'), new Date());
|
addBotMessage(t('error_send'), new Date());
|
||||||
@@ -2610,6 +2675,27 @@ function startSSE(requestId, loadingEl, timestamp, titleInfo) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Re-attach replay: rebuild the bubble from buffered events (snapshot,
|
||||||
|
// not animated) before connecting for the live tail. `processSSEItem`
|
||||||
|
// is the same renderer used by the live onmessage handler, so the
|
||||||
|
// snapshot matches exactly what live rendering would have produced.
|
||||||
|
if (replayItems && replayItems.length) {
|
||||||
|
for (const item of replayItems) {
|
||||||
|
try { processSSEItem(item); } catch (_) {}
|
||||||
|
if (item.type === 'done' || item.type === 'error' || item.type === 'voice_attach') {
|
||||||
|
done = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If the buffered stream already finished, don't reconnect — the
|
||||||
|
// reply is complete and persisted; show its final state and stop.
|
||||||
|
if (done) {
|
||||||
|
clearOwnerRequest();
|
||||||
|
resetSendBtnSendMode();
|
||||||
|
scrollChatToBottom(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
connect();
|
connect();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2639,10 +2725,19 @@ function startPolling() {
|
|||||||
loadingContainers[rid].remove();
|
loadingContainers[rid].remove();
|
||||||
delete loadingContainers[rid];
|
delete loadingContainers[rid];
|
||||||
}
|
}
|
||||||
const welcomeScreen = document.getElementById('welcome-screen');
|
// Skip if this reply is already on screen. Happens when a reply
|
||||||
if (welcomeScreen) welcomeScreen.remove();
|
// arrives via both the SSE stream and the poll queue (e.g. the
|
||||||
addBotMessage(data.content, new Date(data.timestamp * 1000), rid);
|
// user switched away mid-run, leaving the queued reply to be
|
||||||
scrollChatToBottom();
|
// re-fetched on return) — render it only once.
|
||||||
|
const already = rid && messagesDiv.querySelector(
|
||||||
|
`[data-request-id="${rid}"]`
|
||||||
|
);
|
||||||
|
if (!already) {
|
||||||
|
const welcomeScreen = document.getElementById('welcome-screen');
|
||||||
|
if (welcomeScreen) welcomeScreen.remove();
|
||||||
|
addBotMessage(data.content, new Date(data.timestamp * 1000), rid);
|
||||||
|
scrollChatToBottom();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const delay = (data.status === 'success' && data.has_content) ? 5000 : 10000;
|
const delay = (data.status === 'success' && data.has_content) ? 5000 : 10000;
|
||||||
setTimeout(poll, delay);
|
setTimeout(poll, delay);
|
||||||
@@ -2886,10 +2981,23 @@ function createBotMessageEl(content, timestamp, requestId, msg) {
|
|||||||
stepsHtml = renderThinkingHtml(reasoning) + renderToolCallsHtml(toolCalls);
|
stepsHtml = renderThinkingHtml(reasoning) + renderToolCallsHtml(toolCalls);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Self-evolution bubbles get a small badge so the user can feel the agent
|
||||||
|
// learned something on its own (text itself stays clean). History replay
|
||||||
|
// carries msg.kind; live pushes are identified by the evolution_ request id.
|
||||||
|
const isEvolution = (msg && msg.kind === 'evolution')
|
||||||
|
|| (typeof requestId === 'string' && requestId.startsWith('evolution_'));
|
||||||
|
const evolutionBadge = isEvolution
|
||||||
|
? `<div class="flex items-center gap-1 mb-1.5 text-xs text-slate-400 dark:text-slate-500">
|
||||||
|
<i class="fas fa-seedling text-[11px]"></i>
|
||||||
|
<span>${t('evolution_badge')}</span>
|
||||||
|
</div>`
|
||||||
|
: '';
|
||||||
|
|
||||||
el.innerHTML = `
|
el.innerHTML = `
|
||||||
<img src="assets/logo.jpg" alt="CowAgent" class="w-8 h-8 rounded-lg flex-shrink-0">
|
<img src="assets/logo.jpg" alt="CowAgent" class="w-8 h-8 rounded-lg flex-shrink-0">
|
||||||
<div class="min-w-0 flex-1 max-w-[85%]">
|
<div class="min-w-0 flex-1 max-w-[85%]">
|
||||||
<div class="bg-white dark:bg-[#1A1A1A] border border-slate-200 dark:border-white/10 rounded-2xl px-4 py-3 text-sm leading-relaxed msg-content text-slate-700 dark:text-slate-200">
|
<div class="bg-white dark:bg-[#1A1A1A] border border-slate-200 dark:border-white/10 rounded-2xl px-4 py-3 text-sm leading-relaxed msg-content text-slate-700 dark:text-slate-200">
|
||||||
|
${evolutionBadge}
|
||||||
${stepsHtml ? `<div class="agent-steps">${stepsHtml}</div>` : ''}
|
${stepsHtml ? `<div class="agent-steps">${stepsHtml}</div>` : ''}
|
||||||
<div class="answer-content">${renderMarkdown(displayContent)}</div>
|
<div class="answer-content">${renderMarkdown(displayContent)}</div>
|
||||||
<div class="bot-audio-slot"></div>
|
<div class="bot-audio-slot"></div>
|
||||||
@@ -3162,9 +3270,12 @@ function loadHistory(page) {
|
|||||||
historyPage = page;
|
historyPage = page;
|
||||||
|
|
||||||
if (isFirstLoad) {
|
if (isFirstLoad) {
|
||||||
// Use requestAnimationFrame to ensure the DOM has fully rendered
|
// Scroll to the very bottom after the DOM settles. A single
|
||||||
// before scrolling, otherwise scrollHeight may not reflect new content.
|
// rAF isn't enough: markdown/code-highlight/images keep growing
|
||||||
|
// scrollHeight after the first paint, leaving the last bubble's
|
||||||
|
// timestamp clipped. Re-pin a few times to catch late layout.
|
||||||
requestAnimationFrame(() => scrollChatToBottom(true));
|
requestAnimationFrame(() => scrollChatToBottom(true));
|
||||||
|
[120, 350, 700].forEach(d => setTimeout(() => scrollChatToBottom(true), d));
|
||||||
} else {
|
} else {
|
||||||
// Restore scroll position so loading older messages doesn't jump the view
|
// Restore scroll position so loading older messages doesn't jump the view
|
||||||
messagesDiv.scrollTop = messagesDiv.scrollHeight - prevScrollHeight;
|
messagesDiv.scrollTop = messagesDiv.scrollHeight - prevScrollHeight;
|
||||||
@@ -3192,15 +3303,15 @@ function addLoadingIndicator() {
|
|||||||
return el;
|
return el;
|
||||||
}
|
}
|
||||||
|
|
||||||
function newChat() {
|
function newChat(optimistic = true) {
|
||||||
// Close all active SSE connections for the current session
|
// Do NOT close active streams: other sessions keep streaming in the
|
||||||
Object.values(activeStreams).forEach(es => { try { es.close(); } catch (_) {} });
|
// background (each stream self-guards against the foreign view) and their
|
||||||
activeStreams = {};
|
// replies still complete and persist.
|
||||||
|
|
||||||
// Generate a fresh session and persist it so the next page load also starts clean
|
// Generate a fresh session and persist it so the next page load also starts clean
|
||||||
sessionId = generateSessionId();
|
sessionId = generateSessionId();
|
||||||
localStorage.setItem(SESSION_ID_KEY, sessionId);
|
localStorage.setItem(SESSION_ID_KEY, sessionId);
|
||||||
loadingContainers = {};
|
resetSendBtnSendMode(); // fresh session has no in-flight reply
|
||||||
startPolling(); // bump generation so old loop self-cancels, new loop uses fresh sessionId
|
startPolling(); // bump generation so old loop self-cancels, new loop uses fresh sessionId
|
||||||
messagesDiv.innerHTML = '';
|
messagesDiv.innerHTML = '';
|
||||||
const ws = document.createElement('div');
|
const ws = document.createElement('div');
|
||||||
@@ -3296,8 +3407,16 @@ function newChat() {
|
|||||||
_showSessionOverlay();
|
_showSessionOverlay();
|
||||||
_persistPanelState();
|
_persistPanelState();
|
||||||
}
|
}
|
||||||
|
// Only prepend an optimistic "new chat" item when this is a real new-chat
|
||||||
|
// action. When called after deleting the current session, skip it: the
|
||||||
|
// fresh session has no backend record yet, so inserting it would leave an
|
||||||
|
// empty, undeletable item in the list (deleting it just spawns another).
|
||||||
const newSid = sessionId;
|
const newSid = sessionId;
|
||||||
loadSessionList(() => _addOptimisticSessionItem(newSid));
|
if (optimistic) {
|
||||||
|
loadSessionList(() => _addOptimisticSessionItem(newSid));
|
||||||
|
} else {
|
||||||
|
loadSessionList();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// =====================================================================
|
// =====================================================================
|
||||||
@@ -3535,15 +3654,56 @@ function _onSessionListScroll() {
|
|||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
// Returning to a session whose reply is still streaming in the background.
|
||||||
|
// Close the background EventSource, rebuild the bubble from the buffered
|
||||||
|
// events (snapshot), then resume live streaming via a fresh connection that
|
||||||
|
// reads the remaining tail from the backend queue. Returns true if a stream
|
||||||
|
// was re-attached. The user's own bubble is already in history (persisted
|
||||||
|
// eagerly), so it was rendered by loadHistory before this runs.
|
||||||
|
function _reattachStream(sid) {
|
||||||
|
const requestId = sessionActiveRequest[sid];
|
||||||
|
if (!requestId) return false;
|
||||||
|
const buffer = streamBuffers[requestId];
|
||||||
|
if (!buffer) return false;
|
||||||
|
|
||||||
|
// If the buffered stream already finished, the assistant reply is already
|
||||||
|
// persisted and rendered by loadHistory — re-attaching would duplicate it.
|
||||||
|
// Just clean up the buffer/cursor and rely on history.
|
||||||
|
const finished = buffer.items.some(
|
||||||
|
it => it.type === 'done' || it.type === 'error'
|
||||||
|
);
|
||||||
|
if (finished) {
|
||||||
|
const oldEs = activeStreams[requestId];
|
||||||
|
if (oldEs) { try { oldEs.close(); } catch (_) {} delete activeStreams[requestId]; }
|
||||||
|
delete streamBuffers[requestId];
|
||||||
|
delete sessionActiveRequest[sid];
|
||||||
|
resetSendBtnSendMode();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop the background stream so the rebuilt one is the sole consumer of
|
||||||
|
// the backend queue (the queue survives until "done", so the new
|
||||||
|
// connection picks up any remaining events).
|
||||||
|
const oldEs = activeStreams[requestId];
|
||||||
|
if (oldEs) { try { oldEs.close(); } catch (_) {} delete activeStreams[requestId]; }
|
||||||
|
|
||||||
|
// Snapshot the buffered events into the replay, then start a fresh stream
|
||||||
|
// that replays them and reconnects for the live tail.
|
||||||
|
const replay = buffer.items.slice();
|
||||||
|
startSSE(requestId, null, buffer.timestamp || new Date(), null, replay);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
function switchSession(newSessionId) {
|
function switchSession(newSessionId) {
|
||||||
if (newSessionId === sessionId) {
|
if (newSessionId === sessionId) {
|
||||||
if (currentView !== 'chat') navigateTo('chat');
|
if (currentView !== 'chat') navigateTo('chat');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.values(activeStreams).forEach(es => { try { es.close(); } catch (_) {} });
|
// Do NOT close active streams here: sessions run in parallel, so any
|
||||||
activeStreams = {};
|
// in-flight reply for another session must keep streaming in the
|
||||||
loadingContainers = {};
|
// background (it self-guards against rendering into the foreign view).
|
||||||
|
// Switching back re-attaches and resumes live streaming.
|
||||||
|
|
||||||
sessionId = newSessionId;
|
sessionId = newSessionId;
|
||||||
localStorage.setItem(SESSION_ID_KEY, sessionId);
|
localStorage.setItem(SESSION_ID_KEY, sessionId);
|
||||||
@@ -3556,6 +3716,17 @@ function switchSession(newSessionId) {
|
|||||||
loadHistory(1);
|
loadHistory(1);
|
||||||
startPolling();
|
startPolling();
|
||||||
|
|
||||||
|
// Restore the send button to match this session's stream state, and if a
|
||||||
|
// reply is still streaming in the background, re-attach to resume showing
|
||||||
|
// it live (the user turn itself comes from history above).
|
||||||
|
const pendingReq = sessionActiveRequest[sessionId];
|
||||||
|
if (pendingReq) {
|
||||||
|
setSendBtnCancelMode(pendingReq);
|
||||||
|
_reattachStream(sessionId);
|
||||||
|
} else {
|
||||||
|
resetSendBtnSendMode();
|
||||||
|
}
|
||||||
|
|
||||||
document.querySelectorAll('.session-item').forEach(el => {
|
document.querySelectorAll('.session-item').forEach(el => {
|
||||||
el.classList.toggle('active', el.dataset.sessionId === sessionId);
|
el.classList.toggle('active', el.dataset.sessionId === sessionId);
|
||||||
});
|
});
|
||||||
@@ -3566,20 +3737,49 @@ function switchSession(newSessionId) {
|
|||||||
|
|
||||||
function deleteSession(sid) {
|
function deleteSession(sid) {
|
||||||
showConfirmModal(t('delete_session_title'), t('delete_session_confirm'), () => {
|
showConfirmModal(t('delete_session_title'), t('delete_session_confirm'), () => {
|
||||||
|
// Before deleting, find the next real session to fall back to when the
|
||||||
|
// current one is removed (the sibling item in the list, which is sorted
|
||||||
|
// newest-first). Falls back to the welcome screen if none remain.
|
||||||
|
const nextSid = sid === sessionId ? _findNextSessionId(sid) : null;
|
||||||
|
|
||||||
fetch(`/api/sessions/${encodeURIComponent(sid)}`, { method: 'DELETE' })
|
fetch(`/api/sessions/${encodeURIComponent(sid)}`, { method: 'DELETE' })
|
||||||
.then(r => r.json())
|
.then(r => r.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
if (data.status !== 'success') return;
|
if (data.status !== 'success') return;
|
||||||
if (sid === sessionId) {
|
if (sid !== sessionId) {
|
||||||
newChat();
|
|
||||||
} else {
|
|
||||||
loadSessionList();
|
loadSessionList();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (nextSid) {
|
||||||
|
// Switch to an existing session; refresh the list afterwards
|
||||||
|
// so the deleted item disappears.
|
||||||
|
switchSession(nextSid);
|
||||||
|
loadSessionList();
|
||||||
|
} else {
|
||||||
|
// No other sessions: reset to a fresh empty session without
|
||||||
|
// inserting an optimistic placeholder (it has no backend
|
||||||
|
// record and would be an empty, undeletable item).
|
||||||
|
newChat(false);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {});
|
.catch(() => {});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pick the session to show after deleting `sid` (the current session): prefer
|
||||||
|
// the next item below it in the list, otherwise the previous one. Returns null
|
||||||
|
// if no other session exists.
|
||||||
|
function _findNextSessionId(sid) {
|
||||||
|
const items = Array.from(document.querySelectorAll('.session-item[data-session-id]'));
|
||||||
|
const idx = items.findIndex(el => el.dataset.sessionId === sid);
|
||||||
|
if (idx === -1) {
|
||||||
|
const other = items.find(el => el.dataset.sessionId !== sid);
|
||||||
|
return other ? other.dataset.sessionId : null;
|
||||||
|
}
|
||||||
|
const next = items[idx + 1] || items[idx - 1];
|
||||||
|
return next ? next.dataset.sessionId : null;
|
||||||
|
}
|
||||||
|
|
||||||
function showConfirmModal(title, message, onConfirm) {
|
function showConfirmModal(title, message, onConfirm) {
|
||||||
let overlay = document.getElementById('confirm-modal-overlay');
|
let overlay = document.getElementById('confirm-modal-overlay');
|
||||||
if (overlay) overlay.remove();
|
if (overlay) overlay.remove();
|
||||||
|
|||||||
@@ -360,6 +360,13 @@ class WebChannel(ChatChannel):
|
|||||||
):
|
):
|
||||||
logger.debug(f"Polling skipped duplicate file reply for session {session_id}")
|
logger.debug(f"Polling skipped duplicate file reply for session {session_id}")
|
||||||
return
|
return
|
||||||
|
# SSE-enabled requests already stream the text reply to the
|
||||||
|
# client. Do NOT also enqueue it for polling: if the user
|
||||||
|
# switched away mid-run, the queued copy would resurface as a
|
||||||
|
# duplicate bubble when they return and poll the session.
|
||||||
|
if reply.type == ReplyType.TEXT and context.get("on_event") is not None:
|
||||||
|
logger.debug(f"Polling skipped SSE text reply for session {session_id}")
|
||||||
|
return
|
||||||
response_data = {
|
response_data = {
|
||||||
"type": str(reply.type),
|
"type": str(reply.type),
|
||||||
"content": content,
|
"content": content,
|
||||||
@@ -940,7 +947,12 @@ class WebChannel(ChatChannel):
|
|||||||
post_done = True
|
post_done = True
|
||||||
post_deadline = time.time() + 2 # 2s post-attach tail
|
post_deadline = time.time() + 2 # 2s post-attach tail
|
||||||
finally:
|
finally:
|
||||||
self.sse_queues.pop(request_id, None)
|
# Only drop the queue once the reply is actually complete. If the
|
||||||
|
# client disconnected early (e.g. switched sessions and will
|
||||||
|
# re-attach with the same request_id), keep the queue so the new
|
||||||
|
# connection can resume reading the remaining events.
|
||||||
|
if post_done or time.time() >= deadline:
|
||||||
|
self.sse_queues.pop(request_id, None)
|
||||||
|
|
||||||
def cancel_request(self):
|
def cancel_request(self):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
2.1.0
|
2.1.1
|
||||||
|
|||||||
@@ -40,5 +40,6 @@
|
|||||||
"agent_max_steps": 20,
|
"agent_max_steps": 20,
|
||||||
"enable_thinking": false,
|
"enable_thinking": false,
|
||||||
"reasoning_effort": "high",
|
"reasoning_effort": "high",
|
||||||
"knowledge": true
|
"knowledge": true,
|
||||||
|
"self_evolution_enabled": true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -252,9 +252,9 @@ available_setting = {
|
|||||||
"reasoning_effort": "high", # Reasoning depth under thinking mode: "high" or "max"
|
"reasoning_effort": "high", # Reasoning depth under thinking mode: "high" or "max"
|
||||||
"knowledge": True, # whether to enable the knowledge base feature
|
"knowledge": True, # whether to enable the knowledge base feature
|
||||||
# Self-evolution: review idle conversations to learn memory/skills. Flat keys.
|
# Self-evolution: review idle conversations to learn memory/skills. Flat keys.
|
||||||
"self_evolution_enabled": False, # master switch (off until release)
|
"self_evolution_enabled": False, # switch to enable/disable self-evolution
|
||||||
"self_evolution_idle_minutes": 15, # idle time before a session is reviewed
|
"self_evolution_idle_minutes": 15, # idle time before a session is reviewed
|
||||||
"self_evolution_min_turns": 6, # min user turns (or context pressure) to trigger
|
"self_evolution_min_turns": 8, # min user turns (or context pressure) to trigger
|
||||||
"skill": {}, # Per-skill runtime config; nested keys flatten to SKILL_<NAME>_<KEY> env vars at startup
|
"skill": {}, # Per-skill runtime config; nested keys flatten to SKILL_<NAME>_<KEY> env vars at startup
|
||||||
"mcp_servers": [], # MCP server list; each entry supports type "stdio" (local process) or "sse" (remote URL)
|
"mcp_servers": [], # MCP server list; each entry supports type "stdio" (local process) or "sse" (remote URL)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -241,6 +241,7 @@
|
|||||||
"group": "Release Notes",
|
"group": "Release Notes",
|
||||||
"pages": [
|
"pages": [
|
||||||
"releases/overview",
|
"releases/overview",
|
||||||
|
"releases/v2.1.1",
|
||||||
"releases/v2.1.0",
|
"releases/v2.1.0",
|
||||||
"releases/v2.0.9",
|
"releases/v2.0.9",
|
||||||
"releases/v2.0.8",
|
"releases/v2.0.8",
|
||||||
@@ -456,6 +457,7 @@
|
|||||||
"group": "发布记录",
|
"group": "发布记录",
|
||||||
"pages": [
|
"pages": [
|
||||||
"zh/releases/overview",
|
"zh/releases/overview",
|
||||||
|
"zh/releases/v2.1.1",
|
||||||
"zh/releases/v2.1.0",
|
"zh/releases/v2.1.0",
|
||||||
"zh/releases/v2.0.9",
|
"zh/releases/v2.0.9",
|
||||||
"zh/releases/v2.0.8",
|
"zh/releases/v2.0.8",
|
||||||
@@ -671,6 +673,7 @@
|
|||||||
"group": "リリースノート",
|
"group": "リリースノート",
|
||||||
"pages": [
|
"pages": [
|
||||||
"ja/releases/overview",
|
"ja/releases/overview",
|
||||||
|
"ja/releases/v2.1.1",
|
||||||
"ja/releases/v2.1.0",
|
"ja/releases/v2.1.0",
|
||||||
"ja/releases/v2.0.9",
|
"ja/releases/v2.0.9",
|
||||||
"ja/releases/v2.0.8",
|
"ja/releases/v2.0.8",
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ description: One-click install and manage CowAgent with scripts
|
|||||||
|
|
||||||
The project provides scripts for one-click install, configuration, startup, and management. Script-based deployment is recommended for quick setup.
|
The project provides scripts for one-click install, configuration, startup, and management. Script-based deployment is recommended for quick setup.
|
||||||
|
|
||||||
Supports Linux, macOS, and Windows. Requires Python 3.7-3.12 (3.9 recommended).
|
Supports Linux, macOS, and Windows. Requires Python 3.7-3.13 (3.9 recommended).
|
||||||
|
|
||||||
## Install Command
|
## Install Command
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ CowAgent's architecture consists of the following core modules:
|
|||||||
| **Plan** | Understands user intent, decomposes complex tasks into multi-step plans, and iteratively invokes tools until the goal is achieved |
|
| **Plan** | Understands user intent, decomposes complex tasks into multi-step plans, and iteratively invokes tools until the goal is achieved |
|
||||||
| **Memory** | Automatically persists important information as core memory and daily memory, with hybrid keyword and vector retrieval for cross-session context continuity |
|
| **Memory** | Automatically persists important information as core memory and daily memory, with hybrid keyword and vector retrieval for cross-session context continuity |
|
||||||
| **Knowledge** | Organizes structured knowledge by topic. The Agent autonomously distills valuable information into Markdown pages, maintaining indexes and cross-references to build a growing knowledge network |
|
| **Knowledge** | Organizes structured knowledge by topic. The Agent autonomously distills valuable information into Markdown pages, maintaining indexes and cross-references to build a growing knowledge network |
|
||||||
|
| **Evolution** | Reviews a conversation in an isolated environment after it goes idle, improving skills, following up on unfinished tasks, and backfilling memory and knowledge so the Agent keeps growing through everyday use |
|
||||||
| **Tools** | Core capability for Agent to access OS resources. 10+ built-in tools including file read/write, terminal, browser, scheduler, memory search, web search, and more |
|
| **Tools** | Core capability for Agent to access OS resources. 10+ built-in tools including file read/write, terminal, browser, scheduler, memory search, web search, and more |
|
||||||
| **Skills** | Loads and manages Skills. Supports one-click installation from Skill Hub, GitHub, and more, or custom skill creation through conversation |
|
| **Skills** | Loads and manages Skills. Supports one-click installation from Skill Hub, GitHub, and more, or custom skill creation through conversation |
|
||||||
| **Models** | Model layer with unified access to OpenAI, Claude, Gemini, DeepSeek, MiniMax, GLM, Qwen, and other mainstream LLMs |
|
| **Models** | Model layer with unified access to OpenAI, Claude, Gemini, DeepSeek, MiniMax, GLM, Qwen, and other mainstream LLMs |
|
||||||
@@ -84,4 +85,5 @@ Configure Agent mode parameters in `config.json`:
|
|||||||
| `agent_max_steps` | Max decision steps per task | `20` |
|
| `agent_max_steps` | Max decision steps per task | `20` |
|
||||||
| `enable_thinking` | Enable deep-thinking mode | `false` |
|
| `enable_thinking` | Enable deep-thinking mode | `false` |
|
||||||
| `knowledge` | Enable personal knowledge base | `true` |
|
| `knowledge` | Enable personal knowledge base | `true` |
|
||||||
|
| `self_evolution_enabled` | Enable Self-Evolution (on by default for new installs) | `false` |
|
||||||
| `cow_lang` | Language for the UI, command text and system prompts; `auto` to detect, or set `zh` / `en` | `auto` |
|
| `cow_lang` | Language for the UI, command text and system prompts; `auto` to detect, or set `zh` / `en` | `auto` |
|
||||||
|
|||||||
@@ -15,7 +15,13 @@ In subsequent long-term conversations, the Agent intelligently stores or retriev
|
|||||||
<img src="https://cdn.link-ai.tech/doc/20260203000455.png" width="800" />
|
<img src="https://cdn.link-ai.tech/doc/20260203000455.png" width="800" />
|
||||||
</Frame>
|
</Frame>
|
||||||
|
|
||||||
See [Long-term Memory](/memory) and [Deep Dream](/memory/deep-dream) for details.
|
Building on this, **Self-Evolution** lets the Agent keep growing through everyday use: after a conversation goes idle, it reviews it automatically to improve skills, follow up on unfinished tasks, and backfill memory and knowledge. It speaks up only when it actually made a change, and every change can be undone. Enabled by default for new installs.
|
||||||
|
|
||||||
|
<Frame>
|
||||||
|
<img src="https://cdn.jsdelivr.net/gh/zhayujie/cowagent-assets@main/screenshots/en/web-console-evolution-demo.png" width="800" />
|
||||||
|
</Frame>
|
||||||
|
|
||||||
|
See [Long-term Memory](/memory), [Deep Dream](/memory/deep-dream), and [Self-Evolution](/memory/self-evolution) for details.
|
||||||
|
|
||||||
## 2. Personal Knowledge Base
|
## 2. Personal Knowledge Base
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,9 @@ CowAgent is lightweight, easy to deploy, and built to extend. Plug in any major
|
|||||||
<Card title="Personal Knowledge Base" icon="book" href="/knowledge/index">
|
<Card title="Personal Knowledge Base" icon="book" href="/knowledge/index">
|
||||||
Auto-curates structured knowledge into a Markdown wiki, builds an evolving knowledge graph with visual browsing.
|
Auto-curates structured knowledge into a Markdown wiki, builds an evolving knowledge graph with visual browsing.
|
||||||
</Card>
|
</Card>
|
||||||
|
<Card title="Self-Evolution" icon="seedling" href="/memory/self-evolution">
|
||||||
|
Reviews conversations automatically to improve skills, follow up on unfinished tasks, and consolidate memory and knowledge, growing through everyday use.
|
||||||
|
</Card>
|
||||||
<Card title="Skills System" icon="puzzle-piece" href="/skills/index">
|
<Card title="Skills System" icon="puzzle-piece" href="/skills/index">
|
||||||
A complete skill creation and execution engine. Install from Skill Hub or generate custom skills via natural-language conversation.
|
A complete skill creation and execution engine. Install from Skill Hub or generate custom skills via natural-language conversation.
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
[<a href="../../README.md">English</a>] | [<a href="../zh/README.md">中文</a>] | [日本語]
|
[<a href="../../README.md">English</a>] | [<a href="../zh/README.md">中文</a>] | [日本語]
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
**CowAgent** は、自律的にタスクを計画し、コンピュータや外部リソースを操作し、Skill を作成・実行し、パーソナルナレッジベースと長期記憶でユーザーとともに成長するオープンソースのスーパー AI アシスタントです。エンドツーエンドの Agent Harness のリファレンス実装の一つでもあります。
|
**CowAgent** は、自律的にタスクを計画し、コンピュータや外部リソースを操作し、Skill を作成・実行し、パーソナルナレッジベースと長期記憶を構築し、自己進化によってユーザーとともに成長するオープンソースのスーパー AI アシスタントです。エンドツーエンドの Agent Harness のリファレンス実装の一つでもあります。
|
||||||
|
|
||||||
CowAgent は軽量でデプロイしやすく、拡張性に優れています。主要な LLM プロバイダーをそのまま組み込み、Web や主要な IM プラットフォーム上で動作。個人 PC やサーバー上で 24 時間 365 日稼働できます。
|
CowAgent は軽量でデプロイしやすく、拡張性に優れています。主要な LLM プロバイダーをそのまま組み込み、Web や主要な IM プラットフォーム上で動作。個人 PC やサーバー上で 24 時間 365 日稼働できます。
|
||||||
|
|
||||||
@@ -36,6 +36,7 @@ CowAgent は軽量でデプロイしやすく、拡張性に優れています
|
|||||||
| [タスク計画](https://docs.cowagent.ai/ja/intro/architecture) | 複雑なタスクを分解し、目標達成までツールを繰り返し呼び出して段階的に実行 |
|
| [タスク計画](https://docs.cowagent.ai/ja/intro/architecture) | 複雑なタスクを分解し、目標達成までツールを繰り返し呼び出して段階的に実行 |
|
||||||
| [長期記憶](https://docs.cowagent.ai/ja/memory/index) | 三層構造(コンテキスト → デイリー → コア)、Deep Dream による自動蒸留、キーワードとベクトルのハイブリッド検索 |
|
| [長期記憶](https://docs.cowagent.ai/ja/memory/index) | 三層構造(コンテキスト → デイリー → コア)、Deep Dream による自動蒸留、キーワードとベクトルのハイブリッド検索 |
|
||||||
| [ナレッジベース](https://docs.cowagent.ai/ja/knowledge/index) | 構造化された知識を Markdown Wiki として自動整理し、進化し続けるナレッジグラフを可視化ブラウジング |
|
| [ナレッジベース](https://docs.cowagent.ai/ja/knowledge/index) | 構造化された知識を Markdown Wiki として自動整理し、進化し続けるナレッジグラフを可視化ブラウジング |
|
||||||
|
| [自己進化](https://docs.cowagent.ai/ja/memory/self-evolution) | 会話を自動でレビューして Skill を改善し、未完了のタスクを引き継ぎ、記憶と知識を補完。日々の利用を通じて成長 |
|
||||||
| [Skill](https://docs.cowagent.ai/ja/skills/index) | [Skill Hub](https://skills.cowagent.ai/)、GitHub、ClawHub からワンクリックでインストール;対話によるカスタム Skill 作成にも対応 |
|
| [Skill](https://docs.cowagent.ai/ja/skills/index) | [Skill Hub](https://skills.cowagent.ai/)、GitHub、ClawHub からワンクリックでインストール;対話によるカスタム Skill 作成にも対応 |
|
||||||
| [ツール](https://docs.cowagent.ai/ja/tools/index) | ファイル I/O、ターミナル、ブラウザ、スケジューラ、記憶検索、Web 検索など 10+ の組み込みツール — MCP プロトコルに完全対応 |
|
| [ツール](https://docs.cowagent.ai/ja/tools/index) | ファイル I/O、ターミナル、ブラウザ、スケジューラ、記憶検索、Web 検索など 10+ の組み込みツール — MCP プロトコルに完全対応 |
|
||||||
| [チャネル](https://docs.cowagent.ai/ja/channels/index) | 一つの Agent で Web、WeChat、Feishu、DingTalk、WeCom、QQ、公式アカウント、Telegram、Slack を同時にサポート |
|
| [チャネル](https://docs.cowagent.ai/ja/channels/index) | 一つの Agent で Web、WeChat、Feishu、DingTalk、WeCom、QQ、公式アカウント、Telegram、Slack を同時にサポート |
|
||||||
@@ -198,6 +199,8 @@ CowAgent は主要な LLM プロバイダーすべてに対応しています。
|
|||||||
|
|
||||||
## 🏷 更新履歴
|
## 🏷 更新履歴
|
||||||
|
|
||||||
|
> **2026.06.09:** [v2.1.1](https://github.com/zhayujie/CowAgent/releases/tag/2.1.1) — 自己進化、Web コンソールの強化(メッセージ管理、マルチセッション並行)、クロスプラットフォーム対応の MCP 強化と並行呼び出し、新モデル(MiniMax-M3、qwen3.7-plus)、Python 3.13 対応。
|
||||||
|
|
||||||
> **2026.06.01:** [v2.1.0](https://github.com/zhayujie/CowAgent/releases/tag/2.1.0) — 国際化対応、新チャネル(Telegram、Discord、Slack、WeChat カスタマーサービス)、CLI インタラクション強化、ワンライナーインストールの最適化、MCP Streamable HTTP 対応、新モデル(claude-opus-4-8、MiMo)。
|
> **2026.06.01:** [v2.1.0](https://github.com/zhayujie/CowAgent/releases/tag/2.1.0) — 国際化対応、新チャネル(Telegram、Discord、Slack、WeChat カスタマーサービス)、CLI インタラクション強化、ワンライナーインストールの最適化、MCP Streamable HTTP 対応、新モデル(claude-opus-4-8、MiMo)。
|
||||||
|
|
||||||
> **2026.05.22:** [v2.0.9](https://github.com/zhayujie/CowAgent/releases/tag/2.0.9) — モデル管理、MCP プロトコル対応、ブラウザセッション永続化、新モデル(gpt-5.5、gemini-3.5-flash、qwen3.7-max)、デプロイのセキュリティ強化。
|
> **2026.05.22:** [v2.0.9](https://github.com/zhayujie/CowAgent/releases/tag/2.0.9) — モデル管理、MCP プロトコル対応、ブラウザセッション永続化、新モデル(gpt-5.5、gemini-3.5-flash、qwen3.7-max)、デプロイのセキュリティ強化。
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ description: スクリプトによるCowAgentのワンクリックインスト
|
|||||||
|
|
||||||
本プロジェクトでは、ワンクリックでのインストール、設定、起動、管理を行うスクリプトを提供しています。素早くセットアップするには、スクリプトによるデプロイを推奨します。
|
本プロジェクトでは、ワンクリックでのインストール、設定、起動、管理を行うスクリプトを提供しています。素早くセットアップするには、スクリプトによるデプロイを推奨します。
|
||||||
|
|
||||||
Linux、macOS、Windowsに対応しています。Python 3.7〜3.12が必要です(3.9を推奨)。
|
Linux、macOS、Windowsに対応しています。Python 3.7〜3.13が必要です(3.9を推奨)。
|
||||||
|
|
||||||
## インストールコマンド
|
## インストールコマンド
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ CowAgent のアーキテクチャは以下のコアモジュールで構成さ
|
|||||||
| **Plan** | ユーザーの意図を理解し、複雑なタスクをマルチステップの計画に分解、目標達成までツールを反復的に呼び出す |
|
| **Plan** | ユーザーの意図を理解し、複雑なタスクをマルチステップの計画に分解、目標達成までツールを反復的に呼び出す |
|
||||||
| **Memory** | 重要な情報をコアメモリとデイリーメモリとして自動永続化し、キーワードとベクトルのハイブリッド検索でセッション間の連続性を実現 |
|
| **Memory** | 重要な情報をコアメモリとデイリーメモリとして自動永続化し、キーワードとベクトルのハイブリッド検索でセッション間の連続性を実現 |
|
||||||
| **Knowledge** | トピック別に構造化された知識を整理。Agent が価値ある情報を Markdown ページとして自律的に整理し、インデックスと相互参照で成長するナレッジネットワークを構築 |
|
| **Knowledge** | トピック別に構造化された知識を整理。Agent が価値ある情報を Markdown ページとして自律的に整理し、インデックスと相互参照で成長するナレッジネットワークを構築 |
|
||||||
|
| **Evolution** | 会話がアイドルになった後、隔離環境で自動レビューを実行。Skill を改善し、未完了のタスクを引き継ぎ、記憶と知識を補完して、日々の利用を通じて Agent を成長させる |
|
||||||
| **Tools** | Agent が OS リソースにアクセスするための中核能力。ファイル読み書き、ターミナル、ブラウザ、スケジューラ、記憶検索、Web 検索など 10 以上の組み込みツール |
|
| **Tools** | Agent が OS リソースにアクセスするための中核能力。ファイル読み書き、ターミナル、ブラウザ、スケジューラ、記憶検索、Web 検索など 10 以上の組み込みツール |
|
||||||
| **Skills** | Skill の読み込み・管理。Skill Hub や GitHub からのワンクリックインストール、または会話を通じたカスタム Skill の作成をサポート |
|
| **Skills** | Skill の読み込み・管理。Skill Hub や GitHub からのワンクリックインストール、または会話を通じたカスタム Skill の作成をサポート |
|
||||||
| **Models** | モデル層。OpenAI、Claude、Gemini、DeepSeek、MiniMax、GLM、Qwen など主要 LLM への統一アクセスを提供 |
|
| **Models** | モデル層。OpenAI、Claude、Gemini、DeepSeek、MiniMax、GLM、Qwen など主要 LLM への統一アクセスを提供 |
|
||||||
@@ -82,4 +83,5 @@ Agent のワークスペースはデフォルトで `~/cow` にあり、シス
|
|||||||
| `agent_max_context_turns` | 最大コンテキストターン数 | `30` |
|
| `agent_max_context_turns` | 最大コンテキストターン数 | `30` |
|
||||||
| `agent_max_steps` | タスクあたりの最大判断ステップ数 | `15` |
|
| `agent_max_steps` | タスクあたりの最大判断ステップ数 | `15` |
|
||||||
| `knowledge` | パーソナルナレッジベースの有効化 | `true` |
|
| `knowledge` | パーソナルナレッジベースの有効化 | `true` |
|
||||||
|
| `self_evolution_enabled` | 自己進化の有効化(新規インストールではデフォルト有効) | `false` |
|
||||||
| `cow_lang` | UI・コマンド文言・システムプロンプトなどの言語。`auto` で自動検出、`zh` / `en` も指定可 | `auto` |
|
| `cow_lang` | UI・コマンド文言・システムプロンプトなどの言語。`auto` で自動検出、`zh` / `en` も指定可 | `auto` |
|
||||||
|
|||||||
@@ -15,7 +15,13 @@ description: CowAgent の長期記憶、タスク計画、Skill システム、C
|
|||||||
<img src="https://cdn.link-ai.tech/doc/20260203000455.png" width="800" />
|
<img src="https://cdn.link-ai.tech/doc/20260203000455.png" width="800" />
|
||||||
</Frame>
|
</Frame>
|
||||||
|
|
||||||
詳細は [長期記憶](/ja/memory) と [Deep Dream](/ja/memory/deep-dream) を参照してください。
|
これに加えて、**自己進化(Self-Evolution)** により Agent は日々の利用を通じて成長し続けます。会話がアイドルになった後に自動でレビューを行い、Skill を改善し、未完了のタスクを引き継ぎ、記憶と知識を補完します。実際に変更があったときのみ簡潔に通知し、変更はいつでも取り消せます。新規インストールではデフォルトで有効です。
|
||||||
|
|
||||||
|
<Frame>
|
||||||
|
<img src="https://cdn.jsdelivr.net/gh/zhayujie/cowagent-assets@main/screenshots/en/web-console-evolution-demo.png" width="800" />
|
||||||
|
</Frame>
|
||||||
|
|
||||||
|
詳細は [長期記憶](/ja/memory)、[Deep Dream](/ja/memory/deep-dream)、[自己進化](/ja/memory/self-evolution) を参照してください。
|
||||||
|
|
||||||
## 2. パーソナルナレッジベース
|
## 2. パーソナルナレッジベース
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,9 @@ CowAgent は自ら思考しタスクを計画し、コンピュータや外部
|
|||||||
<Card title="ナレッジベース" icon="book" href="/ja/knowledge">
|
<Card title="ナレッジベース" icon="book" href="/ja/knowledge">
|
||||||
構造化された知識を自動整理し、ナレッジグラフの可視化をサポート。相互参照により継続的に成長するナレッジネットワークを構築します。
|
構造化された知識を自動整理し、ナレッジグラフの可視化をサポート。相互参照により継続的に成長するナレッジネットワークを構築します。
|
||||||
</Card>
|
</Card>
|
||||||
|
<Card title="自己進化" icon="seedling" href="/ja/memory/self-evolution">
|
||||||
|
会話の終了後に自動でレビューし、Skill を改善し、未完了のタスクを引き継ぎ、記憶と知識を補完。日々の利用を通じて Agent が成長し続けます。
|
||||||
|
</Card>
|
||||||
<Card title="Skill システム" icon="puzzle-piece" href="/ja/skills/index">
|
<Card title="Skill システム" icon="puzzle-piece" href="/ja/skills/index">
|
||||||
Skill の作成・実行エンジンを実装し、組み込み Skill を搭載。自然言語の会話を通じてカスタム Skill の開発もサポートしています。
|
Skill の作成・実行エンジンを実装し、組み込み Skill を搭載。自然言語の会話を通じてカスタム Skill の開発もサポートしています。
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -36,13 +36,13 @@ description: Self-Evolution — 会話がアイドル状態になった後に振
|
|||||||
|
|
||||||
### 関連設定
|
### 関連設定
|
||||||
|
|
||||||
自律進化はデフォルトでは無効です。Web コンソールの「設定 → Agent 設定」(「ディープシンキング」の下)にあるスイッチで有効にできるほか、設定ファイルで調整することもできます:
|
自律進化は Web コンソールの「設定 → Agent 設定」(「ディープシンキング」の下)にあるスイッチで切り替えられるほか、設定ファイルで調整することもできます:
|
||||||
|
|
||||||
| パラメータ | 説明 | デフォルト値 |
|
| パラメータ | 説明 | デフォルト値 |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `self_evolution_enabled` | 自律進化を有効にするかどうか | `false` |
|
| `self_evolution_enabled` | 自律進化を有効にするかどうか(新規インストールはデフォルトで有効) | `false` |
|
||||||
| `self_evolution_idle_minutes` | 会話がアイドル状態になってからトリガーするまでの時間(分) | `15` |
|
| `self_evolution_idle_minutes` | 会話がアイドル状態になってからトリガーするまでの時間(分) | `15` |
|
||||||
| `self_evolution_min_turns` | トリガーに必要な最小会話ターン数 | `6` |
|
| `self_evolution_min_turns` | トリガーに必要な最小会話ターン数 | `8` |
|
||||||
|
|
||||||
<Tip>
|
<Tip>
|
||||||
Web コンソールでは有効・無効のスイッチのみを提供しています。アイドル時間やターン数のしきい値を変更したい場合は、設定ファイルを編集してください。変更は即時に反映され、再起動は不要です。
|
Web コンソールでは有効・無効のスイッチのみを提供しています。アイドル時間やターン数のしきい値を変更したい場合は、設定ファイルを編集してください。変更は即時に反映され、再起動は不要です。
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ description: CowAgent バージョン更新履歴
|
|||||||
|
|
||||||
| バージョン | 日付 | 説明 |
|
| バージョン | 日付 | 説明 |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
|
| [2.1.1](/ja/releases/v2.1.1) | 2026.06.09 | 自己進化、Web コンソールの強化(メッセージ管理、マルチセッション並行)、クロスプラットフォーム対応の MCP 強化と並行呼び出し、新モデル追加(MiniMax-M3、qwen3.7-plus など)、各種改善 |
|
||||||
| [2.1.0](/ja/releases/v2.1.0) | 2026.06.01 | 国際化対応、Telegram / Discord / Slack / WeChat カスタマーサービスチャネルの追加、CLI インタラクション強化(ストリーミング出力、コマンドあいまいマッチング、タスクキャンセル)、MCP Streamable HTTP、新モデル追加 |
|
| [2.1.0](/ja/releases/v2.1.0) | 2026.06.01 | 国際化対応、Telegram / Discord / Slack / WeChat カスタマーサービスチャネルの追加、CLI インタラクション強化(ストリーミング出力、コマンドあいまいマッチング、タスクキャンセル)、MCP Streamable HTTP、新モデル追加 |
|
||||||
| [2.0.9](/ja/releases/v2.0.9) | 2026.05.22 | モデル管理機能の追加、MCP プロトコル対応、ブラウザログイン状態の永続化、新モデル追加(gpt-5.5、gemini-3.5-flash、qwen3.7-max など)、デプロイ・セキュリティ強化 |
|
| [2.0.9](/ja/releases/v2.0.9) | 2026.05.22 | モデル管理機能の追加、MCP プロトコル対応、ブラウザログイン状態の永続化、新モデル追加(gpt-5.5、gemini-3.5-flash、qwen3.7-max など)、デプロイ・セキュリティ強化 |
|
||||||
| [2.0.8](/ja/releases/v2.0.8) | 2026.05.06 | Feishu チャネル全面アップグレード(音声、ストリーミング出力と Markdown、QR コードによるワンクリック接続)、DeepSeek V4 と百度モデルの追加、スケジュールタスクツールの強化 |
|
| [2.0.8](/ja/releases/v2.0.8) | 2026.05.06 | Feishu チャネル全面アップグレード(音声、ストリーミング出力と Markdown、QR コードによるワンクリック接続)、DeepSeek V4 と百度モデルの追加、スケジュールタスクツールの強化 |
|
||||||
|
|||||||
61
docs/ja/releases/v2.1.1.mdx
Normal file
61
docs/ja/releases/v2.1.1.mdx
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
---
|
||||||
|
title: v2.1.1
|
||||||
|
description: CowAgent 2.1.1 - 自己進化、Web コンソールのメッセージ管理とマルチセッション並行、クロスプラットフォーム対応の MCP 強化、新モデルと改善
|
||||||
|
---
|
||||||
|
|
||||||
|
🌐 [English](https://docs.cowagent.ai/releases/v2.1.1) | [中文](https://docs.cowagent.ai/zh/releases/v2.1.1)
|
||||||
|
|
||||||
|
## 🧬 自己進化
|
||||||
|
|
||||||
|
CowAgent に **自己進化(Self-Evolution)** が加わりました。Agent は単発のタスクをこなすだけでなく、日々の協働を通じて成長し続けます:
|
||||||
|
|
||||||
|
- **アイドル後の自動レビュー**:会話がアイドルになると、Agent はバックグラウンドでその会話をレビューし、使用中に表面化した Skill の問題を修正し、再利用可能な新しい Skill を作成し、未完了のタスクを引き継ぎ、重要な情報を記憶とナレッジベースに記録します
|
||||||
|
- **静かに実行、必要なときだけ通知**:実際に変更があったときのみ調整内容を一言で伝え、変更がなければ何も通知しません
|
||||||
|
- **安全で取り消し可能**:レビューのたびに事前にバックアップを取り、いつでも取り消せます。組み込み Skill は保護され、すべての読み書きはワークスペース内に限定されます
|
||||||
|
|
||||||
|
新規インストールではデフォルトで有効です。既存ユーザーは Web コンソールの **設定 → Agent 設定** からワンクリックで有効化できます。
|
||||||
|
|
||||||
|
<img src="https://cdn.jsdelivr.net/gh/zhayujie/cowagent-assets@main/screenshots/en/web-console-evolution-demo.png" alt="自己進化の会話例" />
|
||||||
|
|
||||||
|
ドキュメント:[自己進化](https://docs.cowagent.ai/ja/memory/self-evolution)
|
||||||
|
|
||||||
|
## 💬 Web コンソールの強化
|
||||||
|
|
||||||
|
Web コンソールのチャット体験がさらに強化され、主要なチャット製品に近い操作感になりました:
|
||||||
|
|
||||||
|
- **メッセージ管理**:ユーザーと Bot のメッセージを編集・削除・再生成できます。コードブロックに言語ラベルとワンクリックコピーボタンを追加。Thanks [@core-power](https://github.com/core-power) (#2865)
|
||||||
|
- **マルチセッション並行**:複数のセッションを同時に実行しても互いに干渉せず、セッションに戻るとライブストリーミングが自動的に再開されます
|
||||||
|
- **細部の改善**:チャット画面のどこにでもファイルをドラッグ&ドロップ可能。アクティブなセッションを削除すると隣接セッションへ自動で切り替わります
|
||||||
|
|
||||||
|
## 🧩 クロスプラットフォーム対応の MCP 強化
|
||||||
|
|
||||||
|
- **Windows 互換性の修正**:Windows で `stdio` 通信が動作しない問題を修正し、サーバーのタイムアウトを `mcp.json` で設定できるようにしました
|
||||||
|
- **並行呼び出し**:`sse` と `streamable-http` トランスポートがセッション間の並行呼び出しに対応し、複数ツールの応答が高速化されました
|
||||||
|
|
||||||
|
Thanks [@xliu123321](https://github.com/xliu123321) (#2859)
|
||||||
|
|
||||||
|
ドキュメント:[MCP ツール](https://docs.cowagent.ai/ja/tools/mcp)
|
||||||
|
|
||||||
|
## 🤖 新モデルと改善
|
||||||
|
|
||||||
|
- **MiniMax-M3**:追加してデフォルトモデルに設定(M2.7 シリーズはオプションとして継続)。Thanks [@octo-patch](https://github.com/octo-patch) (#2855)
|
||||||
|
- **Qwen3.7-plus**:マルチモーダル対話に対応
|
||||||
|
- **ASR モデルの選択**:Web コンソールで ASR(音声認識)モデルを選択して永続化できるようになりました。Thanks [@nightwhite](https://github.com/nightwhite) (#2857)
|
||||||
|
- **インストールメニューの簡素化**:ワンライナーインストールスクリプトのモデルメニューを簡素化し、Xiaomi MiMo オプションを追加
|
||||||
|
|
||||||
|
ドキュメント:[モデル概要](https://docs.cowagent.ai/ja/models)
|
||||||
|
|
||||||
|
## 🛠 改善と修正
|
||||||
|
|
||||||
|
- **Python 3.13 対応**:Python 3.13 環境でのインストールと依存関係の互換性を修正
|
||||||
|
- **国際化**:チャネル一覧を UI の言語順に表示。`auto` モードでの言語自動フォールバックを改善
|
||||||
|
- **より確実なキャンセル**:ストリーミング応答を中断できない場合がある問題を修正
|
||||||
|
- **CLI**:`cow status` に現在のプロジェクトパスを表示
|
||||||
|
- **デプロイのセキュリティ強化**:認証ファイルのブロック範囲を `~/.cow/.env` に限定し、他のディレクトリに影響しないように修正(Thanks [@orbisai0security](https://github.com/orbisai0security) #2863)。WeChat 公式アカウントは `wechatmp_token` が空の場合に Webhook リクエストを拒否します
|
||||||
|
- **グループタスクボードプラグイン**:グループタスクボードプラグインのソースを追加。Thanks [@Wyh-max-star](https://github.com/Wyh-max-star) (#2853)
|
||||||
|
|
||||||
|
## 📦 アップグレード
|
||||||
|
|
||||||
|
ソースコードでデプロイしている場合は `cow update` でワンクリックアップグレードするか、最新コードを取得して手動で再起動してください。詳細は [アップグレードガイド](https://docs.cowagent.ai/ja/guide/upgrade) を参照してください。
|
||||||
|
|
||||||
|
**リリース日**:2026.06.09 | [Full Changelog](https://github.com/zhayujie/CowAgent/compare/2.1.0...2.1.1)
|
||||||
@@ -11,6 +11,10 @@ Self-Evolution lets the Agent do more than finish one task at a time; it keeps i
|
|||||||
|
|
||||||
> Self-Evolution complements [Deep Dream](/memory/deep-dream). Deep Dream organizes memory itself, while Self-Evolution goes a step further to improve skills and push unfinished tasks forward, sharpening the Agent's abilities through everyday use.
|
> Self-Evolution complements [Deep Dream](/memory/deep-dream). Deep Dream organizes memory itself, while Self-Evolution goes a step further to improve skills and push unfinished tasks forward, sharpening the Agent's abilities through everyday use.
|
||||||
|
|
||||||
|
<Frame>
|
||||||
|
<img src="https://cdn.jsdelivr.net/gh/zhayujie/cowagent-assets@main/screenshots/en/web-console-evolution-demo.png" alt="Self-Evolution in a conversation" />
|
||||||
|
</Frame>
|
||||||
|
|
||||||
### Three Goals
|
### Three Goals
|
||||||
|
|
||||||
Self-Evolution focuses on three things:
|
Self-Evolution focuses on three things:
|
||||||
@@ -18,7 +22,7 @@ Self-Evolution focuses on three things:
|
|||||||
| Goal | Description |
|
| Goal | Description |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| **Consolidate memory** | Record important preferences, decisions, and facts from the conversation, filling in what the main chat may have missed |
|
| **Consolidate memory** | Record important preferences, decisions, and facts from the conversation, filling in what the main chat may have missed |
|
||||||
| **Improve skills** | When a skill shows a problem in use (such as a wrong setting or a missing step), fix the skill file directly instead of just noting it; create a new skill when one is genuinely needed |
|
| **Improve skills** | ① When a skill shows a problem in use (such as a wrong setting or a missing step), fix the skill file directly; ② when a reusable workflow emerges, turn it into a new skill so it can be reused next time |
|
||||||
| **Follow up on unfinished tasks** | Spot the to-dos left in a conversation and finish them when possible |
|
| **Follow up on unfinished tasks** | Spot the to-dos left in a conversation and finish them when possible |
|
||||||
|
|
||||||
Once a review is done, if it actually changed something, the Agent tells you in a single line what it just learned and what it adjusted, so you can decide whether to roll it back.
|
Once a review is done, if it actually changed something, the Agent tells you in a single line what it just learned and what it adjusted, so you can decide whether to roll it back.
|
||||||
@@ -36,13 +40,17 @@ Only when both are met does a review begin. This makes sure there is something w
|
|||||||
|
|
||||||
### Configuration
|
### Configuration
|
||||||
|
|
||||||
Self-Evolution is off by default. You can turn it on with the toggle in the Web console under **Settings → Agent Config** (below "Deep Thinking"), or adjust it in the config file:
|
You can toggle Self-Evolution in the Web console under **Settings → Agent Config** (below "Deep Thinking"), or adjust it in the config file:
|
||||||
|
|
||||||
| Parameter | Description | Default |
|
| Parameter | Description | Default |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `self_evolution_enabled` | Whether Self-Evolution is enabled | `false` |
|
| `self_evolution_enabled` | Whether Self-Evolution is enabled (on by default for new installs) | `false` |
|
||||||
| `self_evolution_idle_minutes` | How long the conversation must be idle before it triggers (minutes) | `15` |
|
| `self_evolution_idle_minutes` | How long the conversation must be idle before it triggers (minutes) | `15` |
|
||||||
| `self_evolution_min_turns` | Minimum conversation turns required to trigger | `6` |
|
| `self_evolution_min_turns` | Minimum conversation turns required to trigger | `8` |
|
||||||
|
|
||||||
|
<Frame>
|
||||||
|
<img src="https://cdn.jsdelivr.net/gh/zhayujie/cowagent-assets@main/screenshots/en/web-console-evolution-config.png" alt="Enable Self-Evolution in the Web console" />
|
||||||
|
</Frame>
|
||||||
|
|
||||||
<Tip>
|
<Tip>
|
||||||
The Web console only exposes the on/off toggle. To change the idle window or the turn threshold, edit the config file. Changes take effect immediately, with no restart needed.
|
The Web console only exposes the on/off toggle. To change the idle window or the turn threshold, edit the config file. Changes take effect immediately, with no restart needed.
|
||||||
@@ -52,6 +60,10 @@ Self-Evolution is off by default. You can turn it on with the toggle in the Web
|
|||||||
|
|
||||||
Each review is recorded by date in `memory/evolution/YYYY-MM-DD.md`, viewable in the Web console under the **Memory → Self-Evolution** tab. That tab gathers both self-evolution records and dream diaries in one place, so you can look back on how the Agent has grown.
|
Each review is recorded by date in `memory/evolution/YYYY-MM-DD.md`, viewable in the Web console under the **Memory → Self-Evolution** tab. That tab gathers both self-evolution records and dream diaries in one place, so you can look back on how the Agent has grown.
|
||||||
|
|
||||||
|
<Frame>
|
||||||
|
<img src="https://cdn.jsdelivr.net/gh/zhayujie/cowagent-assets@main/screenshots/en/web-console-evolution-logs.png" alt="Self-Evolution records list" />
|
||||||
|
</Frame>
|
||||||
|
|
||||||
### Rolling Back
|
### Rolling Back
|
||||||
|
|
||||||
If you disagree with a change from a review, just tell the Agent in chat to undo the last change. It restores the affected files from the backup taken before the review. Every review keeps its own backup, so they never interfere with each other.
|
If you disagree with a change from a review, just tell the Agent in chat to undo the last change. It restores the affected files from the backup taken before the review. Every review keeps its own backup, so they never interfere with each other.
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ description: CowAgent version history
|
|||||||
|
|
||||||
| Version | Date | Description |
|
| Version | Date | Description |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
|
| [2.1.1](/releases/v2.1.1) | 2026.06.09 | Self-Evolution, Web Console message management and parallel sessions, cross-platform MCP enhancements with concurrent calls, new models (MiniMax-M3, qwen3.7-plus, etc.), various improvements |
|
||||||
| [2.1.0](/releases/v2.1.0) | 2026.06.01 | Internationalization, new Telegram / Discord / Slack / WeChat Customer Service channels, CLI interaction upgrades (streaming output, fuzzy command matching, task cancellation), MCP Streamable HTTP, new models |
|
| [2.1.0](/releases/v2.1.0) | 2026.06.01 | Internationalization, new Telegram / Discord / Slack / WeChat Customer Service channels, CLI interaction upgrades (streaming output, fuzzy command matching, task cancellation), MCP Streamable HTTP, new models |
|
||||||
| [2.0.9](/releases/v2.0.9) | 2026.05.22 | Model management console, MCP protocol support, browser persistent login, new models (gpt-5.5, gemini-3.5-flash, qwen3.7-max, etc.), deployment hardening |
|
| [2.0.9](/releases/v2.0.9) | 2026.05.22 | Model management console, MCP protocol support, browser persistent login, new models (gpt-5.5, gemini-3.5-flash, qwen3.7-max, etc.), deployment hardening |
|
||||||
| [2.0.8](/releases/v2.0.8) | 2026.05.06 | Major Feishu channel upgrade (voice, streaming and Markdown, one-click QR-scan setup), DeepSeek V4 and Baidu models, scheduler tool enhancements |
|
| [2.0.8](/releases/v2.0.8) | 2026.05.06 | Major Feishu channel upgrade (voice, streaming and Markdown, one-click QR-scan setup), DeepSeek V4 and Baidu models, scheduler tool enhancements |
|
||||||
|
|||||||
63
docs/releases/v2.1.1.mdx
Normal file
63
docs/releases/v2.1.1.mdx
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
---
|
||||||
|
title: v2.1.1
|
||||||
|
description: CowAgent 2.1.1 - Self-Evolution, Web Console message management and parallel sessions, cross-platform MCP enhancements, new models and improvements
|
||||||
|
---
|
||||||
|
|
||||||
|
🌐 [English](https://docs.cowagent.ai/releases/v2.1.1) | [中文](https://docs.cowagent.ai/zh/releases/v2.1.1)
|
||||||
|
|
||||||
|
## 🧬 Self-Evolution
|
||||||
|
|
||||||
|
CowAgent introduces **Self-Evolution**, letting the agent go beyond completing a single task and keep improving through everyday collaboration with you:
|
||||||
|
|
||||||
|
- **Automatic review after idle**: Once a conversation goes idle, the agent reviews it in the background to fix problems a skill exposed in use, create reusable new skills, follow up on unfinished tasks, and record important information into memory and the knowledge base
|
||||||
|
- **Silent by default, notify on demand**: It reports what it changed only when it actually made a change, and stays silent otherwise
|
||||||
|
- **Safe and reversible**: Every review is backed up beforehand and can be undone at any time. Built-in skills are protected, and all reads and writes stay within the workspace
|
||||||
|
|
||||||
|
Enabled by default for new installs. Existing users can turn it on with a single click in the Web Console under **Settings → Agent Config**.
|
||||||
|
|
||||||
|
<img src="https://cdn.jsdelivr.net/gh/zhayujie/cowagent-assets@main/screenshots/en/web-console-evolution-demo.png" alt="Self-Evolution conversation example" />
|
||||||
|
|
||||||
|
Documentation: [Self-Evolution](https://docs.cowagent.ai/memory/self-evolution)
|
||||||
|
|
||||||
|
## 💬 Web Console Upgrades
|
||||||
|
|
||||||
|
The Web Console chat experience gets several enhancements:
|
||||||
|
|
||||||
|
- **Message management**: Edit, delete, and regenerate both user and bot messages; code blocks now include language labels and a one-click copy button
|
||||||
|
- **Parallel sessions**: Run multiple sessions at the same time without interference, with live streaming automatically resumed when you switch back to a session
|
||||||
|
- **Refinements**: Drag and drop files anywhere in the chat view; automatically switch to a sibling session after deleting the active one
|
||||||
|
|
||||||
|
Thanks [@core-power](https://github.com/core-power) (#2865)
|
||||||
|
|
||||||
|
## 🧩 Cross-platform MCP Enhancements
|
||||||
|
|
||||||
|
- **Windows compatibility fix**: Fixed `stdio` communication failing on Windows, and made the server timeout configurable via `mcp.json`
|
||||||
|
- **Concurrent calls**: The `sse` and `streamable-http` transports now support concurrent calls across sessions for faster multi-tool responses
|
||||||
|
|
||||||
|
Thanks [@xliu123321](https://github.com/xliu123321) (#2859)
|
||||||
|
|
||||||
|
Documentation: [MCP Tools](https://docs.cowagent.ai/tools/mcp)
|
||||||
|
|
||||||
|
## 🤖 New Models & Improvements
|
||||||
|
|
||||||
|
- **MiniMax-M3**: Added and set as the default model, with the M2.7 series kept as an option. Thanks [@octo-patch](https://github.com/octo-patch) (#2855)
|
||||||
|
- **Qwen3.7-plus**: Added support for multi-modal conversations
|
||||||
|
- **Selectable ASR model**: The Web Console can now select and persist the ASR (speech recognition) model. Thanks [@nightwhite](https://github.com/nightwhite) (#2857)
|
||||||
|
- **Simplified install menu**: The one-line install script streamlines the model menu and adds the Xiaomi MiMo option
|
||||||
|
|
||||||
|
Documentation: [Models Overview](https://docs.cowagent.ai/models)
|
||||||
|
|
||||||
|
## 🛠 Improvements & Fixes
|
||||||
|
|
||||||
|
- **Python 3.13 support**: Fixed installation and dependency compatibility on Python 3.13
|
||||||
|
- **Internationalization**: The channel list is now ordered by the interface language; refined the automatic language fallback under `auto` mode
|
||||||
|
- **More reliable cancellation**: Fixed cases where a streaming reply could not be interrupted
|
||||||
|
- **CLI**: `cow status` now shows the current project path
|
||||||
|
- **Hardened deployment security**: The credential-file block is narrowed to `~/.cow/.env` so other directories are no longer affected (Thanks [@orbisai0security](https://github.com/orbisai0security) #2863); the WeChat Official Account now rejects webhook requests when `wechatmp_token` is empty
|
||||||
|
- **Group task board plugin**: Added the group task board plugin source. Thanks [@Wyh-max-star](https://github.com/Wyh-max-star) (#2853)
|
||||||
|
|
||||||
|
## 📦 Upgrade
|
||||||
|
|
||||||
|
Source-code deployments can run `cow update` for a one-click upgrade, or pull the latest code and restart manually. See the [Upgrade Guide](https://docs.cowagent.ai/guide/upgrade) for details.
|
||||||
|
|
||||||
|
**Release Date**: 2026.06.09 | [Full Changelog](https://github.com/zhayujie/CowAgent/compare/2.1.0...2.1.1)
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
[<a href="../../README.md">English</a>] | [中文] | [<a href="../ja/README.md">日本語</a>]
|
[<a href="../../README.md">English</a>] | [中文] | [<a href="../ja/README.md">日本語</a>]
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
**CowAgent** 是一个开源的超级 AI 助理,能够主动思考和规划任务、操作计算机和外部资源、创造和执行 Skills、构建知识库与长期记忆,与你一同成长,是 Agent Harness 工程的最佳实践之一。
|
**CowAgent** 是一个开源的超级 AI 助理,能够主动思考和规划任务、操作计算机和外部资源、创造和执行 Skills、构建知识库与长期记忆、通过自主进化与你一同成长,是 Agent Harness 工程的最佳实践之一。
|
||||||
|
|
||||||
CowAgent 轻量、易部署、可扩展,自由接入主流大模型,覆盖微信、飞书、钉钉、企微、QQ、Telegram、Slack、网页等多渠道,7×24 运行于个人电脑或服务器中。
|
CowAgent 轻量、易部署、可扩展,自由接入主流大模型,覆盖微信、飞书、钉钉、企微、QQ、Telegram、Slack、网页等多渠道,7×24 运行于个人电脑或服务器中。
|
||||||
|
|
||||||
@@ -36,6 +36,7 @@ CowAgent 轻量、易部署、可扩展,自由接入主流大模型,覆盖
|
|||||||
| [任务规划](https://docs.cowagent.ai/zh/intro/architecture) | 理解复杂任务并自主分解执行,循环调用工具直到完成目标 |
|
| [任务规划](https://docs.cowagent.ai/zh/intro/architecture) | 理解复杂任务并自主分解执行,循环调用工具直到完成目标 |
|
||||||
| [长期记忆](https://docs.cowagent.ai/zh/memory) | 三层记忆架构(上下文 → 天级 → 核心),梦境蒸馏自动整理,支持关键词与向量混合检索 |
|
| [长期记忆](https://docs.cowagent.ai/zh/memory) | 三层记忆架构(上下文 → 天级 → 核心),梦境蒸馏自动整理,支持关键词与向量混合检索 |
|
||||||
| [知识库](https://docs.cowagent.ai/zh/knowledge) | 自动整理结构化知识为 Markdown Wiki,构建持续增长的知识图谱,可视化浏览 |
|
| [知识库](https://docs.cowagent.ai/zh/knowledge) | 自动整理结构化知识为 Markdown Wiki,构建持续增长的知识图谱,可视化浏览 |
|
||||||
|
| [自主进化](https://docs.cowagent.ai/zh/memory/self-evolution) | 自动复盘对话,优化技能、处理未完成事项、沉淀记忆与知识,在使用中持续成长 |
|
||||||
| [技能](https://docs.cowagent.ai/zh/skills) | 从 [Skill Hub](https://skills.cowagent.ai/)、GitHub、ClawHub 等一键安装;也可通过对话创造自定义技能 |
|
| [技能](https://docs.cowagent.ai/zh/skills) | 从 [Skill Hub](https://skills.cowagent.ai/)、GitHub、ClawHub 等一键安装;也可通过对话创造自定义技能 |
|
||||||
| [工具](https://docs.cowagent.ai/zh/tools) | 内置文件读写、终端、浏览器、定时任务、记忆检索、联网搜索等 10+ 工具,支持 MCP 协议 |
|
| [工具](https://docs.cowagent.ai/zh/tools) | 内置文件读写、终端、浏览器、定时任务、记忆检索、联网搜索等 10+ 工具,支持 MCP 协议 |
|
||||||
| [通道](https://docs.cowagent.ai/zh/channels) | 一个 Agent 同时接入 Web、微信、飞书、钉钉、企微、QQ、公众号、Telegram、Slack 等多个渠道 |
|
| [通道](https://docs.cowagent.ai/zh/channels) | 一个 Agent 同时接入 Web、微信、飞书、钉钉、企微、QQ、公众号、Telegram、Slack 等多个渠道 |
|
||||||
@@ -199,6 +200,8 @@ CowAgent 支持国内外主流厂商的大语言模型。**文本对话、图像
|
|||||||
|
|
||||||
## 🏷 更新日志
|
## 🏷 更新日志
|
||||||
|
|
||||||
|
> **2026.06.09:** [v2.1.1](https://github.com/zhayujie/CowAgent/releases/tag/2.1.1) — 自进化能力、Web 控制台升级(消息管理、多会话并行)、新模型接入(MiniMax-M3、qwen3.7-plus)、Python 3.13 支持
|
||||||
|
|
||||||
> **2026.06.01:** [v2.1.0](https://github.com/zhayujie/CowAgent/releases/tag/2.1.0) — 国际化支持、新增通道(Telegram、Discord、Slack、微信客服)、命令行交互升级、一键安装脚本优化、MCP Streamable HTTP 支持、新模型接入(claude-opus-4-8、MiMo)
|
> **2026.06.01:** [v2.1.0](https://github.com/zhayujie/CowAgent/releases/tag/2.1.0) — 国际化支持、新增通道(Telegram、Discord、Slack、微信客服)、命令行交互升级、一键安装脚本优化、MCP Streamable HTTP 支持、新模型接入(claude-opus-4-8、MiMo)
|
||||||
|
|
||||||
> **2026.05.22:** [v2.0.9](https://github.com/zhayujie/CowAgent/releases/tag/2.0.9) — 模型管理、MCP 协议支持、浏览器登录态持久化、新模型接入(gpt-5.5、gemini-3.5-flash、qwen3.7-max)、部署安全加固
|
> **2026.05.22:** [v2.0.9](https://github.com/zhayujie/CowAgent/releases/tag/2.0.9) — 模型管理、MCP 协议支持、浏览器登录态持久化、新模型接入(gpt-5.5、gemini-3.5-flash、qwen3.7-max)、部署安全加固
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ description: 使用脚本一键安装和管理 CowAgent
|
|||||||
|
|
||||||
项目提供了一键安装、配置、启动、管理程序的脚本,推荐使用脚本快速运行。
|
项目提供了一键安装、配置、启动、管理程序的脚本,推荐使用脚本快速运行。
|
||||||
|
|
||||||
支持 Linux、macOS、Windows 操作系统,需安装 Python 3.7 ~ 3.12(推荐 3.9)。
|
支持 Linux、macOS、Windows 操作系统,需安装 Python 3.7 ~ 3.13(推荐 3.9)。
|
||||||
|
|
||||||
## 安装命令
|
## 安装命令
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ CowAgent 的整体架构由以下核心模块组成:
|
|||||||
| **Plan** | 理解用户意图,将复杂任务分解为多步骤计划,循环调用工具直到完成目标 |
|
| **Plan** | 理解用户意图,将复杂任务分解为多步骤计划,循环调用工具直到完成目标 |
|
||||||
| **Memory** | 自动将重要信息持久化为核心记忆和日级记忆,支持关键词和向量混合检索,跨会话保持上下文连续性 |
|
| **Memory** | 自动将重要信息持久化为核心记忆和日级记忆,支持关键词和向量混合检索,跨会话保持上下文连续性 |
|
||||||
| **Knowledge** | 以主题维度组织结构化知识,Agent 自主整理有价值信息为 Markdown 页面,维护索引和交叉引用,构建持续增长的知识网络 |
|
| **Knowledge** | 以主题维度组织结构化知识,Agent 自主整理有价值信息为 Markdown 页面,维护索引和交叉引用,构建持续增长的知识网络 |
|
||||||
|
| **Evolution** | 对话空闲后在隔离环境中自动复盘,优化技能、处理未完成事项、补全记忆与知识,让 Agent 在使用中持续成长 |
|
||||||
| **Tools** | Agent 访问操作系统资源的核心能力,内置文件读写、终端执行、浏览器操作、定时调度、记忆检索、联网搜索等 10+ 种工具 |
|
| **Tools** | Agent 访问操作系统资源的核心能力,内置文件读写、终端执行、浏览器操作、定时调度、记忆检索、联网搜索等 10+ 种工具 |
|
||||||
| **Skills** | 加载和管理 Skills,支持从 Skill Hub、GitHub 等一键安装,或通过对话创建自定义技能 |
|
| **Skills** | 加载和管理 Skills,支持从 Skill Hub、GitHub 等一键安装,或通过对话创建自定义技能 |
|
||||||
| **Models** | 模型层,统一接入 OpenAI、Claude、Gemini、DeepSeek、MiniMax、GLM、Qwen 等国内外主流大语言模型 |
|
| **Models** | 模型层,统一接入 OpenAI、Claude、Gemini、DeepSeek、MiniMax、GLM、Qwen 等国内外主流大语言模型 |
|
||||||
@@ -84,4 +85,5 @@ Agent 的工作空间默认位于 `~/cow` 目录,用于存储系统提示词
|
|||||||
| `agent_max_steps` | 单次任务最大决策步数 | `20` |
|
| `agent_max_steps` | 单次任务最大决策步数 | `20` |
|
||||||
| `enable_thinking` | 是否启用深度思考模式 | `false` |
|
| `enable_thinking` | 是否启用深度思考模式 | `false` |
|
||||||
| `knowledge` | 是否启用个人知识库 | `true` |
|
| `knowledge` | 是否启用个人知识库 | `true` |
|
||||||
|
| `self_evolution_enabled` | 是否启用自主进化 | `false` |
|
||||||
| `cow_lang` | 界面、命令文案、系统提示词等的语言,`auto` 自动检测,可设为 `zh` / `en` | `auto` |
|
| `cow_lang` | 界面、命令文案、系统提示词等的语言,`auto` 自动检测,可设为 `zh` / `en` | `auto` |
|
||||||
|
|||||||
@@ -15,7 +15,13 @@ description: CowAgent 长期记忆、个人知识库、任务规划、技能系
|
|||||||
<img src="https://cdn.link-ai.tech/doc/20260203000455.png" width="800" />
|
<img src="https://cdn.link-ai.tech/doc/20260203000455.png" width="800" />
|
||||||
</Frame>
|
</Frame>
|
||||||
|
|
||||||
详细说明请参考 [长期记忆](/zh/memory) 和 [梦境蒸馏](/zh/memory/deep-dream)。
|
在此基础上,**自主进化(Self-Evolution)** 让 Agent 在使用中持续成长:对话空闲后自动复盘,优化技能、处理遗留任务、补全记忆与知识,仅在确有改动时简短告知,且每次改动可随时撤销。
|
||||||
|
|
||||||
|
<Frame>
|
||||||
|
<img src="https://cdn.jsdelivr.net/gh/zhayujie/cowagent-assets@main/screenshots/zh/web-console-evolution-demo-zh.png" width="800" />
|
||||||
|
</Frame>
|
||||||
|
|
||||||
|
详细说明请参考 [长期记忆](/zh/memory)、[梦境蒸馏](/zh/memory/deep-dream) 和 [自主进化](/zh/memory/self-evolution)。
|
||||||
|
|
||||||
## 2. 个人知识库
|
## 2. 个人知识库
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,9 @@ CowAgent 支持灵活切换多种模型,能处理文本、语音、图片、
|
|||||||
<Card title="个人知识库" icon="book" href="/zh/knowledge">
|
<Card title="个人知识库" icon="book" href="/zh/knowledge">
|
||||||
自动整理结构化知识,支持知识图谱可视化,通过交叉引用构建持续增长的知识网络。
|
自动整理结构化知识,支持知识图谱可视化,通过交叉引用构建持续增长的知识网络。
|
||||||
</Card>
|
</Card>
|
||||||
|
<Card title="自主进化" icon="seedling" href="/zh/memory/self-evolution">
|
||||||
|
对话结束后自动复盘,优化技能、处理遗留任务、沉淀记忆与知识,让 Agent 在使用中持续成长。
|
||||||
|
</Card>
|
||||||
<Card title="技能系统" icon="puzzle-piece" href="/zh/skills/index">
|
<Card title="技能系统" icon="puzzle-piece" href="/zh/skills/index">
|
||||||
实现了Skills创建和运行的引擎,内置多种技能,并支持通过自然语言对话完成自定义Skills开发。
|
实现了Skills创建和运行的引擎,内置多种技能,并支持通过自然语言对话完成自定义Skills开发。
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -1,25 +1,30 @@
|
|||||||
---
|
---
|
||||||
title: 自主进化
|
title: 自主进化
|
||||||
description: Self-Evolution — 会话空闲后自动复盘,沉淀记忆、优化技能、处理未完成事项
|
description: Self-Evolution:自动复盘,沉淀记忆、优化技能、处理未完成事项
|
||||||
---
|
---
|
||||||
|
|
||||||
## 功能介绍
|
## 功能介绍
|
||||||
|
|
||||||
### 简介
|
### 简介
|
||||||
|
|
||||||
自主进化(Self-Evolution)让 Agent 不止于"完成单次任务",而是能在与你的相处中持续成长。每段对话告一段落后,它会自动"回头复盘"一次:把值得记住的沉淀为长期记忆、把使用中暴露的问题修进技能、把没做完的事情接着推进。久而久之,Agent 会越来越懂你的偏好、越来越少重复犯错、越来越主动地把事情收尾,而这一切都在后台静默完成,只有真正做了事情时才会简短地告诉你。
|
自主进化(Self-Evolution)让 Agent 不止于"完成单次任务",而是能在与你的相处中持续成长。在每段对话告一段落后,它会自动"回头复盘"一次:把使用中暴露的问题修进技能、把没做完的事情接着推进,并把值得记住的沉淀进记忆与知识库。久而久之,Agent 会越来越懂你的偏好、越来越少重复犯错、越来越主动地把事情收尾,而这一切都在后台静默完成,当真正做了事情时才会主动地告诉你。
|
||||||
|
|
||||||
> 它与[梦境蒸馏](/zh/memory/deep-dream)互补:梦境蒸馏负责整理记忆本身,自主进化则在记忆之外,进一步优化技能、推进未完成的任务,让 Agent 的能力随使用不断打磨。
|
> 它与[梦境蒸馏](/zh/memory/deep-dream)互补:梦境蒸馏负责整理记忆本身,自主进化则在记忆之外,进一步优化技能、推进未完成的任务,让 Agent 的能力随使用不断打磨。
|
||||||
|
|
||||||
### 三个目标
|
<Frame>
|
||||||
|
<img src="https://cdn.jsdelivr.net/gh/zhayujie/cowagent-assets@main/screenshots/zh/web-console-evolution-demo-zh.png" alt="自主进化对话示例" />
|
||||||
|
</Frame>
|
||||||
|
|
||||||
自主进化围绕三件事工作:
|
### 几个目标
|
||||||
|
|
||||||
|
自主进化围绕以下几件事工作,并以「优化技能、处理未完成事项」为主,「沉淀记忆、知识」作为主对话的查缺补漏:
|
||||||
|
|
||||||
| 目标 | 说明 |
|
| 目标 | 说明 |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| **沉淀记忆** | 把对话中重要的偏好、决策、事实补记到记忆中,作为主对话的查缺补漏 |
|
| **优化技能** | ① 技能在使用中暴露问题(如配置错误、步骤缺失)时,直接修正技能文件;② 出现一套可复用的流程时,主动固化为新技能,下次直接调用 |
|
||||||
| **优化技能** | 当某个技能在使用中暴露出问题(如配置错误、步骤缺失),直接修正技能文件,而不只是记一笔;也可在需要时创建新技能 |
|
|
||||||
| **处理未完成事项** | 识别对话中遗留的待办,在能完成时直接完成 |
|
| **处理未完成事项** | 识别对话中遗留的待办,在能完成时直接完成 |
|
||||||
|
| **沉淀记忆** | 把对话中重要的偏好、决策、事实补记到记忆中,作为主对话的查缺补漏 |
|
||||||
|
| **沉淀知识** | 把对话中产生的、值得日后查阅的可复用知识补充进知识库(主对话遗漏时) |
|
||||||
|
|
||||||
复盘完成后,如果确实做了改动,Agent 会在对话中用一句话告诉你"刚刚自主学习了什么、调整了哪里",方便你判断是否需要回滚。
|
复盘完成后,如果确实做了改动,Agent 会在对话中用一句话告诉你"刚刚自主学习了什么、调整了哪里",方便你判断是否需要回滚。
|
||||||
|
|
||||||
@@ -30,19 +35,23 @@ description: Self-Evolution — 会话空闲后自动复盘,沉淀记忆、优
|
|||||||
自主进化不是定时执行,而是在**一段对话自然结束、进入空闲后**才触发,避免打断正在进行的交流。需要同时满足:
|
自主进化不是定时执行,而是在**一段对话自然结束、进入空闲后**才触发,避免打断正在进行的交流。需要同时满足:
|
||||||
|
|
||||||
- **对话已空闲** — 距离最后一次互动超过设定的空闲时长(默认 15 分钟)
|
- **对话已空闲** — 距离最后一次互动超过设定的空闲时长(默认 15 分钟)
|
||||||
- **对话有足够内容** — 自上次进化以来累积了足够轮次,或上下文已接近容量上限
|
- **对话有足够内容** — 自上次进化以来累积了足够轮次(默认 8 轮),或上下文已接近容量上限
|
||||||
|
|
||||||
只有两个条件都满足,才会启动一次复盘。这样既保证有足够的内容值得复盘,又不会在你还在对话时打扰你。
|
只有两个条件都满足,才会启动一次复盘。这样既保证有足够的内容值得复盘,又不会在你还在对话时打扰你。
|
||||||
|
|
||||||
### 相关配置
|
### 相关配置
|
||||||
|
|
||||||
自主进化默认关闭,可在 Web 控制台「配置 → Agent 配置」中通过开关启用(位于"深度思考"下方),也可在配置文件中调整:
|
自主进化可在 Web 控制台「配置 → Agent 配置」中通过开关启停(位于"深度思考"下方),也可在配置文件中调整:
|
||||||
|
|
||||||
| 参数 | 说明 | 默认值 |
|
| 参数 | 说明 | 默认值 |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `self_evolution_enabled` | 是否启用自主进化 | `false` |
|
| `self_evolution_enabled` | 是否启用自主进化 | `false` |
|
||||||
| `self_evolution_idle_minutes` | 对话空闲多久后触发(分钟) | `15` |
|
| `self_evolution_idle_minutes` | 对话空闲多久后触发(分钟) | `15` |
|
||||||
| `self_evolution_min_turns` | 触发所需的最少对话轮次 | `6` |
|
| `self_evolution_min_turns` | 触发所需的最少对话轮次 | `8` |
|
||||||
|
|
||||||
|
<Frame>
|
||||||
|
<img src="https://cdn.jsdelivr.net/gh/zhayujie/cowagent-assets@main/screenshots/zh/web-console-evolution-config-zh.png" alt="在 Web 控制台开启自主进化" />
|
||||||
|
</Frame>
|
||||||
|
|
||||||
<Tip>
|
<Tip>
|
||||||
Web 控制台只提供启用开关,若需调整空闲时长或轮次阈值,请编辑配置文件。修改后即时生效,无需重启。
|
Web 控制台只提供启用开关,若需调整空闲时长或轮次阈值,请编辑配置文件。修改后即时生效,无需重启。
|
||||||
@@ -52,6 +61,10 @@ description: Self-Evolution — 会话空闲后自动复盘,沉淀记忆、优
|
|||||||
|
|
||||||
每次进化的过程和结果会按日期记录在 `memory/evolution/YYYY-MM-DD.md` 中,可在 Web 控制台的「记忆管理 → 自主进化」tab 中查看。该 tab 同时汇总了自主进化记录与梦境日记,方便统一回顾 Agent 的成长轨迹。
|
每次进化的过程和结果会按日期记录在 `memory/evolution/YYYY-MM-DD.md` 中,可在 Web 控制台的「记忆管理 → 自主进化」tab 中查看。该 tab 同时汇总了自主进化记录与梦境日记,方便统一回顾 Agent 的成长轨迹。
|
||||||
|
|
||||||
|
<Frame>
|
||||||
|
<img src="https://cdn.jsdelivr.net/gh/zhayujie/cowagent-assets@main/screenshots/zh/web-console-evolution-logs-zh.png" alt="自主进化记录列表" />
|
||||||
|
</Frame>
|
||||||
|
|
||||||
### 如何回滚
|
### 如何回滚
|
||||||
|
|
||||||
如果你不认同某次进化的改动,直接在对话中告诉 Agent "把刚才的改动撤销"即可,它会根据进化前的备份还原相关文件。每次进化的改动都有独立备份,互不影响。
|
如果你不认同某次进化的改动,直接在对话中告诉 Agent "把刚才的改动撤销"即可,它会根据进化前的备份还原相关文件。每次进化的改动都有独立备份,互不影响。
|
||||||
@@ -73,6 +86,6 @@ description: Self-Evolution — 会话空闲后自动复盘,沉淀记忆、优
|
|||||||
| **没做事不通知** | 如果复盘后没有任何实际改动,全程静默,不产生任何通知 |
|
| **没做事不通知** | 如果复盘后没有任何实际改动,全程静默,不产生任何通知 |
|
||||||
| **空闲才触发** | 仅在对话空闲后运行,绝不打断正在进行的对话 |
|
| **空闲才触发** | 仅在对话空闲后运行,绝不打断正在进行的对话 |
|
||||||
| **改动可回滚** | 每次进化前自动备份,若对结果不满意,可一键撤销本次改动 |
|
| **改动可回滚** | 每次进化前自动备份,若对结果不满意,可一键撤销本次改动 |
|
||||||
| **保护内置技能** | 产品自带的内置技能受保护,进化过程不会改动 |
|
| **保护内置技能** | 项目自带的内置技能受保护,进化过程不会改动 |
|
||||||
| **限定工作空间** | 所有读写都限定在工作空间内,不会触碰系统其他文件 |
|
| **限定工作空间** | 所有读写都限定在工作空间内,不会触碰系统其他文件 |
|
||||||
| **后台异步** | 复盘在后台进行,不阻塞正常对话回复 |
|
| **后台异步** | 复盘在后台进行,不阻塞正常对话回复 |
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ description: CowAgent 版本更新历史
|
|||||||
|
|
||||||
| 版本 | 日期 | 说明 |
|
| 版本 | 日期 | 说明 |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
|
| [2.1.1](/zh/releases/v2.1.1) | 2026.06.09 | 自主进化能力、Web 控制台消息管理升级、新模型接入(MiniMax-M3、qwen3.7-plus 等)、其他多项优化和修复 |
|
||||||
| [2.1.0](/zh/releases/v2.1.0) | 2026.06.01 | 国际化支持、新增 Telegram / Discord / Slack / 微信客服通道、命令行交互升级(流式输出、命令模糊匹配、任务取消)、MCP Streamable HTTP、新模型接入 |
|
| [2.1.0](/zh/releases/v2.1.0) | 2026.06.01 | 国际化支持、新增 Telegram / Discord / Slack / 微信客服通道、命令行交互升级(流式输出、命令模糊匹配、任务取消)、MCP Streamable HTTP、新模型接入 |
|
||||||
| [2.0.9](/zh/releases/v2.0.9) | 2026.05.22 | 新增模型管理、MCP 协议支持、浏览器登录态持久化、新模型接入(gpt-5.5、gemini-3.5-flash、qwen3.7-max 等)、部署安全加固 |
|
| [2.0.9](/zh/releases/v2.0.9) | 2026.05.22 | 新增模型管理、MCP 协议支持、浏览器登录态持久化、新模型接入(gpt-5.5、gemini-3.5-flash、qwen3.7-max 等)、部署安全加固 |
|
||||||
| [2.0.8](/zh/releases/v2.0.8) | 2026.05.06 | 飞书渠道全面升级(语音、流式输出和Markdown、扫码一键接入)、DeepSeek V4和百度模型新增、定时任务工具增强 |
|
| [2.0.8](/zh/releases/v2.0.8) | 2026.05.06 | 飞书渠道全面升级(语音、流式输出和Markdown、扫码一键接入)、DeepSeek V4和百度模型新增、定时任务工具增强 |
|
||||||
|
|||||||
63
docs/zh/releases/v2.1.1.mdx
Normal file
63
docs/zh/releases/v2.1.1.mdx
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
---
|
||||||
|
title: v2.1.1
|
||||||
|
description: CowAgent 2.1.1:自主进化能力、Web 控制台消息管理与多会话并行、MCP 跨平台增强、多模型接入与优化
|
||||||
|
---
|
||||||
|
|
||||||
|
🌐 [English](https://docs.cowagent.ai/releases/v2.1.1) | [中文](https://docs.cowagent.ai/zh/releases/v2.1.1)
|
||||||
|
|
||||||
|
## 🧬 自主进化能力
|
||||||
|
|
||||||
|
CowAgent 新增 **自主进化(Self-Evolution)** 能力,让 Agent 不止于完成单次任务,而是在与你的日常协作中持续成长:
|
||||||
|
|
||||||
|
- **空闲后自动复盘**:对话空闲后自动复盘,修正技能在使用中暴露的问题、创建可复用的新技能,处理遗留的未完成事项,并将重要信息补充进记忆与知识库
|
||||||
|
- **静默执行、按需提醒**:仅在实际有改动时主动告知本次调整内容,无变更时全程静默
|
||||||
|
- **安全可回退**:每次复盘前自动备份,可随时撤销本次改动;内置技能受保护,所有读写均限定在工作空间内
|
||||||
|
|
||||||
|
新安装用户默认开启,已有用户可在 Web 控制台 **设置 → Agent 配置** 中一键开启。
|
||||||
|
|
||||||
|
<img src="https://cdn.jsdelivr.net/gh/zhayujie/cowagent-assets@main/screenshots/zh/web-console-evolution-demo-zh.png" alt="自主进化对话示例" />
|
||||||
|
|
||||||
|
相关文档:[自主进化](https://docs.cowagent.ai/zh/memory/self-evolution)
|
||||||
|
|
||||||
|
## 💬 Web 控制台升级
|
||||||
|
|
||||||
|
Web 控制台的聊天体验进一步增强:
|
||||||
|
|
||||||
|
- **消息管理**:用户与机器人的消息均支持编辑、删除、重新生成;代码块新增语言标签和一键复制按钮
|
||||||
|
- **多会话并行**:支持多个会话同时进行、互不干扰,切回会话时自动恢复实时流式输出
|
||||||
|
- **细节优化**:支持将文件拖拽到整个聊天区域;删除当前会话后自动切换到相邻会话
|
||||||
|
|
||||||
|
Thanks @core-power (#2865)
|
||||||
|
|
||||||
|
## 🧩 MCP 跨平台增强
|
||||||
|
|
||||||
|
- **Windows 兼容修复**:修复 MCP 在 Windows 下 `stdio` 通信不可用的问题,并支持通过 `mcp.json` 配置服务超时时间
|
||||||
|
- **并发调用支持**:`sse` 与 `streamable-http` 传输支持跨会话并发调用,多工具响应更快
|
||||||
|
|
||||||
|
Thanks @xliu123321 (#2859)
|
||||||
|
|
||||||
|
相关文档:[MCP 工具](https://docs.cowagent.ai/zh/tools/mcp)
|
||||||
|
|
||||||
|
## 🤖 模型新增与优化
|
||||||
|
|
||||||
|
- **MiniMax-M3**:新增并设为默认模型,保留 M2.7 系列作为可选项。Thanks @octo-patch (#2855)
|
||||||
|
- **通义千问 qwen3.7-plus**:支持多模态对话
|
||||||
|
- **语音识别模型可选**:Web 控制台支持选择 ASR(语音识别)模型并持久化保存。Thanks @nightwhite (#2857)
|
||||||
|
- **安装菜单简化**:一键安装脚本精简模型选择菜单,新增小米 MiMo 选项
|
||||||
|
|
||||||
|
相关文档:[模型概览](https://docs.cowagent.ai/zh/models)
|
||||||
|
|
||||||
|
## 🛠 体验优化与修复
|
||||||
|
|
||||||
|
- **Python 3.13 支持**:修复在 Python 3.13 环境下的安装与依赖兼容问题
|
||||||
|
- **国际化体验**:通道列表按界面语言排序展示;优化 `auto` 模式下的语言自动回退逻辑
|
||||||
|
- **任务取消更可靠**:修复部分场景下流式回复无法中断的问题
|
||||||
|
- **CLI 增强**:`cow status` 新增显示当前项目路径
|
||||||
|
- **部署安全加固**:凭证文件拦截范围收敛至 `~/.cow/.env`,不再误拦其他目录(Thanks @orbisai0security #2863);微信公众号在 `wechatmp_token` 为空时拒绝 Webhook 请求
|
||||||
|
- **群任务看板插件**:新增群聊任务看板插件源。Thanks @Wyh-max-star (#2853)
|
||||||
|
|
||||||
|
## 📦 升级方式
|
||||||
|
|
||||||
|
源码部署可执行 `cow update` 一键升级,或手动拉取代码后重启。详见 [更新升级文档](https://docs.cowagent.ai/zh/guide/upgrade)。
|
||||||
|
|
||||||
|
**发布日期**:2026.06.09 | [Full Changelog](https://github.com/zhayujie/CowAgent/compare/2.1.0...2.1.1)
|
||||||
@@ -3,8 +3,6 @@ aiohttp>=3.8.6,<3.10
|
|||||||
requests>=2.28.2
|
requests>=2.28.2
|
||||||
chardet>=5.1.0
|
chardet>=5.1.0
|
||||||
Pillow
|
Pillow
|
||||||
web.py
|
|
||||||
legacy-cgi; python_version >= "3.13"
|
|
||||||
python-dotenv>=1.0.0
|
python-dotenv>=1.0.0
|
||||||
PyYAML>=6.0
|
PyYAML>=6.0
|
||||||
croniter>=2.0.0
|
croniter>=2.0.0
|
||||||
@@ -32,4 +30,9 @@ python-telegram-bot
|
|||||||
# slack bot
|
# slack bot
|
||||||
slack_bolt
|
slack_bolt
|
||||||
# discord bot
|
# discord bot
|
||||||
discord.py
|
discord.py
|
||||||
|
|
||||||
|
# web.py: PyPI 0.62 fails to build on Python 3.13+ (cgi module removed), use GitHub fix instead
|
||||||
|
web.py; python_version < "3.13"
|
||||||
|
web.py @ git+https://github.com/webpy/webpy.git ; python_version >= "3.13"
|
||||||
|
legacy-cgi; python_version >= "3.13"
|
||||||
|
|||||||
33
run.sh
33
run.sh
@@ -359,39 +359,37 @@ detect_python_command() {
|
|||||||
FOUND_NEWER_VERSION=""
|
FOUND_NEWER_VERSION=""
|
||||||
|
|
||||||
# Try to find Python command in order of preference
|
# Try to find Python command in order of preference
|
||||||
for cmd in python3 python python3.12 python3.11 python3.10 python3.9 python3.8 python3.7; do
|
for cmd in python3 python python3.13 python3.12 python3.11 python3.10 python3.9 python3.8 python3.7; do
|
||||||
if command -v $cmd &> /dev/null; then
|
if command -v $cmd &> /dev/null; then
|
||||||
# Check Python version
|
# Check Python version
|
||||||
major_version=$($cmd -c 'import sys; print(sys.version_info[0])' 2>/dev/null)
|
major_version=$($cmd -c 'import sys; print(sys.version_info[0])' 2>/dev/null)
|
||||||
minor_version=$($cmd -c 'import sys; print(sys.version_info[1])' 2>/dev/null)
|
minor_version=$($cmd -c 'import sys; print(sys.version_info[1])' 2>/dev/null)
|
||||||
|
|
||||||
if [[ "$major_version" == "3" ]]; then
|
if [[ "$major_version" == "3" ]]; then
|
||||||
# Check if version is in supported range (3.7 - 3.12)
|
# Supported range is 3.7+. On 3.13+ web.py is installed from a
|
||||||
if (( minor_version >= 7 && minor_version <= 12 )); then
|
# pinned GitHub commit (see requirements.txt), which needs git.
|
||||||
|
if (( minor_version >= 7 )); then
|
||||||
PYTHON_CMD=$cmd
|
PYTHON_CMD=$cmd
|
||||||
PYTHON_VERSION="${major_version}.${minor_version}"
|
PYTHON_VERSION="${major_version}.${minor_version}"
|
||||||
break
|
break
|
||||||
elif (( minor_version >= 13 )); then
|
|
||||||
# Found Python 3.13+, but not compatible
|
|
||||||
if [ -z "$FOUND_NEWER_VERSION" ]; then
|
|
||||||
FOUND_NEWER_VERSION="${major_version}.${minor_version}"
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
if [ -z "$PYTHON_CMD" ]; then
|
if [ -z "$PYTHON_CMD" ]; then
|
||||||
echo -e "${YELLOW}Tried: python3, python, python3.12, python3.11, python3.10, python3.9, python3.8, python3.7${NC}"
|
echo -e "${YELLOW}Tried: python3, python, python3.13, python3.12, python3.11, python3.10, python3.9, python3.8, python3.7${NC}"
|
||||||
if [ -n "$FOUND_NEWER_VERSION" ]; then
|
echo -e "${RED}❌ No suitable Python found. Please install Python 3.7 or newer${NC}"
|
||||||
echo -e "${RED}❌ Found Python $FOUND_NEWER_VERSION, but this project requires Python 3.7-3.12${NC}"
|
|
||||||
echo -e "${YELLOW}Python 3.13+ has compatibility issues with some dependencies (web.py, cgi module removed)${NC}"
|
|
||||||
echo -e "${YELLOW}Please install Python 3.7-3.12 (recommend Python 3.12)${NC}"
|
|
||||||
else
|
|
||||||
echo -e "${RED}❌ No suitable Python found. Please install Python 3.7-3.12${NC}"
|
|
||||||
fi
|
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# On 3.13+, web.py is pulled from GitHub via pip, which requires git.
|
||||||
|
if [[ "$major_version" == "3" ]] && (( minor_version >= 13 )); then
|
||||||
|
if ! command -v git &> /dev/null; then
|
||||||
|
echo -e "${YELLOW}⚠️ Python $PYTHON_VERSION detected. Installing web.py from GitHub requires git, which was not found.${NC}"
|
||||||
|
echo -e "${YELLOW} Please install git, or use Python 3.12 where web.py installs directly from PyPI.${NC}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
# Export for global use
|
# Export for global use
|
||||||
export PYTHON_CMD
|
export PYTHON_CMD
|
||||||
@@ -883,6 +881,9 @@ base = {
|
|||||||
'agent_max_context_tokens': 40000,
|
'agent_max_context_tokens': 40000,
|
||||||
'agent_max_context_turns': 30,
|
'agent_max_context_turns': 30,
|
||||||
'agent_max_steps': 15,
|
'agent_max_steps': 15,
|
||||||
|
# New installs opt into self-evolution; existing users (no key) keep the
|
||||||
|
# code default (off) so an upgrade never silently changes their behavior.
|
||||||
|
'self_evolution_enabled': True,
|
||||||
}
|
}
|
||||||
channel_map = {
|
channel_map = {
|
||||||
'feishu': {'feishu_app_id': 'FEISHU_APP_ID', 'feishu_app_secret': 'FEISHU_APP_SECRET'},
|
'feishu': {'feishu_app_id': 'FEISHU_APP_ID', 'feishu_app_secret': 'FEISHU_APP_SECRET'},
|
||||||
|
|||||||
@@ -201,7 +201,7 @@ function Find-Python {
|
|||||||
$ver = & $bin.Source -c "import sys; v=sys.version_info; print(f'{v.major}.{v.minor}')" 2>$null
|
$ver = & $bin.Source -c "import sys; v=sys.version_info; print(f'{v.major}.{v.minor}')" 2>$null
|
||||||
$parts = $ver -split '\.'
|
$parts = $ver -split '\.'
|
||||||
$major = [int]$parts[0]; $minor = [int]$parts[1]
|
$major = [int]$parts[0]; $minor = [int]$parts[1]
|
||||||
if ($major -eq 3 -and $minor -ge 9 -and $minor -le 13) {
|
if ($major -eq 3 -and $minor -ge 7 -and $minor -le 13) {
|
||||||
return $bin.Source
|
return $bin.Source
|
||||||
}
|
}
|
||||||
} catch {}
|
} catch {}
|
||||||
@@ -212,7 +212,7 @@ function Find-Python {
|
|||||||
$PythonCmd = Find-Python
|
$PythonCmd = Find-Python
|
||||||
function Assert-Python {
|
function Assert-Python {
|
||||||
if (-not $PythonCmd) {
|
if (-not $PythonCmd) {
|
||||||
Write-Err (T "未找到 Python 3.9-3.13,请从 https://www.python.org/downloads/ 安装" "Python 3.9-3.13 not found. Please install from https://www.python.org/downloads/")
|
Write-Err (T "未找到 Python 3.7-3.13,请从 https://www.python.org/downloads/ 安装" "Python 3.7-3.13 not found. Please install from https://www.python.org/downloads/")
|
||||||
Read-Host (T "按回车退出" "Press Enter to exit")
|
Read-Host (T "按回车退出" "Press Enter to exit")
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -195,6 +195,55 @@ def scenario_silent():
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def scenario_silent_qa():
|
||||||
|
"""A normal knowledge Q&A -> nothing durable, should stay SILENT."""
|
||||||
|
return {
|
||||||
|
"name": "普通问答 (should stay SILENT)",
|
||||||
|
"goal": "none",
|
||||||
|
"turns": [
|
||||||
|
("Python 里 list 和 tuple 有什么区别?",
|
||||||
|
"主要区别:list 可变、用 [];tuple 不可变、用 ()。tuple 更省内存、可作字典键。"),
|
||||||
|
("那什么时候该用 tuple?", "当数据不应被修改、或要做字典键/集合元素时用 tuple。"),
|
||||||
|
("懂了,谢谢", "不客气。"),
|
||||||
|
],
|
||||||
|
"scripted": "[SILENT]",
|
||||||
|
"on_edit": None,
|
||||||
|
"expect_evolved": False,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def scenario_silent_transient():
|
||||||
|
"""User shares transient, non-durable info -> should stay SILENT."""
|
||||||
|
return {
|
||||||
|
"name": "临时信息 (should stay SILENT)",
|
||||||
|
"goal": "none",
|
||||||
|
"turns": [
|
||||||
|
("帮我看下今天天气适合跑步吗,深圳", "深圳今天多云 26°C,傍晚湿度高,清晨或晚上跑步比较合适。"),
|
||||||
|
("那我晚上去吧", "好的,记得补水。"),
|
||||||
|
("行", "👍"),
|
||||||
|
],
|
||||||
|
"scripted": "[SILENT]",
|
||||||
|
"on_edit": None,
|
||||||
|
"expect_evolved": False,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def scenario_silent_advice():
|
||||||
|
"""User asks for one-off advice, no reusable workflow -> should stay SILENT."""
|
||||||
|
return {
|
||||||
|
"name": "一次性建议 (should stay SILENT)",
|
||||||
|
"goal": "none",
|
||||||
|
"turns": [
|
||||||
|
("给我起三个适合咖啡馆的名字", "可以考虑:① 拾光咖啡 ② 角落 Corner ③ 慢半拍。"),
|
||||||
|
("第二个不错", "嗯,「角落 Corner」简洁好记。"),
|
||||||
|
("就用这个了", "好的,祝开业顺利。"),
|
||||||
|
],
|
||||||
|
"scripted": "[SILENT]",
|
||||||
|
"on_edit": None,
|
||||||
|
"expect_evolved": False,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def scenario_memory_preference():
|
def scenario_memory_preference():
|
||||||
"""User states a durable working preference -> update MEMORY.md."""
|
"""User states a durable working preference -> update MEMORY.md."""
|
||||||
def edit(ws):
|
def edit(ws):
|
||||||
@@ -475,6 +524,9 @@ def scenario_unfinished_task():
|
|||||||
|
|
||||||
SCENARIOS = [
|
SCENARIOS = [
|
||||||
scenario_silent,
|
scenario_silent,
|
||||||
|
scenario_silent_qa,
|
||||||
|
scenario_silent_transient,
|
||||||
|
scenario_silent_advice,
|
||||||
scenario_memory_preference,
|
scenario_memory_preference,
|
||||||
scenario_memory_correction,
|
scenario_memory_correction,
|
||||||
scenario_skill_gap,
|
scenario_skill_gap,
|
||||||
@@ -791,6 +843,12 @@ def run_real():
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
if "--debug" in sys.argv:
|
||||||
|
import logging
|
||||||
|
from common.log import logger as _cow_logger
|
||||||
|
_cow_logger.setLevel(logging.DEBUG)
|
||||||
|
for _h in _cow_logger.handlers:
|
||||||
|
_h.setLevel(logging.DEBUG)
|
||||||
if "--real" in sys.argv:
|
if "--real" in sys.argv:
|
||||||
run_real()
|
run_real()
|
||||||
else:
|
else:
|
||||||
|
|||||||
Reference in New Issue
Block a user