- 集成MCP连接管理器,支持MCP服务器连接 - 添加多种内置工具:ClarifyTool、CronTool、DelegateTool、ExecuteCodeTool、 PatchFileTool、ProcessTool、SendMessageTool、SpawnTool、TerminalTool、 TodoTool、WebFetchTool、WebSearchTool、WriteFileTool等 - 实现工具注册和装配功能 - 添加技能选择上下文参数 - 支持思考模式控制参数thinking_enabled feat(coordinator): 重构任务执行计划器参数命名 - 将learning_candidate_enabled重命名为allow_candidate_generation - 更新TeamGraphScheduler中的参数传递 - 修改LocalAgentRunner中的相关参数处理 - 更新README文档中的相应描述 refactor(context): 标准化工具调用参数格式 - 添加_json导入用于参数序列化 - 实现_provider_tool_calls方法标准化OpenAI兼容的工具调用载荷 - 修复工具调用中参数非字符串类型的序列化问题 refactor(session): 优化消息历史记录过滤逻辑 - 修改get_messages_as_conversation为基于运行状态过滤消息 - 排除未完成、失败或错误结束的运行记录 - 改进对话历史的可见性控制机制 fix(store): 修复FTS索引重建逻辑 - 添加异常处理防止FTS索引创建失败 - 实现_rebuild_fts_index方法重新构建全文搜索索引 - 优化索引触发器和表的维护流程
164 lines
6.3 KiB
Python
164 lines
6.3 KiB
Python
"""Built-in cron tool for managing scheduled Beaver Tasks."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from typing import Any
|
|
|
|
from beaver.services.cron_service import CronService, schedule_from_api
|
|
from beaver.tools.base import BaseTool, ToolContext, ToolResult, ToolSpec
|
|
|
|
|
|
CRON_TOOL_DESCRIPTION = (
|
|
"Create and manage scheduled Beaver notifications or Tasks. Notification mode "
|
|
"sends scheduled results to the fixed notification session; task mode creates "
|
|
"a Task run. Actions: add, list, remove, toggle, run."
|
|
)
|
|
|
|
CRON_TOOL_PARAMETERS: dict[str, Any] = {
|
|
"type": "object",
|
|
"properties": {
|
|
"action": {
|
|
"type": "string",
|
|
"enum": ["add", "list", "remove", "toggle", "run"],
|
|
"description": "The scheduled-task operation to perform.",
|
|
},
|
|
"name": {
|
|
"type": "string",
|
|
"description": "Short scheduled-task name. Optional for add.",
|
|
},
|
|
"message": {
|
|
"type": "string",
|
|
"description": "The task instruction to run when the schedule triggers. Required for add.",
|
|
},
|
|
"schedule": {
|
|
"type": "string",
|
|
"description": "Hermes-style schedule, for example 'every 15m', '0 9 * * *', or an ISO datetime.",
|
|
},
|
|
"every_seconds": {
|
|
"type": "integer",
|
|
"minimum": 1,
|
|
"description": "Fixed interval in seconds for recurring scheduled tasks.",
|
|
},
|
|
"cron_expr": {
|
|
"type": "string",
|
|
"description": "Cron expression such as '0 9 * * *'.",
|
|
},
|
|
"tz": {
|
|
"type": "string",
|
|
"description": "IANA timezone for cron_expr, for example 'Asia/Shanghai'.",
|
|
},
|
|
"at_iso": {
|
|
"type": "string",
|
|
"description": "ISO datetime for one-time scheduled tasks.",
|
|
},
|
|
"job_id": {
|
|
"type": "string",
|
|
"description": "Scheduled-task ID for remove, toggle, or run.",
|
|
},
|
|
"enabled": {
|
|
"type": "boolean",
|
|
"description": "Whether the scheduled task should be enabled when action is toggle.",
|
|
},
|
|
"mode": {
|
|
"type": "string",
|
|
"enum": ["notification", "task"],
|
|
"description": "Use notification for reminders/reports; use task only when the scheduled work requires Task tracking.",
|
|
},
|
|
"requires_followup": {
|
|
"type": "boolean",
|
|
"description": "Whether a task-mode scheduled run should appear as an active task awaiting user follow-up.",
|
|
},
|
|
},
|
|
"required": ["action"],
|
|
}
|
|
|
|
|
|
class CronTool(BaseTool):
|
|
"""Tool-facing wrapper around the process CronService."""
|
|
|
|
@property
|
|
def spec(self) -> ToolSpec:
|
|
return ToolSpec(
|
|
name="cron",
|
|
description=CRON_TOOL_DESCRIPTION,
|
|
input_schema=CRON_TOOL_PARAMETERS,
|
|
toolset="cron",
|
|
always_available=False,
|
|
)
|
|
|
|
async def invoke(self, arguments: dict[str, Any], context: ToolContext) -> ToolResult:
|
|
try:
|
|
result = await self._invoke(arguments, context)
|
|
return ToolResult(
|
|
success=bool(result.get("success", True)),
|
|
content=json.dumps(result, ensure_ascii=False),
|
|
tool_name=self.spec.name,
|
|
error=str(result.get("error")) if result.get("error") else None,
|
|
raw_output=result,
|
|
)
|
|
except Exception as exc:
|
|
return ToolResult(
|
|
success=False,
|
|
content=json.dumps({"success": False, "error": str(exc)}, ensure_ascii=False),
|
|
tool_name=self.spec.name,
|
|
error=str(exc),
|
|
)
|
|
|
|
async def _invoke(self, arguments: dict[str, Any], context: ToolContext) -> dict[str, Any]:
|
|
service = self._resolve_cron_service(context)
|
|
action = str(arguments.get("action") or "").strip().lower()
|
|
if action == "add":
|
|
schedule = schedule_from_api(arguments)
|
|
job = service.add_job(
|
|
name=str(arguments.get("name") or "").strip(),
|
|
message=str(arguments.get("message") or "").strip(),
|
|
schedule=schedule,
|
|
session_key=str(arguments.get("session_key") or context.session_id or "").strip() or None,
|
|
payload_kind="agent_turn",
|
|
mode=str(arguments.get("mode") or "notification").strip().lower(),
|
|
requires_followup=bool(arguments.get("requires_followup", False)),
|
|
)
|
|
return {"success": True, "job": job.to_api_dict()}
|
|
if action == "list":
|
|
include_disabled = bool(arguments.get("include_disabled", True))
|
|
return {
|
|
"success": True,
|
|
"jobs": [job.to_api_dict() for job in service.list_jobs(include_disabled=include_disabled)],
|
|
}
|
|
if action == "remove":
|
|
job_id = _required_job_id(arguments)
|
|
return {"success": service.remove_job(job_id), "job_id": job_id}
|
|
if action == "toggle":
|
|
job_id = _required_job_id(arguments)
|
|
job = service.update_enabled(job_id, bool(arguments.get("enabled", True)))
|
|
if job is None:
|
|
return {"success": False, "error": f"Scheduled task {job_id!r} was not found."}
|
|
return {"success": True, "job": job.to_api_dict()}
|
|
if action == "run":
|
|
job_id = _required_job_id(arguments)
|
|
ok = await service.run_job(job_id, force=True)
|
|
job = service.get_job(job_id)
|
|
return {
|
|
"success": ok,
|
|
"job_id": job_id,
|
|
"job": job.to_api_dict() if job is not None else None,
|
|
}
|
|
return {"success": False, "error": "action must be one of: add, list, remove, toggle, run"}
|
|
|
|
@staticmethod
|
|
def _resolve_cron_service(context: ToolContext) -> CronService:
|
|
service = context.get("cron_service")
|
|
if isinstance(service, CronService):
|
|
return service
|
|
if not context.workspace:
|
|
raise RuntimeError("Cron service is unavailable for this runtime.")
|
|
return CronService(f"{context.workspace}/cron/jobs.json")
|
|
|
|
|
|
def _required_job_id(arguments: dict[str, Any]) -> str:
|
|
job_id = str(arguments.get("job_id") or "").strip()
|
|
if not job_id:
|
|
raise ValueError("job_id is required")
|
|
return job_id
|