refactor: migrate built-in plugins to SDK-only interface

- Six-phase plan complete: webui/cli/healthcheck/pluginmgr/clawhubadapter
  now interact with the kernel exclusively via internal/sdk interfaces;
  all Configure() calls and package-level global injection removed
- buildSDK in internal/plugin/registry.go is the single assembly point
- Add internal/sdk/events.go exporting event types/constants
- Fix ProviderManager cooldown sharing: LuaAdaptedProvider.Name() now
  returns the source name instead of lua_<adapter>, so multiple sources
  sharing an adapter (single script load via shared VM AdapterCache) no
  longer share failure-cooldown state
- Verified: build/vet/tests green, deployed to homeagent.service with
  full plugin capability testing via local OpenAI-compatible mock
This commit is contained in:
JianFeeeee
2026-08-01 12:17:17 +08:00
parent 74d30915ea
commit 0d74efd0ea
47 changed files with 1515 additions and 1137 deletions

16
internal/sdk/adapter.go Normal file
View File

@ -0,0 +1,16 @@
package sdk
// AdapterAPI provides access to the Lua LLM protocol adapters.
type AdapterAPI interface {
List() []APIAdapter
// Load writes the adapter source file and hot-reloads it.
Load(name, code string) error
Remove(name string) error
AdapterDir() string
}
// APIAdapter describes a loaded Lua protocol adapter.
type APIAdapter struct {
Name string `json:"name"`
Version string `json:"version"`
}

View File

@ -0,0 +1,62 @@
package sdk
import (
"errors"
"os"
"path/filepath"
luaVM "gitcode.com/JianFeeeee/HomeAgent/internal/lua"
)
// adapterImpl 桥接 Lua VM 的协议适配器管理。
type adapterImpl struct {
vm *luaVM.VM
}
func NewAdapter(vm *luaVM.VM) AdapterAPI {
return &adapterImpl{vm: vm}
}
func (a *adapterImpl) List() []APIAdapter {
if a.vm == nil {
return nil
}
got := a.vm.ListAdapters()
out := make([]APIAdapter, len(got))
for i, ad := range got {
out[i] = APIAdapter{Name: ad.Name, Version: ad.Version}
}
return out
}
func (a *adapterImpl) Load(name, code string) error {
if a.vm == nil {
return errors.New("lua vm not available")
}
path := filepath.Join(a.vm.AdapterDir(), name+".lua")
if err := os.WriteFile(path, []byte(code), 0644); err != nil {
return err
}
return a.vm.LoadAdapter(path)
}
func (a *adapterImpl) Remove(name string) error {
if a.vm == nil {
return errors.New("lua vm not available")
}
path := filepath.Join(a.vm.AdapterDir(), name+".lua")
if err := os.Remove(path); err != nil {
return err
}
a.vm.RemoveAdapter(name)
return nil
}
func (a *adapterImpl) AdapterDir() string {
if a.vm == nil {
return ""
}
return a.vm.AdapterDir()
}
var _ AdapterAPI = (*adapterImpl)(nil)

10
internal/sdk/config.go Normal file
View File

@ -0,0 +1,10 @@
package sdk
import "gitcode.com/JianFeeeee/HomeAgent/pkg/types"
// ConfigAPI provides read/write access to the runtime config object
// (unrelated to the config database).
type ConfigAPI interface {
Get() *types.Config
Put(cfg *types.Config)
}

View File

@ -0,0 +1,22 @@
package sdk
import "gitcode.com/JianFeeeee/HomeAgent/pkg/types"
// configImpl 持有运行期配置对象的引用(与配置数据库无关)。
type configImpl struct {
cfg *types.Config
}
func NewConfig(cfg *types.Config) ConfigAPI {
return &configImpl{cfg: cfg}
}
func (c *configImpl) Get() *types.Config {
return c.cfg
}
func (c *configImpl) Put(cfg *types.Config) {
c.cfg = cfg
}
var _ ConfigAPI = (*configImpl)(nil)

20
internal/sdk/events.go Normal file
View File

@ -0,0 +1,20 @@
package sdk
import "gitcode.com/JianFeeeee/HomeAgent/internal/events"
// 事件类型/事件类型别名:插件经 SDK 订阅/发布内核事件,
// 无需直接 import internal/events(内核事件总线仅通过 SDK 暴露)。
type EventType = events.EventType
type Event = events.Event
const (
EventRawInput = events.EventRawInput
EventAgentOutput = events.EventAgentOutput
EventAgentLLMChain = events.EventAgentLLMChain
EventToolCall = events.EventToolCall
EventReasoning = events.EventReasoning
EventStage = events.EventStage
EventSystem = events.EventSystem
EventAll = events.EventAll
)

17
internal/sdk/indexer.go Normal file
View File

@ -0,0 +1,17 @@
package sdk
// IndexerAPI exposes the memory context indexer.
type IndexerAPI interface {
BuildContext(q string) (*IndexContext, error)
FormatContext(ctx *IndexContext) string
GetToolDefinitions() []map[string]interface{}
BuildToolPrompt() string
}
// IndexContext is a neutral snapshot of the injected memory context.
type IndexContext struct {
Entities []Entity `json:"entities"`
Relations []Relation `json:"relations"`
Summary string `json:"summary"`
TokenEstimate int `json:"token_estimate"`
}

View File

@ -0,0 +1,71 @@
package sdk
import (
"errors"
"gitcode.com/JianFeeeee/HomeAgent/internal/memory"
)
// indexerImpl 桥接 memory.Indexer 到中立的 IndexerAPI。
type indexerImpl struct {
idx *memory.Indexer
}
func NewIndexer(idx *memory.Indexer) IndexerAPI {
return &indexerImpl{idx: idx}
}
func (i *indexerImpl) BuildContext(q string) (*IndexContext, error) {
if i.idx == nil {
return nil, errors.New("indexer not available")
}
ctx := i.idx.BuildContext(q)
if ctx == nil {
return &IndexContext{}, nil
}
entities := make([]Entity, len(ctx.Entities))
for j, e := range ctx.Entities {
entities[j] = Entity{Name: e.Name, Type: e.Type, MentionCount: e.MentionCount}
}
relations := make([]Relation, len(ctx.Relations))
for j, r := range ctx.Relations {
relations[j] = Relation{SourceName: r.SourceName, TargetName: r.TargetName, RelationType: r.RelationType}
}
return &IndexContext{
Entities: entities,
Relations: relations,
Summary: ctx.Summary,
TokenEstimate: ctx.TokenEstimate,
}, nil
}
func (i *indexerImpl) FormatContext(ctx *IndexContext) string {
if i.idx == nil || ctx == nil {
return ""
}
entities := make([]memory.Entity, len(ctx.Entities))
for j, e := range ctx.Entities {
entities[j] = memory.Entity{Name: e.Name, Type: e.Type, MentionCount: e.MentionCount}
}
return i.idx.FormatContext(&memory.InjectedContext{
Entities: entities,
Summary: ctx.Summary,
TokenEstimate: ctx.TokenEstimate,
})
}
func (i *indexerImpl) GetToolDefinitions() []map[string]interface{} {
if i.idx == nil {
return nil
}
return i.idx.GetToolDefinitions()
}
func (i *indexerImpl) BuildToolPrompt() string {
if i.idx == nil {
return ""
}
return i.idx.BuildToolPrompt()
}
var _ IndexerAPI = (*indexerImpl)(nil)

View File

@ -2,5 +2,13 @@ package sdk
import pubsdk "gitcode.com/JianFeeeee/homeagent-sdk/sdk"
type KnowledgeAPI = pubsdk.KnowledgeAPI
// KnowledgeAPI 是内置插件使用的全量知识库接口。
type KnowledgeAPI interface {
pubsdk.KnowledgeAPI
// Stats 返回知识库的运行统计。
Stats() map[string]interface{}
// Remove 按名称删除一条知识。
Remove(name string) error
}
type Knowledge = pubsdk.Knowledge

View File

@ -26,4 +26,14 @@ func (k *knowledgeImpl) List() ([]string, error) {
return k.ks.List(), nil
}
func (k *knowledgeImpl) Stats() map[string]interface{} {
if k.ks == nil { return map[string]interface{}{} }
return k.ks.Stats()
}
func (k *knowledgeImpl) Remove(name string) error {
if k.ks == nil { return nil }
return k.ks.Remove(name)
}
var _ KnowledgeAPI = (*knowledgeImpl)(nil)

View File

@ -1,5 +1,62 @@
package sdk
import pubsdk "gitcode.com/JianFeeeee/homeagent-sdk/sdk"
import (
"context"
type LLMAPI = pubsdk.LLMAPI
pubsdk "gitcode.com/JianFeeeee/homeagent-sdk/sdk"
)
// LLMAPI 是内置插件使用的全量 LLM Provider 接口。
// 内置 SDK 为全量接口,外部 SDK 仅暴露其中的安全子集(pubsdk.LLMAPI)。
type LLMAPI interface {
pubsdk.LLMAPI
// ReloadFromConfig 从内核配置重建所有 LLM Provider。
ReloadFromConfig() error
// Chat 向默认 Provider 发起一次补全调用(非记忆管线)。
Chat(ctx context.Context, req *LLMCompletionRequest) (*LLMCompletionResponse, error)
}
// LLMMessage 是面向 LLM Provider 的中立对话消息。
type LLMMessage struct {
Role string `json:"role"`
Content string `json:"content,omitempty"`
ReasoningContent string `json:"reasoning_content,omitempty"`
ToolCallID string `json:"tool_call_id,omitempty"`
ToolCalls []LLMToolCall `json:"tool_calls,omitempty"`
}
// LLMToolCall 是中立的工具调用请求。
type LLMToolCall struct {
ID string `json:"id"`
Name string `json:"name"`
Arguments map[string]interface{} `json:"arguments"`
}
// LLMTokenUsage 报告一次补全的 token 消耗。
type LLMTokenUsage struct {
Prompt int `json:"prompt"`
Completion int `json:"completion"`
Total int `json:"total"`
}
// LLMCompletionRequest 是中立的补全请求。
// Tools/ToolChoice 使用 OpenAI 风格载荷,便于 Provider 直接转发。
type LLMCompletionRequest struct {
Model string `json:"model,omitempty"`
Messages []LLMMessage `json:"messages"`
Temperature float64 `json:"temperature,omitempty"`
MaxTokens int `json:"max_tokens,omitempty"`
Stream bool `json:"stream,omitempty"`
Tools []interface{} `json:"tools,omitempty"`
ToolChoice interface{} `json:"tool_choice,omitempty"`
DisableThinking bool `json:"disable_thinking,omitempty"`
}
// LLMCompletionResponse 是中立的补全响应。
type LLMCompletionResponse struct {
Content string `json:"content"`
ReasoningContent string `json:"reasoning_content,omitempty"`
FinishReason string `json:"finish_reason,omitempty"`
TokenUsage LLMTokenUsage `json:"token_usage,omitempty"`
ToolCalls []LLMToolCall `json:"tool_calls,omitempty"`
}

View File

@ -1,26 +1,133 @@
package sdk
import agentAPI "gitcode.com/JianFeeeee/HomeAgent/internal/agent/api"
import (
"context"
"fmt"
type llmImpl struct{ mgr *agentAPI.ProviderManager }
agentAPI "gitcode.com/JianFeeeee/HomeAgent/internal/agent/api"
internalConfig "gitcode.com/JianFeeeee/HomeAgent/internal/config"
luaVM "gitcode.com/JianFeeeee/HomeAgent/internal/lua"
)
func NewLLM(mgr *agentAPI.ProviderManager) LLMAPI { return &llmImpl{mgr: mgr} }
type llmImpl struct {
mgr *agentAPI.ProviderManager
cfgReg *internalConfig.ConfigRegistry
lua *luaVM.VM
baseAPIKey string
}
func NewLLM(mgr *agentAPI.ProviderManager, cfgReg *internalConfig.ConfigRegistry, lua *luaVM.VM, baseAPIKey string) LLMAPI {
return &llmImpl{mgr: mgr, cfgReg: cfgReg, lua: lua, baseAPIKey: baseAPIKey}
}
func (l *llmImpl) ListSources() []string {
if l.mgr == nil { return nil }
if l.mgr == nil {
return nil
}
return l.mgr.List()
}
func (l *llmImpl) SetSource(name string) error {
if l.mgr == nil { return nil }
if l.mgr == nil {
return nil
}
return l.mgr.SetDefault(name)
}
func (l *llmImpl) CurrentSource() string {
if l.mgr == nil { return "" }
if l.mgr == nil {
return ""
}
p := l.mgr.Default()
if p == nil { return "" }
if p == nil {
return ""
}
return p.Name()
}
func (l *llmImpl) Chat(ctx context.Context, req *LLMCompletionRequest) (*LLMCompletionResponse, error) {
if l.mgr == nil {
return nil, fmt.Errorf("llm: provider manager not available")
}
p := l.mgr.Default()
if p == nil {
return nil, fmt.Errorf("llm: no default provider")
}
apiReq := &agentAPI.CompletionRequest{
Model: req.Model,
Temperature: req.Temperature,
MaxTokens: req.MaxTokens,
Stream: req.Stream,
Tools: req.Tools,
ToolChoice: req.ToolChoice,
DisableThinking: req.DisableThinking,
}
if len(req.Messages) > 0 {
apiReq.Messages = make([]agentAPI.Message, len(req.Messages))
for i, m := range req.Messages {
msg := agentAPI.Message{
Role: m.Role,
Content: m.Content,
ReasoningContent: m.ReasoningContent,
ToolCallID: m.ToolCallID,
}
if len(m.ToolCalls) > 0 {
msg.ToolCalls = make([]agentAPI.ToolCall, len(m.ToolCalls))
for j, tc := range m.ToolCalls {
msg.ToolCalls[j] = agentAPI.ToolCall{ID: tc.ID, Name: tc.Name, Arguments: tc.Arguments}
}
}
apiReq.Messages[i] = msg
}
}
resp, err := p.Chat(ctx, apiReq)
if err != nil {
return nil, err
}
out := &LLMCompletionResponse{
Content: resp.Content,
ReasoningContent: resp.ReasoningContent,
FinishReason: resp.FinishReason,
TokenUsage: LLMTokenUsage{
Prompt: resp.TokenUsage.Prompt,
Completion: resp.TokenUsage.Completion,
Total: resp.TokenUsage.Total,
},
}
for _, tc := range resp.ToolCalls {
out.ToolCalls = append(out.ToolCalls, LLMToolCall{ID: tc.ID, Name: tc.Name, Arguments: tc.Arguments})
}
return out, nil
}
func (l *llmImpl) ReloadFromConfig() error {
if l.mgr == nil || l.cfgReg == nil || l.lua == nil {
return nil
}
cfg := l.cfgReg.ToConfig()
if cfg == nil {
return nil
}
l.mgr.Reset()
for _, src := range cfg.LLM.Sources {
key := src.APIKey
if key == "" {
key = l.baseAPIKey
}
provider := agentAPI.NewLuaAdaptedProvider(agentAPI.BaseConfig{
Model: src.Model,
BaseURL: src.BaseURL,
APIKey: key,
Temperature: cfg.LLM.Temperature,
MaxTokens: cfg.LLM.MaxTokens,
ContextWindow: src.ContextWindow,
}, l.lua, src.Name, src.Adapter)
l.mgr.Register(src.Name, provider)
}
if cfg.LLM.Provider != "" {
_ = l.mgr.SetDefault(cfg.LLM.Provider)
}
return nil
}
var _ LLMAPI = (*llmImpl)(nil)

View File

@ -2,12 +2,26 @@ package sdk
import pubsdk "gitcode.com/JianFeeeee/homeagent-sdk/sdk"
type MemoryAPI = pubsdk.MemoryAPI
// MemoryAPI 是内置插件使用的全量图记忆接口。
type MemoryAPI interface {
pubsdk.MemoryAPI
// GraphData 返回整个知识图谱的完整快照。
GraphData() (map[string]interface{}, error)
}
type Entity = pubsdk.Entity
type Relation = pubsdk.Relation
type Triple = pubsdk.Triple
type TextMemoryAPI = pubsdk.TextMemoryAPI
// TextMemoryAPI 是内置插件使用的全量文本记忆接口。
type TextMemoryAPI interface {
pubsdk.TextMemoryAPI
// RecentEvents 返回最近 n 条记忆事件。
RecentEvents(n int) ([]TextEvent, error)
// Stats 返回文本记忆的运行统计。
Stats() map[string]interface{}
}
type TextEvent = pubsdk.TextEvent
type DocMemoryAPI = pubsdk.DocMemoryAPI

View File

@ -50,6 +50,11 @@ func (m *graphMemory) Purge(criteria map[string]string, mode string) (int, error
return m.db.Purge(criteria, mode)
}
func (m *graphMemory) GraphData() (map[string]interface{}, error) {
if m.db == nil { return map[string]interface{}{}, nil }
return m.db.GraphData()
}
type textMemoryImpl struct{ tm *text.Memory }
func NewTextMemory(tm *text.Memory) TextMemoryAPI { return &textMemoryImpl{tm: tm} }
@ -61,6 +66,22 @@ func (m *textMemoryImpl) Append(evt TextEvent) error {
})
}
func (m *textMemoryImpl) RecentEvents(n int) ([]TextEvent, error) {
if m.tm == nil { return nil, nil }
got, err := m.tm.RecentEvents(n)
if err != nil { return nil, err }
out := make([]TextEvent, len(got))
for i, e := range got {
out[i] = TextEvent{Role: e.Source, Content: e.Input, Timestamp: e.Timestamp, Channel: e.AgentID}
}
return out, nil
}
func (m *textMemoryImpl) Stats() map[string]interface{} {
if m.tm == nil { return map[string]interface{}{} }
return m.tm.Stats()
}
type docMemoryImpl struct{ ds *doc.Store }
func NewDocMemory(ds *doc.Store) DocMemoryAPI { return &docMemoryImpl{ds: ds} }

View File

@ -57,6 +57,12 @@ type DisabledPluginInfo struct {
DisabledBy string `json:"disabled_by"`
}
// PluginMeta is the display-name metadata for a plugin (from plg.json / RegisterPluginMeta).
type PluginMeta struct {
NameZh string `json:"name_zh"`
NameEn string `json:"name_en"`
}
type PluginManager interface {
ListLoadedPlugins() []string
ListDisabledPlugins() []DisabledPluginInfo
@ -64,18 +70,45 @@ type PluginManager interface {
DisablePlugin(name, by string) error
EnablePlugin(name string) error
ReloadPlugins() (string, error)
PluginMetas() map[string]PluginMeta
PluginDir() string
}
type PluginSDK struct {
*pubsdk.PluginSDK
settings SettingsAPI
memory MemoryAPI
textMem TextMemoryAPI
docMem DocMemoryAPI
know KnowledgeAPI
llm LLMAPI
iom *agentIO.IOManager
eventBus *events.Bus
logger *log.Logger
pluginMgr PluginManager
status StatusAPI
supervisor SupervisorAPI
skill SkillAPI
adapter AdapterAPI
tracker TrackerAPI
config ConfigAPI
tool ToolAPI
indexer IndexerAPI
}
func (s *PluginSDK) PluginMgr() PluginManager { return s.pluginMgr }
// 以下访问器遮蔽公共 SDK 的同名方法,返回内置插件可用的全量接口。
func (s *PluginSDK) Settings() SettingsAPI { return s.settings }
func (s *PluginSDK) Memory() MemoryAPI { return s.memory }
func (s *PluginSDK) TextMemory() TextMemoryAPI { return s.textMem }
func (s *PluginSDK) DocMemory() DocMemoryAPI { return s.docMem }
func (s *PluginSDK) Knowledge() KnowledgeAPI { return s.know }
func (s *PluginSDK) LLM() LLMAPI { return s.llm }
// ioAdapter 桥接 IOManager 到公共 SDK 的 IOInjector 接口,
// 确保外部插件通过 s.InjectText() 等方法的调用能被路由到内核 IO 层。
type ioAdapter struct{ iom *agentIO.IOManager }
@ -114,6 +147,15 @@ type SDKConfig struct {
RegOutput OutputChannelRegistrar
RegInput InputChannelRegistrar
PluginMgr PluginManager
Status StatusAPI
Supervisor SupervisorAPI
Skill SkillAPI
Adapter AdapterAPI
Tracker TrackerAPI
Config ConfigAPI
Tool ToolAPI
Indexer IndexerAPI
}
func New(name string, cfg SDKConfig) *PluginSDK {
@ -131,13 +173,38 @@ func New(name string, cfg SDKConfig) *PluginSDK {
base.SetLLMAPI(cfg.LLM)
return &PluginSDK{
PluginSDK: base,
settings: cfg.Settings,
memory: cfg.Memory,
textMem: cfg.TextMemory,
docMem: cfg.DocMemory,
know: cfg.Knowledge,
llm: cfg.LLM,
iom: cfg.IOManager,
eventBus: cfg.EventBus,
logger: log.Default(),
pluginMgr: cfg.PluginMgr,
status: cfg.Status,
supervisor: cfg.Supervisor,
skill: cfg.Skill,
adapter: cfg.Adapter,
tracker: cfg.Tracker,
config: cfg.Config,
tool: cfg.Tool,
indexer: cfg.Indexer,
}
}
func (s *PluginSDK) Status() StatusAPI { return s.status }
func (s *PluginSDK) Supervisor() SupervisorAPI { return s.supervisor }
func (s *PluginSDK) Skill() SkillAPI { return s.skill }
func (s *PluginSDK) Adapter() AdapterAPI { return s.adapter }
func (s *PluginSDK) Tracker() TrackerAPI { return s.tracker }
func (s *PluginSDK) Config() ConfigAPI { return s.config }
func (s *PluginSDK) Tool() ToolAPI { return s.tool }
func (s *PluginSDK) Indexer() IndexerAPI { return s.indexer }
func (s *PluginSDK) InjectInput(source, channel, eventType string, payload map[string]interface{}) {
if s.iom != nil {
s.iom.InjectInputTo(source, channel, eventType, payload)

View File

@ -2,5 +2,13 @@ package sdk
import pubsdk "gitcode.com/JianFeeeee/homeagent-sdk/sdk"
type SettingsAPI = pubsdk.SettingsAPI
// SettingsAPI 是内置插件使用的全量配置接口。
type SettingsAPI interface {
pubsdk.SettingsAPI
// DefsCore 返回核心配置表中匹配前缀的配置定义。
DefsCore(prefix string) []*ConfigDef
// DefsPlugin 返回另一个插件的配置定义。
DefsPlugin(plugin, prefix string) []*ConfigDef
}
type ConfigDef = pubsdk.ConfigDef

View File

@ -79,7 +79,7 @@ func (s *settingsImpl) Defs(prefix string) []*ConfigDef {
// ConfigDef = pubsdk.ConfigDef (type alias), so direct conversion works
cpy := ConfigDef{
Key: d.Key, Type: d.Type, DisplayName: d.DisplayName, Description: d.Description,
Category: d.Category, Options: d.Options,
Category: d.Category, Options: d.Options, Default: d.Default,
}
out[i] = &cpy
}
@ -97,6 +97,25 @@ func (s *settingsImpl) Plugins() []string {
result = append(result, names...)
return result
}
func (s *settingsImpl) DefsCore(prefix string) []*ConfigDef {
if s.reg == nil { return nil }
return mapDefs(s.reg.ListDefs(prefix))
}
func (s *settingsImpl) DefsPlugin(plugin, prefix string) []*ConfigDef {
if s.reg == nil { return nil }
return mapDefs(s.reg.PluginConfig(plugin).ListDefs(prefix))
}
func mapDefs(defs []*internalConfig.ConfigDef) []*ConfigDef {
out := make([]*ConfigDef, len(defs))
for i, d := range defs {
out[i] = &ConfigDef{
Key: d.Key, Type: d.Type, DisplayName: d.DisplayName, Description: d.Description,
Category: d.Category, Options: d.Options, Default: d.Default,
}
}
return out
}
func stringifyDefault(v interface{}) string {
if v == nil { return "" }

22
internal/sdk/skill.go Normal file
View File

@ -0,0 +1,22 @@
package sdk
// SkillAPI provides access to the skill manager.
type SkillAPI interface {
List() []*Skill
Get(name string) *Skill
Install(name, content string) error
Uninstall(name string) error
Toggle(name string, enabled bool) error
}
// Skill is a neutral description of an installed skill.
type Skill struct {
Name string `json:"name"`
Description string `json:"description"`
Version string `json:"version"`
Author string `json:"author,omitempty"`
Entry string `json:"entry,omitempty"`
Source string `json:"source,omitempty"`
Enabled bool `json:"enabled"`
RawContent string `json:"-"`
}

99
internal/sdk/status.go Normal file
View File

@ -0,0 +1,99 @@
package sdk
// StatusAPI provides a snapshot of the kernel runtime status.
type StatusAPI interface {
GetKernelStatus() *KernelStatus
}
// KernelStatus is the aggregated runtime snapshot of all kernel subsystems.
type KernelStatus struct {
Uptime string `json:"uptime"`
StartTime string `json:"start_time"`
AgentID string `json:"agent_id"`
Plugins []PluginInfo `json:"plugins"`
Tools []ToolDef `json:"tools"`
Channels []ChannelInfo `json:"channels"`
Memory MemoryStatus `json:"memory"`
Knowledge KnowledgeStatus `json:"knowledge"`
Documents DocumentStatus `json:"documents"`
TextMemory TextMemoryStatus `json:"text_memory"`
Social SocialStatus `json:"social"`
Skills SkillsStatus `json:"skills"`
LLM LLMStatus `json:"llm"`
Context ContextStatus `json:"context"`
Runtime RuntimeStatus `json:"runtime"`
Tracker TrackerStatus `json:"tracker"`
}
type PluginInfo struct {
Name string `json:"name"`
Loaded bool `json:"loaded"`
}
type ChannelInfo struct {
Name string `json:"name"`
Type string `json:"type"`
Ready bool `json:"ready"`
}
type MemoryStatus struct {
Available bool `json:"available"`
EntityCount int `json:"entity_count"`
RelationCount int `json:"relation_count"`
EntityTypes int `json:"entity_types"`
}
type KnowledgeStatus struct {
Available bool `json:"available"`
ItemCount int `json:"item_count"`
Items []string `json:"items,omitempty"`
}
type DocumentStatus struct {
Available bool `json:"available"`
DocCount int `json:"doc_count"`
VectorCount int `json:"vector_count"`
}
type TextMemoryStatus struct {
Available bool `json:"available"`
FileCount int `json:"file_count"`
}
type SocialStatus struct {
Available bool `json:"available"`
PersonCount int `json:"person_count"`
}
type SkillsStatus struct {
Available bool `json:"available"`
SkillList []string `json:"skill_list,omitempty"`
}
type LLMStatus struct {
Available bool `json:"available"`
Provider string `json:"provider,omitempty"`
Sources int `json:"sources,omitempty"`
}
type ContextStatus struct {
EventCount int `json:"event_count,omitempty"`
}
type RuntimeStatus struct {
Goroutines int `json:"goroutines"`
MemoryMB int64 `json:"memory_mb"`
GoVersion string `json:"go_version"`
}
type TrackerStatus struct {
Available bool `json:"available"`
Dir string `json:"dir,omitempty"`
}

View File

@ -0,0 +1,25 @@
package sdk
import (
"time"
"gitcode.com/JianFeeeee/HomeAgent/pkg/types"
)
// SupervisorAPI provides access to agent lifecycle management.
type SupervisorAPI interface {
ListAgents() []AgentStatus
GetAgentStatus(id string) (*AgentStatus, error)
PreActionSnapshot(id string) (*types.Snapshot, error)
RollbackAgent(id string, snapID string) error
}
// AgentStatus is a neutral snapshot of a supervised agent.
type AgentStatus struct {
ID types.AgentID `json:"id"`
State types.AgentState `json:"state"`
Health types.HealthStatus `json:"health"`
Uptime time.Duration `json:"uptime,omitempty"`
Network types.NetworkCheckResult `json:"network,omitempty"`
TrackerStats map[string]interface{} `json:"tracker_stats,omitempty"`
}

20
internal/sdk/tool.go Normal file
View File

@ -0,0 +1,20 @@
package sdk
// ToolSource 描述内核工具注册表/执行器的只读视图(由 StageHost 实现)。
// 用接口而非具体类型,避免 sdk 依赖内核包(内核包反向依赖 sdk)。
type ToolSource interface {
GetToolDefs() []ToolDef
ToolDef(name string) *ToolDef
ExecuteTool(name string, args map[string]interface{}) (interface{}, error)
}
// ToolAPI exposes the kernel tool registry and executor
// (StageHost for plugin tools + IOManager for device/channel tools).
type ToolAPI interface {
// GetToolDefs returns all tools registered on the stage host (plugin tools).
GetToolDefs() []ToolDef
// GetAllTools returns all tools exposed by IO devices/channels.
GetAllTools() []ToolDef
// ExecuteTool executes a tool by name, resolving across the stage host first.
ExecuteTool(name string, args map[string]interface{}) (interface{}, error)
}

50
internal/sdk/tool_impl.go Normal file
View File

@ -0,0 +1,50 @@
package sdk
import (
"fmt"
agentIO "gitcode.com/JianFeeeee/HomeAgent/internal/agent/io"
)
// toolImpl 桥接 StageHost(插件工具)与 IOManager(设备/通道工具)。
type toolImpl struct {
stageHost ToolSource
iom *agentIO.IOManager
}
func NewTool(sh ToolSource, iom *agentIO.IOManager) ToolAPI {
return &toolImpl{stageHost: sh, iom: iom}
}
func (t *toolImpl) GetToolDefs() []ToolDef {
if t.stageHost == nil {
return nil
}
return t.stageHost.GetToolDefs()
}
func (t *toolImpl) GetAllTools() []ToolDef {
if t.iom == nil {
return nil
}
defs := t.iom.GetAllTools()
out := make([]ToolDef, 0, len(defs))
for _, d := range defs {
out = append(out, ToolDef{Name: d.Name, Description: d.Description, Parameters: d.Parameters})
}
return out
}
func (t *toolImpl) ExecuteTool(name string, args map[string]interface{}) (interface{}, error) {
if t.stageHost != nil {
if def := t.stageHost.ToolDef(name); def != nil {
return t.stageHost.ExecuteTool(name, args)
}
}
if t.iom != nil {
return t.iom.ExecuteTool(name, args)
}
return nil, fmt.Errorf("tool %s not found", name)
}
var _ ToolAPI = (*toolImpl)(nil)

36
internal/sdk/tracker.go Normal file
View File

@ -0,0 +1,36 @@
package sdk
import "time"
// TrackerAPI provides access to the file change tracker / rollback.
type TrackerAPI interface {
ChangeSets() []*ChangeSet
Rollback() error
Stats() map[string]interface{}
HasChanges() bool
}
type ChangeType string
const (
ChangeFileCreated ChangeType = "created"
ChangeFileModified ChangeType = "modified"
ChangeFileDeleted ChangeType = "deleted"
)
type FileChange struct {
Path string `json:"path"`
Type ChangeType `json:"type"`
SizeBefore int64 `json:"size_before,omitempty"`
SizeAfter int64 `json:"size_after,omitempty"`
HashBefore string `json:"hash_before,omitempty"`
HashAfter string `json:"hash_after,omitempty"`
Content []byte `json:"-"` // 回滚用原始内容,不参与 JSON 序列化
}
type ChangeSet struct {
ID string `json:"id"`
Action string `json:"action"`
Timestamp time.Time `json:"timestamp"`
Files []FileChange `json:"files"`
}