mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-14 03:33:43 +08:00
170 lines
7.7 KiB
Java
170 lines
7.7 KiB
Java
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;
|
|
|
|
@vip.mate.tool.ConcurrencyUnsafe("cron job creation persists to mate_cron_job; concurrent creates can race on name")
|
|
@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());
|
|
}
|
|
}
|
|
|
|
@vip.mate.tool.ConcurrencyUnsafe("toggles row state in mate_cron_job; serialize to keep enabled/disabled deterministic")
|
|
@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());
|
|
}
|
|
}
|
|
|
|
@vip.mate.tool.ConcurrencyUnsafe("destructive — removes row from mate_cron_job")
|
|
@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);
|
|
}
|
|
}
|