Why: Allow consumers to download an immutable module revision without nested source checkouts or local path replacements. Evidence: SELF package tests and the prior canonical-path rejection are recorded in the Dev dependency-workspace task card; remote consumption is replayed against the committed candidate. Impact: Change the module identity and its Go imports while preserving API behavior. Existing historical submodule revisions remain buildable. Refs: Versioned dependencies U1/U3; feature/dependency-workspace
94 lines
3.3 KiB
Go
94 lines
3.3 KiB
Go
package memory
|
||
|
||
import (
|
||
"context"
|
||
"encoding/json"
|
||
"fmt"
|
||
"strings"
|
||
"time"
|
||
|
||
"git-ssh.u1680832.nyat.app/weichen/Agent-Native.git/runtime/tools"
|
||
)
|
||
|
||
// memory_write 工具:模型自服务写回(fail-closed 校验 scope/type/content)。
|
||
// 只在宿主配置 Store 时注册(RegisterWrite 由循环在 Run 启动时接线)。
|
||
type writeTool struct {
|
||
store Store
|
||
scope string // 默认 scope(AgentSpec.MemoryScope 驱动;空 = user)
|
||
}
|
||
|
||
var validScopes = map[string]bool{"user": true, "project": true}
|
||
var validTypes = map[string]bool{
|
||
"fact": true, "preference": true, "decision": true, "episode": true, "procedure": true,
|
||
}
|
||
|
||
// RegisterWrite 把 memory_write 注册进 reg(store nil 时跳过;重复注册按
|
||
// 覆盖处理——宿主不得在同一注册表上给不同 run 挂不同实现)。
|
||
func RegisterWrite(reg *tools.Registry, store Store, defaultScope string) {
|
||
if reg == nil || store == nil {
|
||
return
|
||
}
|
||
reg.Register(&writeTool{store: store, scope: defaultScope})
|
||
}
|
||
|
||
func (t *writeTool) Name() string { return "memory_write" }
|
||
func (t *writeTool) Description() string {
|
||
return "把一条值得跨会话记住的事实/偏好/决定写入长期记忆(scope: user 或 project;type: fact|preference|decision|episode|procedure)。"
|
||
}
|
||
func (t *writeTool) Parameters() json.RawMessage {
|
||
return json.RawMessage(`{"type":"object","required":["type","content"],"properties":{
|
||
"scope":{"type":"string"},
|
||
"type":{"type":"string"},
|
||
"content":{"type":"string"},
|
||
"expires_in_days":{"type":"number"}
|
||
}}`)
|
||
}
|
||
|
||
// maxWriteContent 是单条记忆的内容上限(字符)。
|
||
const maxWriteContent = 2000
|
||
|
||
func (t *writeTool) Execute(ctx context.Context, args json.RawMessage) (tools.Result, error) {
|
||
var a struct {
|
||
Scope string `json:"scope"`
|
||
Type string `json:"type"`
|
||
Content string `json:"content"`
|
||
ExpiresInDays float64 `json:"expires_in_days"`
|
||
}
|
||
if err := json.Unmarshal(args, &a); err != nil {
|
||
return tools.Result{Content: "memory_write: 参数不是完整 JSON 对象", IsError: true}, nil
|
||
}
|
||
scope := strings.TrimSpace(a.Scope)
|
||
if scope == "" {
|
||
scope = t.scope
|
||
}
|
||
if scope == "" {
|
||
scope = "user"
|
||
}
|
||
if !validScopes[scope] {
|
||
return tools.Result{Content: fmt.Sprintf("memory_write: scope %q 必须是 user 或 project", scope), IsError: true}, nil
|
||
}
|
||
typ := strings.TrimSpace(a.Type)
|
||
if !validTypes[typ] {
|
||
return tools.Result{Content: fmt.Sprintf("memory_write: type %q 必须是 fact|preference|decision|episode|procedure 之一", typ), IsError: true}, nil
|
||
}
|
||
content := strings.TrimSpace(a.Content)
|
||
if content == "" {
|
||
return tools.Result{Content: "memory_write: content 必填且非空", IsError: true}, nil
|
||
}
|
||
if len([]rune(content)) > maxWriteContent {
|
||
return tools.Result{Content: fmt.Sprintf("memory_write: content 超过 %d 字符上限", maxWriteContent), IsError: true}, nil
|
||
}
|
||
var expires *time.Time
|
||
if a.ExpiresInDays > 0 {
|
||
t := time.Now().Add(time.Duration(a.ExpiresInDays*24) * time.Hour)
|
||
expires = &t
|
||
}
|
||
if err := t.store.Append(ctx, Entry{
|
||
Scope: scope, Type: typ, Content: content,
|
||
Source: "agent", CreatedAt: time.Now(), ExpiresAt: expires,
|
||
}); err != nil {
|
||
return tools.Result{Content: "memory_write: " + err.Error(), IsError: true}, nil
|
||
}
|
||
return tools.Result{Content: fmt.Sprintf("已记住(scope=%s type=%s)", scope, typ)}, nil
|
||
}
|