chore(plan): planner 心跳默认/下限 300s(5min) → 600s(10min)

This commit is contained in:
Autumn-27 committed 2026-08-13 12:23:42 -04:00
1 parent 06e714598d
commit c85b2397e8
4 files changed
+13 -41

No files matched your search

+2 -2
View File
@@ -444,10 +444,10 @@ func (e *Engine) Run(ctx context.Context, t *Task) {
}
// plannerHeartbeatInterval 解析任务的 planner 心跳间隔。db.CreateTask 已归一
// (低于 300 一律抬到 300);这里再兜一次底,防内存态异常值。
// (低于 600 一律抬到 600);这里再兜一次底,防内存态异常值。
func plannerHeartbeatInterval(t *Task) time.Duration {
sec := t.PlanHeartbeatSeconds
if sec < db.MinPlanHeartbeatSeconds { // 下限=默认=300(5min)
if sec < db.MinPlanHeartbeatSeconds { // 下限=默认=600(10min)
sec = db.MinPlanHeartbeatSeconds
}
return time.Duration(sec) * time.Second
+4 -8
View File
@@ -239,7 +239,7 @@ func (s *Server) toolSpawnTask() actool.CoreTool {
"parent_ref": strParam("可选:父任务 id(做父子关联)"),
"llm_profile_id": map[string]any{"type": "integer", "description": "可选:指定本子任务 planner/worker 用的 LLM 配置 id(见 list_llm_profiles);留空则继承父任务、再回退全局激活配置"},
"timeout_seconds": map[string]any{"type": "integer", "description": "可选:任务级超时(秒)。到点后触发优雅收尾并进入 timeout 终态;留空或 0 = 不限时"},
"plan_heartbeat_seconds": map[string]any{"type": "integer", "description": "可选:planner 心跳触发间隔(秒)。距上轮规划结束/任务开始满该值且期间无触发 → 触发一轮规划(兜底死锁 + 唤醒去监督飞行中的 worker)。留空或 0 = 默认 300(5min);"},
"plan_heartbeat_seconds": map[string]any{"type": "integer", "description": "可选:planner 心跳触发间隔(秒)。距上轮规划结束/任务开始满该值且期间无触发 → 触发一轮规划(兜底死锁 + 唤醒去监督飞行中的 worker)。留空或 0 = 默认 600(10min);"},
"seed_first_intent": map[string]any{"type": "boolean", "description": "可选:默认 true。true=创建即把「描述+目标」作为一条种子意图下发,worker 免等首轮 planner 直接开跑(CTF 等常一个 work 解决的场景推荐);false=走标准的先规划再执行"},
}, "description", "goal"),
func(_ context.Context, in json.RawMessage) (actool.Result, error) {
@@ -282,13 +282,9 @@ func (s *Server) toolSpawnTask() actool.CoreTool {
_ = s.m.PG().SetParentRef(id, a.ParentRef)
}
}
s.seed(t, a.Description+" "+a.Goal) // seed 初始资产,喂给事件驱动 loop
// 种子意图(默认开启,仅显式 false 关闭):worker 免等首轮 planner 直接开跑。
if a.SeedFirstIntent == nil || *a.SeedFirstIntent {
s.seedFirstIntent(t)
}
s.createGoals(s.ctx, t, nil) // 目标分解(LLM;规则兜底)
s.engine.Run(s.ctx, t) // 启动该任务的探索引擎
// 共享的建后流程,与 HTTP 建任务(server.go createTask)复用同一段 launchTask:
// seed + 种子意图 + 后台可见地做目标分解(第0轮/LLM步骤/逐条goal) + engine.Run。
s.launchTask(t, a.Description+" "+a.Goal, a.SeedFirstIntent == nil || *a.SeedFirstIntent)
return actool.Text(fmt.Sprintf("task created: %s", t.ID)), nil
})
}
+4 -28
View File
@@ -1017,7 +1017,7 @@ type createTaskReq struct {
Goal string `json:"goal"`
LLMProfileID *int64 `json:"llm_profile_id,omitempty"` // 指定运行本任务的 LLM 配置;省略/null=用激活配置
TimeoutSeconds int `json:"timeout_seconds"` // 任务级超时(秒);0/省略=不限时
PlanHeartbeatSeconds int `json:"plan_heartbeat_seconds"` // planner 心跳触发间隔(秒);0/省略=默认300(5min);下限=默认=300,低于自动抬到300
PlanHeartbeatSeconds int `json:"plan_heartbeat_seconds"` // planner 心跳触发间隔(秒);0/省略=默认600(10min);下限=默认=600,低于自动抬到600
SeedFirstIntent *bool `json:"seed_first_intent,omitempty"` // 创建时直接下发一条种子意图(内容=描述+目标),让 worker 免等首轮 planner 直接开跑;省略/null=默认开启。CTF 常一 work 解决,省掉开跑前的 planner 轮。
}
@@ -1047,34 +1047,10 @@ func (s *Server) createTask(w http.ResponseWriter, r *http.Request) {
return
}
log.Printf("[task] 新建任务 #%s «%s» 目标: %s", t.ID, req.Description, req.Goal)
// seed initial asset(s) from goal/description so the event-driven loop has a root.
s.seed(t, req.Description+" "+req.Goal)
// 种子意图(默认开启):直接把"描述+目标"作为一条待办意图写入 frontier,worker 轮询即可
// 领取开跑,省掉开跑前的首轮 planner LLM;跑完由 NotifyDone 正常唤醒 planner 判定/补充。
// CTF 场景常一个 work 就解决。仅显式传 false 才关闭。
if req.SeedFirstIntent == nil || *req.SeedFirstIntent {
s.seedFirstIntent(t)
}
// Return immediately — goal decomposition is async so the UI isn't blocked.
// 共享的建后流程(seed + 种子意图 + 后台目标分解 + engine.Run),与 spawn_task 复用同一段。
// launchTask 内部异步,不阻塞 UI —— 目标分解在后台可见地进行。
s.launchTask(t, req.Description+" "+req.Goal, req.SeedFirstIntent == nil || *req.SeedFirstIntent)
writeJSON(w, 201, t)
// Background: emit round-0 marker, decompose goals via LLM, then start engine.
// Engine starts only after goals are seeded to avoid a race where the planner
// fires before any goal nodes exist.
go func() {
s.engine.emitActivity(t, db.Activity{Worker: "planner", Kind: "round",
Summary: "第 0 轮目标拆解"})
goals := s.createGoals(s.ctx, t, func(r db.Activity) {
s.engine.emitActivity(t, r)
})
for _, g := range goals {
summary := g.Text
if g.VulnClass != "" {
summary = fmt.Sprintf("[%s] %s", g.VulnClass, g.Text)
}
s.engine.emitActivity(t, db.Activity{Worker: "planner", Kind: "text", Summary: summary})
}
s.engine.Run(s.ctx, t)
}()
}
var (