feat: 提示词新增全局变量 DataDir + 触发合并/种子意图默认值调整
- prompt: 新增全局运行时变量 {{.DataDir}}(服务端数据根目录 s.m.dir),
与 {{.Now}} 一样对所有 agent(planner/worker/mainagent/goals/chat 及自定义)可用;
各 Vars 结构体加 DataDir 字段并在渲染时填入根 workDir,globalPromptVars 登记以便
校验/UI 变量列表/预览识别(纯运行时变量,不入库,无需迁移)。
- trigger_merge_mode 默认 by_task→all(schema/config/后端兜底/前端初值同步)。
- seed_first_intent 默认 true→false(走标准先规划再执行);spawn_task 不再暴露该参数,
工具建任务一律 false。
This commit is contained in:
1 parent
4c33317c6a
commit
80fcf528dc
18 files changed
+61
-43
No files matched your search
+7
-6
@@ -57,14 +57,15 @@ func chatWorkDirSpec(workDir string) string {
|
||||
|
||||
// chatSystem renders the DB-managed prompt body for agentKey. Custom agents have
|
||||
// no per-key in-code default, so DefaultAssistantPrompt is the render fallback.
|
||||
func chatSystem(agentKey, workDir string) string {
|
||||
return renderSystem(agentKey, DefaultAssistantPrompt, chatVars{Now: nowStr()}) + chatWorkDirSpec(workDir)
|
||||
func chatSystem(agentKey, dataDir, workDir string) string {
|
||||
return renderSystem(agentKey, DefaultAssistantPrompt, chatVars{DataDir: dataDir, Now: nowStr()}) + chatWorkDirSpec(workDir)
|
||||
}
|
||||
|
||||
// chatVars carries the runtime variables a custom agent's prompt may reference.
|
||||
// Now (server wall-clock, refreshed each turn) is the only one today; any other
|
||||
// {{.X}} fails to render and falls back to DefaultAssistantPrompt.
|
||||
type chatVars struct{ Now string }
|
||||
// DataDir (server data root) + Now (server wall-clock, refreshed each turn) are the
|
||||
// universal ones; any other {{.X}} fails to render and falls back to
|
||||
// DefaultAssistantPrompt.
|
||||
type chatVars struct{ DataDir, Now string }
|
||||
|
||||
// Chat runs ONE turn of a conversation with the agent identified by agentKey,
|
||||
// resuming prior history keyed by sessionID. maxTurns is the per-turn agent step
|
||||
@@ -94,7 +95,7 @@ func (c *ChatAgent) Chat(ctx context.Context, agentKey, sessionID, message strin
|
||||
tools, def, cleanup := AugmentTools(ctx, agentKey, base)
|
||||
defer cleanup()
|
||||
|
||||
system, boundary := deferredSystem(chatSystem(agentKey, sessionWorkDir), def)
|
||||
system, boundary := deferredSystem(chatSystem(agentKey, c.workDir, sessionWorkDir), def)
|
||||
opts := agentcore.Options{
|
||||
Provider: c.prov,
|
||||
SystemPrompt: system,
|
||||
|
||||
+2
-2
@@ -72,7 +72,7 @@ type GoalSpec struct {
|
||||
//
|
||||
// as + taskID, when non-nil/positive, wire the add_task_scope tool so the
|
||||
// decomposer can register the explicit asset scope it extracts from the goal.
|
||||
func DecomposeGoals(ctx context.Context, c Config, goalText, desc string, as *db.AssetStore, taskID int64, emit func(db.Activity)) []GoalSpec {
|
||||
func DecomposeGoals(ctx context.Context, c Config, dataDir, goalText, desc string, as *db.AssetStore, taskID int64, emit func(db.Activity)) []GoalSpec {
|
||||
prov, err := c.NewProvider()
|
||||
if err != nil {
|
||||
return nil
|
||||
@@ -102,7 +102,7 @@ func DecomposeGoals(ctx context.Context, c Config, goalText, desc string, as *db
|
||||
// Description rides in the user message (same channel as the goal), NOT via the
|
||||
// {{.EngagementDescription}} template var — else a prompt that references the var
|
||||
// would inject the description twice. System prompt stays pure static instructions.
|
||||
sys := renderSystem("goals", goalsDefaultTmpl, GoalsVars{Now: nowStr()})
|
||||
sys := renderSystem("goals", goalsDefaultTmpl, GoalsVars{DataDir: dataDir, Now: nowStr()})
|
||||
tools := []actool.CoreTool{submitTool}
|
||||
// Wire add_task_scope only when we have a real asset store + task to write to.
|
||||
// The scope-extraction tail is appended in lockstep so the prompt never asks for
|
||||
|
||||
+3
-3
@@ -54,8 +54,8 @@ const mainAgentDefaultTmpl = `你是一个授权渗透测试系统的"主 agent"
|
||||
|
||||
不要编造发现;只根据工具返回的真实数据回答。`
|
||||
|
||||
func mainAgentSystem(goal, workDir string) string {
|
||||
body := renderSystem("mainagent", mainAgentDefaultTmpl, MainVars{Goal: goal, Now: nowStr()})
|
||||
func mainAgentSystem(goal, dataDir, workDir string) string {
|
||||
body := renderSystem("mainagent", mainAgentDefaultTmpl, MainVars{Goal: goal, DataDir: dataDir, Now: nowStr()})
|
||||
return body + artifactSpec(workDir)
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ func (m *MainAgent) Chat(ctx context.Context, taskID int64, as *db.AssetStore, t
|
||||
defer cleanup()
|
||||
// 本任务的工作目录 <workDir>/<taskID>,先建好。
|
||||
mainDir := ensureRunDir(m.workDir, taskID, 0)
|
||||
system, boundary := deferredSystem(mainAgentSystem(goal, mainDir), def)
|
||||
system, boundary := deferredSystem(mainAgentSystem(goal, m.workDir, mainDir), def)
|
||||
opts := agentcore.Options{
|
||||
Provider: m.prov,
|
||||
SystemPrompt: system,
|
||||
|
||||
+3
-3
@@ -228,8 +228,8 @@ const plannerDefaultTmpl = `你是一个授权渗透测试系统的"规划者"
|
||||
|
||||
宁可不生成,也不要重复或硬凑。简洁、克制、高效。`
|
||||
|
||||
func plannerSystem(goal, workDir string) string {
|
||||
body := renderSystem("planner", plannerDefaultTmpl, PlannerVars{Goal: goal, Now: nowStr()})
|
||||
func plannerSystem(goal, dataDir, workDir string) string {
|
||||
body := renderSystem("planner", plannerDefaultTmpl, PlannerVars{Goal: goal, DataDir: dataDir, Now: nowStr()})
|
||||
return body + artifactSpec(workDir)
|
||||
}
|
||||
|
||||
@@ -269,7 +269,7 @@ func (p *Planner) Plan(ctx context.Context, taskID int64, as *db.AssetStore, ts
|
||||
}
|
||||
// 本任务的工作目录 <workDir>/<taskID>,先建好。
|
||||
taskDir := ensureRunDir(p.workDir, taskID, 0)
|
||||
system, boundary := deferredSystem(plannerSystem(goal, taskDir), def)
|
||||
system, boundary := deferredSystem(plannerSystem(goal, p.workDir, taskDir), def)
|
||||
// planner 无自身墙钟预算;有 deadline 时把 MaxDuration 夹逼到剩余,让在跑的规划轮在
|
||||
// 任务到点时进收尾(因超时→任务超时词,因步数→per-run 词)。
|
||||
maxDur, clamped := clampMaxDuration(tc.DeadlineUnix, 0)
|
||||
|
||||
+4
-4
@@ -15,10 +15,10 @@ var PromptOverride func(agentKey string) (string, bool)
|
||||
// Prompt-variable structs — fields mirror each agent's catalog (docs §5a) so a
|
||||
// user template referencing a catalog variable renders; referencing anything else
|
||||
// fails template execution and falls back to the built-in default.
|
||||
type PlannerVars struct{ Goal, Scope, AssetSummary, Now string }
|
||||
type WorkerVars struct{ ProxyAddr, WorkerName, Now string }
|
||||
type MainVars struct{ Goal, AssetSummary, FindingsSummary, Now string }
|
||||
type GoalsVars struct{ EngagementDescription, Now string }
|
||||
type PlannerVars struct{ Goal, Scope, AssetSummary, DataDir, Now string }
|
||||
type WorkerVars struct{ ProxyAddr, WorkerName, DataDir, Now string }
|
||||
type MainVars struct{ Goal, AssetSummary, FindingsSummary, DataDir, Now string }
|
||||
type GoalsVars struct{ EngagementDescription, DataDir, Now string }
|
||||
|
||||
// nowStr is the server-local wall-clock string exposed as the universal {{.Now}}
|
||||
// prompt variable. renderSystem runs on every agent turn/round, so this is fresh
|
||||
|
||||
@@ -20,7 +20,7 @@ func TestChatNowVarRenders(t *testing.T) {
|
||||
return "", false
|
||||
}
|
||||
|
||||
out := chatSystem("tec_benchmark", "/tmp/x")
|
||||
out := chatSystem("tec_benchmark", "/app/data", "/tmp/x")
|
||||
if !strings.Contains(out, "当前时间:") {
|
||||
t.Fatalf("custom prompt body missing, likely fell back to default: %q", out)
|
||||
}
|
||||
@@ -30,6 +30,23 @@ func TestChatNowVarRenders(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestChatDataDirVarRenders verifies the universal {{.DataDir}} runtime variable:
|
||||
// a custom prompt referencing it renders the server data root (s.m.dir), rather
|
||||
// than failing template execution and falling back to DefaultAssistantPrompt.
|
||||
func TestChatDataDirVarRenders(t *testing.T) {
|
||||
prev := PromptOverride
|
||||
defer func() { PromptOverride = prev }()
|
||||
|
||||
PromptOverride = func(key string) (string, bool) {
|
||||
return "数据根目录:{{.DataDir}}", true
|
||||
}
|
||||
|
||||
out := chatSystem("tec_benchmark", "/app/data", "/tmp/x")
|
||||
if !strings.Contains(out, "数据根目录:/app/data") {
|
||||
t.Fatalf("{{.DataDir}} did not render the data root: %q", out)
|
||||
}
|
||||
}
|
||||
|
||||
// TestChatUnknownVarFallsBack verifies an out-of-catalog {{.X}} still degrades
|
||||
// safely to the default assistant prompt (never a half-rendered prompt).
|
||||
func TestChatUnknownVarFallsBack(t *testing.T) {
|
||||
@@ -40,7 +57,7 @@ func TestChatUnknownVarFallsBack(t *testing.T) {
|
||||
return "引用了不存在的变量:{{.Bogus}}", true
|
||||
}
|
||||
|
||||
out := chatSystem("whatever", "/tmp/x")
|
||||
out := chatSystem("whatever", "/app/data", "/tmp/x")
|
||||
if strings.Contains(out, "引用了不存在的变量") {
|
||||
t.Fatalf("broken template should have fallen back, got custom body: %q", out)
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ func TestRenderSystemOverrideAndFallback(t *testing.T) {
|
||||
// full plannerSystem path: DB body [A] is honored, then the code-owned tail
|
||||
// [C] (中间产物输出规约) is ALWAYS appended — editing the body can't drop it.
|
||||
PromptOverride = func(k string) (string, bool) { return "PLANNER {{.Goal}}", true }
|
||||
got := plannerSystem("拿下X", "/data")
|
||||
got := plannerSystem("拿下X", "/data", "/data")
|
||||
if !strings.HasPrefix(got, "PLANNER 拿下X") {
|
||||
t.Fatalf("plannerSystem body not honored: %q", got)
|
||||
}
|
||||
@@ -47,7 +47,7 @@ func TestRenderSystemOverrideAndFallback(t *testing.T) {
|
||||
PromptOverride = func(k string) (string, bool) {
|
||||
return "{{if .ProxyAddr}}走代理 {{.ProxyAddr}}{{else}}手动{{end}}", true
|
||||
}
|
||||
withProxy := workerSystem("127.0.0.1:8080", "/data")
|
||||
withProxy := workerSystem("127.0.0.1:8080", "/data", "/data")
|
||||
if !strings.HasPrefix(withProxy, "走代理 127.0.0.1:8080") {
|
||||
t.Fatalf("worker proxy branch body: %q", withProxy)
|
||||
}
|
||||
@@ -57,7 +57,7 @@ func TestRenderSystemOverrideAndFallback(t *testing.T) {
|
||||
if !strings.Contains(withProxy, "中间产物输出规约") {
|
||||
t.Fatalf("worker missing artifact tail: %q", withProxy)
|
||||
}
|
||||
noProxy := workerSystem("", "/data")
|
||||
noProxy := workerSystem("", "/data", "/data")
|
||||
if !strings.HasPrefix(noProxy, "手动") {
|
||||
t.Fatalf("worker no-proxy branch body: %q", noProxy)
|
||||
}
|
||||
|
||||
+3
-3
@@ -182,8 +182,8 @@ func ensureRunDir(base string, taskID, intentID int64) string {
|
||||
// cmdOutDir is the SDK large-tool-output spill dir under an agent's run dir.
|
||||
func cmdOutDir(dir string) string { return filepath.Join(dir, "cmd-output") }
|
||||
|
||||
func workerSystem(proxyAddr, runDir string) string {
|
||||
body := renderSystem("worker", workerDefaultTmpl, WorkerVars{ProxyAddr: proxyAddr, Now: nowStr()})
|
||||
func workerSystem(proxyAddr, dataDir, runDir string) string {
|
||||
body := renderSystem("worker", workerDefaultTmpl, WorkerVars{ProxyAddr: proxyAddr, DataDir: dataDir, Now: nowStr()})
|
||||
return body + workerTrafficBlock(proxyAddr) + workerArtifactSpec(runDir)
|
||||
}
|
||||
|
||||
@@ -263,7 +263,7 @@ func (w *Worker) Execute(ctx context.Context, name string, taskID int64, as *db.
|
||||
// 压缩。本次意图的专属工作目录 <workDir>/<taskID>/i<intentID>,引擎侧先建好。
|
||||
runDir := ensureRunDir(w.workDir, taskID, intent.ID)
|
||||
overview := renderWorkerGraphOverview(tsx.graphOverviewData())
|
||||
system, boundary := deferredSystem(workerSystem(w.proxyAddr, runDir), def)
|
||||
system, boundary := deferredSystem(workerSystem(w.proxyAddr, w.workDir, runDir), def)
|
||||
// 任务级 deadline(经 ctx 注入)夹逼本 run 的墙钟预算 + 决定收尾词(见 taskclock.go)。
|
||||
tc := taskClockFrom(ctx)
|
||||
maxDur, clamped := clampMaxDuration(tc.DeadlineUnix, w.runTimeout)
|
||||
|
||||
+1
-1
@@ -142,7 +142,7 @@ type Agent struct {
|
||||
TriggerMaxParallel int `json:"trigger_max_parallel"`
|
||||
}
|
||||
|
||||
const agentCols = `id,key,name,COALESCE(description,''),role,builtin,enabled,COALESCE(max_turns,0),COALESCE(run_seconds,600),COALESCE(web_search,false),COALESCE(interactive_shell,false),COALESCE(wrapup_prompt,''),COALESCE(wrapup_max_turns,0),COALESCE(task_timeout_wrapup_prompt,''),COALESCE(task_timeout_wrapup_max_turns,0),COALESCE(trigger_run_mode,'serial'),COALESCE(trigger_merge_mode,'by_task'),COALESCE(trigger_max_parallel,5),llm_profile_id`
|
||||
const agentCols = `id,key,name,COALESCE(description,''),role,builtin,enabled,COALESCE(max_turns,0),COALESCE(run_seconds,600),COALESCE(web_search,false),COALESCE(interactive_shell,false),COALESCE(wrapup_prompt,''),COALESCE(wrapup_max_turns,0),COALESCE(task_timeout_wrapup_prompt,''),COALESCE(task_timeout_wrapup_max_turns,0),COALESCE(trigger_run_mode,'serial'),COALESCE(trigger_merge_mode,'all'),COALESCE(trigger_max_parallel,5),llm_profile_id`
|
||||
|
||||
func scanAgent(sc interface{ Scan(...any) error }) (*Agent, error) {
|
||||
var a Agent
|
||||
|
||||
+2
-2
@@ -300,7 +300,7 @@ CREATE TABLE IF NOT EXISTS agents (
|
||||
task_timeout_wrapup_prompt TEXT NOT NULL DEFAULT '',
|
||||
task_timeout_wrapup_max_turns INTEGER NOT NULL DEFAULT 0,
|
||||
trigger_run_mode TEXT NOT NULL DEFAULT 'serial' CHECK (trigger_run_mode IN ('serial','parallel')),
|
||||
trigger_merge_mode TEXT NOT NULL DEFAULT 'by_task' CHECK (trigger_merge_mode IN ('by_task','all','none')),
|
||||
trigger_merge_mode TEXT NOT NULL DEFAULT 'all' CHECK (trigger_merge_mode IN ('by_task','all','none')),
|
||||
trigger_max_parallel INTEGER NOT NULL DEFAULT 5,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
@@ -308,7 +308,7 @@ CREATE TABLE IF NOT EXISTS agents (
|
||||
);
|
||||
-- 加列迁移(已发版,旧库升级补列;新库 CREATE 已含。迁移不带 CHECK:旧库存量安全 + 后端写入白名单兜底)。
|
||||
ALTER TABLE agents ADD COLUMN IF NOT EXISTS trigger_run_mode TEXT NOT NULL DEFAULT 'serial';
|
||||
ALTER TABLE agents ADD COLUMN IF NOT EXISTS trigger_merge_mode TEXT NOT NULL DEFAULT 'by_task';
|
||||
ALTER TABLE agents ADD COLUMN IF NOT EXISTS trigger_merge_mode TEXT NOT NULL DEFAULT 'all';
|
||||
ALTER TABLE agents ADD COLUMN IF NOT EXISTS trigger_max_parallel INTEGER NOT NULL DEFAULT 5;
|
||||
-- per-agent LLM 绑定(agent 级默认模型):列自初版即在上方 CREATE 中,此 ALTER 仅为极旧库兜底(幂等)。
|
||||
ALTER TABLE agents ADD COLUMN IF NOT EXISTS llm_profile_id BIGINT REFERENCES llm_profiles(id) ON DELETE SET NULL;
|
||||
|
||||
@@ -363,9 +363,9 @@ type triggerBehavior struct {
|
||||
}
|
||||
|
||||
// readTriggerBehavior loads an agent's策略, falling back to safe defaults
|
||||
// (serial / by_task / 5) on any error or unknown enum value.
|
||||
// (serial / all / 5) on any error or unknown enum value.
|
||||
func (s *Server) readTriggerBehavior(agentKey string) triggerBehavior {
|
||||
b := triggerBehavior{runMode: "serial", mergeMode: "by_task", maxParallel: 5}
|
||||
b := triggerBehavior{runMode: "serial", mergeMode: "all", maxParallel: 5}
|
||||
if s.m.pg == nil {
|
||||
return b
|
||||
}
|
||||
|
||||
+1
-1
@@ -65,7 +65,7 @@ func (s *Server) createGoals(ctx context.Context, t *Task, emit func(db.Activity
|
||||
as = s.m.Assets()
|
||||
}
|
||||
taskID, _ := strconv.ParseInt(t.ID, 10, 64)
|
||||
for _, g := range agent.DecomposeGoals(ctx, cfg, t.Goal, t.Description, as, taskID, emit) {
|
||||
for _, g := range agent.DecomposeGoals(ctx, cfg, s.m.dir, t.Goal, t.Description, as, taskID, emit) {
|
||||
if strings.TrimSpace(g.Text) != "" {
|
||||
specs = append(specs, goalSpec{Text: g.Text, VulnClass: g.VulnClass})
|
||||
}
|
||||
|
||||
@@ -240,7 +240,6 @@ func (s *Server) toolSpawnTask() actool.CoreTool {
|
||||
"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 = 默认 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) {
|
||||
var a struct {
|
||||
@@ -248,7 +247,6 @@ func (s *Server) toolSpawnTask() actool.CoreTool {
|
||||
LLMProfileID json.RawMessage `json:"llm_profile_id"`
|
||||
TimeoutSeconds int `json:"timeout_seconds"`
|
||||
PlanHeartbeatSeconds int `json:"plan_heartbeat_seconds"`
|
||||
SeedFirstIntent *bool `json:"seed_first_intent"`
|
||||
}
|
||||
_ = json.Unmarshal(in, &a)
|
||||
if strings.TrimSpace(a.Description) == "" {
|
||||
@@ -283,8 +281,9 @@ func (s *Server) toolSpawnTask() actool.CoreTool {
|
||||
}
|
||||
}
|
||||
// 共享的建后流程,与 HTTP 建任务(server.go createTask)复用同一段 launchTask:
|
||||
// seed + 种子意图 + 后台可见地做目标分解(第0轮/LLM步骤/逐条goal) + engine.Run。
|
||||
s.launchTask(t, a.Description+" "+a.Goal, a.SeedFirstIntent == nil || *a.SeedFirstIntent)
|
||||
// seed + 后台可见地做目标分解(第0轮/LLM步骤/逐条goal) + engine.Run。
|
||||
// spawn_task 不暴露 seed_first_intent:工具创建的任务一律走标准先规划再执行(false)。
|
||||
s.launchTask(t, a.Description+" "+a.Goal, false)
|
||||
return actool.Text(fmt.Sprintf("task created: %s", t.ID)), nil
|
||||
})
|
||||
}
|
||||
|
||||
+2
-2
@@ -1018,7 +1018,7 @@ type createTaskReq struct {
|
||||
LLMProfileID *int64 `json:"llm_profile_id,omitempty"` // 指定运行本任务的 LLM 配置;省略/null=用激活配置
|
||||
TimeoutSeconds int `json:"timeout_seconds"` // 任务级超时(秒);0/省略=不限时
|
||||
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 轮。
|
||||
SeedFirstIntent *bool `json:"seed_first_intent,omitempty"` // 创建时直接下发一条种子意图(内容=描述+目标),让 worker 免等首轮 planner 直接开跑;省略/null=默认关闭,走标准先规划再执行。显式传 true 才开(CTF 常一 work 解决时可省掉开跑前的 planner 轮)。
|
||||
}
|
||||
|
||||
func (s *Server) createTask(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -1049,7 +1049,7 @@ func (s *Server) createTask(w http.ResponseWriter, r *http.Request) {
|
||||
log.Printf("[task] 新建任务 #%s «%s» 目标: %s", t.ID, req.Description, req.Goal)
|
||||
// 共享的建后流程(seed + 种子意图 + 后台目标分解 + engine.Run),与 spawn_task 复用同一段。
|
||||
// launchTask 内部异步,不阻塞 UI —— 目标分解在后台可见地进行。
|
||||
s.launchTask(t, req.Description+" "+req.Goal, req.SeedFirstIntent == nil || *req.SeedFirstIntent)
|
||||
s.launchTask(t, req.Description+" "+req.Goal, req.SeedFirstIntent != nil && *req.SeedFirstIntent)
|
||||
writeJSON(w, 201, t)
|
||||
}
|
||||
|
||||
|
||||
@@ -1749,6 +1749,7 @@ func (s *Server) pgListModels(w http.ResponseWriter, r *http.Request) {
|
||||
// {{.Now}} — e.g. subtract it from a fixed start stamp to reason about elapsed time.
|
||||
var globalPromptVars = []db.PromptVar{
|
||||
{Name: "Now", Description: "服务端当前时间(每次运行实时刷新;可与固定起始时间相减判断已用时长)", Example: "2026-08-11 14:30:00 CST", Source: "runtime"},
|
||||
{Name: "DataDir", Description: "服务端数据根目录(所有任务/会话产物的根;各 agent 实际写盘在其下的子目录,如 <DataDir>/<taskID>)", Example: "/app/data", Source: "runtime"},
|
||||
}
|
||||
|
||||
// withGlobalVars appends the universal runtime vars onto an agent's own catalog,
|
||||
|
||||
@@ -112,7 +112,7 @@ export default function TasksPage() {
|
||||
const [llmProfile, setLlmProfile] = React.useState<string>(ACTIVE_PROFILE); // sentinel = active
|
||||
const [timeoutMin, setTimeoutMin] = React.useState(""); // 任务级超时(分钟);空/0 = 不限时
|
||||
const [heartbeatMin, setHeartbeatMin] = React.useState("10"); // planner 心跳(分钟);默认10,下限10(与后端一致)
|
||||
const [seedFirstIntent, setSeedFirstIntent] = React.useState(true); // 创建时下发种子意图,worker 免等首轮 planner 直接开跑
|
||||
const [seedFirstIntent, setSeedFirstIntent] = React.useState(false); // 创建时下发种子意图,worker 免等首轮 planner 直接开跑;默认关闭,走标准先规划再执行
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const [query, setQuery] = React.useState("");
|
||||
const [statusFilter, setStatusFilter] = React.useState<TaskStatus | "all">("all");
|
||||
@@ -182,7 +182,7 @@ export default function TasksPage() {
|
||||
setLlmProfile(ACTIVE_PROFILE);
|
||||
setTimeoutMin("");
|
||||
setHeartbeatMin("10");
|
||||
setSeedFirstIntent(true);
|
||||
setSeedFirstIntent(false);
|
||||
setOpen(false);
|
||||
load();
|
||||
} catch (e) {
|
||||
|
||||
@@ -734,11 +734,11 @@ function AgentTriggersTab({ agentKey, agent }: { agentKey: string; agent?: Agent
|
||||
const [tools, setTools] = React.useState<Tool[]>([]);
|
||||
// 触发后处理策略(每 agent);初值来自 agent detail,改动即保存。
|
||||
const [runMode, setRunMode] = React.useState<"serial" | "parallel">(agent?.trigger_run_mode ?? "serial");
|
||||
const [mergeMode, setMergeMode] = React.useState<"by_task" | "all" | "none">(agent?.trigger_merge_mode ?? "by_task");
|
||||
const [mergeMode, setMergeMode] = React.useState<"by_task" | "all" | "none">(agent?.trigger_merge_mode ?? "all");
|
||||
const [maxParallel, setMaxParallel] = React.useState(String(agent?.trigger_max_parallel ?? 5));
|
||||
React.useEffect(() => {
|
||||
setRunMode(agent?.trigger_run_mode ?? "serial");
|
||||
setMergeMode(agent?.trigger_merge_mode ?? "by_task");
|
||||
setMergeMode(agent?.trigger_merge_mode ?? "all");
|
||||
setMaxParallel(String(agent?.trigger_max_parallel ?? 5));
|
||||
}, [agent?.trigger_run_mode, agent?.trigger_merge_mode, agent?.trigger_max_parallel]);
|
||||
|
||||
|
||||
+1
-1
@@ -115,7 +115,7 @@ export const api = {
|
||||
goal,
|
||||
llm_profile_id: llmProfileId ?? null,
|
||||
timeout_seconds: timeoutSeconds ?? 0,
|
||||
seed_first_intent: seedFirstIntent ?? true,
|
||||
seed_first_intent: seedFirstIntent ?? false,
|
||||
plan_heartbeat_seconds: planHeartbeatSeconds ?? 0, // 0 = 后端归一到默认 600(10min)
|
||||
}),
|
||||
deleteTask: (id: string) => del<{ deleted: number }>(`/tasks/${id}`),
|
||||
|
||||
Reference in new issue
Block a user