Files
homeagent-sdk/example/memo/plugin.go
root 1762f0c34b docs: 修正全部文档使其与源码实现一致
- Plugin.Start(sdk *PluginSDK) 接口签名改为指针
- 方法表重写: 移除 CallLLM/QueryKnowledge/SetMemory 等不存在方法
- IOInjector 参数顺序修正为 (source, channel, text)
- 删除虚构 SDKConfig, 替换为实际 New() 构造函数签名
- .hmap 内容描述一致化 (plugin.so + plugin.dll + main.lua)
- 添加 meta/ 包元数据文件
2026-07-18 20:47:17 +08:00

275 lines
5.7 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}