Files
Agent-Intent/intent/catalog.go
T
weichenandCursor 05e74d8e32
ci / vet-and-test (pull_request) Successful in 18s
ci / docker-checkout-hostgateway (pull_request) Successful in 1s
Fold NFKC input and stop catalog metadata leaks
Why: fullwidth text missed case-folded terms, SpecByID shared maps, the catalog prompt printed handlers and private metadata, overlapping slots kept both values, and negation was easy to mistake for semantics.
Evidence: GOWORK=off go vet ./... && go test -count=1 -race ./... passed, including TestR2NFKCFoldsFullwidthButNotTraditional, TestR2SpecByIDDeepCopiesMetadata, TestR2CatalogHidesPrivateMetadataAndHandlerInMarkdown, TestR2EvalRulesDocumentsMaxSpecs, TestR2OverlappingSlotsAreAmbiguous, and TestR2NegationIsOnlyExcludesAny.
Impact: NFKC is opt-in and does not convert traditional Chinese; catalog markdown omits handlers and non-allowlisted metadata; overlapping slot spans are invalid.
Refs: I4, I7, I8, I10, I11, I12
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-03 12:56:55 +00:00

122 lines
4.0 KiB
Go
Raw 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 intent
import (
"fmt"
"strings"
)
// CatalogSlot 目录中的槽位视图:给规划者/领导 Agent 看的声明式信息
// (有哪些能力、缺什么信息、建议怎么问),不暴露匹配实现细节。
type CatalogSlot struct {
Key string `json:"key"`
Required bool `json:"required,omitempty"`
Label string `json:"label,omitempty"`
Question string `json:"question,omitempty"`
Enum []string `json:"enum,omitempty"`
Format string `json:"format,omitempty"`
}
// CatalogSpec 目录中的单条能力。
type CatalogSpec struct {
ID string `json:"id"`
Domain string `json:"domain,omitempty"`
Version int `json:"version,omitempty"`
Description string `json:"description,omitempty"`
Risk string `json:"risk,omitempty"`
RequiresConfirm bool `json:"requires_confirm,omitempty"`
Handler string `json:"handler,omitempty"`
Slots []CatalogSlot `json:"slots,omitempty"`
Metadata map[string]string `json:"metadata,omitempty"`
}
// Catalog 返回能力目录:全部启用的非守卫 spec,按合并序(确定性)。
// 守卫域 spec 是拦截器而非可调用能力,不进目录;需要全量时用 SpecIDs/SpecByID。
func (s *Snapshot) Catalog() []CatalogSpec {
out := make([]CatalogSpec, 0, len(s.specs))
for _, cs := range s.specs {
if cs.guard {
continue
}
view := CatalogSpec{
ID: cs.spec.ID,
Domain: cs.spec.Domain,
Version: cs.spec.Version,
Description: cs.spec.Description,
Risk: cs.spec.Risk,
RequiresConfirm: cs.spec.RequiresConfirm,
Handler: cs.spec.Handler,
Metadata: catalogMetadata(cs.spec.Metadata),
}
if view.Risk == "" {
view.Risk = RiskLow
}
for _, sl := range cs.slots {
view.Slots = append(view.Slots, CatalogSlot{
Key: sl.key, Required: sl.required, Label: sl.label, Question: sl.question,
Enum: sl.enum, Format: specSlotFormat(cs.spec, sl.key),
})
}
out = append(out, view)
}
return out
}
// CatalogMarkdown 渲染注入规划者/领导提示词用的目录文本(确定性)。
// 文案面向 LLM:说明可直接调用、必填槽位与提问建议。
func (s *Snapshot) CatalogMarkdown() string {
var b strings.Builder
fmt.Fprintf(&b, "# 系统能力目录(意图快照 revision=%d)\n\n", s.rev)
b.WriteString("以下能力可直接调用,无需派智能体执行。子任务命中其一时,输出携带 intent_id 的步骤并填齐必填槽位;\n")
b.WriteString("必填槽位信息不足时不要编造,改为向用户澄清(优先使用各槽位的提问建议)。\n\n")
specs := s.Catalog()
if len(specs) == 0 {
b.WriteString("(目录为空:当前没有可直接调用的系统能力)\n")
return b.String()
}
for _, sp := range specs {
desc := sp.Description
if desc == "" {
desc = sp.ID
}
fmt.Fprintf(&b, "## %s — %s\n", sp.ID, desc)
confirm := "否"
if sp.RequiresConfirm {
confirm = "是"
}
fmt.Fprintf(&b, "- 风险:%s;执行前需人工确认:%s\n", sp.Risk, confirm)
for _, sl := range sp.Slots {
req := "选填"
if sl.Required {
req = "必填"
}
label := sl.Label
if label == "" {
label = sl.Key
}
fmt.Fprintf(&b, "- 槽位 %s(%s,%s)", sl.Key, req, label)
if len(sl.Enum) > 0 {
fmt.Fprintf(&b, ";取值:%s", strings.Join(sl.Enum, " | "))
}
if sl.Format != "" {
fmt.Fprintf(&b, ";格式:%s", sl.Format)
}
b.WriteString("\n")
if sl.Question != "" {
fmt.Fprintf(&b, " - 提问建议:%s\n", sl.Question)
}
}
b.WriteString("\n")
}
return b.String()
}
// specSlotFormat 从原始 spec 取槽位的 Format 声明(编译产物不保留字符串正则源)。
func specSlotFormat(spec IntentSpec, key string) string {
for _, sl := range spec.Slots {
if sl.Key == key {
return sl.Format
}
}
return ""
}