主人 2026-10-03 过目 design/22 §8.1 草案并点头后实现。
- 新包 internal/runtime/sessionread:工具壳 + 只读取数 + 归属排版(按职责拆 8 文件)
- 只读由类型保证:注入的 HistoryReader 只声明 List/ListSessions —— 本包结构上无法写入
任何会话、也推进不了目标会话的 read_at
- fail-closed 授权,顺序 = 环境 → 白名单 → 存在性:
非 owner 环境拒(ErrNotOwner)→ 白名单只许 web / web:<uuid>(ErrSessionNotAllowed)
→ 会话不存在是硬错误(ErrSessionNotFound,绝不返回空材料,防"读不到"被当成"对方没说")
- 排版:头部三行恒定(外部会话材料 · 只读 · 不属于你的记忆 / 来源角色 / 来源会话)
+ 时间跨度与翻页游标 + 逐条「[时间] 说话人:」归属
- 说话人取消息自身 persona_id;'' → 全局默认角色;未知 id 原样显示;user → SenderName(空=主人)
- Execute 返回 string:loop 的 formatToolResult 对 string 原样透传,换行不被 json 转义
- 接线 cmd/cyrene/wire.go 一行 + wireSessionReadTool():能力默认标记(双 false)= owner-only,
不新增任何 bypass 路径;审计仍走 loop 的 gate
- 日志 source=runtime/sessionread,只记 actor/target/count/limit/before/has_older,不含正文
验证:go build/vet ./... 绿;internal/runtime/sessionread(25 测试)+ cmd/cyrene 绿;
linux/amd64 + darwin/arm64 交叉编译绿;
TestWireSessionReadToolRegistersOwnerOnly 断言 owner 放行 / group_member / stranger 拒绝
遗留(登记,未解决):§8.1 约束 4 要求"每次调用落审计",但 loop 的 gate 只在拒绝时发
AuditEvent ⇒ 成功读取目前只有日志可追溯,未进审计库;补 loop 侧成功路径钩子需先定口径。
22 lines
1.3 KiB
Go
22 lines
1.3 KiB
Go
// Package sessionread implements the design/22 §8-3「主动读取其他会话上下文」tool: the
|
|
// running role may pull another session's recent messages as *external, read-only
|
|
// material*, rendered with a fixed attribution header so she can never mistake
|
|
// someone else's words for her own memory or her own voice (design/22 §8.1).
|
|
//
|
|
// Read-only by construction, not by discipline: the package depends on narrow
|
|
// interfaces that declare read calls only (HistoryReader, PersonaNamer) and never
|
|
// touches internal/persona/registry directly, so no write — memory write, message
|
|
// append, read-cursor advance, broadcast — is reachable from inside. It also never
|
|
// logs message bodies; only attribution metadata (design/22 §8.1 约束 4).
|
|
//
|
|
// Access is fail-closed (design/22 §8.1 约束 3): the calling conversation must be
|
|
// the owner's, and only the Web main session ("web") and Web sub sessions
|
|
// ("web:<uuid>") may be read. Every other session id — the IM group and stranger
|
|
// sessions in particular — is rejected, and an absent session is a hard error
|
|
// rather than empty material, so "read nothing" can never pass for "they said
|
|
// nothing".
|
|
//
|
|
// The tool is registered by cmd/cyrene (wireSessionReadTool) and reaches the model
|
|
// through the internal/runtime/tool dispatcher like any other tool.
|
|
package sessionread
|