mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-08-29 14:10:50 +02:00
feat: persist hitl default config
This commit is contained in:
+5
-1
@@ -135,8 +135,12 @@ agent:
|
|||||||
# approval → audit_agent_prompt
|
# approval → audit_agent_prompt
|
||||||
# review_edit → audit_agent_prompt_review_edit(可改参后放行)
|
# review_edit → audit_agent_prompt_review_edit(可改参后放行)
|
||||||
hitl:
|
hitl:
|
||||||
# 全局默认审批方:human=人工审批,audit_agent=审计 Agent;未选会话时切换会写入本项,重启后仍生效
|
# 全局默认人机协同模式:off=关闭,approval=审批模式,review_edit=审查编辑;新建会话无独立配置时沿用
|
||||||
|
default_mode: off
|
||||||
|
# 全局默认审批方:human=人工审批,audit_agent=审计 Agent;新建会话无独立配置时沿用
|
||||||
default_reviewer: human
|
default_reviewer: human
|
||||||
|
# 全局默认审批等待时限(秒):300=5分钟,0=不限时;新建会话无独立配置时沿用
|
||||||
|
default_timeout_seconds: 300
|
||||||
# 审计 Agent 专用模型;字段留空则复用上方 openai 配置。建议 model 填小模型,用于降低审批成本。
|
# 审计 Agent 专用模型;字段留空则复用上方 openai 配置。建议 model 填小模型,用于降低审批成本。
|
||||||
audit_model:
|
audit_model:
|
||||||
provider: "" # openai / claude;留空跟随 openai.provider
|
provider: "" # openai / claude;留空跟随 openai.provider
|
||||||
|
|||||||
@@ -972,6 +972,8 @@ func setupRoutes(
|
|||||||
protected.GET("/hitl/tool-whitelist", agentHandler.GetHITLGlobalToolWhitelist)
|
protected.GET("/hitl/tool-whitelist", agentHandler.GetHITLGlobalToolWhitelist)
|
||||||
protected.PUT("/hitl/tool-whitelist", agentHandler.SetHITLGlobalToolWhitelist)
|
protected.PUT("/hitl/tool-whitelist", agentHandler.SetHITLGlobalToolWhitelist)
|
||||||
protected.POST("/hitl/tool-whitelist", agentHandler.MergeHITLGlobalToolWhitelist)
|
protected.POST("/hitl/tool-whitelist", agentHandler.MergeHITLGlobalToolWhitelist)
|
||||||
|
protected.GET("/hitl/default-config", agentHandler.GetHITLDefaultConfig)
|
||||||
|
protected.PUT("/hitl/default-config", agentHandler.UpdateHITLDefaultConfig)
|
||||||
protected.GET("/hitl/default-reviewer", agentHandler.GetHITLDefaultReviewer)
|
protected.GET("/hitl/default-reviewer", agentHandler.GetHITLDefaultReviewer)
|
||||||
protected.PUT("/hitl/default-reviewer", agentHandler.UpdateHITLDefaultReviewer)
|
protected.PUT("/hitl/default-reviewer", agentHandler.UpdateHITLDefaultReviewer)
|
||||||
protected.GET("/hitl/audit-strategy", agentHandler.GetHITLAuditStrategy)
|
protected.GET("/hitl/audit-strategy", agentHandler.GetHITLAuditStrategy)
|
||||||
|
|||||||
@@ -1062,8 +1062,24 @@ type HitlConfig struct {
|
|||||||
AuditAgentPromptReviewEdit string `yaml:"audit_agent_prompt_review_edit,omitempty" json:"audit_agent_prompt_review_edit,omitempty"`
|
AuditAgentPromptReviewEdit string `yaml:"audit_agent_prompt_review_edit,omitempty" json:"audit_agent_prompt_review_edit,omitempty"`
|
||||||
// RetentionDays 已决策审计日志(hitl_interrupts 非 pending)保留天数;省略时默认 90;0 表示不自动清理。
|
// RetentionDays 已决策审计日志(hitl_interrupts 非 pending)保留天数;省略时默认 90;0 表示不自动清理。
|
||||||
RetentionDays *int `yaml:"retention_days,omitempty" json:"retention_days,omitempty"`
|
RetentionDays *int `yaml:"retention_days,omitempty" json:"retention_days,omitempty"`
|
||||||
// DefaultReviewer 全局默认审批方(human | audit_agent);未选会话时切换会写入 config.yaml;新建会话无独立配置时沿用。
|
// DefaultMode 全局默认人机协同模式(off | approval | review_edit);新建会话无独立配置时沿用。
|
||||||
|
DefaultMode string `yaml:"default_mode,omitempty" json:"default_mode,omitempty"`
|
||||||
|
// DefaultReviewer 全局默认审批方(human | audit_agent);新建会话无独立配置时沿用。
|
||||||
DefaultReviewer string `yaml:"default_reviewer,omitempty" json:"default_reviewer,omitempty"`
|
DefaultReviewer string `yaml:"default_reviewer,omitempty" json:"default_reviewer,omitempty"`
|
||||||
|
// DefaultTimeoutSeconds 全局默认审批等待秒数;nil 表示使用前端历史默认 300 秒,0 表示不限时。
|
||||||
|
DefaultTimeoutSeconds *int `yaml:"default_timeout_seconds,omitempty" json:"default_timeout_seconds,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// EffectiveDefaultMode returns off, approval, or review_edit; omitted or unknown values default to off.
|
||||||
|
func (h HitlConfig) EffectiveDefaultMode() string {
|
||||||
|
switch strings.ToLower(strings.TrimSpace(h.DefaultMode)) {
|
||||||
|
case "feedback", "followup":
|
||||||
|
return "approval"
|
||||||
|
case "approval", "review_edit":
|
||||||
|
return strings.ToLower(strings.TrimSpace(h.DefaultMode))
|
||||||
|
default:
|
||||||
|
return "off"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// EffectiveDefaultReviewer returns human or audit_agent; omitted or unknown values default to human.
|
// EffectiveDefaultReviewer returns human or audit_agent; omitted or unknown values default to human.
|
||||||
@@ -1076,6 +1092,17 @@ func (h HitlConfig) EffectiveDefaultReviewer() string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EffectiveDefaultTimeoutSeconds returns the default HITL approval timeout; nil defaults to 5 minutes.
|
||||||
|
func (h HitlConfig) EffectiveDefaultTimeoutSeconds() int {
|
||||||
|
if h.DefaultTimeoutSeconds == nil {
|
||||||
|
return 300
|
||||||
|
}
|
||||||
|
if *h.DefaultTimeoutSeconds < 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return *h.DefaultTimeoutSeconds
|
||||||
|
}
|
||||||
|
|
||||||
// RetentionDaysEffective returns retention; 0 means keep forever; omitted defaults to 90.
|
// RetentionDaysEffective returns retention; 0 means keep forever; omitted defaults to 90.
|
||||||
func (h HitlConfig) RetentionDaysEffective() int {
|
func (h HitlConfig) RetentionDaysEffective() int {
|
||||||
if h.RetentionDays == nil {
|
if h.RetentionDays == nil {
|
||||||
|
|||||||
@@ -95,6 +95,29 @@ func TestHitlAuditModelEffectiveFallsBackToMainConfig(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestHitlDefaultConfigEffectiveValues(t *testing.T) {
|
||||||
|
if got := (HitlConfig{}).EffectiveDefaultMode(); got != "off" {
|
||||||
|
t.Fatalf("empty default mode = %q, want off", got)
|
||||||
|
}
|
||||||
|
if got := (HitlConfig{DefaultMode: "review-edit"}).EffectiveDefaultMode(); got != "off" {
|
||||||
|
t.Fatalf("unknown default mode = %q, want off", got)
|
||||||
|
}
|
||||||
|
if got := (HitlConfig{DefaultMode: "review_edit"}).EffectiveDefaultMode(); got != "review_edit" {
|
||||||
|
t.Fatalf("review_edit default mode = %q, want review_edit", got)
|
||||||
|
}
|
||||||
|
if got := (HitlConfig{}).EffectiveDefaultTimeoutSeconds(); got != 300 {
|
||||||
|
t.Fatalf("empty default timeout = %d, want 300", got)
|
||||||
|
}
|
||||||
|
zero := 0
|
||||||
|
if got := (HitlConfig{DefaultTimeoutSeconds: &zero}).EffectiveDefaultTimeoutSeconds(); got != 0 {
|
||||||
|
t.Fatalf("zero default timeout = %d, want 0", got)
|
||||||
|
}
|
||||||
|
neg := -1
|
||||||
|
if got := (HitlConfig{DefaultTimeoutSeconds: &neg}).EffectiveDefaultTimeoutSeconds(); got != 0 {
|
||||||
|
t.Fatalf("negative default timeout = %d, want 0", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestLoadUsesAIDefaultChannelAsRuntimeOpenAI(t *testing.T) {
|
func TestLoadUsesAIDefaultChannelAsRuntimeOpenAI(t *testing.T) {
|
||||||
dir := t.TempDir()
|
dir := t.TempDir()
|
||||||
path := filepath.Join(dir, "config.yaml")
|
path := filepath.Join(dir, "config.yaml")
|
||||||
|
|||||||
@@ -315,12 +315,13 @@ func (h *AgentHandler) SetHitlToolWhitelistSaver(s HitlToolWhitelistSaver) {
|
|||||||
h.hitlWhitelistSaver = s
|
h.hitlWhitelistSaver = s
|
||||||
}
|
}
|
||||||
|
|
||||||
// HitlDefaultReviewerSaver 持久化全局默认审批方到 config.yaml。
|
// HitlDefaultReviewerSaver 持久化全局默认人机协同配置到 config.yaml。
|
||||||
type HitlDefaultReviewerSaver interface {
|
type HitlDefaultReviewerSaver interface {
|
||||||
|
UpdateHitlDefaultConfig(mode, reviewer string, timeoutSeconds int) error
|
||||||
UpdateHitlDefaultReviewer(reviewer string) error
|
UpdateHitlDefaultReviewer(reviewer string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetHitlDefaultReviewerSaver 设置 HITL 默认审批方落盘。
|
// SetHitlDefaultReviewerSaver 设置 HITL 默认配置落盘。
|
||||||
func (h *AgentHandler) SetHitlDefaultReviewerSaver(s HitlDefaultReviewerSaver) {
|
func (h *AgentHandler) SetHitlDefaultReviewerSaver(s HitlDefaultReviewerSaver) {
|
||||||
h.hitlDefaultReviewerSaver = s
|
h.hitlDefaultReviewerSaver = s
|
||||||
}
|
}
|
||||||
@@ -332,6 +333,35 @@ func (h *AgentHandler) hitlEffectiveDefaultReviewer() string {
|
|||||||
return "human"
|
return "human"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *AgentHandler) hitlEffectiveDefaultMode() string {
|
||||||
|
if h != nil && h.config != nil {
|
||||||
|
return normalizeHitlDefaultMode(h.config.Hitl.EffectiveDefaultMode())
|
||||||
|
}
|
||||||
|
return "off"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *AgentHandler) hitlEffectiveDefaultTimeoutSeconds() int {
|
||||||
|
if h != nil && h.config != nil {
|
||||||
|
timeout := h.config.Hitl.EffectiveDefaultTimeoutSeconds()
|
||||||
|
if timeout < 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return timeout
|
||||||
|
}
|
||||||
|
return 300
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *AgentHandler) hitlEffectiveDefaultRequest() *HITLRequest {
|
||||||
|
mode := h.hitlEffectiveDefaultMode()
|
||||||
|
return &HITLRequest{
|
||||||
|
Enabled: mode != "off",
|
||||||
|
Mode: mode,
|
||||||
|
Reviewer: h.hitlEffectiveDefaultReviewer(),
|
||||||
|
SensitiveTools: []string{},
|
||||||
|
TimeoutSeconds: h.hitlEffectiveDefaultTimeoutSeconds(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// HITLNeedsToolApproval 供 C2 危险任务门控:与会话侧人机协同及免审批白名单判定一致。
|
// HITLNeedsToolApproval 供 C2 危险任务门控:与会话侧人机协同及免审批白名单判定一致。
|
||||||
func (h *AgentHandler) HITLNeedsToolApproval(conversationID, toolName string) bool {
|
func (h *AgentHandler) HITLNeedsToolApproval(conversationID, toolName string) bool {
|
||||||
if h == nil || h.hitlManager == nil {
|
if h == nil || h.hitlManager == nil {
|
||||||
|
|||||||
@@ -891,7 +891,14 @@ func (h *ConfigHandler) UpdateConfig(c *gin.Context) {
|
|||||||
if req.Hitl != nil {
|
if req.Hitl != nil {
|
||||||
h.config.Hitl.AuditModel = req.Hitl.AuditModel
|
h.config.Hitl.AuditModel = req.Hitl.AuditModel
|
||||||
h.config.Hitl.ToolWhitelist = mergeHitlToolWhitelistSlice(nil, req.Hitl.ToolWhitelist)
|
h.config.Hitl.ToolWhitelist = mergeHitlToolWhitelistSlice(nil, req.Hitl.ToolWhitelist)
|
||||||
|
if strings.TrimSpace(req.Hitl.DefaultMode) != "" {
|
||||||
|
h.config.Hitl.DefaultMode = req.Hitl.EffectiveDefaultMode()
|
||||||
|
}
|
||||||
h.config.Hitl.DefaultReviewer = req.Hitl.EffectiveDefaultReviewer()
|
h.config.Hitl.DefaultReviewer = req.Hitl.EffectiveDefaultReviewer()
|
||||||
|
if req.Hitl.DefaultTimeoutSeconds != nil {
|
||||||
|
v := req.Hitl.EffectiveDefaultTimeoutSeconds()
|
||||||
|
h.config.Hitl.DefaultTimeoutSeconds = &v
|
||||||
|
}
|
||||||
h.config.Hitl.AuditAgentPrompt = strings.TrimSpace(req.Hitl.AuditAgentPrompt)
|
h.config.Hitl.AuditAgentPrompt = strings.TrimSpace(req.Hitl.AuditAgentPrompt)
|
||||||
h.config.Hitl.AuditAgentPromptReviewEdit = strings.TrimSpace(req.Hitl.AuditAgentPromptReviewEdit)
|
h.config.Hitl.AuditAgentPromptReviewEdit = strings.TrimSpace(req.Hitl.AuditAgentPromptReviewEdit)
|
||||||
if req.Hitl.RetentionDays != nil {
|
if req.Hitl.RetentionDays != nil {
|
||||||
@@ -2141,12 +2148,35 @@ func updateHitlConfig(doc *yaml.Node, cfg config.HitlConfig) {
|
|||||||
setStringInMap(auditModelNode, "model", cfg.AuditModel.Model)
|
setStringInMap(auditModelNode, "model", cfg.AuditModel.Model)
|
||||||
// flow 样式 [a, b, c] 单行展示,工具多时比块序列省行数
|
// flow 样式 [a, b, c] 单行展示,工具多时比块序列省行数
|
||||||
setFlowStringSliceInMap(hitlNode, "tool_whitelist", cfg.ToolWhitelist)
|
setFlowStringSliceInMap(hitlNode, "tool_whitelist", cfg.ToolWhitelist)
|
||||||
|
setStringInMap(hitlNode, "default_mode", cfg.EffectiveDefaultMode())
|
||||||
setStringInMap(hitlNode, "default_reviewer", cfg.EffectiveDefaultReviewer())
|
setStringInMap(hitlNode, "default_reviewer", cfg.EffectiveDefaultReviewer())
|
||||||
|
setIntInMap(hitlNode, "default_timeout_seconds", cfg.EffectiveDefaultTimeoutSeconds())
|
||||||
setIntInMap(hitlNode, "retention_days", cfg.RetentionDaysEffective())
|
setIntInMap(hitlNode, "retention_days", cfg.RetentionDaysEffective())
|
||||||
setStringInMap(hitlNode, "audit_agent_prompt", cfg.AuditAgentPrompt)
|
setStringInMap(hitlNode, "audit_agent_prompt", cfg.AuditAgentPrompt)
|
||||||
setStringInMap(hitlNode, "audit_agent_prompt_review_edit", cfg.AuditAgentPromptReviewEdit)
|
setStringInMap(hitlNode, "audit_agent_prompt_review_edit", cfg.AuditAgentPromptReviewEdit)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateHitlDefaultConfig 更新全局默认人机协同配置并写入 config.yaml。
|
||||||
|
func (h *ConfigHandler) UpdateHitlDefaultConfig(mode, reviewer string, timeoutSeconds int) error {
|
||||||
|
h.mu.Lock()
|
||||||
|
defer h.mu.Unlock()
|
||||||
|
h.config.Hitl.DefaultMode = config.HitlConfig{DefaultMode: mode}.EffectiveDefaultMode()
|
||||||
|
h.config.Hitl.DefaultReviewer = config.HitlConfig{DefaultReviewer: reviewer}.EffectiveDefaultReviewer()
|
||||||
|
if timeoutSeconds < 0 {
|
||||||
|
timeoutSeconds = 0
|
||||||
|
}
|
||||||
|
h.config.Hitl.DefaultTimeoutSeconds = &timeoutSeconds
|
||||||
|
if err := h.saveConfig(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
h.logger.Info("HITL 全局默认配置已写入配置文件",
|
||||||
|
zap.String("default_mode", h.config.Hitl.DefaultMode),
|
||||||
|
zap.String("default_reviewer", h.config.Hitl.DefaultReviewer),
|
||||||
|
zap.Int("default_timeout_seconds", timeoutSeconds),
|
||||||
|
)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// UpdateHitlDefaultReviewer 更新全局默认审批方并写入 config.yaml。
|
// UpdateHitlDefaultReviewer 更新全局默认审批方并写入 config.yaml。
|
||||||
func (h *ConfigHandler) UpdateHitlDefaultReviewer(reviewer string) error {
|
func (h *ConfigHandler) UpdateHitlDefaultReviewer(reviewer string) error {
|
||||||
h.mu.Lock()
|
h.mu.Lock()
|
||||||
|
|||||||
@@ -289,6 +289,18 @@ func normalizeHitlMode(mode string) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func normalizeHitlDefaultMode(mode string) string {
|
||||||
|
v := strings.ToLower(strings.TrimSpace(mode))
|
||||||
|
switch v {
|
||||||
|
case "feedback", "followup":
|
||||||
|
return "approval"
|
||||||
|
case "approval", "review_edit":
|
||||||
|
return v
|
||||||
|
default:
|
||||||
|
return "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (m *HITLManager) ActivateConversation(conversationID string, req *HITLRequest) {
|
func (m *HITLManager) ActivateConversation(conversationID string, req *HITLRequest) {
|
||||||
if req == nil || !req.Enabled {
|
if req == nil || !req.Enabled {
|
||||||
m.DeactivateConversation(conversationID)
|
m.DeactivateConversation(conversationID)
|
||||||
@@ -629,7 +641,7 @@ func (h *AgentHandler) loadHITLConversationConfig(conversationID string) (*HITLR
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if !has {
|
if !has {
|
||||||
cfg.Reviewer = h.hitlEffectiveDefaultReviewer()
|
return h.hitlEffectiveDefaultRequest(), nil
|
||||||
}
|
}
|
||||||
return cfg, nil
|
return cfg, nil
|
||||||
}
|
}
|
||||||
@@ -994,7 +1006,9 @@ func (h *AgentHandler) GetHITLConversationConfig(c *gin.Context) {
|
|||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"conversationId": conversationID,
|
"conversationId": conversationID,
|
||||||
"hitl": cfg,
|
"hitl": cfg,
|
||||||
|
"defaultMode": h.hitlEffectiveDefaultMode(),
|
||||||
"defaultReviewer": h.hitlEffectiveDefaultReviewer(),
|
"defaultReviewer": h.hitlEffectiveDefaultReviewer(),
|
||||||
|
"defaultTimeoutSeconds": h.hitlEffectiveDefaultTimeoutSeconds(),
|
||||||
"hitlGlobalToolWhitelist": h.hitlConfigGlobalToolWhitelist(),
|
"hitlGlobalToolWhitelist": h.hitlConfigGlobalToolWhitelist(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -1051,11 +1065,64 @@ type setHitlDefaultReviewerReq struct {
|
|||||||
Reviewer string `json:"reviewer"`
|
Reviewer string `json:"reviewer"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type setHitlDefaultConfigReq struct {
|
||||||
|
Mode string `json:"mode"`
|
||||||
|
Reviewer string `json:"reviewer"`
|
||||||
|
TimeoutSeconds int `json:"timeoutSeconds"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *AgentHandler) hitlDefaultConfigResponse() gin.H {
|
||||||
|
return gin.H{
|
||||||
|
"defaultMode": h.hitlEffectiveDefaultMode(),
|
||||||
|
"defaultReviewer": h.hitlEffectiveDefaultReviewer(),
|
||||||
|
"defaultTimeoutSeconds": h.hitlEffectiveDefaultTimeoutSeconds(),
|
||||||
|
"hitlGlobalToolWhitelist": h.hitlConfigGlobalToolWhitelist(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetHITLDefaultConfig 返回 config.yaml 中的全局默认人机协同配置。
|
||||||
|
func (h *AgentHandler) GetHITLDefaultConfig(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusOK, h.hitlDefaultConfigResponse())
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateHITLDefaultConfig 将全局默认人机协同配置写入 config.yaml。
|
||||||
|
func (h *AgentHandler) UpdateHITLDefaultConfig(c *gin.Context) {
|
||||||
|
if h.hitlDefaultReviewerSaver == nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "HITL 配置持久化不可用"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var req setHitlDefaultConfigReq
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
mode := normalizeHitlDefaultMode(req.Mode)
|
||||||
|
reviewer := normalizeHitlReviewer(req.Reviewer)
|
||||||
|
timeoutSeconds := req.TimeoutSeconds
|
||||||
|
if timeoutSeconds < 0 {
|
||||||
|
timeoutSeconds = 0
|
||||||
|
}
|
||||||
|
if err := h.hitlDefaultReviewerSaver.UpdateHitlDefaultConfig(mode, reviewer, timeoutSeconds); err != nil {
|
||||||
|
h.logger.Warn("写入 HITL 默认配置到 config.yaml 失败", zap.Error(err))
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if h.config != nil {
|
||||||
|
h.config.Hitl.DefaultMode = mode
|
||||||
|
h.config.Hitl.DefaultReviewer = reviewer
|
||||||
|
h.config.Hitl.DefaultTimeoutSeconds = &timeoutSeconds
|
||||||
|
}
|
||||||
|
if h.audit != nil {
|
||||||
|
h.audit.RecordOK(c, "hitl", "default_config_update", "HITL 全局默认配置更新", "hitl_config", "default", nil)
|
||||||
|
}
|
||||||
|
out := h.hitlDefaultConfigResponse()
|
||||||
|
out["ok"] = true
|
||||||
|
c.JSON(http.StatusOK, out)
|
||||||
|
}
|
||||||
|
|
||||||
// GetHITLDefaultReviewer 返回 config.yaml 中的全局默认审批方。
|
// GetHITLDefaultReviewer 返回 config.yaml 中的全局默认审批方。
|
||||||
func (h *AgentHandler) GetHITLDefaultReviewer(c *gin.Context) {
|
func (h *AgentHandler) GetHITLDefaultReviewer(c *gin.Context) {
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, h.hitlDefaultConfigResponse())
|
||||||
"defaultReviewer": h.hitlEffectiveDefaultReviewer(),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateHITLDefaultReviewer 将全局默认审批方写入 config.yaml(未选会话时切换审批方)。
|
// UpdateHITLDefaultReviewer 将全局默认审批方写入 config.yaml(未选会话时切换审批方)。
|
||||||
@@ -1081,10 +1148,9 @@ func (h *AgentHandler) UpdateHITLDefaultReviewer(c *gin.Context) {
|
|||||||
if h.audit != nil {
|
if h.audit != nil {
|
||||||
h.audit.RecordOK(c, "hitl", "default_reviewer_update", "HITL 全局默认审批方更新", "hitl_config", "default_reviewer", nil)
|
h.audit.RecordOK(c, "hitl", "default_reviewer_update", "HITL 全局默认审批方更新", "hitl_config", "default_reviewer", nil)
|
||||||
}
|
}
|
||||||
c.JSON(http.StatusOK, gin.H{
|
out := h.hitlDefaultConfigResponse()
|
||||||
"ok": true,
|
out["ok"] = true
|
||||||
"defaultReviewer": reviewer,
|
c.JSON(http.StatusOK, out)
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetHITLGlobalToolWhitelist 整表替换 config.yaml 中的全局免审批工具白名单。
|
// SetHITLGlobalToolWhitelist 整表替换 config.yaml 中的全局免审批工具白名单。
|
||||||
|
|||||||
@@ -217,7 +217,7 @@ func resourceAllowed(c *gin.Context, db *database.DB) bool {
|
|||||||
return session.Scope == database.RBACScopeAll
|
return session.Scope == database.RBACScopeAll
|
||||||
case strings.HasPrefix(path, "/c2/profiles") && c.Request.Method != http.MethodGet:
|
case strings.HasPrefix(path, "/c2/profiles") && c.Request.Method != http.MethodGet:
|
||||||
return session.Scope == database.RBACScopeAll
|
return session.Scope == database.RBACScopeAll
|
||||||
case (strings.HasPrefix(path, "/hitl/tool-whitelist") || strings.HasPrefix(path, "/hitl/default-reviewer") || strings.HasPrefix(path, "/hitl/audit-strategy")) && c.Request.Method != http.MethodGet:
|
case (strings.HasPrefix(path, "/hitl/tool-whitelist") || strings.HasPrefix(path, "/hitl/default-config") || strings.HasPrefix(path, "/hitl/default-reviewer") || strings.HasPrefix(path, "/hitl/audit-strategy")) && c.Request.Method != http.MethodGet:
|
||||||
return session.Scope == database.RBACScopeAll
|
return session.Scope == database.RBACScopeAll
|
||||||
case isMutationMethod(c.Request.Method) && isProcessGlobalMutationPath(path):
|
case isMutationMethod(c.Request.Method) && isProcessGlobalMutationPath(path):
|
||||||
// These definitions/configurations are shared by every user and do not
|
// These definitions/configurations are shared by every user and do not
|
||||||
|
|||||||
@@ -819,6 +819,7 @@
|
|||||||
"hitlWhitelistHint": "Separate with commas or new lines; shown merged with the global allowlist in config.",
|
"hitlWhitelistHint": "Separate with commas or new lines; shown merged with the global allowlist in config.",
|
||||||
"hitlApply": "Apply",
|
"hitlApply": "Apply",
|
||||||
"hitlApplyOkSync": "HITL settings saved and synced to the server.",
|
"hitlApplyOkSync": "HITL settings saved and synced to the server.",
|
||||||
|
"hitlApplyOkDefaultConfig": "Default HITL settings saved to config.yaml and activated.",
|
||||||
"hitlApplyOkWhitelistYaml": "Tool whitelist merged into config.yaml and active. Session settings are saved automatically.",
|
"hitlApplyOkWhitelistYaml": "Tool whitelist merged into config.yaml and active. Session settings are saved automatically.",
|
||||||
"hitlApplyOkLocal": "Saved in this browser.",
|
"hitlApplyOkLocal": "Saved in this browser.",
|
||||||
"hitlApplyFail": "Failed to sync to server",
|
"hitlApplyFail": "Failed to sync to server",
|
||||||
|
|||||||
@@ -807,6 +807,7 @@
|
|||||||
"hitlWhitelistHint": "白名单内工具免审批;每行一个或逗号分隔,与 config 全局白名单合并。",
|
"hitlWhitelistHint": "白名单内工具免审批;每行一个或逗号分隔,与 config 全局白名单合并。",
|
||||||
"hitlApply": "应用",
|
"hitlApply": "应用",
|
||||||
"hitlApplyOkSync": "人机协同配置已保存并同步到服务器。",
|
"hitlApplyOkSync": "人机协同配置已保存并同步到服务器。",
|
||||||
|
"hitlApplyOkDefaultConfig": "人机协同默认配置已写入 config.yaml 并生效。",
|
||||||
"hitlApplyOkWhitelistYaml": "免审批工具已合并进 config.yaml 并生效。会话配置会自动保存。",
|
"hitlApplyOkWhitelistYaml": "免审批工具已合并进 config.yaml 并生效。会话配置会自动保存。",
|
||||||
"hitlApplyOkLocal": "已保存到本浏览器。",
|
"hitlApplyOkLocal": "已保存到本浏览器。",
|
||||||
"hitlApplyFail": "同步到服务器失败",
|
"hitlApplyFail": "同步到服务器失败",
|
||||||
|
|||||||
+27
-82
@@ -154,9 +154,6 @@ const chatSystemModelCache = new Map();
|
|||||||
|
|
||||||
// 人机协同(HITL)会话级配置
|
// 人机协同(HITL)会话级配置
|
||||||
const HITL_STORAGE_PREFIX = 'cyberstrike-chat-hitl';
|
const HITL_STORAGE_PREFIX = 'cyberstrike-chat-hitl';
|
||||||
const HITL_DRAFT_KEY = 'cyberstrike-chat-hitl-draft';
|
|
||||||
/** 跨会话记忆:用户最近一次在侧栏选择的 HITL 偏好(与 hitl.js 中 readHitlGlobalLast 使用同一 key) */
|
|
||||||
const HITL_GLOBAL_LAST_KEY = `${HITL_STORAGE_PREFIX}:__last__`;
|
|
||||||
const HITL_MODE_OFF = 'off';
|
const HITL_MODE_OFF = 'off';
|
||||||
const HITL_MODE_APPROVAL = 'approval';
|
const HITL_MODE_APPROVAL = 'approval';
|
||||||
const HITL_MODE_REVIEW_EDIT = 'review_edit';
|
const HITL_MODE_REVIEW_EDIT = 'review_edit';
|
||||||
@@ -432,14 +429,17 @@ function normalizeHitlTimeoutForChat(value, fallback) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function defaultHitlConfig() {
|
function defaultHitlConfig() {
|
||||||
const serverReviewer = (typeof window !== 'undefined' && window.csaiHitlDefaultReviewer)
|
const serverDefault = (typeof window !== 'undefined' && window.csaiHitlDefaultConfig && typeof window.csaiHitlDefaultConfig === 'object')
|
||||||
|
? window.csaiHitlDefaultConfig
|
||||||
|
: {};
|
||||||
|
const serverReviewer = serverDefault.reviewer || ((typeof window !== 'undefined' && window.csaiHitlDefaultReviewer)
|
||||||
? window.csaiHitlDefaultReviewer
|
? window.csaiHitlDefaultReviewer
|
||||||
: 'human';
|
: 'human');
|
||||||
return {
|
return {
|
||||||
mode: HITL_MODE_OFF,
|
mode: normalizeHitlMode(serverDefault.mode || HITL_MODE_OFF),
|
||||||
reviewer: normalizeHitlReviewer(serverReviewer),
|
reviewer: normalizeHitlReviewer(serverReviewer),
|
||||||
sensitiveTools: DEFAULT_HITL_SESSION_TOOL_WHITELIST,
|
sensitiveTools: DEFAULT_HITL_SESSION_TOOL_WHITELIST,
|
||||||
timeoutSeconds: DEFAULT_HITL_TIMEOUT_SECONDS,
|
timeoutSeconds: normalizeHitlTimeoutForChat(serverDefault.timeoutSeconds, DEFAULT_HITL_TIMEOUT_SECONDS),
|
||||||
updatedAt: ''
|
updatedAt: ''
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -520,70 +520,11 @@ function getHitlModeLabel(mode) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getHitlLastGlobalConfig() {
|
|
||||||
const fallback = defaultHitlConfig();
|
|
||||||
try {
|
|
||||||
const raw = localStorage.getItem(HITL_GLOBAL_LAST_KEY);
|
|
||||||
if (!raw) return null;
|
|
||||||
const parsed = JSON.parse(raw);
|
|
||||||
if (!parsed || typeof parsed !== 'object') return null;
|
|
||||||
return {
|
|
||||||
mode: normalizeHitlMode(parsed.mode),
|
|
||||||
reviewer: normalizeHitlReviewer(parsed.reviewer),
|
|
||||||
sensitiveTools: typeof parsed.sensitiveTools === 'string' ? parsed.sensitiveTools : fallback.sensitiveTools,
|
|
||||||
timeoutSeconds: normalizeHitlTimeoutForChat(parsed.timeoutSeconds, fallback.timeoutSeconds),
|
|
||||||
updatedAt: typeof parsed.updatedAt === 'string' ? parsed.updatedAt : ''
|
|
||||||
};
|
|
||||||
} catch (e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function saveHitlLastGlobalConfig(payload) {
|
|
||||||
if (!payload || typeof payload !== 'object') return;
|
|
||||||
try {
|
|
||||||
localStorage.setItem(HITL_GLOBAL_LAST_KEY, JSON.stringify(payload));
|
|
||||||
} catch (e) {
|
|
||||||
console.warn('saveHitlLastGlobalConfig failed', e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getHitlConfigForConversation(conversationId) {
|
function getHitlConfigForConversation(conversationId) {
|
||||||
const fallback = defaultHitlConfig();
|
const fallback = defaultHitlConfig();
|
||||||
const cid = conversationId ? String(conversationId).trim() : '';
|
const cid = conversationId ? String(conversationId).trim() : '';
|
||||||
if (!cid) {
|
if (!cid) {
|
||||||
const globalLast = getHitlLastGlobalConfig();
|
return fallback;
|
||||||
let draftCfg = null;
|
|
||||||
try {
|
|
||||||
const raw = localStorage.getItem(HITL_DRAFT_KEY);
|
|
||||||
if (raw) {
|
|
||||||
const parsed = JSON.parse(raw);
|
|
||||||
if (parsed && typeof parsed === 'object') {
|
|
||||||
draftCfg = {
|
|
||||||
mode: normalizeHitlMode(parsed.mode),
|
|
||||||
reviewer: normalizeHitlReviewer(parsed.reviewer),
|
|
||||||
sensitiveTools: typeof parsed.sensitiveTools === 'string' ? parsed.sensitiveTools : fallback.sensitiveTools,
|
|
||||||
timeoutSeconds: normalizeHitlTimeoutForChat(parsed.timeoutSeconds, fallback.timeoutSeconds),
|
|
||||||
updatedAt: typeof parsed.updatedAt === 'string' ? parsed.updatedAt : ''
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
draftCfg = null;
|
|
||||||
}
|
|
||||||
const g = globalLast ? {
|
|
||||||
mode: normalizeHitlMode(globalLast.mode),
|
|
||||||
reviewer: normalizeHitlReviewer(globalLast.reviewer),
|
|
||||||
sensitiveTools: typeof globalLast.sensitiveTools === 'string' ? globalLast.sensitiveTools : fallback.sensitiveTools,
|
|
||||||
timeoutSeconds: normalizeHitlTimeoutForChat(globalLast.timeoutSeconds, fallback.timeoutSeconds),
|
|
||||||
updatedAt: typeof globalLast.updatedAt === 'string' ? globalLast.updatedAt : ''
|
|
||||||
} : null;
|
|
||||||
if (!draftCfg && !g) return fallback;
|
|
||||||
if (!draftCfg) return g;
|
|
||||||
if (!g) return draftCfg;
|
|
||||||
const tg = Date.parse(g.updatedAt) || 0;
|
|
||||||
const td = Date.parse(draftCfg.updatedAt) || 0;
|
|
||||||
return tg > td ? g : draftCfg;
|
|
||||||
}
|
}
|
||||||
const key = getHitlStorageKeyByConversation(cid);
|
const key = getHitlStorageKeyByConversation(cid);
|
||||||
try {
|
try {
|
||||||
@@ -627,6 +568,8 @@ async function onHitlReviewerChanged(reviewer) {
|
|||||||
try {
|
try {
|
||||||
if (cid && typeof window.saveHitlConversationConfig === 'function') {
|
if (cid && typeof window.saveHitlConversationConfig === 'function') {
|
||||||
await window.saveHitlConversationConfig(cid, cfg);
|
await window.saveHitlConversationConfig(cid, cfg);
|
||||||
|
} else if (typeof window.putHitlDefaultConfig === 'function') {
|
||||||
|
await window.putHitlDefaultConfig(cfg);
|
||||||
} else if (typeof window.putHitlDefaultReviewer === 'function') {
|
} else if (typeof window.putHitlDefaultReviewer === 'function') {
|
||||||
await window.putHitlDefaultReviewer(cfg.reviewer);
|
await window.putHitlDefaultReviewer(cfg.reviewer);
|
||||||
}
|
}
|
||||||
@@ -652,7 +595,10 @@ function bindHitlReviewerToggleListeners() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function saveHitlConfigForConversation(conversationId, cfg, opts) {
|
function saveHitlConfigForConversation(conversationId, cfg, opts) {
|
||||||
const syncGlobalLast = !!(opts && opts.syncGlobalLast);
|
void opts;
|
||||||
|
if (!conversationId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const payload = {
|
const payload = {
|
||||||
mode: normalizeHitlMode(cfg && cfg.mode),
|
mode: normalizeHitlMode(cfg && cfg.mode),
|
||||||
reviewer: normalizeHitlReviewer(cfg && cfg.reviewer),
|
reviewer: normalizeHitlReviewer(cfg && cfg.reviewer),
|
||||||
@@ -660,12 +606,9 @@ function saveHitlConfigForConversation(conversationId, cfg, opts) {
|
|||||||
timeoutSeconds: normalizeHitlTimeoutForChat(cfg && cfg.timeoutSeconds, DEFAULT_HITL_TIMEOUT_SECONDS),
|
timeoutSeconds: normalizeHitlTimeoutForChat(cfg && cfg.timeoutSeconds, DEFAULT_HITL_TIMEOUT_SECONDS),
|
||||||
updatedAt: typeof (cfg && cfg.updatedAt) === 'string' ? cfg.updatedAt : ''
|
updatedAt: typeof (cfg && cfg.updatedAt) === 'string' ? cfg.updatedAt : ''
|
||||||
};
|
};
|
||||||
const key = conversationId ? getHitlStorageKeyByConversation(conversationId) : HITL_DRAFT_KEY;
|
const key = getHitlStorageKeyByConversation(conversationId);
|
||||||
try {
|
try {
|
||||||
localStorage.setItem(key, JSON.stringify(payload));
|
localStorage.setItem(key, JSON.stringify(payload));
|
||||||
if (syncGlobalLast) {
|
|
||||||
saveHitlLastGlobalConfig(payload);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn('saveHitlConfigForConversation failed', e);
|
console.warn('saveHitlConfigForConversation failed', e);
|
||||||
}
|
}
|
||||||
@@ -752,8 +695,9 @@ async function waitForHitlConfigReady(conversationId) {
|
|||||||
await hitlConfigSyncPromise;
|
await hitlConfigSyncPromise;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!cid && window.csaiHitlDefaultReviewerReady && typeof window.csaiHitlDefaultReviewerReady.then === 'function') {
|
const defaultReady = window.csaiHitlDefaultConfigReady || window.csaiHitlDefaultReviewerReady;
|
||||||
await window.csaiHitlDefaultReviewerReady.catch(function () {});
|
if (!cid && defaultReady && typeof defaultReady.then === 'function') {
|
||||||
|
await defaultReady.catch(function () {});
|
||||||
if (!currentConversationId) refreshHitlConfigByCurrentConversation();
|
if (!currentConversationId) refreshHitlConfigByCurrentConversation();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -818,6 +762,10 @@ async function applyHitlSidebarConfig() {
|
|||||||
await window.saveHitlConversationConfig(cid, cfg);
|
await window.saveHitlConversationConfig(cid, cfg);
|
||||||
const ok = typeof window.t === 'function' ? window.t('chat.hitlApplyOkSync') : '人机协同配置已保存并同步到服务器。';
|
const ok = typeof window.t === 'function' ? window.t('chat.hitlApplyOkSync') : '人机协同配置已保存并同步到服务器。';
|
||||||
showHitlApplyFeedback(ok, false);
|
showHitlApplyFeedback(ok, false);
|
||||||
|
} else if (typeof window.putHitlDefaultConfig === 'function') {
|
||||||
|
await window.putHitlDefaultConfig(cfg);
|
||||||
|
const okDefault = typeof window.t === 'function' ? window.t('chat.hitlApplyOkDefaultConfig') : '人机协同默认配置已写入 config.yaml 并生效。';
|
||||||
|
showHitlApplyFeedback(okDefault, false);
|
||||||
} else if (yamlMerged) {
|
} else if (yamlMerged) {
|
||||||
const okYaml = typeof window.t === 'function' ? window.t('chat.hitlApplyOkWhitelistYaml') : '免审批工具已合并进 config.yaml 并生效。会话配置会自动保存。';
|
const okYaml = typeof window.t === 'function' ? window.t('chat.hitlApplyOkWhitelistYaml') : '免审批工具已合并进 config.yaml 并生效。会话配置会自动保存。';
|
||||||
showHitlApplyFeedback(okYaml, false);
|
showHitlApplyFeedback(okYaml, false);
|
||||||
@@ -913,6 +861,11 @@ function applyConversationAgentMode(conversationId, conversation) {
|
|||||||
|
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
window.csaiHitlGlobalToolWhitelist = window.csaiHitlGlobalToolWhitelist || [];
|
window.csaiHitlGlobalToolWhitelist = window.csaiHitlGlobalToolWhitelist || [];
|
||||||
|
window.csaiHitlDefaultConfig = window.csaiHitlDefaultConfig || {
|
||||||
|
mode: HITL_MODE_OFF,
|
||||||
|
reviewer: 'human',
|
||||||
|
timeoutSeconds: DEFAULT_HITL_TIMEOUT_SECONDS
|
||||||
|
};
|
||||||
window.csaiHitlDefaultReviewer = window.csaiHitlDefaultReviewer || 'human';
|
window.csaiHitlDefaultReviewer = window.csaiHitlDefaultReviewer || 'human';
|
||||||
window.csaiChatAgentMode = {
|
window.csaiChatAgentMode = {
|
||||||
EINO_MODES: CHAT_AGENT_EINO_MODES,
|
EINO_MODES: CHAT_AGENT_EINO_MODES,
|
||||||
@@ -934,7 +887,6 @@ if (typeof window !== 'undefined') {
|
|||||||
window.setHitlReviewerUI = setHitlReviewerUI;
|
window.setHitlReviewerUI = setHitlReviewerUI;
|
||||||
window.onHitlReviewerChanged = onHitlReviewerChanged;
|
window.onHitlReviewerChanged = onHitlReviewerChanged;
|
||||||
window.bindHitlReviewerToggleListeners = bindHitlReviewerToggleListeners;
|
window.bindHitlReviewerToggleListeners = bindHitlReviewerToggleListeners;
|
||||||
window.getHitlLastGlobalConfig = getHitlLastGlobalConfig;
|
|
||||||
window.hitlMergeToolsForDisplay = hitlMergeToolsForDisplay;
|
window.hitlMergeToolsForDisplay = hitlMergeToolsForDisplay;
|
||||||
window.hitlStripGlobalToolsFromFormString = hitlStripGlobalToolsFromFormString;
|
window.hitlStripGlobalToolsFromFormString = hitlStripGlobalToolsFromFormString;
|
||||||
window.hitlToolsSplitToArray = hitlToolsSplitToArray;
|
window.hitlToolsSplitToArray = hitlToolsSplitToArray;
|
||||||
@@ -5964,13 +5916,6 @@ async function startNewConversation(options = {}) {
|
|||||||
chatInput.value = '';
|
chatInput.value = '';
|
||||||
adjustTextareaHeight(chatInput);
|
adjustTextareaHeight(chatInput);
|
||||||
}
|
}
|
||||||
// 把当前侧栏人机协同选项写入草稿与「最近应用」记忆,避免刷新时被旧草稿里的「关闭」覆盖
|
|
||||||
try {
|
|
||||||
if (typeof readHitlConfigFromForm === 'function' && typeof saveHitlConfigForConversation === 'function') {
|
|
||||||
const snap = readHitlConfigFromForm();
|
|
||||||
saveHitlConfigForConversation('', snap, { syncGlobalLast: true });
|
|
||||||
}
|
|
||||||
} catch (e) { /* ignore */ }
|
|
||||||
refreshHitlConfigByCurrentConversation();
|
refreshHitlConfigByCurrentConversation();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+67
-19
@@ -248,47 +248,89 @@ async function fetchHitlConversationConfig(conversationId) {
|
|||||||
if (!data || !data.hitl) return null;
|
if (!data || !data.hitl) return null;
|
||||||
return {
|
return {
|
||||||
hitl: data.hitl,
|
hitl: data.hitl,
|
||||||
|
defaultMode: hitlModeNormalize(data.defaultMode || 'off'),
|
||||||
defaultReviewer: hitlReviewerNormalize(data.defaultReviewer || 'human'),
|
defaultReviewer: hitlReviewerNormalize(data.defaultReviewer || 'human'),
|
||||||
|
defaultTimeoutSeconds: normalizeHitlTimeoutSeconds(data.defaultTimeoutSeconds, 300),
|
||||||
hitlGlobalToolWhitelist: Array.isArray(data.hitlGlobalToolWhitelist) ? data.hitlGlobalToolWhitelist : []
|
hitlGlobalToolWhitelist: Array.isArray(data.hitlGlobalToolWhitelist) ? data.hitlGlobalToolWhitelist : []
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyHitlDefaultReviewerFromServer(reviewer) {
|
function applyHitlDefaultReviewerFromServer(reviewer) {
|
||||||
const v = hitlReviewerNormalize(reviewer);
|
return applyHitlDefaultConfigFromServer({ defaultReviewer: reviewer });
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyHitlDefaultConfigFromServer(data) {
|
||||||
|
const src = data && typeof data === 'object' ? data : {};
|
||||||
|
const mode = hitlModeNormalize(src.defaultMode || src.mode || 'off');
|
||||||
|
const reviewer = hitlReviewerNormalize(src.defaultReviewer || src.reviewer || 'human');
|
||||||
|
const timeoutSeconds = normalizeHitlTimeoutSeconds(
|
||||||
|
src.defaultTimeoutSeconds != null ? src.defaultTimeoutSeconds : src.timeoutSeconds,
|
||||||
|
300
|
||||||
|
);
|
||||||
|
const out = {
|
||||||
|
mode: mode,
|
||||||
|
reviewer: reviewer,
|
||||||
|
timeoutSeconds: timeoutSeconds
|
||||||
|
};
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
window.csaiHitlDefaultReviewer = v;
|
window.csaiHitlDefaultConfig = out;
|
||||||
|
window.csaiHitlDefaultReviewer = reviewer;
|
||||||
|
if (Array.isArray(src.hitlGlobalToolWhitelist)) {
|
||||||
|
window.csaiHitlGlobalToolWhitelist = src.hitlGlobalToolWhitelist;
|
||||||
}
|
}
|
||||||
return v;
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchHitlDefaultConfig() {
|
||||||
|
const resp = await hitlApiFetch('/api/hitl/default-config', { credentials: 'same-origin' });
|
||||||
|
if (!resp.ok) {
|
||||||
|
return applyHitlDefaultConfigFromServer({ defaultMode: 'off', defaultReviewer: 'human', defaultTimeoutSeconds: 300 });
|
||||||
|
}
|
||||||
|
const data = await resp.json();
|
||||||
|
return applyHitlDefaultConfigFromServer(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchHitlDefaultReviewer() {
|
async function fetchHitlDefaultReviewer() {
|
||||||
const resp = await hitlApiFetch('/api/hitl/default-reviewer', { credentials: 'same-origin' });
|
const cfg = await fetchHitlDefaultConfig();
|
||||||
if (!resp.ok) {
|
return hitlReviewerNormalize(cfg && cfg.reviewer);
|
||||||
return applyHitlDefaultReviewerFromServer('human');
|
|
||||||
}
|
|
||||||
const data = await resp.json();
|
|
||||||
return applyHitlDefaultReviewerFromServer(data && data.defaultReviewer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function putHitlDefaultReviewer(reviewer) {
|
async function putHitlDefaultConfig(config) {
|
||||||
const normalized = hitlReviewerNormalize(reviewer);
|
const current = (typeof window !== 'undefined' && window.csaiHitlDefaultConfig && typeof window.csaiHitlDefaultConfig === 'object')
|
||||||
const resp = await hitlApiFetch('/api/hitl/default-reviewer', {
|
? window.csaiHitlDefaultConfig
|
||||||
|
: { mode: 'off', reviewer: 'human', timeoutSeconds: 300 };
|
||||||
|
const cfg = config && typeof config === 'object' ? config : {};
|
||||||
|
const payload = {
|
||||||
|
mode: hitlModeNormalize(cfg.mode != null ? cfg.mode : current.mode),
|
||||||
|
reviewer: hitlReviewerNormalize(cfg.reviewer != null ? cfg.reviewer : current.reviewer),
|
||||||
|
timeoutSeconds: normalizeHitlTimeoutSeconds(
|
||||||
|
cfg.timeoutSeconds != null ? cfg.timeoutSeconds : current.timeoutSeconds,
|
||||||
|
300
|
||||||
|
)
|
||||||
|
};
|
||||||
|
const resp = await hitlApiFetch('/api/hitl/default-config', {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
credentials: 'same-origin',
|
credentials: 'same-origin',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ reviewer: normalized })
|
body: JSON.stringify(payload)
|
||||||
});
|
});
|
||||||
if (!resp.ok) {
|
if (!resp.ok) {
|
||||||
const msg = await readHitlApiError(resp);
|
const msg = await readHitlApiError(resp);
|
||||||
throw new Error(msg || ('HTTP ' + resp.status));
|
throw new Error(msg || ('HTTP ' + resp.status));
|
||||||
}
|
}
|
||||||
const data = await resp.json();
|
const data = await resp.json();
|
||||||
return applyHitlDefaultReviewerFromServer(data && data.defaultReviewer);
|
return applyHitlDefaultConfigFromServer(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function putHitlDefaultReviewer(reviewer) {
|
||||||
|
const cfg = await putHitlDefaultConfig({ reviewer: reviewer });
|
||||||
|
return hitlReviewerNormalize(cfg && cfg.reviewer);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function initHitlDefaultReviewerFromServer() {
|
async function initHitlDefaultReviewerFromServer() {
|
||||||
try {
|
try {
|
||||||
await fetchHitlDefaultReviewer();
|
await fetchHitlDefaultConfig();
|
||||||
if (!getCurrentConversationIdForHitl() && typeof window.refreshHitlConfigByCurrentConversation === 'function') {
|
if (!getCurrentConversationIdForHitl() && typeof window.refreshHitlConfigByCurrentConversation === 'function') {
|
||||||
window.refreshHitlConfigByCurrentConversation();
|
window.refreshHitlConfigByCurrentConversation();
|
||||||
}
|
}
|
||||||
@@ -535,10 +577,13 @@ async function syncHitlConfigFromServer(conversationId) {
|
|||||||
const pack = await fetchHitlConversationConfig(conversationId);
|
const pack = await fetchHitlConversationConfig(conversationId);
|
||||||
if (!pack || !pack.hitl) return;
|
if (!pack || !pack.hitl) return;
|
||||||
const cfg = pack.hitl;
|
const cfg = pack.hitl;
|
||||||
if (pack.defaultReviewer) {
|
|
||||||
applyHitlDefaultReviewerFromServer(pack.defaultReviewer);
|
|
||||||
}
|
|
||||||
const globalWL = pack.hitlGlobalToolWhitelist || [];
|
const globalWL = pack.hitlGlobalToolWhitelist || [];
|
||||||
|
applyHitlDefaultConfigFromServer({
|
||||||
|
defaultMode: pack.defaultMode,
|
||||||
|
defaultReviewer: pack.defaultReviewer,
|
||||||
|
defaultTimeoutSeconds: pack.defaultTimeoutSeconds,
|
||||||
|
hitlGlobalToolWhitelist: globalWL
|
||||||
|
});
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
window.csaiHitlGlobalToolWhitelist = globalWL;
|
window.csaiHitlGlobalToolWhitelist = globalWL;
|
||||||
}
|
}
|
||||||
@@ -1820,7 +1865,8 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||||||
if (typeof window.bindHitlReviewerToggleListeners === 'function') {
|
if (typeof window.bindHitlReviewerToggleListeners === 'function') {
|
||||||
window.bindHitlReviewerToggleListeners();
|
window.bindHitlReviewerToggleListeners();
|
||||||
}
|
}
|
||||||
window.csaiHitlDefaultReviewerReady = initHitlDefaultReviewerFromServer();
|
window.csaiHitlDefaultConfigReady = initHitlDefaultReviewerFromServer();
|
||||||
|
window.csaiHitlDefaultReviewerReady = window.csaiHitlDefaultConfigReady;
|
||||||
setTimeout(reconcileHitlUiState, 0);
|
setTimeout(reconcileHitlUiState, 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1836,6 +1882,8 @@ document.addEventListener('languagechange', function () {
|
|||||||
window.syncHitlConfigToServerByCurrentConversation = syncHitlConfigToServerByCurrentConversation;
|
window.syncHitlConfigToServerByCurrentConversation = syncHitlConfigToServerByCurrentConversation;
|
||||||
window.saveHitlConversationConfig = saveHitlConversationConfig;
|
window.saveHitlConversationConfig = saveHitlConversationConfig;
|
||||||
window.mergeHitlGlobalToolWhitelist = mergeHitlGlobalToolWhitelist;
|
window.mergeHitlGlobalToolWhitelist = mergeHitlGlobalToolWhitelist;
|
||||||
|
window.fetchHitlDefaultConfig = fetchHitlDefaultConfig;
|
||||||
|
window.putHitlDefaultConfig = putHitlDefaultConfig;
|
||||||
|
|
||||||
// 由 chat.js 在 loadConversation 内 await 调用;挂到 window 供其它入口显式触发
|
// 由 chat.js 在 loadConversation 内 await 调用;挂到 window 供其它入口显式触发
|
||||||
window.syncHitlConfigFromServer = syncHitlConfigFromServer;
|
window.syncHitlConfigFromServer = syncHitlConfigFromServer;
|
||||||
|
|||||||
Reference in New Issue
Block a user