feat: 添加任务调度器、后台任务运行器及多种工具支持

实现后台任务调度器(scheduler.py)和任务运行器(task_runner.py),支持长时间运行任务的异步执行和状态跟踪
新增多种工具支持:Shell命令执行、文件操作(读写/搜索/发送)、网页搜索/问答、定时提醒等
扩展README和ROADMAP文档,描述新功能和未来多主机架构规划
在配置文件中添加METASO_API_KEY支持秘塔AI搜索功能
优化代理逻辑,自动识别通用问题直接回答而不创建会话
This commit is contained in:
Yuyao Huang (Sam)
2026-03-28 13:45:20 +08:00
parent 6a0d409dd6
commit 8ecc701d5e
13 changed files with 1499 additions and 7 deletions
+79 -1
View File
@@ -10,8 +10,10 @@ import uuid
from typing import Optional, Tuple
from agent.manager import manager
from agent.scheduler import scheduler
from agent.task_runner import task_runner
from orchestrator.agent import agent
from orchestrator.tools import set_current_user, get_current_user
from orchestrator.tools import set_current_user, get_current_user, get_current_chat
logger = logging.getLogger(__name__)
@@ -59,6 +61,12 @@ async def handle_command(user_id: str, text: str) -> Optional[str]:
return _cmd_direct(user_id)
elif cmd == "/smart":
return _cmd_smart(user_id)
elif cmd == "/tasks":
return _cmd_tasks()
elif cmd == "/shell":
return await _cmd_shell(args)
elif cmd == "/remind":
return await _cmd_remind(args)
else:
return None
@@ -203,6 +211,73 @@ def _cmd_smart(user_id: str) -> str:
return "✓ Smart mode ON. Messages go through LLM for intelligent routing."
def _cmd_tasks() -> str:
"""List background tasks."""
tasks = task_runner.list_tasks()
if not tasks:
return "No background tasks."
lines = ["**Background Tasks:**\n"]
for t in tasks:
status_emoji = {"completed": "", "failed": "", "running": "", "pending": "⏸️"}.get(
t["status"], ""
)
lines.append(f"{status_emoji} #{t['task_id']} - {t['description'][:50]} ({t['elapsed']}s)")
return "\n".join(lines)
async def _cmd_shell(args: str) -> str:
"""Execute a shell command directly."""
if not args:
return "Usage: /shell <command>\nExample: /shell git status"
from orchestrator.tools import ShellTool, get_current_chat
tool = ShellTool()
result = await tool._arun(command=args)
try:
data = json.loads(result)
if "error" in data:
return f"{data['error']}"
output = []
if data.get("stdout"):
output.append(data["stdout"])
if data.get("stderr"):
output.append(f"[stderr] {data['stderr']}")
output.append(f"[exit code: {data.get('exit_code', '?')}]")
return "\n".join(output) if output else "(no output)"
except json.JSONDecodeError:
return result
async def _cmd_remind(args: str) -> str:
"""Set a reminder."""
if not args:
return "Usage: /remind <time> <message>\nExample: /remind 10m check the build\nTime format: 30s, 10m, 1h"
parts = args.split(None, 1)
if len(parts) < 2:
return "Usage: /remind <time> <message>\nExample: /remind 10m check the build"
time_str, message = parts
match = re.match(r'^(\d+)(s|m|h)$', time_str.lower())
if not match:
return "Invalid time format. Use: 30s, 10m, 1h"
value = int(match.group(1))
unit = match.group(2)
seconds = value * {'s': 1, 'm': 60, 'h': 3600}[unit]
chat_id = get_current_chat()
job_id = await scheduler.schedule_once(
delay_seconds=seconds,
message=message,
notify_chat_id=chat_id,
)
return f"⏰ Reminder #{job_id} set for {value}{unit} from now"
def _cmd_help() -> str:
"""Show help."""
return """**Commands:**
@@ -212,5 +287,8 @@ def _cmd_help() -> str:
/switch <n> - Switch to session by number
/direct - Direct mode: messages → Claude Code (no LLM overhead)
/smart - Smart mode: messages → LLM routing (default)
/shell <cmd> - Run shell command (bypasses LLM)
/remind <time> <msg> - Set reminder (e.g. /remind 10m check build)
/tasks - List background tasks
/retry - Retry last message
/help - Show this help"""
+3
View File
@@ -15,6 +15,7 @@ from bot.commands import handle_command
from bot.feishu import send_text
from config import FEISHU_APP_ID, FEISHU_APP_SECRET, is_user_allowed
from orchestrator.agent import agent
from orchestrator.tools import set_current_chat
logger = logging.getLogger(__name__)
@@ -87,6 +88,8 @@ def _handle_message(data: P2ImMessageReceiveV1) -> None:
async def _process_message(user_id: str, chat_id: str, text: str) -> None:
"""Process message: check allowlist, then commands, then agent."""
try:
set_current_chat(chat_id)
if not is_user_allowed(user_id):
logger.warning("Rejected message from unauthorized user: ...%s", user_id[-8:])
await send_text(chat_id, "chat_id", "Sorry, you are not authorized to use this bot.")