refactor(graph_overview): 精简态势概览字段
- 删除 cold_index 字段及其 expand_index 工具(含两处注册),移除 仅服务它的 coldDigestOverview 分桶逻辑(改用 activeDigestBodies) - 删除 covered_members 字段(悬空血缘解析表) - 删除 coverage.scope 字段(范围根资产 + company 富化) - finding_list 每条仅保留 id/summary/from_intent,去掉 evidence/assets, 并清理随之无用的 findingMeta 预计算与 assetValue 辅助函数
This commit is contained in:
1 parent
bf7f414477
commit
06a43f37ff
3 files changed
+14
-319
No files matched your search
+4
-164
@@ -2,122 +2,19 @@ package agent
|
||||
|
||||
// cold-digest §6: graph_overview folding + the restore tools.
|
||||
//
|
||||
// coldDigestOverview — builds the folded cold region for graph_overview:
|
||||
// cold_digests (flat {id, body, member_count}) and cold_index (§6.2, the
|
||||
// per-asset directory that collapses independent directions).
|
||||
// expand_digest(id) — level-1 restore: a digest's member compact list.
|
||||
// expand_index(asset_id) — level-0 restore: the digests under one asset.
|
||||
// activeDigestBodies — builds the folded cold region for graph_overview:
|
||||
// cold_digests (flat {id, body, member_count}).
|
||||
// expand_digest(id) — level-1 restore: a digest's member compact list.
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/Autumn-27/artex/db"
|
||||
actool "github.com/Autumn-27/norma/tool"
|
||||
)
|
||||
|
||||
// coldDigestOverview returns the folded cold region for graph_overview: the flat
|
||||
// digest bodies and the asset-grouped index (§6.1/§6.2). covered is unused here
|
||||
// (kept for symmetry with the caller's coverage computation).
|
||||
func (t *ToolSet) coldDigestOverview() (digests []map[string]any, index []map[string]any) {
|
||||
ads, err := t.ts.ActiveDigests()
|
||||
if err != nil || len(ads) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
memByDigest := map[int64][]int64{}
|
||||
var allMembers []int64
|
||||
for _, d := range ads {
|
||||
ms, _ := t.ts.DigestMembers(d.ID)
|
||||
memByDigest[d.ID] = ms
|
||||
allMembers = append(allMembers, ms...)
|
||||
}
|
||||
assetsByNode, _ := t.ts.NodeAssets(allMembers)
|
||||
|
||||
digests = make([]map[string]any, 0, len(ads))
|
||||
for _, d := range ads {
|
||||
var p struct {
|
||||
Body string `json:"body"`
|
||||
}
|
||||
_ = json.Unmarshal(d.Payload, &p)
|
||||
digests = append(digests, map[string]any{
|
||||
"id": d.ID,
|
||||
"body": p.Body,
|
||||
"member_count": len(memByDigest[d.ID]),
|
||||
})
|
||||
}
|
||||
|
||||
// index (§6.2): asset → digests. Each digest lands in EXACTLY ONE bucket — its
|
||||
// representative asset = the asset anchored on the most of its members (mode;
|
||||
// tie-break lowest id). This is what makes the index converge: bucketing a digest
|
||||
// into every asset its members touch would duplicate it across dozens of buckets
|
||||
// and blow up the top-level count instead of shrinking it (asset_ids are fine-
|
||||
// grained — a real task has ~144 of them). A digest whose members anchor no asset
|
||||
// falls into the 0 bucket ("(未锚定资产)").
|
||||
byAsset := map[int64]map[int64]bool{} // asset id → set of digest ids
|
||||
assetSet := map[int64]bool{}
|
||||
for dID, ms := range memByDigest {
|
||||
counts := map[int64]int{}
|
||||
for _, m := range ms {
|
||||
for _, a := range assetsByNode[m] {
|
||||
counts[a]++
|
||||
}
|
||||
}
|
||||
rep, best := int64(0), 0
|
||||
for a, c := range counts {
|
||||
if c > best || (c == best && (rep == 0 || a < rep)) {
|
||||
rep, best = a, c
|
||||
}
|
||||
}
|
||||
if byAsset[rep] == nil {
|
||||
byAsset[rep] = map[int64]bool{}
|
||||
}
|
||||
byAsset[rep][dID] = true
|
||||
if rep != 0 {
|
||||
assetSet[rep] = true
|
||||
}
|
||||
}
|
||||
labels := map[int64]string{}
|
||||
if t.as != nil && len(assetSet) > 0 {
|
||||
ids := make([]int64, 0, len(assetSet))
|
||||
for a := range assetSet {
|
||||
ids = append(ids, a)
|
||||
}
|
||||
if assets, err := t.as.GetByIDs(ids); err == nil {
|
||||
for _, a := range assets {
|
||||
if v := assetValue(a); v != "" {
|
||||
labels[a.ID] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
index = make([]map[string]any, 0, len(byAsset))
|
||||
for a, dset := range byAsset {
|
||||
dids := make([]int64, 0, len(dset))
|
||||
for d := range dset {
|
||||
dids = append(dids, d)
|
||||
}
|
||||
sort.Slice(dids, func(i, j int) bool { return dids[i] < dids[j] })
|
||||
entry := map[string]any{"digest_ids": dids}
|
||||
if a == 0 {
|
||||
entry["asset"] = "(未锚定资产)"
|
||||
} else {
|
||||
entry["asset_id"] = a
|
||||
if l := labels[a]; l != "" {
|
||||
entry["asset"] = l
|
||||
} else {
|
||||
entry["asset"] = fmt.Sprintf("#%d", a)
|
||||
}
|
||||
}
|
||||
index = append(index, entry)
|
||||
}
|
||||
sort.Slice(index, func(i, j int) bool {
|
||||
return fmt.Sprint(index[i]["asset"]) < fmt.Sprint(index[j]["asset"])
|
||||
})
|
||||
return digests, index
|
||||
}
|
||||
|
||||
// digestMemberEntry builds the compact per-member view expand_digest returns —
|
||||
// same shape as recent_facts / recent_done_intents (§6.1 middle level). store is
|
||||
// the digest's OWNING store (the current task, or a read-only source task §2).
|
||||
@@ -200,7 +97,7 @@ func (t *ToolSet) expandDigest() actool.CoreTool {
|
||||
map[string]any{
|
||||
"type": "object",
|
||||
"properties": map[string]any{
|
||||
"id": map[string]any{"type": "integer", "description": "digest 节点 id(来自概览 cold_digests / cold_index / covered_members)"},
|
||||
"id": map[string]any{"type": "integer", "description": "digest 节点 id(来自概览 cold_digests)"},
|
||||
},
|
||||
"required": []any{"id"},
|
||||
},
|
||||
@@ -240,60 +137,3 @@ func (t *ToolSet) expandDigest() actool.CoreTool {
|
||||
return jsonResult(out)
|
||||
})
|
||||
}
|
||||
|
||||
// expandIndex returns the active digests under one asset (§6.2 level-0), each
|
||||
// with its body + member count — so the planner can drill an asset directory down
|
||||
// to its directions without reading every digest globally.
|
||||
func (t *ToolSet) expandIndex() actool.CoreTool {
|
||||
return t.writeExpTool("expand_index",
|
||||
"展开冷区某个资产条目(来自概览 cold_index):返回该资产名下的 cold digest 列表(id/body/member_count)。再往下看某个 digest 的成员用 expand_digest(id)。",
|
||||
map[string]any{
|
||||
"type": "object",
|
||||
"properties": map[string]any{
|
||||
"asset_id": map[string]any{"type": "integer", "description": "资产 id(来自概览 cold_index 的 asset_id;传 0 或省略取未锚定资产桶)"},
|
||||
},
|
||||
},
|
||||
func(ctx context.Context, raw json.RawMessage) (actool.Result, error) {
|
||||
var in struct {
|
||||
AssetID int64 `json:"asset_id"`
|
||||
}
|
||||
_ = json.Unmarshal(raw, &in)
|
||||
ads, _ := t.ts.ActiveDigests()
|
||||
out := make([]map[string]any, 0)
|
||||
for _, d := range ads {
|
||||
members, _ := t.ts.DigestMembers(d.ID)
|
||||
assetsByNode, _ := t.ts.NodeAssets(members)
|
||||
match := in.AssetID == 0
|
||||
for _, m := range members {
|
||||
for _, a := range assetsByNode[m] {
|
||||
if a == in.AssetID {
|
||||
match = true
|
||||
}
|
||||
}
|
||||
if match {
|
||||
break
|
||||
}
|
||||
}
|
||||
// asset_id==0 means "unanchored bucket": include only digests with no asset.
|
||||
if in.AssetID == 0 {
|
||||
anchored := false
|
||||
for _, m := range members {
|
||||
if len(assetsByNode[m]) > 0 {
|
||||
anchored = true
|
||||
break
|
||||
}
|
||||
}
|
||||
match = !anchored
|
||||
}
|
||||
if !match {
|
||||
continue
|
||||
}
|
||||
var p struct {
|
||||
Body string `json:"body"`
|
||||
}
|
||||
_ = json.Unmarshal(d.Payload, &p)
|
||||
out = append(out, map[string]any{"id": d.ID, "body": p.Body, "member_count": len(members)})
|
||||
}
|
||||
return jsonResult(map[string]any{"asset_id": in.AssetID, "digests": out})
|
||||
})
|
||||
}
|
||||
Reference in new issue
Block a user