package permission // 权限拒绝追踪系统. // // 当用户反复拒绝同类操作时,系统会自动检测并建议 Agent 调整策略: // - 连续拒绝 3 次同一工具 → 注入系统提醒 // - 总拒绝 10 次 → 建议 Agent 停下来询问用户 // - 用户批准不同工具 → 重置连续拒绝计数 // // 线程安全,可在并发环境下使用. import ( "fmt" "sync" ) // 默认连续拒绝警告阈值. const defaultConsecutiveDenialThreshold = 3 // 默认总拒绝停止阈值. const defaultTotalDenialThreshold = 10 // DenialTracker 追踪权限拒绝事件. type DenialTracker struct { mu sync.Mutex consecutiveDenials int // 连续拒绝同一工具的次数 totalDenials int // 总拒绝次数 lastDeniedTool string // 最后被拒绝的工具 lastDeniedInput string // 最后被拒绝的输入摘要 // 可配置的阈值 consecutiveThreshold int // 连续拒绝警告阈值 totalThreshold int // 总拒绝停止阈值 } // NewDenialTracker 创建一个新的拒绝追踪器. // // consecutiveThreshold 为连续拒绝警告阈值(<=0 使用默认值 3). // totalThreshold 为总拒绝停止阈值(<=0 使用默认值 10). func NewDenialTracker(consecutiveThreshold, totalThreshold int) *DenialTracker { if consecutiveThreshold <= 0 { consecutiveThreshold = defaultConsecutiveDenialThreshold } if totalThreshold <= 0 { totalThreshold = defaultTotalDenialThreshold } return &DenialTracker{ consecutiveThreshold: consecutiveThreshold, totalThreshold: totalThreshold, } } // RecordDenial 记录一次权限拒绝事件. // // toolName 是被拒绝的工具名称,inputSummary 是输入内容的简短摘要. // 如果连续拒绝同一工具,连续计数递增;否则重置连续计数. func (dt *DenialTracker) RecordDenial(toolName, inputSummary string) { dt.mu.Lock() defer dt.mu.Unlock() dt.totalDenials++ if toolName == dt.lastDeniedTool { // 同一工具连续拒绝 dt.consecutiveDenials++ } else { // 不同工具,重置连续计数 dt.consecutiveDenials = 1 dt.lastDeniedTool = toolName } dt.lastDeniedInput = inputSummary } // RecordApproval 记录一次权限批准事件. // // 当用户批准了任何工具的调用时调用此方法. // 批准后重置连续拒绝计数(表示用户意图已改变). // 精妙之处(CLEVER): 任何工具被批准都重置连续拒绝计数--表示用户意图已经改变. // 用户可能先拒绝了 3 次 rm,然后批准了 mkdir,说明用户仍在参与决策, // 不应该继续认为用户在"连续拒绝"同类操作. func (dt *DenialTracker) RecordApproval(toolName string) { dt.mu.Lock() defer dt.mu.Unlock() // 批准任何工具都重置连续拒绝计数 dt.consecutiveDenials = 0 dt.lastDeniedTool = "" dt.lastDeniedInput = "" } // ShouldWarnAgent 检查是否应该向 Agent 发出警告. // // 当连续拒绝同一工具达到阈值时,返回 true 和警告消息. // Agent 应该在下次工具调用前收到此消息,换一种方式完成任务. func (dt *DenialTracker) ShouldWarnAgent() (warn bool, message string) { dt.mu.Lock() defer dt.mu.Unlock() if dt.consecutiveDenials >= dt.consecutiveThreshold && dt.lastDeniedTool != "" { return true, fmt.Sprintf( "用户已拒绝 %s 操作 %d 次,请换一种方式完成任务。", dt.lastDeniedTool, dt.consecutiveDenials, ) } return false, "" } // ShouldStopAndAsk 检查是否应该停下来询问用户. // // 当总拒绝次数达到阈值时,返回 true. // Agent 应该停下来直接问用户想怎么做,而不是继续尝试. func (dt *DenialTracker) ShouldStopAndAsk() bool { dt.mu.Lock() defer dt.mu.Unlock() return dt.totalDenials >= dt.totalThreshold } // Reset 重置所有追踪数据. // 通常在会话开始或用户显式请求时调用. func (dt *DenialTracker) Reset() { dt.mu.Lock() defer dt.mu.Unlock() dt.consecutiveDenials = 0 dt.totalDenials = 0 dt.lastDeniedTool = "" dt.lastDeniedInput = "" } // Stats returns a snapshot of the denial tracker's current state. // // Shape: pull. Consumer calls Stats() anytime to read the current cumulative // denial state; the four DenialStats fields (ConsecutiveDenials / // TotalDenials / LastDeniedTool / LastDeniedInput) are the payload. // // Stats 返回拒绝追踪器当前状态的快照. // // 形态: 调取 (pull). 消费者任意时刻调 Stats() 读当前累计拒绝状态, 四字段 // (ConsecutiveDenials / TotalDenials / LastDeniedTool / LastDeniedInput) // 即 payload. func (dt *DenialTracker) Stats() DenialStats { dt.mu.Lock() defer dt.mu.Unlock() return DenialStats{ ConsecutiveDenials: dt.consecutiveDenials, TotalDenials: dt.totalDenials, LastDeniedTool: dt.lastDeniedTool, LastDeniedInput: dt.lastDeniedInput, } } // DenialStats 是拒绝追踪器的统计信息. type DenialStats struct { ConsecutiveDenials int // 连续拒绝同一工具的次数 TotalDenials int // 总拒绝次数 LastDeniedTool string // 最后被拒绝的工具 LastDeniedInput string // 最后被拒绝的输入摘要 }