mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-09-21 09:28:14 +00:00
## 数据面补齐 - doc.insert / doc.insertWithMedia:新增 doc_ref / attachments_ref, 模板序列化后 putValueInArena - knowledge.add:新增 content_ref(内容是 JSON 字符串,读出后再解一层) - 抽出通用 resolveJSONRef(resolveBlocks 也改用它),三处共用一套 “共享优先、内联回退”逻辑 ## 协议版本 bump:让错配显式失败,而不是静默坏 这是本轮更重要的部分。§13.6/§13.13 改了内核→插件 payload 的承载方式, 两种错配都不会报错、只会静默失效: - v1 插件只读内联 args(tool/cleaner/output)→ 遇到 v2 内核拿到空参数 - v2 插件发 blocks_ref → v1 内核反序列化时静默忽略(旧内核 io.setToolBlocks 还是桩实现) 现场表现为“输出变空 / 图注入没反应”,极难定位。所以把 ProtocolVersion 与模板 procProtocolVersion 一起 bump 到 2:双方都是等值校验,v1 插件遇上 v2 内核会在建链时明确报“协议版本不匹配…请用配套 plugindev 重编”。 测试里把“错误必须带出重编指令”也断言上了——生产上碰到它的现场就是 “只更新了内核没重编插件”,光报“不匹配”定位不到行动。 testdata 8 个插件的 protocol 同步更新(badprotoplugin 仍用 999 验证拒绝)。 工具链已重建并安装(协议 2,内嵌 blocks_ref/doc_ref/content_ref), 回滚副本 plugindev.bak-20260910-232544。 验证:-race 全绿。新增 KnowledgeAddViaArena(12000B 正文)、 KnowledgeAddInline、DocInsertViaArena、SetToolBlocks 三例 + 真实模板 e2e + 协议不匹配断言强化。 §13.13 第 5 条(反向大结果)范围更大——需把内核→插件的应答路径整体改成 “大结果写段 + 返回 ref”,涉及 callCore 的返回处理与 doc.query/llm.chat 等 所有读大结果的 method。已在 plan.md 标注未做,不冒充完成。
182 lines
4.0 KiB
Go
182 lines
4.0 KiB
Go
//go:build ignore
|
||
|
||
// readonlyplugin 是只读 stage 插件(模拟 weather 的 AfterToolcall):
|
||
// 读取共享段但不写回任何字段。
|
||
//
|
||
// **这是 lost update 修复的关键验证对象**:C ABI 副本模型下,
|
||
// 它会把自己收到的旧快照无条件回传,覆盖 sanitizer 的清洗结果
|
||
// (§8.6 实测现网 1.6~4.3% 被覆盖)。共享内存模型下它零写入。
|
||
package main
|
||
|
||
import (
|
||
"bufio"
|
||
"encoding/binary"
|
||
"encoding/json"
|
||
"fmt"
|
||
"os"
|
||
"sync"
|
||
"syscall"
|
||
)
|
||
|
||
type request struct {
|
||
ID uint64 `json:"id,omitempty"`
|
||
Method string `json:"method"`
|
||
Params json.RawMessage `json:"params,omitempty"`
|
||
}
|
||
|
||
type response struct {
|
||
ID uint64 `json:"id"`
|
||
Result interface{} `json:"result,omitempty"`
|
||
Error string `json:"error,omitempty"`
|
||
}
|
||
|
||
var (
|
||
out = bufio.NewWriter(os.Stdout)
|
||
writeMu sync.Mutex
|
||
|
||
nextID uint64
|
||
pendMu sync.Mutex
|
||
pending = map[uint64]chan json.RawMessage{}
|
||
|
||
shm []byte
|
||
)
|
||
|
||
func send(v interface{}) {
|
||
b, _ := json.Marshal(v)
|
||
writeMu.Lock()
|
||
out.Write(b)
|
||
out.WriteByte('\n')
|
||
out.Flush()
|
||
writeMu.Unlock()
|
||
}
|
||
|
||
func callKernel(method string, params interface{}) json.RawMessage {
|
||
pendMu.Lock()
|
||
nextID++
|
||
id := nextID
|
||
ch := make(chan json.RawMessage, 1)
|
||
pending[id] = ch
|
||
pendMu.Unlock()
|
||
|
||
var raw json.RawMessage
|
||
if params != nil {
|
||
b, _ := json.Marshal(params)
|
||
raw = b
|
||
}
|
||
send(request{ID: id, Method: method, Params: raw})
|
||
return <-ch
|
||
}
|
||
|
||
const (
|
||
offArenaBase = 8
|
||
offCtxBase = 20
|
||
sliceSize = 8
|
||
fToolResults = 10
|
||
)
|
||
|
||
func readToolResults() []byte {
|
||
base := binary.LittleEndian.Uint32(shm[offArenaBase:])
|
||
cb := binary.LittleEndian.Uint32(shm[offCtxBase:])
|
||
o := cb + uint32(fToolResults*sliceSize)
|
||
off := binary.LittleEndian.Uint32(shm[o:])
|
||
ln := binary.LittleEndian.Uint32(shm[o+4:])
|
||
if off == 0 && ln == 0 {
|
||
return nil
|
||
}
|
||
return shm[base+off : base+off+ln]
|
||
}
|
||
|
||
func main() {
|
||
in := bufio.NewScanner(bufio.NewReader(os.Stdin))
|
||
in.Buffer(make([]byte, 0, 64*1024), 1024*1024)
|
||
|
||
for in.Scan() {
|
||
line := make([]byte, len(in.Bytes()))
|
||
copy(line, in.Bytes())
|
||
|
||
var probe struct {
|
||
ID uint64 `json:"id"`
|
||
Method string `json:"method"`
|
||
}
|
||
if json.Unmarshal(line, &probe) != nil {
|
||
continue
|
||
}
|
||
if probe.Method == "" {
|
||
var resp struct {
|
||
ID uint64 `json:"id"`
|
||
Result json.RawMessage `json:"result"`
|
||
}
|
||
json.Unmarshal(line, &resp)
|
||
pendMu.Lock()
|
||
ch, ok := pending[resp.ID]
|
||
delete(pending, resp.ID)
|
||
pendMu.Unlock()
|
||
if ok {
|
||
ch <- resp.Result
|
||
}
|
||
continue
|
||
}
|
||
|
||
var req request
|
||
json.Unmarshal(line, &req)
|
||
|
||
switch req.Method {
|
||
case "handshake":
|
||
var hp struct {
|
||
ShmSize int `json:"shm_size"`
|
||
}
|
||
json.Unmarshal(req.Params, &hp)
|
||
if hp.ShmSize > 0 {
|
||
m, err := syscall.Mmap(3, 0, hp.ShmSize,
|
||
syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_SHARED)
|
||
if err != nil {
|
||
send(response{ID: req.ID, Error: fmt.Sprintf("mmap: %v", err)})
|
||
continue
|
||
}
|
||
// 统一区域:前 64B 是 SuperBlock,StageContext 段在其后
|
||
ctxOff := binary.LittleEndian.Uint32(m[20:])
|
||
ctxSize := binary.LittleEndian.Uint32(m[24:])
|
||
shm = m[ctxOff : ctxOff+ctxSize]
|
||
}
|
||
send(response{ID: req.ID, Result: map[string]interface{}{
|
||
"protocol": 2, "sdk_version": "test", "plugin_name": "readonly", "pid": os.Getpid(),
|
||
}})
|
||
|
||
case "plugin.init":
|
||
send(response{ID: req.ID})
|
||
|
||
case "plugin.start":
|
||
go func(id uint64) {
|
||
callKernel("stage.register", map[string]interface{}{
|
||
"stage": "after_toolcall",
|
||
"scope": "global",
|
||
})
|
||
send(response{ID: id})
|
||
}(req.ID)
|
||
|
||
case "plugin.stop":
|
||
send(response{ID: req.ID})
|
||
out.Flush()
|
||
os.Exit(0)
|
||
|
||
case "stage.invoke":
|
||
go func(id uint64) {
|
||
if shm == nil {
|
||
send(response{ID: id, Error: "共享段未挂载"})
|
||
return
|
||
}
|
||
callKernel("stage.lock", nil)
|
||
// 只读:读了但一个字节都不写回
|
||
_ = readToolResults()
|
||
callKernel("stage.unlock", nil)
|
||
send(response{ID: id, Result: map[string]interface{}{"dirty_fields": 0}})
|
||
}(req.ID)
|
||
|
||
default:
|
||
if req.ID != 0 {
|
||
send(response{ID: req.ID})
|
||
}
|
||
}
|
||
}
|
||
}
|