mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-09-23 10:28:06 +00:00
feat(shm): 输出通道 payload 走共享内存调用帧(§13.6)
§13.3 给 ToolInvokeParams 加了调用帧,但 OutputInvokeParams 没跟上—— payload 仍内联在 RPC JSON 里。核实后确认这个 lane 只做了半边:注入侧 (injectParams.TextRef + resolveText)可用,输出侧从内核到插件仍是内联。 改法照搬工具调用的 funccall 帧模型: - OutputInvokeParams 加 Frame/ArgsLen(Args 仅留给直连 RPC 的测试) - invokeOutput 序列化参数 → Alloc 帧 → 写帧 → 只传偏移描述符 → defer Free - 帧尾不预留结果区:output 应答很小("ok"/status map),走 RPC 应答字段即可。 但 OutputInvokeResult 支持插件把大结果写回帧(ResultRef),并识别 sharedRefFlagExpand 单独归还扩容块——与 invokeTool 一致。 - 模板 output.invoke 用 frameInput 从帧读参数,无帧才回退内联 Args 为什么要做:output payload 在真机上可能含图片/文件描述等大字段,内联会 撑爆 stdin/stdout 管道。 验证: - TestPlugin_OutputPayloadViaArena:9000 字节 payload 经帧完整送达(插件回报 实收长度,不是只看返回值)+ 调用后 arena 归零 - TestE2E_RealTemplateOutputPayloadViaFrame:同上,但用真实 SDK 模板编译的 插件——生产插件走的就是模板,模板不读帧则此改动等于没做 - TestPlugin_OutputChannelReportsRealFailure 仍绿:同步等真实结果、失败必须 上报(§9.4)的语义未被破坏 - go test -race ./internal/plugin/... 全绿 plan.md §13.5 也如实标注:注入 side 内核→插件方向仍未接线(procCore.InjectText 直接传字符串,从不 Alloc/Put 构造 TextRef),不是仅打勾的项。
This commit is contained in:
46
internal/plugin/proc/testdata/stageplugin.go
vendored
46
internal/plugin/proc/testdata/stageplugin.go
vendored
@ -526,18 +526,40 @@ func main() {
|
||||
}(req.ID)
|
||||
|
||||
case "output.invoke":
|
||||
var p struct {
|
||||
Channel string `json:"channel"`
|
||||
Args map[string]interface{} `json:"args"`
|
||||
}
|
||||
json.Unmarshal(req.Params, &p)
|
||||
payload, _ := p.Args["payload"].(string)
|
||||
if payload == "fail" {
|
||||
// 模拟现网 qq 插件的真实失败:meta 缺 user_id
|
||||
send(response{ID: req.ID, Error: "meta 中缺少 user_id 字段"})
|
||||
continue
|
||||
}
|
||||
send(response{ID: req.ID, Result: map[string]interface{}{"status": "sent"}})
|
||||
// §13.6:payload 走共享内存调用帧,与 tool.invoke 同一模型。
|
||||
// 必须在独立 goroutine 里处理:插件可能反向调用内核。
|
||||
go func(id uint64, raw json.RawMessage) {
|
||||
var p struct {
|
||||
Channel string `json:"channel"`
|
||||
Args map[string]interface{} `json:"args"`
|
||||
Frame SharedRef `json:"frame"`
|
||||
ArgsLen uint32 `json:"args_len"`
|
||||
}
|
||||
json.Unmarshal(raw, &p)
|
||||
|
||||
args := p.Args
|
||||
if !p.Frame.IsZero() {
|
||||
if blob := frameInput(p.Frame, p.ArgsLen); len(blob) > 0 {
|
||||
var decoded map[string]interface{}
|
||||
if err := json.Unmarshal(blob, &decoded); err != nil {
|
||||
send(response{ID: id, Error: "解析输出参数: " + err.Error()})
|
||||
return
|
||||
}
|
||||
args = decoded
|
||||
}
|
||||
}
|
||||
payload, _ := args["payload"].(string)
|
||||
if payload == "fail" {
|
||||
// 模拟现网 qq 插件的真实失败:meta 缺 user_id
|
||||
send(response{ID: id, Error: "meta 中缺少 user_id 字段"})
|
||||
return
|
||||
}
|
||||
// 标量结果直接在应答字段返回(output 应答很小,无需走共享内存)。
|
||||
// payload_len 让测试能验证共享帧把完整 payload 带到了插件侧。
|
||||
send(response{ID: id, Result: map[string]interface{}{
|
||||
"status": "sent", "payload_len": len(payload),
|
||||
}})
|
||||
}(req.ID, req.Params)
|
||||
|
||||
default:
|
||||
if req.ID != 0 {
|
||||
|
||||
Reference in New Issue
Block a user