- 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)——已逐条说明理由
98 lines
5.3 KiB
Go
98 lines
5.3 KiB
Go
// backthink.go 是后台思考(backthink)的**可变源**:`$DATA_DIR/config/backthink.json`
|
||
// (design/12 §N C.5 + design/13 §2.1「运行期生成的配置进 `$DATA_DIR/config/`,
|
||
// 新增配置家族从一开始就只写这里,不落老位置」)。
|
||
//
|
||
// 边界划清(与 websearch.go 同源):
|
||
//
|
||
// 1. 本文件只管**文件形状 + 读取**:它不认识 `backthink.Config`,也不认识
|
||
// 「≤0 回落默认」这条裁决 —— 那是服务侧 normalize 的事。这里只如实把盘上的
|
||
// 数字交出去(零值 = 没配,由调用方用代码默认值兜)。
|
||
// 2. **只读**:写路径(面板保存 + admin 端点)不在 C1 内,属 C2。因此本文件
|
||
// 刻意没有 `Save*`:多一个没人调用的写口,就多一条没人验证的落盘语义。
|
||
// 3. **零新 env 键**(主人 2026-10-06 `.env` 冻结红线):自排的成本旋钮只从这里来,
|
||
// 不进 `.env` / `.env.example`,也不进面板的事实卡(那张表的键名被模板钉住)。
|
||
//
|
||
// 读取口径:文件缺失或全空白 = 还没配过(零值 + nil,**不是错误**);
|
||
// 存在但解不开 = 返回错误,调用方记 WARN 并回落**代码默认值** —— 绝不静默变成
|
||
// 「无上限」(design/12 §N C.5 的 G3 同一条纪律:坏配置不许把成本保护关掉)。
|
||
package runtimeconfig
|
||
|
||
import (
|
||
"encoding/json"
|
||
"errors"
|
||
"fmt"
|
||
"os"
|
||
"path/filepath"
|
||
"strings"
|
||
)
|
||
|
||
// backthinkFileName 是文件名(家族命名 = 服务/模块名 + .json)。
|
||
const backthinkFileName = "backthink.json"
|
||
|
||
// backthinkConfigSubdir 是落点的第一段目录(`$DATA_DIR/config/backthink.json`)。
|
||
// 与 web-search 插件各自声明一份而不是共用一个常量:两处的目录段虽然今天相同,
|
||
// 但一旦某个家族换位置,共用常量会让另一处**静默**跟着搬家。
|
||
const backthinkConfigSubdir = "config"
|
||
|
||
// BackthinkConfigRelPath 是落点相对 `$DATA_DIR` 的路径(`config/backthink.json`)。
|
||
//
|
||
// 单独导出它,是为了让面板能**如实**告诉主人「改哪个文件」,而不必把部署相关的
|
||
// 绝对路径塞进接口形状(绝对路径随部署而变,由占用它的部署自己拼 `$DATA_DIR`)。
|
||
// 它与 `DefaultBackthinkPath` 取自同一对常量(目录段 + 文件名),因此不会漂移。
|
||
const BackthinkConfigRelPath = backthinkConfigSubdir + "/" + backthinkFileName
|
||
|
||
// ErrInvalidBackthink 是 backthink.json 结构不合法时的分类错误(errors.Is 可判类)。
|
||
var ErrInvalidBackthink = errors.New("runtimeconfig: invalid backthink config")
|
||
|
||
// BackgroundTuning 是 `backthink.json` 的形状:她自己排起一轮思考的成本旋钮。
|
||
//
|
||
// 字段名与 design/12 §N C.5 的规格逐字一致(camelCase 的 JSON 键,文件是给人看、
|
||
// 给人改的)。**没有版本字段**:这个家族只有这两个数字,等它真的长形状时再加版本,
|
||
// 现在加一个永远为 0 的字段只是噪音。
|
||
//
|
||
// ⚠️ **写路径未实现(切片 C2 如实登记)**:本文件至今只有读口。C.5 把「面板保存 +
|
||
// `SaveBackgroundTuning` + admin 端点」列为 C2,而面板写路径要经 design/09 §5 的
|
||
// 面板声明机制(`Plugins.Declare` + 动作分发),那需要组合根接线(`cmd/cyrene`)
|
||
// 与一个 `backthink` 面板声明 —— 都不在 C2 的文件面内。因此 C2 只交付**读侧**:
|
||
// 运行期生效的旋钮经 `GET /api/v1/admin/backthink/self-wake` 可见(含代码默认值),
|
||
// 改动仍要**人手改这个文件 + 重启**(与 `QUIET_*` 同口径:启动期读一次)。
|
||
type BackgroundTuning struct {
|
||
// SelfWakePerWindow 是每窗口允许的自排轮数。零/负 = 未声明或笔误 ⇒
|
||
// 由服务侧 normalize 回落代码默认(**没有「关闭上限」语义**)。
|
||
SelfWakePerWindow int `json:"selfWakePerWindow"`
|
||
// SelfWakeWindowSec 是执行上述上限的滑动窗口秒数。零/负 ⇒ 同上回落默认。
|
||
SelfWakeWindowSec int `json:"selfWakeWindowSec"`
|
||
}
|
||
|
||
// DefaultBackthinkPath 返回后台思考调优的落点:`$DATA_DIR/config/backthink.json`。
|
||
func DefaultBackthinkPath(dataDir string) string {
|
||
return filepath.Join(dataDir, backthinkConfigSubdir, backthinkFileName)
|
||
}
|
||
|
||
// LoadBackgroundTuning 读取 backthink.json(**只读**)。
|
||
//
|
||
// 文件缺失或全空白 ⇒ 零值 + nil:调用方用代码默认值(`normalize` 会把 ≤0 回落默认),
|
||
// 因此「没配过」与「配成 0」得到同一个结果,正是这条裁决要的。
|
||
//
|
||
// 存在但解不开 ⇒ 返回错误。调用方应记一条带 source 的 WARN 并回落默认;
|
||
// **不得覆盖**那个文件(损坏的文件可以人工救回,程序盖掉它就丢了人工写的意图)。
|
||
//
|
||
// 它不建目录、不读老位置:新家族没有老位置(design/13 §2.1 第 5 条)。
|
||
func LoadBackgroundTuning(path string) (BackgroundTuning, error) {
|
||
raw, err := os.ReadFile(path)
|
||
if err != nil {
|
||
if os.IsNotExist(err) {
|
||
return BackgroundTuning{}, nil
|
||
}
|
||
return BackgroundTuning{}, fmt.Errorf("runtimeconfig: read %s: %w", path, err)
|
||
}
|
||
if strings.TrimSpace(string(raw)) == "" {
|
||
return BackgroundTuning{}, nil
|
||
}
|
||
var t BackgroundTuning
|
||
if err := json.Unmarshal(raw, &t); err != nil {
|
||
return BackgroundTuning{}, fmt.Errorf("%w: decode %s: %w", ErrInvalidBackthink, path, err)
|
||
}
|
||
return t, nil
|
||
}
|