- 四组并行完成:main/wire*.go、wire_capacity|channels|runtime、channels|persona_*|emotion|observability、 voice|plugins|stream|skills|vision|subagent_tool|extractor|mcp|sessionread_tool|terminal_tool。 - 机械校验(git diff -U0 去 // 行与空行)仅剩 wire_frontend.go:84 一处行尾注释:代码前缀 「\ta.gw = gw 」经 cat -A 与 HEAD 逐字节比对一致(allow 清单已声明)。 - 验收:隔离 worktree(HEAD + 本批 patch)go build/vet 绿、全树 58 包 ok、 linux/amd64 + darwin/arm64 交叉编译绿;gofmt -l 干净。 - 备注:整树两次红均为负载敏感 flake(backthink / orchestration 的 timeout 类用例单独 -count=20 全过)。
92 lines
3.4 KiB
Go
92 lines
3.4 KiB
Go
package main
|
|
|
|
import (
|
|
"log/slog"
|
|
"time"
|
|
|
|
"git.yeij.top/AskaEth/Cyrene_v2/internal/logger"
|
|
personareg "git.yeij.top/AskaEth/Cyrene_v2/internal/persona/registry"
|
|
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/sessionread"
|
|
)
|
|
|
|
// wireSessionReadTool 注册 design/22 §8-3 的跨会话读取工具,使运行中的角色能把
|
|
// 另一个 Web 会话的上下文作为外部只读材料拉取过来(design/22 §8.1)。
|
|
//
|
|
// 该能力以默认的可达性标记(两者皆 false)注册,从而保持仅主人可用:design/11 §3 的
|
|
// 策略引擎会对群聊/陌生人会话隐藏其 schema,循环的安全门也会在那里拒绝调用。工具
|
|
// 自身的 fail-closed 检查(主人环境 + web/web:<uuid> 白名单)运行在那道门之后,
|
|
// 因此未授权的读取会被双重拒绝。审计链路就是循环的 gate/OnAudit,与其他每个工具
|
|
// 完全一致 —— 这里不添加任何绕过路径。
|
|
func (a *app) wireSessionReadTool() {
|
|
log := logger.WithSource(a.log, "cmd/cyrene")
|
|
if a.history == nil {
|
|
log.Warn("session read tool skipped: no history store")
|
|
return
|
|
}
|
|
if a.dispatcher == nil {
|
|
log.Warn("session read tool skipped: no tool dispatcher")
|
|
return
|
|
}
|
|
reader := sessionread.New(
|
|
sessionread.WithHistory(a.history),
|
|
sessionread.WithPersonas(sessionReadPersonas{reg: a.personas}),
|
|
sessionread.WithLocation(a.sessionReadLocation(log)),
|
|
sessionread.WithLogger(a.log),
|
|
)
|
|
if err := a.dispatcher.Register(reader); err != nil {
|
|
log.Warn("session read tool register skipped", "error", err)
|
|
return
|
|
}
|
|
log.Info("session read tool registered", "tool", sessionread.ToolName, "owner_only", true)
|
|
}
|
|
|
|
// sessionReadLocation 解析材料时间戳所用的已配置 IANA 时区,使外部 transcript 与
|
|
// 循环逐轮注入的「当前时间」读同一个时钟(internal/runtime/loop/time.go)。未设置
|
|
// 时区或加载失败时返回 nil,保持工具的进程本地默认值 —— 读取不该因为一个展示设置
|
|
// 而失败。
|
|
func (a *app) sessionReadLocation(log *slog.Logger) *time.Location {
|
|
if a.cfg == nil || a.cfg.Timezone == "" {
|
|
return nil
|
|
}
|
|
loc, err := time.LoadLocation(a.cfg.Timezone)
|
|
if err != nil {
|
|
log.Warn("session read tool timezone load failed; using the process-local zone",
|
|
"tz", a.cfg.Timezone, "error", err)
|
|
return nil
|
|
}
|
|
return loc
|
|
}
|
|
|
|
// sessionReadPersonas 把人格注册表适配为 sessionread.PersonaNamer。该工具包刻意不
|
|
// 导入 internal/persona/registry(而是持有一个窄接口,保持分层单向),因此由组合根
|
|
// 提供显示名查询。
|
|
type sessionReadPersonas struct {
|
|
reg *personareg.Registry
|
|
}
|
|
|
|
// PersonaName 实现 sessionread.PersonaNamer:返回人格的显示名;对未知/未注册的角色
|
|
// 返回 ok=false,使工具展示原始 id 而不是编造一个名字。
|
|
func (p sessionReadPersonas) PersonaName(id string) (string, bool) {
|
|
if p.reg == nil || id == "" {
|
|
return "", false
|
|
}
|
|
persona, ok := p.reg.Persona(id)
|
|
if !ok || persona == nil || persona.Name == "" {
|
|
return "", false
|
|
}
|
|
return persona.Name, true
|
|
}
|
|
|
|
// DefaultPersonaID 实现 sessionread.PersonaNamer:返回库默认角色所绑定的人格,与
|
|
// (*app).defaultPersonaID 保持一致。空 id 表示材料渲染显式的「未设置角色」标记,
|
|
// 而不是靠猜。
|
|
func (p sessionReadPersonas) DefaultPersonaID() string {
|
|
if p.reg == nil {
|
|
return ""
|
|
}
|
|
if c, ok := p.reg.DefaultCharacter(); ok && c != nil {
|
|
return c.PersonaID
|
|
}
|
|
return ""
|
|
}
|