om-memory-system 4.6.0 → 4.8.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/dist/adapters/opencode/profile-learning.js +11 -6
- package/dist/adapters/pi/extension.js +11 -2
- package/dist/config.d.ts +7 -3
- package/dist/config.js +52 -9
- package/dist/core/capture.js +7 -3
- package/dist/core/context-budget.d.ts +74 -0
- package/dist/core/context-budget.js +98 -0
- package/dist/core/memory-operations.js +4 -1
- package/dist/core/retrieval.d.ts +8 -3
- package/dist/core/retrieval.js +27 -16
- package/dist/importer/claude-hook-api.js +14 -5
- package/dist/index.js +27 -5
- package/dist/services/client.js +5 -1
- package/dist/services/context.d.ts +9 -1
- package/dist/services/context.js +44 -16
- package/dist/services/global-config-writer.js +18 -2
- package/dist/services/settings-snapshot.d.ts +1 -7
- package/dist/services/settings-snapshot.js +49 -2
- package/dist/services/web-ensure.js +27 -15
- package/dist/utils/memory-limits.d.ts +34 -0
- package/dist/utils/memory-limits.js +40 -0
- package/dist/v2/adapter.js +28 -10
- package/dist/v2/memory-bridge.d.ts +12 -4
- package/dist/v2/memory-bridge.js +22 -5
- package/dist/web/assets/index-bvbynkv3.js +125 -0
- package/dist/web/assets/index-kKl1G5gD.css +2 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/dist/web/assets/index-BEwbs3t2.js +0 -125
- package/dist/web/assets/index-BYLF18Gt.css +0 -2
package/dist/v2/adapter.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { OPENCODE_IMPORT_COMMAND, OPENCODE_IMPORT_DESCRIPTION, runOpencodeImportCommand, } from "../adapters/opencode/import-command.js";
|
|
2
2
|
import { addPackageSkill } from "../adapters/opencode/package-skills.js";
|
|
3
3
|
import { log } from "../services/logger.js";
|
|
4
|
+
import { utf8ByteLength } from "../utils/context-limit.js";
|
|
4
5
|
import { eventBelongsToLocation, legacyToolResult, toLegacyEvent } from "./legacy-client.js";
|
|
5
6
|
const memoryInput = {
|
|
6
7
|
type: "object",
|
|
@@ -158,6 +159,7 @@ export async function registerV2Adapter(ctx, legacy, memory) {
|
|
|
158
159
|
},
|
|
159
160
|
}));
|
|
160
161
|
await ctx.session.hook("prompt", async (event) => {
|
|
162
|
+
const budgetBytes = memory.snapshotRequestBudget();
|
|
161
163
|
const sessionID = event.sessionID;
|
|
162
164
|
const text = event.prompt.text ?? "";
|
|
163
165
|
setBounded(messageIDs, sessionID, event.messageID);
|
|
@@ -173,26 +175,39 @@ export async function registerV2Adapter(ctx, legacy, memory) {
|
|
|
173
175
|
}
|
|
174
176
|
if (!memory.isInjectionEnabled() || !text.trim())
|
|
175
177
|
return;
|
|
176
|
-
// Start the search now so it overlaps prompt admission
|
|
177
|
-
|
|
178
|
+
// Start the search now with the entry snapshot, so it overlaps prompt admission.
|
|
179
|
+
// An edit during retrieval must not change this request's allowance.
|
|
180
|
+
const promise = memory.retrieve(text, sessionID, budgetBytes).catch((error) => {
|
|
178
181
|
log("v2 prompt: memory retrieval failed", { sessionID, error: String(error) });
|
|
179
182
|
return null;
|
|
180
183
|
});
|
|
181
|
-
setBounded(retrievals, sessionID, { promise });
|
|
184
|
+
setBounded(retrievals, sessionID, { promise, budgetBytes });
|
|
182
185
|
});
|
|
183
186
|
await ctx.session.hook("context", async (event) => {
|
|
184
187
|
const sessionID = event.sessionID;
|
|
188
|
+
const retrieval = retrievals.get(sessionID);
|
|
189
|
+
const budgetBytes = retrieval?.budgetBytes ?? memory.snapshotRequestBudget();
|
|
185
190
|
// Before chat.params, which stores the model on the recorded prompt row.
|
|
186
191
|
await recordAdmittedPrompts(sessionID, event.messages);
|
|
187
|
-
|
|
192
|
+
// The retrieval and the restored section share one request budget: the
|
|
193
|
+
// restored section is formatted here, within what the retrieval left.
|
|
194
|
+
let remainingBytes = null;
|
|
188
195
|
if (retrieval) {
|
|
189
196
|
const section = await settledRetrieval(retrieval);
|
|
197
|
+
if (section) {
|
|
198
|
+
event.system.push({ type: "text", text: section });
|
|
199
|
+
remainingBytes = retrieval.budgetBytes - utf8ByteLength(section);
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
remainingBytes = retrieval.budgetBytes;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
const restoredMemories = restored.get(sessionID);
|
|
206
|
+
if (restoredMemories) {
|
|
207
|
+
const section = memory.formatRestoredSession(restoredMemories, remainingBytes ?? budgetBytes);
|
|
190
208
|
if (section)
|
|
191
209
|
event.system.push({ type: "text", text: section });
|
|
192
210
|
}
|
|
193
|
-
const restoredSection = restored.get(sessionID);
|
|
194
|
-
if (restoredSection)
|
|
195
|
-
event.system.push({ type: "text", text: restoredSection });
|
|
196
211
|
if (legacy["chat.params"]) {
|
|
197
212
|
await legacy["chat.params"]({
|
|
198
213
|
message: { id: messageIDs.get(sessionID) ?? sessionID },
|
|
@@ -201,13 +216,16 @@ export async function registerV2Adapter(ctx, legacy, memory) {
|
|
|
201
216
|
}
|
|
202
217
|
});
|
|
203
218
|
await ctx.session.hook("compaction", async (event) => {
|
|
219
|
+
memory.snapshotRequestBudget();
|
|
204
220
|
if (!memory.isCompactionEnabled())
|
|
205
221
|
return;
|
|
206
222
|
const sessionID = event.sessionID;
|
|
207
223
|
try {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
224
|
+
// Load once here; the restored section is formatted when a request
|
|
225
|
+
// emits it, against that request's captured budget.
|
|
226
|
+
const memories = await memory.restoreSession(sessionID);
|
|
227
|
+
if (memories)
|
|
228
|
+
setBounded(restored, sessionID, memories);
|
|
211
229
|
else
|
|
212
230
|
restored.delete(sessionID);
|
|
213
231
|
}
|
|
@@ -10,9 +10,17 @@ export interface V2MemoryBridge {
|
|
|
10
10
|
isCompactionEnabled(): boolean;
|
|
11
11
|
isInternalPrompt(sessionID: string, text: string): boolean;
|
|
12
12
|
recordPrompt(sessionID: string, messageID: string, text: string): Promise<void>;
|
|
13
|
-
/**
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
/**
|
|
14
|
+
* One request's total byte allowance for memory context, from a fresh
|
|
15
|
+
* config read. Call once before the request's async memory work starts; an
|
|
16
|
+
* operation in flight keeps that captured total even if the config changes.
|
|
17
|
+
*/
|
|
18
|
+
snapshotRequestBudget(): number;
|
|
19
|
+
/** Wrapped per-prompt retrieval section packed within `budgetBytes`, or null when nothing is relevant. */
|
|
20
|
+
retrieve(prompt: string, sessionID: string, budgetBytes: number): Promise<string | null>;
|
|
21
|
+
/** The session's own memories for restore after compaction, loaded once, or null. */
|
|
22
|
+
restoreSession(sessionID: string): Promise<unknown[] | null>;
|
|
23
|
+
/** Cached restored memories formatted within `budgetBytes`, or null when nothing fits. */
|
|
24
|
+
formatRestoredSession(memories: unknown[], budgetBytes: number): string | null;
|
|
17
25
|
}
|
|
18
26
|
export declare function createV2MemoryBridge(directory: string): V2MemoryBridge;
|
package/dist/v2/memory-bridge.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { CONFIG, isConfigured } from "../config.js";
|
|
2
|
-
import { buildRetrievalSection, formatMemoriesForCompaction, wrapRetrievalSection, } from "../core/retrieval.js";
|
|
1
|
+
import { CONFIG, isConfigured, refreshConfigIfChanged } from "../config.js";
|
|
2
|
+
import { buildRetrievalSection, formatMemoriesForCompaction, retrievalWrapperBytes, wrapRetrievalSection, } from "../core/retrieval.js";
|
|
3
|
+
import { DEFAULT_RETRIEVAL_MAX_TOKENS, tokensToByteCeiling } from "../core/context-budget.js";
|
|
3
4
|
import { isInternalPrompt, recordUserPrompt } from "../adapters/opencode/user-prompt.js";
|
|
4
5
|
import { memoryClient } from "../services/client.js";
|
|
5
6
|
import { V2_RETRIEVAL_TIMEOUT_MS } from "../services/request-timeouts.js";
|
|
@@ -14,8 +15,17 @@ export function createV2MemoryBridge(directory) {
|
|
|
14
15
|
async recordPrompt(sessionID, messageID, text) {
|
|
15
16
|
await recordUserPrompt(sessionID, messageID, directory, text);
|
|
16
17
|
},
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
snapshotRequestBudget() {
|
|
19
|
+
refreshConfigIfChanged(directory);
|
|
20
|
+
// The default guards partial CONFIG stubs in tests.
|
|
21
|
+
return tokensToByteCeiling(CONFIG.retrievalMaxTokens ?? DEFAULT_RETRIEVAL_MAX_TOKENS);
|
|
22
|
+
},
|
|
23
|
+
async retrieve(prompt, sessionID, budgetBytes) {
|
|
24
|
+
// The wrapper counts against the request budget, so the packed section reserves it.
|
|
25
|
+
const section = await buildRetrievalSection(prompt, directory, sessionID, {
|
|
26
|
+
maxBytes: budgetBytes,
|
|
27
|
+
wrapperBytes: retrievalWrapperBytes(),
|
|
28
|
+
});
|
|
19
29
|
return section ? wrapRetrievalSection(section) : null;
|
|
20
30
|
},
|
|
21
31
|
async restoreSession(sessionID) {
|
|
@@ -23,7 +33,14 @@ export function createV2MemoryBridge(directory) {
|
|
|
23
33
|
const result = await memoryClient.searchMemoriesBySessionID(sessionID, tags.project.tag, CONFIG.compaction.memoryLimit);
|
|
24
34
|
if (!result.success || result.results.length === 0)
|
|
25
35
|
return null;
|
|
26
|
-
|
|
36
|
+
// Raw results: the request budget is only known when the restored
|
|
37
|
+
// section is emitted, so formatting happens in formatRestoredSession.
|
|
38
|
+
return result.results;
|
|
39
|
+
},
|
|
40
|
+
formatRestoredSession(memories, budgetBytes) {
|
|
41
|
+
if (memories.length === 0)
|
|
42
|
+
return null;
|
|
43
|
+
return formatMemoriesForCompaction(memories, { maxBytes: budgetBytes });
|
|
27
44
|
},
|
|
28
45
|
};
|
|
29
46
|
}
|