stderr 的 JSONL 是无主人在场的落盘面,却在此前原样落工具结果(result)、 子代理回复(text/summary)与上游报文,等于一次 tail 就能拷走整段对话。 本切片交付两件地基: 1) 写侧脱敏(trace.JSONRecorder) - 新增 trace/span_props.go:safePropKeys 白名单与 projectSpanProps 投影, 读侧(safeSpanProps)与写侧共用同一张表、同一个投影函数; - WriteSpan 落盘前经 trace.SanitizeRecord 投影:白名单键清洗,表外键只留 Digest(bytes/runes/sha256 前缀,收进 omitted),事件先 RedactEvent 再把 正文键折成 Digest。落到磁盘的只有元数据,正文一个字节都不留; - 新增 trace/digest.go:Digest/DigestOf/DigestOfValue,给「脱敏后仍要能诊断」 一个构造上不含正文的取值形态(体积 + 指纹),两条投影都按类型放行它。 2) 共享上游错误体收敛 helper(给 M41/M42,禁止三家各写一份) - trace.UpstreamError(upstream, status, contentType, body):只保留 status / contentType / bodyLen / body sha256 前缀 / 上游 JSON 的 error.code / error.type / 截断 128 rune 的 error.message,其余一律丢弃,响应体原文绝不 进 error 或日志;*UpstreamErrorInfo 提供 Error()/Attrs()/Unwrap()(errors.Is 可判 ErrUpstream 走降级链); - trace.HostOf / trace.MaskQuery:日志里的 URL 只回来源,凭据类 query 与 fragment 值打码为 ***,user:pass@ 整段打码。 3) 剥掉三处正文上 span - loop:tool span 不再放 result,改放 result_summary(与 tool_execution.end 帧同源、已脱敏截断)与 result_digest; - orchestration:subagent span 不再放 text,subagent.done 事件不再放 summary, 改记 status/steps/chars/sha256; - automations:AddRuleByName 不再 %v 整个 payload,改记 subject/type/bytes/sha256。 测试:以哨兵正文在落盘字节上断言(JSONL / slog 输出),并分别对「编排层收到的 原始 span」与「写侧投影」两层各钉一遍;UpstreamError 用回显 input 的 422 报文 与 HTML 错误页各钉一条;解析出/未解析出 JSON 都不回原文。以上均做过变异验证 (去掉保护即变红)。 验收:go build ./... + go vet ./... + go test -count=1 ./... 全绿; trace/loop/orchestration 另跑 -race 全绿。
批次A admin 端点(原本 404 变 200 真实数据): - GET /admin/llm:读 s.cfg.LLM 组 openai+anthropic 两 provider(enabled=当前 Provider、multimodal=配 VisionModel),APIKey 不进 wire 零泄露。 - GET /admin/automations:*automations.Service.List()(reminders+event rules+cron schedules,cron 加只读登记)。 - GET /admin/users:cfg.AdminUser + *auth.APIKey.List()(密钥掩码);无用户存储,恒先返回当前 admin 账号。 - GET /admin/backthink:*backthink.Backthink.List(ctx,50)。均只读、owner/admin RBAC、{data} 信封。 批次B 视觉真推理 + 语音: - rest.go chatRequest 加 Image?;流式 publishChatStream 带 image;userChatMessages 构造 ImageMessage(model.go:71)。 - loop/vision.go:ImageResolver 可注入(MVP 先看图再对话);run.go Run 开头 resolveImages,无 resolver 透传直交主模型。 - vision/resolver.go:MessageResolver 调 vision.Router.Route——KindMain 保留图片交主模型多模态、KindVisionTool/OCRTool 经 dispatcher.Invoke 折叠成 [图片内容] 文本、KindUnsupported 记日志保留原图不崩。 - gateway/voice.go:VoiceService 接口 + SetVoice 注入点 + handleVoiceInput(未注入回 error 不崩)+ runVoiceTurn 异步 STT→loop chat→TTS→定向 voice_output 帧。 - ws_hub/ws_route:voice_input 接 handleVoiceInput;前端 ws.ts decodeBase64/playVoiceAudio/case voice_output。 第3步 wire.go 收口(唯一碰 wire.go 的一步): - Deps 补传 Automations:a.automation / Backthink:a.backthink / Users:auth.NewAPIKey(a.cfg.Auth)(*auth.APIKey 原生满足 UserStore,避免 admin 重复的复合类型)。 - gw.SetVoice(a.voice);loop.Options.Vision = vision.NewMessageResolver(router, info, dispatcher.Invoke)(router=NewRouter(WithVisionTool/WithOCRTool)、info=cfg.LLM.VisionModel)。 实机验证:四 admin 端点 200 真实数据(llm 双 provider 脱敏、users=admin,yeij0942,owner);WS 握手正常。go build/vet/test 全绿 + 前端 tsc。wire.go 557 行。