package intent import ( "context" "crypto/sha256" "encoding/hex" "fmt" "strings" ) func attachPlanProposal(ctx context.Context, r *RuleBasedRecognizer, input string, snap *ContextSnapshot, res *IntentRecognitionResult) { if res == nil { return } proposal := buildPlanProposal(ctx, r, input, snap, res) if proposal == nil { return } res.PlanProposal = proposal res.PlanShadow = buildPlanShadow(res, proposal) } func buildPlanProposal(ctx context.Context, r *RuleBasedRecognizer, input string, snap *ContextSnapshot, primary *IntentRecognitionResult) *PlanProposal { trimmed := strings.TrimSpace(input) if trimmed == "" { return nil } if primary != nil && primary.TopIntent != nil && primary.TopIntent.Intent == IntentSecurityForbidden { return rejectedProposal(trimmed, ComboRejected, "security_redline") } if isSecurityViolation(trimmed) && !isNegatedWriteWithReadonlyIntent(trimmed) { return rejectedProposal(trimmed, ComboRejected, "security_redline") } rawSegs := SplitPlanSegments(input) if len(rawSegs) == 0 { return nil } if len(rawSegs) > maxPlanSegments { p := &PlanProposal{ SegmenterVersion: SegmenterVersion, ComboHint: ComboRejectedBatchSize, Evidence: []IntentEvidence{{Kind: "batch_size", Detail: fmt.Sprintf("%d", len(rawSegs)), Weight: 1}}, } for i, s := range rawSegs { p.Segments = append(p.Segments, proposedFromInput(s, i, nil, nil)) } p.Digest = proposalDigest(p) return p } proposed := make([]ProposedSegment, 0, len(rawSegs)) for i, s := range rawSegs { text := s.Normalized if text == "" { text = strings.TrimSpace(s.Text) } var frame *IntentFrameV2 var missing []string reusePrimary := len(rawSegs) == 1 && primary != nil && (text == "" || text == normalizeSegmentText(input) || text == strings.TrimSpace(input)) if reusePrimary { frame = primary.IntentFrameV2 if frame == nil { frame = BuildIntentFrameV2(primary, DefaultRegistry()) } if primary.TopIntent != nil { missing = append([]string{}, primary.TopIntent.MissingSlots...) } } else { leaf, err := r.recognizeCore(ctx, text, snap) if err == nil && leaf != nil { frame = BuildIntentFrameV2(leaf, DefaultRegistry()) if leaf.TopIntent != nil { missing = append([]string{}, leaf.TopIntent.MissingSlots...) } if frame != nil && len(frame.MissingSlots) > 0 { missing = append(missing, frame.MissingSlots...) } if leaf.TopIntent != nil && leaf.TopIntent.Intent == IntentSecurityForbidden { p := rejectedProposal(trimmed, ComboRejected, "segment_security") p.Segments = append(p.Segments, proposedFromInput(s, i, frame, missing)) p.Digest = proposalDigest(p) return p } } } if frame != nil && len(frame.MissingSlots) > 0 { missing = append(missing, frame.MissingSlots...) } seg := proposedFromInput(s, i, frame, uniqueStrings(missing)) if i > 0 { seg.UnresolvedAnaphora = collectAnaphora(text) seg.DependsOn = []string{fmt.Sprintf("%d", i)} } proposed = append(proposed, seg) } p := &PlanProposal{ SegmenterVersion: SegmenterVersion, Segments: proposed, } p.ComboHint = comboHintFor(p.Segments, primary) p.Evidence = comboEvidence(p) p.Digest = proposalDigest(p) return p } func proposedFromInput(s InputSegment, i int, frame *IntentFrameV2, missing []string) ProposedSegment { return ProposedSegment{ StepID: fmt.Sprintf("%d", i+1), Text: s.Text, Normalized: s.Normalized, Span: s.Span, Connector: s.Connector, Evidence: s.Evidence, StructureConfidence: s.StructureConfidence, Frame: frame, MissingSlots: missing, } } func comboHintFor(segs []ProposedSegment, primary *IntentRecognitionResult) ComboHint { if len(segs) <= 1 { return ComboSingle } needsClarify := false needsConfirm := false allQuery := true seenKeys := map[string]int{} for _, s := range segs { if s.StructureConfidence == "low" || len(s.UnresolvedAnaphora) > 0 || len(s.MissingSlots) > 0 || s.Frame == nil { needsClarify = true } if s.Frame == nil { allQuery = false continue } if s.Frame.Score > 0 && s.Frame.Score < 0.6 { needsClarify = true } if isWriteIntent(s.Frame.LegacyIntent) || s.Frame.Goal == GoalGenerate || s.Frame.Goal == GoalMutate || s.Frame.Goal == GoalControl { needsConfirm = true allQuery = false } if s.Frame.Goal != GoalQuery && s.Frame.Goal != "" { allQuery = false } if s.Frame.ActionKey != "" { seenKeys[s.Frame.ActionKey]++ if seenKeys[s.Frame.ActionKey] > 1 { needsClarify = true } } } if needsClarify { return ComboNeedsClarify } if needsConfirm { return ComboNeedsConfirm } if allQuery { return ComboReadOnly } if primary != nil && primary.RequiresClarifying { return ComboNeedsClarify } return ComboNeedsConfirm } func comboEvidence(p *PlanProposal) []IntentEvidence { if p == nil { return nil } ev := []IntentEvidence{{Kind: "segmenter", Detail: SegmenterVersion, Weight: 1}} for _, s := range p.Segments { if s.Evidence != "" { ev = append(ev, IntentEvidence{Kind: "split", Detail: s.Evidence, Weight: 0.5}) } } return ev } func rejectedProposal(input string, hint ComboHint, reason string) *PlanProposal { _ = input p := &PlanProposal{ SegmenterVersion: SegmenterVersion, ComboHint: hint, Evidence: []IntentEvidence{{Kind: reason, Detail: string(hint), Weight: 1}}, Segments: []ProposedSegment{{ StepID: "1", Text: input, Normalized: normalizeSegmentText(input), Span: TextSpan{Start: 0, End: len(input)}, StructureConfidence: "high", Evidence: reason, }}, } p.Digest = proposalDigest(p) return p } func proposalDigest(p *PlanProposal) string { if p == nil { return "" } h := sha256.New() _, _ = h.Write([]byte(p.SegmenterVersion)) _, _ = h.Write([]byte{0}) _, _ = h.Write([]byte(p.ComboHint)) for _, s := range p.Segments { _, _ = h.Write([]byte{0}) _, _ = h.Write([]byte(s.Normalized)) _, _ = h.Write([]byte{0}) if s.Frame != nil { _, _ = h.Write([]byte(s.Frame.ActionKey)) } for _, a := range s.UnresolvedAnaphora { _, _ = h.Write([]byte{1}) _, _ = h.Write([]byte(a)) } } return hex.EncodeToString(h.Sum(nil)) } func buildPlanShadow(res *IntentRecognitionResult, p *PlanProposal) *PlanShadowReport { if p == nil { return nil } dropped := false if p.ComboHint == ComboNeedsConfirm || hasWriteSegment(p) { dropped = res.IntentPlan == nil } return &PlanShadowReport{ Enabled: true, SegmenterVersion: p.SegmenterVersion, SegmentCount: len(p.Segments), Digest: p.Digest, ComboHint: p.ComboHint, LegacyPlanAttached: res.IntentPlan != nil, LegacyDroppedWrite: dropped && hasWriteSegment(p), CounterfactualAutoRead: counterfactualAutoRead(p), } } func hasWriteSegment(p *PlanProposal) bool { if p == nil { return false } for _, s := range p.Segments { if s.Frame == nil { continue } if isWriteIntent(s.Frame.LegacyIntent) || s.Frame.Goal == GoalGenerate || s.Frame.Goal == GoalMutate || s.Frame.Goal == GoalControl { return true } } return false } // CounterfactualAutoRead answers "if the system were in auto_read, what would it do?" // It must not treat "did not execute because shadow" as zero error. func counterfactualAutoRead(p *PlanProposal) string { if p == nil { return "would_clarify" } switch p.ComboHint { case ComboReadOnly: return "would_execute" case ComboSingle: if hasWriteSegment(p) { return "would_confirm" } return "would_passthrough" case ComboNeedsConfirm: return "would_confirm" case ComboNeedsClarify: return "would_clarify" case ComboRejected, ComboRejectedBatchSize: return "would_reject" default: return "would_clarify" } } func uniqueStrings(in []string) []string { if len(in) == 0 { return nil } seen := map[string]struct{}{} var out []string for _, s := range in { s = strings.TrimSpace(s) if s == "" { continue } if _, ok := seen[s]; ok { continue } seen[s] = struct{}{} out = append(out, s) } return out }