Files
homeagent-sdk/tools/plugindev/templates/proc_main.go.tmpl
JianFeeeee 09b64dcb53 plugindev: 支持子进程插件构建(entry=plugin.bin,零 cgo)
Part 3 工具链改造。插件业务代码零改动,只需把 plg.json 的 entry
从 plugin.so 换成 plugin.bin。

新增 templates/proc_main.go.tmpl(1113 行)——子进程运行时:
- 51 个 core method 的插件侧 RPC 实现(procIO/procMemory/procSettings/
  procSocial/procLLM/procKnowledge/procDocMemory/procTextMemory/procPluginMgr)
- 共享段访问(fd 3 = 内核经 ExtraFiles 传入的 memfd)+ 16 字段
  StageContext 编解码,布局常量与 internal/plugin/proc/shm.go 逐一对齐
- handleStageInvoke:拿锁 → 读段 → handler → **只写脏字段** → 放锁。
  只读插件脏字段集为空 → 零写入 → 不可能覆盖他人改写
  (对照 C ABI 副本模型实测 35.8~36.8% lost update)
- 主循环每请求独立 goroutine:handler 内会反向调用内核并等应答,
  在读循环里同步处理会死锁
- plugin.start 后显式上报 AutoRestart:公开 SDK 的 SetAutoRestart 是纯
  setter 无 hook,隔着进程边界内核读不到(内核侧 corehandler.go:145 已就绪)

模板选择真实 .go 源文件 + //go:embed 而非 raw string:900+ 行代码塞在
字符串里写错只能等生成插件时才炸,作为源文件可被 parser/gofmt/vet 检查。

cmd_build.go:resolveBuild(target, proc) 分派;proc 走 go build -trimpath
+ CGO_ENABLED=0,交叉编译不再需要目标平台 C 工具链。bundle 模式各平台
产物同名故 zip 内加平台后缀(plugin.bin.linux.amd64)。

proc_runtime.go 生成时清理残留 z_bridge_gen.go/z_entry.c——同目录两套
main 会编译冲突,这让 .so → .bin 切换无需人工清理。

proc_runtime_test.go 16 项静态检查,防内核/插件两侧漂移:
method 名清单、7 个内核调用、共享段常量与字段枚举顺序、stage 加锁顺序、
快照必须存序列化字符串(切片共享底层数组的坑在 11.3 已踩过)、
arena 不足须报错、日志走 stderr、版本不匹配须拒绝、零 cgo。

验证:真实 plugindev 构建 example/weather,plugin.go 逐字节未改,
产出静态链接 ELF;git diff sdk/ 为空(接口冻结)。

Ref: docs/zh/架构迁移评估.md §3、docs/zh/plugin-migration-plan.md Part 3
2026-09-02 12:13:45 +08:00

1111 lines
29 KiB
Cheetah
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package main
// 子进程插件入口(由 plugindev 自动生成,请勿手工编辑)。
//
// 与旧 C ABI bridgez_bridge_gen.go的关键差异
// - **零 cgo**:没有 //export、没有 C.CString/C.free、不需要 -buildmode=c-shared
// - 51 个整数 method id 换成可读 method 名(内核侧 internal/plugin/proc/protocol.go
// - StageContext 走共享内存fd 3 传入的 memfd插件在同一份状态上读改写
// 消除副本模型的 lost update实测 35.8~36.8% → 0
// - **插件业务代码零改动**:仍是 NewPluginFactory + sdk.PluginSDK
//
// 设计依据docs/zh/架构迁移评估.md 第三章
import (
"bufio"
"encoding/binary"
"encoding/json"
"fmt"
"log"
"os"
"sync"
"syscall"
sdk "gitcode.com/JianFeeeee/homeagent-sdk/sdk"
)
// ---- 协议常量(须与内核 internal/plugin/proc/protocol.go 一致)----
const procProtocolVersion = 1
// ---- 共享段布局(须与内核 internal/plugin/proc/shm.go 一致)----
const (
shmStageFieldCount = 18
shmSliceSize = 8
shmOffMagic = 0
shmOffVersion = 4
shmOffArenaBase = 8
shmOffArenaCap = 12
shmOffArenaUsed = 16
shmOffCtxBase = 20
shmOffSeq = 24
shmMagic = 0x48415348
shmVersion = 1
)
// 字段索引(顺序须与内核 stageField 枚举一致)
const (
fRawMessage = iota
fUserID
fGroupID
fLLMText
fReasoningContent
fFinalText
fResponse
fPhase
fContextMsgs
fToolCalls
fToolResults
fMemory
fTokenUsage
fErrors
fExtraMediaBlocks
fExtraMediaType
fExtraInputSource
fExtraOutputChannel
)
const (
flagNoMemory = 0
flagResponseSet = 1
)
// ---- 全局状态 ----
var (
stdoutW = bufio.NewWriter(os.Stdout)
writeMu sync.Mutex
nextID uint64
pendMu sync.Mutex
pending = map[uint64]chan rpcResponse{}
plg sdk.Plugin
pluginSDK *sdk.PluginSDK
pluginName string
handlerMu sync.RWMutex
toolHandlers = map[string]sdk.ToolHandler{}
stageHandlers = map[string]sdk.StageHandler{}
outputHandlers = map[string]sdk.ToolHandler{}
shm []byte
)
type rpcRequest struct {
ID uint64 `json:"id,omitempty"`
Method string `json:"method"`
Params json.RawMessage `json:"params,omitempty"`
}
type rpcResponse struct {
ID uint64 `json:"id"`
Result json.RawMessage `json:"result,omitempty"`
Error string `json:"error,omitempty"`
}
func writeFrame(v interface{}) {
b, err := json.Marshal(v)
if err != nil {
log.Printf("序列化帧失败: %v", err)
return
}
writeMu.Lock()
stdoutW.Write(b)
stdoutW.WriteByte('\n')
stdoutW.Flush()
writeMu.Unlock()
}
func respond(id uint64, result interface{}) {
resp := rpcResponse{ID: id}
if result != nil {
if b, err := json.Marshal(result); err == nil {
resp.Result = b
}
}
writeFrame(&resp)
}
func respondErr(id uint64, err error) {
writeFrame(&rpcResponse{ID: id, Error: err.Error()})
}
// callCore 反向调用内核(对应旧 bridge 的 callVoid/callString
func callCore(method string, params interface{}) (json.RawMessage, error) {
pendMu.Lock()
nextID++
id := nextID
ch := make(chan rpcResponse, 1)
pending[id] = ch
pendMu.Unlock()
var raw json.RawMessage
if params != nil {
b, err := json.Marshal(params)
if err != nil {
return nil, err
}
raw = b
}
writeFrame(&rpcRequest{ID: id, Method: method, Params: raw})
resp := <-ch
if resp.Error != "" {
return nil, fmt.Errorf("%s", resp.Error)
}
return resp.Result, nil
}
func callCoreVoid(method string, params interface{}) error {
_, err := callCore(method, params)
return err
}
// ---- 共享段访问插件作者永远不接触这些§3.4----
func shmU32(off int) uint32 { return binary.LittleEndian.Uint32(shm[off:]) }
func shmArenaBase() uint32 { return shmU32(shmOffArenaBase) }
func shmArenaCap() uint32 { return shmU32(shmOffArenaCap) }
func shmCtxBase() uint32 { return shmU32(shmOffCtxBase) }
func shmDescOff(field int) uint32 {
return shmCtxBase() + uint32(field*shmSliceSize)
}
func shmGetDesc(field int) (off, ln uint32) {
o := shmDescOff(field)
return binary.LittleEndian.Uint32(shm[o:]), binary.LittleEndian.Uint32(shm[o+4:])
}
func shmSetDesc(field int, off, ln uint32) {
o := shmDescOff(field)
binary.LittleEndian.PutUint32(shm[o:], off)
binary.LittleEndian.PutUint32(shm[o+4:], ln)
}
func shmFlagsOff() uint32 {
return shmCtxBase() + uint32(shmStageFieldCount*shmSliceSize)
}
func shmGetFlag(bit int) bool { return shm[shmFlagsOff()+uint32(bit)] != 0 }
func shmSetFlag(bit int, v bool) {
b := byte(0)
if v {
b = 1
}
shm[shmFlagsOff()+uint32(bit)] = b
}
func shmRead(field int) []byte {
off, ln := shmGetDesc(field)
if off == 0 && ln == 0 {
return nil
}
if ln == 0 {
return []byte{}
}
base := shmArenaBase()
return shm[base+off : base+off+ln]
}
// shmWrite 在 arena 上 append-only 分配并更新描述符。
// arena 用尽显式报错,不静默截断(与内核侧同一约定)。
func shmWrite(field int, data []byte) error {
if len(data) == 0 {
shmSetDesc(field, 1, 0)
return nil
}
used := shmU32(shmOffArenaUsed)
if used == 0 {
used = 1
}
end := used + uint32(len(data))
if end > shmArenaCap() {
return fmt.Errorf("共享段 arena 空间不足:需要 %d 字节,容量 %d已用 %d",
len(data), shmArenaCap(), used)
}
base := shmArenaBase()
copy(shm[base+used:], data)
binary.LittleEndian.PutUint32(shm[shmOffArenaUsed:], end)
shmSetDesc(field, used, uint32(len(data)))
return nil
}
func shmBumpSeq() {
v := binary.LittleEndian.Uint64(shm[shmOffSeq:])
binary.LittleEndian.PutUint64(shm[shmOffSeq:], v+1)
}
// readStageContext 从共享段构造插件侧原生 StageContext。
// 全 16 个字段可见——C ABI 下只有 10 个§8.3)。
func readStageContext() (*sdk.StageContext, error) {
sc := &sdk.StageContext{}
sc.RawMessage = string(shmRead(fRawMessage))
sc.UserID = string(shmRead(fUserID))
sc.GroupID = string(shmRead(fGroupID))
sc.LLMText = string(shmRead(fLLMText))
sc.ReasoningContent = string(shmRead(fReasoningContent))
sc.FinalText = string(shmRead(fFinalText))
sc.Phase = sdk.Stage(string(shmRead(fPhase)))
sc.NoMemory = shmGetFlag(flagNoMemory)
if shmGetFlag(flagResponseSet) {
r := string(shmRead(fResponse))
sc.Response = &r
}
unmarshalField := func(field int, out interface{}) error {
b := shmRead(field)
if len(b) == 0 {
return nil
}
return json.Unmarshal(b, out)
}
if err := unmarshalField(fContextMsgs, &sc.ContextMsgs); err != nil {
return nil, err
}
if err := unmarshalField(fToolCalls, &sc.ToolCalls); err != nil {
return nil, err
}
if err := unmarshalField(fToolResults, &sc.ToolResults); err != nil {
return nil, err
}
if err := unmarshalField(fMemory, &sc.Memory); err != nil {
return nil, err
}
if err := unmarshalField(fTokenUsage, &sc.TokenUsage); err != nil {
return nil, err
}
if err := unmarshalField(fErrors, &sc.Errors); err != nil {
return nil, err
}
extra := map[string]interface{}{}
for _, pair := range []struct {
field int
key string
}{
{fExtraMediaBlocks, "media_blocks"},
{fExtraMediaType, "media_type"},
{fExtraInputSource, "input_source"},
{fExtraOutputChannel, "output_channel"},
} {
var v interface{}
if err := unmarshalField(pair.field, &v); err != nil {
return nil, err
}
if v != nil {
extra[pair.key] = v
}
}
if len(extra) > 0 {
sc.Extra = extra
}
return sc, nil
}
// stageSnapshot 是 handler 运行前的序列化快照,用于计算脏字段。
//
// ❗ 必须存序列化后的字符串handler 原地改切片元素
// sc.ToolResults[0].Result = x直接持有的 Go 值快照会跟着变,
// 脏字段计算失效——这个坑在修 C ABI 侧的 11.3 时已经踩过一次。
type stageSnapshot struct {
strs map[int]string
jsons map[int]string
response string
responseSet bool
noMemory bool
}
func takeStageSnapshot(sc *sdk.StageContext) *stageSnapshot {
sn := &stageSnapshot{strs: map[int]string{}, jsons: map[int]string{}}
sn.strs[fRawMessage] = sc.RawMessage
sn.strs[fUserID] = sc.UserID
sn.strs[fGroupID] = sc.GroupID
sn.strs[fLLMText] = sc.LLMText
sn.strs[fReasoningContent] = sc.ReasoningContent
sn.strs[fFinalText] = sc.FinalText
sn.strs[fPhase] = string(sc.Phase)
marshal := func(v interface{}, n int) string {
if n == 0 {
return ""
}
b, err := json.Marshal(v)
if err != nil {
return ""
}
return string(b)
}
sn.jsons[fContextMsgs] = marshal(sc.ContextMsgs, len(sc.ContextMsgs))
sn.jsons[fToolCalls] = marshal(sc.ToolCalls, len(sc.ToolCalls))
sn.jsons[fToolResults] = marshal(sc.ToolResults, len(sc.ToolResults))
sn.jsons[fMemory] = marshal(sc.Memory, len(sc.Memory))
sn.jsons[fTokenUsage] = marshal(sc.TokenUsage, len(sc.TokenUsage))
sn.jsons[fErrors] = marshal(sc.Errors, len(sc.Errors))
if sc.Response != nil {
sn.response = *sc.Response
sn.responseSet = true
}
sn.noMemory = sc.NoMemory
return sn
}
// writeStageDirty 只把变更字段写回共享段,返回写回字段数。
//
// **这是消除 lost update 的核心**:只读插件的脏字段集为空 → 零写入 →
// 不可能覆盖其他插件的改写(对照 C ABI 副本模型实测 35.8~36.8% 丢失)。
func writeStageDirty(sc *sdk.StageContext, base *stageSnapshot) (int, error) {
now := takeStageSnapshot(sc)
changed := 0
for field, cur := range now.strs {
if base.strs[field] != cur {
if err := shmWrite(field, []byte(cur)); err != nil {
return changed, err
}
changed++
}
}
for field, cur := range now.jsons {
if base.jsons[field] == cur {
continue
}
if err := shmWrite(field, []byte(cur)); err != nil {
return changed, err
}
changed++
}
if base.responseSet != now.responseSet || base.response != now.response {
if now.responseSet {
if err := shmWrite(fResponse, []byte(now.response)); err != nil {
return changed, err
}
shmSetFlag(flagResponseSet, true)
changed++
}
// Response 置回 nil 不清空内核已设的值:短路语义不应被撑销
}
if base.noMemory != now.noMemory {
shmSetFlag(flagNoMemory, now.noMemory)
changed++
}
if changed > 0 {
shmBumpSeq()
}
return changed, nil
}
// ---- SDK 装配:全部 API 经 RPC 打回内核51 个 method 的插件侧一半)----
func buildPluginSDK(name string) *sdk.PluginSDK {
base := sdk.New(name, procSettings{},
func(toolName string, def sdk.ToolDef, handler sdk.ToolHandler) error {
handlerMu.Lock()
toolHandlers[toolName] = handler
handlerMu.Unlock()
return callCoreVoid("tool.register", map[string]interface{}{
"name": toolName, "def": def,
})
},
func(stage sdk.Stage, handler sdk.StageHandler) {
handlerMu.Lock()
stageHandlers[string(stage)] = handler
handlerMu.Unlock()
if err := callCoreVoid("stage.register", map[string]interface{}{
"stage": string(stage), "scope": "global",
}); err != nil {
log.Printf("注册阶段 %s 失败: %v", stage, err)
}
},
func(apiName string) error {
return callCoreVoid("api.register", map[string]interface{}{"name": apiName})
},
func(chName string, caps int, desc string, def sdk.ChannelDef, handler sdk.ToolHandler) error {
handlerMu.Lock()
outputHandlers[chName] = handler
handlerMu.Unlock()
return callCoreVoid("output.register", map[string]interface{}{
"name": chName, "caps": caps, "desc": desc,
"def": map[string]interface{}{"NoMemory": def.NoMemory},
})
},
)
base.SetIOInjector(procIO{})
base.SetMemoryAPI(procMemory{})
base.SetDocMemoryAPI(procDocMemory{})
base.SetKnowledgeAPI(procKnowledge{})
base.SetLLMAPI(procLLM{})
base.SetSocialAPI(procSocial{})
base.SetTextMemoryAPI(procTextMemory{})
base.SetPluginMgrAPI(procPluginMgr{})
base.SetInputChannelRegistrar(func(chName string, def sdk.ChannelDef) error {
return callCoreVoid("input.register", map[string]interface{}{
"name": chName,
"def": map[string]interface{}{"NoMemory": def.NoMemory},
})
})
return base
}
type procIO struct{}
func (procIO) InjectText(s, c, t string) {
callCoreVoid("io.injectText", map[string]string{"source": s, "channel": c, "text": t})
}
func (procIO) InjectInterruptText(s, c, t string) {
callCoreVoid("io.injectInterrupt", map[string]string{"source": s, "channel": c, "text": t})
}
func (procIO) InjectTextNoMemory(s, c, t string) {
callCoreVoid("io.injectTextNoMem", map[string]string{"source": s, "channel": c, "text": t})
}
func (procIO) InjectInputSync(s, c, t string) string {
raw, err := callCore("io.injectInputSync", map[string]string{"source": s, "channel": c, "text": t})
if err != nil {
return ""
}
var r struct {
Reply string `json:"reply"`
}
json.Unmarshal(raw, &r)
return r.Reply
}
func (procIO) SetToolBlocks(blocks []sdk.ContentBlock) {
if err := callCoreVoid("io.setToolBlocks", map[string]interface{}{"blocks": blocks}); err != nil {
log.Printf("SetToolBlocks: %v", err)
}
}
type procMemory struct{}
func (procMemory) Recall(q []string, d int) ([]sdk.Entity, []sdk.Relation, error) {
raw, err := callCore("memory.recall", map[string]interface{}{"query": q, "depth": d})
if err != nil {
return nil, nil, err
}
var r struct {
Entities []sdk.Entity `json:"entities"`
Relations []sdk.Relation `json:"relations"`
}
if err := json.Unmarshal(raw, &r); err != nil {
return nil, nil, err
}
return r.Entities, r.Relations, nil
}
func (procMemory) Commit(t []sdk.Triple) error {
return callCoreVoid("memory.commit", map[string]interface{}{"triples": t})
}
func (procMemory) Introspect() (map[string]interface{}, error) {
raw, err := callCore("memory.introspect", nil)
if err != nil {
return nil, err
}
var m map[string]interface{}
json.Unmarshal(raw, &m)
return m, nil
}
func (procMemory) MergeEntities(s, t string) (int, error) {
raw, err := callCore("memory.merge", map[string]string{"source": s, "target": t})
if err != nil {
return 0, err
}
var r struct {
Merged int `json:"merged"`
}
json.Unmarshal(raw, &r)
return r.Merged, nil
}
func (procMemory) Purge(c map[string]string, mode string) (int, error) {
raw, err := callCore("memory.purge", map[string]interface{}{"criteria": c, "mode": mode})
if err != nil {
return 0, err
}
var r struct {
Purged int `json:"purged"`
}
json.Unmarshal(raw, &r)
return r.Purged, nil
}
type procDocMemory struct{}
func (procDocMemory) Query(text string, topK int) []*sdk.Doc {
raw, err := callCore("doc.query", map[string]interface{}{"text": text, "top_k": topK})
if err != nil {
return nil
}
var r struct {
Docs []*sdk.Doc `json:"docs"`
}
json.Unmarshal(raw, &r)
return r.Docs
}
func (procDocMemory) Insert(d *sdk.Doc) error {
return callCoreVoid("doc.insert", map[string]interface{}{"doc": d})
}
func (procDocMemory) Remove(id string) {
callCoreVoid("doc.remove", map[string]string{"id": id})
}
func (procDocMemory) Stats() map[string]interface{} {
raw, err := callCore("doc.stats", nil)
if err != nil {
return nil
}
var m map[string]interface{}
json.Unmarshal(raw, &m)
return m
}
type procKnowledge struct{}
func (procKnowledge) Search(q string, topK int) ([]*sdk.Knowledge, error) {
raw, err := callCore("knowledge.search", map[string]interface{}{"query": q, "top_k": topK})
if err != nil {
return nil, err
}
var r struct {
Results []*sdk.Knowledge `json:"results"`
}
json.Unmarshal(raw, &r)
return r.Results, nil
}
func (procKnowledge) Add(name, content string) error {
return callCoreVoid("knowledge.add", map[string]string{"name": name, "content": content})
}
func (procKnowledge) List() ([]string, error) {
raw, err := callCore("knowledge.list", nil)
if err != nil {
return nil, err
}
var r struct {
Names []string `json:"names"`
}
json.Unmarshal(raw, &r)
return r.Names, nil
}
type procTextMemory struct{}
func (procTextMemory) Append(evt sdk.TextEvent) error {
return callCoreVoid("textmemory.append", map[string]interface{}{"event": evt})
}
type procLLM struct{}
func (procLLM) ListSources() []string {
raw, err := callCore("llm.listSources", nil)
if err != nil {
return nil
}
var r struct {
Sources []string `json:"sources"`
}
json.Unmarshal(raw, &r)
return r.Sources
}
func (procLLM) SetSource(name string) error {
return callCoreVoid("llm.setSource", map[string]string{"name": name})
}
func (procLLM) CurrentSource() string {
raw, err := callCore("llm.currentSource", nil)
if err != nil {
return ""
}
var r struct {
Source string `json:"source"`
}
json.Unmarshal(raw, &r)
return r.Source
}
type procSocial struct{}
func (procSocial) GetPerson(name string) (*sdk.PersonProfile, error) {
raw, err := callCore("social.getPerson", map[string]string{"name": name})
if err != nil {
return nil, err
}
var r struct {
Person *sdk.PersonProfile `json:"person"`
}
json.Unmarshal(raw, &r)
return r.Person, nil
}
func (procSocial) GetTrait(name, trait string) (string, bool) {
raw, err := callCore("social.getTrait", map[string]string{"name": name, "trait": trait})
if err != nil {
return "", false
}
var r struct {
Value string `json:"value"`
Found bool `json:"found"`
}
json.Unmarshal(raw, &r)
return r.Value, r.Found
}
func (procSocial) GetRelations(name string) ([]sdk.SocialRelation, error) {
raw, err := callCore("social.getRelations", map[string]string{"name": name})
if err != nil {
return nil, err
}
var r struct {
Relations []sdk.SocialRelation `json:"relations"`
}
json.Unmarshal(raw, &r)
return r.Relations, nil
}
func (procSocial) GetNetwork(name string, depth int) ([]*sdk.PersonProfile, error) {
raw, err := callCore("social.getNetwork", map[string]interface{}{"name": name, "depth": depth})
if err != nil {
return nil, err
}
var r struct {
Network []*sdk.PersonProfile `json:"network"`
}
json.Unmarshal(raw, &r)
return r.Network, nil
}
func (procSocial) ListPersons() ([]string, error) {
raw, err := callCore("social.listPersons", nil)
if err != nil {
return nil, err
}
var r struct {
Persons []string `json:"persons"`
}
json.Unmarshal(raw, &r)
return r.Persons, nil
}
type procPluginMgr struct{}
func (procPluginMgr) ReloadOne(name string) error {
return callCoreVoid("plugin.reloadOne", map[string]string{"name": name})
}
func (procPluginMgr) ListLoadedPlugins() []string {
raw, err := callCore("plugin.listLoaded", nil)
if err != nil {
return nil
}
var r struct {
Plugins []string `json:"plugins"`
}
json.Unmarshal(raw, &r)
return r.Plugins
}
func (procPluginMgr) IsPluginDisabled(name string) bool {
raw, err := callCore("plugin.isDisabled", map[string]string{"name": name})
if err != nil {
return false
}
var r struct {
Disabled bool `json:"disabled"`
}
json.Unmarshal(raw, &r)
return r.Disabled
}
type procSettings struct{}
func (procSettings) Get(key string) (interface{}, error) {
return settingsValue("settings.get", map[string]string{"key": key})
}
func (procSettings) Set(key string, v interface{}) error {
return callCoreVoid("settings.set", map[string]interface{}{"key": key, "value": v})
}
func (procSettings) List(prefix string) ([]string, error) {
return settingsKeys("settings.list", map[string]string{"prefix": prefix})
}
func (procSettings) GetCore(key string) (interface{}, error) {
return settingsValue("settings.getCore", map[string]string{"key": key})
}
func (procSettings) SetCore(key string, v interface{}) error {
return callCoreVoid("settings.setCore", map[string]interface{}{"key": key, "value": v})
}
func (procSettings) ListCore(prefix string) ([]string, error) {
return settingsKeys("settings.listCore", map[string]string{"prefix": prefix})
}
func (procSettings) DataDir() string {
raw, err := callCore("settings.dataDir", nil)
if err != nil {
return ""
}
var r struct {
Dir string `json:"dir"`
}
json.Unmarshal(raw, &r)
return r.Dir
}
func (procSettings) GetPlugin(plugin, key string) (interface{}, error) {
return settingsValue("settings.getPlugin", map[string]string{"plugin": plugin, "key": key})
}
func (procSettings) SetPlugin(plugin, key string, v interface{}) error {
return callCoreVoid("settings.setPlugin", map[string]interface{}{
"plugin": plugin, "key": key, "value": v,
})
}
func (procSettings) ListPlugin(plugin, prefix string) ([]string, error) {
return settingsKeys("settings.listPlugin", map[string]string{"plugin": plugin, "prefix": prefix})
}
func (procSettings) RegisterDef(def sdk.ConfigDef) {
callCoreVoid("settings.registerDef", map[string]interface{}{"def": def})
}
func (procSettings) Defs(prefix string) []*sdk.ConfigDef {
raw, err := callCore("settings.defs", map[string]string{"prefix": prefix})
if err != nil {
return nil
}
var r struct {
Defs []*sdk.ConfigDef `json:"defs"`
}
json.Unmarshal(raw, &r)
return r.Defs
}
func (procSettings) Dump() map[string]interface{} {
raw, err := callCore("settings.dump", nil)
if err != nil {
return nil
}
var m map[string]interface{}
json.Unmarshal(raw, &m)
return m
}
func (procSettings) Plugins() []string {
raw, err := callCore("settings.plugins", nil)
if err != nil {
return nil
}
var r struct {
Plugins []string `json:"plugins"`
}
json.Unmarshal(raw, &r)
return r.Plugins
}
func settingsValue(method string, params interface{}) (interface{}, error) {
raw, err := callCore(method, params)
if err != nil {
return nil, err
}
var r struct {
Value interface{} `json:"value"`
}
if err := json.Unmarshal(raw, &r); err != nil {
return nil, err
}
return r.Value, nil
}
func settingsKeys(method string, params interface{}) ([]string, error) {
raw, err := callCore(method, params)
if err != nil {
return nil, err
}
var r struct {
Keys []string `json:"keys"`
}
if err := json.Unmarshal(raw, &r); err != nil {
return nil, err
}
return r.Keys, nil
}
// ---- 内核 → 插件的调用处理 ----
func handleKernelRequest(req *rpcRequest) {
defer func() {
if r := recover(); r != nil {
// handler panic 只影响本次调用,不带崩进程;
// 真崩溃时进程退出,内核经 EOF 感知并按 recordCrash 处理。
if req.ID != 0 {
respondErr(req.ID, fmt.Errorf("插件 handler panic: %v", r))
}
log.Printf("handler panic (%s): %v", req.Method, r)
}
}()
switch req.Method {
case "handshake":
handleHandshake(req)
case "plugin.init":
var p struct {
Name string `json:"name"`
Config map[string]interface{} `json:"config"`
}
json.Unmarshal(req.Params, &p)
if p.Name != "" {
pluginName = p.Name
}
instance, err := NewPluginFactory(pluginName, p.Config)
if err != nil {
respondErr(req.ID, err)
return
}
plg = instance
respond(req.ID, nil)
case "plugin.start":
if plg == nil {
respondErr(req.ID, fmt.Errorf("plugin.start 前未 init"))
return
}
pluginSDK = buildPluginSDK(pluginName)
if err := plg.Start(pluginSDK); err != nil {
respondErr(req.ID, err)
return
}
// 上报 AutoRestart公开 SDK 的 SetAutoRestart 是纯 setter无回调 hook
// 插件在 Start() 里调它只改进程内副本。C ABI 路径下内核在 Start 返回后
// 直接读 plgSDK.AutoRestart();子进程隔着进程边界读不到,故在此显式上报。
// **不改公开 SDK 接口**(接口冻结约束)。
if err := callCoreVoid("lifecycle.autoRestart", map[string]interface{}{
"enabled": pluginSDK.AutoRestart(),
}); err != nil {
log.Printf("上报 autoRestart 失败: %v", err)
}
respond(req.ID, nil)
case "plugin.stop":
if pluginSDK != nil {
pluginSDK.RunStopHandlers()
}
if plg != nil {
if err := plg.Stop(); err != nil {
log.Printf("Stop: %v", err)
}
}
respond(req.ID, nil)
stdoutW.Flush()
os.Exit(0)
case "tool.invoke":
var p struct {
Name string `json:"name"`
Args map[string]interface{} `json:"args"`
}
json.Unmarshal(req.Params, &p)
handlerMu.RLock()
h, ok := toolHandlers[p.Name]
handlerMu.RUnlock()
if !ok {
respondErr(req.ID, fmt.Errorf("未注册的工具: %s", p.Name))
return
}
res, err := h(p.Args)
if err != nil {
respondErr(req.ID, err)
return
}
respond(req.ID, map[string]interface{}{"result": res})
case "stage.invoke":
handleStageInvoke(req)
case "output.invoke":
var p struct {
Channel string `json:"channel"`
Args map[string]interface{} `json:"args"`
}
json.Unmarshal(req.Params, &p)
handlerMu.RLock()
h, ok := outputHandlers[p.Channel]
handlerMu.RUnlock()
if !ok {
respondErr(req.ID, fmt.Errorf("未注册的输出通道: %s", p.Channel))
return
}
// 同步返回真实结果——内核据此告知模型成功/失败不再假成功§9.4
res, err := h(p.Args)
if err != nil {
respondErr(req.ID, err)
return
}
if m, ok := res.(map[string]interface{}); ok {
respond(req.ID, m)
return
}
respond(req.ID, map[string]interface{}{"status": "sent"})
default:
if req.ID != 0 {
respondErr(req.ID, fmt.Errorf("未实现的 method: %s", req.Method))
}
}
}
func handleHandshake(req *rpcRequest) {
var p struct {
Protocol int `json:"protocol"`
ShmVersion uint32 `json:"shm_version"`
ShmSize int `json:"shm_size"`
PluginName string `json:"plugin_name"`
}
json.Unmarshal(req.Params, &p)
if p.Protocol != procProtocolVersion {
respondErr(req.ID, fmt.Errorf("协议版本不匹配(内核 %d插件 %d——请用配套 plugindev 重编",
p.Protocol, procProtocolVersion))
return
}
if p.ShmVersion != shmVersion {
respondErr(req.ID, fmt.Errorf("共享段版本不匹配(内核 %d插件 %d", p.ShmVersion, shmVersion))
return
}
if p.PluginName != "" {
pluginName = p.PluginName
}
// fd 3 = 内核经 ExtraFiles 传入的共享段 memfd
if p.ShmSize > 0 {
m, err := syscall.Mmap(3, 0, p.ShmSize,
syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_SHARED)
if err != nil {
respondErr(req.ID, fmt.Errorf("挂载共享段失败: %w", err))
return
}
if got := binary.LittleEndian.Uint32(m[shmOffMagic:]); got != shmMagic {
respondErr(req.ID, fmt.Errorf("共享段魔数不匹配0x%x", got))
return
}
shm = m
}
respond(req.ID, map[string]interface{}{
"protocol": procProtocolVersion,
"sdk_version": sdk.SDKVersion,
"plugin_name": pluginName,
"pid": os.Getpid(),
})
}
// handleStageInvoke 执行阶段处理器:拿锁 → 读共享段 → handler → 只写脏字段 → 放锁。
//
// 插件作者的 handler 与 .so 时代完全一致(仍是 func(ctx *sdk.StageContext) error
// 共享内存与锁的复杂度全部由本模板承担§3.4)。
func handleStageInvoke(req *rpcRequest) {
var p struct {
Stage string `json:"stage"`
Seq uint64 `json:"seq"`
}
json.Unmarshal(req.Params, &p)
handlerMu.RLock()
h, ok := stageHandlers[p.Stage]
handlerMu.RUnlock()
if !ok {
respond(req.ID, map[string]interface{}{"dirty_fields": 0})
return
}
if shm == nil {
respondErr(req.ID, fmt.Errorf("共享段未挂载"))
return
}
// 跨进程写锁内核仲裁§3.7),持锁进程崩溃由内核代为释放
if err := callCoreVoid("stage.lock", nil); err != nil {
respondErr(req.ID, fmt.Errorf("申请 stage 锁: %w", err))
return
}
unlocked := false
unlock := func() {
if !unlocked {
unlocked = true
if err := callCoreVoid("stage.unlock", nil); err != nil {
log.Printf("释放 stage 锁: %v", err)
}
}
}
defer unlock()
sc, err := readStageContext()
if err != nil {
respondErr(req.ID, fmt.Errorf("读共享段: %w", err))
return
}
snap := takeStageSnapshot(sc)
if err := h(sc); err != nil {
respondErr(req.ID, err)
return
}
dirty, err := writeStageDirty(sc, snap)
if err != nil {
respondErr(req.ID, fmt.Errorf("写回共享段: %w", err))
return
}
unlock()
respond(req.ID, map[string]interface{}{"dirty_fields": dirty, "seq": p.Seq})
}
// ---- 主循环 ----
func main() {
// 日志走 stderrstdout 是 RPC 通道,写日志会破坏帧
log.SetOutput(os.Stderr)
log.SetPrefix("[plugin] ")
in := bufio.NewScanner(bufio.NewReader(os.Stdin))
// 单帧上限 1MB控制面帧本应很小大 payload 走共享段
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 err := json.Unmarshal(line, &probe); err != nil {
log.Printf("非法 JSON 帧: %v", err)
continue
}
// method 为空 = 内核对我们反向调用的应答
if probe.Method == "" {
var resp rpcResponse
if err := json.Unmarshal(line, &resp); err != nil {
continue
}
pendMu.Lock()
ch, ok := pending[resp.ID]
delete(pending, resp.ID)
pendMu.Unlock()
if ok {
ch <- resp
}
continue
}
var req rpcRequest
if err := json.Unmarshal(line, &req); err != nil {
continue
}
// 每个请求独立 goroutinehandler 内可能反向调用内核,
// 在读循环里同步处理会死锁(等应答但没人读)。
go handleKernelRequest(&req)
}
if err := in.Err(); err != nil {
log.Printf("读 stdin 出错: %v", err)
}
// stdin 关闭 = 内核结束了我们
if pluginSDK != nil {
pluginSDK.RunStopHandlers()
}
if plg != nil {
plg.Stop()
}
}