C ABI v2: invoke_stage 写回 + ABI 版本对齐核心版本号 (v0.9.0)

- 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 项目状态说明
This commit is contained in:
JianFeeeee
2026-08-15 15:38:57 +08:00
parent 15b45c7653
commit 710b1afdf9
8 changed files with 412 additions and 151 deletions

View File

@ -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。
依赖Go 1.25+, CGo (go-sqlite3), Linux/Windows。

View File

@ -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

View File

@ -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

View File

@ -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=800v0.9.x → CABINum=900invoke_stage 写回)。
// 小版本patch演进不影响 ABICABINum 不变。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
)

View File

@ -6,9 +6,9 @@
#include <stdlib.h>
#include <string.h>
// HOMEAGENT_ABI_VERSION 与 internal/meta/meta.go ABIVersion 同步
// HOMEAGENT_ABI_VERSION 与 internal/meta/meta.go CABINum 同步major*100+minorv0.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); }

View File

@ -6,9 +6,10 @@ package cabi
#cgo LDFLAGS: -ldl
#include <stdlib.h>
// 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→800v0.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)
}

View File

@ -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 是字符串 semvervar 转发,因 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

View File

@ -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=800v0.9.x → CABINum=900invoke_stage 写回)。
// 小版本patch演进不影响 ABICABINum 不变。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 同步) ----