toon-memory 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +66 -0
- package/bin/setup.js +67 -0
- package/package.json +26 -0
- package/skills/toon-memory.md +68 -0
- package/src/memory.ts +244 -0
package/README.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# toon-memory
|
|
2
|
+
|
|
3
|
+
Persistent memory system for [OpenCode](https://opencode.ai) AI agent using TOON format.
|
|
4
|
+
|
|
5
|
+
**40% fewer tokens than JSON** for the same data. The agent remembers decisions, patterns, and bugs between sessions.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# In your project
|
|
11
|
+
npm install toon-memory @toon-format/toon
|
|
12
|
+
npx toon-memory
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Or globally:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install -g toon-memory @toon-format/toon
|
|
19
|
+
# Then in any project:
|
|
20
|
+
npx toon-memory
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## What it does
|
|
24
|
+
|
|
25
|
+
Copies 3 files to your project:
|
|
26
|
+
|
|
27
|
+
| File | Purpose |
|
|
28
|
+
|------|---------|
|
|
29
|
+
| `.opencode/tools/memory.ts` | 5 tools for the AI agent |
|
|
30
|
+
| `.opencode/memory/data.toon` | Your project's memory (TOON format) |
|
|
31
|
+
| `.opencode/skills/toon-memory.md` | Usage instructions |
|
|
32
|
+
|
|
33
|
+
## Tools
|
|
34
|
+
|
|
35
|
+
| Tool | Description |
|
|
36
|
+
|------|-------------|
|
|
37
|
+
| `memory_remember` | Save a decision, pattern, bug, or knowledge |
|
|
38
|
+
| `memory_recall` | Search memory (use BEFORE reading files) |
|
|
39
|
+
| `memory_forget` | Remove an entry by key or id |
|
|
40
|
+
| `memory_stats` | View memory state |
|
|
41
|
+
| `memory_summary` | Save/retrieve file summaries |
|
|
42
|
+
|
|
43
|
+
## How it works
|
|
44
|
+
|
|
45
|
+
The agent stores entries in TOON format (Token-Oriented Object Notation):
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
version: 1
|
|
49
|
+
entries[2|]{id|category|key|content|file|tags|date}:
|
|
50
|
+
a1b2c3d4|decision|use-pandas|Use pandas for time series|indicators.py|python;analytics|2026-07-10
|
|
51
|
+
e5f6g7h8|bug|redis-pool|max_connections missing|redis.adapter.ts|redis;pool|2026-07-10
|
|
52
|
+
summaries:
|
|
53
|
+
path/to/file.py: Brief summary of what this file does
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Token savings
|
|
57
|
+
|
|
58
|
+
| Format | Tokens (10 entries) | vs JSON |
|
|
59
|
+
|--------|---------------------|---------|
|
|
60
|
+
| JSON | ~800 | baseline |
|
|
61
|
+
| YAML | ~650 | -19% |
|
|
62
|
+
| **TOON** | **~480** | **-40%** |
|
|
63
|
+
|
|
64
|
+
## License
|
|
65
|
+
|
|
66
|
+
MIT
|
package/bin/setup.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { existsSync, mkdirSync, copyFileSync, readFileSync, writeFileSync } from "fs"
|
|
4
|
+
import { join, dirname } from "path"
|
|
5
|
+
import { fileURLToPath } from "url"
|
|
6
|
+
|
|
7
|
+
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
8
|
+
const PROJECT_ROOT = process.cwd()
|
|
9
|
+
const TOOLS_DIR = join(PROJECT_ROOT, ".opencode", "tools")
|
|
10
|
+
const MEMORY_DIR = join(PROJECT_ROOT, ".opencode", "memory")
|
|
11
|
+
const MEMORY_FILE = join(MEMORY_DIR, "data.toon")
|
|
12
|
+
const SKILL_DIR = join(PROJECT_ROOT, ".opencode", "skills")
|
|
13
|
+
const SKILL_FILE = join(SKILL_DIR, "toon-memory.md")
|
|
14
|
+
const PKG_DIR = join(__dirname, "..")
|
|
15
|
+
|
|
16
|
+
function ensureDir(dir) {
|
|
17
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true })
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function main() {
|
|
21
|
+
console.log("🧠toon-memory — Installing persistent memory for OpenCode...\n")
|
|
22
|
+
|
|
23
|
+
// 1. Copy tool file
|
|
24
|
+
ensureDir(TOOLS_DIR)
|
|
25
|
+
const toolSrc = join(PKG_DIR, "src", "memory.ts")
|
|
26
|
+
const toolDest = join(TOOLS_DIR, "memory.ts")
|
|
27
|
+
copyFileSync(toolSrc, toolDest)
|
|
28
|
+
console.log(` âś… Tool: ${toolDest}`)
|
|
29
|
+
|
|
30
|
+
// 2. Create memory directory and initial data file
|
|
31
|
+
ensureDir(MEMORY_DIR)
|
|
32
|
+
if (!existsSync(MEMORY_FILE)) {
|
|
33
|
+
writeFileSync(MEMORY_FILE, "version: 1\nentries[0|]:\nsummaries:\n", "utf-8")
|
|
34
|
+
console.log(` âś… Memory: ${MEMORY_FILE}`)
|
|
35
|
+
} else {
|
|
36
|
+
console.log(` âŹď¸Ź Memory already exists: ${MEMORY_FILE}`)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// 3. Copy SKILL.md
|
|
40
|
+
ensureDir(SKILL_DIR)
|
|
41
|
+
const skillSrc = join(PKG_DIR, "skills", "toon-memory.md")
|
|
42
|
+
if (existsSync(skillSrc)) {
|
|
43
|
+
copyFileSync(skillSrc, SKILL_FILE)
|
|
44
|
+
console.log(` âś… Skill: ${SKILL_FILE}`)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// 4. Check if @toon-format/toon is installed
|
|
48
|
+
const pkgPath = join(PROJECT_ROOT, "package.json")
|
|
49
|
+
if (existsSync(pkgPath)) {
|
|
50
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"))
|
|
51
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies }
|
|
52
|
+
if (!deps["@toon-format/toon"]) {
|
|
53
|
+
console.log("\n ⚠️ @toon-format/toon not found in dependencies.")
|
|
54
|
+
console.log(" Run: npm install @toon-format/toon")
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
console.log("\n🎉 Done! Restart OpenCode to load the memory tools.")
|
|
59
|
+
console.log("\nUsage:")
|
|
60
|
+
console.log(" memory_stats — See memory state")
|
|
61
|
+
console.log(" memory_remember — Save a decision/pattern/bug")
|
|
62
|
+
console.log(" memory_recall — Search memory before reading files")
|
|
63
|
+
console.log(" memory_forget — Remove an entry")
|
|
64
|
+
console.log(" memory_summary — Save/retrieve file summaries")
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
main()
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "toon-memory",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Persistent memory system for OpenCode AI agent using TOON format (40% fewer tokens than JSON)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"toon-memory": "./bin/setup.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"src/",
|
|
11
|
+
"bin/",
|
|
12
|
+
"skills/"
|
|
13
|
+
],
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@toon-format/toon": "^2.3.0"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"opencode",
|
|
19
|
+
"ai-agent",
|
|
20
|
+
"memory",
|
|
21
|
+
"toon",
|
|
22
|
+
"llm",
|
|
23
|
+
"token-efficiency"
|
|
24
|
+
],
|
|
25
|
+
"license": "MIT"
|
|
26
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# toon-memory — Persistent Memory for OpenCode
|
|
2
|
+
|
|
3
|
+
## What is this?
|
|
4
|
+
|
|
5
|
+
A persistent memory system for the OpenCode AI agent. It remembers decisions, patterns, bugs, and knowledge **between sessions** using TOON format (40% fewer tokens than JSON).
|
|
6
|
+
|
|
7
|
+
## Tools
|
|
8
|
+
|
|
9
|
+
| Tool | Description |
|
|
10
|
+
|------|-------------|
|
|
11
|
+
| `memory_remember` | Save a decision, pattern, bug, or knowledge |
|
|
12
|
+
| `memory_recall` | Search memory (use BEFORE reading files) |
|
|
13
|
+
| `memory_forget` | Remove an entry by key or id |
|
|
14
|
+
| `memory_stats` | View memory state |
|
|
15
|
+
| `memory_summary` | Save/retrieve file summaries |
|
|
16
|
+
|
|
17
|
+
## How to use
|
|
18
|
+
|
|
19
|
+
### At the START of every session
|
|
20
|
+
|
|
21
|
+
1. Run `memory_stats` to see what's in memory.
|
|
22
|
+
2. If the user asks something that might be in memory → `memory_recall(query)` BEFORE reading files.
|
|
23
|
+
|
|
24
|
+
### When making decisions
|
|
25
|
+
|
|
26
|
+
- Before implementing a non-trivial change → `memory_remember(category='decision')`
|
|
27
|
+
- When you resolve a complex bug → `memory_remember(category='bug')`
|
|
28
|
+
- When you observe a code pattern → `memory_remember(category='pattern')`
|
|
29
|
+
|
|
30
|
+
### When exploring the project
|
|
31
|
+
|
|
32
|
+
1. First: `memory_recall(query="what you need to know")`
|
|
33
|
+
2. If no result: use CodeGraph or grep
|
|
34
|
+
3. Only if both fail: read files directly
|
|
35
|
+
|
|
36
|
+
### At the END of every session
|
|
37
|
+
|
|
38
|
+
1. If you made design decisions → `memory_remember(category='decision')`
|
|
39
|
+
2. If you resolved a bug → `memory_remember(category='bug')`
|
|
40
|
+
3. If you observed a pattern → `memory_remember(category='pattern')`
|
|
41
|
+
|
|
42
|
+
## Categories
|
|
43
|
+
|
|
44
|
+
| Category | When | Example |
|
|
45
|
+
|----------|------|---------|
|
|
46
|
+
| `decision` | Non-trivial design decision | "Use pandas instead of numpy for time series" |
|
|
47
|
+
| `pattern` | Observed code pattern | "This project uses Pydantic v2 for configs" |
|
|
48
|
+
| `bug` | Complex bug resolved | "Redis pool exhaustion, missing max_connections" |
|
|
49
|
+
| `knowledge` | General project knowledge | "Broker uses RESP protocol, not Redis-specific" |
|
|
50
|
+
|
|
51
|
+
## File format
|
|
52
|
+
|
|
53
|
+
Data is stored in `.opencode/memory/data.toon` using TOON (Token-Oriented Object Notation):
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
version: 1
|
|
57
|
+
entries[2|]{id|category|key|content|file|tags|date}:
|
|
58
|
+
a1b2c3d4|decision|risk-priority|Risk Engine has priority|spec.md:145|risk;spec|2026-07-10
|
|
59
|
+
e5f6g7h8|pattern|pandas-over-numpy|Analytics uses pandas|indicators.py|python;analytics|2026-07-10
|
|
60
|
+
summaries:
|
|
61
|
+
path/to/big-file.py: Brief summary of what this file does
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Anti-patterns
|
|
65
|
+
|
|
66
|
+
- Don't store trivia in memory (only important facts)
|
|
67
|
+
- Don't forget to save a decision before implementing
|
|
68
|
+
- Don't re-read files that already have a summary in `memory_summary`
|
package/src/memory.ts
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import { tool } from "@opencode-ai/plugin"
|
|
2
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs"
|
|
3
|
+
import * as path from "path"
|
|
4
|
+
import { encode, decode } from "@toon-format/toon"
|
|
5
|
+
import { randomUUID } from "crypto"
|
|
6
|
+
|
|
7
|
+
const MEMORY_DIR = path.join(process.cwd(), ".opencode", "memory")
|
|
8
|
+
const MEMORY_FILE = path.join(MEMORY_DIR, "data.toon")
|
|
9
|
+
|
|
10
|
+
interface MemoryEntry {
|
|
11
|
+
id: string
|
|
12
|
+
category: "decision" | "pattern" | "bug" | "knowledge"
|
|
13
|
+
key: string
|
|
14
|
+
content: string
|
|
15
|
+
file: string
|
|
16
|
+
tags: string
|
|
17
|
+
date: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface MemoryData {
|
|
21
|
+
version: number
|
|
22
|
+
entries: MemoryEntry[]
|
|
23
|
+
summaries: Record<string, string>
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function defaultData(): MemoryData {
|
|
27
|
+
return { version: 1, entries: [], summaries: {} }
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function ensureDir() {
|
|
31
|
+
if (!existsSync(MEMORY_DIR)) mkdirSync(MEMORY_DIR, { recursive: true })
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function readMemory(): MemoryData {
|
|
35
|
+
ensureDir()
|
|
36
|
+
if (!existsSync(MEMORY_FILE)) return defaultData()
|
|
37
|
+
try {
|
|
38
|
+
const raw = readFileSync(MEMORY_FILE, "utf-8")
|
|
39
|
+
const parsed = decode(raw) as MemoryData
|
|
40
|
+
if (!parsed || typeof parsed !== "object") return defaultData()
|
|
41
|
+
if (!Array.isArray(parsed.entries)) parsed.entries = []
|
|
42
|
+
if (!parsed.summaries || typeof parsed.summaries !== "object") parsed.summaries = {}
|
|
43
|
+
return parsed
|
|
44
|
+
} catch {
|
|
45
|
+
return defaultData()
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function writeMemory(data: MemoryData): void {
|
|
50
|
+
ensureDir()
|
|
51
|
+
const toon = encode(data, { delimiter: "|", indent: 2 })
|
|
52
|
+
writeFileSync(MEMORY_FILE, toon, "utf-8")
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function genId(): string {
|
|
56
|
+
return randomUUID().slice(0, 8)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export const memory_remember = tool({
|
|
60
|
+
description:
|
|
61
|
+
"Guarda un hecho en la memoria persistente del proyecto (decisiones, patrones, bugs, conocimiento). Se recuerda entre sesiones. Usar cuando el agente tome una decisiĂłn importante, resuelva un bug, o observe un patrĂłn.",
|
|
62
|
+
args: {
|
|
63
|
+
category: tool.schema
|
|
64
|
+
.enum(["decision", "pattern", "bug", "knowledge"])
|
|
65
|
+
.describe("CategorĂa: decision (diseño), pattern (cĂłdigo), bug (resoluciĂłn), knowledge (general)"),
|
|
66
|
+
key: tool.schema
|
|
67
|
+
.string()
|
|
68
|
+
.describe("TĂtulo corto en kebab-case (ej: risk-engine-prioridad)"),
|
|
69
|
+
content: tool.schema
|
|
70
|
+
.string()
|
|
71
|
+
.describe("Contenido detallado del hecho"),
|
|
72
|
+
file: tool.schema
|
|
73
|
+
.string()
|
|
74
|
+
.describe("Archivo o lĂnea relacionada (ej: spec.md:145)")
|
|
75
|
+
.default(""),
|
|
76
|
+
tags: tool.schema
|
|
77
|
+
.string()
|
|
78
|
+
.describe("Tags separados por punto y coma (ej: risk;spec)")
|
|
79
|
+
.default(""),
|
|
80
|
+
},
|
|
81
|
+
async execute({ category, key, content, file, tags }) {
|
|
82
|
+
const state = readMemory()
|
|
83
|
+
const existing = state.entries.find(
|
|
84
|
+
(e) => e.category === category && e.key === key,
|
|
85
|
+
)
|
|
86
|
+
const entry: MemoryEntry = {
|
|
87
|
+
id: existing?.id || genId(),
|
|
88
|
+
category,
|
|
89
|
+
key,
|
|
90
|
+
content,
|
|
91
|
+
file,
|
|
92
|
+
tags,
|
|
93
|
+
date: new Date().toISOString().slice(0, 10),
|
|
94
|
+
}
|
|
95
|
+
if (existing) {
|
|
96
|
+
const idx = state.entries.indexOf(existing)
|
|
97
|
+
state.entries[idx] = entry
|
|
98
|
+
} else {
|
|
99
|
+
state.entries.push(entry)
|
|
100
|
+
}
|
|
101
|
+
writeMemory(state)
|
|
102
|
+
return {
|
|
103
|
+
title: existing ? "đź§ Actualizado" : "đź§ Guardado",
|
|
104
|
+
output: `${category}/${key} (${entry.id})\n${content}`,
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
export const memory_recall = tool({
|
|
110
|
+
description:
|
|
111
|
+
"Busca en la memoria persistente del proyecto. Devuelve entradas relevantes por categorĂa, tag, o texto libre. Usar ANTES de leer archivos para evitar re-investigar.",
|
|
112
|
+
args: {
|
|
113
|
+
query: tool.schema
|
|
114
|
+
.string()
|
|
115
|
+
.describe("Texto a buscar (busca en key, content, tags)"),
|
|
116
|
+
category: tool.schema
|
|
117
|
+
.string()
|
|
118
|
+
.describe("Filtrar por categorĂa (decision|pattern|bug|knowledge). VacĂo = todos.")
|
|
119
|
+
.default(""),
|
|
120
|
+
},
|
|
121
|
+
async execute({ query, category }) {
|
|
122
|
+
const state = readMemory()
|
|
123
|
+
const q = query.toLowerCase()
|
|
124
|
+
let results = state.entries.filter((e) => {
|
|
125
|
+
const matchText =
|
|
126
|
+
e.key.toLowerCase().includes(q) ||
|
|
127
|
+
e.content.toLowerCase().includes(q) ||
|
|
128
|
+
e.tags.toLowerCase().includes(q)
|
|
129
|
+
const matchCat = category ? e.category === category : true
|
|
130
|
+
return matchText && matchCat
|
|
131
|
+
})
|
|
132
|
+
results = results.slice(0, 10)
|
|
133
|
+
if (results.length === 0) {
|
|
134
|
+
return {
|
|
135
|
+
title: "🔍 Sin resultados",
|
|
136
|
+
output: `No encontrĂ© nada para "${query}"${category ? ` en categorĂa "${category}"` : ""}.`,
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
const output = results
|
|
140
|
+
.map(
|
|
141
|
+
(e) =>
|
|
142
|
+
`[${e.category}] ${e.key} (${e.id})\n ${e.content}\n File: ${e.file || "N/A"} | Tags: ${e.tags || "N/A"} | Date: ${e.date}`,
|
|
143
|
+
)
|
|
144
|
+
.join("\n\n")
|
|
145
|
+
return { title: `🔍 ${results.length} resultados`, output }
|
|
146
|
+
},
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
export const memory_forget = tool({
|
|
150
|
+
description: "Elimina una entrada de la memoria por su key o id.",
|
|
151
|
+
args: {
|
|
152
|
+
key: tool.schema
|
|
153
|
+
.string()
|
|
154
|
+
.describe("Key o id de la entrada a eliminar"),
|
|
155
|
+
},
|
|
156
|
+
async execute({ key }) {
|
|
157
|
+
const state = readMemory()
|
|
158
|
+
const before = state.entries.length
|
|
159
|
+
state.entries = state.entries.filter(
|
|
160
|
+
(e) => e.key !== key && e.id !== key,
|
|
161
|
+
)
|
|
162
|
+
if (state.entries.length === before) {
|
|
163
|
+
return {
|
|
164
|
+
title: "⚠️ No encontrado",
|
|
165
|
+
output: `No encontré "${key}" en la memoria.`,
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
writeMemory(state)
|
|
169
|
+
return {
|
|
170
|
+
title: "🗑️ Eliminado",
|
|
171
|
+
output: `"${key}" eliminado. Quedan ${state.entries.length} entradas.`,
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
export const memory_stats = tool({
|
|
177
|
+
description:
|
|
178
|
+
"Muestra estadĂsticas de la memoria: total entradas, por categorĂa, y resĂşmenes de archivos.",
|
|
179
|
+
args: {},
|
|
180
|
+
async execute() {
|
|
181
|
+
const state = readMemory()
|
|
182
|
+
const byCategory: Record<string, number> = {}
|
|
183
|
+
for (const e of state.entries) {
|
|
184
|
+
byCategory[e.category] = (byCategory[e.category] || 0) + 1
|
|
185
|
+
}
|
|
186
|
+
const catStr = Object.entries(byCategory)
|
|
187
|
+
.map(([k, v]) => ` ${k}: ${v}`)
|
|
188
|
+
.join("\n")
|
|
189
|
+
const recent = state.entries
|
|
190
|
+
.slice(-5)
|
|
191
|
+
.reverse()
|
|
192
|
+
.map((e) => ` [${e.category}] ${e.key} (${e.date})`)
|
|
193
|
+
.join("\n")
|
|
194
|
+
return {
|
|
195
|
+
title: "📊 Memoria del proyecto",
|
|
196
|
+
output: [
|
|
197
|
+
`Entradas totales: ${state.entries.length}`,
|
|
198
|
+
`ResĂşmenes de archivos: ${Object.keys(state.summaries).length}`,
|
|
199
|
+
"",
|
|
200
|
+
"Por categorĂa:",
|
|
201
|
+
catStr || " (vacĂo)",
|
|
202
|
+
"",
|
|
203
|
+
"Ăšltimas 5 entradas:",
|
|
204
|
+
recent || " (ninguna)",
|
|
205
|
+
].join("\n"),
|
|
206
|
+
}
|
|
207
|
+
},
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
export const memory_summary = tool({
|
|
211
|
+
description:
|
|
212
|
+
"Guarda o recupera un resumen de un archivo grande. El agente puede leer el resumen en vez del archivo completo para ahorrar tokens.",
|
|
213
|
+
args: {
|
|
214
|
+
action: tool.schema
|
|
215
|
+
.enum(["get", "set"])
|
|
216
|
+
.describe("get para leer, set para guardar"),
|
|
217
|
+
file: tool.schema
|
|
218
|
+
.string()
|
|
219
|
+
.describe("Ruta del archivo"),
|
|
220
|
+
summary: tool.schema
|
|
221
|
+
.string()
|
|
222
|
+
.describe("Resumen del archivo (solo para set)")
|
|
223
|
+
.default(""),
|
|
224
|
+
},
|
|
225
|
+
async execute({ action, file, summary }) {
|
|
226
|
+
const state = readMemory()
|
|
227
|
+
if (action === "get") {
|
|
228
|
+
const s = state.summaries[file]
|
|
229
|
+
if (!s) {
|
|
230
|
+
return {
|
|
231
|
+
title: "đź“„ Sin resumen",
|
|
232
|
+
output: `No hay resumen para "${file}". Usar memory_summary action=set para guardar uno.`,
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return { title: `đź“„ ${file}`, output: s }
|
|
236
|
+
}
|
|
237
|
+
state.summaries[file] = summary
|
|
238
|
+
writeMemory(state)
|
|
239
|
+
return {
|
|
240
|
+
title: "đź“„ Resumen guardado",
|
|
241
|
+
output: `${file} actualizado.`,
|
|
242
|
+
}
|
|
243
|
+
},
|
|
244
|
+
})
|