Files

99 lines
4.6 KiB
Go

package agent
import (
"context"
"encoding/json"
"fmt"
"github.com/Autumn-27/artex/db"
actool "github.com/Autumn-27/norma/tool"
)
// Missing host/setting means off. Rechecked on every write for running sessions.
var IntelligenceEnabled func() bool
var IntelligenceRecord func(context.Context, int64, int64, string, db.IntelligenceInput) (int64, error)
func intelligenceEnabled() bool { return IntelligenceEnabled != nil && IntelligenceEnabled() }
func intelligenceSchema() map[string]any {
return obj(map[string]any{
"kind": map[string]any{"type": "string", "enum": []string{"credential", "lead", "sensitive_data"}},
"title": str("简短标题,禁止包含密码、Token 或敏感数据原文"),
"content": str("实际获取的信息。凭据用 JSON 字符串保存账号/密码/Token 等;敏感数据仅少量样例、数量和文件位置,禁止整库粘贴"),
"evidence": str("真实来源:命令/请求/响应/文件位置及关键观察,禁止编造"),
"confidence": map[string]any{"type": "string", "enum": []string{"observed", "inferred"}},
"status": str("凭据 unverified/verified/invalid;线索 pending/in_progress/dismissed/converted;敏感数据 suspected/confirmed/dismissed。省略时保守默认,verified 需要真实认证成功证据"),
"asset_ids": map[string]any{"type": "array", "items": map[string]any{"type": "integer"}},
}, "kind", "title", "content", "evidence", "confidence")
}
func (t *ToolSet) saveIntelligence(ctx context.Context, nodeID int64, in db.IntelligenceInput) (int64, error) {
if !intelligenceEnabled() || IntelligenceRecord == nil {
return 0, fmt.Errorf("任务情报自动记录已关闭")
}
taskID := t.taskID
if taskID <= 0 {
taskID = RunInfoFrom(ctx).TaskID
}
if taskID <= 0 {
return 0, fmt.Errorf("任务情报需要当前任务上下文")
}
if nodeID <= 0 {
nodeID = t.ownerNode
}
if nodeID <= 0 {
nodeID = RunInfoFrom(ctx).IntentID
}
id, err := IntelligenceRecord(ctx, taskID, nodeID, t.worker, in)
if err == nil {
t.writes.Intelligence++
}
return id, err
}
func (t *ToolSet) recordIntelligence() actool.CoreTool {
return writeTool("record_intelligence", "记录当前任务实际获取的凭据、重要线索或敏感数据。任务和执行来源由系统绑定;凭据发现不等于验证成功。重复记录会去重。已有 insert_assets.auth 自动登记凭据,无需重复调用。", intelligenceSchema(), func(ctx context.Context, raw json.RawMessage) (actool.Result, error) {
var in db.IntelligenceInput
if err := json.Unmarshal(raw, &in); err != nil {
return actool.Errorf("无效情报参数"), nil
}
id, err := t.saveIntelligence(ctx, 0, in)
if err != nil {
return actool.Errorf(err.Error()), nil
}
return actool.Text(fmt.Sprintf("intelligence recorded: %d", id)), nil
})
}
func intelligenceWorkflow(ctx context.Context, tools []actool.CoreTool) ([]actool.CoreTool, string) {
enabled := intelligenceEnabled() && RunInfoFrom(ctx).TaskID > 0
out := make([]actool.CoreTool, 0, len(tools))
hasRecord := false
hasFact := false
for _, tool := range tools {
if tool.Name() == "record_intelligence" {
if !enabled {
continue
}
hasRecord = true
}
if tool.Name() == "record_fact" {
raw, _ := json.Marshal(tool.InputSchema())
var schema map[string]any
if json.Unmarshal(raw, &schema) == nil {
props, _ := schema["properties"].(map[string]any)
if props != nil {
delete(props, "intelligence")
if enabled {
props["intelligence"] = map[string]any{"type": "array", "maxItems": 20, "description": "随事实一次写回情报,字段与 record_intelligence 相同;无需再单独调用该工具", "items": intelligenceSchema()}
hasFact = true
}
}
tool = DecorateTool(tool, tool.Description(), schema)
}
}
out = append(out, tool)
}
guide := ""
if enabled && (hasRecord || hasFact) {
guide = "\n\n**任务情报记录已开启**:发现凭据、值得跟进的线索或真实敏感数据时立即结构化写回,不等用户追问;结束前检查遗漏。record_fact 可附 intelligence 数组一起写回,每条事实/批量 facts 的元素均支持;单独记录用 record_intelligence(若可用)。insert_assets.auth 已由后端自动登记,勿重复调用。只记录新增、有价值的信息,普通指纹/端口状态仍仅作 fact。title 禁止包含秘密原文;正文带实际内容和来源。凭据默认未验证,只有真实认证成功才标 verified;推断必须 inferred;敏感数据写类型、数量、少量样例、文件位置,勿粘整库。"
}
return out, guide
}