feat(agent):Supports custom agent and chat experience with refactoring (#957)

* feat: add agent management functionality with creation, editing, and deletion

* feat: enhance agent creation and chat experience

- Added AgentWelcome component to display agent description on new thread creation.
- Improved agent name validation with availability check during agent creation.
- Updated NewAgentPage to handle agent creation flow more effectively, including enhanced error handling and user feedback.
- Refactored chat components to streamline message handling and improve user experience.
- Introduced new bootstrap skill for personalized onboarding conversations, including detailed conversation phases and a structured SOUL.md template.
- Updated localization files to reflect new features and error messages.
- General code cleanup and optimizations across various components and hooks.

* Refactor workspace layout and agent management components

- Updated WorkspaceLayout to use useLayoutEffect for sidebar state initialization.
- Removed unused AgentFormDialog and related edit functionality from AgentCard.
- Introduced ArtifactTrigger component to manage artifact visibility.
- Enhanced ChatBox to handle artifact selection and display.
- Improved message list rendering logic to avoid loading states.
- Updated localization files to remove deprecated keys and add new translations.
- Refined hooks for local settings and thread management to improve performance and clarity.
- Added temporal awareness guidelines to deep research skill documentation.

* feat: refactor chat components and introduce thread management hooks

* feat: improve artifact file detail preview logic and clean up console logs

* feat: refactor lead agent creation logic and improve logging details

* feat: validate agent name format and enhance error handling in agent setup

* feat: simplify thread search query by removing unnecessary metadata

* feat: update query key in useDeleteThread and useRenameThread for consistency

* feat: add isMock parameter to thread and artifact handling for improved testing

* fix: reorder import of setup_agent for consistency in builtins module

* feat: append mock parameter to thread links in CaseStudySection for testing purposes

* fix: update load_agent_soul calls to use cfg.name for improved clarity

* fix: update date format in apply_prompt_template for consistency

* feat: integrate isMock parameter into artifact content loading for enhanced testing

* docs: add license section to SKILL.md for clarity and attribution

* feat(agent): enhance model resolution and agent configuration handling

* chore: remove unused import of _resolve_model_name from agents

* feat(agent): remove unused field

* fix(agent): set default value for requested_model_name in _resolve_model_name function

* feat(agent): update get_available_tools call to handle optional agent_config and improve middleware function signature

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
This commit is contained in:
JeffJiang
2026-03-03 21:32:01 +08:00
committed by GitHub
parent 8342e88534
commit 7de94394d4
61 changed files with 3002 additions and 503 deletions

View File

@@ -15,8 +15,19 @@ from src.config.paths import get_paths
from src.models import create_chat_model
def _get_memory_file_path() -> Path:
"""Get the path to the memory file."""
def _get_memory_file_path(agent_name: str | None = None) -> Path:
"""Get the path to the memory file.
Args:
agent_name: If provided, returns the per-agent memory file path.
If None, returns the global memory file path.
Returns:
Path to the memory file.
"""
if agent_name is not None:
return get_paths().agent_memory_file(agent_name)
config = get_memory_config()
if config.storage_path:
p = Path(config.storage_path)
@@ -44,24 +55,24 @@ def _create_empty_memory() -> dict[str, Any]:
}
# Global memory data cache
_memory_data: dict[str, Any] | None = None
# Track file modification time for cache invalidation
_memory_file_mtime: float | None = None
# Per-agent memory cache: keyed by agent_name (None = global)
# Value: (memory_data, file_mtime)
_memory_cache: dict[str | None, tuple[dict[str, Any], float | None]] = {}
def get_memory_data() -> dict[str, Any]:
def get_memory_data(agent_name: str | None = None) -> dict[str, Any]:
"""Get the current memory data (cached with file modification time check).
The cache is automatically invalidated if the memory file has been modified
since the last load, ensuring fresh data is always returned.
Args:
agent_name: If provided, loads per-agent memory. If None, loads global memory.
Returns:
The memory data dictionary.
"""
global _memory_data, _memory_file_mtime
file_path = _get_memory_file_path()
file_path = _get_memory_file_path(agent_name)
# Get current file modification time
try:
@@ -69,41 +80,48 @@ def get_memory_data() -> dict[str, Any]:
except OSError:
current_mtime = None
cached = _memory_cache.get(agent_name)
# Invalidate cache if file has been modified or doesn't exist
if _memory_data is None or _memory_file_mtime != current_mtime:
_memory_data = _load_memory_from_file()
_memory_file_mtime = current_mtime
if cached is None or cached[1] != current_mtime:
memory_data = _load_memory_from_file(agent_name)
_memory_cache[agent_name] = (memory_data, current_mtime)
return memory_data
return _memory_data
return cached[0]
def reload_memory_data() -> dict[str, Any]:
def reload_memory_data(agent_name: str | None = None) -> dict[str, Any]:
"""Reload memory data from file, forcing cache invalidation.
Args:
agent_name: If provided, reloads per-agent memory. If None, reloads global memory.
Returns:
The reloaded memory data dictionary.
"""
global _memory_data, _memory_file_mtime
file_path = _get_memory_file_path(agent_name)
memory_data = _load_memory_from_file(agent_name)
file_path = _get_memory_file_path()
_memory_data = _load_memory_from_file()
# Update file modification time after reload
try:
_memory_file_mtime = file_path.stat().st_mtime if file_path.exists() else None
mtime = file_path.stat().st_mtime if file_path.exists() else None
except OSError:
_memory_file_mtime = None
mtime = None
return _memory_data
_memory_cache[agent_name] = (memory_data, mtime)
return memory_data
def _load_memory_from_file() -> dict[str, Any]:
def _load_memory_from_file(agent_name: str | None = None) -> dict[str, Any]:
"""Load memory data from file.
Args:
agent_name: If provided, loads per-agent memory file. If None, loads global.
Returns:
The memory data dictionary.
"""
file_path = _get_memory_file_path()
file_path = _get_memory_file_path(agent_name)
if not file_path.exists():
return _create_empty_memory()
@@ -117,17 +135,17 @@ def _load_memory_from_file() -> dict[str, Any]:
return _create_empty_memory()
def _save_memory_to_file(memory_data: dict[str, Any]) -> bool:
def _save_memory_to_file(memory_data: dict[str, Any], agent_name: str | None = None) -> bool:
"""Save memory data to file and update cache.
Args:
memory_data: The memory data to save.
agent_name: If provided, saves to per-agent memory file. If None, saves to global.
Returns:
True if successful, False otherwise.
"""
global _memory_data, _memory_file_mtime
file_path = _get_memory_file_path()
file_path = _get_memory_file_path(agent_name)
try:
# Ensure directory exists
@@ -145,11 +163,12 @@ def _save_memory_to_file(memory_data: dict[str, Any]) -> bool:
temp_path.replace(file_path)
# Update cache and file modification time
_memory_data = memory_data
try:
_memory_file_mtime = file_path.stat().st_mtime
mtime = file_path.stat().st_mtime
except OSError:
_memory_file_mtime = None
mtime = None
_memory_cache[agent_name] = (memory_data, mtime)
print(f"Memory saved to {file_path}")
return True
@@ -175,12 +194,13 @@ class MemoryUpdater:
model_name = self._model_name or config.model_name
return create_chat_model(name=model_name, thinking_enabled=False)
def update_memory(self, messages: list[Any], thread_id: str | None = None) -> bool:
def update_memory(self, messages: list[Any], thread_id: str | None = None, agent_name: str | None = None) -> bool:
"""Update memory based on conversation messages.
Args:
messages: List of conversation messages.
thread_id: Optional thread ID for tracking source.
agent_name: If provided, updates per-agent memory. If None, updates global memory.
Returns:
True if update was successful, False otherwise.
@@ -194,7 +214,7 @@ class MemoryUpdater:
try:
# Get current memory
current_memory = get_memory_data()
current_memory = get_memory_data(agent_name)
# Format conversation for prompt
conversation_text = format_conversation_for_update(messages)
@@ -225,7 +245,7 @@ class MemoryUpdater:
updated_memory = self._apply_updates(current_memory, update_data, thread_id)
# Save
return _save_memory_to_file(updated_memory)
return _save_memory_to_file(updated_memory, agent_name)
except json.JSONDecodeError as e:
print(f"Failed to parse LLM response for memory update: {e}")
@@ -305,15 +325,16 @@ class MemoryUpdater:
return current_memory
def update_memory_from_conversation(messages: list[Any], thread_id: str | None = None) -> bool:
def update_memory_from_conversation(messages: list[Any], thread_id: str | None = None, agent_name: str | None = None) -> bool:
"""Convenience function to update memory from a conversation.
Args:
messages: List of conversation messages.
thread_id: Optional thread ID.
agent_name: If provided, updates per-agent memory. If None, updates global memory.
Returns:
True if successful, False otherwise.
"""
updater = MemoryUpdater()
return updater.update_memory(messages, thread_id)
return updater.update_memory(messages, thread_id, agent_name)