feat(trigger): 新增"任务创建"触发机制(on_task_create)
自定义 agent 触发条件从 5 种扩展到 6 种,新增任务被创建时触发。 用 tasks.id 单调水位线 + scheduler_state 防重复,重启不补触发; 触发消息完整分行给出任务ID/描述/目标。DB 加列并带 ALTER 迁移。 Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
1 parent
5962826f2d
commit
4cf622593a
6 files changed
+128
-27
No files matched your search
+7
-3
@@ -457,11 +457,13 @@ CREATE TABLE IF NOT EXISTS agent_triggers (
|
||||
on_goal_met BOOLEAN NOT NULL DEFAULT false,
|
||||
on_task_timeout BOOLEAN NOT NULL DEFAULT false,
|
||||
on_tool_call BOOLEAN NOT NULL DEFAULT false,
|
||||
on_task_create BOOLEAN NOT NULL DEFAULT false,
|
||||
interval_message TEXT NOT NULL DEFAULT '',
|
||||
finding_message TEXT NOT NULL DEFAULT '',
|
||||
goal_message TEXT NOT NULL DEFAULT '',
|
||||
task_timeout_message TEXT NOT NULL DEFAULT '',
|
||||
tool_call_message TEXT NOT NULL DEFAULT '',
|
||||
task_create_message TEXT NOT NULL DEFAULT '',
|
||||
tool_names TEXT NOT NULL DEFAULT '',
|
||||
last_fire TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
@@ -469,9 +471,11 @@ CREATE TABLE IF NOT EXISTS agent_triggers (
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_agent_triggers_agent ON agent_triggers(agent_key);
|
||||
-- 加列迁移(已发版,旧库升级补列;新库 CREATE 已含这些列,ALTER 为 no-op)。幂等,每次启动可重复执行。
|
||||
ALTER TABLE agent_triggers ADD COLUMN IF NOT EXISTS on_tool_call BOOLEAN NOT NULL DEFAULT false;
|
||||
ALTER TABLE agent_triggers ADD COLUMN IF NOT EXISTS tool_call_message TEXT NOT NULL DEFAULT '';
|
||||
ALTER TABLE agent_triggers ADD COLUMN IF NOT EXISTS tool_names TEXT NOT NULL DEFAULT '';
|
||||
ALTER TABLE agent_triggers ADD COLUMN IF NOT EXISTS on_tool_call BOOLEAN NOT NULL DEFAULT false;
|
||||
ALTER TABLE agent_triggers ADD COLUMN IF NOT EXISTS tool_call_message TEXT NOT NULL DEFAULT '';
|
||||
ALTER TABLE agent_triggers ADD COLUMN IF NOT EXISTS tool_names TEXT NOT NULL DEFAULT '';
|
||||
ALTER TABLE agent_triggers ADD COLUMN IF NOT EXISTS on_task_create BOOLEAN NOT NULL DEFAULT false;
|
||||
ALTER TABLE agent_triggers ADD COLUMN IF NOT EXISTS task_create_message TEXT NOT NULL DEFAULT '';
|
||||
DROP TRIGGER IF EXISTS trg_agent_triggers_upd ON agent_triggers;
|
||||
CREATE TRIGGER trg_agent_triggers_upd BEFORE UPDATE ON agent_triggers
|
||||
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
|
||||
|
||||
+41
-13
@@ -6,10 +6,11 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// AgentTrigger is one P3 trigger attached to a custom agent. Five trigger
|
||||
// AgentTrigger is one P3 trigger attached to a custom agent. Six trigger
|
||||
// conditions can be on at once: interval / on_finding / on_goal_met /
|
||||
// on_task_timeout / on_tool_call. ToolNames scopes the tool-call trigger to a
|
||||
// non-empty set of tool keys (empty is rejected at the API layer for on_tool_call).
|
||||
// on_task_timeout / on_tool_call / on_task_create. ToolNames scopes the tool-call
|
||||
// trigger to a non-empty set of tool keys (empty is rejected at the API layer for
|
||||
// on_tool_call).
|
||||
type AgentTrigger struct {
|
||||
ID int64 `json:"id"`
|
||||
AgentKey string `json:"agent_key"`
|
||||
@@ -19,16 +20,18 @@ type AgentTrigger struct {
|
||||
OnGoalMet bool `json:"on_goal_met"`
|
||||
OnTaskTimeout bool `json:"on_task_timeout"`
|
||||
OnToolCall bool `json:"on_tool_call"`
|
||||
OnTaskCreate bool `json:"on_task_create"`
|
||||
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"`
|
||||
TaskCreateMessage string `json:"task_create_message"`
|
||||
ToolNames []string `json:"tool_names"` // 选中的工具 key 列表(DB 存 JSON 文本)
|
||||
LastFire *time.Time `json:"last_fire,omitempty"`
|
||||
}
|
||||
|
||||
const triggerCols = `id, agent_key, enabled, interval_sec, on_finding, on_goal_met, on_task_timeout, on_tool_call, interval_message, finding_message, goal_message, task_timeout_message, tool_call_message, tool_names, last_fire`
|
||||
const triggerCols = `id, agent_key, enabled, interval_sec, on_finding, on_goal_met, on_task_timeout, on_tool_call, on_task_create, interval_message, finding_message, goal_message, task_timeout_message, tool_call_message, task_create_message, tool_names, last_fire`
|
||||
|
||||
// marshalToolNames encodes the tool-key list as JSON text for the tool_names column.
|
||||
// A nil/empty list stores "" (not "null"/"[]") so the column default stays clean.
|
||||
@@ -47,8 +50,8 @@ func scanTrigger(sc interface{ Scan(...any) error }) (*AgentTrigger, error) {
|
||||
var t AgentTrigger
|
||||
var lf sql.NullTime
|
||||
var toolNames string
|
||||
if err := sc.Scan(&t.ID, &t.AgentKey, &t.Enabled, &t.IntervalSec, &t.OnFinding, &t.OnGoalMet, &t.OnTaskTimeout, &t.OnToolCall,
|
||||
&t.IntervalMessage, &t.FindingMessage, &t.GoalMessage, &t.TaskTimeoutMessage, &t.ToolCallMessage, &toolNames, &lf); err != nil {
|
||||
if err := sc.Scan(&t.ID, &t.AgentKey, &t.Enabled, &t.IntervalSec, &t.OnFinding, &t.OnGoalMet, &t.OnTaskTimeout, &t.OnToolCall, &t.OnTaskCreate,
|
||||
&t.IntervalMessage, &t.FindingMessage, &t.GoalMessage, &t.TaskTimeoutMessage, &t.ToolCallMessage, &t.TaskCreateMessage, &toolNames, &lf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t.ToolNames = []string{}
|
||||
@@ -64,18 +67,18 @@ func scanTrigger(sc interface{ Scan(...any) error }) (*AgentTrigger, error) {
|
||||
// CreateTrigger inserts a trigger for agentKey and returns it.
|
||||
func (d *DB) CreateTrigger(t *AgentTrigger) (*AgentTrigger, error) {
|
||||
row := d.QueryRow(`
|
||||
INSERT INTO agent_triggers(agent_key, enabled, interval_sec, on_finding, on_goal_met, on_task_timeout, on_tool_call, interval_message, finding_message, goal_message, task_timeout_message, tool_call_message, tool_names)
|
||||
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13) RETURNING `+triggerCols,
|
||||
t.AgentKey, t.Enabled, t.IntervalSec, t.OnFinding, t.OnGoalMet, t.OnTaskTimeout, t.OnToolCall,
|
||||
t.IntervalMessage, t.FindingMessage, t.GoalMessage, t.TaskTimeoutMessage, t.ToolCallMessage, marshalToolNames(t.ToolNames))
|
||||
INSERT INTO agent_triggers(agent_key, enabled, interval_sec, on_finding, on_goal_met, on_task_timeout, on_tool_call, on_task_create, interval_message, finding_message, goal_message, task_timeout_message, tool_call_message, task_create_message, tool_names)
|
||||
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15) RETURNING `+triggerCols,
|
||||
t.AgentKey, t.Enabled, t.IntervalSec, t.OnFinding, t.OnGoalMet, t.OnTaskTimeout, t.OnToolCall, t.OnTaskCreate,
|
||||
t.IntervalMessage, t.FindingMessage, t.GoalMessage, t.TaskTimeoutMessage, t.ToolCallMessage, t.TaskCreateMessage, marshalToolNames(t.ToolNames))
|
||||
return scanTrigger(row)
|
||||
}
|
||||
|
||||
// UpdateTrigger updates a trigger's fields (not last_fire).
|
||||
func (d *DB) UpdateTrigger(t *AgentTrigger) error {
|
||||
_, err := d.Exec(`UPDATE agent_triggers SET enabled=$2, interval_sec=$3, on_finding=$4, on_goal_met=$5, on_task_timeout=$6, on_tool_call=$7, interval_message=$8, finding_message=$9, goal_message=$10, task_timeout_message=$11, tool_call_message=$12, tool_names=$13 WHERE id=$1`,
|
||||
t.ID, t.Enabled, t.IntervalSec, t.OnFinding, t.OnGoalMet, t.OnTaskTimeout, t.OnToolCall,
|
||||
t.IntervalMessage, t.FindingMessage, t.GoalMessage, t.TaskTimeoutMessage, t.ToolCallMessage, marshalToolNames(t.ToolNames))
|
||||
_, err := d.Exec(`UPDATE agent_triggers SET enabled=$2, interval_sec=$3, on_finding=$4, on_goal_met=$5, on_task_timeout=$6, on_tool_call=$7, on_task_create=$8, interval_message=$9, finding_message=$10, goal_message=$11, task_timeout_message=$12, tool_call_message=$13, task_create_message=$14, tool_names=$15 WHERE id=$1`,
|
||||
t.ID, t.Enabled, t.IntervalSec, t.OnFinding, t.OnGoalMet, t.OnTaskTimeout, t.OnToolCall, t.OnTaskCreate,
|
||||
t.IntervalMessage, t.FindingMessage, t.GoalMessage, t.TaskTimeoutMessage, t.ToolCallMessage, t.TaskCreateMessage, marshalToolNames(t.ToolNames))
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -210,6 +213,31 @@ ORDER BY id`, lastID)
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
// NewTasksSince returns tasks created with id > lastID (excluding deleted),
|
||||
// ordered by id (monotonic watermark → no double-fire across restarts). Triggered
|
||||
// agent runs are conversations, not tasks, so this never fires on its own output.
|
||||
func (d *DB) NewTasksSince(lastID int64) ([]TaskEvent, error) {
|
||||
rows, err := d.Query(`
|
||||
SELECT id, description, goal FROM tasks
|
||||
WHERE deleted_at IS NULL AND id > $1
|
||||
ORDER BY id`, lastID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
out := []TaskEvent{}
|
||||
for rows.Next() {
|
||||
var e TaskEvent
|
||||
if err := rows.Scan(&e.NodeID, &e.TaskDesc, &e.TaskGoal); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
e.TaskID = e.NodeID // task id doubles as NodeID for the watermark
|
||||
e.Summary = e.TaskGoal
|
||||
out = append(out, e)
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
// NewToolCallsSince returns completed tool calls (a tool_result row) with activity
|
||||
// id > lastID across all live tasks, ordered by id (monotonic watermark → no
|
||||
// double-fire). It is driven by tool_result rows (the tool finished, so both input
|
||||
|
||||
+48
-4
@@ -26,10 +26,11 @@ type Scheduler struct {
|
||||
}
|
||||
|
||||
const (
|
||||
schedKeyLastFinding = "last_finding_id" // watermark: max finding node id fired for
|
||||
schedKeyFiredGoals = "fired_goals" // JSON array of goal node ids already fired
|
||||
schedKeyLastTimeout = "last_timeout_id" // watermark: max task id fired for task timeout
|
||||
schedKeyLastToolCall = "last_toolcall_id" // watermark: max activity id fired for tool call
|
||||
schedKeyLastFinding = "last_finding_id" // watermark: max finding node id fired for
|
||||
schedKeyFiredGoals = "fired_goals" // JSON array of goal node ids already fired
|
||||
schedKeyLastTimeout = "last_timeout_id" // watermark: max task id fired for task timeout
|
||||
schedKeyLastToolCall = "last_toolcall_id" // watermark: max activity id fired for tool call
|
||||
schedKeyLastTaskCreate = "last_taskcreate_id" // watermark: max task id fired for task create
|
||||
)
|
||||
|
||||
func newScheduler(s *Server) *Scheduler {
|
||||
@@ -99,6 +100,17 @@ func (sc *Scheduler) init() {
|
||||
}
|
||||
_ = sc.pg.SetSchedState(schedKeyLastToolCall, strconv.FormatInt(maxID, 10))
|
||||
}
|
||||
if sc.mustState(schedKeyLastTaskCreate) == "" {
|
||||
var maxID int64
|
||||
if evs, err := sc.pg.NewTasksSince(0); err == nil {
|
||||
for _, e := range evs {
|
||||
if e.NodeID > maxID {
|
||||
maxID = e.NodeID
|
||||
}
|
||||
}
|
||||
}
|
||||
_ = sc.pg.SetSchedState(schedKeyLastTaskCreate, strconv.FormatInt(maxID, 10))
|
||||
}
|
||||
}
|
||||
|
||||
func (sc *Scheduler) step() {
|
||||
@@ -114,6 +126,7 @@ func (sc *Scheduler) step() {
|
||||
sc.fireGoals(triggers)
|
||||
sc.fireTaskTimeouts(triggers)
|
||||
sc.fireToolCalls(triggers)
|
||||
sc.fireTaskCreates(triggers)
|
||||
}
|
||||
|
||||
// fireIntervals fires triggers whose interval has elapsed since last_fire.
|
||||
@@ -229,6 +242,37 @@ func (sc *Scheduler) fireTaskTimeouts(triggers []*db.AgentTrigger) {
|
||||
_ = sc.pg.SetSchedState(schedKeyLastTimeout, strconv.FormatInt(maxID, 10))
|
||||
}
|
||||
|
||||
// fireTaskCreates fires on_task_create triggers for tasks newly created above the
|
||||
// persisted watermark (task id → no double-fire across restarts).
|
||||
func (sc *Scheduler) fireTaskCreates(triggers []*db.AgentTrigger) {
|
||||
var want []*db.AgentTrigger
|
||||
for _, tr := range triggers {
|
||||
if tr.OnTaskCreate {
|
||||
want = append(want, tr)
|
||||
}
|
||||
}
|
||||
if len(want) == 0 {
|
||||
return
|
||||
}
|
||||
last, _ := strconv.ParseInt(sc.mustState(schedKeyLastTaskCreate), 10, 64)
|
||||
events, err := sc.pg.NewTasksSince(last)
|
||||
if err != nil || len(events) == 0 {
|
||||
return
|
||||
}
|
||||
maxID := last
|
||||
for _, e := range events {
|
||||
if e.NodeID > maxID {
|
||||
maxID = e.NodeID
|
||||
}
|
||||
msgCtx := fmt.Sprintf("\n\n【本次由任务创建触发】\n任务ID: #%d\n任务描述: %s\n任务目标: %s",
|
||||
e.TaskID, e.TaskDesc, e.TaskGoal)
|
||||
for _, tr := range want {
|
||||
sc.s.StartTriggeredRun(tr.AgentKey, fmt.Sprintf("任务创建触发 · task#%d", e.TaskID), tr.TaskCreateMessage+msgCtx, e.TaskID, true)
|
||||
}
|
||||
}
|
||||
_ = sc.pg.SetSchedState(schedKeyLastTaskCreate, strconv.FormatInt(maxID, 10))
|
||||
}
|
||||
|
||||
// fireToolCalls fires on_tool_call triggers for tool calls (tool_result rows) above
|
||||
// the persisted watermark whose tool name is in the trigger's selected set. The fire
|
||||
// message carries the task id/desc/goal + tool name + (truncated) input & output.
|
||||
|
||||
+8
-6
@@ -28,19 +28,21 @@ type triggerReq struct {
|
||||
OnGoalMet bool `json:"on_goal_met"`
|
||||
OnTaskTimeout bool `json:"on_task_timeout"`
|
||||
OnToolCall bool `json:"on_tool_call"`
|
||||
OnTaskCreate bool `json:"on_task_create"`
|
||||
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"`
|
||||
TaskCreateMessage string `json:"task_create_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.IntervalSec == 0 && !req.OnFinding && !req.OnGoalMet && !req.OnTaskTimeout && !req.OnToolCall && !req.OnTaskCreate {
|
||||
return "至少选择一种触发条件(定时/发现finding/目标达成/任务超时/工具调用/任务创建)"
|
||||
}
|
||||
if req.OnToolCall && len(req.ToolNames) == 0 {
|
||||
return "工具调用触发至少选择一个工具"
|
||||
@@ -71,10 +73,10 @@ func (s *Server) pgCreateTrigger(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
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,
|
||||
OnFinding: req.OnFinding, OnGoalMet: req.OnGoalMet, OnTaskTimeout: req.OnTaskTimeout, OnToolCall: req.OnToolCall, OnTaskCreate: req.OnTaskCreate,
|
||||
IntervalMessage: req.IntervalMessage, FindingMessage: req.FindingMessage,
|
||||
GoalMessage: req.GoalMessage, TaskTimeoutMessage: req.TaskTimeoutMessage,
|
||||
ToolCallMessage: req.ToolCallMessage, ToolNames: req.ToolNames,
|
||||
ToolCallMessage: req.ToolCallMessage, TaskCreateMessage: req.TaskCreateMessage, ToolNames: req.ToolNames,
|
||||
})
|
||||
if err != nil {
|
||||
writeErr(w, 500, err.Error())
|
||||
@@ -107,10 +109,10 @@ func (s *Server) pgUpdateTrigger(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
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,
|
||||
OnFinding: req.OnFinding, OnGoalMet: req.OnGoalMet, OnTaskTimeout: req.OnTaskTimeout, OnToolCall: req.OnToolCall, OnTaskCreate: req.OnTaskCreate,
|
||||
IntervalMessage: req.IntervalMessage, FindingMessage: req.FindingMessage,
|
||||
GoalMessage: req.GoalMessage, TaskTimeoutMessage: req.TaskTimeoutMessage,
|
||||
ToolCallMessage: req.ToolCallMessage, ToolNames: req.ToolNames,
|
||||
ToolCallMessage: req.ToolCallMessage, TaskCreateMessage: req.TaskCreateMessage, ToolNames: req.ToolNames,
|
||||
}); err != nil {
|
||||
writeErr(w, 500, err.Error())
|
||||
return
|
||||
|
||||
@@ -721,11 +721,13 @@ function AgentTriggersTab({ agentKey, agent }: { agentKey: string; agent?: Agent
|
||||
const [onGoalMet, setOnGoalMet] = React.useState(false);
|
||||
const [onTaskTimeout, setOnTaskTimeout] = React.useState(false);
|
||||
const [onToolCall, setOnToolCall] = React.useState(false);
|
||||
const [onTaskCreate, setOnTaskCreate] = React.useState(false);
|
||||
const [intervalMsg, setIntervalMsg] = React.useState("");
|
||||
const [findingMsg, setFindingMsg] = React.useState("");
|
||||
const [goalMsg, setGoalMsg] = React.useState("");
|
||||
const [taskTimeoutMsg, setTaskTimeoutMsg] = React.useState("");
|
||||
const [toolCallMsg, setToolCallMsg] = React.useState("");
|
||||
const [taskCreateMsg, setTaskCreateMsg] = React.useState("");
|
||||
const [toolNames, setToolNames] = React.useState<string[]>([]);
|
||||
const [saving, setSaving] = React.useState(false);
|
||||
|
||||
@@ -745,7 +747,7 @@ function AgentTriggersTab({ agentKey, agent }: { agentKey: string; agent?: Agent
|
||||
|
||||
async function create() {
|
||||
const n = onInterval ? Math.max(1, Math.floor(Number(intervalSec) || 0)) : 0;
|
||||
if (n === 0 && !onFinding && !onGoalMet && !onTaskTimeout && !onToolCall) {
|
||||
if (n === 0 && !onFinding && !onGoalMet && !onTaskTimeout && !onToolCall && !onTaskCreate) {
|
||||
toast.error("至少选择一种触发条件");
|
||||
return;
|
||||
}
|
||||
@@ -762,11 +764,13 @@ function AgentTriggersTab({ agentKey, agent }: { agentKey: string; agent?: Agent
|
||||
on_goal_met: onGoalMet,
|
||||
on_task_timeout: onTaskTimeout,
|
||||
on_tool_call: onToolCall,
|
||||
on_task_create: onTaskCreate,
|
||||
interval_message: intervalMsg.trim(),
|
||||
finding_message: findingMsg.trim(),
|
||||
goal_message: goalMsg.trim(),
|
||||
task_timeout_message: taskTimeoutMsg.trim(),
|
||||
tool_call_message: toolCallMsg.trim(),
|
||||
task_create_message: taskCreateMsg.trim(),
|
||||
tool_names: onToolCall ? toolNames : [],
|
||||
});
|
||||
toast.success("已添加触发器");
|
||||
@@ -776,11 +780,13 @@ function AgentTriggersTab({ agentKey, agent }: { agentKey: string; agent?: Agent
|
||||
setOnGoalMet(false);
|
||||
setOnTaskTimeout(false);
|
||||
setOnToolCall(false);
|
||||
setOnTaskCreate(false);
|
||||
setIntervalMsg("");
|
||||
setFindingMsg("");
|
||||
setGoalMsg("");
|
||||
setTaskTimeoutMsg("");
|
||||
setToolCallMsg("");
|
||||
setTaskCreateMsg("");
|
||||
setToolNames([]);
|
||||
reload();
|
||||
} catch (e) {
|
||||
@@ -798,11 +804,13 @@ function AgentTriggersTab({ agentKey, agent }: { agentKey: string; agent?: Agent
|
||||
on_goal_met: t.on_goal_met,
|
||||
on_task_timeout: t.on_task_timeout,
|
||||
on_tool_call: t.on_tool_call,
|
||||
on_task_create: t.on_task_create,
|
||||
interval_message: t.interval_message,
|
||||
finding_message: t.finding_message,
|
||||
goal_message: t.goal_message,
|
||||
task_timeout_message: t.task_timeout_message,
|
||||
tool_call_message: t.tool_call_message,
|
||||
task_create_message: t.task_create_message,
|
||||
tool_names: t.tool_names,
|
||||
});
|
||||
reload();
|
||||
@@ -826,6 +834,7 @@ function AgentTriggersTab({ agentKey, agent }: { agentKey: string; agent?: Agent
|
||||
if (t.on_goal_met) parts.push("目标达成");
|
||||
if (t.on_task_timeout) parts.push("任务超时");
|
||||
if (t.on_tool_call) parts.push(`工具调用(${t.tool_names.length})`);
|
||||
if (t.on_task_create) parts.push("任务创建");
|
||||
return parts.join(" · ") || "(无条件)";
|
||||
}
|
||||
|
||||
@@ -999,6 +1008,17 @@ function AgentTriggersTab({ agentKey, agent }: { agentKey: string; agent?: Agent
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 任务创建 */}
|
||||
<div className="grid gap-1.5">
|
||||
<label className="flex items-center gap-2 text-sm">
|
||||
<Checkbox checked={onTaskCreate} onCheckedChange={(v) => setOnTaskCreate(!!v)} /> 任务创建时触发
|
||||
</label>
|
||||
{onTaskCreate && (
|
||||
<Textarea className="text-xs" rows={2} value={taskCreateMsg}
|
||||
placeholder="任务被创建时发给 agent 的话(系统会附带任务编号与目标)" onChange={(e) => setTaskCreateMsg(e.target.value)} />
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Button size="sm" onClick={create} disabled={saving}>
|
||||
<SaveIcon /> 添加触发器
|
||||
@@ -1025,6 +1045,7 @@ function AgentTriggersTab({ agentKey, agent }: { agentKey: string; agent?: Agent
|
||||
{t.on_finding && t.finding_message && <div className="line-clamp-1">finding:{t.finding_message}</div>}
|
||||
{t.on_goal_met && t.goal_message && <div className="line-clamp-1">目标:{t.goal_message}</div>}
|
||||
{t.on_task_timeout && t.task_timeout_message && <div className="line-clamp-1">超时:{t.task_timeout_message}</div>}
|
||||
{t.on_task_create && t.task_create_message && <div className="line-clamp-1">任务创建:{t.task_create_message}</div>}
|
||||
{t.on_tool_call && (
|
||||
<>
|
||||
<div className="line-clamp-1">工具:{t.tool_names.join("、") || "(未选)"}</div>
|
||||
|
||||
@@ -293,11 +293,13 @@ export interface AgentTrigger {
|
||||
on_goal_met: boolean; // 任意任务达成目标时触发
|
||||
on_task_timeout: boolean; // 任意任务超时时触发
|
||||
on_tool_call: boolean; // 选中工具被调用(执行完成)时触发
|
||||
on_task_create: boolean; // 任意任务被创建时触发
|
||||
interval_message: string; // 各触发条件的独立用户消息
|
||||
finding_message: string;
|
||||
goal_message: string;
|
||||
task_timeout_message: string;
|
||||
tool_call_message: string;
|
||||
task_create_message: string;
|
||||
tool_names: string[]; // on_tool_call 选中的工具 key(至少一个)
|
||||
last_fire?: string;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user