Files
weichen dc29b84cdf feat(ctx): inject cleaned project dialogue into squad and assistant
Add dialoguectx packaging, conflictresolve adjudication with HITL routing,
workspace context/dialogue/ injection (default on), and goose 00071.
2026-09-24 17:04:04 +00:00

123 lines
3.2 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package dialoguectx
import (
"crypto/sha256"
"encoding/hex"
"strings"
"unicode/utf8"
"chat-agent-system/internal/replyexport"
)
// cleanResult is the outcome of cleaning a raw message.
type cleanResult struct {
Include bool
Content string
Noise bool
Private bool
NoAccess bool
}
// cleanMessage applies visibility, export-policy, noise, and R13 neutralization.
func cleanMessage(role, content, contentKind, exportPolicy, stageKey, allowedStageKey string, metadata map[string]any) cleanResult {
trimmed := strings.TrimSpace(content)
if trimmed == "" {
return cleanResult{Noise: true}
}
if strings.HasPrefix(trimmed, "[private]") || strings.HasPrefix(trimmed, "[draft]") {
return cleanResult{Private: true}
}
if vis, _ := metadata["visibility"].(string); vis == "private" {
return cleanResult{Private: true}
}
if allowedStageKey != "" && stageKey != "" && stageKey != allowedStageKey {
return cleanResult{NoAccess: true}
}
switch role {
case "user":
if utf8.RuneCountInString(trimmed) < MinContentRunes {
return cleanResult{Noise: true}
}
if isGreetingNoise(trimmed) {
return cleanResult{Noise: true}
}
return cleanResult{Include: true, Content: neutralizeDirective(trimmed)}
case "assistant":
switch contentKind {
case string(replyexport.KindActionCard), string(replyexport.KindStatusUpdate),
string(replyexport.KindErrorNotice), string(replyexport.KindSystemNotice):
return cleanResult{Noise: true}
}
switch exportPolicy {
case string(replyexport.PolicyFull), string(replyexport.PolicyCopyOnly):
if utf8.RuneCountInString(trimmed) < MinContentRunes {
return cleanResult{Noise: true}
}
return cleanResult{Include: true, Content: neutralizeDirective(trimmed)}
default:
return cleanResult{Noise: true}
}
default:
return cleanResult{Noise: true}
}
}
func isGreetingNoise(s string) bool {
lower := strings.ToLower(strings.TrimSpace(s))
greetings := []string{
"你好", "您好", "hi", "hello", "hey", "在吗", "早上好", "下午好", "晚上好",
"谢谢", "感谢", "ok", "好的", "收到", "嗯", "哈哈",
}
for _, g := range greetings {
if lower == g || lower == g+"。" || lower == g+"!" || lower == g+"?" || lower == g+"?" {
return true
}
}
return false
}
// neutralizeDirective marks instruction-like text as user material (R13).
func neutralizeDirective(c string) string {
lower := strings.ToLower(c)
markers := []string{
"ignore previous",
"system:",
"忽略以上",
"忽略之前",
"提权",
"grant yourself",
"override auth",
"将所有阶段",
}
for _, m := range markers {
if strings.Contains(lower, strings.ToLower(m)) || strings.Contains(c, m) {
return "【用户材料】" + c
}
}
return c
}
func contentFingerprint(role, content string) string {
sum := sha256.Sum256([]byte(role + "|" + content))
return hex.EncodeToString(sum[:8])
}
func truncateRunes(s string, max int) string {
if max <= 0 || utf8.RuneCountInString(s) <= max {
return s
}
runes := []rune(s)
return string(runes[:max]) + "…"
}
func stageKeyFromMeta(metadata map[string]any) string {
if metadata == nil {
return ""
}
if sk, ok := metadata["stage_key"].(string); ok {
return sk
}
return ""
}