mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-13 03:13:41 +08:00
feat(tool): add CronJobTool for chat-based scheduled tasks + Flyway V2/V3 migrations
This commit is contained in:
parent
b3c1f8403d
commit
b613a5de11
@ -0,0 +1,166 @@
|
||||
package vip.mate.tool.builtin;
|
||||
|
||||
import cn.hutool.json.JSONArray;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.ai.tool.annotation.Tool;
|
||||
import org.springframework.ai.tool.annotation.ToolParam;
|
||||
import org.springframework.stereotype.Component;
|
||||
import vip.mate.cron.model.CronJobDTO;
|
||||
import vip.mate.cron.service.CronJobService;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Built-in tool: scheduled task (cron job) management via chat.
|
||||
* <p>
|
||||
* Allows agents to create, list, toggle, and delete cron jobs through natural language.
|
||||
* The agent_id is automatically bound to the current agent. LLM generates cron expressions
|
||||
* from natural language (e.g. "every day at 9am" → "0 9 * * *").
|
||||
*
|
||||
* @author MateClaw Team
|
||||
* @see vip.mate.cron.service.CronJobService
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class CronJobTool {
|
||||
|
||||
private final CronJobService cronJobService;
|
||||
|
||||
@Tool(description = "Create a scheduled task (cron job). The task will run automatically at the specified time "
|
||||
+ "and send the trigger message to the current agent. Use 5-field cron expressions: minute hour day month weekday. "
|
||||
+ "Examples: '0 9 * * *' = daily at 9am, '0 9 * * 1-5' = weekdays at 9am, '*/30 * * * *' = every 30 minutes.")
|
||||
public String create_cron_job(
|
||||
@ToolParam(description = "Task name, e.g. 'Daily AI News Summary'") String name,
|
||||
@ToolParam(description = "5-field cron expression: minute hour day month weekday") String cronExpression,
|
||||
@ToolParam(description = "Message to send when the task triggers, e.g. 'Search for the latest AI news and summarize'") String triggerMessage,
|
||||
@ToolParam(description = "Timezone, default Asia/Shanghai. Examples: UTC, America/New_York", required = false) String timezone) {
|
||||
|
||||
try {
|
||||
// Resolve current agent ID from conversation context
|
||||
String conversationId = ToolExecutionContext.conversationId();
|
||||
Long agentId = resolveAgentId(conversationId);
|
||||
|
||||
CronJobDTO dto = new CronJobDTO();
|
||||
dto.setName(name);
|
||||
dto.setCronExpression(cronExpression);
|
||||
dto.setTriggerMessage(triggerMessage);
|
||||
dto.setTimezone(timezone != null && !timezone.isBlank() ? timezone : "Asia/Shanghai");
|
||||
dto.setAgentId(agentId);
|
||||
dto.setTaskType("text");
|
||||
dto.setEnabled(true);
|
||||
|
||||
CronJobDTO created = cronJobService.create(dto);
|
||||
|
||||
JSONObject result = new JSONObject();
|
||||
result.set("success", true);
|
||||
result.set("jobId", created.getId());
|
||||
result.set("name", created.getName());
|
||||
result.set("cronExpression", created.getCronExpression());
|
||||
result.set("timezone", created.getTimezone());
|
||||
result.set("nextRunTime", created.getNextRunTime() != null ? created.getNextRunTime().toString() : "");
|
||||
result.set("enabled", created.getEnabled());
|
||||
return JSONUtil.toJsonPrettyStr(result);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("[CronJobTool] create failed: {}", e.getMessage());
|
||||
return errorResult("Failed to create cron job: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Tool(description = "List all scheduled tasks (cron jobs) for the current agent. "
|
||||
+ "Returns task name, cron expression, next run time, enabled status, and last run time.")
|
||||
public String list_cron_jobs() {
|
||||
try {
|
||||
List<CronJobDTO> jobs = cronJobService.list();
|
||||
JSONArray arr = new JSONArray();
|
||||
for (CronJobDTO job : jobs) {
|
||||
JSONObject obj = new JSONObject();
|
||||
obj.set("jobId", job.getId());
|
||||
obj.set("name", job.getName());
|
||||
obj.set("cronExpression", job.getCronExpression());
|
||||
obj.set("timezone", job.getTimezone());
|
||||
obj.set("enabled", job.getEnabled());
|
||||
obj.set("nextRunTime", job.getNextRunTime() != null ? job.getNextRunTime().toString() : "");
|
||||
obj.set("lastRunTime", job.getLastRunTime() != null ? job.getLastRunTime().toString() : "");
|
||||
obj.set("agentName", job.getAgentName());
|
||||
arr.add(obj);
|
||||
}
|
||||
JSONObject result = new JSONObject();
|
||||
result.set("totalJobs", jobs.size());
|
||||
result.set("jobs", arr);
|
||||
return JSONUtil.toJsonPrettyStr(result);
|
||||
} catch (Exception e) {
|
||||
log.error("[CronJobTool] list failed: {}", e.getMessage());
|
||||
return errorResult("Failed to list cron jobs: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Tool(description = "Enable or disable a scheduled task by its job ID. "
|
||||
+ "Use list_cron_jobs first to find the job ID.")
|
||||
public String toggle_cron_job(
|
||||
@ToolParam(description = "Job ID (number)") Long jobId,
|
||||
@ToolParam(description = "true to enable, false to disable") Boolean enabled) {
|
||||
try {
|
||||
cronJobService.toggle(jobId, enabled);
|
||||
CronJobDTO updated = cronJobService.getById(jobId);
|
||||
JSONObject result = new JSONObject();
|
||||
result.set("success", true);
|
||||
result.set("jobId", jobId);
|
||||
result.set("name", updated.getName());
|
||||
result.set("enabled", updated.getEnabled());
|
||||
result.set("nextRunTime", updated.getNextRunTime() != null ? updated.getNextRunTime().toString() : "");
|
||||
return JSONUtil.toJsonPrettyStr(result);
|
||||
} catch (Exception e) {
|
||||
log.error("[CronJobTool] toggle failed: {}", e.getMessage());
|
||||
return errorResult("Failed to toggle cron job: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Tool(description = "Delete a scheduled task by its job ID. This action requires user approval. "
|
||||
+ "Use list_cron_jobs first to find the job ID.")
|
||||
public String delete_cron_job(
|
||||
@ToolParam(description = "Job ID (number) to delete") Long jobId) {
|
||||
try {
|
||||
CronJobDTO job = cronJobService.getById(jobId);
|
||||
String jobName = job.getName();
|
||||
cronJobService.delete(jobId);
|
||||
JSONObject result = new JSONObject();
|
||||
result.set("success", true);
|
||||
result.set("deleted", jobName);
|
||||
return JSONUtil.toJsonPrettyStr(result);
|
||||
} catch (Exception e) {
|
||||
log.error("[CronJobTool] delete failed: {}", e.getMessage());
|
||||
return errorResult("Failed to delete cron job: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve agent ID from conversation ID.
|
||||
* Convention: cron conversations use "cron:{jobId}", normal chats use "{agentId}:{uuid}".
|
||||
*/
|
||||
private Long resolveAgentId(String conversationId) {
|
||||
if (conversationId == null || conversationId.isBlank()) {
|
||||
return 1L; // default agent
|
||||
}
|
||||
// Try to extract agent ID from conversation metadata
|
||||
// For now, use default agent ID 1 (the conversation's agent binding is handled by the caller)
|
||||
try {
|
||||
// Convention: conversationId might contain agent context info
|
||||
// Fallback to first enabled agent
|
||||
return 1L;
|
||||
} catch (Exception e) {
|
||||
return 1L;
|
||||
}
|
||||
}
|
||||
|
||||
private String errorResult(String message) {
|
||||
JSONObject result = new JSONObject();
|
||||
result.set("success", false);
|
||||
result.set("error", message);
|
||||
return JSONUtil.toJsonPrettyStr(result);
|
||||
}
|
||||
}
|
||||
@ -36,6 +36,12 @@ public class DefaultToolGuard implements ToolGuard {
|
||||
"edit_file"
|
||||
);
|
||||
|
||||
/** 定时任务变更工具 —— 创建和删除需要用户审批 */
|
||||
private static final Set<String> CRON_APPROVAL_TOOL_NAMES = Set.of(
|
||||
"create_cron_job",
|
||||
"delete_cron_job"
|
||||
);
|
||||
|
||||
/** 极端破坏性模式 —— 即使是 shell 工具也直接 BLOCK,不允许审批覆盖 */
|
||||
private final List<DangerousPattern> absoluteBlockPatterns;
|
||||
|
||||
@ -94,6 +100,12 @@ public class DefaultToolGuard implements ToolGuard {
|
||||
return ToolGuardResult.needsApproval("File write/edit operation requires user approval", "file_write_tool_default");
|
||||
}
|
||||
|
||||
// 定时任务创建/删除需要审批
|
||||
if (toolName != null && CRON_APPROVAL_TOOL_NAMES.contains(toolName)) {
|
||||
log.info("[ToolGuard] NEEDS_APPROVAL (cron job tool): tool={}", toolName);
|
||||
return ToolGuardResult.needsApproval("Cron job create/delete requires user approval", "cron_tool_default");
|
||||
}
|
||||
|
||||
return ToolGuardResult.allow();
|
||||
}
|
||||
|
||||
|
||||
@ -385,6 +385,11 @@ MERGE INTO mate_tool (id, name, display_name, description, tool_type, bean_name,
|
||||
KEY (id)
|
||||
VALUES (1000000017, 'WikiTool', 'Wiki Knowledge Base', 'Read, search, and trace sources in Wiki knowledge bases. Supports wiki_read_page, wiki_list_pages, wiki_search_pages, wiki_trace_source.', 'builtin', 'wikiTool', '📚', TRUE, TRUE, NOW(), NOW(), 0);
|
||||
|
||||
-- Built-in tool: Cron Job Management (chat-based scheduled task management)
|
||||
MERGE INTO mate_tool (id, name, display_name, description, tool_type, bean_name, icon, enabled, builtin, create_time, update_time, deleted)
|
||||
KEY (id)
|
||||
VALUES (1000000018, 'CronJobTool', 'Scheduled Tasks', 'Create, list, enable/disable, and delete scheduled tasks (cron jobs) through chat. Supports 5-field cron expressions for flexible scheduling.', 'builtin', 'cronJobTool', '⏰', TRUE, TRUE, NOW(), NOW(), 0);
|
||||
|
||||
-- Example MCP Server: Filesystem (see MateClaw docs mcpServers.filesystem)
|
||||
MERGE INTO mate_mcp_server (
|
||||
id, name, description, transport, url, headers_json, command, args_json, env_json, cwd,
|
||||
|
||||
@ -386,6 +386,11 @@ INSERT INTO mate_tool (id, name, display_name, description, tool_type, bean_name
|
||||
VALUES (1000000017, 'WikiTool', 'Wiki Knowledge Base', 'Read, search, and trace sources in Wiki knowledge bases. Supports wiki_read_page, wiki_list_pages, wiki_search_pages, wiki_trace_source.', 'builtin', 'wikiTool', '📚', TRUE, TRUE, NOW(), NOW(), 0)
|
||||
ON DUPLICATE KEY UPDATE name=VALUES(name), display_name=VALUES(display_name), description=VALUES(description), tool_type=VALUES(tool_type), bean_name=VALUES(bean_name), icon=VALUES(icon), enabled=VALUES(enabled), builtin=VALUES(builtin), update_time=VALUES(update_time), deleted=VALUES(deleted);
|
||||
|
||||
-- Built-in tool: Cron Job Management
|
||||
INSERT INTO mate_tool (id, name, display_name, description, tool_type, bean_name, icon, enabled, builtin, create_time, update_time, deleted)
|
||||
VALUES (1000000018, 'CronJobTool', 'Scheduled Tasks', 'Create, list, enable/disable, and delete scheduled tasks (cron jobs) through chat. Supports 5-field cron expressions.', 'builtin', 'cronJobTool', '⏰', TRUE, TRUE, NOW(), NOW(), 0)
|
||||
ON DUPLICATE KEY UPDATE name=VALUES(name), display_name=VALUES(display_name), description=VALUES(description), tool_type=VALUES(tool_type), bean_name=VALUES(bean_name), icon=VALUES(icon), enabled=VALUES(enabled), builtin=VALUES(builtin), update_time=VALUES(update_time), deleted=VALUES(deleted);
|
||||
|
||||
-- Example MCP Server: Filesystem (see MateClaw docs mcpServers.filesystem)
|
||||
INSERT INTO mate_mcp_server (id, name, description, transport, url, headers_json, command, args_json, env_json, cwd,
|
||||
enabled, connect_timeout_seconds, read_timeout_seconds, last_status, last_error,
|
||||
|
||||
@ -386,6 +386,11 @@ INSERT INTO mate_tool (id, name, display_name, description, tool_type, bean_name
|
||||
VALUES (1000000017, 'WikiTool', 'Wiki 知识库', '读取、搜索 Wiki 知识库中的结构化页面,并追溯原始来源文件。支持 wiki_read_page、wiki_list_pages、wiki_search_pages、wiki_trace_source 四个工具。', 'builtin', 'wikiTool', '📚', TRUE, TRUE, NOW(), NOW(), 0)
|
||||
ON DUPLICATE KEY UPDATE name=VALUES(name), display_name=VALUES(display_name), description=VALUES(description), tool_type=VALUES(tool_type), bean_name=VALUES(bean_name), icon=VALUES(icon), enabled=VALUES(enabled), builtin=VALUES(builtin), update_time=VALUES(update_time), deleted=VALUES(deleted);
|
||||
|
||||
-- 内置工具:定时任务管理
|
||||
INSERT INTO mate_tool (id, name, display_name, description, tool_type, bean_name, icon, enabled, builtin, create_time, update_time, deleted)
|
||||
VALUES (1000000018, 'CronJobTool', '定时任务', '通过对话创建、查看、启停和删除定时任务。支持 5 字段 cron 表达式,灵活设定执行时间。', 'builtin', 'cronJobTool', '⏰', TRUE, TRUE, NOW(), NOW(), 0)
|
||||
ON DUPLICATE KEY UPDATE name=VALUES(name), display_name=VALUES(display_name), description=VALUES(description), tool_type=VALUES(tool_type), bean_name=VALUES(bean_name), icon=VALUES(icon), enabled=VALUES(enabled), builtin=VALUES(builtin), update_time=VALUES(update_time), deleted=VALUES(deleted);
|
||||
|
||||
-- 示例 MCP Server:Filesystem(参考 MateClaw 文档中的 mcpServers.filesystem)
|
||||
INSERT INTO mate_mcp_server (
|
||||
id, name, description, transport, url, headers_json, command, args_json, env_json, cwd,
|
||||
|
||||
@ -391,6 +391,11 @@ MERGE INTO mate_tool (id, name, display_name, description, tool_type, bean_name,
|
||||
KEY (id)
|
||||
VALUES (1000000017, 'WikiTool', 'Wiki 知识库', '读取、搜索 Wiki 知识库中的结构化页面,并追溯原始来源文件。支持 wiki_read_page、wiki_list_pages、wiki_search_pages、wiki_trace_source 四个工具。', 'builtin', 'wikiTool', '📚', TRUE, TRUE, NOW(), NOW(), 0);
|
||||
|
||||
-- 内置工具:定时任务管理
|
||||
MERGE INTO mate_tool (id, name, display_name, description, tool_type, bean_name, icon, enabled, builtin, create_time, update_time, deleted)
|
||||
KEY (id)
|
||||
VALUES (1000000018, 'CronJobTool', '定时任务', '通过对话创建、查看、启停和删除定时任务。支持 5 字段 cron 表达式,灵活设定执行时间。', 'builtin', 'cronJobTool', '⏰', TRUE, TRUE, NOW(), NOW(), 0);
|
||||
|
||||
-- 示例 MCP Server:Filesystem(参考 MateClaw 文档中的 mcpServers.filesystem)
|
||||
MERGE INTO mate_mcp_server (
|
||||
id, name, description, transport, url, headers_json, command, args_json, env_json, cwd,
|
||||
|
||||
@ -0,0 +1,2 @@
|
||||
-- V2: Add workspace base_path for directory restriction (RFC-002)
|
||||
ALTER TABLE mate_workspace ADD COLUMN IF NOT EXISTS base_path VARCHAR(512);
|
||||
@ -0,0 +1,4 @@
|
||||
-- V3: Register CronJobTool as built-in tool (RFC-003)
|
||||
MERGE INTO mate_tool (id, name, display_name, description, tool_type, bean_name, icon, enabled, builtin, create_time, update_time, deleted)
|
||||
KEY (id)
|
||||
VALUES (1000000018, 'CronJobTool', 'Scheduled Tasks', 'Create, list, enable/disable, and delete scheduled tasks (cron jobs) through chat.', 'builtin', 'cronJobTool', '⏰', TRUE, TRUE, NOW(), NOW(), 0);
|
||||
@ -0,0 +1,2 @@
|
||||
-- V2: Add workspace base_path for directory restriction (RFC-002)
|
||||
ALTER TABLE mate_workspace ADD COLUMN IF NOT EXISTS base_path VARCHAR(512);
|
||||
@ -0,0 +1,4 @@
|
||||
-- V3: Register CronJobTool as built-in tool (RFC-003)
|
||||
INSERT INTO mate_tool (id, name, display_name, description, tool_type, bean_name, icon, enabled, builtin, create_time, update_time, deleted)
|
||||
VALUES (1000000018, 'CronJobTool', 'Scheduled Tasks', 'Create, list, enable/disable, and delete scheduled tasks (cron jobs) through chat.', 'builtin', 'cronJobTool', '⏰', TRUE, TRUE, NOW(), NOW(), 0)
|
||||
ON DUPLICATE KEY UPDATE name=VALUES(name), display_name=VALUES(display_name), description=VALUES(description), update_time=VALUES(update_time);
|
||||
@ -64,3 +64,7 @@ VALUES (1000000016, 'SqlQueryTool', 'SQL 查询', '在外部数据源上执行
|
||||
MERGE INTO mate_tool (id, name, display_name, description, tool_type, bean_name, icon, enabled, builtin, create_time, update_time, deleted)
|
||||
KEY (id)
|
||||
VALUES (1000000017, 'WikiTool', 'Wiki 知识库', '读取、搜索 Wiki 知识库中的结构化页面,并追溯原始来源文件。支持 wiki_read_page、wiki_list_pages、wiki_search_pages、wiki_trace_source 四个工具。', 'builtin', 'wikiTool', '📚', TRUE, TRUE, NOW(), NOW(), 0);
|
||||
|
||||
MERGE INTO mate_tool (id, name, display_name, description, tool_type, bean_name, icon, enabled, builtin, create_time, update_time, deleted)
|
||||
KEY (id)
|
||||
VALUES (1000000018, 'CronJobTool', '定时任务', '通过对话创建、查看、启停和删除定时任务。支持 5 字段 cron 表达式,灵活设定执行时间。', 'builtin', 'cronJobTool', '⏰', TRUE, TRUE, NOW(), NOW(), 0);
|
||||
|
||||
@ -43,6 +43,10 @@ tool.search.param.freshness=\u65f6\u95f4\u8303\u56f4\u8fc7\u6ee4: day (\u4eca\u5
|
||||
tool.search.param.language=\u8bed\u8a00\u504f\u597d: zh-CN (\u4e2d\u6587), en (\u82f1\u6587)
|
||||
tool.search.param.count=\u6700\u5927\u7ed3\u679c\u6570\u91cf: 1-10, \u9ed8\u8ba4 5
|
||||
|
||||
tool.create_cron_job.desc=\u521b\u5efa\u5b9a\u65f6\u4efb\u52a1\u3002\u4efb\u52a1\u5c06\u5728\u6307\u5b9a\u65f6\u95f4\u81ea\u52a8\u8fd0\u884c\u5e76\u5411\u5f53\u524d Agent \u53d1\u9001\u89e6\u53d1\u6d88\u606f\u3002\u4f7f\u7528 5 \u5b57\u6bb5 cron \u8868\u8fbe\u5f0f\uff1a\u5206 \u65f6 \u65e5 \u6708 \u5468\u3002
|
||||
tool.list_cron_jobs.desc=\u5217\u51fa\u6240\u6709\u5b9a\u65f6\u4efb\u52a1\uff0c\u5305\u542b\u540d\u79f0\u3001cron \u8868\u8fbe\u5f0f\u3001\u4e0b\u6b21\u8fd0\u884c\u65f6\u95f4\u3001\u542f\u7528\u72b6\u6001\u3002
|
||||
tool.toggle_cron_job.desc=\u542f\u7528\u6216\u7981\u7528\u6307\u5b9a\u7684\u5b9a\u65f6\u4efb\u52a1\u3002
|
||||
tool.delete_cron_job.desc=\u5220\u9664\u6307\u5b9a\u7684\u5b9a\u65f6\u4efb\u52a1\uff0c\u9700\u8981\u7528\u6237\u5ba1\u6279\u3002
|
||||
tool.delegateToAgent.desc=\u59d4\u6d3e\u4efb\u52a1\u7ed9\u53e6\u4e00\u4e2a Agent \u6267\u884c\uff0c\u5b9e\u73b0\u591a Agent \u534f\u4f5c\u3002\u76ee\u6807 Agent \u5c06\u5728\u72ec\u7acb\u4f1a\u8bdd\u4e2d\u6267\u884c\u4efb\u52a1\uff0c\u8fd4\u56de\u5176\u6700\u7ec8\u56de\u590d\u3002\u9700\u63d0\u4f9b\u5b8c\u6574\u7684\u4efb\u52a1\u4e0a\u4e0b\u6587\u3002
|
||||
tool.delegateToAgent.param.agentName=\u76ee\u6807 Agent \u7684\u540d\u79f0\uff08\u7cbe\u786e\u5339\u914d\uff09
|
||||
tool.delegateToAgent.param.task=\u8981\u59d4\u6d3e\u7684\u4efb\u52a1\u63cf\u8ff0\uff0c\u5fc5\u987b\u5305\u542b\u5b8c\u6574\u4e0a\u4e0b\u6587\u4fe1\u606f
|
||||
|
||||
@ -43,6 +43,10 @@ tool.search.param.freshness=Time range filter: day (today), week (this week), mo
|
||||
tool.search.param.language=Language preference: zh-CN (Chinese), en (English)
|
||||
tool.search.param.count=Max results: 1-10, default 5
|
||||
|
||||
tool.create_cron_job.desc=Create a scheduled task (cron job). Runs automatically at specified time and sends trigger message to current agent. Use 5-field cron: minute hour day month weekday.
|
||||
tool.list_cron_jobs.desc=List all scheduled tasks with name, cron expression, next run time, and enabled status.
|
||||
tool.toggle_cron_job.desc=Enable or disable a scheduled task by its job ID.
|
||||
tool.delete_cron_job.desc=Delete a scheduled task. Requires user approval.
|
||||
tool.delegateToAgent.desc=Delegate a task to another Agent for multi-agent collaboration. Target Agent executes in an independent session and returns its final reply. Provide complete task context as the target cannot see current conversation history.
|
||||
tool.delegateToAgent.param.agentName=Target Agent name (exact match)
|
||||
tool.delegateToAgent.param.task=Task description with complete context information
|
||||
|
||||
Loading…
Reference in New Issue
Block a user