memento-mcp 0.2.0 → 0.2.2
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/CHANGELOG.md +7 -0
- package/package.json +1 -1
- package/scripts/README.md +19 -2
- package/scripts/memento-precompact-distill.sh +113 -14
- package/scripts/memento-stop-recall.sh +2 -1
- package/scripts/memento-userprompt-recall.sh +2 -1
- package/src/config.js +4 -1
- package/src/index.js +109 -4
- package/src/storage/hosted.js +25 -2
- package/src/storage/interface.js +29 -0
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,13 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Version
|
|
|
8
8
|
|
|
9
9
|
## [Unreleased]
|
|
10
10
|
|
|
11
|
+
### Added
|
|
12
|
+
- `precompact-distill` hook supports `model` config option in `.memento.json`: `"llama"` (default, free via Cloudflare Workers AI) or `"claude-code"` (runs `claude -p` locally, better extraction quality, uses API credits).
|
|
13
|
+
- `/v1/context` memory matches now include `created_at` timestamp — enables contradiction resolution and temporal reasoning when comparing recalled memories.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
- `source:distill` tag renamed to `source:distill:llama-3.1-8b` to encode model provenance. The `claude-code` path tags memories `source:distill:claude-code`.
|
|
17
|
+
|
|
11
18
|
---
|
|
12
19
|
|
|
13
20
|
## [0.2.0] - 2026-02-20
|
package/package.json
CHANGED
package/scripts/README.md
CHANGED
|
@@ -107,12 +107,29 @@ Fires after every assistant response. Uses the assistant's own output as the rec
|
|
|
107
107
|
|
|
108
108
|
### `memento-precompact-distill.sh` — PreCompact
|
|
109
109
|
|
|
110
|
-
Fires before Claude Code compresses the conversation. Parses the full JSONL transcript and
|
|
110
|
+
Fires before Claude Code compresses the conversation. Parses the full JSONL transcript and extracts novel facts, decisions, and observations as stored memories. Supports two extraction backends:
|
|
111
111
|
|
|
112
|
-
- **
|
|
112
|
+
- **`"llama"` (default)** — sends transcript to `/v1/distill`, which runs Llama 3.1 8B via Cloudflare Workers AI. Free.
|
|
113
|
+
- **`"claude-code"`** — runs `claude -p` locally for better extraction quality, then pushes to `/v1/memories/ingest`. Uses API credits.
|
|
114
|
+
|
|
115
|
+
Configure the model in `.memento.json`:
|
|
116
|
+
|
|
117
|
+
```json
|
|
118
|
+
{
|
|
119
|
+
"hooks": {
|
|
120
|
+
"precompact-distill": {
|
|
121
|
+
"enabled": true,
|
|
122
|
+
"model": "claude-code"
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
- **Timeout:** 30s (llama) / 60s (claude-code)
|
|
113
129
|
- **User sees:** "Memento Distill: extracted N memories"
|
|
114
130
|
- **Minimum threshold:** Transcripts under 200 characters are skipped
|
|
115
131
|
- **Transcript parsing:** Extracts user and assistant text from the JSONL format
|
|
132
|
+
- **Source tag:** `source:distill:llama-3.1-8b` or `source:distill:claude-code` — identifies which model extracted each memory
|
|
116
133
|
|
|
117
134
|
**Output format:** JSON with `systemMessage` only (informational).
|
|
118
135
|
|
|
@@ -54,6 +54,7 @@ print('true' if hook.get('enabled', True) else 'false')
|
|
|
54
54
|
MEMENTO_API_KEY="${MEMENTO_API_KEY:-$(echo "$CONFIG_JSON" | python3 -c "import json,sys; print(json.load(sys.stdin).get('apiKey',''))" 2>/dev/null)}"
|
|
55
55
|
MEMENTO_API_URL="${MEMENTO_API_URL:-$(echo "$CONFIG_JSON" | python3 -c "import json,sys; print(json.load(sys.stdin).get('apiUrl',''))" 2>/dev/null)}"
|
|
56
56
|
MEMENTO_WORKSPACE="${MEMENTO_WORKSPACE:-$(echo "$CONFIG_JSON" | python3 -c "import json,sys; print(json.load(sys.stdin).get('workspace',''))" 2>/dev/null)}"
|
|
57
|
+
DISTILL_MODEL="${DISTILL_MODEL:-$(echo "$CONFIG_JSON" | python3 -c "import json,sys; print(json.load(sys.stdin).get('hooks',{}).get('precompact-distill',{}).get('model','llama'))" 2>/dev/null)}"
|
|
57
58
|
fi
|
|
58
59
|
# --- End config block ---
|
|
59
60
|
|
|
@@ -81,6 +82,99 @@ if [ ${#TRANSCRIPT_TEXT} -lt 200 ]; then
|
|
|
81
82
|
exit 0 # Too short to distill anything useful
|
|
82
83
|
fi
|
|
83
84
|
|
|
85
|
+
DISTILL_MODEL="${DISTILL_MODEL:-llama}"
|
|
86
|
+
|
|
87
|
+
# claude-code path: run extraction locally via claude -p, push to /v1/memories/ingest
|
|
88
|
+
if [ "$DISTILL_MODEL" = "claude-code" ]; then
|
|
89
|
+
PROMPT_FILE=$(mktemp)
|
|
90
|
+
cat > "$PROMPT_FILE" << 'DISTILL_PROMPT'
|
|
91
|
+
You are a memory extraction system. Read the conversation transcript below and extract discrete memories worth remembering long-term.
|
|
92
|
+
|
|
93
|
+
Rules:
|
|
94
|
+
- Extract ONLY genuinely new information — facts, decisions, preferences, instructions, observations, or insights.
|
|
95
|
+
- Each memory should be a single, self-contained statement.
|
|
96
|
+
- Each memory needs a type: "fact", "decision", "instruction", "observation", or "preference".
|
|
97
|
+
- Each memory needs tags (max 6, lowercase, hyphenated). Do NOT include a source tag.
|
|
98
|
+
- If the conversation is trivial, return an empty array.
|
|
99
|
+
- Return ONLY valid JSON — no markdown, no commentary, no code fences.
|
|
100
|
+
|
|
101
|
+
Memory writing style:
|
|
102
|
+
- Lead with the most searchable term: entity names, project names, specific identifiers.
|
|
103
|
+
- Preserve exact values verbatim: IDs, amounts, measurements, dates.
|
|
104
|
+
- Use direct active phrasing.
|
|
105
|
+
|
|
106
|
+
Output format:
|
|
107
|
+
[{"content": "...", "type": "fact", "tags": ["tag1", "tag2"]}]
|
|
108
|
+
|
|
109
|
+
If nothing novel to extract, return: []
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
TRANSCRIPT:
|
|
113
|
+
DISTILL_PROMPT
|
|
114
|
+
echo "$TRANSCRIPT_TEXT" >> "$PROMPT_FILE"
|
|
115
|
+
|
|
116
|
+
RAW_OUTPUT=$(claude -p "$(cat "$PROMPT_FILE")" 2>/dev/null)
|
|
117
|
+
rm -f "$PROMPT_FILE"
|
|
118
|
+
|
|
119
|
+
CC_SUMMARY=$(echo "$RAW_OUTPUT" | python3 -c "
|
|
120
|
+
import json, sys, re
|
|
121
|
+
from collections import Counter
|
|
122
|
+
raw = sys.stdin.read()
|
|
123
|
+
cleaned = re.sub(r'^\x60{3}(?:json)?\s*', '', raw.strip(), flags=re.IGNORECASE)
|
|
124
|
+
cleaned = re.sub(r'\s*\x60{3}$', '', cleaned.strip())
|
|
125
|
+
try:
|
|
126
|
+
parsed = json.loads(cleaned.strip())
|
|
127
|
+
except Exception:
|
|
128
|
+
match = re.search(r'\[[\s\S]*\]', raw)
|
|
129
|
+
try:
|
|
130
|
+
parsed = json.loads(match.group(0)) if match else []
|
|
131
|
+
except Exception:
|
|
132
|
+
parsed = []
|
|
133
|
+
if not isinstance(parsed, list):
|
|
134
|
+
parsed = []
|
|
135
|
+
count = len(parsed)
|
|
136
|
+
if count == 0:
|
|
137
|
+
print('0|')
|
|
138
|
+
else:
|
|
139
|
+
type_counts = Counter(m.get('type', 'unknown') for m in parsed)
|
|
140
|
+
breakdown = ', '.join(f\"{v} {k}{'s' if v != 1 else ''}\" for k, v in sorted(type_counts.items()))
|
|
141
|
+
print(f'{count}|{breakdown}')
|
|
142
|
+
" 2>/dev/null)
|
|
143
|
+
|
|
144
|
+
MEMORY_COUNT=$(echo "$CC_SUMMARY" | cut -d'|' -f1)
|
|
145
|
+
TYPE_BREAKDOWN=$(echo "$CC_SUMMARY" | cut -d'|' -f2)
|
|
146
|
+
|
|
147
|
+
if [ "${MEMORY_COUNT:-0}" -gt 0 ] 2>/dev/null; then
|
|
148
|
+
INGEST_PAYLOAD=$(echo "$RAW_OUTPUT" | python3 -c "
|
|
149
|
+
import json, sys, re
|
|
150
|
+
raw = sys.stdin.read()
|
|
151
|
+
cleaned = re.sub(r'^\x60{3}(?:json)?\s*', '', raw.strip(), flags=re.IGNORECASE)
|
|
152
|
+
cleaned = re.sub(r'\s*\x60{3}$', '', cleaned.strip())
|
|
153
|
+
try:
|
|
154
|
+
parsed = json.loads(cleaned.strip())
|
|
155
|
+
except Exception:
|
|
156
|
+
match = re.search(r'\[[\s\S]*\]', raw)
|
|
157
|
+
parsed = json.loads(match.group(0)) if match else []
|
|
158
|
+
print(json.dumps({'memories': parsed, 'source': 'distill:claude-code'}))
|
|
159
|
+
" 2>/dev/null)
|
|
160
|
+
|
|
161
|
+
curl -s --max-time 60 \
|
|
162
|
+
-X POST \
|
|
163
|
+
-H "Authorization: Bearer $MEMENTO_KEY" \
|
|
164
|
+
-H "X-Memento-Workspace: $MEMENTO_WS" \
|
|
165
|
+
-H "Content-Type: application/json" \
|
|
166
|
+
-d "$INGEST_PAYLOAD" \
|
|
167
|
+
"$MEMENTO_API/v1/memories/ingest" > /dev/null 2>&1
|
|
168
|
+
|
|
169
|
+
python3 -c "import json,sys; print(json.dumps({'systemMessage': sys.argv[1]}))" \
|
|
170
|
+
"Memento Distill (claude-code): ${MEMORY_COUNT} memories — ${TYPE_BREAKDOWN}"
|
|
171
|
+
else
|
|
172
|
+
python3 -c "import json,sys; print(json.dumps({'systemMessage': sys.argv[1]}))" \
|
|
173
|
+
"Memento Distill (claude-code): no memories extracted"
|
|
174
|
+
fi
|
|
175
|
+
exit 0
|
|
176
|
+
fi
|
|
177
|
+
|
|
84
178
|
# Send to Memento SaaS /v1/distill
|
|
85
179
|
RESPONSE=$(curl -s --max-time 30 \
|
|
86
180
|
-X POST \
|
|
@@ -91,27 +185,32 @@ RESPONSE=$(curl -s --max-time 30 \
|
|
|
91
185
|
"$MEMENTO_API/v1/distill" 2>/dev/null)
|
|
92
186
|
|
|
93
187
|
# Report results
|
|
94
|
-
|
|
188
|
+
DISTILL_SUMMARY=$(echo "$RESPONSE" | python3 -c "
|
|
95
189
|
import json, sys
|
|
190
|
+
from collections import Counter
|
|
96
191
|
try:
|
|
97
192
|
data = json.load(sys.stdin)
|
|
98
|
-
|
|
193
|
+
memories = data.get('memories', [])
|
|
194
|
+
count = len(memories)
|
|
195
|
+
if count == 0:
|
|
196
|
+
print('0|')
|
|
197
|
+
else:
|
|
198
|
+
type_counts = Counter(m.get('type', 'unknown') for m in memories)
|
|
199
|
+
breakdown = ', '.join(f\"{v} {k}{'s' if v != 1 else ''}\" for k, v in sorted(type_counts.items()))
|
|
200
|
+
print(f'{count}|{breakdown}')
|
|
99
201
|
except Exception:
|
|
100
|
-
print('0')
|
|
202
|
+
print('0|')
|
|
101
203
|
" 2>/dev/null)
|
|
102
204
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
print(json.dumps({'systemMessage':
|
|
108
|
-
"
|
|
205
|
+
MEMORY_COUNT=$(echo "$DISTILL_SUMMARY" | cut -d'|' -f1)
|
|
206
|
+
TYPE_BREAKDOWN=$(echo "$DISTILL_SUMMARY" | cut -d'|' -f2)
|
|
207
|
+
|
|
208
|
+
if [ "${MEMORY_COUNT:-0}" -gt 0 ] 2>/dev/null; then
|
|
209
|
+
python3 -c "import json,sys; print(json.dumps({'systemMessage': sys.argv[1]}))" \
|
|
210
|
+
"Memento Distill: ${MEMORY_COUNT} memories — ${TYPE_BREAKDOWN}"
|
|
109
211
|
else
|
|
110
|
-
python3 -c "
|
|
111
|
-
|
|
112
|
-
msg = sys.argv[1]
|
|
113
|
-
print(json.dumps({'systemMessage': msg}))
|
|
114
|
-
" "Memento Distill: no memories extracted"
|
|
212
|
+
python3 -c "import json,sys; print(json.dumps({'systemMessage': sys.argv[1]}))" \
|
|
213
|
+
"Memento Distill: no memories extracted"
|
|
115
214
|
fi
|
|
116
215
|
|
|
117
216
|
exit 0
|
|
@@ -96,7 +96,8 @@ try:
|
|
|
96
96
|
tag_str = f' [{\", \".join(tags)}]' if tags else ''
|
|
97
97
|
content = m['content'][:${RECALL_MAX_LENGTH:-200}]
|
|
98
98
|
score = m.get('score', '?')
|
|
99
|
-
|
|
99
|
+
date_str = f' {m["created_at"][:10]}' if m.get('created_at') else ''
|
|
100
|
+
lines.append(f' {m[\"id\"]} ({m[\"type\"]}, {score}{date_str}){tag_str} — {content}')
|
|
100
101
|
count += 1
|
|
101
102
|
|
|
102
103
|
skip_matches = data.get('skip_matches', [])
|
|
@@ -89,7 +89,8 @@ try:
|
|
|
89
89
|
tag_str = f' [{\", \".join(tags)}]' if tags else ''
|
|
90
90
|
content = m['content'][:${RECALL_MAX_LENGTH:-200}]
|
|
91
91
|
score = m.get('score', '?')
|
|
92
|
-
|
|
92
|
+
date_str = f' {m["created_at"][:10]}' if m.get('created_at') else ''
|
|
93
|
+
lines.append(f' {m[\"id\"]} ({m[\"type\"]}, {score}{date_str}){tag_str} — {content}')
|
|
93
94
|
count += 1
|
|
94
95
|
|
|
95
96
|
# Skip matches (WARNING format)
|
package/src/config.js
CHANGED
|
@@ -68,5 +68,8 @@ export function resolveConfig(startDir = process.cwd()) {
|
|
|
68
68
|
hooks[key] = { ...defaults, ...(fileConfig.hooks?.[key] || {}) };
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
|
|
71
|
+
// peek_workspaces: array of workspace names to include in recall/list queries
|
|
72
|
+
const peek_workspaces = fileConfig.peek_workspaces || [];
|
|
73
|
+
|
|
74
|
+
return { apiKey, apiUrl, workspace, features, hooks, peek_workspaces };
|
|
72
75
|
}
|
package/src/index.js
CHANGED
|
@@ -39,6 +39,7 @@ const storage = new HostedStorageAdapter({
|
|
|
39
39
|
apiKey: config.apiKey,
|
|
40
40
|
apiUrl: config.apiUrl,
|
|
41
41
|
workspace: config.workspace,
|
|
42
|
+
peekWorkspaces: config.peek_workspaces,
|
|
42
43
|
});
|
|
43
44
|
|
|
44
45
|
// ---------------------------------------------------------------------------
|
|
@@ -268,9 +269,10 @@ Results are ranked by relevance (keyword match + recency + access frequency). Ea
|
|
|
268
269
|
.optional()
|
|
269
270
|
.describe("Filter by type: fact, decision, observation, instruction"),
|
|
270
271
|
limit: z.number().optional().describe("Max results (default: 10)"),
|
|
272
|
+
peek_workspaces: z.array(z.string()).optional().describe("Peek into other workspaces (read-only). Returns their memories tagged with workspace name. Max 5."),
|
|
271
273
|
},
|
|
272
|
-
async ({ query, tags, type, limit }) => {
|
|
273
|
-
const result = await storage.recallMemories(null, { query, tags, type, limit });
|
|
274
|
+
async ({ query, tags, type, limit, peek_workspaces }) => {
|
|
275
|
+
const result = await storage.recallMemories(null, { query, tags, type, limit, peek_workspaces });
|
|
274
276
|
|
|
275
277
|
if (result._raw) {
|
|
276
278
|
return {
|
|
@@ -522,6 +524,108 @@ Use this before routine checks (news, weather, HN stories) to avoid re-reading t
|
|
|
522
524
|
}
|
|
523
525
|
);
|
|
524
526
|
|
|
527
|
+
// ---------------------------------------------------------------------------
|
|
528
|
+
// Tool: memento_skip_list
|
|
529
|
+
// ---------------------------------------------------------------------------
|
|
530
|
+
|
|
531
|
+
server.tool(
|
|
532
|
+
"memento_skip_list",
|
|
533
|
+
`List all skip list entries with their IDs. Use this to find entry IDs for removal with memento_skip_remove.
|
|
534
|
+
|
|
535
|
+
Auto-purges expired entries before returning results.`,
|
|
536
|
+
{},
|
|
537
|
+
async () => {
|
|
538
|
+
const result = await storage.listSkips(null);
|
|
539
|
+
|
|
540
|
+
if (result.error) {
|
|
541
|
+
return { content: [{ type: "text", text: result.error }], isError: true };
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
if (!result.entries || result.entries.length === 0) {
|
|
545
|
+
return {
|
|
546
|
+
content: [{ type: "text", text: "Skip list is empty." }],
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
const formatted = result.entries
|
|
551
|
+
.map((e) => `**${e.id}** "${e.item}"\n Reason: ${e.reason}\n Expires: ${e.expires_at}`)
|
|
552
|
+
.join("\n\n");
|
|
553
|
+
|
|
554
|
+
return {
|
|
555
|
+
content: [
|
|
556
|
+
{
|
|
557
|
+
type: "text",
|
|
558
|
+
text: `${result.total} skip list entr${result.total === 1 ? "y" : "ies"}:\n\n${formatted}`,
|
|
559
|
+
},
|
|
560
|
+
],
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
);
|
|
564
|
+
|
|
565
|
+
// ---------------------------------------------------------------------------
|
|
566
|
+
// Tool: memento_skip_remove
|
|
567
|
+
// ---------------------------------------------------------------------------
|
|
568
|
+
|
|
569
|
+
server.tool(
|
|
570
|
+
"memento_skip_remove",
|
|
571
|
+
`Remove a skip list entry by ID. Use memento_skip_list first to find the entry ID.
|
|
572
|
+
|
|
573
|
+
Use this when a skip condition has been resolved early or was added in error.`,
|
|
574
|
+
{
|
|
575
|
+
id: z.string().describe("Skip entry ID to remove"),
|
|
576
|
+
},
|
|
577
|
+
async ({ id }) => {
|
|
578
|
+
const result = await storage.deleteSkip(null, id);
|
|
579
|
+
|
|
580
|
+
if (result._raw) {
|
|
581
|
+
return {
|
|
582
|
+
content: [{ type: "text", text: result.text }],
|
|
583
|
+
...(result.isError ? { isError: true } : {}),
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
if (result.error) {
|
|
588
|
+
return { content: [{ type: "text", text: result.error }], isError: true };
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
return {
|
|
592
|
+
content: [{ type: "text", text: `Skip entry ${id} removed.` }],
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
);
|
|
596
|
+
|
|
597
|
+
// ---------------------------------------------------------------------------
|
|
598
|
+
// Tool: memento_memory_delete
|
|
599
|
+
// ---------------------------------------------------------------------------
|
|
600
|
+
|
|
601
|
+
server.tool(
|
|
602
|
+
"memento_memory_delete",
|
|
603
|
+
`Permanently delete a memory by ID. Prefer memento_consolidate over deletion for most cases — consolidation preserves history while sharpening recall.
|
|
604
|
+
|
|
605
|
+
Use deletion only for memories that are incorrect, contain errors, or should never have been stored.`,
|
|
606
|
+
{
|
|
607
|
+
id: z.string().describe("Memory ID to delete"),
|
|
608
|
+
},
|
|
609
|
+
async ({ id }) => {
|
|
610
|
+
const result = await storage.deleteMemory(null, id);
|
|
611
|
+
|
|
612
|
+
if (result._raw) {
|
|
613
|
+
return {
|
|
614
|
+
content: [{ type: "text", text: result.text }],
|
|
615
|
+
...(result.isError ? { isError: true } : {}),
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
if (result.error) {
|
|
620
|
+
return { content: [{ type: "text", text: result.error }], isError: true };
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
return {
|
|
624
|
+
content: [{ type: "text", text: `Memory ${id} deleted.` }],
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
);
|
|
628
|
+
|
|
525
629
|
// ---------------------------------------------------------------------------
|
|
526
630
|
// Tool: memento_health
|
|
527
631
|
// ---------------------------------------------------------------------------
|
|
@@ -710,9 +814,10 @@ server.tool(
|
|
|
710
814
|
.optional()
|
|
711
815
|
.describe("Filter by status"),
|
|
712
816
|
query: z.string().optional().describe("Search title and content"),
|
|
817
|
+
peek_workspaces: z.array(z.string()).optional().describe("Peek into other workspaces (read-only). Returns their items tagged with workspace name. Max 5."),
|
|
713
818
|
},
|
|
714
|
-
async ({ category, status, query }) => {
|
|
715
|
-
const result = await storage.listItems(null, { category, status, query });
|
|
819
|
+
async ({ category, status, query, peek_workspaces }) => {
|
|
820
|
+
const result = await storage.listItems(null, { category, status, query, peek_workspaces });
|
|
716
821
|
|
|
717
822
|
if (result.error) {
|
|
718
823
|
return { content: [{ type: "text", text: result.error }], isError: true };
|
package/src/storage/hosted.js
CHANGED
|
@@ -17,11 +17,12 @@
|
|
|
17
17
|
import { StorageInterface } from "./interface.js";
|
|
18
18
|
|
|
19
19
|
export class HostedStorageAdapter extends StorageInterface {
|
|
20
|
-
constructor({ apiKey, apiUrl, workspace }) {
|
|
20
|
+
constructor({ apiKey, apiUrl, workspace, peekWorkspaces }) {
|
|
21
21
|
super();
|
|
22
22
|
this.apiKey = apiKey;
|
|
23
23
|
this.apiUrl = apiUrl.replace(/\/$/, ""); // strip trailing slash
|
|
24
24
|
this.workspace = workspace || "default";
|
|
25
|
+
this.peekWorkspaces = peekWorkspaces || [];
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
/**
|
|
@@ -108,11 +109,13 @@ export class HostedStorageAdapter extends StorageInterface {
|
|
|
108
109
|
return { _raw: true, text, isError: false };
|
|
109
110
|
}
|
|
110
111
|
|
|
111
|
-
async recallMemories(_wsPath, { query, tags, type, limit }) {
|
|
112
|
+
async recallMemories(_wsPath, { query, tags, type, limit, peek_workspaces }) {
|
|
112
113
|
const params = new URLSearchParams({ query, format: "json" });
|
|
113
114
|
if (tags?.length) params.set("tags", tags.join(","));
|
|
114
115
|
if (type) params.set("type", type);
|
|
115
116
|
if (limit) params.set("limit", String(limit));
|
|
117
|
+
const peek = peek_workspaces?.length ? peek_workspaces : this.peekWorkspaces;
|
|
118
|
+
if (peek?.length) params.set("peek_workspaces", peek.join(","));
|
|
116
119
|
|
|
117
120
|
const json = await this._fetchJson(
|
|
118
121
|
"GET",
|
|
@@ -149,6 +152,24 @@ export class HostedStorageAdapter extends StorageInterface {
|
|
|
149
152
|
return { _raw: true, text, isError: false };
|
|
150
153
|
}
|
|
151
154
|
|
|
155
|
+
async listSkips(_wsPath) {
|
|
156
|
+
const json = await this._fetchJson("GET", "/v1/skip-list");
|
|
157
|
+
if (json.error) return { error: json.error };
|
|
158
|
+
return json;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async deleteSkip(_wsPath, id) {
|
|
162
|
+
const { text, isError } = await this._fetch("DELETE", `/v1/skip-list/${id}`);
|
|
163
|
+
if (isError) return { error: text };
|
|
164
|
+
return { _raw: true, text, isError: false };
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async deleteMemory(_wsPath, id) {
|
|
168
|
+
const { text, isError } = await this._fetch("DELETE", `/v1/memories/${id}`);
|
|
169
|
+
if (isError) return { error: text };
|
|
170
|
+
return { _raw: true, text, isError: false };
|
|
171
|
+
}
|
|
172
|
+
|
|
152
173
|
async checkSkip(_wsPath, query) {
|
|
153
174
|
const params = new URLSearchParams({ query });
|
|
154
175
|
const { text, isError } = await this._fetch(
|
|
@@ -188,6 +209,8 @@ export class HostedStorageAdapter extends StorageInterface {
|
|
|
188
209
|
if (filters.category) params.set("category", filters.category);
|
|
189
210
|
if (filters.status) params.set("status", filters.status);
|
|
190
211
|
if (filters.query) params.set("q", filters.query);
|
|
212
|
+
const peek = filters.peek_workspaces?.length ? filters.peek_workspaces : this.peekWorkspaces;
|
|
213
|
+
if (peek?.length) params.set("peek_workspaces", peek.join(","));
|
|
191
214
|
const qs = params.toString();
|
|
192
215
|
const path = `/v1/working-memory/items${qs ? `?${qs}` : ""}`;
|
|
193
216
|
const res = await this._fetchJson("GET", path);
|
package/src/storage/interface.js
CHANGED
|
@@ -79,6 +79,35 @@ export class StorageInterface {
|
|
|
79
79
|
throw new Error("Not implemented");
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
+
/**
|
|
83
|
+
* List all skip list entries with IDs. Auto-purges expired entries.
|
|
84
|
+
* @param {string} wsPath - Resolved workspace path
|
|
85
|
+
* @returns {Promise<{ entries?: Array, total?: number, error?: string }>}
|
|
86
|
+
*/
|
|
87
|
+
async listSkips(wsPath) {
|
|
88
|
+
throw new Error("Not implemented");
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Remove a skip list entry by ID.
|
|
93
|
+
* @param {string} wsPath - Resolved workspace path
|
|
94
|
+
* @param {string} id - Skip entry ID
|
|
95
|
+
* @returns {Promise<{ _raw?: boolean, text?: string, error?: string }>}
|
|
96
|
+
*/
|
|
97
|
+
async deleteSkip(wsPath, id) {
|
|
98
|
+
throw new Error("Not implemented");
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Permanently delete a memory by ID.
|
|
103
|
+
* @param {string} wsPath - Resolved workspace path
|
|
104
|
+
* @param {string} id - Memory ID
|
|
105
|
+
* @returns {Promise<{ _raw?: boolean, text?: string, error?: string }>}
|
|
106
|
+
*/
|
|
107
|
+
async deleteMemory(wsPath, id) {
|
|
108
|
+
throw new Error("Not implemented");
|
|
109
|
+
}
|
|
110
|
+
|
|
82
111
|
/**
|
|
83
112
|
* Report memory system health and stats.
|
|
84
113
|
* @param {string} wsPath - Resolved workspace path
|