后端:
- 新表 skill_usage(无外键,任务/会话删除后统计保留),一次 Skill()
调用记一行,只记维度不记 args 正文;含 found 标记未命中调用
- 用 RunInfo 经 ctx 把 task/exploration/intent/session 传到工具装配层,
在 Skill meta-tool 外包一层做埋点(可记 args 长度与不存在的 skill)
- fsListSkills 返回附带调用次数/覆盖任务/最近调用;新增
GET /api/skills/missing 与 GET /api/skills/{name}/usage
前端(/system/skills):
- 树节点显示调用次数 badge;目录优先排序 + 子项缩进错开一级
- 空态改为技能库总览(指标卡 + 调用排行 + 最近/未使用)
- 选中 skill 的详情面板改为响应式左右分栏(配置在左、统计在右,
窄屏回落上下),含最近调用明细与未命中缺口清单
Claude-Session: https://claude.ai/code/session_01BNN7npEJs4X6LfTtgniMd8
48 lines
1.6 KiB
Go
48 lines
1.6 KiB
Go
package agent
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/Autumn-27/artex/db"
|
|
)
|
|
|
|
// RunInfo identifies WHICH run a tool call belongs to. Tool assembly only receives
|
|
// (ctx, agentKey) — the task/exploration ids live in the caller's arguments, not the
|
|
// ctx — so anything wired at assembly time (currently the Skill ledger in
|
|
// server/assembly.go) has no way to attribute a call to a task. Each run attaches
|
|
// its own RunInfo before calling AugmentTools; the wiring closure reads it once and
|
|
// captures it, so per-run attribution stays correct without threading parameters
|
|
// through the tool layer. Same pattern as TaskClock (see taskclock.go).
|
|
//
|
|
// Zero value = attribution unknown; every consumer must treat it as optional.
|
|
type RunInfo struct {
|
|
TaskID int64 // task registry id; 0 for non-task runs (chat sessions)
|
|
ExplorationID int64 // exploration id; 0 when unknown
|
|
IntentID int64 // worker's intent node; 0 for planner/mainagent/chat
|
|
SessionID string // chat conversation id; empty for task runs
|
|
}
|
|
|
|
// explorationID reads a store's exploration id, tolerating a nil store (planner and
|
|
// worker runs can be driven without one in tests).
|
|
func explorationID(ts *db.ExplorationStore) int64 {
|
|
if ts == nil {
|
|
return 0
|
|
}
|
|
return ts.ID()
|
|
}
|
|
|
|
type runInfoKey struct{}
|
|
|
|
// WithRunInfo attaches run attribution to ctx.
|
|
func WithRunInfo(ctx context.Context, ri RunInfo) context.Context {
|
|
return context.WithValue(ctx, runInfoKey{}, ri)
|
|
}
|
|
|
|
// RunInfoFrom reads the RunInfo (zero value if none attached).
|
|
func RunInfoFrom(ctx context.Context) RunInfo {
|
|
if v, ok := ctx.Value(runInfoKey{}).(RunInfo); ok {
|
|
return v
|
|
}
|
|
return RunInfo{}
|
|
}
|