Files
ARTEX/server/triggers.go
T
Autumn-27andClaude Opus 4.8 f9716269ea feat(agent): 触发器新增「工具调用」条件 + 定时触发改为复选框展开
- 定时触发统一为前置复选框,勾选后展开「每 N 秒」+ 消息(与其它条件一致)
- 新增工具调用触发(P3 第 5 种条件):
  - 选中系统工具列表(至少一个),任务执行中这些工具每次调用完成即触发 agent
  - 触发消息带上任务 id/描述/目标 + 工具名 + 入参 + 返回内容(各截断 1500 字)
  - 只读任务执行的 activity,触发跑的会话写 conversation_activities,天然无自触发循环
- DB: agent_triggers 加 on_tool_call/tool_call_message/tool_names 三列;
  已发版,附幂等 ALTER TABLE ADD COLUMN IF NOT EXISTS 供旧库升级补列
- scheduler: 新增 last_toolcall_id 水位线(首启 seed) + fireToolCalls,以 tool_result
  为水位、回连 tool_use 取入参,按选中工具集合匹配后触发

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-08-08 02:59:07 -04:00

137 lines
3.8 KiB
Go

package server
import (
"net/http"
"github.com/Autumn-27/artex/db"
)
// ---------- P3 agent triggers (仅自定义 agent) ----------
func (s *Server) pgListTriggers(w http.ResponseWriter, r *http.Request) {
pg, a, ok := s.agentByKey(w, r)
if !ok {
return
}
trs, err := pg.ListTriggersFor(a.Key)
if err != nil {
writeErr(w, 500, err.Error())
return
}
writeJSON(w, 200, map[string]any{"triggers": trs})
}
type triggerReq struct {
Enabled bool `json:"enabled"`
IntervalSec int `json:"interval_sec"`
OnFinding bool `json:"on_finding"`
OnGoalMet bool `json:"on_goal_met"`
OnTaskTimeout bool `json:"on_task_timeout"`
OnToolCall bool `json:"on_tool_call"`
IntervalMessage string `json:"interval_message"`
FindingMessage string `json:"finding_message"`
GoalMessage string `json:"goal_message"`
TaskTimeoutMessage string `json:"task_timeout_message"`
ToolCallMessage string `json:"tool_call_message"`
ToolNames []string `json:"tool_names"`
}
// validateTrigger enforces the shared trigger rules for create/update:
// at least one condition, and on_tool_call requires a non-empty tool set.
func validateTrigger(req *triggerReq) string {
if req.IntervalSec == 0 && !req.OnFinding && !req.OnGoalMet && !req.OnTaskTimeout && !req.OnToolCall {
return "至少选择一种触发条件(定时/发现finding/目标达成/任务超时/工具调用)"
}
if req.OnToolCall && len(req.ToolNames) == 0 {
return "工具调用触发至少选择一个工具"
}
return ""
}
func (s *Server) pgCreateTrigger(w http.ResponseWriter, r *http.Request) {
pg, a, ok := s.agentByKey(w, r)
if !ok {
return
}
if a.Builtin {
writeErr(w, 400, "触发器仅支持自定义 agent")
return
}
var req triggerReq
if err := decode(r, &req); err != nil {
writeErr(w, 400, err.Error())
return
}
if req.IntervalSec < 0 {
req.IntervalSec = 0
}
if msg := validateTrigger(&req); msg != "" {
writeErr(w, 400, msg)
return
}
tr, err := pg.CreateTrigger(&db.AgentTrigger{
AgentKey: a.Key, Enabled: req.Enabled, IntervalSec: req.IntervalSec,
OnFinding: req.OnFinding, OnGoalMet: req.OnGoalMet, OnTaskTimeout: req.OnTaskTimeout, OnToolCall: req.OnToolCall,
IntervalMessage: req.IntervalMessage, FindingMessage: req.FindingMessage,
GoalMessage: req.GoalMessage, TaskTimeoutMessage: req.TaskTimeoutMessage,
ToolCallMessage: req.ToolCallMessage, ToolNames: req.ToolNames,
})
if err != nil {
writeErr(w, 500, err.Error())
return
}
writeJSON(w, 200, tr)
}
func (s *Server) pgUpdateTrigger(w http.ResponseWriter, r *http.Request) {
pg := s.pg(w)
if pg == nil {
return
}
id, ok := pathInt(r, "id")
if !ok {
writeErr(w, 400, "bad trigger id")
return
}
var req triggerReq
if err := decode(r, &req); err != nil {
writeErr(w, 400, err.Error())
return
}
if req.IntervalSec < 0 {
req.IntervalSec = 0
}
if msg := validateTrigger(&req); msg != "" {
writeErr(w, 400, msg)
return
}
if err := pg.UpdateTrigger(&db.AgentTrigger{
ID: id, Enabled: req.Enabled, IntervalSec: req.IntervalSec,
OnFinding: req.OnFinding, OnGoalMet: req.OnGoalMet, OnTaskTimeout: req.OnTaskTimeout, OnToolCall: req.OnToolCall,
IntervalMessage: req.IntervalMessage, FindingMessage: req.FindingMessage,
GoalMessage: req.GoalMessage, TaskTimeoutMessage: req.TaskTimeoutMessage,
ToolCallMessage: req.ToolCallMessage, ToolNames: req.ToolNames,
}); err != nil {
writeErr(w, 500, err.Error())
return
}
writeJSON(w, 200, map[string]any{"ok": true})
}
func (s *Server) pgDeleteTrigger(w http.ResponseWriter, r *http.Request) {
pg := s.pg(w)
if pg == nil {
return
}
id, ok := pathInt(r, "id")
if !ok {
writeErr(w, 400, "bad trigger id")
return
}
if err := pg.DeleteTrigger(id); err != nil {
writeErr(w, 500, err.Error())
return
}
writeJSON(w, 200, map[string]any{"deleted": id})
}