audrey 0.16.1 → 0.20.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/LICENSE +21 -21
- package/README.md +146 -724
- package/dist/mcp-server/config.d.ts +20 -0
- package/dist/mcp-server/config.d.ts.map +1 -0
- package/dist/mcp-server/config.js +125 -0
- package/dist/mcp-server/config.js.map +1 -0
- package/dist/mcp-server/index.d.ts +100 -0
- package/dist/mcp-server/index.d.ts.map +1 -0
- package/dist/mcp-server/index.js +1113 -0
- package/dist/mcp-server/index.js.map +1 -0
- package/dist/src/adaptive.d.ts +7 -0
- package/dist/src/adaptive.d.ts.map +1 -0
- package/dist/src/adaptive.js +49 -0
- package/dist/src/adaptive.js.map +1 -0
- package/dist/src/affect.d.ts +19 -0
- package/dist/src/affect.d.ts.map +1 -0
- package/dist/src/affect.js +72 -0
- package/dist/src/affect.js.map +1 -0
- package/dist/src/audrey.d.ts +140 -0
- package/dist/src/audrey.d.ts.map +1 -0
- package/dist/src/audrey.js +564 -0
- package/dist/src/audrey.js.map +1 -0
- package/dist/src/capsule.d.ts +68 -0
- package/dist/src/capsule.d.ts.map +1 -0
- package/dist/src/capsule.js +311 -0
- package/dist/src/capsule.js.map +1 -0
- package/dist/src/causal.d.ts +28 -0
- package/dist/src/causal.d.ts.map +1 -0
- package/dist/src/causal.js +65 -0
- package/dist/src/causal.js.map +1 -0
- package/dist/src/confidence.d.ts +12 -0
- package/dist/src/confidence.d.ts.map +1 -0
- package/dist/src/confidence.js +63 -0
- package/dist/src/confidence.js.map +1 -0
- package/dist/src/consolidate.d.ts +8 -0
- package/dist/src/consolidate.d.ts.map +1 -0
- package/dist/src/consolidate.js +218 -0
- package/dist/src/consolidate.js.map +1 -0
- package/dist/src/context.d.ts +3 -0
- package/dist/src/context.d.ts.map +1 -0
- package/dist/src/context.js +19 -0
- package/dist/src/context.js.map +1 -0
- package/dist/src/db.d.ts +12 -0
- package/dist/src/db.d.ts.map +1 -0
- package/dist/src/db.js +380 -0
- package/dist/src/db.js.map +1 -0
- package/dist/src/decay.d.ts +7 -0
- package/dist/src/decay.d.ts.map +1 -0
- package/dist/src/decay.js +68 -0
- package/dist/src/decay.js.map +1 -0
- package/dist/src/embedding.d.ts +57 -0
- package/dist/src/embedding.d.ts.map +1 -0
- package/dist/src/embedding.js +254 -0
- package/dist/src/embedding.js.map +1 -0
- package/dist/src/encode.d.ts +15 -0
- package/dist/src/encode.d.ts.map +1 -0
- package/dist/src/encode.js +36 -0
- package/dist/src/encode.js.map +1 -0
- package/dist/src/events.d.ts +69 -0
- package/dist/src/events.d.ts.map +1 -0
- package/dist/src/events.js +149 -0
- package/dist/src/events.js.map +1 -0
- package/dist/src/export.d.ts +3 -0
- package/dist/src/export.d.ts.map +1 -0
- package/dist/src/export.js +46 -0
- package/dist/src/export.js.map +1 -0
- package/dist/src/forget.d.ts +11 -0
- package/dist/src/forget.d.ts.map +1 -0
- package/dist/src/forget.js +105 -0
- package/dist/src/forget.js.map +1 -0
- package/dist/src/fts.d.ts +34 -0
- package/dist/src/fts.d.ts.map +1 -0
- package/dist/src/fts.js +117 -0
- package/dist/src/fts.js.map +1 -0
- package/dist/src/hybrid-recall.d.ts +37 -0
- package/dist/src/hybrid-recall.d.ts.map +1 -0
- package/dist/src/hybrid-recall.js +213 -0
- package/dist/src/hybrid-recall.js.map +1 -0
- package/dist/src/import.d.ts +4 -0
- package/dist/src/import.d.ts.map +1 -0
- package/dist/src/import.js +127 -0
- package/dist/src/import.js.map +1 -0
- package/dist/src/index.d.ts +22 -0
- package/dist/src/index.d.ts.map +1 -0
- package/{src → dist/src}/index.js +5 -13
- package/dist/src/index.js.map +1 -0
- package/dist/src/interference.d.ts +13 -0
- package/dist/src/interference.d.ts.map +1 -0
- package/dist/src/interference.js +45 -0
- package/dist/src/interference.js.map +1 -0
- package/dist/src/introspect.d.ts +4 -0
- package/dist/src/introspect.d.ts.map +1 -0
- package/dist/src/introspect.js +40 -0
- package/dist/src/introspect.js.map +1 -0
- package/dist/src/llm.d.ts +38 -0
- package/dist/src/llm.d.ts.map +1 -0
- package/dist/src/llm.js +167 -0
- package/dist/src/llm.js.map +1 -0
- package/dist/src/migrate.d.ts +6 -0
- package/dist/src/migrate.d.ts.map +1 -0
- package/dist/src/migrate.js +51 -0
- package/dist/src/migrate.js.map +1 -0
- package/dist/src/promote.d.ts +40 -0
- package/dist/src/promote.d.ts.map +1 -0
- package/dist/src/promote.js +200 -0
- package/dist/src/promote.js.map +1 -0
- package/dist/src/prompts.d.ts +16 -0
- package/dist/src/prompts.d.ts.map +1 -0
- package/{src → dist/src}/prompts.js +172 -203
- package/dist/src/prompts.js.map +1 -0
- package/dist/src/recall.d.ts +9 -0
- package/dist/src/recall.d.ts.map +1 -0
- package/dist/src/recall.js +432 -0
- package/dist/src/recall.js.map +1 -0
- package/dist/src/redact.d.ts +27 -0
- package/dist/src/redact.d.ts.map +1 -0
- package/dist/src/redact.js +228 -0
- package/dist/src/redact.js.map +1 -0
- package/dist/src/rollback.d.ts +8 -0
- package/dist/src/rollback.d.ts.map +1 -0
- package/dist/src/rollback.js +33 -0
- package/dist/src/rollback.js.map +1 -0
- package/dist/src/routes.d.ts +7 -0
- package/dist/src/routes.d.ts.map +1 -0
- package/dist/src/routes.js +226 -0
- package/dist/src/routes.js.map +1 -0
- package/dist/src/rules-compiler.d.ts +20 -0
- package/dist/src/rules-compiler.d.ts.map +1 -0
- package/dist/src/rules-compiler.js +143 -0
- package/dist/src/rules-compiler.js.map +1 -0
- package/dist/src/server.d.ts +12 -0
- package/dist/src/server.d.ts.map +1 -0
- package/dist/src/server.js +22 -0
- package/dist/src/server.js.map +1 -0
- package/dist/src/tool-trace.d.ts +37 -0
- package/dist/src/tool-trace.d.ts.map +1 -0
- package/dist/src/tool-trace.js +142 -0
- package/dist/src/tool-trace.js.map +1 -0
- package/dist/src/types.d.ts +446 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +6 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/ulid.d.ts +3 -0
- package/dist/src/ulid.d.ts.map +1 -0
- package/dist/src/ulid.js +11 -0
- package/dist/src/ulid.js.map +1 -0
- package/dist/src/utils.d.ts +10 -0
- package/dist/src/utils.d.ts.map +1 -0
- package/dist/src/utils.js +41 -0
- package/dist/src/utils.js.map +1 -0
- package/dist/src/validate.d.ts +22 -0
- package/dist/src/validate.d.ts.map +1 -0
- package/dist/src/validate.js +109 -0
- package/dist/src/validate.js.map +1 -0
- package/docs/assets/benchmarks/local-benchmark.svg +45 -0
- package/docs/assets/benchmarks/operations-benchmark.svg +45 -0
- package/docs/assets/benchmarks/published-memory-standards.svg +50 -0
- package/docs/benchmarking.md +151 -0
- package/docs/production-readiness.md +124 -0
- package/examples/fintech-ops-demo.js +67 -0
- package/examples/healthcare-ops-demo.js +67 -0
- package/examples/stripe-demo.js +105 -0
- package/package.json +53 -13
- package/mcp-server/config.js +0 -80
- package/mcp-server/index.js +0 -729
- package/src/adaptive.js +0 -53
- package/src/affect.js +0 -64
- package/src/audrey.js +0 -604
- package/src/causal.js +0 -95
- package/src/confidence.js +0 -120
- package/src/consolidate.js +0 -265
- package/src/context.js +0 -15
- package/src/db.js +0 -370
- package/src/decay.js +0 -84
- package/src/embedding.js +0 -256
- package/src/encode.js +0 -63
- package/src/export.js +0 -67
- package/src/forget.js +0 -111
- package/src/import.js +0 -245
- package/src/interference.js +0 -51
- package/src/introspect.js +0 -48
- package/src/llm.js +0 -246
- package/src/migrate.js +0 -58
- package/src/recall.js +0 -352
- package/src/rollback.js +0 -42
- package/src/ulid.js +0 -18
- package/src/utils.js +0 -38
- package/src/validate.js +0 -172
package/src/llm.js
DELETED
|
@@ -1,246 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @typedef {Object} ChatMessage
|
|
3
|
-
* @property {'system' | 'user' | 'assistant'} role
|
|
4
|
-
* @property {string} content
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
function extractJSON(text) {
|
|
8
|
-
const fenced = text.match(/```(?:json)?\s*\n?([\s\S]*?)```/);
|
|
9
|
-
return fenced ? fenced[1].trim() : text.trim();
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* @typedef {Object} LLMCompletionResult
|
|
14
|
-
* @property {string} content
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* @typedef {Object} LLMCompletionOptions
|
|
19
|
-
* @property {number} [maxTokens]
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* @typedef {Object} LLMProvider
|
|
24
|
-
* @property {string} modelName
|
|
25
|
-
* @property {string} modelVersion
|
|
26
|
-
* @property {(messages: ChatMessage[], options?: LLMCompletionOptions) => Promise<LLMCompletionResult>} complete
|
|
27
|
-
* @property {(messages: ChatMessage[], options?: LLMCompletionOptions) => Promise<Object>} json
|
|
28
|
-
*/
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* @typedef {Object} MockLLMConfig
|
|
32
|
-
* @property {'mock'} provider
|
|
33
|
-
* @property {Record<string, Object>} [responses={}]
|
|
34
|
-
*/
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* @typedef {Object} AnthropicLLMConfig
|
|
38
|
-
* @property {'anthropic'} provider
|
|
39
|
-
* @property {string} [apiKey]
|
|
40
|
-
* @property {string} [model='claude-sonnet-4-6']
|
|
41
|
-
* @property {number} [maxTokens=1024]
|
|
42
|
-
*/
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* @typedef {Object} OpenAILLMConfig
|
|
46
|
-
* @property {'openai'} provider
|
|
47
|
-
* @property {string} [apiKey]
|
|
48
|
-
* @property {string} [model='gpt-4o']
|
|
49
|
-
* @property {number} [maxTokens=1024]
|
|
50
|
-
*/
|
|
51
|
-
|
|
52
|
-
const PROMPT_TYPE_KEYS = [
|
|
53
|
-
'principleExtraction',
|
|
54
|
-
'contradictionDetection',
|
|
55
|
-
'causalArticulation',
|
|
56
|
-
'contextResolution',
|
|
57
|
-
];
|
|
58
|
-
|
|
59
|
-
/** @implements {LLMProvider} */
|
|
60
|
-
export class MockLLMProvider {
|
|
61
|
-
/** @param {Partial<MockLLMConfig>} [config={}] */
|
|
62
|
-
constructor({ responses = {} } = {}) {
|
|
63
|
-
this.responses = responses;
|
|
64
|
-
this.modelName = 'mock-llm';
|
|
65
|
-
this.modelVersion = '1.0.0';
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
_matchPromptType(messages) {
|
|
69
|
-
const systemMsg = messages.find(m => m.role === 'system')?.content || '';
|
|
70
|
-
for (const key of PROMPT_TYPE_KEYS) {
|
|
71
|
-
if (systemMsg.includes(key)) return key;
|
|
72
|
-
}
|
|
73
|
-
return null;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* @param {ChatMessage[]} messages
|
|
78
|
-
* @returns {Promise<LLMCompletionResult>}
|
|
79
|
-
*/
|
|
80
|
-
async complete(messages) {
|
|
81
|
-
const promptType = this._matchPromptType(messages);
|
|
82
|
-
const cannedResponse = promptType ? this.responses[promptType] : undefined;
|
|
83
|
-
return { content: cannedResponse !== undefined ? JSON.stringify(cannedResponse) : '{}' };
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* @param {ChatMessage[]} messages
|
|
88
|
-
* @returns {Promise<Object>}
|
|
89
|
-
*/
|
|
90
|
-
async json(messages) {
|
|
91
|
-
const promptType = this._matchPromptType(messages);
|
|
92
|
-
const cannedResponse = promptType ? this.responses[promptType] : undefined;
|
|
93
|
-
return cannedResponse !== undefined ? cannedResponse : {};
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/** @implements {LLMProvider} */
|
|
98
|
-
export class AnthropicLLMProvider {
|
|
99
|
-
/** @param {Partial<AnthropicLLMConfig>} [config={}] */
|
|
100
|
-
constructor({ apiKey, model = 'claude-sonnet-4-6', maxTokens = 1024, timeout = 30000 } = {}) {
|
|
101
|
-
this.apiKey = apiKey || process.env.ANTHROPIC_API_KEY;
|
|
102
|
-
this.model = model;
|
|
103
|
-
this.maxTokens = maxTokens;
|
|
104
|
-
this.timeout = timeout;
|
|
105
|
-
this.modelName = model;
|
|
106
|
-
this.modelVersion = 'latest';
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* @param {ChatMessage[]} messages
|
|
111
|
-
* @param {LLMCompletionOptions} [options={}]
|
|
112
|
-
* @returns {Promise<LLMCompletionResult>}
|
|
113
|
-
*/
|
|
114
|
-
async complete(messages, options = {}) {
|
|
115
|
-
const systemMsg = messages.find(m => m.role === 'system')?.content;
|
|
116
|
-
const nonSystemMsgs = messages.filter(m => m.role !== 'system');
|
|
117
|
-
|
|
118
|
-
const body = {
|
|
119
|
-
model: this.model,
|
|
120
|
-
max_tokens: options.maxTokens || this.maxTokens,
|
|
121
|
-
messages: nonSystemMsgs,
|
|
122
|
-
};
|
|
123
|
-
if (systemMsg) body.system = systemMsg;
|
|
124
|
-
|
|
125
|
-
const controller = new AbortController();
|
|
126
|
-
const timer = setTimeout(() => controller.abort(), this.timeout);
|
|
127
|
-
try {
|
|
128
|
-
const response = await fetch('https://api.anthropic.com/v1/messages', {
|
|
129
|
-
method: 'POST',
|
|
130
|
-
headers: {
|
|
131
|
-
'x-api-key': this.apiKey,
|
|
132
|
-
'anthropic-version': '2023-06-01',
|
|
133
|
-
'content-type': 'application/json',
|
|
134
|
-
},
|
|
135
|
-
body: JSON.stringify(body),
|
|
136
|
-
signal: controller.signal,
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
if (!response.ok) {
|
|
140
|
-
const errorBody = await response.text().catch(() => '');
|
|
141
|
-
throw new Error(`Anthropic API error: ${response.status} ${errorBody}`);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
const data = await response.json();
|
|
145
|
-
const text = data.content?.[0]?.text || '';
|
|
146
|
-
return { content: text };
|
|
147
|
-
} finally {
|
|
148
|
-
clearTimeout(timer);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* @param {ChatMessage[]} messages
|
|
154
|
-
* @param {LLMCompletionOptions} [options={}]
|
|
155
|
-
* @returns {Promise<Object>}
|
|
156
|
-
*/
|
|
157
|
-
async json(messages, options = {}) {
|
|
158
|
-
const result = await this.complete(messages, options);
|
|
159
|
-
try {
|
|
160
|
-
return JSON.parse(extractJSON(result.content));
|
|
161
|
-
} catch {
|
|
162
|
-
throw new Error(`Failed to parse LLM response as JSON: ${result.content.slice(0, 200)}`);
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/** @implements {LLMProvider} */
|
|
168
|
-
export class OpenAILLMProvider {
|
|
169
|
-
/** @param {Partial<OpenAILLMConfig>} [config={}] */
|
|
170
|
-
constructor({ apiKey, model = 'gpt-4o', maxTokens = 1024, timeout = 30000 } = {}) {
|
|
171
|
-
this.apiKey = apiKey || process.env.OPENAI_API_KEY;
|
|
172
|
-
this.model = model;
|
|
173
|
-
this.maxTokens = maxTokens;
|
|
174
|
-
this.timeout = timeout;
|
|
175
|
-
this.modelName = model;
|
|
176
|
-
this.modelVersion = 'latest';
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* @param {ChatMessage[]} messages
|
|
181
|
-
* @param {LLMCompletionOptions} [options={}]
|
|
182
|
-
* @returns {Promise<LLMCompletionResult>}
|
|
183
|
-
*/
|
|
184
|
-
async complete(messages, options = {}) {
|
|
185
|
-
const body = {
|
|
186
|
-
model: this.model,
|
|
187
|
-
max_tokens: options.maxTokens || this.maxTokens,
|
|
188
|
-
messages,
|
|
189
|
-
};
|
|
190
|
-
|
|
191
|
-
const controller = new AbortController();
|
|
192
|
-
const timer = setTimeout(() => controller.abort(), this.timeout);
|
|
193
|
-
try {
|
|
194
|
-
const response = await fetch('https://api.openai.com/v1/chat/completions', {
|
|
195
|
-
method: 'POST',
|
|
196
|
-
headers: {
|
|
197
|
-
'Authorization': `Bearer ${this.apiKey}`,
|
|
198
|
-
'Content-Type': 'application/json',
|
|
199
|
-
},
|
|
200
|
-
body: JSON.stringify(body),
|
|
201
|
-
signal: controller.signal,
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
if (!response.ok) {
|
|
205
|
-
throw new Error(`OpenAI API error: ${response.status}`);
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
const data = await response.json();
|
|
209
|
-
const text = data.choices?.[0]?.message?.content || '';
|
|
210
|
-
return { content: text };
|
|
211
|
-
} finally {
|
|
212
|
-
clearTimeout(timer);
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
/**
|
|
217
|
-
* @param {ChatMessage[]} messages
|
|
218
|
-
* @param {LLMCompletionOptions} [options={}]
|
|
219
|
-
* @returns {Promise<Object>}
|
|
220
|
-
*/
|
|
221
|
-
async json(messages, options = {}) {
|
|
222
|
-
const result = await this.complete(messages, options);
|
|
223
|
-
try {
|
|
224
|
-
return JSON.parse(extractJSON(result.content));
|
|
225
|
-
} catch {
|
|
226
|
-
throw new Error(`Failed to parse LLM response as JSON: ${result.content.slice(0, 200)}`);
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
/**
|
|
232
|
-
* @param {MockLLMConfig | AnthropicLLMConfig | OpenAILLMConfig} config
|
|
233
|
-
* @returns {MockLLMProvider | AnthropicLLMProvider | OpenAILLMProvider}
|
|
234
|
-
*/
|
|
235
|
-
export function createLLMProvider(config) {
|
|
236
|
-
switch (config.provider) {
|
|
237
|
-
case 'mock':
|
|
238
|
-
return new MockLLMProvider(config);
|
|
239
|
-
case 'anthropic':
|
|
240
|
-
return new AnthropicLLMProvider(config);
|
|
241
|
-
case 'openai':
|
|
242
|
-
return new OpenAILLMProvider(config);
|
|
243
|
-
default:
|
|
244
|
-
throw new Error(`Unknown LLM provider: ${config.provider}. Valid: mock, anthropic, openai`);
|
|
245
|
-
}
|
|
246
|
-
}
|
package/src/migrate.js
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import { dropVec0Tables, createVec0Tables } from './db.js';
|
|
2
|
-
|
|
3
|
-
export async function reembedAll(db, embeddingProvider, { dropAndRecreate = false } = {}) {
|
|
4
|
-
if (dropAndRecreate) {
|
|
5
|
-
dropVec0Tables(db);
|
|
6
|
-
createVec0Tables(db, embeddingProvider.dimensions);
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
const episodes = db.prepare('SELECT id, content, source FROM episodes').all();
|
|
10
|
-
const semantics = db.prepare('SELECT id, content, state FROM semantics').all();
|
|
11
|
-
const procedures = db.prepare('SELECT id, content, state FROM procedures').all();
|
|
12
|
-
|
|
13
|
-
const episodeVectors = episodes.length > 0
|
|
14
|
-
? await embeddingProvider.embedBatch(episodes.map(ep => ep.content))
|
|
15
|
-
: [];
|
|
16
|
-
const semanticVectors = semantics.length > 0
|
|
17
|
-
? await embeddingProvider.embedBatch(semantics.map(s => s.content))
|
|
18
|
-
: [];
|
|
19
|
-
const procedureVectors = procedures.length > 0
|
|
20
|
-
? await embeddingProvider.embedBatch(procedures.map(p => p.content))
|
|
21
|
-
: [];
|
|
22
|
-
|
|
23
|
-
const updateEpLegacy = db.prepare('UPDATE episodes SET embedding = ? WHERE id = ?');
|
|
24
|
-
const deleteVecEp = db.prepare('DELETE FROM vec_episodes WHERE id = ?');
|
|
25
|
-
const insertVecEp = db.prepare('INSERT INTO vec_episodes(id, embedding, source, consolidated) VALUES (?, ?, ?, ?)');
|
|
26
|
-
|
|
27
|
-
const updateSemLegacy = db.prepare('UPDATE semantics SET embedding = ? WHERE id = ?');
|
|
28
|
-
const deleteVecSem = db.prepare('DELETE FROM vec_semantics WHERE id = ?');
|
|
29
|
-
const insertVecSem = db.prepare('INSERT INTO vec_semantics(id, embedding, state) VALUES (?, ?, ?)');
|
|
30
|
-
|
|
31
|
-
const updateProcLegacy = db.prepare('UPDATE procedures SET embedding = ? WHERE id = ?');
|
|
32
|
-
const deleteVecProc = db.prepare('DELETE FROM vec_procedures WHERE id = ?');
|
|
33
|
-
const insertVecProc = db.prepare('INSERT INTO vec_procedures(id, embedding, state) VALUES (?, ?, ?)');
|
|
34
|
-
|
|
35
|
-
const writeTx = db.transaction(() => {
|
|
36
|
-
for (let i = 0; i < episodes.length; i++) {
|
|
37
|
-
const buf = embeddingProvider.vectorToBuffer(episodeVectors[i]);
|
|
38
|
-
updateEpLegacy.run(buf, episodes[i].id);
|
|
39
|
-
deleteVecEp.run(episodes[i].id);
|
|
40
|
-
insertVecEp.run(episodes[i].id, buf, episodes[i].source, BigInt(0));
|
|
41
|
-
}
|
|
42
|
-
for (let i = 0; i < semantics.length; i++) {
|
|
43
|
-
const buf = embeddingProvider.vectorToBuffer(semanticVectors[i]);
|
|
44
|
-
updateSemLegacy.run(buf, semantics[i].id);
|
|
45
|
-
deleteVecSem.run(semantics[i].id);
|
|
46
|
-
insertVecSem.run(semantics[i].id, buf, semantics[i].state);
|
|
47
|
-
}
|
|
48
|
-
for (let i = 0; i < procedures.length; i++) {
|
|
49
|
-
const buf = embeddingProvider.vectorToBuffer(procedureVectors[i]);
|
|
50
|
-
updateProcLegacy.run(buf, procedures[i].id);
|
|
51
|
-
deleteVecProc.run(procedures[i].id);
|
|
52
|
-
insertVecProc.run(procedures[i].id, buf, procedures[i].state);
|
|
53
|
-
}
|
|
54
|
-
});
|
|
55
|
-
writeTx();
|
|
56
|
-
|
|
57
|
-
return { episodes: episodes.length, semantics: semantics.length, procedures: procedures.length };
|
|
58
|
-
}
|
package/src/recall.js
DELETED
|
@@ -1,352 +0,0 @@
|
|
|
1
|
-
import { computeConfidence, DEFAULT_HALF_LIVES, salienceModifier } from './confidence.js';
|
|
2
|
-
import { interferenceModifier } from './interference.js';
|
|
3
|
-
import { contextMatchRatio, contextModifier } from './context.js';
|
|
4
|
-
import { moodCongruenceModifier, affectSimilarity } from './affect.js';
|
|
5
|
-
import { daysBetween, safeJsonParse } from './utils.js';
|
|
6
|
-
|
|
7
|
-
function computeEpisodicConfidence(ep, now, confidenceConfig = {}) {
|
|
8
|
-
const ageDays = daysBetween(ep.created_at, now);
|
|
9
|
-
const halfLives = confidenceConfig.halfLives || DEFAULT_HALF_LIVES;
|
|
10
|
-
let confidence = computeConfidence({
|
|
11
|
-
sourceType: ep.source,
|
|
12
|
-
supportingCount: 1,
|
|
13
|
-
contradictingCount: 0,
|
|
14
|
-
ageDays,
|
|
15
|
-
halfLifeDays: halfLives.episodic ?? DEFAULT_HALF_LIVES.episodic,
|
|
16
|
-
retrievalCount: 0,
|
|
17
|
-
daysSinceRetrieval: ageDays,
|
|
18
|
-
weights: confidenceConfig.weights,
|
|
19
|
-
customSourceReliability: confidenceConfig.sourceReliability,
|
|
20
|
-
});
|
|
21
|
-
confidence *= salienceModifier(ep.salience);
|
|
22
|
-
return Math.max(0, Math.min(1, confidence));
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function computeSemanticConfidence(sem, now, confidenceConfig = {}) {
|
|
26
|
-
const ageDays = daysBetween(sem.created_at, now);
|
|
27
|
-
const daysSinceRetrieval = sem.last_reinforced_at
|
|
28
|
-
? daysBetween(sem.last_reinforced_at, now)
|
|
29
|
-
: ageDays;
|
|
30
|
-
const halfLives = confidenceConfig.halfLives || DEFAULT_HALF_LIVES;
|
|
31
|
-
let confidence = computeConfidence({
|
|
32
|
-
sourceType: 'tool-result',
|
|
33
|
-
supportingCount: sem.supporting_count || 0,
|
|
34
|
-
contradictingCount: sem.contradicting_count || 0,
|
|
35
|
-
ageDays,
|
|
36
|
-
halfLifeDays: halfLives.semantic ?? DEFAULT_HALF_LIVES.semantic,
|
|
37
|
-
retrievalCount: sem.retrieval_count || 0,
|
|
38
|
-
daysSinceRetrieval,
|
|
39
|
-
weights: confidenceConfig.weights,
|
|
40
|
-
customSourceReliability: confidenceConfig.sourceReliability,
|
|
41
|
-
});
|
|
42
|
-
confidence *= interferenceModifier(sem.interference_count || 0, confidenceConfig.interferenceWeight);
|
|
43
|
-
confidence *= salienceModifier(sem.salience);
|
|
44
|
-
return Math.max(0, Math.min(1, confidence));
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function computeProceduralConfidence(proc, now, confidenceConfig = {}) {
|
|
48
|
-
const ageDays = daysBetween(proc.created_at, now);
|
|
49
|
-
const daysSinceRetrieval = proc.last_reinforced_at
|
|
50
|
-
? daysBetween(proc.last_reinforced_at, now)
|
|
51
|
-
: ageDays;
|
|
52
|
-
const halfLives = confidenceConfig.halfLives || DEFAULT_HALF_LIVES;
|
|
53
|
-
let confidence = computeConfidence({
|
|
54
|
-
sourceType: 'tool-result',
|
|
55
|
-
supportingCount: proc.success_count || 0,
|
|
56
|
-
contradictingCount: proc.failure_count || 0,
|
|
57
|
-
ageDays,
|
|
58
|
-
halfLifeDays: halfLives.procedural ?? DEFAULT_HALF_LIVES.procedural,
|
|
59
|
-
retrievalCount: proc.retrieval_count || 0,
|
|
60
|
-
daysSinceRetrieval,
|
|
61
|
-
weights: confidenceConfig.weights,
|
|
62
|
-
customSourceReliability: confidenceConfig.sourceReliability,
|
|
63
|
-
});
|
|
64
|
-
confidence *= interferenceModifier(proc.interference_count || 0, confidenceConfig.interferenceWeight);
|
|
65
|
-
confidence *= salienceModifier(proc.salience);
|
|
66
|
-
return Math.max(0, Math.min(1, confidence));
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function buildEpisodicEntry(ep, confidence, score, includeProvenance, contextMatch, moodCongruence) {
|
|
70
|
-
const entry = {
|
|
71
|
-
id: ep.id,
|
|
72
|
-
content: ep.content,
|
|
73
|
-
type: 'episodic',
|
|
74
|
-
confidence,
|
|
75
|
-
score,
|
|
76
|
-
source: ep.source,
|
|
77
|
-
createdAt: ep.created_at,
|
|
78
|
-
};
|
|
79
|
-
if (contextMatch !== undefined) {
|
|
80
|
-
entry.contextMatch = contextMatch;
|
|
81
|
-
}
|
|
82
|
-
if (moodCongruence !== undefined) {
|
|
83
|
-
entry.moodCongruence = moodCongruence;
|
|
84
|
-
}
|
|
85
|
-
if (includeProvenance) {
|
|
86
|
-
entry.provenance = {
|
|
87
|
-
source: ep.source,
|
|
88
|
-
sourceReliability: ep.source_reliability,
|
|
89
|
-
createdAt: ep.created_at,
|
|
90
|
-
supersedes: ep.supersedes || null,
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
return entry;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function buildSemanticEntry(sem, confidence, score, includeProvenance) {
|
|
97
|
-
const entry = {
|
|
98
|
-
id: sem.id,
|
|
99
|
-
content: sem.content,
|
|
100
|
-
type: 'semantic',
|
|
101
|
-
confidence,
|
|
102
|
-
score,
|
|
103
|
-
source: 'consolidation',
|
|
104
|
-
state: sem.state,
|
|
105
|
-
createdAt: sem.created_at,
|
|
106
|
-
};
|
|
107
|
-
if (includeProvenance) {
|
|
108
|
-
entry.provenance = {
|
|
109
|
-
evidenceEpisodeIds: safeJsonParse(sem.evidence_episode_ids, []),
|
|
110
|
-
evidenceCount: sem.evidence_count || 0,
|
|
111
|
-
supportingCount: sem.supporting_count || 0,
|
|
112
|
-
contradictingCount: sem.contradicting_count || 0,
|
|
113
|
-
consolidationCheckpoint: sem.consolidation_checkpoint || null,
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
return entry;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function buildProceduralEntry(proc, confidence, score, includeProvenance) {
|
|
120
|
-
const entry = {
|
|
121
|
-
id: proc.id,
|
|
122
|
-
content: proc.content,
|
|
123
|
-
type: 'procedural',
|
|
124
|
-
confidence,
|
|
125
|
-
score,
|
|
126
|
-
source: 'consolidation',
|
|
127
|
-
state: proc.state,
|
|
128
|
-
createdAt: proc.created_at,
|
|
129
|
-
};
|
|
130
|
-
if (includeProvenance) {
|
|
131
|
-
entry.provenance = {
|
|
132
|
-
evidenceEpisodeIds: safeJsonParse(proc.evidence_episode_ids, []),
|
|
133
|
-
successCount: proc.success_count || 0,
|
|
134
|
-
failureCount: proc.failure_count || 0,
|
|
135
|
-
triggerConditions: proc.trigger_conditions || null,
|
|
136
|
-
};
|
|
137
|
-
}
|
|
138
|
-
return entry;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
function stateClause(includeDormant) {
|
|
142
|
-
return includeDormant
|
|
143
|
-
? "AND (v.state = 'active' OR v.state = 'context_dependent' OR v.state = 'dormant')"
|
|
144
|
-
: "AND (v.state = 'active' OR v.state = 'context_dependent')";
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function matchesDateFilters(createdAt, filters) {
|
|
148
|
-
if (filters.after && createdAt <= filters.after) return false;
|
|
149
|
-
if (filters.before && createdAt >= filters.before) return false;
|
|
150
|
-
return true;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
function safeKForTable(db, table, candidateK) {
|
|
154
|
-
const rowCount = db.prepare(`SELECT COUNT(*) AS c FROM ${table}`).get().c;
|
|
155
|
-
return rowCount > 0 ? Math.min(candidateK, rowCount) : 0;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
function knnEpisodic(db, queryBuffer, candidateK, now, minConfidence, includeProvenance, confidenceConfig, filters = {}, includePrivate = false) {
|
|
159
|
-
const safeK = safeKForTable(db, 'vec_episodes', candidateK);
|
|
160
|
-
if (safeK === 0) return [];
|
|
161
|
-
const privateClause = includePrivate ? '' : 'AND e."private" = 0';
|
|
162
|
-
const rows = db.prepare(`
|
|
163
|
-
SELECT e.*, (1.0 - v.distance) AS similarity
|
|
164
|
-
FROM vec_episodes v
|
|
165
|
-
JOIN episodes e ON e.id = v.id
|
|
166
|
-
WHERE v.embedding MATCH ?
|
|
167
|
-
AND k = ?
|
|
168
|
-
AND e.superseded_by IS NULL
|
|
169
|
-
${privateClause}
|
|
170
|
-
`).all(queryBuffer, safeK);
|
|
171
|
-
|
|
172
|
-
const results = [];
|
|
173
|
-
for (const row of rows) {
|
|
174
|
-
if (!matchesDateFilters(row.created_at, filters)) continue;
|
|
175
|
-
if (filters.tags?.length) {
|
|
176
|
-
const rowTags = safeJsonParse(row.tags, []);
|
|
177
|
-
if (!filters.tags.some(t => rowTags.includes(t))) continue;
|
|
178
|
-
}
|
|
179
|
-
if (filters.sources?.length && !filters.sources.includes(row.source)) continue;
|
|
180
|
-
let confidence = computeEpisodicConfidence(row, now, confidenceConfig);
|
|
181
|
-
|
|
182
|
-
let ctxMatch;
|
|
183
|
-
if (confidenceConfig?.retrievalContext) {
|
|
184
|
-
const encodingCtx = safeJsonParse(row.context, {});
|
|
185
|
-
ctxMatch = contextMatchRatio(encodingCtx, confidenceConfig.retrievalContext);
|
|
186
|
-
confidence *= contextModifier(encodingCtx, confidenceConfig.retrievalContext, confidenceConfig.contextWeight);
|
|
187
|
-
confidence = Math.max(0, Math.min(1, confidence));
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
let moodMatch;
|
|
191
|
-
if (confidenceConfig?.retrievalMood) {
|
|
192
|
-
const encodingAffect = safeJsonParse(row.affect, {});
|
|
193
|
-
moodMatch = affectSimilarity(encodingAffect, confidenceConfig.retrievalMood);
|
|
194
|
-
confidence *= moodCongruenceModifier(encodingAffect, confidenceConfig.retrievalMood, confidenceConfig.affectWeight);
|
|
195
|
-
confidence = Math.max(0, Math.min(1, confidence));
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
if (confidence < minConfidence) continue;
|
|
199
|
-
const score = row.similarity * confidence;
|
|
200
|
-
results.push(buildEpisodicEntry(row, confidence, score, includeProvenance, ctxMatch, moodMatch));
|
|
201
|
-
}
|
|
202
|
-
return results;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
function knnSemantic(db, queryBuffer, candidateK, now, minConfidence, includeProvenance, includeDormant, confidenceConfig, filters = {}) {
|
|
206
|
-
const safeK = safeKForTable(db, 'vec_semantics', candidateK);
|
|
207
|
-
if (safeK === 0) return { results: [], matchedIds: [] };
|
|
208
|
-
const rows = db.prepare(`
|
|
209
|
-
SELECT s.*, (1.0 - v.distance) AS similarity
|
|
210
|
-
FROM vec_semantics v
|
|
211
|
-
JOIN semantics s ON s.id = v.id
|
|
212
|
-
WHERE v.embedding MATCH ?
|
|
213
|
-
AND k = ?
|
|
214
|
-
${stateClause(includeDormant)}
|
|
215
|
-
`).all(queryBuffer, safeK);
|
|
216
|
-
|
|
217
|
-
const results = [];
|
|
218
|
-
const matchedIds = [];
|
|
219
|
-
for (const row of rows) {
|
|
220
|
-
if (!matchesDateFilters(row.created_at, filters)) continue;
|
|
221
|
-
const confidence = computeSemanticConfidence(row, now, confidenceConfig);
|
|
222
|
-
if (confidence < minConfidence) continue;
|
|
223
|
-
const score = row.similarity * confidence;
|
|
224
|
-
matchedIds.push(row.id);
|
|
225
|
-
results.push(buildSemanticEntry(row, confidence, score, includeProvenance));
|
|
226
|
-
}
|
|
227
|
-
return { results, matchedIds };
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
function knnProcedural(db, queryBuffer, candidateK, now, minConfidence, includeProvenance, includeDormant, confidenceConfig, filters = {}) {
|
|
231
|
-
const safeK = safeKForTable(db, 'vec_procedures', candidateK);
|
|
232
|
-
if (safeK === 0) return { results: [], matchedIds: [] };
|
|
233
|
-
const rows = db.prepare(`
|
|
234
|
-
SELECT p.*, (1.0 - v.distance) AS similarity
|
|
235
|
-
FROM vec_procedures v
|
|
236
|
-
JOIN procedures p ON p.id = v.id
|
|
237
|
-
WHERE v.embedding MATCH ?
|
|
238
|
-
AND k = ?
|
|
239
|
-
${stateClause(includeDormant)}
|
|
240
|
-
`).all(queryBuffer, safeK);
|
|
241
|
-
|
|
242
|
-
const results = [];
|
|
243
|
-
const matchedIds = [];
|
|
244
|
-
for (const row of rows) {
|
|
245
|
-
if (!matchesDateFilters(row.created_at, filters)) continue;
|
|
246
|
-
const confidence = computeProceduralConfidence(row, now, confidenceConfig);
|
|
247
|
-
if (confidence < minConfidence) continue;
|
|
248
|
-
const score = row.similarity * confidence;
|
|
249
|
-
matchedIds.push(row.id);
|
|
250
|
-
results.push(buildProceduralEntry(row, confidence, score, includeProvenance));
|
|
251
|
-
}
|
|
252
|
-
return { results, matchedIds };
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
/**
|
|
256
|
-
* @param {import('better-sqlite3').Database} db
|
|
257
|
-
* @param {import('./embedding.js').EmbeddingProvider} embeddingProvider
|
|
258
|
-
* @param {string} query
|
|
259
|
-
* @param {{ minConfidence?: number, types?: string[], limit?: number, includeProvenance?: boolean, includeDormant?: boolean, tags?: string[], sources?: string[], after?: string, before?: string }} [options]
|
|
260
|
-
* @returns {AsyncGenerator<{ id: string, content: string, type: string, confidence: number, score: number, source: string, createdAt: string }>}
|
|
261
|
-
*/
|
|
262
|
-
export async function* recallStream(db, embeddingProvider, query, options = {}) {
|
|
263
|
-
const {
|
|
264
|
-
minConfidence = 0,
|
|
265
|
-
types,
|
|
266
|
-
limit = 10,
|
|
267
|
-
includeProvenance = false,
|
|
268
|
-
includeDormant = false,
|
|
269
|
-
confidenceConfig,
|
|
270
|
-
tags,
|
|
271
|
-
sources,
|
|
272
|
-
after,
|
|
273
|
-
before,
|
|
274
|
-
includePrivate = false,
|
|
275
|
-
} = options;
|
|
276
|
-
|
|
277
|
-
const queryVector = await embeddingProvider.embed(query);
|
|
278
|
-
const queryBuffer = embeddingProvider.vectorToBuffer(queryVector);
|
|
279
|
-
const searchTypes = types || ['episodic', 'semantic', 'procedural'];
|
|
280
|
-
const now = new Date();
|
|
281
|
-
const hasFilters = tags?.length || sources?.length || after || before;
|
|
282
|
-
const candidateK = hasFilters ? limit * 5 : limit * 3;
|
|
283
|
-
const filters = { tags, sources, after, before };
|
|
284
|
-
|
|
285
|
-
const allResults = [];
|
|
286
|
-
|
|
287
|
-
if (searchTypes.includes('episodic')) {
|
|
288
|
-
try {
|
|
289
|
-
const episodic = knnEpisodic(db, queryBuffer, candidateK, now, minConfidence, includeProvenance, confidenceConfig, filters, includePrivate);
|
|
290
|
-
allResults.push(...episodic);
|
|
291
|
-
} catch {
|
|
292
|
-
// A broken episodic index should not block semantic/procedural recall.
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
if (searchTypes.includes('semantic')) {
|
|
297
|
-
try {
|
|
298
|
-
const { results: semResults, matchedIds: semIds } =
|
|
299
|
-
knnSemantic(db, queryBuffer, candidateK, now, minConfidence, includeProvenance, includeDormant, confidenceConfig, filters);
|
|
300
|
-
allResults.push(...semResults);
|
|
301
|
-
|
|
302
|
-
if (semIds.length > 0) {
|
|
303
|
-
const nowISO = now.toISOString();
|
|
304
|
-
const placeholders = semIds.map(() => '?').join(',');
|
|
305
|
-
db.prepare(
|
|
306
|
-
`UPDATE semantics SET retrieval_count = retrieval_count + 1, last_reinforced_at = ? WHERE id IN (${placeholders})`
|
|
307
|
-
).run(nowISO, ...semIds);
|
|
308
|
-
}
|
|
309
|
-
} catch {
|
|
310
|
-
// A broken semantic index should not block other memory types.
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
if (searchTypes.includes('procedural')) {
|
|
315
|
-
try {
|
|
316
|
-
const { results: procResults, matchedIds: procIds } =
|
|
317
|
-
knnProcedural(db, queryBuffer, candidateK, now, minConfidence, includeProvenance, includeDormant, confidenceConfig, filters);
|
|
318
|
-
allResults.push(...procResults);
|
|
319
|
-
|
|
320
|
-
if (procIds.length > 0) {
|
|
321
|
-
const nowISO = now.toISOString();
|
|
322
|
-
const placeholders = procIds.map(() => '?').join(',');
|
|
323
|
-
db.prepare(
|
|
324
|
-
`UPDATE procedures SET retrieval_count = retrieval_count + 1, last_reinforced_at = ? WHERE id IN (${placeholders})`
|
|
325
|
-
).run(nowISO, ...procIds);
|
|
326
|
-
}
|
|
327
|
-
} catch {
|
|
328
|
-
// A broken procedural index should not block other memory types.
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
allResults.sort((a, b) => b.score - a.score);
|
|
333
|
-
const top = allResults.slice(0, limit);
|
|
334
|
-
for (const entry of top) {
|
|
335
|
-
yield entry;
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
/**
|
|
340
|
-
* @param {import('better-sqlite3').Database} db
|
|
341
|
-
* @param {import('./embedding.js').EmbeddingProvider} embeddingProvider
|
|
342
|
-
* @param {string} query
|
|
343
|
-
* @param {{ minConfidence?: number, types?: string[], limit?: number, includeProvenance?: boolean, includeDormant?: boolean, tags?: string[], sources?: string[], after?: string, before?: string }} [options]
|
|
344
|
-
* @returns {Promise<Array<{ id: string, content: string, type: string, confidence: number, score: number, source: string, createdAt: string }>>}
|
|
345
|
-
*/
|
|
346
|
-
export async function recall(db, embeddingProvider, query, options = {}) {
|
|
347
|
-
const results = [];
|
|
348
|
-
for await (const entry of recallStream(db, embeddingProvider, query, options)) {
|
|
349
|
-
results.push(entry);
|
|
350
|
-
}
|
|
351
|
-
return results;
|
|
352
|
-
}
|
package/src/rollback.js
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { safeJsonParse } from './utils.js';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* @param {import('better-sqlite3').Database} db
|
|
5
|
-
* @returns {Array<{ id: string, checkpoint_cursor: string|null, input_episode_ids: string, output_memory_ids: string, started_at: string, completed_at: string|null, status: string }>}
|
|
6
|
-
*/
|
|
7
|
-
export function getConsolidationHistory(db) {
|
|
8
|
-
return db.prepare(`
|
|
9
|
-
SELECT id, checkpoint_cursor, input_episode_ids, output_memory_ids,
|
|
10
|
-
started_at, completed_at, status
|
|
11
|
-
FROM consolidation_runs ORDER BY started_at DESC
|
|
12
|
-
`).all();
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* @param {import('better-sqlite3').Database} db
|
|
17
|
-
* @param {string} runId
|
|
18
|
-
* @returns {{ rolledBackMemories: number, restoredEpisodes: number }}
|
|
19
|
-
*/
|
|
20
|
-
export function rollbackConsolidation(db, runId) {
|
|
21
|
-
const run = db.prepare('SELECT * FROM consolidation_runs WHERE id = ?').get(runId);
|
|
22
|
-
if (!run) throw new Error(`Consolidation run not found: ${runId}`);
|
|
23
|
-
if (run.status === 'rolled_back') throw new Error(`Run already rolled back: ${runId}`);
|
|
24
|
-
|
|
25
|
-
const outputIds = safeJsonParse(run.output_memory_ids, []);
|
|
26
|
-
const inputIds = safeJsonParse(run.input_episode_ids, []);
|
|
27
|
-
|
|
28
|
-
const doRollback = db.transaction(() => {
|
|
29
|
-
const markSemantics = db.prepare('UPDATE semantics SET state = ? WHERE id = ?');
|
|
30
|
-
const markProcedures = db.prepare('UPDATE procedures SET state = ? WHERE id = ?');
|
|
31
|
-
for (const id of outputIds) {
|
|
32
|
-
markSemantics.run('rolled_back', id);
|
|
33
|
-
markProcedures.run('rolled_back', id);
|
|
34
|
-
}
|
|
35
|
-
const unmark = db.prepare('UPDATE episodes SET consolidated = 0 WHERE id = ?');
|
|
36
|
-
for (const id of inputIds) { unmark.run(id); }
|
|
37
|
-
db.prepare('UPDATE consolidation_runs SET status = ? WHERE id = ?').run('rolled_back', runId);
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
doRollback();
|
|
41
|
-
return { rolledBackMemories: outputIds.length, restoredEpisodes: inputIds.length };
|
|
42
|
-
}
|