feat(agent): 触发后处理可配置(运行模式/合并模式/并发上限) + 修复抽屉内下拉误关
每个自定义 agent 可配置触发后如何调度运行: - 运行模式 serial|parallel:串行排队一次一个 / 每次触发各自并发一个会话 - 合并模式 by_task|all|none(仅 serial):同任务合并 / 全部合并成一条 / 不合并 - parallel 带每 agent 最大并发上限(0=不限),超限排队等空位 实现: - DB: agents 加 trigger_run_mode/trigger_merge_mode/trigger_max_parallel 三列 (schema + 幂等 ALTER 迁移,已发版补旧库);Agent 结构/scan/SetAgentTriggerBehavior - 调度重构: triggerRun bool → triggerActive 计数 + triggerCfg 缓存; StartTriggeredRun 走 pumpLocked/runAndPump 统一泵模型,nextTriggerRun 按合并模式 取(新增 mergeAllRuns);并发安全(session 按 conv-id 隔离,已确认) - config 接口暴露/接收三字段;max_turns 改可选指针,避免触发 tab 部分 patch 清零 - 前端: 触发 tab 顶部加运行/合并模式下拉 + 最大并发输入,改动即存 修复: 抽屉(Sheet)内 Select 下拉 portal 到抽屉外,点选项/点别处被当成点击外部 误关抽屉。SelectContent 改 position=popper,并给 SheetContent 加 onInteractOutside 守卫(按 e.detail.originalEvent.target 判定弹层内点击则不关)。 Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
1 parent
f9716269ea
commit
6d8fa5bc48
10 files changed
+374
-98
No files matched your search
+41
-12
@@ -20,9 +20,9 @@ import (
|
||||
"text/template/parse"
|
||||
"time"
|
||||
|
||||
"github.com/Autumn-27/norma/skill"
|
||||
"github.com/Autumn-27/artex/agent"
|
||||
"github.com/Autumn-27/artex/db"
|
||||
"github.com/Autumn-27/norma/skill"
|
||||
)
|
||||
|
||||
// reSkillName is the base character-set pattern; validSkillName enforces
|
||||
@@ -212,24 +212,32 @@ func (s *Server) pgSaveAgentConfig(w http.ResponseWriter, r *http.Request) {
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
// run_seconds is optional (pointer) so omitting it leaves the stored value
|
||||
// untouched rather than resetting it to 0/unlimited.
|
||||
// All fields optional (pointers) so a partial patch (e.g. the triggers tab sending
|
||||
// only trigger_* fields) leaves the untouched settings alone instead of resetting
|
||||
// max_turns/run_seconds to 0.
|
||||
var req struct {
|
||||
MaxTurns int `json:"max_turns"`
|
||||
MaxTurns *int `json:"max_turns"`
|
||||
RunSeconds *int `json:"run_seconds"`
|
||||
WebSearch *bool `json:"web_search"`
|
||||
InteractiveShell *bool `json:"interactive_shell"`
|
||||
// P3 触发后处理策略(三者一起可选,提供任一即整体写入;未提供则不动)。
|
||||
TriggerRunMode *string `json:"trigger_run_mode"`
|
||||
TriggerMergeMode *string `json:"trigger_merge_mode"`
|
||||
TriggerMaxParallel *int `json:"trigger_max_parallel"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeErr(w, 400, err.Error())
|
||||
return
|
||||
}
|
||||
if req.MaxTurns < 0 {
|
||||
req.MaxTurns = 0
|
||||
}
|
||||
if err := pg.SetAgentMaxTurns(a.Key, req.MaxTurns); err != nil {
|
||||
writeErr(w, 500, err.Error())
|
||||
return
|
||||
if req.MaxTurns != nil {
|
||||
mt := *req.MaxTurns
|
||||
if mt < 0 {
|
||||
mt = 0
|
||||
}
|
||||
if err := pg.SetAgentMaxTurns(a.Key, mt); err != nil {
|
||||
writeErr(w, 500, err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
if req.RunSeconds != nil {
|
||||
rs := *req.RunSeconds
|
||||
@@ -253,6 +261,24 @@ func (s *Server) pgSaveAgentConfig(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
}
|
||||
// P3 触发策略:三者作为一组写入(SetAgentTriggerBehavior 一次写三列),缺的字段用
|
||||
// 当前存量值回填,避免只传一个把另两个覆盖成默认。
|
||||
if req.TriggerRunMode != nil || req.TriggerMergeMode != nil || req.TriggerMaxParallel != nil {
|
||||
runMode, mergeMode, maxPar := a.TriggerRunMode, a.TriggerMergeMode, a.TriggerMaxParallel
|
||||
if req.TriggerRunMode != nil {
|
||||
runMode = *req.TriggerRunMode
|
||||
}
|
||||
if req.TriggerMergeMode != nil {
|
||||
mergeMode = *req.TriggerMergeMode
|
||||
}
|
||||
if req.TriggerMaxParallel != nil {
|
||||
maxPar = *req.TriggerMaxParallel
|
||||
}
|
||||
if err := pg.SetAgentTriggerBehavior(a.Key, runMode, mergeMode, maxPar); err != nil {
|
||||
writeErr(w, 500, err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
// rebuild the live agents so the new max_turns/run_seconds apply without a restart.
|
||||
s.cfgMu.Lock()
|
||||
cfg, on := s.llmCfg, s.llmOn
|
||||
@@ -260,7 +286,10 @@ func (s *Server) pgSaveAgentConfig(w http.ResponseWriter, r *http.Request) {
|
||||
if on {
|
||||
_ = s.applyLLM(cfg)
|
||||
}
|
||||
resp := map[string]any{"ok": true, "max_turns": req.MaxTurns}
|
||||
resp := map[string]any{"ok": true}
|
||||
if req.MaxTurns != nil {
|
||||
resp["max_turns"] = *req.MaxTurns
|
||||
}
|
||||
if req.RunSeconds != nil {
|
||||
resp["run_seconds"] = *req.RunSeconds
|
||||
}
|
||||
@@ -1544,7 +1573,7 @@ func (s *Server) pgActivateProfile(w http.ResponseWriter, r *http.Request) {
|
||||
// the OpenAI path, it falls back to the OpenAI endpoint at the stripped root.
|
||||
func (s *Server) pgListModels(w http.ResponseWriter, r *http.Request) {
|
||||
var req struct {
|
||||
Provider string `json:"provider"` // "openai" | "anthropic"
|
||||
Provider string `json:"provider"` // "openai" | "anthropic"
|
||||
BaseURL string `json:"base_url"`
|
||||
APIKey string `json:"api_key"`
|
||||
Proxy string `json:"proxy"`
|
||||
|
||||
Reference in new issue
Block a user