mirror of
https://github.com/agent0ai/agent-zero.git
synced 2026-05-01 21:20:33 +00:00
(WIP) feat: Task Scheduler Management UI/UX and tools - Part 3
This commit is contained in:
parent
282cbf90dc
commit
eb275d9c9c
37 changed files with 3417 additions and 673 deletions
|
|
@ -1,16 +1,28 @@
|
|||
from python.helpers.api import ApiHandler, Input, Output, Request, Response
|
||||
from agent import AgentContext
|
||||
from python.helpers import persist_chat
|
||||
from python.helpers.task_scheduler import TaskScheduler
|
||||
|
||||
|
||||
class RemoveChat(ApiHandler):
|
||||
async def process(self, input: Input, request: Request) -> Output:
|
||||
ctxid = input.get("context", "")
|
||||
|
||||
# context instance - get or create
|
||||
context = AgentContext.get(ctxid)
|
||||
if context:
|
||||
# stop processing any tasks
|
||||
context.reset()
|
||||
|
||||
AgentContext.remove(ctxid)
|
||||
persist_chat.remove_chat(ctxid)
|
||||
|
||||
scheduler = TaskScheduler.get()
|
||||
await scheduler.reload()
|
||||
|
||||
tasks = scheduler.get_tasks_by_context_id(ctxid)
|
||||
for task in tasks:
|
||||
await scheduler.remove_task_by_uuid(task.uuid)
|
||||
|
||||
return {
|
||||
"message": "Context removed.",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import time
|
||||
|
||||
from datetime import datetime
|
||||
from python.helpers.api import ApiHandler
|
||||
from flask import Request, Response
|
||||
|
||||
|
|
@ -7,6 +7,8 @@ from agent import AgentContext
|
|||
|
||||
from python.helpers import persist_chat
|
||||
from python.helpers.task_scheduler import TaskScheduler
|
||||
from python.helpers.localization import Localization
|
||||
from python.helpers.dotenv import get_dotenv_value
|
||||
|
||||
|
||||
class Poll(ApiHandler):
|
||||
|
|
@ -15,6 +17,10 @@ class Poll(ApiHandler):
|
|||
ctxid = input.get("context", None)
|
||||
from_no = input.get("log_from", 0)
|
||||
|
||||
# Get timezone from input (default to dotenv default or UTC if not provided)
|
||||
timezone = input.get("timezone", get_dotenv_value("DEFAULT_USER_TIMEZONE", "UTC"))
|
||||
Localization.get().set_timezone(timezone)
|
||||
|
||||
# context instance - get or create
|
||||
context = self.get_context(ctxid)
|
||||
|
||||
|
|
@ -28,7 +34,7 @@ class Poll(ApiHandler):
|
|||
# Always reload the scheduler on each poll to ensure we have the latest task state
|
||||
await scheduler.reload()
|
||||
|
||||
# loop AgentContext._contexts and number unnamed chats
|
||||
# loop AgentContext._contexts and divide into contexts and tasks
|
||||
|
||||
ctxs = []
|
||||
tasks = []
|
||||
|
|
@ -42,21 +48,15 @@ class Poll(ApiHandler):
|
|||
continue
|
||||
|
||||
# Create the base context data that will be returned
|
||||
context_data = {
|
||||
"id": ctx.id,
|
||||
"name": ctx.name,
|
||||
"created_at": ctx.created_at,
|
||||
"no": ctx.no,
|
||||
"log_guid": ctx.log.guid,
|
||||
"log_version": len(ctx.log.updates),
|
||||
"log_length": len(ctx.log.logs),
|
||||
"paused": ctx.paused,
|
||||
}
|
||||
context_data = ctx.serialize()
|
||||
|
||||
# Determine if this is a task by checking if a task with this UUID exists
|
||||
is_task = scheduler.get_task_by_uuid(ctx.id) is not None
|
||||
context_task = scheduler.get_task_by_uuid(ctx.id)
|
||||
# Determine if this is a task-dedicated context by checking if a task with this UUID exists
|
||||
is_task_context = (
|
||||
context_task is not None and context_task.context_id == ctx.id
|
||||
)
|
||||
|
||||
if not is_task:
|
||||
if not is_task_context:
|
||||
ctxs.append(context_data)
|
||||
else:
|
||||
# If this is a task, get task details from the scheduler
|
||||
|
|
@ -65,6 +65,7 @@ class Poll(ApiHandler):
|
|||
# Add task details to context_data with the same field names
|
||||
# as used in scheduler endpoints to maintain UI compatibility
|
||||
context_data.update({
|
||||
"task_name": task_details.get("name"), # name is for context, task_name for the task name
|
||||
"uuid": task_details.get("uuid"),
|
||||
"state": task_details.get("state"),
|
||||
"type": task_details.get("type"),
|
||||
|
|
@ -72,12 +73,15 @@ class Poll(ApiHandler):
|
|||
"prompt": task_details.get("prompt"),
|
||||
"last_run": task_details.get("last_run"),
|
||||
"last_result": task_details.get("last_result"),
|
||||
"attachments": task_details.get("attachments", [])
|
||||
"attachments": task_details.get("attachments", []),
|
||||
"context_id": task_details.get("context_id"),
|
||||
})
|
||||
|
||||
# Add type-specific fields
|
||||
if task_details.get("type") == "scheduled":
|
||||
context_data["schedule"] = task_details.get("schedule")
|
||||
elif task_details.get("type") == "planned":
|
||||
context_data["plan"] = task_details.get("plan")
|
||||
else:
|
||||
context_data["token"] = task_details.get("token")
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
## JSON Body
|
||||
# api_key
|
||||
# task_id
|
||||
# params
|
||||
# callback
|
||||
|
|
@ -1,8 +1,11 @@
|
|||
from python.helpers.api import ApiHandler, Input, Output, Request
|
||||
from python.helpers.task_scheduler import (
|
||||
TaskScheduler, ScheduledTask, AdHocTask, TaskSchedule,
|
||||
serialize_task, parse_task_schedule
|
||||
TaskScheduler, ScheduledTask, AdHocTask, PlannedTask, TaskSchedule,
|
||||
serialize_task, parse_task_schedule, parse_task_plan, TaskType
|
||||
)
|
||||
from python.helpers.localization import Localization
|
||||
from python.helpers.print_style import PrintStyle
|
||||
import random
|
||||
|
||||
|
||||
class SchedulerTaskCreate(ApiHandler):
|
||||
|
|
@ -10,6 +13,12 @@ class SchedulerTaskCreate(ApiHandler):
|
|||
"""
|
||||
Create a new task in the scheduler
|
||||
"""
|
||||
printer = PrintStyle(italic=True, font_color="blue", padding=False)
|
||||
|
||||
# Get timezone from input (do not set if not provided, we then rely on poll() to set it)
|
||||
if timezone := input.get("timezone", None):
|
||||
Localization.get().set_timezone(timezone)
|
||||
|
||||
scheduler = TaskScheduler.get()
|
||||
await scheduler.reload()
|
||||
|
||||
|
|
@ -18,11 +27,22 @@ class SchedulerTaskCreate(ApiHandler):
|
|||
system_prompt = input.get("system_prompt")
|
||||
prompt = input.get("prompt")
|
||||
attachments = input.get("attachments", [])
|
||||
context_id = input.get("context_id", None)
|
||||
|
||||
# Check if schedule is provided (for ScheduledTask)
|
||||
schedule = input.get("schedule", {})
|
||||
token: str = input.get("token", "")
|
||||
|
||||
# Debug log the token value
|
||||
printer.print(f"Token received from frontend: '{token}' (type: {type(token)}, length: {len(token) if token else 0})")
|
||||
|
||||
# Generate a random token if empty or not provided
|
||||
if not token:
|
||||
token = str(random.randint(1000000000000000000, 9999999999999999999))
|
||||
printer.print(f"Generated new token: '{token}'")
|
||||
|
||||
plan = input.get("plan", {})
|
||||
|
||||
# Validate required fields
|
||||
if not name or not system_prompt or not prompt:
|
||||
return {"error": "Missing required fields: name, system_prompt, prompt"}
|
||||
|
|
@ -55,24 +75,61 @@ class SchedulerTaskCreate(ApiHandler):
|
|||
system_prompt=system_prompt,
|
||||
prompt=prompt,
|
||||
schedule=task_schedule,
|
||||
attachments=attachments
|
||||
attachments=attachments,
|
||||
context_id=context_id,
|
||||
timezone=timezone
|
||||
)
|
||||
elif plan:
|
||||
# Create a planned task
|
||||
try:
|
||||
# Use our standardized parsing function
|
||||
task_plan = parse_task_plan(plan)
|
||||
except ValueError as e:
|
||||
return {"error": str(e)}
|
||||
|
||||
task = PlannedTask.create(
|
||||
name=name,
|
||||
system_prompt=system_prompt,
|
||||
prompt=prompt,
|
||||
plan=task_plan,
|
||||
attachments=attachments,
|
||||
context_id=context_id
|
||||
)
|
||||
else:
|
||||
# Create an ad-hoc task
|
||||
printer.print(f"Creating AdHocTask with token: '{token}'")
|
||||
task = AdHocTask.create(
|
||||
name=name,
|
||||
system_prompt=system_prompt,
|
||||
prompt=prompt,
|
||||
token=token,
|
||||
attachments=attachments
|
||||
attachments=attachments,
|
||||
context_id=context_id
|
||||
)
|
||||
# Verify token after creation
|
||||
if isinstance(task, AdHocTask):
|
||||
printer.print(f"AdHocTask created with token: '{task.token}'")
|
||||
|
||||
# Add the task to the scheduler
|
||||
await scheduler.add_task(task)
|
||||
|
||||
# Verify the task was added correctly - retrieve by UUID to check persistence
|
||||
saved_task = scheduler.get_task_by_uuid(task.uuid)
|
||||
if saved_task:
|
||||
if saved_task.type == TaskType.AD_HOC and isinstance(saved_task, AdHocTask):
|
||||
printer.print(f"Task verified after save, token: '{saved_task.token}'")
|
||||
else:
|
||||
printer.print("Task verified after save, not an adhoc task")
|
||||
else:
|
||||
printer.print("WARNING: Task not found after save!")
|
||||
|
||||
# Return the created task using our standardized serialization function
|
||||
task_dict = serialize_task(task)
|
||||
|
||||
# Debug log the serialized task
|
||||
if task_dict and task_dict.get('type') == 'adhoc':
|
||||
printer.print(f"Serialized adhoc task, token in response: '{task_dict.get('token')}'")
|
||||
|
||||
return {
|
||||
"task": task_dict
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
from python.helpers.api import ApiHandler, Input, Output, Request
|
||||
from python.helpers.task_scheduler import TaskScheduler
|
||||
from python.helpers.task_scheduler import TaskScheduler, TaskState
|
||||
from python.helpers.localization import Localization
|
||||
from agent import AgentContext
|
||||
from python.helpers import persist_chat
|
||||
|
||||
|
||||
class SchedulerTaskDelete(ApiHandler):
|
||||
|
|
@ -7,6 +10,10 @@ class SchedulerTaskDelete(ApiHandler):
|
|||
"""
|
||||
Delete a task from the scheduler by ID
|
||||
"""
|
||||
# Get timezone from input (do not set if not provided, we then rely on poll() to set it)
|
||||
if timezone := input.get("timezone", None):
|
||||
Localization.get().set_timezone(timezone)
|
||||
|
||||
scheduler = TaskScheduler.get()
|
||||
await scheduler.reload()
|
||||
|
||||
|
|
@ -21,6 +28,24 @@ class SchedulerTaskDelete(ApiHandler):
|
|||
if not task:
|
||||
return {"error": f"Task with ID {task_id} not found"}
|
||||
|
||||
context = None
|
||||
if task.context_id:
|
||||
context = self.get_context(task.context_id)
|
||||
|
||||
# If the task is running, update its state to IDLE first
|
||||
if task.state == TaskState.RUNNING:
|
||||
if context:
|
||||
context.reset()
|
||||
# Update the state to IDLE so any ongoing processes know to terminate
|
||||
await scheduler.update_task(task_id, state=TaskState.IDLE)
|
||||
# Force a save to ensure the state change is persisted
|
||||
await scheduler.save()
|
||||
|
||||
# This is a dedicated context for the task, so we remove it
|
||||
if context and context.id == task.uuid:
|
||||
AgentContext.remove(context.id)
|
||||
persist_chat.remove_chat(context.id)
|
||||
|
||||
# Remove the task
|
||||
await scheduler.remove_task_by_uuid(task_id)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
from python.helpers.api import ApiHandler, Input, Output, Request
|
||||
from python.helpers.task_scheduler import TaskScheduler, TaskState
|
||||
from python.helpers.print_style import PrintStyle
|
||||
from python.helpers.localization import Localization
|
||||
|
||||
|
||||
class SchedulerTaskRun(ApiHandler):
|
||||
|
|
@ -11,6 +12,9 @@ class SchedulerTaskRun(ApiHandler):
|
|||
"""
|
||||
Manually run a task from the scheduler by ID
|
||||
"""
|
||||
# Get timezone from input (do not set if not provided, we then rely on poll() to set it)
|
||||
if timezone := input.get("timezone", None):
|
||||
Localization.get().set_timezone(timezone)
|
||||
|
||||
# Get task ID from input
|
||||
task_id: str = input.get("task_id", "")
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
from python.helpers.api import ApiHandler, Input, Output, Request
|
||||
from python.helpers.task_scheduler import (
|
||||
TaskScheduler, ScheduledTask, AdHocTask, TaskState,
|
||||
serialize_task, parse_task_schedule
|
||||
TaskScheduler, ScheduledTask, AdHocTask, PlannedTask, TaskState,
|
||||
serialize_task, parse_task_schedule, parse_task_plan
|
||||
)
|
||||
from python.helpers.localization import Localization
|
||||
|
||||
|
||||
class SchedulerTaskUpdate(ApiHandler):
|
||||
|
|
@ -10,6 +11,10 @@ class SchedulerTaskUpdate(ApiHandler):
|
|||
"""
|
||||
Update an existing task in the scheduler
|
||||
"""
|
||||
# Get timezone from input (do not set if not provided, we then rely on poll() to set it)
|
||||
if timezone := input.get("timezone", None):
|
||||
Localization.get().set_timezone(timezone)
|
||||
|
||||
scheduler = TaskScheduler.get()
|
||||
await scheduler.reload()
|
||||
|
||||
|
|
@ -47,11 +52,28 @@ class SchedulerTaskUpdate(ApiHandler):
|
|||
if isinstance(task, ScheduledTask) and "schedule" in input:
|
||||
schedule_data = input.get("schedule", {})
|
||||
try:
|
||||
update_params["schedule"] = parse_task_schedule(schedule_data)
|
||||
# Parse the schedule with timezone handling
|
||||
task_schedule = parse_task_schedule(schedule_data)
|
||||
|
||||
# Set the timezone from the request if not already in schedule_data
|
||||
if not schedule_data.get('timezone', None) and timezone:
|
||||
task_schedule.timezone = timezone
|
||||
|
||||
update_params["schedule"] = task_schedule
|
||||
except ValueError as e:
|
||||
return {"error": f"Invalid schedule format: {str(e)}"}
|
||||
elif isinstance(task, AdHocTask) and "token" in input:
|
||||
update_params["token"] = input.get("token", "")
|
||||
token_value = input.get("token", "")
|
||||
if token_value: # Only update if non-empty
|
||||
update_params["token"] = token_value
|
||||
elif isinstance(task, PlannedTask) and "plan" in input:
|
||||
plan_data = input.get("plan", {})
|
||||
try:
|
||||
# Parse the plan data
|
||||
task_plan = parse_task_plan(plan_data)
|
||||
update_params["plan"] = task_plan
|
||||
except ValueError as e:
|
||||
return {"error": f"Invalid plan format: {str(e)}"}
|
||||
|
||||
# Use atomic update method to apply changes
|
||||
updated_task = await scheduler.update_task(task_id, **update_params)
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ from python.helpers.api import ApiHandler, Input, Output, Request
|
|||
from python.helpers.task_scheduler import TaskScheduler
|
||||
import traceback
|
||||
from python.helpers.print_style import PrintStyle
|
||||
from python.helpers.localization import Localization
|
||||
|
||||
|
||||
class SchedulerTasksList(ApiHandler):
|
||||
|
|
@ -10,6 +11,10 @@ class SchedulerTasksList(ApiHandler):
|
|||
List all tasks in the scheduler with their types
|
||||
"""
|
||||
try:
|
||||
# Get timezone from input (do not set if not provided, we then rely on poll() to set it)
|
||||
if timezone := input.get("timezone", None):
|
||||
Localization.get().set_timezone(timezone)
|
||||
|
||||
# Get task scheduler
|
||||
scheduler = TaskScheduler.get()
|
||||
await scheduler.reload()
|
||||
|
|
|
|||
|
|
@ -3,14 +3,19 @@ from datetime import datetime
|
|||
from python.helpers.api import ApiHandler, Input, Output, Request
|
||||
from python.helpers.print_style import PrintStyle
|
||||
from python.helpers.task_scheduler import TaskScheduler
|
||||
from python.helpers.localization import Localization
|
||||
|
||||
|
||||
class SchedulerTick(ApiHandler):
|
||||
@classmethod
|
||||
def requires_loopback(cls):
|
||||
def requires_loopback(cls) -> bool:
|
||||
return True
|
||||
|
||||
async def process(self, input: Input, request: Request) -> Output:
|
||||
# Get timezone from input (do not set if not provided, we then rely on poll() to set it)
|
||||
if timezone := input.get("timezone", None):
|
||||
Localization.get().set_timezone(timezone)
|
||||
|
||||
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
printer = PrintStyle(font_color="green", padding=False)
|
||||
printer.print(f"Scheduler tick - API: {timestamp}")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue