post inicial
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
package tools
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"sysagent/internal/memory"
|
||||
)
|
||||
|
||||
// rememberTool guarda conocimiento persistente en memoria.md (§21, §81).
|
||||
//
|
||||
// Existe como herramienta dedicada (en lugar de write_file) para que la
|
||||
// memoria mantenga una estructura estable y sin duplicados (§82): el modelo
|
||||
// indica sección/entrada/hechos y el programa decide si añadir o actualizar.
|
||||
type rememberTool struct{}
|
||||
|
||||
type rememberArgs struct {
|
||||
Section string `json:"section"`
|
||||
Entry string `json:"entry"`
|
||||
Facts []string `json:"facts"`
|
||||
Replace bool `json:"replace"`
|
||||
Sensitive bool `json:"sensitive"`
|
||||
}
|
||||
|
||||
func (rememberTool) Name() string { return "remember" }
|
||||
func (rememberTool) Description() string {
|
||||
return "Save durable knowledge to memoria.md (not a log): hosts, IPs, ports, config paths, how to restart a service, OS/kernel, special dependencies, tools created, important decisions. " +
|
||||
"Facts as \"Label: value\" update the same label instead of duplicating. Do not store passwords: store where they are kept (e.g. \"Password: secret-tool lookup service x\"); set sensitive=true only if keeping the secret itself is strictly necessary."
|
||||
}
|
||||
func (rememberTool) Schema() map[string]any {
|
||||
return obj([]string{"section", "entry", "facts"}, map[string]any{
|
||||
"section": str("top-level group, e.g. Hosts, Servicios, Sistema, Herramientas, Decisiones"),
|
||||
"entry": str("item inside the section, e.g. db01, nginx, kernel"),
|
||||
"facts": strList("facts, preferably \"Label: value\", e.g. [\"IP: 10.10.10.15\", \"Puerto: 5432\"]"),
|
||||
"replace": boolean("replace the whole entry instead of merging"),
|
||||
"sensitive": boolean("confirm that storing a credential is strictly necessary"),
|
||||
})
|
||||
}
|
||||
|
||||
func (rememberTool) Run(_ context.Context, env *Env, raw json.RawMessage) Result {
|
||||
var a rememberArgs
|
||||
if err := decode(raw, &a); err != nil {
|
||||
return fail("remember", err)
|
||||
}
|
||||
if env.Memory == nil {
|
||||
return fail("remember", errors.New("memory store not available"))
|
||||
}
|
||||
label := fmt.Sprintf("remember %s/%s", a.Section, a.Entry)
|
||||
if !a.Sensitive {
|
||||
for _, f := range a.Facts {
|
||||
if memory.LooksSecret(f) {
|
||||
return fail(label, fmt.Errorf("fact %q looks like a credential; store a reference to where it is kept, or set sensitive=true if keeping it is strictly necessary", clip(env.Log.Redact(f), 40)))
|
||||
}
|
||||
}
|
||||
}
|
||||
// La API key nunca se persiste (§58).
|
||||
facts := make([]string, len(a.Facts))
|
||||
for i, f := range a.Facts {
|
||||
facts[i] = env.Log.Redact(f)
|
||||
}
|
||||
if env.DryRun {
|
||||
return dryRun(fmt.Sprintf("store in memoria.md under %s/%s: %s", a.Section, a.Entry, strings.Join(facts, "; ")))
|
||||
}
|
||||
res, err := env.Memory.Upsert(a.Section, a.Entry, facts, a.Replace)
|
||||
if err != nil {
|
||||
return fail(label, err)
|
||||
}
|
||||
out := Result{OK: true, ExitCode: 0, Summary: label}
|
||||
var b strings.Builder
|
||||
switch {
|
||||
case !res.Changed() && !a.Replace:
|
||||
b.WriteString("nothing changed: all facts were already stored")
|
||||
out.Summary += " (unchanged)"
|
||||
default:
|
||||
out.FilesChanged = []string{env.Memory.Path()}
|
||||
if res.Created {
|
||||
b.WriteString("new entry created\n")
|
||||
}
|
||||
for _, f := range res.Added {
|
||||
fmt.Fprintf(&b, "added: %s\n", f)
|
||||
}
|
||||
for _, f := range res.Updated {
|
||||
fmt.Fprintf(&b, "updated: %s\n", f)
|
||||
}
|
||||
for _, f := range res.Existing {
|
||||
fmt.Fprintf(&b, "already present: %s\n", f)
|
||||
}
|
||||
}
|
||||
if a.Sensitive {
|
||||
out.Audit = []string{"sensitive", "true"}
|
||||
}
|
||||
out.Output = b.String()
|
||||
return out
|
||||
}
|
||||
|
||||
// forgetTool elimina conocimiento obsoleto o erróneo de memoria.md.
|
||||
type forgetTool struct{}
|
||||
|
||||
type forgetArgs struct {
|
||||
Section string `json:"section"`
|
||||
Entry string `json:"entry"`
|
||||
Reason string `json:"reason"`
|
||||
}
|
||||
|
||||
func (forgetTool) Name() string { return "forget" }
|
||||
func (forgetTool) Description() string {
|
||||
return "Remove an obsolete or wrong entry from memoria.md (or a whole section if entry is empty)."
|
||||
}
|
||||
func (forgetTool) Schema() map[string]any {
|
||||
return obj([]string{"section"}, map[string]any{
|
||||
"section": str("section name"),
|
||||
"entry": str("entry name; empty removes the whole section"),
|
||||
"reason": str("why it is no longer valid (audit log)"),
|
||||
})
|
||||
}
|
||||
|
||||
func (forgetTool) Run(_ context.Context, env *Env, raw json.RawMessage) Result {
|
||||
var a forgetArgs
|
||||
if err := decode(raw, &a); err != nil {
|
||||
return fail("forget", err)
|
||||
}
|
||||
if env.Memory == nil {
|
||||
return fail("forget", errors.New("memory store not available"))
|
||||
}
|
||||
label := fmt.Sprintf("forget %s/%s", a.Section, a.Entry)
|
||||
if env.DryRun {
|
||||
return dryRun(label)
|
||||
}
|
||||
ok, err := env.Memory.Forget(a.Section, a.Entry)
|
||||
if err != nil {
|
||||
return fail(label, err)
|
||||
}
|
||||
if !ok {
|
||||
return Result{OK: true, ExitCode: 0, Summary: label + " (not found)", Output: "not found; nothing removed"}
|
||||
}
|
||||
return Result{OK: true, ExitCode: 0, Summary: label, Output: "removed", FilesChanged: []string{env.Memory.Path()},
|
||||
Audit: []string{"reason", a.Reason}}
|
||||
}
|
||||
Reference in New Issue
Block a user