feat(backthink): 思考轮工具面补齐(裁决 15)—— 能力面接入 + 只读 capability_list + 本轮状态快照
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled

- 缺口 d:状态在**思考前**读一次,同一份快照既喂思考轮又喂 Decide(读不到 = Known=false,绝不伪装成中性状态)
- 缺口 c:新增只读工具 capability_list(owner-only、三重输出边界、读写隔离),她可自查当下真实可用面
- 缺口 b:内置允许集补 transcode / capability_list(此前 CAPACITY_ENABLED=true 时新工具会从模型可见面消失)
- 缺口 e:guidancePrompt 旧文案承诺的「IoT/日历/提醒」在全仓根本没有对应工具(chat 侧也没有)⇒ 按诚实化改为「与主人对话同等工具面 + 指名自查入口」,不再有空头支票
- (a) 未做且**不自创**:那三把工具无任何设计规格(名字/schema/读写边界/输出上限)⇒ 已报回并登记进 design/20(裁决:走能力/插件/MCP 暴露路线)
- 变异实测:允许集去掉新工具 ⇒ TestDecide_AllowedSets + 组合根跨层测试红;丢掉状态快照 ⇒ TestLoopThinker_Think_InjectsRoundStateBlock 红
- 已知弱项(如实登记):组合根那行 wireCapabilitySurface() 调用本身无测试覆盖(与既有 wireTranscode 同惯例)
This commit is contained in:
AskaEth committed 2026-10-05 13:54:57 +08:00
1 parent 34cef95e17
commit 321cc3b763
14 files changed
+1664 -73

No files matched your search

+40
View File
@@ -6,9 +6,11 @@ import (
"log/slog"
"strconv"
"strings"
"time"
"git.yeij.top/AskaEth/Cyrene_v2/internal/iot"
"git.yeij.top/AskaEth/Cyrene_v2/internal/logger"
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/history"
)
// StateProvider 为打扰决策提供用户当前状况
@@ -71,6 +73,44 @@ func (p *IoTStateProvider) Current(ctx context.Context) (UserState, error) {
return state, nil
}
// currentState 读取用户当前状况,并回答「到底读到了没」:打扰决策用它作为中性
// 回退(已知为 false 时按零值判定),思考轮用它决定要不要注入状态块(X1 缺口 d
// 让同一份快照同时服务两侧)。提供者为 nil 或读取失败都返回 Known=false,并记一条
// 带来源的 warn 日志 —— 绝不把「读不到」伪装成一份中性状态去误导她的判断。
func (b *Backthink) currentState(ctx context.Context) (UserState, bool) {
if b.deps.State == nil {
return UserState{}, false
}
s, err := b.deps.State.Current(ctx)
if err != nil {
b.log.Warn("backthink state provider failed", "error", err)
return UserState{}, false
}
return s, true
}
// lastUserActivity 报告主人最近一条消息的墙钟时间
// (在主会话里),以及是否找到了任何用户消息。它是活动
// 门的活动信号(design/12 §6):提供者为 nil、读取失败或
// 尾部为空都得到 has=false,因此活动门会落到当前
// 行为而不是靠猜。最近的一条用户消息是最近尾部里
// 最新的 RoleUser 条目(Recent 返回的是最旧在前)。
func (b *Backthink) lastUserActivity(ctx context.Context) (time.Time, bool) {
if b.deps.Recent == nil {
return time.Time{}, false
}
msgs, err := b.deps.Recent.Recent(ctx, history.MainSessionID, recentContextCount)
if err != nil {
return time.Time{}, false
}
for i := len(msgs) - 1; i >= 0; i-- {
if msgs[i].Role == history.RoleUser {
return time.UnixMilli(msgs[i].CreatedAt), true
}
}
return time.Time{}, false
}
// numState 从设备状态 map 中按 keys 读取首个数值。
func numState(d iot.Device, keys ...string) (int, bool) {
for _, k := range keys {