From 710b1afdf908b981b77a89d96290cfcfa3bace85 Mon Sep 17 00:00:00 2001 From: JianFeeeee Date: Sat, 15 Aug 2026 15:38:57 +0800 Subject: [PATCH] =?UTF-8?q?C=20ABI=20v2:=20invoke=5Fstage=20=E5=86=99?= =?UTF-8?q?=E5=9B=9E=20+=20ABI=20=E7=89=88=E6=9C=AC=E5=AF=B9=E9=BD=90?= =?UTF-8?q?=E6=A0=B8=E5=BF=83=E7=89=88=E6=9C=AC=E5=8F=B7=20(v0.9.0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - C ABI invoke_stage 增加 result 输出参数,外部插件 stage 回调可将修改后的 StageContext(RawMessage/LLMText/FinalText/Response/ToolResults) 写回内核 - ABI 标识版本改为字符串 semver 与核心 Version 对齐(ABIVersion="0.9.0"), C 层协商用派生整数 CABINum=900(major*100+minor),不再使用独立数字编码 - version_min 保证 v0.8.x(800) 旧插件向后兼容可加载 - 修复工具循环 zen 兼容补位误伤首轮 system 上下文(仅尾部为 assistant/tool 时补位) - 更新 README 项目状态说明 --- README.md | 5 +- README_EN.md | 3 + internal/agent/core/process.go | 36 +-- internal/meta/meta.go | 115 ++++---- internal/plugin/cabi/loader.c | 8 +- internal/plugin/cabi/loader.go | 365 ++++++++++++++++++++----- internal/plugin/cabi/types.go | 9 +- third_party/homeagent-sdk/meta/meta.go | 22 +- 8 files changed, 412 insertions(+), 151 deletions(-) diff --git a/README.md b/README.md index cc40214..6af7bcb 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ homed(内核零 IO) ← PluginSDK → 插件(所有 IO 能力) **核心域与应用域分离** — 内核职责限定为 LLM 编排、记忆管理与知识检索;所有 IO 能力(消息收发、文件读写、网络请求、硬件交互等)由插件实现。这种划分在 Agent 框架层面进行领域边界界定,内核与插件各有其责任范围。 **三层记忆架构** — 通过分级存储策略管理 Agent 长期运行中的信息留存: + - **Context 层**:预训练词嵌入 / TF-IDF 回退的相关性评分事件窗口,保护最近 10 条,维护 topK 条上下文 - **Document 层**:临时记忆,冷数据自动下沉,也支持用户主动提交 - **Graph 层**:SQLite 图数据库,持久化实体关系和语义记忆,支持蒸馏管道从原始对话中提取三元组 @@ -171,6 +172,8 @@ internal/ ## 项目状态 +**v0.9.0** — C ABI v2:外部插件 Stage 回调支持写回(`invoke_stage` 增加 result 输出,插件可在 OnInput/AfterToolcall/PostAction 修改 RawMessage/LLMText/ToolResults 等并同步回内核),ABI 版本随内核 minor 对齐(v0.9.x → ABIVersion=2,`version_min=1` 向后兼容旧插件)。同步修复工具循环 zen 兼容补位误伤首轮 system 上下文的问题。配套 SDK 提供增强版 sanitizer 示例(坏 UTF-8/U+FFFD/ANSI 转义全链路清洗)。 + **v0.8.0** — 核心可用,插件系统增强。内置 20+ 插件,外部插件开发见 [homeagent-sdk](https://gitcode.com/JianFeeeee/homeagent-sdk) 仓库。新增输入通道 `NoMemory`/`Cleaner`、`ChannelDef`、插件禁用/启用系统(CLI + WebUI),`plugindev` 工具链完成 C ABI `ChannelDef` 传递。 ## 文档 @@ -189,4 +192,4 @@ make test # go test ./... make install # 安装到系统 ``` -依赖:Go 1.25+, CGo (go-sqlite3), Linux/Windows。 \ No newline at end of file +依赖:Go 1.25+, CGo (go-sqlite3), Linux/Windows。 diff --git a/README_EN.md b/README_EN.md index fdd3697..67dd8c0 100644 --- a/README_EN.md +++ b/README_EN.md @@ -15,6 +15,7 @@ homed (kernel, zero IO) ← PluginSDK → plugins (all IO capabilities) **Separation of Core Domain and Application Domain** — The kernel's responsibilities are limited to LLM orchestration, memory management, and knowledge retrieval; all IO capabilities (message send/receive, file read/write, network requests, hardware interaction, etc.) are implemented by plugins. This separation defines domain boundaries at the Agent framework level, with distinct responsibility scopes for the kernel and plugins. **Three-Layer Memory Architecture** — Manages information retention in long-running agents through a tiered storage strategy: + - **Context Layer**: Pretrained word embedding / TF-IDF fallback relevance-scored event window, protects last 10 entries, maintains topK context entries - **Document Layer**: Temporary memory with automatic cold data sinking, also supports user-initiated submissions - **Graph Layer**: SQLite graph database, persists entity relationships and semantic memory, supports distillation pipelines to extract triples from conversations @@ -171,6 +172,8 @@ External plugin development: see [homeagent-sdk](https://gitcode.com/JianFeeeee/ ## Project Status +**v0.9.0** — C ABI v2: external plugin Stage callbacks can now write back (`invoke_stage` gained a result out-param; plugins may mutate RawMessage/LLMText/ToolResults etc. in OnInput/AfterToolcall/PostAction and have them synced to the core). ABI version now tracks core minor releases (v0.9.x → ABIVersion=2, `version_min=1` keeps old plugins loadable). Also fixes the tool-loop zen-compat placeholder that wrongly fired on first-turn system context tail. The SDK ships an enhanced sanitizer example (bad-UTF-8 / U+FFFD / ANSI-escape scrub across the whole pipeline). + **v0.8.0** — Core is functional, plugin system enhanced. 20+ built-in plugins. External plugin development via [homeagent-sdk](https://gitcode.com/JianFeeeee/homeagent-sdk) repo. Added input channel `NoMemory`/`Cleaner`, `ChannelDef`, plugin disable/enable system (CLI + WebUI), `plugindev` toolchain C ABI `ChannelDef` support. ## Documentation diff --git a/internal/agent/core/process.go b/internal/agent/core/process.go index e99a5aa..749f89d 100644 --- a/internal/agent/core/process.go +++ b/internal/agent/core/process.go @@ -70,7 +70,9 @@ func (a *Agent) process(input string, stageCtx *sdk.StageContext) (response stri // zen 兼容网关要求请求的最后一条消息必须是 user(thinking 续写模式校验), // 工具轮产出的 tool/assistant 消息作结尾会被 400 拒绝,故补一条 user 占位。 - if last := msgs[len(msgs)-1]; last.Role != "user" { + // 注意:仅当尾部确为工具轮产物(assistant/tool)时才补位;首轮 system 上下文结尾不补, + // 否则会错误覆盖实际用户输入(如 injectSourceContext 追加的 system 说明)。 + if last := msgs[len(msgs)-1]; last.Role == "assistant" || last.Role == "tool" { msgs = append(msgs, agentAPI.Message{ Role: "user", Content: "请根据以上工具结果继续。", @@ -211,14 +213,14 @@ func (a *Agent) process(input string, stageCtx *sdk.StageContext) (response stri for _, interrupt := range a.drainInterrupts() { msgs = append(msgs, agentAPI.Message{Role: "system", Content: "[中断消息] " + interrupt}) } - a.publishEvent(events.EventToolCall, map[string]interface{}{ - "tool": tc.Name, - "plugin": a.resolveToolPlugin(tc.Name), - "args": tc.Arguments, - "status": "interrupted", - "reason": "user interrupt before execution", - "channel": a.currentOutputChannel, - }) + a.publishEvent(events.EventToolCall, map[string]interface{}{ + "tool": tc.Name, + "plugin": a.resolveToolPlugin(tc.Name), + "args": tc.Arguments, + "status": "interrupted", + "reason": "user interrupt before execution", + "channel": a.currentOutputChannel, + }) break } @@ -233,14 +235,14 @@ func (a *Agent) process(input string, stageCtx *sdk.StageContext) (response stri result := fmt.Sprintf("工具 %s 已被插件拒绝", tc.Name) msgs = append(msgs, agentAPI.Message{Role: "assistant", ToolCalls: []agentAPI.ToolCall{tc}}) msgs = append(msgs, agentAPI.Message{Role: "tool", ToolCallID: tc.ID, Content: result}) - a.publishEvent(events.EventToolCall, map[string]interface{}{ - "tool": tc.Name, - "plugin": pluginName, - "args": tc.Arguments, - "result": result, - "status": "denied", - "channel": a.currentOutputChannel, - }) + a.publishEvent(events.EventToolCall, map[string]interface{}{ + "tool": tc.Name, + "plugin": pluginName, + "args": tc.Arguments, + "result": result, + "status": "denied", + "channel": a.currentOutputChannel, + }) continue } tc.Arguments = stageCtx.ToolCalls[0].Arguments diff --git a/internal/meta/meta.go b/internal/meta/meta.go index ecc8db4..1cd4836 100644 --- a/internal/meta/meta.go +++ b/internal/meta/meta.go @@ -7,7 +7,7 @@ package meta var ( // Version 是 HomeAgent 内核版本号。 // 通过 `-ldflags="-X gitcode.com/JianFeeeee/HomeAgent/internal/meta.Version=vX.Y.Z"` 注入。 - Version = "0.8.0" + Version = "0.9.0" // Commit 是构建时的 Git commit hash。 Commit = "unknown" @@ -19,7 +19,7 @@ var ( KernelName = "HomeAgent" // SDKCompatibleVersion 是此内核可兼容的最高 SDK 版本(semver)。 - SDKCompatibleVersion = "0.8.0" + SDKCompatibleVersion = "0.9.0" ) // FullVersion 返回完整的版本字符串。 @@ -28,61 +28,74 @@ func FullVersion() string { } // ---- ABI 版本(C ABI 协议版本,插件与内核通信用) ---- +// ABI 版本直接取内核版本号字符串(semver),与核心 Version 保持一致,不再使用独立数字编码。 +// 协商层(C 结构体 int version 字段)使用 CABINum:由版本字符串派生的整数(major*100 + minor)。 +// 映射:v0.8.x → CABINum=800;v0.9.x → CABINum=900(invoke_stage 写回)。 +// 小版本(patch)演进不影响 ABI,CABINum 不变。version_min 保证旧 ABI 插件仍可加载。 + +var ( + // ABIVersion 是 ABI 标识版本(字符串 semver,与核心 Version 对齐)。 + ABIVersion = Version + // ABIVersionMin 是兼容的最低 ABI 标识版本。 + ABIVersionMin = "0.8.0" +) const ( - ABIVersion = 1 - ABIVersionMin = 1 + // CABINum 是 C 层协商用的整数版本(major*100 + minor),随 ABIVersion 派生。 + CABINum = 900 + // CABINumMin 是 C 层兼容的最低整数版本。 + CABINumMin = 800 ) // ---- Dispatch Method IDs ---- // 核心→插件:这些 ID 通过 CoreAPI.dispatch 传递,标识 SDK 调用。 // 插件端的 C enum 定义在 plugindev 的 C ABI header 模板中。 const ( - CoreRegisterTool = 1 - CoreRegisterStage = 2 - CoreRegisterOutputCh = 3 - CoreRegisterPluginAPI = 4 - CoreInjectText = 5 - CoreInjectInterruptText = 6 - CoreInjectTextNoMemory = 7 - CoreSetAutoRestart = 8 - CoreMemoryRecall = 9 - CoreMemoryCommit = 10 - CoreMemoryIntrospect = 11 - CoreMemoryMerge = 12 - CoreMemoryPurge = 13 - CoreDocQuery = 14 - CoreKnowledgeSearch = 15 - CoreSettingsGet = 16 - CoreSettingsSet = 17 - CoreSettingsRegisterDef = 18 - CoreLLMListSources = 19 - CoreLLMSetSource = 20 - CoreSocialGetPerson = 21 - CoreSocialGetNetwork = 22 - CoreSubscribe = 23 - CoreUnsubscribe = 24 - CoreFreeString = 25 - CoreSettingsGetCore = 26 - CoreSettingsSetCore = 27 - CoreSettingsListCore = 28 - CoreSettingsGetPlugin = 29 - CoreSettingsSetPlugin = 30 - CoreSettingsListPlugin = 31 - CoreDocInsert = 32 - CoreDocRemove = 33 - CoreDocStats = 34 - CoreKnowledgeAdd = 35 - CoreKnowledgeList = 36 - CoreLLMCurrentSource = 37 - CoreSocialGetTrait = 38 - CoreSocialGetRelations = 39 - CoreSocialListPersons = 40 - CoreTextMemoryAppend = 41 - CoreSettingsList = 42 - CoreSettingsDefs = 43 - CoreSettingsDump = 44 - CoreSettingsPlugins = 45 - CoreRegisterInputCh = 46 - CoreInjectInputSync = 47 + CoreRegisterTool = 1 + CoreRegisterStage = 2 + CoreRegisterOutputCh = 3 + CoreRegisterPluginAPI = 4 + CoreInjectText = 5 + CoreInjectInterruptText = 6 + CoreInjectTextNoMemory = 7 + CoreSetAutoRestart = 8 + CoreMemoryRecall = 9 + CoreMemoryCommit = 10 + CoreMemoryIntrospect = 11 + CoreMemoryMerge = 12 + CoreMemoryPurge = 13 + CoreDocQuery = 14 + CoreKnowledgeSearch = 15 + CoreSettingsGet = 16 + CoreSettingsSet = 17 + CoreSettingsRegisterDef = 18 + CoreLLMListSources = 19 + CoreLLMSetSource = 20 + CoreSocialGetPerson = 21 + CoreSocialGetNetwork = 22 + CoreSubscribe = 23 + CoreUnsubscribe = 24 + CoreFreeString = 25 + CoreSettingsGetCore = 26 + CoreSettingsSetCore = 27 + CoreSettingsListCore = 28 + CoreSettingsGetPlugin = 29 + CoreSettingsSetPlugin = 30 + CoreSettingsListPlugin = 31 + CoreDocInsert = 32 + CoreDocRemove = 33 + CoreDocStats = 34 + CoreKnowledgeAdd = 35 + CoreKnowledgeList = 36 + CoreLLMCurrentSource = 37 + CoreSocialGetTrait = 38 + CoreSocialGetRelations = 39 + CoreSocialListPersons = 40 + CoreTextMemoryAppend = 41 + CoreSettingsList = 42 + CoreSettingsDefs = 43 + CoreSettingsDump = 44 + CoreSettingsPlugins = 45 + CoreRegisterInputCh = 46 + CoreInjectInputSync = 47 ) diff --git a/internal/plugin/cabi/loader.c b/internal/plugin/cabi/loader.c index 5ac4a11..bc16f07 100644 --- a/internal/plugin/cabi/loader.c +++ b/internal/plugin/cabi/loader.c @@ -6,9 +6,9 @@ #include #include -// HOMEAGENT_ABI_VERSION 与 internal/meta/meta.go ABIVersion 同步。 +// HOMEAGENT_ABI_VERSION 与 internal/meta/meta.go CABINum 同步(major*100+minor,v0.9.x→900)。 // C ABI 通过 version/version_min 协商,旧插件不受影响。 -#define HOMEAGENT_ABI_VERSION 1 +#define HOMEAGENT_ABI_VERSION 900 // PluginAPI — provided by the plugin typedef struct { @@ -17,7 +17,7 @@ typedef struct { int (*start_plugin)(void*, int, char**); int (*stop_plugin)(char**); int (*invoke_tool)(char*, char*, char**, char**); - int (*invoke_stage)(char*, char*, char**); + int (*invoke_stage)(char*, char*, char**, char**); int (*invoke_output)(char*, char*, char*, char**); void (*free_string)(char*); } plugin_api_t; @@ -75,5 +75,5 @@ int call_init_plugin(plugin_api_t* api, char* name, char* config, char** err) { int call_start_plugin(plugin_api_t* api, void* core, int ver, char** err) { return api->start_plugin(core, ver, err); } int call_stop_plugin(plugin_api_t* api, char** err) { return api->stop_plugin(err); } int call_invoke_tool(plugin_api_t* api, char* n, char* a, char** r, char** e) { return api->invoke_tool(n, a, r, e); } -int call_invoke_stage(plugin_api_t* api, char* s, char* c, char** e) { return api->invoke_stage(s, c, e); } +int call_invoke_stage(plugin_api_t* api, char* s, char* c, char** r, char** e) { return api->invoke_stage(s, c, r, e); } int call_invoke_output(plugin_api_t* api, char* c, char* m, char* p, char** e) { return api->invoke_output(c, m, p, e); } diff --git a/internal/plugin/cabi/loader.go b/internal/plugin/cabi/loader.go index 2df88db..3703465 100644 --- a/internal/plugin/cabi/loader.go +++ b/internal/plugin/cabi/loader.go @@ -6,9 +6,10 @@ package cabi #cgo LDFLAGS: -ldl #include -// HOMEAGENT_ABI_VERSION 是当前内核的 ABI 版本号,与 internal/meta/meta.go ABIVersion 保持同步。 -// 旧插件使用低版本 ABI 不受影响——C ABI wrapper 通过 version/version_min 字段协商兼容。 -#define HOMEAGENT_ABI_VERSION 1 +// HOMEAGENT_ABI_VERSION 是当前内核的 C ABI 整数协商版本,由 internal/meta/meta.go CABINum 派生 +// (major*100 + minor,随核心版本号映射:v0.8.x→800,v0.9.x→900)。 +// 旧插件使用低整数版本不受影响——C ABI wrapper 通过 version/version_min 字段协商兼容。 +#define HOMEAGENT_ABI_VERSION 900 // PluginAPI — provided by the plugin via plugin_init() typedef struct { @@ -17,7 +18,7 @@ typedef struct { int (*start_plugin)(void*, int, char**); int (*stop_plugin)(char**); int (*invoke_tool)(char*, char*, char**, char**); - int (*invoke_stage)(char*, char*, char**); + int (*invoke_stage)(char*, char*, char**, char**); int (*invoke_output)(char*, char*, char*, char**); void (*free_string)(char*); } plugin_api_t; @@ -36,7 +37,7 @@ extern int call_init_plugin(plugin_api_t*, char*, char*, char**); extern int call_start_plugin(plugin_api_t*, void*, int, char**); extern int call_stop_plugin(plugin_api_t*, char**); extern int call_invoke_tool(plugin_api_t*, char*, char*, char**, char**); -extern int call_invoke_stage(plugin_api_t*, char*, char*, char**); +extern int call_invoke_stage(plugin_api_t*, char*, char*, char**, char**); extern int call_invoke_output(plugin_api_t*, char*, char*, char*, char**); extern void api_free_string(plugin_api_t*, char*); extern void* lib_open(const char*); @@ -62,10 +63,10 @@ var ( ) type pluginState struct { - id int32 - name string - sdk *sdk.PluginSDK - api *C.plugin_api_t + id int32 + name string + sdk *sdk.PluginSDK + api *C.plugin_api_t } // Handle represents a loaded C ABI plugin. @@ -96,9 +97,14 @@ func Load(soPath, name string, config map[string]interface{}) (*Handle, error) { C.lib_close(lib) return nil, fmt.Errorf("plugin %s: invalid PluginAPI (version=%d)", name, int(api.version)) } - if int(api.version) > ABIVersion { + if int(api.version) > CABINum { C.lib_close(lib) - return nil, fmt.Errorf("plugin %s: ABI version %d > core %d, requires newer HomeAgent core", name, int(api.version), ABIVersion) + return nil, fmt.Errorf("plugin %s: ABI version %d > core %d (v%s), requires newer HomeAgent core", name, int(api.version), CABINum, ABIVersion) + } + + if int(api.version) < CABINumMin { + C.lib_close(lib) + return nil, fmt.Errorf("plugin %s: ABI version %d < core min %d (v%s), plugin too old", name, int(api.version), CABINumMin, ABIVersionMin) } id := atomic.AddInt32(&nextID, 1) @@ -119,7 +125,10 @@ func Load(soPath, name string, config map[string]interface{}) (*Handle, error) { if ret := int(C.call_init_plugin(api, cName, cConfig, &initErr)); ret != 0 { errMsg := "" - if initErr != nil { errMsg = C.GoString(initErr); C.api_free_string(api, initErr) } + if initErr != nil { + errMsg = C.GoString(initErr) + C.api_free_string(api, initErr) + } handle.Close() return nil, fmt.Errorf("init_plugin %s: %s", name, errMsg) } @@ -131,7 +140,9 @@ func Load(soPath, name string, config map[string]interface{}) (*Handle, error) { // The CoreAPI dispatches all SDK calls back to Go, routing to the plugin's PluginSDK. func (h *Handle) CreateCoreAPI(s *sdk.PluginSDK) unsafe.Pointer { core := C.make_core_api() - if core == nil { return nil } + if core == nil { + return nil + } h.core = core h.pstate.sdk = s @@ -152,9 +163,12 @@ func (h *Handle) FreeCoreAPI() { // Start calls the plugin's Start with a CoreAPI pointer. func (h *Handle) Start(corePtr unsafe.Pointer) error { var cErr *C.char - if ret := int(C.call_start_plugin(h.api, corePtr, C.int(ABIVersion), &cErr)); ret != 0 { + if ret := int(C.call_start_plugin(h.api, corePtr, C.int(CABINum), &cErr)); ret != 0 { errMsg := "" - if cErr != nil { errMsg = C.GoString(cErr); C.api_free_string(h.api, cErr) } + if cErr != nil { + errMsg = C.GoString(cErr) + C.api_free_string(h.api, cErr) + } return fmt.Errorf("start_plugin: %s", errMsg) } return nil @@ -165,7 +179,10 @@ func (h *Handle) Stop() error { var cErr *C.char if ret := int(C.call_stop_plugin(h.api, &cErr)); ret != 0 { errMsg := "" - if cErr != nil { errMsg = C.GoString(cErr); C.api_free_string(h.api, cErr) } + if cErr != nil { + errMsg = C.GoString(cErr) + C.api_free_string(h.api, cErr) + } return fmt.Errorf("stop_plugin: %s", errMsg) } return nil @@ -182,13 +199,20 @@ func (h *Handle) InvokeTool(name string, args map[string]interface{}) (map[strin if ret := int(C.call_invoke_tool(h.api, cName, cArgs, &result, &cErr)); ret != 0 { errMsg := "" - if cErr != nil { errMsg = C.GoString(cErr); C.api_free_string(h.api, cErr) } + if cErr != nil { + errMsg = C.GoString(cErr) + C.api_free_string(h.api, cErr) + } return nil, fmt.Errorf("invoke_tool %s: %s", name, errMsg) } - if result == nil { return nil, nil } + if result == nil { + return nil, nil + } defer C.api_free_string(h.api, result) var r map[string]interface{} - if err := json.Unmarshal([]byte(C.GoString(result)), &r); err != nil { return nil, err } + if err := json.Unmarshal([]byte(C.GoString(result)), &r); err != nil { + return nil, err + } return r, nil } @@ -204,9 +228,13 @@ func (h *Handle) Close() { func pluginInvokeTool(pluginID int32, name, argsJSON string) (string, error) { v, ok := pluginMap.Load(pluginID) - if !ok { return "", fmt.Errorf("plugin %d not found", pluginID) } + if !ok { + return "", fmt.Errorf("plugin %d not found", pluginID) + } ps := v.(*pluginState) - if ps.api == nil { return "", fmt.Errorf("plugin %d: nil api", pluginID) } + if ps.api == nil { + return "", fmt.Errorf("plugin %d: nil api", pluginID) + } cName := C.CString(name) cArgs := C.CString(argsJSON) var result, cErr *C.char @@ -214,19 +242,28 @@ func pluginInvokeTool(pluginID int32, name, argsJSON string) (string, error) { defer C.free(unsafe.Pointer(cArgs)) if ret := int(C.call_invoke_tool(ps.api, cName, cArgs, &result, &cErr)); ret != 0 { errMsg := "" - if cErr != nil { errMsg = C.GoString(cErr); C.api_free_string(ps.api, cErr) } + if cErr != nil { + errMsg = C.GoString(cErr) + C.api_free_string(ps.api, cErr) + } return "", fmt.Errorf("invoke_tool %s: %s", name, errMsg) } - if result == nil { return "", nil } + if result == nil { + return "", nil + } defer C.api_free_string(ps.api, result) return C.GoString(result), nil } func pluginInvokeOutput(pluginID int32, channel, payload string) error { v, ok := pluginMap.Load(pluginID) - if !ok { return fmt.Errorf("plugin %d not found", pluginID) } + if !ok { + return fmt.Errorf("plugin %d not found", pluginID) + } ps := v.(*pluginState) - if ps.api == nil { return fmt.Errorf("plugin %d: nil api", pluginID) } + if ps.api == nil { + return fmt.Errorf("plugin %d: nil api", pluginID) + } cCh := C.CString(channel) cPayload := C.CString(payload) var cErr *C.char @@ -234,27 +271,92 @@ func pluginInvokeOutput(pluginID int32, channel, payload string) error { defer C.free(unsafe.Pointer(cPayload)) if ret := int(C.call_invoke_output(ps.api, cCh, nil, cPayload, &cErr)); ret != 0 { errMsg := "" - if cErr != nil { errMsg = C.GoString(cErr); C.api_free_string(ps.api, cErr) } + if cErr != nil { + errMsg = C.GoString(cErr) + C.api_free_string(ps.api, cErr) + } return fmt.Errorf("invoke_output %s: %s", channel, errMsg) } return nil } -func pluginInvokeStage(pluginID int32, stage, ctxJSON string) error { +// applyStageResult 将插件回传的修改后上下文应用回内核 StageContext。 +// 只回写插件有权改写的字段(RawMessage/LLMText/FinalText/Response/ToolResults/NoMemory)。 +func applyStageResult(sc *sdk.StageContext, resultJSON string) { + var m map[string]interface{} + if err := json.Unmarshal([]byte(resultJSON), &m); err != nil { + return + } + sc.Lock() + defer sc.Unlock() + if v, ok := m["raw_message"].(string); ok { + sc.RawMessage = v + } + if v, ok := m["llm_text"].(string); ok { + sc.LLMText = v + } + if v, ok := m["final_text"].(string); ok { + sc.FinalText = v + } + if v, ok := m["user_id"].(string); ok { + sc.UserID = v + } + if v, ok := m["group_id"].(string); ok { + sc.GroupID = v + } + if v, ok := m["no_memory"].(bool); ok { + sc.NoMemory = v + } + if v, ok := m["response"].(string); ok { + vv := v + sc.Response = &vv + } + if v, ok := m["tool_calls"].([]interface{}); ok && len(v) > 0 { + if b, err := json.Marshal(v); err == nil { + var tcs []sdk.ToolCall + if json.Unmarshal(b, &tcs) == nil { + sc.ToolCalls = tcs + } + } + } + if v, ok := m["tool_results"].([]interface{}); ok && len(v) > 0 { + if b, err := json.Marshal(v); err == nil { + var trs []sdk.ToolResult + if json.Unmarshal(b, &trs) == nil { + sc.ToolResults = trs + } + } + } +} + +func pluginInvokeStage(pluginID int32, stage, ctxJSON string, resultOut *string) error { v, ok := pluginMap.Load(pluginID) - if !ok { return fmt.Errorf("plugin %d not found", pluginID) } + if !ok { + return fmt.Errorf("plugin %d not found", pluginID) + } ps := v.(*pluginState) - if ps.api == nil { return fmt.Errorf("plugin %d: nil api", pluginID) } + if ps.api == nil { + return fmt.Errorf("plugin %d: nil api", pluginID) + } cStage := C.CString(stage) cCtx := C.CString(ctxJSON) var cErr *C.char + var cResult *C.char defer C.free(unsafe.Pointer(cStage)) defer C.free(unsafe.Pointer(cCtx)) - if ret := int(C.call_invoke_stage(ps.api, cStage, cCtx, &cErr)); ret != 0 { + // 仅当调用方要求回传时传 &cResult,否则传 NULL(兼容无需写回的阶段)。 + if ret := int(C.call_invoke_stage(ps.api, cStage, cCtx, &cResult, &cErr)); ret != 0 { errMsg := "" - if cErr != nil { errMsg = C.GoString(cErr); C.api_free_string(ps.api, cErr) } + if cErr != nil { + errMsg = C.GoString(cErr) + C.api_free_string(ps.api, cErr) + } return fmt.Errorf("invoke_stage %s: %s", stage, errMsg) } + if resultOut != nil && cResult != nil { + *resultOut = C.GoString(cResult) + C.api_free_string(ps.api, cResult) + } return nil } @@ -264,10 +366,14 @@ func pluginInvokeStage(pluginID int32, stage, ctxJSON string) error { func go_core_dispatch(methodID C.int, ctx unsafe.Pointer, s1, s2, s3 *C.char, i1, i2 C.int, result **C.char, errorOut **C.char) C.int { pluginID := int32(uintptr(ctx)) v, ok := pluginMap.Load(pluginID) - if !ok { return 1 } + if !ok { + return 1 + } ps := v.(*pluginState) s := ps.sdk - if s == nil { return 1 } + if s == nil { + return 1 + } a1, a2, a3 := goStr(s1), goStr(s2), goStr(s3) n1, n2 := int(i1), int(i2) @@ -275,15 +381,22 @@ func go_core_dispatch(methodID C.int, ctx unsafe.Pointer, s1, s2, s3 *C.char, i1 switch int(methodID) { case 1: // CORE_REGISTER_TOOL var def sdk.ToolDef - if err := json.Unmarshal([]byte(a2), &def); err != nil { setErr(errorOut, err); return 1 } + if err := json.Unmarshal([]byte(a2), &def); err != nil { + setErr(errorOut, err) + return 1 + } def.Plugin = ps.name pid := pluginID toolName := a1 _ = s.RegisterTool(a1, def, func(args map[string]interface{}) (interface{}, error) { argsJSON, _ := json.Marshal(args) r, err := pluginInvokeTool(pid, toolName, string(argsJSON)) - if err != nil { return nil, err } - if r == "" { return nil, nil } + if err != nil { + return nil, err + } + if r == "" { + return nil, nil + } var res map[string]interface{} if err := json.Unmarshal([]byte(r), &res); err != nil { return r, nil @@ -303,12 +416,27 @@ func go_core_dispatch(methodID C.int, ctx unsafe.Pointer, s1, s2, s3 *C.char, i1 "llm_text": sc.LLMText, "final_text": sc.FinalText, "no_memory": sc.NoMemory, } - if sc.Response != nil { m["response"] = *sc.Response } - if len(sc.ToolCalls) > 0 { m["tool_calls"] = sc.ToolCalls } - if len(sc.ToolResults) > 0 { m["tool_results"] = sc.ToolResults } + if sc.Response != nil { + m["response"] = *sc.Response + } + if len(sc.ToolCalls) > 0 { + m["tool_calls"] = sc.ToolCalls + } + if len(sc.ToolResults) > 0 { + m["tool_results"] = sc.ToolResults + } sc.RUnlock() b, _ := json.Marshal(m) - return pluginInvokeStage(pid, st, string(b)) + + // ABI v2: 插件可回传修改后的上下文写回内核 sc(如 RawMessage/LLMText/Response/ToolResults)。 + var result string + if err := pluginInvokeStage(pid, st, string(b), &result); err != nil { + return err + } + if result != "" { + applyStageResult(sc, result) + } + return nil } scope := sdk.StageScopeGlobal if a3 == "own_tools" { @@ -373,7 +501,10 @@ func go_core_dispatch(methodID C.int, ctx unsafe.Pointer, s1, s2, s3 *C.char, i1 case 9: // CORE_MEMORY_RECALL if mem := s.Memory(); mem != nil { entities, relations, err := mem.Recall([]string{a1}, n1) - if err != nil { setErr(errorOut, err); return 1 } + if err != nil { + setErr(errorOut, err) + return 1 + } b, _ := json.Marshal(map[string]interface{}{"entities": entities, "relations": relations}) setResult(result, string(b)) } @@ -382,15 +513,24 @@ func go_core_dispatch(methodID C.int, ctx unsafe.Pointer, s1, s2, s3 *C.char, i1 case 10: // CORE_MEMORY_COMMIT if mem := s.Memory(); mem != nil { var triples []sdk.Triple - if err := json.Unmarshal([]byte(a1), &triples); err != nil { setErr(errorOut, err); return 1 } - if err := mem.Commit(triples); err != nil { setErr(errorOut, err); return 1 } + if err := json.Unmarshal([]byte(a1), &triples); err != nil { + setErr(errorOut, err) + return 1 + } + if err := mem.Commit(triples); err != nil { + setErr(errorOut, err) + return 1 + } } return 0 case 11: // CORE_MEMORY_INTROSPECT if mem := s.Memory(); mem != nil { r, err := mem.Introspect() - if err != nil { setErr(errorOut, err); return 1 } + if err != nil { + setErr(errorOut, err) + return 1 + } b, _ := json.Marshal(r) setResult(result, string(b)) } @@ -398,17 +538,28 @@ func go_core_dispatch(methodID C.int, ctx unsafe.Pointer, s1, s2, s3 *C.char, i1 case 12: // CORE_MEMORY_MERGE if mem := s.Memory(); mem != nil { - if _, err := mem.MergeEntities(a1, a2); err != nil { setErr(errorOut, err); return 1 } + if _, err := mem.MergeEntities(a1, a2); err != nil { + setErr(errorOut, err) + return 1 + } } return 0 case 13: // CORE_MEMORY_PURGE if mem := s.Memory(); mem != nil { var criteria map[string]string - if err := json.Unmarshal([]byte(a1), &criteria); err != nil { setErr(errorOut, err); return 1 } + if err := json.Unmarshal([]byte(a1), &criteria); err != nil { + setErr(errorOut, err) + return 1 + } mode := "soft" - if n1 != 0 { mode = "hard" } - if _, err := mem.Purge(criteria, mode); err != nil { setErr(errorOut, err); return 1 } + if n1 != 0 { + mode = "hard" + } + if _, err := mem.Purge(criteria, mode); err != nil { + setErr(errorOut, err) + return 1 + } } return 0 @@ -422,7 +573,10 @@ func go_core_dispatch(methodID C.int, ctx unsafe.Pointer, s1, s2, s3 *C.char, i1 case 15: // CORE_KNOWLEDGE_SEARCH if kn := s.Knowledge(); kn != nil { results, err := kn.Search(a1, n1) - if err != nil { setErr(errorOut, err); return 1 } + if err != nil { + setErr(errorOut, err) + return 1 + } b, _ := json.Marshal(results) setResult(result, string(b)) } @@ -431,7 +585,10 @@ func go_core_dispatch(methodID C.int, ctx unsafe.Pointer, s1, s2, s3 *C.char, i1 case 16: // CORE_SETTINGS_GET if sett := s.Settings(); sett != nil { v, err := sett.Get(a1) - if err != nil { setErr(errorOut, err); return 1 } + if err != nil { + setErr(errorOut, err) + return 1 + } b, _ := json.Marshal(v) setResult(result, string(b)) } @@ -441,14 +598,20 @@ func go_core_dispatch(methodID C.int, ctx unsafe.Pointer, s1, s2, s3 *C.char, i1 if sett := s.Settings(); sett != nil { var v interface{} json.Unmarshal([]byte(a2), &v) - if err := sett.Set(a1, v); err != nil { setErr(errorOut, err); return 1 } + if err := sett.Set(a1, v); err != nil { + setErr(errorOut, err) + return 1 + } } return 0 case 18: // CORE_SETTINGS_REGISTER_DEF if sett := s.Settings(); sett != nil { var def sdk.ConfigDef - if err := json.Unmarshal([]byte(a1), &def); err != nil { setErr(errorOut, err); return 1 } + if err := json.Unmarshal([]byte(a1), &def); err != nil { + setErr(errorOut, err) + return 1 + } sett.RegisterDef(def) } return 0 @@ -462,14 +625,20 @@ func go_core_dispatch(methodID C.int, ctx unsafe.Pointer, s1, s2, s3 *C.char, i1 case 20: // CORE_LLM_SET_SOURCE if llm := s.LLM(); llm != nil { - if err := llm.SetSource(a1); err != nil { setErr(errorOut, err); return 1 } + if err := llm.SetSource(a1); err != nil { + setErr(errorOut, err) + return 1 + } } return 0 case 21: // CORE_SOCIAL_GET_PERSON if social := s.Social(); social != nil { p, err := social.GetPerson(a1) - if err != nil { setErr(errorOut, err); return 1 } + if err != nil { + setErr(errorOut, err) + return 1 + } b, _ := json.Marshal(p) setResult(result, string(b)) } @@ -478,7 +647,10 @@ func go_core_dispatch(methodID C.int, ctx unsafe.Pointer, s1, s2, s3 *C.char, i1 case 22: // CORE_SOCIAL_GET_NETWORK if social := s.Social(); social != nil { profiles, err := social.GetNetwork(a1, n1) - if err != nil { setErr(errorOut, err); return 1 } + if err != nil { + setErr(errorOut, err) + return 1 + } b, _ := json.Marshal(profiles) setResult(result, string(b)) } @@ -493,13 +665,18 @@ func go_core_dispatch(methodID C.int, ctx unsafe.Pointer, s1, s2, s3 *C.char, i1 return 0 case 25: // CORE_FREE_STRING - if s1 != nil { C.free(unsafe.Pointer(s1)) } + if s1 != nil { + C.free(unsafe.Pointer(s1)) + } return 0 case 26: // CORE_SETTINGS_GET_CORE if sett := s.Settings(); sett != nil { v, err := sett.GetCore(a1) - if err != nil { setErr(errorOut, err); return 1 } + if err != nil { + setErr(errorOut, err) + return 1 + } b, _ := json.Marshal(v) setResult(result, string(b)) } @@ -509,14 +686,20 @@ func go_core_dispatch(methodID C.int, ctx unsafe.Pointer, s1, s2, s3 *C.char, i1 if sett := s.Settings(); sett != nil { var v interface{} json.Unmarshal([]byte(a2), &v) - if err := sett.SetCore(a1, v); err != nil { setErr(errorOut, err); return 1 } + if err := sett.SetCore(a1, v); err != nil { + setErr(errorOut, err) + return 1 + } } return 0 case 28: // CORE_SETTINGS_LIST_CORE if sett := s.Settings(); sett != nil { keys, err := sett.ListCore(a1) - if err != nil { setErr(errorOut, err); return 1 } + if err != nil { + setErr(errorOut, err) + return 1 + } b, _ := json.Marshal(keys) setResult(result, string(b)) } @@ -525,7 +708,10 @@ func go_core_dispatch(methodID C.int, ctx unsafe.Pointer, s1, s2, s3 *C.char, i1 case 29: // CORE_SETTINGS_GET_PLUGIN if sett := s.Settings(); sett != nil { v, err := sett.GetPlugin(a1, a2) - if err != nil { setErr(errorOut, err); return 1 } + if err != nil { + setErr(errorOut, err) + return 1 + } b, _ := json.Marshal(v) setResult(result, string(b)) } @@ -535,14 +721,20 @@ func go_core_dispatch(methodID C.int, ctx unsafe.Pointer, s1, s2, s3 *C.char, i1 if sett := s.Settings(); sett != nil { var v interface{} json.Unmarshal([]byte(a3), &v) - if err := sett.SetPlugin(a1, a2, v); err != nil { setErr(errorOut, err); return 1 } + if err := sett.SetPlugin(a1, a2, v); err != nil { + setErr(errorOut, err) + return 1 + } } return 0 case 31: // CORE_SETTINGS_LIST_PLUGIN if sett := s.Settings(); sett != nil { keys, err := sett.ListPlugin(a1, a2) - if err != nil { setErr(errorOut, err); return 1 } + if err != nil { + setErr(errorOut, err) + return 1 + } b, _ := json.Marshal(keys) setResult(result, string(b)) } @@ -551,8 +743,14 @@ func go_core_dispatch(methodID C.int, ctx unsafe.Pointer, s1, s2, s3 *C.char, i1 case 32: // CORE_DOC_INSERT if dm := s.DocMemory(); dm != nil { var doc sdk.Doc - if err := json.Unmarshal([]byte(a1), &doc); err != nil { setErr(errorOut, err); return 1 } - if err := dm.Insert(&doc); err != nil { setErr(errorOut, err); return 1 } + if err := json.Unmarshal([]byte(a1), &doc); err != nil { + setErr(errorOut, err) + return 1 + } + if err := dm.Insert(&doc); err != nil { + setErr(errorOut, err) + return 1 + } } return 0 @@ -571,14 +769,20 @@ func go_core_dispatch(methodID C.int, ctx unsafe.Pointer, s1, s2, s3 *C.char, i1 case 35: // CORE_KNOWLEDGE_ADD if kn := s.Knowledge(); kn != nil { - if err := kn.Add(a1, a2); err != nil { setErr(errorOut, err); return 1 } + if err := kn.Add(a1, a2); err != nil { + setErr(errorOut, err) + return 1 + } } return 0 case 36: // CORE_KNOWLEDGE_LIST if kn := s.Knowledge(); kn != nil { list, err := kn.List() - if err != nil { setErr(errorOut, err); return 1 } + if err != nil { + setErr(errorOut, err) + return 1 + } b, _ := json.Marshal(list) setResult(result, string(b)) } @@ -602,7 +806,10 @@ func go_core_dispatch(methodID C.int, ctx unsafe.Pointer, s1, s2, s3 *C.char, i1 case 39: // CORE_SOCIAL_GET_RELATIONS if social := s.Social(); social != nil { rels, err := social.GetRelations(a1) - if err != nil { setErr(errorOut, err); return 1 } + if err != nil { + setErr(errorOut, err) + return 1 + } b, _ := json.Marshal(rels) setResult(result, string(b)) } @@ -611,7 +818,10 @@ func go_core_dispatch(methodID C.int, ctx unsafe.Pointer, s1, s2, s3 *C.char, i1 case 40: // CORE_SOCIAL_LIST_PERSONS if social := s.Social(); social != nil { persons, err := social.ListPersons() - if err != nil { setErr(errorOut, err); return 1 } + if err != nil { + setErr(errorOut, err) + return 1 + } b, _ := json.Marshal(persons) setResult(result, string(b)) } @@ -620,15 +830,24 @@ func go_core_dispatch(methodID C.int, ctx unsafe.Pointer, s1, s2, s3 *C.char, i1 case 41: // CORE_TEXT_MEMORY_APPEND if tm := s.TextMemory(); tm != nil { var evt sdk.TextEvent - if err := json.Unmarshal([]byte(a1), &evt); err != nil { setErr(errorOut, err); return 1 } - if err := tm.Append(evt); err != nil { setErr(errorOut, err); return 1 } + if err := json.Unmarshal([]byte(a1), &evt); err != nil { + setErr(errorOut, err) + return 1 + } + if err := tm.Append(evt); err != nil { + setErr(errorOut, err) + return 1 + } } return 0 case 42: // CORE_SETTINGS_LIST if sett := s.Settings(); sett != nil { keys, err := sett.List(a1) - if err != nil { setErr(errorOut, err); return 1 } + if err != nil { + setErr(errorOut, err) + return 1 + } b, _ := json.Marshal(keys) setResult(result, string(b)) } @@ -673,7 +892,9 @@ func go_core_dispatch(methodID C.int, ctx unsafe.Pointer, s1, s2, s3 *C.char, i1 } func goStr(s *C.char) string { - if s == nil { return "" } + if s == nil { + return "" + } return C.GoString(s) } diff --git a/internal/plugin/cabi/types.go b/internal/plugin/cabi/types.go index a459958..764f0a3 100644 --- a/internal/plugin/cabi/types.go +++ b/internal/plugin/cabi/types.go @@ -3,11 +3,18 @@ package cabi import "gitcode.com/JianFeeeee/HomeAgent/internal/meta" // ABI version constants — single source of truth is meta.go -const ( +// ABIVersion/ABIVersionMin 是字符串 semver(var 转发,因 meta 侧 Version 为注入变量); +// CABINum/CABINumMin 是 C 层整数协商版本。 +var ( ABIVersion = meta.ABIVersion ABIVersionMin = meta.ABIVersionMin ) +const ( + CABINum = meta.CABINum + CABINumMin = meta.CABINumMin +) + // Dispatch method IDs — single source of truth is meta.go const ( CoreRegisterTool = meta.CoreRegisterTool diff --git a/third_party/homeagent-sdk/meta/meta.go b/third_party/homeagent-sdk/meta/meta.go index d683eca..6025258 100644 --- a/third_party/homeagent-sdk/meta/meta.go +++ b/third_party/homeagent-sdk/meta/meta.go @@ -6,7 +6,7 @@ package meta var ( // Version 是 HomeAgent SDK 版本号。 // 通过 `-ldflags="-X gitcode.com/JianFeeeee/homeagent-sdk/meta.Version=vX.Y.Z"` 注入。 - Version = "0.8.0" + Version = "0.9.0" // Commit 是构建时的 Git commit hash。 Commit = "unknown" @@ -21,7 +21,7 @@ var ( CoreModule = "gitcode.com/JianFeeeee/HomeAgent" // CoreVersion 是此 SDK 所兼容的最低核心版本。 - CoreVersion = "0.8.0" + CoreVersion = "0.9.0" ) // FullVersion 返回完整的版本字符串。 @@ -30,11 +30,23 @@ func FullVersion() string { } // ---- ABI 版本(与核心仓 internal/meta/meta.go 同步) ---- -// 修改时需确保核心仓与 SDK 仓的值一致。 +// ABI 标识版本直接取内核版本号字符串(semver),与核心 Version 保持一致,不使用独立数字编码。 +// 协商层(C 结构体 int version 字段)使用 CABINum:由版本字符串派生的整数(major*100 + minor)。 +// 映射:v0.8.x → CABINum=800;v0.9.x → CABINum=900(invoke_stage 写回)。 +// 小版本(patch)演进不影响 ABI,CABINum 不变。version_min 保证旧 ABI 插件仍可加载。 + +var ( + // ABIVersion 是 ABI 标识版本(字符串 semver,与 SDK CoreVersion 对齐)。 + ABIVersion = CoreVersion + // ABIVersionMin 是兼容的最低 ABI 标识版本。 + ABIVersionMin = "0.8.0" +) const ( - ABIVersion = 1 - ABIVersionMin = 1 + // CABINum 是 C 层协商用的整数版本(major*100 + minor),随 ABIVersion 派生。 + CABINum = 900 + // CABINumMin 是 C 层兼容的最低整数版本。 + CABINumMin = 800 ) // ---- Dispatch Method IDs(与核心仓 internal/meta/meta.go 同步) ----