feat(backthink): 自排的成本可见面(C2 读侧,Y3)

- GET /api/v1/admin/backthink 加可选 ?trigger=(闭集=内建触发标签 + 本服务订阅的事件主题;
  未知/空 ⇒ 400 invalid_trigger,不静默忽略;合法值下推到 SQL ⇒ limit 是匹配条数)
- 新端点 GET /api/v1/admin/backthink/self-wake(writeAuth):闸的只读镜像
  {available,running,reason?,configFile?,gate{pending,used,limit,windowSec,default*,*IsDefault}}
  三态分开(未接线 / 有配置未运行 / 运行中),读它**不消耗额度**
- 零新 env 键;旋钮源 = $DATA_DIR/config/backthink.json(**写面未做**,前置=组合根接线,已登记)
- 键名沿用设计与现有代码的 selfWakePerWindow(派工书写错成 selfWakeLimit,未改键名以免废掉主人已写的配置)
- 5 个变异(未知值静默忽略/关掉下推/恒报运行中/白名单摘键/前端把看不到画成 0/0)
⚠️ 本片改了 4 个文件面外文件(admin_query.go/routes.go/api.ts/adminSystem.ts)——已逐条说明理由
This commit is contained in:
AskaEth committed 2026-10-06 21:48:49 +08:00
1 parent 215eedf8e9
commit 4213b268fe
13 files changed
+1289 -43

No files matched your search

+7 -32
View File
@@ -3,7 +3,6 @@ package gateway
import (
"encoding/json"
"net/http"
"strconv"
"strings"
"time"
@@ -363,41 +362,17 @@ func (s *Server) adminListUsers(w http.ResponseWriter, r *http.Request) {
}
// adminListBackthink 列出最近的后台思考记录(管理读)。
//
// 它现在只做**筛选参数校验**并把读侧交给 writeBackthinkList
// (admin_backthink_filter.go,切片 C2):`?trigger=` 未知取值如实 400
// (design/12 §N C.6 的服务端筛选),合法取值则下推到存储层取匹配条数。
func (s *Server) adminListBackthink(w http.ResponseWriter, r *http.Request) {
out := make([]AdminBackthinkItem, 0)
if s.backthink == nil {
writeJSON(w, http.StatusOK, out)
return
}
records, err := s.backthink.List(r.Context(), backthinkListLimit)
trigger, err := parseBackthinkTrigger(r.URL.Query())
if err != nil {
s.log.Error("list backthink", "err", err)
writeError(w, apperr.Wrap(apperr.ErrInternal, "backthink_failed", "failed to list backthink records"))
writeError(w, err)
return
}
// 安静时段窗口取**运行时真正生效**的那一个(0/0 是「未填」而不是「没有安静时段」,
// 见 admin_config_facts.go 的 effectiveQuietHours —— 与 backthink 的 normalize 同口径),
// 否则 0/0 部署下会漏报真正的穿透。
quietStart, quietEnd := s.effectiveQuietHours()
for _, rec := range records {
quiet := backthinkQuietOutcomeOf(rec, quietStart, quietEnd)
out = append(out, AdminBackthinkItem{
ID: strconv.FormatInt(rec.ID, 10),
Trigger: rec.Trigger,
Conclusion: rec.Conclusion,
ShouldNotify: rec.ShouldPush,
Pushed: rec.PushResult.Pushed,
Route: string(rec.PushResult.Route),
Detail: rec.PushResult.Detail,
Bypassed: rec.PushResult.Bypassed,
At: rec.At.UnixMilli(),
// 紧急度(优先精确档位,旧行带下限标记)/ 是否穿透安静时段:只报存档可证的部分。
Urgency: quiet.urgency,
UrgencyLowerBound: quiet.urgencyLowerBound,
QuietHoursBypassed: quiet.bypassed,
})
}
writeJSON(w, http.StatusOK, out)
s.writeBackthinkList(w, r, trigger)
}
// adminListRag 列出知识库文档,并在带 ?query= 时