@openanonymity/nanomem 0.1.0 → 0.1.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/LICENSE +21 -0
- package/README.md +64 -18
- package/package.json +7 -3
- package/src/backends/BaseStorage.js +147 -3
- package/src/backends/indexeddb.js +21 -8
- package/src/browser.js +227 -0
- package/src/bullets/parser.js +8 -9
- package/src/cli/auth.js +1 -1
- package/src/cli/commands.js +58 -9
- package/src/cli/config.js +1 -1
- package/src/cli/help.js +5 -2
- package/src/cli/output.js +4 -0
- package/src/cli.js +6 -3
- package/src/engine/compactor.js +3 -6
- package/src/engine/deleter.js +187 -0
- package/src/engine/executors.js +474 -11
- package/src/engine/ingester.js +98 -63
- package/src/engine/recentConversation.js +110 -0
- package/src/engine/retriever.js +243 -37
- package/src/engine/toolLoop.js +51 -9
- package/src/imports/chatgpt.js +1 -1
- package/src/imports/claude.js +85 -0
- package/src/imports/importData.js +462 -0
- package/src/imports/index.js +10 -0
- package/src/index.js +95 -2
- package/src/llm/openai.js +204 -58
- package/src/llm/tinfoil.js +508 -0
- package/src/omf.js +343 -0
- package/src/prompt_sets/conversation/ingestion.js +111 -12
- package/src/prompt_sets/document/ingestion.js +98 -4
- package/src/prompt_sets/index.js +12 -4
- package/src/types.js +135 -4
- package/src/vendor/tinfoil.browser.d.ts +2 -0
- package/src/vendor/tinfoil.browser.js +41596 -0
- package/types/backends/BaseStorage.d.ts +19 -0
- package/types/backends/indexeddb.d.ts +1 -0
- package/types/browser.d.ts +17 -0
- package/types/engine/deleter.d.ts +67 -0
- package/types/engine/executors.d.ts +56 -2
- package/types/engine/recentConversation.d.ts +18 -0
- package/types/engine/retriever.d.ts +22 -9
- package/types/imports/claude.d.ts +14 -0
- package/types/imports/importData.d.ts +29 -0
- package/types/imports/index.d.ts +2 -0
- package/types/index.d.ts +9 -0
- package/types/llm/openai.d.ts +6 -9
- package/types/llm/tinfoil.d.ts +13 -0
- package/types/omf.d.ts +40 -0
- package/types/prompt_sets/conversation/ingestion.d.ts +8 -3
- package/types/prompt_sets/document/ingestion.d.ts +8 -3
- package/types/types.d.ts +127 -2
- package/types/vendor/tinfoil.browser.d.ts +6348 -0
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MemoryDeleter — targeted bullet deletion via agentic tool-calling.
|
|
3
|
+
*
|
|
4
|
+
* Takes a plain-text query (e.g. "my job at Acme") and uses the LLM to find
|
|
5
|
+
* and delete only the matching bullets. Mirrors the retriever pattern but
|
|
6
|
+
* writes instead of reads.
|
|
7
|
+
*
|
|
8
|
+
* Two modes:
|
|
9
|
+
* default — LLM searches the index for relevant files, reads and deletes.
|
|
10
|
+
* deep — all files are enumerated upfront; LLM reads every one.
|
|
11
|
+
*/
|
|
12
|
+
/** @import { LLMClient, StorageBackend } from '../types.js' */
|
|
13
|
+
import { runAgenticToolLoop } from './toolLoop.js';
|
|
14
|
+
import { createDeletionExecutors } from './executors.js';
|
|
15
|
+
import { resolvePromptSet } from '../prompt_sets/index.js';
|
|
16
|
+
|
|
17
|
+
/** Tools used in default (index-guided) delete mode. */
|
|
18
|
+
const DELETION_TOOLS = [
|
|
19
|
+
{
|
|
20
|
+
type: 'function',
|
|
21
|
+
function: {
|
|
22
|
+
name: 'list_directory',
|
|
23
|
+
description: 'List all files and subdirectories in a directory.',
|
|
24
|
+
parameters: {
|
|
25
|
+
type: 'object',
|
|
26
|
+
properties: {
|
|
27
|
+
dir_path: { type: 'string', description: 'Directory path (e.g. "health", "personal", "work"). Use empty string for root.' }
|
|
28
|
+
},
|
|
29
|
+
required: ['dir_path']
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
type: 'function',
|
|
35
|
+
function: {
|
|
36
|
+
name: 'retrieve_file',
|
|
37
|
+
description: 'Search memory files by keyword. Returns paths of files whose content or path matches the query.',
|
|
38
|
+
parameters: {
|
|
39
|
+
type: 'object',
|
|
40
|
+
properties: {
|
|
41
|
+
query: { type: 'string', description: 'Keyword to search for in file contents.' }
|
|
42
|
+
},
|
|
43
|
+
required: ['query']
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
type: 'function',
|
|
49
|
+
function: {
|
|
50
|
+
name: 'read_file',
|
|
51
|
+
description: 'Read the full content of a memory file by its path.',
|
|
52
|
+
parameters: {
|
|
53
|
+
type: 'object',
|
|
54
|
+
properties: {
|
|
55
|
+
path: { type: 'string', description: 'File path to read (e.g. personal/about.md)' }
|
|
56
|
+
},
|
|
57
|
+
required: ['path']
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
type: 'function',
|
|
63
|
+
function: {
|
|
64
|
+
name: 'delete_bullet',
|
|
65
|
+
description: 'PERMANENTLY delete a specific bullet from a memory file. Use ONLY for bullets that are about the target subject. This cannot be undone. Pass the EXACT bullet text as it appears in the file, including all | metadata.',
|
|
66
|
+
parameters: {
|
|
67
|
+
type: 'object',
|
|
68
|
+
properties: {
|
|
69
|
+
path: { type: 'string', description: 'File path containing the bullet (e.g. personal/about.md)' },
|
|
70
|
+
bullet_text: { type: 'string', description: 'The EXACT text of the bullet to delete, as it appears in the file, including all | metadata.' }
|
|
71
|
+
},
|
|
72
|
+
required: ['path', 'bullet_text']
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
];
|
|
77
|
+
|
|
78
|
+
/** Tools used in deep delete mode — no discovery tools needed since all paths are listed upfront. */
|
|
79
|
+
const DEEP_DELETION_TOOLS = DELETION_TOOLS.filter(t =>
|
|
80
|
+
['read_file', 'delete_bullet'].includes(t.function.name)
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
export class MemoryDeleter {
|
|
84
|
+
/**
|
|
85
|
+
* @param {{ backend: StorageBackend, bulletIndex: object, llmClient: LLMClient, model: string, onToolCall?: Function }} options
|
|
86
|
+
*/
|
|
87
|
+
constructor({ backend, bulletIndex, llmClient, model, onToolCall }) {
|
|
88
|
+
this._backend = backend;
|
|
89
|
+
this._bulletIndex = bulletIndex;
|
|
90
|
+
this._llmClient = llmClient;
|
|
91
|
+
this._model = model;
|
|
92
|
+
this._onToolCall = onToolCall || null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Delete memory content matching the given query.
|
|
97
|
+
*
|
|
98
|
+
* @param {string} query Plain-text description of what to delete.
|
|
99
|
+
* @param {{ deep?: boolean, mode?: string }} [options]
|
|
100
|
+
* @returns {Promise<{ status: string, deleteCalls: number, writes: Array }>}
|
|
101
|
+
*/
|
|
102
|
+
async deleteForQuery(query, options = {}) {
|
|
103
|
+
if (!query || !query.trim()) {
|
|
104
|
+
return { status: 'skipped', deleteCalls: 0, writes: [] };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const isDocument = options.mode === 'document';
|
|
108
|
+
|
|
109
|
+
return options.deep
|
|
110
|
+
? this._deepDelete(query, isDocument)
|
|
111
|
+
: this._standardDelete(query, isDocument);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async _standardDelete(query, isDocument) {
|
|
115
|
+
await this._backend.init();
|
|
116
|
+
const index = await this._backend.getTree() || '';
|
|
117
|
+
|
|
118
|
+
const promptKey = isDocument ? 'document_delete' : 'delete';
|
|
119
|
+
const { ingestionPrompt } = resolvePromptSet(promptKey);
|
|
120
|
+
const systemPrompt = ingestionPrompt
|
|
121
|
+
.replace('{QUERY}', query)
|
|
122
|
+
.replace('{INDEX}', index);
|
|
123
|
+
|
|
124
|
+
return this._runDeletionLoop(query, systemPrompt, DELETION_TOOLS, 8);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async _deepDelete(query, isDocument) {
|
|
128
|
+
await this._backend.init();
|
|
129
|
+
|
|
130
|
+
const allFiles = await this._backend.exportAll();
|
|
131
|
+
const paths = allFiles
|
|
132
|
+
.map(f => f.path)
|
|
133
|
+
.filter(p => !p.endsWith('_tree.md'))
|
|
134
|
+
.sort();
|
|
135
|
+
|
|
136
|
+
if (paths.length === 0) {
|
|
137
|
+
return { status: 'skipped', deleteCalls: 0, writes: [] };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const fileList = paths.map(p => `- ${p}`).join('\n');
|
|
141
|
+
|
|
142
|
+
const promptKey = isDocument ? 'document_deep_delete' : 'deep_delete';
|
|
143
|
+
const { ingestionPrompt } = resolvePromptSet(promptKey);
|
|
144
|
+
const systemPrompt = ingestionPrompt
|
|
145
|
+
.replace('{QUERY}', query)
|
|
146
|
+
.replace('{FILE_LIST}', fileList);
|
|
147
|
+
|
|
148
|
+
// Each file needs a read + potentially multiple deletes; allow enough iterations.
|
|
149
|
+
const maxIterations = Math.max(30, paths.length * 3);
|
|
150
|
+
|
|
151
|
+
return this._runDeletionLoop(query, systemPrompt, DEEP_DELETION_TOOLS, maxIterations);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async _runDeletionLoop(query, systemPrompt, tools, maxIterations) {
|
|
155
|
+
const writes = [];
|
|
156
|
+
const onToolCall = this._onToolCall;
|
|
157
|
+
|
|
158
|
+
const toolExecutors = createDeletionExecutors(this._backend, {
|
|
159
|
+
refreshIndex: (path) => this._bulletIndex.refreshPath(path),
|
|
160
|
+
onWrite: (path, before, after) => writes.push({ path, before, after }),
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
try {
|
|
164
|
+
await runAgenticToolLoop({
|
|
165
|
+
llmClient: this._llmClient,
|
|
166
|
+
model: this._model,
|
|
167
|
+
tools,
|
|
168
|
+
toolExecutors,
|
|
169
|
+
messages: [
|
|
170
|
+
{ role: 'system', content: systemPrompt },
|
|
171
|
+
{ role: 'user', content: query }
|
|
172
|
+
],
|
|
173
|
+
maxIterations,
|
|
174
|
+
maxOutputTokens: 2000,
|
|
175
|
+
temperature: 0,
|
|
176
|
+
onToolCall: (name, args, result) => {
|
|
177
|
+
onToolCall?.(name, args, result);
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
} catch (error) {
|
|
181
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
182
|
+
return { status: 'error', deleteCalls: 0, writes: [], error: message };
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return { status: 'processed', deleteCalls: writes.length, writes };
|
|
186
|
+
}
|
|
187
|
+
}
|