docs: 修正全部文档使其与源码实现一致

- Plugin.Start(sdk *PluginSDK) 接口签名改为指针
- 方法表重写: 移除 CallLLM/QueryKnowledge/SetMemory 等不存在方法
- IOInjector 参数顺序修正为 (source, channel, text)
- 删除虚构 SDKConfig, 替换为实际 New() 构造函数签名
- .hmap 内容描述一致化 (plugin.so + plugin.dll + main.lua)
- 添加 meta/ 包元数据文件
This commit is contained in:
root
2026-07-18 20:47:17 +08:00
commit 1762f0c34b
62 changed files with 9454 additions and 0 deletions

274
example/memo/plugin.go Normal file
View File

@ -0,0 +1,274 @@
package main
import (
"encoding/json"
"fmt"
"log"
"os"
"path/filepath"
"strings"
"sync"
"time"
"gitcode.com/JianFeeeee/homeagent-sdk/sdk"
)
type Memo struct {
ID int64 `json:"id"`
Content string `json:"content"`
CreatedAt int64 `json:"created_at"`
Done bool `json:"done"`
}
type Plugin struct {
name string
sdk *sdk.PluginSDK
mu sync.RWMutex
memos []Memo
nextID int64
filePath string
stopCh chan struct{}
tp string
}
func (p *Plugin) Name() string { return p.name }
func (p *Plugin) Start(s *sdk.PluginSDK) error {
s.SetAutoRestart(true)
p.sdk = s
p.tp = p.name + "_"
p.stopCh = make(chan struct{})
dataDirVal, err := s.Settings().GetCore("core.daemon.data_dir")
if err != nil || dataDirVal == "" {
dataDirVal = "."
}
p.filePath = filepath.Join(fmt.Sprint(dataDirVal), "memos.json")
p.load()
s.RegisterTool(p.tp+"create", sdk.ToolDef{
Name: p.tp + "create",
Description: "创建一条备忘条目。备忘内容应包含具体事项的完整描述。",
Parameters: map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"content": map[string]interface{}{"type": "string", "description": "备忘内容"},
},
"required": []string{"content"},
},
}, p.handleCreate)
s.RegisterTool(p.tp+"complete", sdk.ToolDef{
Name: p.tp + "complete",
Description: "将指定ID的备忘标记为已完成。",
Parameters: map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"id": map[string]interface{}{"type": "integer", "description": "备忘ID"},
},
"required": []string{"id"},
},
}, p.handleComplete)
s.RegisterTool(p.tp+"list", sdk.ToolDef{
Name: p.tp + "list",
Description: "列出所有未完成的备忘条目包含ID、内容和创建时间。",
Parameters: map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{},
},
}, p.handleList)
s.RegisterStage(sdk.StagePreAction, p.stagePreAction)
go p.periodicCheck()
log.Printf("[%s] started, path=%s", p.name, p.filePath)
return nil
}
func (p *Plugin) Stop() error {
close(p.stopCh)
p.save()
log.Printf("[%s] stopped", p.name)
return nil
}
func (p *Plugin) load() {
p.mu.Lock()
defer p.mu.Unlock()
data, err := os.ReadFile(p.filePath)
if err != nil {
p.memos = nil
p.nextID = 1
return
}
var store struct {
Memos []Memo `json:"memos"`
NextID int64 `json:"next_id"`
}
if json.Unmarshal(data, &store) != nil {
p.memos = nil
p.nextID = 1
return
}
p.memos = store.Memos
p.nextID = store.NextID
if p.memos == nil {
p.memos = []Memo{}
}
if p.nextID < 1 {
p.nextID = 1
}
}
func (p *Plugin) save() {
data, _ := json.MarshalIndent(map[string]interface{}{
"memos": p.memos,
"next_id": p.nextID,
}, "", " ")
os.WriteFile(p.filePath, data, 0644)
}
func (p *Plugin) pendingCount() int {
p.mu.RLock()
defer p.mu.RUnlock()
n := 0
for _, m := range p.memos {
if !m.Done {
n++
}
}
return n
}
func (p *Plugin) pendingMemos() []Memo {
p.mu.RLock()
defer p.mu.RUnlock()
var out []Memo
for _, m := range p.memos {
if !m.Done {
out = append(out, m)
}
}
return out
}
func (p *Plugin) stagePreAction(ctx *sdk.StageContext) error {
n := p.pendingCount()
if n == 0 {
return nil
}
ctx.Lock()
ctx.ContextMsgs = append(ctx.ContextMsgs, map[string]interface{}{
"role": "system",
"content": fmt.Sprintf("目前有%d条备忘未完成调用%slist工具读取具体内容", n, p.tp),
})
ctx.Unlock()
return nil
}
func (p *Plugin) periodicCheck() {
ticker := time.NewTicker(5 * time.Minute)
defer ticker.Stop()
for {
select {
case <-p.stopCh:
return
case <-ticker.C:
n := p.pendingCount()
if n == 0 {
continue
}
if p.sdk != nil {
p.sdk.InjectInterruptText(p.name, p.name,
fmt.Sprintf("注意,你还有%d条备忘未标记完成请检查", n))
}
}
}
}
func (p *Plugin) handleCreate(args map[string]interface{}) (interface{}, error) {
content, _ := args["content"].(string)
if content == "" {
return errorResult("content is required"), nil
}
p.mu.Lock()
memo := Memo{
ID: p.nextID,
Content: content,
CreatedAt: time.Now().Unix(),
Done: false,
}
p.nextID++
p.memos = append(p.memos, memo)
p.mu.Unlock()
p.save()
return map[string]interface{}{
"content": fmt.Sprintf("备忘已创建 (ID: %d)", memo.ID),
"id": memo.ID,
}, nil
}
func (p *Plugin) handleComplete(args map[string]interface{}) (interface{}, error) {
id, ok := args["id"].(float64)
if !ok {
return errorResult("id is required"), nil
}
p.mu.Lock()
found := false
for i := range p.memos {
if p.memos[i].ID == int64(id) && !p.memos[i].Done {
p.memos[i].Done = true
found = true
break
}
}
p.mu.Unlock()
if !found {
return errorResult(fmt.Sprintf("未找到未完成的备忘 ID: %d", int64(id))), nil
}
p.save()
return map[string]interface{}{
"content": fmt.Sprintf("备忘 %d 已标记为完成", int64(id)),
}, nil
}
func (p *Plugin) handleList(args map[string]interface{}) (interface{}, error) {
memos := p.pendingMemos()
if len(memos) == 0 {
return map[string]interface{}{
"content": "暂无未完成的备忘",
}, nil
}
var sb strings.Builder
for i, m := range memos {
t := time.Unix(m.CreatedAt, 0).Format("01-02 15:04")
if i > 0 {
sb.WriteString("\n")
}
sb.WriteString(fmt.Sprintf("%d. [ID:%d] %s — %s", i+1, m.ID, m.Content, t))
}
return map[string]interface{}{
"content": sb.String(),
"count": len(memos),
}, nil
}
func errorResult(msg string) map[string]interface{} {
return map[string]interface{}{
"isError": true,
"content": msg,
}
}
func NewPlugin(name string, config map[string]interface{}) (sdk.Plugin, error) {
return &Plugin{name: name}, nil
}