mirror of
https://github.com/eigent-ai/eigent.git
synced 2026-04-28 11:40:25 +00:00
Merge branch 'main' into fix_install_logic
This commit is contained in:
commit
4eaf7a2e4c
30 changed files with 485 additions and 159 deletions
|
|
@ -23,14 +23,21 @@ def set_user_env_path(env_path: str | None = None):
|
||||||
Set user-specific environment path for current thread.
|
Set user-specific environment path for current thread.
|
||||||
If env_path is None, uses default global environment.
|
If env_path is None, uses default global environment.
|
||||||
"""
|
"""
|
||||||
|
traceroot_logger.info("Setting user environment path", extra={"env_path": env_path, "exists": env_path and os.path.exists(env_path) if env_path else None})
|
||||||
|
|
||||||
if env_path and os.path.exists(env_path):
|
if env_path and os.path.exists(env_path):
|
||||||
_thread_local.env_path = env_path
|
_thread_local.env_path = env_path
|
||||||
# Load user-specific environment variables
|
# Load user-specific environment variables
|
||||||
load_dotenv(dotenv_path=env_path, override=True)
|
load_dotenv(dotenv_path=env_path, override=True)
|
||||||
|
traceroot_logger.info("User-specific environment loaded", extra={"env_path": env_path})
|
||||||
else:
|
else:
|
||||||
# Clear thread-local env_path to fall back to global
|
# Clear thread-local env_path to fall back to global
|
||||||
if hasattr(_thread_local, 'env_path'):
|
if hasattr(_thread_local, 'env_path'):
|
||||||
delattr(_thread_local, 'env_path')
|
delattr(_thread_local, 'env_path')
|
||||||
|
traceroot_logger.info("Reset to default global environment")
|
||||||
|
|
||||||
|
if env_path and not os.path.exists(env_path):
|
||||||
|
traceroot_logger.warning("User environment path does not exist, falling back to global", extra={"env_path": env_path})
|
||||||
|
|
||||||
|
|
||||||
def get_current_env_path() -> str:
|
def get_current_env_path() -> str:
|
||||||
|
|
@ -64,10 +71,14 @@ def env(key: str, default=None):
|
||||||
from dotenv import dotenv_values
|
from dotenv import dotenv_values
|
||||||
user_env_values = dotenv_values(_thread_local.env_path)
|
user_env_values = dotenv_values(_thread_local.env_path)
|
||||||
if key in user_env_values:
|
if key in user_env_values:
|
||||||
return user_env_values[key] or default
|
value = user_env_values[key] or default
|
||||||
|
traceroot_logger.debug("Environment variable retrieved from user-specific config", extra={"key": key, "env_path": _thread_local.env_path, "has_value": value is not None})
|
||||||
|
return value
|
||||||
|
|
||||||
# Fall back to global environment
|
# Fall back to global environment
|
||||||
return os.getenv(key, default)
|
value = os.getenv(key, default)
|
||||||
|
traceroot_logger.debug("Environment variable retrieved from global config", extra={"key": key, "has_value": value is not None, "using_default": value == default})
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
def env_or_fail(key: str):
|
def env_or_fail(key: str):
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
from fastapi import APIRouter
|
from fastapi import APIRouter
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
from utils import traceroot_wrapper as traceroot
|
||||||
|
|
||||||
|
logger = traceroot.get_logger("health_controller")
|
||||||
|
|
||||||
router = APIRouter(tags=["Health"])
|
router = APIRouter(tags=["Health"])
|
||||||
|
|
||||||
|
|
@ -12,5 +15,8 @@ class HealthResponse(BaseModel):
|
||||||
@router.get("/health", name="health check", response_model=HealthResponse)
|
@router.get("/health", name="health check", response_model=HealthResponse)
|
||||||
async def health_check():
|
async def health_check():
|
||||||
"""Health check endpoint for verifying backend is ready to accept requests."""
|
"""Health check endpoint for verifying backend is ready to accept requests."""
|
||||||
return HealthResponse(status="ok", service="eigent")
|
logger.debug("Health check requested")
|
||||||
|
response = HealthResponse(status="ok", service="eigent")
|
||||||
|
logger.debug("Health check completed", extra={"status": response.status, "service": response.service})
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -362,6 +362,9 @@ async def step_solve(options: Chat, request: Request, task_lock: TaskLock):
|
||||||
# Update the sync_step with new task_id
|
# Update the sync_step with new task_id
|
||||||
if hasattr(item, 'new_task_id') and item.new_task_id:
|
if hasattr(item, 'new_task_id') and item.new_task_id:
|
||||||
set_current_task_id(options.project_id, item.new_task_id)
|
set_current_task_id(options.project_id, item.new_task_id)
|
||||||
|
# Reset summary generation flag for new tasks to ensure proper summaries
|
||||||
|
task_lock.summary_generated = False
|
||||||
|
logger.info("Reset summary_generated flag for new task", extra={"project_id": options.project_id, "new_task_id": item.new_task_id})
|
||||||
|
|
||||||
yield sse_json("confirmed", {"question": question})
|
yield sse_json("confirmed", {"question": question})
|
||||||
|
|
||||||
|
|
@ -385,14 +388,13 @@ async def step_solve(options: Chat, request: Request, task_lock: TaskLock):
|
||||||
context_for_coordinator
|
context_for_coordinator
|
||||||
)
|
)
|
||||||
|
|
||||||
if not task_lock.summary_generated:
|
|
||||||
summary_task_agent = task_summary_agent(options)
|
summary_task_agent = task_summary_agent(options)
|
||||||
try:
|
try:
|
||||||
summary_task_content = await asyncio.wait_for(
|
summary_task_content = await asyncio.wait_for(
|
||||||
summary_task(summary_task_agent, camel_task), timeout=10
|
summary_task(summary_task_agent, camel_task), timeout=10
|
||||||
)
|
)
|
||||||
task_lock.summary_generated = True
|
task_lock.summary_generated = True
|
||||||
logger.info("Generated summary for first task", extra={"project_id": options.project_id})
|
logger.info("Generated summary for task", extra={"project_id": options.project_id})
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
logger.warning("summary_task timeout", extra={"project_id": options.project_id, "task_id": options.task_id})
|
logger.warning("summary_task timeout", extra={"project_id": options.project_id, "task_id": options.task_id})
|
||||||
# Fallback to a minimal summary to unblock UI
|
# Fallback to a minimal summary to unblock UI
|
||||||
|
|
@ -405,12 +407,6 @@ async def step_solve(options: Chat, request: Request, task_lock: TaskLock):
|
||||||
)
|
)
|
||||||
summary_task_content = f"{fallback_name}|{fallback_summary}"
|
summary_task_content = f"{fallback_name}|{fallback_summary}"
|
||||||
task_lock.summary_generated = True
|
task_lock.summary_generated = True
|
||||||
else:
|
|
||||||
if len(question) > 100:
|
|
||||||
summary_task_content = f"Task|{question[:97]}..."
|
|
||||||
else:
|
|
||||||
summary_task_content = f"Task|{question}"
|
|
||||||
logger.info("Skipped summary generation for subsequent task", extra={"project_id": options.project_id})
|
|
||||||
|
|
||||||
yield to_sub_tasks(camel_task, summary_task_content)
|
yield to_sub_tasks(camel_task, summary_task_content)
|
||||||
# tracer.stop()
|
# tracer.stop()
|
||||||
|
|
@ -514,8 +510,10 @@ async def step_solve(options: Chat, request: Request, task_lock: TaskLock):
|
||||||
new_task_state = item.data.get('state', 'unknown')
|
new_task_state = item.data.get('state', 'unknown')
|
||||||
new_task_result = item.data.get('result', '')
|
new_task_result = item.data.get('result', '')
|
||||||
|
|
||||||
|
if camel_task is None:
|
||||||
assert camel_task is not None
|
logger.error(f"NEW_TASK_STATE action received but camel_task is None for project {options.project_id}, task {new_task_id}")
|
||||||
|
yield sse_json("error", {"message": "Cannot process new task state: current task not initialized."})
|
||||||
|
continue
|
||||||
|
|
||||||
old_task_content: str = camel_task.content
|
old_task_content: str = camel_task.content
|
||||||
old_task_result: str = await get_task_result_with_optional_summary(camel_task, options)
|
old_task_result: str = await get_task_result_with_optional_summary(camel_task, options)
|
||||||
|
|
@ -588,6 +586,24 @@ async def step_solve(options: Chat, request: Request, task_lock: TaskLock):
|
||||||
coordinator_context=context_for_multi_turn
|
coordinator_context=context_for_multi_turn
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Generate proper LLM summary for multi-turn tasks instead of hardcoded fallback
|
||||||
|
try:
|
||||||
|
multi_turn_summary_agent = task_summary_agent(options)
|
||||||
|
new_summary_content = await asyncio.wait_for(
|
||||||
|
summary_task(multi_turn_summary_agent, camel_task), timeout=10
|
||||||
|
)
|
||||||
|
logger.info("Generated LLM summary for multi-turn task", extra={"project_id": options.project_id})
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
logger.warning("Multi-turn summary_task timeout", extra={"project_id": options.project_id, "task_id": task_id})
|
||||||
|
# Fallback to descriptive but not generic summary
|
||||||
|
task_content_for_summary = new_task_content
|
||||||
|
if len(task_content_for_summary) > 100:
|
||||||
|
new_summary_content = f"Follow-up Task|{task_content_for_summary[:97]}..."
|
||||||
|
else:
|
||||||
|
new_summary_content = f"Follow-up Task|{task_content_for_summary}"
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error generating multi-turn task summary: {e}")
|
||||||
|
# Fallback to descriptive but not generic summary
|
||||||
task_content_for_summary = new_task_content
|
task_content_for_summary = new_task_content
|
||||||
if len(task_content_for_summary) > 100:
|
if len(task_content_for_summary) > 100:
|
||||||
new_summary_content = f"Follow-up Task|{task_content_for_summary[:97]}..."
|
new_summary_content = f"Follow-up Task|{task_content_for_summary[:97]}..."
|
||||||
|
|
@ -666,15 +682,31 @@ async def step_solve(options: Chat, request: Request, task_lock: TaskLock):
|
||||||
)
|
)
|
||||||
workforce.resume()
|
workforce.resume()
|
||||||
elif item.action == Action.end:
|
elif item.action == Action.end:
|
||||||
assert camel_task is not None
|
logger.info(f"Processing END action for project {options.project_id}, task {options.task_id}, camel_task exists: {camel_task is not None}, current status: {task_lock.status}")
|
||||||
task_lock.status = Status.done
|
|
||||||
|
# Prevent duplicate end processing
|
||||||
|
if task_lock.status == Status.done:
|
||||||
|
logger.warning(f"END action received but task already marked as done for project {options.project_id}, task {options.task_id}. Ignoring duplicate END action.")
|
||||||
|
continue
|
||||||
|
|
||||||
|
if camel_task is None:
|
||||||
|
logger.warning(f"END action received but camel_task is None for project {options.project_id}, task {options.task_id}. This may indicate multiple END actions or improper task lifecycle management.")
|
||||||
|
# Use the item data as the final result if camel_task is None
|
||||||
|
final_result: str = str(item.data) if item.data else "Task completed"
|
||||||
|
else:
|
||||||
final_result: str = await get_task_result_with_optional_summary(camel_task, options)
|
final_result: str = await get_task_result_with_optional_summary(camel_task, options)
|
||||||
|
|
||||||
|
task_lock.status = Status.done
|
||||||
|
|
||||||
task_lock.last_task_result = final_result
|
task_lock.last_task_result = final_result
|
||||||
|
|
||||||
|
# Handle task content - use fallback if camel_task is None
|
||||||
|
if camel_task is not None:
|
||||||
task_content: str = camel_task.content
|
task_content: str = camel_task.content
|
||||||
if "=== CURRENT TASK ===" in task_content:
|
if "=== CURRENT TASK ===" in task_content:
|
||||||
task_content = task_content.split("=== CURRENT TASK ===")[-1].strip()
|
task_content = task_content.split("=== CURRENT TASK ===")[-1].strip()
|
||||||
|
else:
|
||||||
|
task_content: str = f"Task {options.task_id}"
|
||||||
|
|
||||||
task_lock.add_conversation('task_result', {
|
task_lock.add_conversation('task_result', {
|
||||||
'task_content': task_content,
|
'task_content': task_content,
|
||||||
|
|
@ -702,8 +734,9 @@ async def step_solve(options: Chat, request: Request, task_lock: TaskLock):
|
||||||
# Check if this might be a misrouted second question
|
# Check if this might be a misrouted second question
|
||||||
if camel_task is None:
|
if camel_task is None:
|
||||||
logger.warning(f"SUPPLEMENT action received but camel_task is None for project {options.project_id}")
|
logger.warning(f"SUPPLEMENT action received but camel_task is None for project {options.project_id}")
|
||||||
|
yield sse_json("error", {"message": "Cannot supplement task: task not initialized. Please start a task first."})
|
||||||
|
continue
|
||||||
else:
|
else:
|
||||||
assert camel_task is not None
|
|
||||||
task_lock.status = Status.processing
|
task_lock.status = Status.processing
|
||||||
camel_task.add_subtask(
|
camel_task.add_subtask(
|
||||||
Task(
|
Task(
|
||||||
|
|
|
||||||
|
|
@ -283,30 +283,39 @@ class TaskLock:
|
||||||
self.question_agent = None
|
self.question_agent = None
|
||||||
self.current_task_id = None
|
self.current_task_id = None
|
||||||
|
|
||||||
|
logger.info("Task lock initialized", extra={"task_id": id, "created_at": self.created_at.isoformat()})
|
||||||
|
|
||||||
async def put_queue(self, data: ActionData):
|
async def put_queue(self, data: ActionData):
|
||||||
self.last_accessed = datetime.now()
|
self.last_accessed = datetime.now()
|
||||||
|
logger.debug("Adding item to task queue", extra={"task_id": self.id, "action": data.action})
|
||||||
await self.queue.put(data)
|
await self.queue.put(data)
|
||||||
|
|
||||||
async def get_queue(self):
|
async def get_queue(self):
|
||||||
self.last_accessed = datetime.now()
|
self.last_accessed = datetime.now()
|
||||||
|
logger.debug("Getting item from task queue", extra={"task_id": self.id})
|
||||||
return await self.queue.get()
|
return await self.queue.get()
|
||||||
|
|
||||||
async def put_human_input(self, agent: str, data: Any = None):
|
async def put_human_input(self, agent: str, data: Any = None):
|
||||||
|
logger.debug("Adding human input", extra={"task_id": self.id, "agent": agent, "has_data": data is not None})
|
||||||
await self.human_input[agent].put(data)
|
await self.human_input[agent].put(data)
|
||||||
|
|
||||||
async def get_human_input(self, agent: str):
|
async def get_human_input(self, agent: str):
|
||||||
|
logger.debug("Getting human input", extra={"task_id": self.id, "agent": agent})
|
||||||
return await self.human_input[agent].get()
|
return await self.human_input[agent].get()
|
||||||
|
|
||||||
def add_human_input_listen(self, agent: str):
|
def add_human_input_listen(self, agent: str):
|
||||||
|
logger.debug("Adding human input listener", extra={"task_id": self.id, "agent": agent})
|
||||||
self.human_input[agent] = asyncio.Queue(1)
|
self.human_input[agent] = asyncio.Queue(1)
|
||||||
|
|
||||||
def add_background_task(self, task: asyncio.Task) -> None:
|
def add_background_task(self, task: asyncio.Task) -> None:
|
||||||
r"""Add a task to track and clean up weak references"""
|
r"""Add a task to track and clean up weak references"""
|
||||||
|
logger.debug("Adding background task", extra={"task_id": self.id, "background_tasks_count": len(self.background_tasks)})
|
||||||
self.background_tasks.add(task)
|
self.background_tasks.add(task)
|
||||||
task.add_done_callback(lambda t: self.background_tasks.discard(t))
|
task.add_done_callback(lambda t: self.background_tasks.discard(t))
|
||||||
|
|
||||||
async def cleanup(self):
|
async def cleanup(self):
|
||||||
r"""Cancel all background tasks and clean up resources"""
|
r"""Cancel all background tasks and clean up resources"""
|
||||||
|
logger.info("Starting task lock cleanup", extra={"task_id": self.id, "background_tasks_count": len(self.background_tasks)})
|
||||||
for task in list(self.background_tasks):
|
for task in list(self.background_tasks):
|
||||||
if not task.done():
|
if not task.done():
|
||||||
task.cancel()
|
task.cancel()
|
||||||
|
|
@ -315,9 +324,11 @@ class TaskLock:
|
||||||
except asyncio.CancelledError:
|
except asyncio.CancelledError:
|
||||||
pass
|
pass
|
||||||
self.background_tasks.clear()
|
self.background_tasks.clear()
|
||||||
|
logger.info("Task lock cleanup completed", extra={"task_id": self.id})
|
||||||
|
|
||||||
def add_conversation(self, role: str, content: str | dict):
|
def add_conversation(self, role: str, content: str | dict):
|
||||||
"""Add a conversation entry to history"""
|
"""Add a conversation entry to history"""
|
||||||
|
logger.debug("Adding conversation entry", extra={"task_id": self.id, "role": role, "content_length": len(str(content))})
|
||||||
self.conversation_history.append({
|
self.conversation_history.append({
|
||||||
'role': role,
|
'role': role,
|
||||||
'content': content,
|
'content': content,
|
||||||
|
|
@ -344,7 +355,9 @@ task_index: dict[str, weakref.ref[Task]] = {}
|
||||||
|
|
||||||
def get_task_lock(id: str) -> TaskLock:
|
def get_task_lock(id: str) -> TaskLock:
|
||||||
if id not in task_locks:
|
if id not in task_locks:
|
||||||
|
logger.error("Task lock not found", extra={"task_id": id})
|
||||||
raise ProgramException("Task not found")
|
raise ProgramException("Task not found")
|
||||||
|
logger.debug("Task lock retrieved", extra={"task_id": id})
|
||||||
return task_locks[id]
|
return task_locks[id]
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -357,12 +370,15 @@ def set_current_task_id(project_id: str, task_id: str) -> None:
|
||||||
"""Set the current task ID for a project's task lock"""
|
"""Set the current task ID for a project's task lock"""
|
||||||
task_lock = get_task_lock(project_id)
|
task_lock = get_task_lock(project_id)
|
||||||
task_lock.current_task_id = task_id
|
task_lock.current_task_id = task_id
|
||||||
logger.info(f"Updated current_task_id to {task_id} for project {project_id}")
|
logger.info("Updated current task ID", extra={"project_id": project_id, "task_id": task_id})
|
||||||
|
|
||||||
|
|
||||||
def create_task_lock(id: str) -> TaskLock:
|
def create_task_lock(id: str) -> TaskLock:
|
||||||
if id in task_locks:
|
if id in task_locks:
|
||||||
|
logger.warning("Attempting to create task lock that already exists", extra={"task_id": id})
|
||||||
raise ProgramException("Task already exists")
|
raise ProgramException("Task already exists")
|
||||||
|
|
||||||
|
logger.info("Creating new task lock", extra={"task_id": id})
|
||||||
task_locks[id] = TaskLock(id=id, queue=asyncio.Queue(), human_input={})
|
task_locks[id] = TaskLock(id=id, queue=asyncio.Queue(), human_input={})
|
||||||
|
|
||||||
# Start cleanup task if not running
|
# Start cleanup task if not running
|
||||||
|
|
@ -370,26 +386,31 @@ def create_task_lock(id: str) -> TaskLock:
|
||||||
# if _cleanup_task is None or _cleanup_task.done():
|
# if _cleanup_task is None or _cleanup_task.done():
|
||||||
# _cleanup_task = asyncio.create_task(_periodic_cleanup())
|
# _cleanup_task = asyncio.create_task(_periodic_cleanup())
|
||||||
|
|
||||||
|
logger.info("Task lock created successfully", extra={"task_id": id, "total_task_locks": len(task_locks)})
|
||||||
return task_locks[id]
|
return task_locks[id]
|
||||||
|
|
||||||
|
|
||||||
def get_or_create_task_lock(id: str) -> TaskLock:
|
def get_or_create_task_lock(id: str) -> TaskLock:
|
||||||
"""Get existing task lock or create a new one if it doesn't exist"""
|
"""Get existing task lock or create a new one if it doesn't exist"""
|
||||||
if id in task_locks:
|
if id in task_locks:
|
||||||
|
logger.debug("Using existing task lock", extra={"task_id": id})
|
||||||
return task_locks[id]
|
return task_locks[id]
|
||||||
|
logger.info("Task lock not found, creating new one", extra={"task_id": id})
|
||||||
return create_task_lock(id)
|
return create_task_lock(id)
|
||||||
|
|
||||||
|
|
||||||
async def delete_task_lock(id: str):
|
async def delete_task_lock(id: str):
|
||||||
if id not in task_locks:
|
if id not in task_locks:
|
||||||
|
logger.warning("Attempting to delete non-existent task lock", extra={"task_id": id})
|
||||||
raise ProgramException("Task not found")
|
raise ProgramException("Task not found")
|
||||||
|
|
||||||
# Clean up background tasks before deletion
|
# Clean up background tasks before deletion
|
||||||
task_lock = task_locks[id]
|
task_lock = task_locks[id]
|
||||||
|
logger.info("Cleaning up task lock", extra={"task_id": id, "background_tasks": len(task_lock.background_tasks)})
|
||||||
await task_lock.cleanup()
|
await task_lock.cleanup()
|
||||||
|
|
||||||
del task_locks[id]
|
del task_locks[id]
|
||||||
logger.debug(f"Deleted task lock {id}, remaining locks: {len(task_locks)}")
|
logger.info("Task lock deleted successfully", extra={"task_id": id, "remaining_task_locks": len(task_locks)})
|
||||||
|
|
||||||
|
|
||||||
def get_camel_task(id: str, tasks: list[Task]) -> None | Task:
|
def get_camel_task(id: str, tasks: list[Task]) -> None | Task:
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,13 @@ class SingleAgentWorker(BaseSingleAgentWorker):
|
||||||
context_utility: ContextUtility | None = None,
|
context_utility: ContextUtility | None = None,
|
||||||
enable_workflow_memory: bool = False,
|
enable_workflow_memory: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
logger.info("Initializing SingleAgentWorker", extra={
|
||||||
|
"description": description,
|
||||||
|
"worker_agent_name": worker.agent_name,
|
||||||
|
"use_agent_pool": use_agent_pool,
|
||||||
|
"pool_max_size": pool_max_size,
|
||||||
|
"enable_workflow_memory": enable_workflow_memory
|
||||||
|
})
|
||||||
super().__init__(
|
super().__init__(
|
||||||
description=description,
|
description=description,
|
||||||
worker=worker,
|
worker=worker,
|
||||||
|
|
@ -61,6 +68,12 @@ class SingleAgentWorker(BaseSingleAgentWorker):
|
||||||
worker_agent = await self._get_worker_agent()
|
worker_agent = await self._get_worker_agent()
|
||||||
worker_agent.process_task_id = task.id # type: ignore rewrite line
|
worker_agent.process_task_id = task.id # type: ignore rewrite line
|
||||||
|
|
||||||
|
logger.info("Starting task processing", extra={
|
||||||
|
"task_id": task.id,
|
||||||
|
"worker_agent_id": worker_agent.agent_id,
|
||||||
|
"dependencies_count": len(dependencies)
|
||||||
|
})
|
||||||
|
|
||||||
response_content = ""
|
response_content = ""
|
||||||
final_response = None
|
final_response = None
|
||||||
try:
|
try:
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,9 @@ from app.service.task import Action, ActionTerminalData, Agents, get_task_lock
|
||||||
from app.utils.listen.toolkit_listen import auto_listen_toolkit
|
from app.utils.listen.toolkit_listen import auto_listen_toolkit
|
||||||
from app.utils.toolkit.abstract_toolkit import AbstractToolkit
|
from app.utils.toolkit.abstract_toolkit import AbstractToolkit
|
||||||
from app.service.task import process_task
|
from app.service.task import process_task
|
||||||
|
from utils import traceroot_wrapper as traceroot
|
||||||
|
|
||||||
|
logger = traceroot.get_logger("terminal_toolkit")
|
||||||
|
|
||||||
|
|
||||||
@auto_listen_toolkit(BaseTerminalToolkit)
|
@auto_listen_toolkit(BaseTerminalToolkit)
|
||||||
|
|
@ -37,11 +40,22 @@ class TerminalToolkit(BaseTerminalToolkit, AbstractToolkit):
|
||||||
self.agent_name = agent_name
|
self.agent_name = agent_name
|
||||||
if working_directory is None:
|
if working_directory is None:
|
||||||
working_directory = env("file_save_path", os.path.expanduser("~/.eigent/terminal/"))
|
working_directory = env("file_save_path", os.path.expanduser("~/.eigent/terminal/"))
|
||||||
|
|
||||||
|
logger.info("Initializing TerminalToolkit", extra={
|
||||||
|
"api_task_id": api_task_id,
|
||||||
|
"agent_name": self.agent_name,
|
||||||
|
"working_directory": working_directory,
|
||||||
|
"safe_mode": safe_mode,
|
||||||
|
"use_docker_backend": use_docker_backend
|
||||||
|
})
|
||||||
|
|
||||||
if TerminalToolkit._thread_pool is None:
|
if TerminalToolkit._thread_pool is None:
|
||||||
TerminalToolkit._thread_pool = ThreadPoolExecutor(
|
TerminalToolkit._thread_pool = ThreadPoolExecutor(
|
||||||
max_workers=1,
|
max_workers=1,
|
||||||
thread_name_prefix="terminal_toolkit"
|
thread_name_prefix="terminal_toolkit"
|
||||||
)
|
)
|
||||||
|
logger.debug("Created terminal toolkit thread pool")
|
||||||
|
|
||||||
super().__init__(
|
super().__init__(
|
||||||
timeout=timeout,
|
timeout=timeout,
|
||||||
working_directory=working_directory,
|
working_directory=working_directory,
|
||||||
|
|
@ -62,6 +76,11 @@ class TerminalToolkit(BaseTerminalToolkit, AbstractToolkit):
|
||||||
"""
|
"""
|
||||||
# Convert ANSI escape sequences to plain text
|
# Convert ANSI escape sequences to plain text
|
||||||
super()._write_to_log(log_file, content)
|
super()._write_to_log(log_file, content)
|
||||||
|
logger.debug("Terminal output logged", extra={
|
||||||
|
"api_task_id": self.api_task_id,
|
||||||
|
"log_file": log_file,
|
||||||
|
"content_length": len(content)
|
||||||
|
})
|
||||||
self._update_terminal_output(_to_plain(content))
|
self._update_terminal_output(_to_plain(content))
|
||||||
|
|
||||||
def _update_terminal_output(self, output: str):
|
def _update_terminal_output(self, output: str):
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ from app.service.task import (
|
||||||
Action,
|
Action,
|
||||||
ActionAssignTaskData,
|
ActionAssignTaskData,
|
||||||
ActionEndData,
|
ActionEndData,
|
||||||
ActionNewTaskStateData,
|
|
||||||
ActionTaskStateData,
|
ActionTaskStateData,
|
||||||
get_camel_task,
|
get_camel_task,
|
||||||
get_task_lock,
|
get_task_lock,
|
||||||
|
|
@ -45,6 +44,14 @@ class Workforce(BaseWorkforce):
|
||||||
use_structured_output_handler: bool = True,
|
use_structured_output_handler: bool = True,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.api_task_id = api_task_id
|
self.api_task_id = api_task_id
|
||||||
|
logger.info("Initializing workforce", extra={
|
||||||
|
"api_task_id": api_task_id,
|
||||||
|
"description": description[:100] + "..." if len(description) > 100 else description,
|
||||||
|
"children_count": len(children) if children else 0,
|
||||||
|
"graceful_shutdown_timeout": graceful_shutdown_timeout,
|
||||||
|
"share_memory": share_memory,
|
||||||
|
"use_structured_output_handler": use_structured_output_handler
|
||||||
|
})
|
||||||
super().__init__(
|
super().__init__(
|
||||||
description=description,
|
description=description,
|
||||||
children=children,
|
children=children,
|
||||||
|
|
@ -65,13 +72,20 @@ class Workforce(BaseWorkforce):
|
||||||
coordinator_context: Optional context ONLY for coordinator agent during decomposition.
|
coordinator_context: Optional context ONLY for coordinator agent during decomposition.
|
||||||
This context will NOT be passed to subtasks or worker agents.
|
This context will NOT be passed to subtasks or worker agents.
|
||||||
"""
|
"""
|
||||||
|
logger.info("Starting task decomposition", extra={
|
||||||
|
"api_task_id": self.api_task_id,
|
||||||
|
"task_id": task.id,
|
||||||
|
"task_content": task.content[:200] + "..." if len(task.content) > 200 else task.content,
|
||||||
|
"has_coordinator_context": bool(coordinator_context)
|
||||||
|
})
|
||||||
|
|
||||||
if not validate_task_content(task.content, task.id):
|
if not validate_task_content(task.content, task.id):
|
||||||
task.state = TaskState.FAILED
|
task.state = TaskState.FAILED
|
||||||
task.result = "Task failed: Invalid or empty content provided"
|
task.result = "Task failed: Invalid or empty content provided"
|
||||||
logger.warning(
|
logger.warning("Task rejected: Invalid or empty content", extra={
|
||||||
f"Task {task.id} rejected: Invalid or empty content. Content preview: '{task.content[:50]}...'"
|
"task_id": task.id,
|
||||||
)
|
"content_preview": task.content[:50] + "..." if len(task.content) > 50 else task.content
|
||||||
|
})
|
||||||
raise UserException(code.error, task.result)
|
raise UserException(code.error, task.result)
|
||||||
|
|
||||||
self.reset()
|
self.reset()
|
||||||
|
|
@ -81,11 +95,19 @@ class Workforce(BaseWorkforce):
|
||||||
task.state = TaskState.OPEN
|
task.state = TaskState.OPEN
|
||||||
|
|
||||||
subtasks = asyncio.run(self.handle_decompose_append_task(task))
|
subtasks = asyncio.run(self.handle_decompose_append_task(task))
|
||||||
|
logger.info("Task decomposition completed", extra={
|
||||||
|
"api_task_id": self.api_task_id,
|
||||||
|
"task_id": task.id,
|
||||||
|
"subtasks_count": len(subtasks)
|
||||||
|
})
|
||||||
return subtasks
|
return subtasks
|
||||||
|
|
||||||
async def eigent_start(self, subtasks: list[Task]):
|
async def eigent_start(self, subtasks: list[Task]):
|
||||||
"""start the workforce"""
|
"""start the workforce"""
|
||||||
logger.debug(f"start the workforce {subtasks=}")
|
logger.info("Starting workforce execution", extra={
|
||||||
|
"api_task_id": self.api_task_id,
|
||||||
|
"subtasks_count": len(subtasks)
|
||||||
|
})
|
||||||
self._pending_tasks.extendleft(reversed(subtasks))
|
self._pending_tasks.extendleft(reversed(subtasks))
|
||||||
# Save initial snapshot
|
# Save initial snapshot
|
||||||
self.save_snapshot("Initial task decomposition")
|
self.save_snapshot("Initial task decomposition")
|
||||||
|
|
@ -93,7 +115,10 @@ class Workforce(BaseWorkforce):
|
||||||
try:
|
try:
|
||||||
await self.start()
|
await self.start()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error in workforce execution: {e}")
|
logger.error("Error in workforce execution", extra={
|
||||||
|
"api_task_id": self.api_task_id,
|
||||||
|
"error": str(e)
|
||||||
|
}, exc_info=True)
|
||||||
self._state = WorkforceState.STOPPED
|
self._state = WorkforceState.STOPPED
|
||||||
raise
|
raise
|
||||||
finally:
|
finally:
|
||||||
|
|
@ -294,13 +319,6 @@ class Workforce(BaseWorkforce):
|
||||||
"failure_count": task.failure_count,
|
"failure_count": task.failure_count,
|
||||||
}
|
}
|
||||||
|
|
||||||
if self._task_is_new(task_data):
|
|
||||||
await task_lock.put_queue(
|
|
||||||
ActionNewTaskStateData(
|
|
||||||
data=task_data
|
|
||||||
)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
await task_lock.put_queue(
|
await task_lock.put_queue(
|
||||||
ActionTaskStateData(
|
ActionTaskStateData(
|
||||||
data=task_data
|
data=task_data
|
||||||
|
|
@ -339,36 +357,6 @@ class Workforce(BaseWorkforce):
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def _task_is_new(self, item:dict) -> bool:
|
|
||||||
# Validate the task state data object first
|
|
||||||
assert isinstance(item, dict)
|
|
||||||
task_id = item.get("task_id", "")
|
|
||||||
state = item.get("state", "")
|
|
||||||
result = item.get("result", "")
|
|
||||||
failure_count = item.get("failure_count", 0)
|
|
||||||
|
|
||||||
# Validate required fields
|
|
||||||
if not task_id:
|
|
||||||
logger.error("Missing task_id in task_state data")
|
|
||||||
return False
|
|
||||||
elif not state:
|
|
||||||
logger.error(f"Missing state in task_state data for task {task_id}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Ensure failure_count is an integer
|
|
||||||
try:
|
|
||||||
failure_count = int(failure_count)
|
|
||||||
except (ValueError, TypeError):
|
|
||||||
logger.error(f"Invalid failure_count in task_state data for task {task_id}: {failure_count}")
|
|
||||||
failure_count = 0 # Default to 0 if invalid
|
|
||||||
|
|
||||||
should_send_new_task_state = (
|
|
||||||
state == "FAILED" or
|
|
||||||
(failure_count == 0 and result.strip() == "")
|
|
||||||
)
|
|
||||||
|
|
||||||
return should_send_new_task_state
|
|
||||||
|
|
||||||
def stop(self) -> None:
|
def stop(self) -> None:
|
||||||
super().stop()
|
super().stop()
|
||||||
task_lock = get_task_lock(self.api_task_id)
|
task_lock = get_task_lock(self.api_task_id)
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,9 @@ exports.default = async function notarizing(context) {
|
||||||
|
|
||||||
// Validate required environment variables
|
// Validate required environment variables
|
||||||
if (!process.env.APPLE_ID || !process.env.APPLE_APP_SPECIFIC_PASSWORD || !process.env.APPLE_TEAM_ID) {
|
if (!process.env.APPLE_ID || !process.env.APPLE_APP_SPECIFIC_PASSWORD || !process.env.APPLE_TEAM_ID) {
|
||||||
console.error("Missing required environment variables for notarization");
|
console.warn("Missing Apple environment variables for notarization");
|
||||||
console.error("Required: APPLE_ID, APPLE_APP_SPECIFIC_PASSWORD, APPLE_TEAM_ID");
|
console.warn("Skipping notarization. Required: APPLE_ID, APPLE_APP_SPECIFIC_PASSWORD, APPLE_TEAM_ID");
|
||||||
throw new Error("Notarization failed: Missing required environment variables");
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return notarize({
|
return notarize({
|
||||||
|
|
|
||||||
|
|
@ -76,6 +76,7 @@ export async function downloadWithRedirects(url, destinationPath) {
|
||||||
|
|
||||||
// Check if file exists and has size > 0
|
// Check if file exists and has size > 0
|
||||||
try {
|
try {
|
||||||
|
|
||||||
if (fs.existsSync(destinationPath)) {
|
if (fs.existsSync(destinationPath)) {
|
||||||
const stats = fs.statSync(destinationPath)
|
const stats = fs.statSync(destinationPath)
|
||||||
if (stats.size === 0) {
|
if (stats.size === 0) {
|
||||||
|
|
@ -89,6 +90,7 @@ export async function downloadWithRedirects(url, destinationPath) {
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
safeReject(new Error(`Failed to verify download: ${err.message}`))
|
safeReject(new Error(`Failed to verify download: ${err.message}`))
|
||||||
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,14 @@
|
||||||
from sqlmodel import Session, create_engine
|
from sqlmodel import Session, create_engine
|
||||||
from app.component.environment import env, env_or_fail
|
from app.component.environment import env, env_or_fail
|
||||||
|
from utils import traceroot_wrapper as traceroot
|
||||||
|
|
||||||
|
logger = traceroot.get_logger("database")
|
||||||
|
|
||||||
|
logger.info("Initializing database engine", extra={
|
||||||
|
"database_url_prefix": env_or_fail("database_url")[:20] + "...",
|
||||||
|
"debug_mode": env("debug") == "on",
|
||||||
|
"pool_size": 36
|
||||||
|
})
|
||||||
|
|
||||||
engine = create_engine(
|
engine = create_engine(
|
||||||
env_or_fail("database_url"),
|
env_or_fail("database_url"),
|
||||||
|
|
@ -8,11 +16,19 @@ engine = create_engine(
|
||||||
pool_size=36,
|
pool_size=36,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
logger.info("Database engine initialized successfully")
|
||||||
|
|
||||||
|
|
||||||
def session_make():
|
def session_make():
|
||||||
return Session(engine)
|
logger.debug("Creating new database session")
|
||||||
|
session = Session(engine)
|
||||||
|
logger.debug("Database session created successfully")
|
||||||
|
return session
|
||||||
|
|
||||||
|
|
||||||
def session():
|
def session():
|
||||||
|
logger.debug("Creating database session context")
|
||||||
with Session(engine) as session:
|
with Session(engine) as session:
|
||||||
|
logger.debug("Database session context established")
|
||||||
yield session
|
yield session
|
||||||
|
logger.debug("Database session context closed")
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,13 @@ from fastapi import APIRouter, FastAPI
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
import importlib
|
import importlib
|
||||||
from typing import Any, overload
|
from typing import Any, overload
|
||||||
|
from utils import traceroot_wrapper as traceroot
|
||||||
|
|
||||||
|
logger = traceroot.get_logger("environment")
|
||||||
|
|
||||||
|
logger.info("Loading environment variables from .env file")
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
|
logger.info("Environment variables loaded successfully")
|
||||||
|
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
|
|
@ -23,20 +27,26 @@ def env(key: str, default: Any) -> Any: ...
|
||||||
|
|
||||||
|
|
||||||
def env(key: str, default=None):
|
def env(key: str, default=None):
|
||||||
return os.getenv(key, default)
|
value = os.getenv(key, default)
|
||||||
|
logger.debug("Environment variable accessed", extra={"key": key, "has_value": value is not None, "using_default": value == default})
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
def env_or_fail(key: str):
|
def env_or_fail(key: str):
|
||||||
value = env(key)
|
value = env(key)
|
||||||
if value is None:
|
if value is None:
|
||||||
|
logger.error("Required environment variable missing", extra={"key": key})
|
||||||
raise Exception("can't get env config value.")
|
raise Exception("can't get env config value.")
|
||||||
|
logger.debug("Required environment variable retrieved", extra={"key": key})
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
def env_not_empty(key: str):
|
def env_not_empty(key: str):
|
||||||
value = env(key)
|
value = env(key)
|
||||||
if not value:
|
if not value:
|
||||||
|
logger.error("Environment variable is empty", extra={"key": key})
|
||||||
raise Exception("env config value can't be empty.")
|
raise Exception("env config value can't be empty.")
|
||||||
|
logger.debug("Non-empty environment variable retrieved", extra={"key": key})
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -71,8 +81,14 @@ def auto_include_routers(api: FastAPI, prefix: str, directory: str):
|
||||||
:param prefix: 路由前缀
|
:param prefix: 路由前缀
|
||||||
:param directory: 要扫描的目录路径
|
:param directory: 要扫描的目录路径
|
||||||
"""
|
"""
|
||||||
|
logger.info("Starting automatic router registration", extra={
|
||||||
|
"prefix": prefix,
|
||||||
|
"directory": directory
|
||||||
|
})
|
||||||
|
|
||||||
# 将目录转换为绝对路径
|
# 将目录转换为绝对路径
|
||||||
dir_path = Path(directory).resolve()
|
dir_path = Path(directory).resolve()
|
||||||
|
router_count = 0
|
||||||
|
|
||||||
# 遍历目录下所有.py文件
|
# 遍历目录下所有.py文件
|
||||||
for root, _, files in os.walk(dir_path):
|
for root, _, files in os.walk(dir_path):
|
||||||
|
|
@ -81,12 +97,19 @@ def auto_include_routers(api: FastAPI, prefix: str, directory: str):
|
||||||
# 构造完整文件路径
|
# 构造完整文件路径
|
||||||
file_path = Path(root) / file_name
|
file_path = Path(root) / file_name
|
||||||
|
|
||||||
|
logger.debug("Processing controller file", extra={
|
||||||
|
"file_name": file_name,
|
||||||
|
"file_path": str(file_path)
|
||||||
|
})
|
||||||
|
|
||||||
# 生成模块名称
|
# 生成模块名称
|
||||||
module_name = file_path.stem
|
module_name = file_path.stem
|
||||||
|
|
||||||
|
try:
|
||||||
# 使用importlib加载模块
|
# 使用importlib加载模块
|
||||||
spec = importlib.util.spec_from_file_location(module_name, file_path)
|
spec = importlib.util.spec_from_file_location(module_name, file_path)
|
||||||
if spec is None or spec.loader is None:
|
if spec is None or spec.loader is None:
|
||||||
|
logger.warning("Failed to create module spec", extra={"file_path": str(file_path)})
|
||||||
continue
|
continue
|
||||||
module = importlib.util.module_from_spec(spec)
|
module = importlib.util.module_from_spec(spec)
|
||||||
spec.loader.exec_module(module)
|
spec.loader.exec_module(module)
|
||||||
|
|
@ -95,3 +118,23 @@ def auto_include_routers(api: FastAPI, prefix: str, directory: str):
|
||||||
router = getattr(module, "router", None)
|
router = getattr(module, "router", None)
|
||||||
if isinstance(router, APIRouter):
|
if isinstance(router, APIRouter):
|
||||||
api.include_router(router, prefix=prefix)
|
api.include_router(router, prefix=prefix)
|
||||||
|
router_count += 1
|
||||||
|
logger.debug("Router registered successfully", extra={
|
||||||
|
"module_name": module_name,
|
||||||
|
"prefix": prefix
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
logger.debug("No valid router found in module", extra={"module_name": module_name})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error("Failed to load controller module", extra={
|
||||||
|
"module_name": module_name,
|
||||||
|
"file_path": str(file_path),
|
||||||
|
"error": str(e)
|
||||||
|
}, exc_info=True)
|
||||||
|
|
||||||
|
logger.info("Automatic router registration completed", extra={
|
||||||
|
"prefix": prefix,
|
||||||
|
"directory": directory,
|
||||||
|
"routers_registered": router_count
|
||||||
|
})
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,9 @@ from fastapi_babel import _
|
||||||
from app.exception.exception import UserException
|
from app.exception.exception import UserException
|
||||||
from app.component.database import engine
|
from app.component.database import engine
|
||||||
from convert_case import snake_case
|
from convert_case import snake_case
|
||||||
|
from utils import traceroot_wrapper as traceroot
|
||||||
|
|
||||||
|
logger = traceroot.get_logger("abstract_model")
|
||||||
|
|
||||||
|
|
||||||
class AbstractModel(SQLModel):
|
class AbstractModel(SQLModel):
|
||||||
|
|
@ -27,6 +30,13 @@ class AbstractModel(SQLModel):
|
||||||
options: ExecutableOption | list[ExecutableOption] | None = None,
|
options: ExecutableOption | list[ExecutableOption] | None = None,
|
||||||
s: Session,
|
s: Session,
|
||||||
):
|
):
|
||||||
|
logger.debug("Executing query by conditions", extra={
|
||||||
|
"model_class": cls.__name__,
|
||||||
|
"has_order_by": order_by is not None,
|
||||||
|
"limit": limit,
|
||||||
|
"offset": offset,
|
||||||
|
"has_options": options is not None
|
||||||
|
})
|
||||||
stmt = select(cls).where(*whereclause)
|
stmt = select(cls).where(*whereclause)
|
||||||
if order_by is not None:
|
if order_by is not None:
|
||||||
stmt = stmt.order_by(order_by)
|
stmt = stmt.order_by(order_by)
|
||||||
|
|
@ -44,8 +54,15 @@ class AbstractModel(SQLModel):
|
||||||
*whereclause: ColumnExpressionArgument[bool] | bool,
|
*whereclause: ColumnExpressionArgument[bool] | bool,
|
||||||
s: Session,
|
s: Session,
|
||||||
) -> bool:
|
) -> bool:
|
||||||
|
logger.debug("Checking if record exists", extra={"model_class": cls.__name__})
|
||||||
res = s.exec(select(func.count("*")).where(*whereclause)).first()
|
res = s.exec(select(func.count("*")).where(*whereclause)).first()
|
||||||
return res is not None and res > 0
|
result = res is not None and res > 0
|
||||||
|
logger.debug("Record existence check result", extra={
|
||||||
|
"model_class": cls.__name__,
|
||||||
|
"exists": result,
|
||||||
|
"count": res
|
||||||
|
})
|
||||||
|
return result
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def count(
|
def count(
|
||||||
|
|
@ -71,11 +88,24 @@ class AbstractModel(SQLModel):
|
||||||
*whereclause: ColumnExpressionArgument[bool],
|
*whereclause: ColumnExpressionArgument[bool],
|
||||||
s: Session,
|
s: Session,
|
||||||
):
|
):
|
||||||
|
logger.info("Deleting records by conditions", extra={"model_class": cls.__name__})
|
||||||
stmt = delete(cls).where(*whereclause)
|
stmt = delete(cls).where(*whereclause)
|
||||||
s.connection().execute(stmt)
|
result = s.connection().execute(stmt)
|
||||||
s.commit()
|
s.commit()
|
||||||
|
logger.info("Records deleted", extra={
|
||||||
|
"model_class": cls.__name__,
|
||||||
|
"rows_affected": result.rowcount
|
||||||
|
})
|
||||||
|
|
||||||
def save(self, s: Session | None = None):
|
def save(self, s: Session | None = None):
|
||||||
|
model_id = getattr(self, 'id', None)
|
||||||
|
is_new = model_id is None
|
||||||
|
logger.info("Saving model", extra={
|
||||||
|
"model_class": self.__class__.__name__,
|
||||||
|
"model_id": model_id,
|
||||||
|
"is_new_record": is_new
|
||||||
|
})
|
||||||
|
|
||||||
if s is None:
|
if s is None:
|
||||||
with Session(engine, expire_on_commit=False) as s:
|
with Session(engine, expire_on_commit=False) as s:
|
||||||
s.add(self)
|
s.add(self)
|
||||||
|
|
@ -84,7 +114,22 @@ class AbstractModel(SQLModel):
|
||||||
s.add(self)
|
s.add(self)
|
||||||
s.commit()
|
s.commit()
|
||||||
|
|
||||||
|
logger.info("Model saved successfully", extra={
|
||||||
|
"model_class": self.__class__.__name__,
|
||||||
|
"model_id": getattr(self, 'id', None),
|
||||||
|
"was_new_record": is_new
|
||||||
|
})
|
||||||
|
|
||||||
def delete(self, s: Session):
|
def delete(self, s: Session):
|
||||||
|
model_id = getattr(self, 'id', None)
|
||||||
|
is_soft_delete = isinstance(self, DefaultTimes)
|
||||||
|
|
||||||
|
logger.info("Deleting model", extra={
|
||||||
|
"model_class": self.__class__.__name__,
|
||||||
|
"model_id": model_id,
|
||||||
|
"is_soft_delete": is_soft_delete
|
||||||
|
})
|
||||||
|
|
||||||
if isinstance(self, DefaultTimes):
|
if isinstance(self, DefaultTimes):
|
||||||
self.deleted_at = datetime.now()
|
self.deleted_at = datetime.now()
|
||||||
self.save(s)
|
self.save(s)
|
||||||
|
|
@ -92,6 +137,12 @@ class AbstractModel(SQLModel):
|
||||||
s.delete(self)
|
s.delete(self)
|
||||||
s.commit()
|
s.commit()
|
||||||
|
|
||||||
|
logger.info("Model deleted successfully", extra={
|
||||||
|
"model_class": self.__class__.__name__,
|
||||||
|
"model_id": model_id,
|
||||||
|
"was_soft_delete": is_soft_delete
|
||||||
|
})
|
||||||
|
|
||||||
def update_fields(self, update_dict: dict):
|
def update_fields(self, update_dict: dict):
|
||||||
for k, v in update_dict.items():
|
for k, v in update_dict.items():
|
||||||
setattr(self, k, v)
|
setattr(self, k, v)
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,9 @@ export interface FloatingActionProps {
|
||||||
/** Current task status */
|
/** Current task status */
|
||||||
status: "running" | "pause" | "pending" | "finished";
|
status: "running" | "pause" | "pending" | "finished";
|
||||||
/** Callback when pause button is clicked */
|
/** Callback when pause button is clicked */
|
||||||
onPause?: () => void;
|
// onPause?: () => void; // Commented out - temporary not needed
|
||||||
/** Callback when resume button is clicked */
|
/** Callback when resume button is clicked */
|
||||||
onResume?: () => void;
|
// onResume?: () => void; // Commented out - temporary not needed
|
||||||
/** Callback when skip to next is clicked */
|
/** Callback when skip to next is clicked */
|
||||||
onSkip?: () => void;
|
onSkip?: () => void;
|
||||||
/** Loading state for pause/resume actions */
|
/** Loading state for pause/resume actions */
|
||||||
|
|
@ -19,14 +19,14 @@ export interface FloatingActionProps {
|
||||||
|
|
||||||
export const FloatingAction = ({
|
export const FloatingAction = ({
|
||||||
status,
|
status,
|
||||||
onPause,
|
// onPause, // Commented out - temporary not needed
|
||||||
onResume,
|
// onResume, // Commented out - temporary not needed
|
||||||
onSkip,
|
onSkip,
|
||||||
loading = false,
|
loading = false,
|
||||||
className,
|
className,
|
||||||
}: FloatingActionProps) => {
|
}: FloatingActionProps) => {
|
||||||
// Only show when task is running or paused
|
// Only show when task is running (removed pause state)
|
||||||
if (status !== "running" && status !== "pause") {
|
if (status !== "running") {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -38,6 +38,18 @@ export const FloatingAction = ({
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="pointer-events-auto flex items-center gap-2 bg-bg-surface-primary/95 backdrop-blur-md rounded-full p-1 shadow-[0px_4px_16px_rgba(0,0,0,0.12)] border border-border-default">
|
<div className="pointer-events-auto flex items-center gap-2 bg-bg-surface-primary/95 backdrop-blur-md rounded-full p-1 shadow-[0px_4px_16px_rgba(0,0,0,0.12)] border border-border-default">
|
||||||
|
{/* Always show Stop Task button when running (removed pause/resume logic) */}
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={onSkip}
|
||||||
|
disabled={loading}
|
||||||
|
className="gap-1.5 rounded-full"
|
||||||
|
>
|
||||||
|
<span className="text-sm font-semibold">Stop Task</span>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
{/* Commented out pause/resume functionality
|
||||||
{status === "running" ? (
|
{status === "running" ? (
|
||||||
// State 1: Running - Show Pause button
|
// State 1: Running - Show Pause button
|
||||||
<Button
|
<Button
|
||||||
|
|
@ -73,6 +85,7 @@ export const FloatingAction = ({
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
*/}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -5,14 +5,14 @@ import useChatStoreAdapter from '@/hooks/useChatStoreAdapter';
|
||||||
|
|
||||||
interface ProjectChatContainerProps {
|
interface ProjectChatContainerProps {
|
||||||
className?: string;
|
className?: string;
|
||||||
onPauseResume: () => void;
|
// onPauseResume: () => void; // Commented out - temporary not needed
|
||||||
onSkip: () => void;
|
onSkip: () => void;
|
||||||
isPauseResumeLoading: boolean;
|
isPauseResumeLoading: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ProjectChatContainer: React.FC<ProjectChatContainerProps> = ({
|
export const ProjectChatContainer: React.FC<ProjectChatContainerProps> = ({
|
||||||
className = "",
|
className = "",
|
||||||
onPauseResume,
|
// onPauseResume, // Commented out - temporary not needed
|
||||||
onSkip,
|
onSkip,
|
||||||
isPauseResumeLoading
|
isPauseResumeLoading
|
||||||
}) => {
|
}) => {
|
||||||
|
|
@ -165,7 +165,7 @@ export const ProjectChatContainer: React.FC<ProjectChatContainerProps> = ({
|
||||||
chatStore={chatStore}
|
chatStore={chatStore}
|
||||||
activeQueryId={activeQueryId}
|
activeQueryId={activeQueryId}
|
||||||
onQueryActive={setActiveQueryId}
|
onQueryActive={setActiveQueryId}
|
||||||
onPauseResume={onPauseResume}
|
// onPauseResume={onPauseResume} // Commented out - temporary not needed
|
||||||
onSkip={onSkip}
|
onSkip={onSkip}
|
||||||
isPauseResumeLoading={isPauseResumeLoading}
|
isPauseResumeLoading={isPauseResumeLoading}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ interface ProjectSectionProps {
|
||||||
chatStore: VanillaChatStore;
|
chatStore: VanillaChatStore;
|
||||||
activeQueryId: string | null;
|
activeQueryId: string | null;
|
||||||
onQueryActive: (queryId: string | null) => void;
|
onQueryActive: (queryId: string | null) => void;
|
||||||
onPauseResume: () => void;
|
// onPauseResume: () => void; // Commented out - temporary not needed
|
||||||
onSkip: () => void;
|
onSkip: () => void;
|
||||||
isPauseResumeLoading: boolean;
|
isPauseResumeLoading: boolean;
|
||||||
}
|
}
|
||||||
|
|
@ -19,7 +19,7 @@ export const ProjectSection = React.forwardRef<HTMLDivElement, ProjectSectionPro
|
||||||
chatStore,
|
chatStore,
|
||||||
activeQueryId,
|
activeQueryId,
|
||||||
onQueryActive,
|
onQueryActive,
|
||||||
onPauseResume,
|
// onPauseResume, // Commented out - temporary not needed
|
||||||
onSkip,
|
onSkip,
|
||||||
isPauseResumeLoading
|
isPauseResumeLoading
|
||||||
}, ref) => {
|
}, ref) => {
|
||||||
|
|
@ -64,8 +64,8 @@ export const ProjectSection = React.forwardRef<HTMLDivElement, ProjectSectionPro
|
||||||
{activeTaskId && (
|
{activeTaskId && (
|
||||||
<FloatingAction
|
<FloatingAction
|
||||||
status={task.status}
|
status={task.status}
|
||||||
onPause={onPauseResume}
|
// onPause={onPauseResume} // Commented out - temporary not needed
|
||||||
onResume={onPauseResume}
|
// onResume={onPauseResume} // Commented out - temporary not needed
|
||||||
onSkip={onSkip}
|
onSkip={onSkip}
|
||||||
loading={isPauseResumeLoading}
|
loading={isPauseResumeLoading}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -103,7 +103,8 @@ export default function ChatBox(): JSX.Element {
|
||||||
const requiresHumanReply = Boolean(task?.activeAsk);
|
const requiresHumanReply = Boolean(task?.activeAsk);
|
||||||
const isTaskInProgress = ["running", "pause"].includes(task?.status || "");
|
const isTaskInProgress = ["running", "pause"].includes(task?.status || "");
|
||||||
const isTaskBusy = (
|
const isTaskBusy = (
|
||||||
(task.status === 'running' && !task.hasMessages) || task.status === 'pause' ||
|
// running or paused counts as busy
|
||||||
|
(task.status === 'running' && task.hasMessages) || task.status === 'pause' ||
|
||||||
// splitting phase: has to_sub_tasks not confirmed OR skeleton computing
|
// splitting phase: has to_sub_tasks not confirmed OR skeleton computing
|
||||||
task.messages.some(m => m.step === 'to_sub_tasks' && !m.isConfirm) ||
|
task.messages.some(m => m.step === 'to_sub_tasks' && !m.isConfirm) ||
|
||||||
((!task.messages.find(m => m.step === 'to_sub_tasks') && !task.hasWaitComfirm && task.messages.length > 0) || task.isTakeControl) ||
|
((!task.messages.find(m => m.step === 'to_sub_tasks') && !task.hasWaitComfirm && task.messages.length > 0) || task.isTakeControl) ||
|
||||||
|
|
@ -175,11 +176,14 @@ export default function ChatBox(): JSX.Element {
|
||||||
const hasComplexTask = chatStore.tasks[_taskId as string].messages.some(
|
const hasComplexTask = chatStore.tasks[_taskId as string].messages.some(
|
||||||
m => m.step === "to_sub_tasks"
|
m => m.step === "to_sub_tasks"
|
||||||
);
|
);
|
||||||
|
const hasErrorMessage = chatStore.tasks[_taskId as string].messages.some(
|
||||||
|
m => m.role === "agent" && m.content.startsWith("❌ **Error**:")
|
||||||
|
);
|
||||||
|
|
||||||
// Only start a new task if: pending, no messages processed yet
|
// Only start a new task if: pending, no messages processed yet
|
||||||
// OR while or after replaying a project
|
// OR while or after replaying a project
|
||||||
if ((chatStore.tasks[_taskId as string].status === "pending" && !hasSimpleResponse && !hasComplexTask && !isFinished)
|
if ((chatStore.tasks[_taskId as string].status === "pending" && !hasSimpleResponse && !hasComplexTask && !isFinished)
|
||||||
|| chatStore.tasks[_taskId].type === "replay") {
|
|| chatStore.tasks[_taskId].type === "replay" || hasErrorMessage) {
|
||||||
setMessage("");
|
setMessage("");
|
||||||
// Pass the message content to startTask instead of adding it to current chatStore
|
// Pass the message content to startTask instead of adding it to current chatStore
|
||||||
const attachesToSend = JSON.parse(JSON.stringify(chatStore.tasks[_taskId]?.attaches)) || [];
|
const attachesToSend = JSON.parse(JSON.stringify(chatStore.tasks[_taskId]?.attaches)) || [];
|
||||||
|
|
@ -423,7 +427,10 @@ export default function ChatBox(): JSX.Element {
|
||||||
chatStore.setStatus(taskId, 'finished');
|
chatStore.setStatus(taskId, 'finished');
|
||||||
chatStore.setIsPending(taskId, false);
|
chatStore.setIsPending(taskId, false);
|
||||||
|
|
||||||
toast.success("Task skipped successfully", {
|
// toast.success("Task skipped successfully", {
|
||||||
|
// closeButton: true,
|
||||||
|
// });
|
||||||
|
toast.success("Task stopped successfully", {
|
||||||
closeButton: true,
|
closeButton: true,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -671,7 +678,7 @@ export default function ChatBox(): JSX.Element {
|
||||||
<div className="w-full h-full flex-1 flex flex-col">
|
<div className="w-full h-full flex-1 flex flex-col">
|
||||||
{/* New Project Chat Container */}
|
{/* New Project Chat Container */}
|
||||||
<ProjectChatContainer
|
<ProjectChatContainer
|
||||||
onPauseResume={handlePauseResume}
|
// onPauseResume={handlePauseResume} // Commented out - temporary not needed
|
||||||
onSkip={handleSkip}
|
onSkip={handleSkip}
|
||||||
isPauseResumeLoading={isPauseResumeLoading}
|
isPauseResumeLoading={isPauseResumeLoading}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -145,16 +145,16 @@ function HeaderWin() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//TODO: Mark ChatStore details as completed
|
||||||
const handleEndProject = async () => {
|
const handleEndProject = async () => {
|
||||||
const taskId = chatStore.activeTaskId;
|
const taskId = chatStore.activeTaskId;
|
||||||
const currentProjectId = projectStore.activeProjectId;
|
const projectId = projectStore.activeProjectId;
|
||||||
|
|
||||||
if (!taskId) {
|
if (!taskId) {
|
||||||
toast.error(t("layout.no-active-project-to-end"));
|
toast.error(t("layout.no-active-project-to-end"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const projectId = projectStore.activeProjectId;
|
|
||||||
const historyId = projectId ? projectStore.getHistoryId(projectId) : null;
|
const historyId = projectId ? projectStore.getHistoryId(projectId) : null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
@ -167,26 +167,26 @@ function HeaderWin() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete task from backend if it exists
|
// Stop Workforce
|
||||||
try {
|
try {
|
||||||
await fetchDelete(`/chat/${taskId}`);
|
await fetchDelete(`/chat/${projectId}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("Task may not exist on backend:", error);
|
console.log("Task may not exist on backend:", error);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete from history using historyId
|
// Delete from history using historyId
|
||||||
if (historyId) {
|
if (historyId && task.status !== "finished") {
|
||||||
try {
|
try {
|
||||||
await proxyFetchDelete(`/api/chat/history/${historyId}`);
|
await proxyFetchDelete(`/api/chat/history/${historyId}`);
|
||||||
|
// Remove from local store
|
||||||
|
chatStore.removeTask(taskId);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("History may not exist:", error);
|
console.log("History may not exist:", error);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.warn("No historyId found for project, skipping history deletion");
|
console.warn("No historyId found for project or task finished, skipping history deletion");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove from local store
|
|
||||||
chatStore.removeTask(taskId);
|
|
||||||
|
|
||||||
// Create a completely new project instead of just a new task
|
// Create a completely new project instead of just a new task
|
||||||
// This ensures we start fresh without any residual state
|
// This ensures we start fresh without any residual state
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ const useChatStoreAdapter = ():{
|
||||||
}
|
}
|
||||||
|
|
||||||
// Subscribe to store changes
|
// Subscribe to store changes
|
||||||
const unsubscribe = activeChatStore.subscribe((state) => {
|
const unsubscribe = activeChatStore.subscribe((state: ChatStore) => {
|
||||||
setChatState(state);
|
setChatState(state);
|
||||||
});
|
});
|
||||||
// Set initial state
|
// Set initial state
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,7 @@
|
||||||
"agent-tool": "أداة الوكيل",
|
"agent-tool": "أداة الوكيل",
|
||||||
"agent-tool-tooltip": "اختر الأدوات والتكاملات لوكيلك",
|
"agent-tool-tooltip": "اختر الأدوات والتكاملات لوكيلك",
|
||||||
"resume": "استئناف",
|
"resume": "استئناف",
|
||||||
|
"stop-task":"إيقاف المهمة",
|
||||||
"next-task": "المهمة التالية",
|
"next-task": "المهمة التالية",
|
||||||
"task-splitting": "تقسيم المهام",
|
"task-splitting": "تقسيم المهام",
|
||||||
"task-running": "تشغيل المهام",
|
"task-running": "تشغيل المهام",
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,7 @@
|
||||||
"agent-tool": "Agent-Tool",
|
"agent-tool": "Agent-Tool",
|
||||||
"agent-tool-tooltip": "Wählen Sie Tools und Integrationen für Ihren Agenten",
|
"agent-tool-tooltip": "Wählen Sie Tools und Integrationen für Ihren Agenten",
|
||||||
"resume": "Fortsetzen",
|
"resume": "Fortsetzen",
|
||||||
|
"stop-task": "Aufgabe stoppen",
|
||||||
"next-task": "Nächste Aufgabe",
|
"next-task": "Nächste Aufgabe",
|
||||||
"task-splitting": "Aufgabenteilung",
|
"task-splitting": "Aufgabenteilung",
|
||||||
"task-running": "Aufgabe wird ausgeführt",
|
"task-running": "Aufgabe wird ausgeführt",
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,7 @@
|
||||||
"agent-tool": "Agent Tool",
|
"agent-tool": "Agent Tool",
|
||||||
"agent-tool-tooltip": "Select tools and integrations for your agent",
|
"agent-tool-tooltip": "Select tools and integrations for your agent",
|
||||||
"resume": "Resume",
|
"resume": "Resume",
|
||||||
|
"stop-task": "Stop Task",
|
||||||
"next-task": "Next Task",
|
"next-task": "Next Task",
|
||||||
"task-splitting": "Task Splitting",
|
"task-splitting": "Task Splitting",
|
||||||
"task-running": "Task Running",
|
"task-running": "Task Running",
|
||||||
|
|
@ -114,6 +115,7 @@
|
||||||
"project-ended-successfully": "Project ended successfully",
|
"project-ended-successfully": "Project ended successfully",
|
||||||
"failed-to-end-project": "Failed to end project",
|
"failed-to-end-project": "Failed to end project",
|
||||||
"message-queued": "Message queued. It will be processed when the current task finishes.",
|
"message-queued": "Message queued. It will be processed when the current task finishes.",
|
||||||
|
"task-stopped-successfully": "Task stopped successfully",
|
||||||
"task-skipped-successfully": "Task skipped successfully",
|
"task-skipped-successfully": "Task skipped successfully",
|
||||||
"failed-to-skip-task": "Failed to skip task",
|
"failed-to-skip-task": "Failed to skip task",
|
||||||
"no-reply-received-task-continue": "No reply received, task continue",
|
"no-reply-received-task-continue": "No reply received, task continue",
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,7 @@
|
||||||
"agent-tool": "Herramienta de Agente",
|
"agent-tool": "Herramienta de Agente",
|
||||||
"agent-tool-tooltip": "Selecciona herramientas e integraciones para tu agente",
|
"agent-tool-tooltip": "Selecciona herramientas e integraciones para tu agente",
|
||||||
"resume": "Reanudar",
|
"resume": "Reanudar",
|
||||||
|
"stop-task": "Detener Tarea",
|
||||||
"next-task": "Siguiente Tarea",
|
"next-task": "Siguiente Tarea",
|
||||||
"task-splitting": "División de Tareas",
|
"task-splitting": "División de Tareas",
|
||||||
"task-running": "Ejecutando Tarea",
|
"task-running": "Ejecutando Tarea",
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,7 @@
|
||||||
"agent-tool": "Outil Agent",
|
"agent-tool": "Outil Agent",
|
||||||
"agent-tool-tooltip": "Sélectionnez les outils et intégrations pour votre agent",
|
"agent-tool-tooltip": "Sélectionnez les outils et intégrations pour votre agent",
|
||||||
"resume": "Reprendre",
|
"resume": "Reprendre",
|
||||||
|
"stop-task": "Arrêter la Tâche",
|
||||||
"next-task": "Tâche suivante",
|
"next-task": "Tâche suivante",
|
||||||
"task-splitting": "Division des Tâches",
|
"task-splitting": "Division des Tâches",
|
||||||
"task-running": "Exécution des Tâches",
|
"task-running": "Exécution des Tâches",
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,7 @@
|
||||||
"agent-tool": "Strumento Agente",
|
"agent-tool": "Strumento Agente",
|
||||||
"agent-tool-tooltip": "Seleziona strumenti e integrazioni per il tuo agente",
|
"agent-tool-tooltip": "Seleziona strumenti e integrazioni per il tuo agente",
|
||||||
"resume": "Riprendi",
|
"resume": "Riprendi",
|
||||||
|
"stop-task": "Ferma Attività",
|
||||||
"next-task": "Prossima Attività",
|
"next-task": "Prossima Attività",
|
||||||
"task-splitting": "Divisione Attività",
|
"task-splitting": "Divisione Attività",
|
||||||
"task-running": "Esecuzione Attività",
|
"task-running": "Esecuzione Attività",
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,7 @@
|
||||||
"agent-tool": "エージェントツール",
|
"agent-tool": "エージェントツール",
|
||||||
"agent-tool-tooltip": "エージェントのツールと統合を選択",
|
"agent-tool-tooltip": "エージェントのツールと統合を選択",
|
||||||
"resume": "再開",
|
"resume": "再開",
|
||||||
|
"stop-task": "タスクを停止",
|
||||||
"next-task": "次のタスク",
|
"next-task": "次のタスク",
|
||||||
"task-splitting": "タスク分割",
|
"task-splitting": "タスク分割",
|
||||||
"task-running": "タスク実行中",
|
"task-running": "タスク実行中",
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,7 @@
|
||||||
"agent-tool": "에이전트 도구",
|
"agent-tool": "에이전트 도구",
|
||||||
"agent-tool-tooltip": "에이전트를 위한 도구 및 통합 선택",
|
"agent-tool-tooltip": "에이전트를 위한 도구 및 통합 선택",
|
||||||
"resume": "재개",
|
"resume": "재개",
|
||||||
|
"stop-task": "작업 중지",
|
||||||
"next-task": "다음 작업",
|
"next-task": "다음 작업",
|
||||||
"task-splitting": "작업 분할",
|
"task-splitting": "작업 분할",
|
||||||
"task-running": "작업 실행",
|
"task-running": "작업 실행",
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,7 @@
|
||||||
"agent-tool": "Инструмент агента",
|
"agent-tool": "Инструмент агента",
|
||||||
"agent-tool-tooltip": "Выберите инструменты и интеграции для вашего агента",
|
"agent-tool-tooltip": "Выберите инструменты и интеграции для вашего агента",
|
||||||
"resume": "Возобновить",
|
"resume": "Возобновить",
|
||||||
|
"stop-task": "Остановить задачу",
|
||||||
"next-task": "Следующая задача",
|
"next-task": "Следующая задача",
|
||||||
"task-splitting": "Разделение задачи",
|
"task-splitting": "Разделение задачи",
|
||||||
"task-running": "Выполнение задачи",
|
"task-running": "Выполнение задачи",
|
||||||
|
|
|
||||||
|
|
@ -73,6 +73,7 @@
|
||||||
"agent-tool": "智能体工具",
|
"agent-tool": "智能体工具",
|
||||||
"agent-tool-tooltip": "为您的智能体选择工具和集成",
|
"agent-tool-tooltip": "为您的智能体选择工具和集成",
|
||||||
"resume": "恢复",
|
"resume": "恢复",
|
||||||
|
"stop-task": "停止任务",
|
||||||
"next-task": "下一个任务",
|
"next-task": "下一个任务",
|
||||||
"task-splitting": "任务拆分",
|
"task-splitting": "任务拆分",
|
||||||
"task-running": "任务运行",
|
"task-running": "任务运行",
|
||||||
|
|
|
||||||
|
|
@ -74,6 +74,7 @@
|
||||||
"agent-tool": "智能體工具",
|
"agent-tool": "智能體工具",
|
||||||
"agent-tool-tooltip": "為您的智能體選擇工具和整合",
|
"agent-tool-tooltip": "為您的智能體選擇工具和整合",
|
||||||
"resume": "恢復",
|
"resume": "恢復",
|
||||||
|
"stop-task": "停止任務",
|
||||||
"next-task": "下一個任務",
|
"next-task": "下一個任務",
|
||||||
"task-splitting": "任務拆分",
|
"task-splitting": "任務拆分",
|
||||||
"task-running": "任務執行",
|
"task-running": "任務執行",
|
||||||
|
|
|
||||||
|
|
@ -105,6 +105,7 @@ export interface ChatStore {
|
||||||
|
|
||||||
export type VanillaChatStore = {
|
export type VanillaChatStore = {
|
||||||
getState: () => ChatStore;
|
getState: () => ChatStore;
|
||||||
|
subscribe: (listener: (state: ChatStore) => void) => () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -463,7 +464,27 @@ const chatStore = (initial?: Partial<ChatStore>) => createStore<ChatStore>()(
|
||||||
}) : undefined,
|
}) : undefined,
|
||||||
|
|
||||||
async onmessage(event: any) {
|
async onmessage(event: any) {
|
||||||
const agentMessages: AgentMessage = JSON.parse(event.data);
|
let agentMessages: AgentMessage;
|
||||||
|
|
||||||
|
try {
|
||||||
|
agentMessages = JSON.parse(event.data);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to parse SSE message:', error);
|
||||||
|
console.error('Raw event.data:', event.data);
|
||||||
|
|
||||||
|
// Create error task to notify user
|
||||||
|
const currentStore = getCurrentChatStore();
|
||||||
|
const newTaskId = currentStore.create();
|
||||||
|
currentStore.setActiveTaskId(newTaskId);
|
||||||
|
currentStore.setHasWaitComfirm(newTaskId, true);
|
||||||
|
currentStore.addMessages(newTaskId, {
|
||||||
|
id: generateUniqueId(),
|
||||||
|
role: "agent",
|
||||||
|
content: `**System Error**: Failed to parse server message. The connection may be unstable.\n\nPlease try again or contact support if this persists.`,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
console.log("agentMessages", agentMessages);
|
console.log("agentMessages", agentMessages);
|
||||||
const agentNameMap = {
|
const agentNameMap = {
|
||||||
developer_agent: "Developer Agent",
|
developer_agent: "Developer Agent",
|
||||||
|
|
@ -1048,7 +1069,15 @@ const chatStore = (initial?: Partial<ChatStore>) => createStore<ChatStore>()(
|
||||||
addWebViewUrl(currentTaskId, agentMessages.data.message as string, agentMessages.data.process_task_id as string)
|
addWebViewUrl(currentTaskId, agentMessages.data.message as string, agentMessages.data.process_task_id as string)
|
||||||
}
|
}
|
||||||
if (agentMessages.data.method_name === 'browser_navigate' && agentMessages.data.message?.startsWith('{"url"')) {
|
if (agentMessages.data.method_name === 'browser_navigate' && agentMessages.data.message?.startsWith('{"url"')) {
|
||||||
addWebViewUrl(currentTaskId, JSON.parse(agentMessages.data.message)?.url as string, agentMessages.data.process_task_id as string)
|
try {
|
||||||
|
const urlData = JSON.parse(agentMessages.data.message);
|
||||||
|
if (urlData?.url) {
|
||||||
|
addWebViewUrl(currentTaskId, urlData.url as string, agentMessages.data.process_task_id as string)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to parse browser_navigate URL:', error);
|
||||||
|
console.error('Raw message:', agentMessages.data.message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let taskRunning = [...tasks[currentTaskId].taskRunning]
|
let taskRunning = [...tasks[currentTaskId].taskRunning]
|
||||||
|
|
||||||
|
|
@ -1101,7 +1130,7 @@ const chatStore = (initial?: Partial<ChatStore>) => createStore<ChatStore>()(
|
||||||
return toolkit.toolkitName === agentMessages.data.toolkit_name && toolkit.toolkitMethods === agentMessages.data.method_name && toolkit.toolkitStatus === 'running'
|
return toolkit.toolkitName === agentMessages.data.toolkit_name && toolkit.toolkitMethods === agentMessages.data.method_name && toolkit.toolkitStatus === 'running'
|
||||||
})
|
})
|
||||||
|
|
||||||
if (task.toolkits && index !== -1) {
|
if (task.toolkits && index !== -1 && index !== undefined) {
|
||||||
task.toolkits[index].message += '\n' + message.data.message as string
|
task.toolkits[index].message += '\n' + message.data.message as string
|
||||||
task.toolkits[index].toolkitStatus = "completed"
|
task.toolkits[index].toolkitStatus = "completed"
|
||||||
}
|
}
|
||||||
|
|
@ -1206,24 +1235,86 @@ const chatStore = (initial?: Partial<ChatStore>) => createStore<ChatStore>()(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (agentMessages.step === "error") {
|
if (agentMessages.step === "error") {
|
||||||
console.error('Model error:', agentMessages.data)
|
try {
|
||||||
const errorMessage = agentMessages.data.message || 'An error occurred while processing your request';
|
console.error('Model error:', agentMessages.data);
|
||||||
|
|
||||||
// Create a new task to avoid "Task already exists" error
|
// Validate that agentMessages.data exists before processing
|
||||||
// and completely reset the interface
|
if (agentMessages.data === undefined || agentMessages.data === null) {
|
||||||
const newTaskId = create();
|
throw new Error('Invalid error message format: missing data');
|
||||||
// Prevent showing task skeleton after an error occurs
|
}
|
||||||
setActiveTaskId(newTaskId);
|
|
||||||
setHasWaitComfirm(newTaskId, true);
|
|
||||||
|
|
||||||
// Add error message to the new clean task
|
// Safely extract error message with fallback chain
|
||||||
addMessages(newTaskId, {
|
const errorMessage = agentMessages.data?.message ||
|
||||||
|
(typeof agentMessages.data === 'string' ? agentMessages.data : null) ||
|
||||||
|
'An error occurred while processing your request';
|
||||||
|
|
||||||
|
// Mark all incomplete tasks as failed
|
||||||
|
let taskRunning = [...tasks[currentTaskId].taskRunning];
|
||||||
|
let taskAssigning = [...tasks[currentTaskId].taskAssigning];
|
||||||
|
|
||||||
|
// Update taskRunning - mark non-completed tasks as failed
|
||||||
|
taskRunning = taskRunning.map((task) => {
|
||||||
|
if (task.status !== "completed" && task.status !== "failed") {
|
||||||
|
task.status = "failed";
|
||||||
|
}
|
||||||
|
return task;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update taskAssigning - mark non-completed tasks as failed
|
||||||
|
taskAssigning = taskAssigning.map((agent) => {
|
||||||
|
agent.tasks = agent.tasks.map((task) => {
|
||||||
|
if (task.status !== "completed" && task.status !== "failed") {
|
||||||
|
task.status = "failed";
|
||||||
|
}
|
||||||
|
return task;
|
||||||
|
});
|
||||||
|
return agent;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Apply the updates
|
||||||
|
setTaskRunning(currentTaskId, taskRunning);
|
||||||
|
setTaskAssigning(currentTaskId, taskAssigning);
|
||||||
|
|
||||||
|
// Complete the current task with error status
|
||||||
|
setStatus(currentTaskId, 'finished');
|
||||||
|
setIsPending(currentTaskId, false);
|
||||||
|
|
||||||
|
// Add error message to the current task
|
||||||
|
addMessages(currentTaskId, {
|
||||||
id: generateUniqueId(),
|
id: generateUniqueId(),
|
||||||
role: "agent",
|
role: "agent",
|
||||||
content: `❌ **Error**: ${errorMessage}`,
|
content: `❌ **Error**: ${errorMessage}`,
|
||||||
});
|
});
|
||||||
uploadLog(currentTaskId, type)
|
uploadLog(currentTaskId, type)
|
||||||
return
|
|
||||||
|
// Stop the workforce
|
||||||
|
try {
|
||||||
|
await fetchDelete(`/chat/${project_id}`);
|
||||||
|
} catch (error) {
|
||||||
|
console.log("Task may not exist on backend:", error);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to handle model error:', error);
|
||||||
|
console.error('Original agentMessages:', agentMessages);
|
||||||
|
|
||||||
|
// Fallback: try to create error task with minimal operations
|
||||||
|
try {
|
||||||
|
const { create, setActiveTaskId, setHasWaitComfirm, addMessages } = get();
|
||||||
|
const fallbackTaskId = create();
|
||||||
|
setActiveTaskId(fallbackTaskId);
|
||||||
|
setHasWaitComfirm(fallbackTaskId, true);
|
||||||
|
addMessages(fallbackTaskId, {
|
||||||
|
id: generateUniqueId(),
|
||||||
|
role: "agent",
|
||||||
|
content: `**Critical Error**: An unexpected error occurred while handling a model error. Please refresh the application or contact support.`,
|
||||||
|
});
|
||||||
|
} catch (fallbackError) {
|
||||||
|
console.error('Failed to create fallback error task:', fallbackError);
|
||||||
|
// Last resort: just log the error without creating UI elements
|
||||||
|
console.error('Original error that could not be displayed:', agentMessages);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle add_task events for project store
|
// Handle add_task events for project store
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue