@bd7pil/opencode-deep-memory 0.6.0 → 0.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/README.md +114 -148
- package/dist/index.js +130 -38
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -261,6 +261,7 @@ var PluginState = class {
|
|
|
261
261
|
_ccrCache = /* @__PURE__ */ new Map();
|
|
262
262
|
_lastInputTokens = 0;
|
|
263
263
|
_lastNudgeMessageCount = /* @__PURE__ */ new Map();
|
|
264
|
+
_lastMemoryNudgeMessageCount = /* @__PURE__ */ new Map();
|
|
264
265
|
_lastCCRCleanup = 0;
|
|
265
266
|
_modelContextWindow = 0;
|
|
266
267
|
agentOf(sessionID) {
|
|
@@ -274,6 +275,7 @@ var PluginState = class {
|
|
|
274
275
|
this._models.delete(sessionID);
|
|
275
276
|
this._lastUserText.delete(sessionID);
|
|
276
277
|
this._lastNudgeMessageCount.delete(sessionID);
|
|
278
|
+
this._lastMemoryNudgeMessageCount.delete(sessionID);
|
|
277
279
|
}
|
|
278
280
|
recordModel(sessionID, model) {
|
|
279
281
|
this._models.set(sessionID, model);
|
|
@@ -423,6 +425,13 @@ var PluginState = class {
|
|
|
423
425
|
const last = this._lastNudgeMessageCount.get(sessionID);
|
|
424
426
|
return last != null ? currentMessageCount - last : Number.POSITIVE_INFINITY;
|
|
425
427
|
}
|
|
428
|
+
recordMemoryNudge(sessionID, messageCount) {
|
|
429
|
+
this._lastMemoryNudgeMessageCount.set(sessionID, messageCount);
|
|
430
|
+
}
|
|
431
|
+
messagesSinceLastMemoryNudge(sessionID, currentMessageCount) {
|
|
432
|
+
const last = this._lastMemoryNudgeMessageCount.get(sessionID);
|
|
433
|
+
return last != null ? currentMessageCount - last : Number.POSITIVE_INFINITY;
|
|
434
|
+
}
|
|
426
435
|
setModelContextWindow(tokens) {
|
|
427
436
|
if (tokens > 0) this._modelContextWindow = tokens;
|
|
428
437
|
}
|
|
@@ -1127,7 +1136,7 @@ async function runDream(opts) {
|
|
|
1127
1136
|
tools: {
|
|
1128
1137
|
memory_search: true,
|
|
1129
1138
|
memory_store: true,
|
|
1130
|
-
memory_forget:
|
|
1139
|
+
memory_forget: true,
|
|
1131
1140
|
read: true,
|
|
1132
1141
|
list: true
|
|
1133
1142
|
}
|
|
@@ -1228,23 +1237,38 @@ async function handleSessionCreatedForDream(args) {
|
|
|
1228
1237
|
}
|
|
1229
1238
|
const notesPath = memoryFilePath("project", "notes", projectPath);
|
|
1230
1239
|
let notesLines = 0;
|
|
1240
|
+
let notesContent = "";
|
|
1231
1241
|
try {
|
|
1232
|
-
|
|
1233
|
-
if (
|
|
1242
|
+
notesContent = fs5.readFileSync(notesPath, "utf8");
|
|
1243
|
+
if (notesContent.trim().length === 0) {
|
|
1234
1244
|
logger?.debug("auto-dream: notes.md is empty, skipping spawn");
|
|
1235
1245
|
return;
|
|
1236
1246
|
}
|
|
1237
|
-
notesLines =
|
|
1247
|
+
notesLines = notesContent.split("\n").filter((l) => l.trim()).length;
|
|
1238
1248
|
} catch {
|
|
1239
1249
|
logger?.debug("auto-dream: notes.md not found, skipping spawn");
|
|
1240
1250
|
return;
|
|
1241
1251
|
}
|
|
1242
1252
|
const memoryPath = memoryFilePath("project", "memory", projectPath);
|
|
1243
1253
|
if (!fs5.existsSync(memoryPath) || fs5.statSync(memoryPath).size < 50) {
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1254
|
+
if (notesLines >= 5) {
|
|
1255
|
+
try {
|
|
1256
|
+
fs5.writeFileSync(memoryPath, notesContent, "utf8");
|
|
1257
|
+
logger?.info("auto-dream: bootstrapped MEMORY.md from notes.md", {
|
|
1258
|
+
notesLines
|
|
1259
|
+
});
|
|
1260
|
+
} catch (err) {
|
|
1261
|
+
logger?.warn("auto-dream: failed to bootstrap MEMORY.md", {
|
|
1262
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1263
|
+
});
|
|
1264
|
+
return;
|
|
1265
|
+
}
|
|
1266
|
+
} else {
|
|
1267
|
+
logger?.debug("auto-dream: MEMORY.md missing and notes too small, skipping", {
|
|
1268
|
+
sessionID: info.id
|
|
1269
|
+
});
|
|
1270
|
+
return;
|
|
1271
|
+
}
|
|
1248
1272
|
}
|
|
1249
1273
|
const isSevenDayDue = schedule.lastDream === null || Date.now() - Date.parse(schedule.lastDream) > DREAM_INTERVAL_MS;
|
|
1250
1274
|
let isAccumulationDue = false;
|
|
@@ -1386,7 +1410,7 @@ async function runDistill(opts) {
|
|
|
1386
1410
|
tools: {
|
|
1387
1411
|
memory_search: true,
|
|
1388
1412
|
memory_store: true,
|
|
1389
|
-
memory_forget:
|
|
1413
|
+
memory_forget: true,
|
|
1390
1414
|
read: true,
|
|
1391
1415
|
list: true
|
|
1392
1416
|
}
|
|
@@ -15284,12 +15308,10 @@ function createCompactingHandler(args) {
|
|
|
15284
15308
|
}
|
|
15285
15309
|
|
|
15286
15310
|
// src/compress/pressure.ts
|
|
15287
|
-
var FALLBACK_MAX_CONTEXT =
|
|
15311
|
+
var FALLBACK_MAX_CONTEXT = 1e6;
|
|
15288
15312
|
var OPENCODE_COMPACTION_RATIO = 0.75;
|
|
15289
|
-
var
|
|
15290
|
-
|
|
15291
|
-
high: 0.4
|
|
15292
|
-
};
|
|
15313
|
+
var PRESSURE_MEDIUM_TOKENS = 5e4;
|
|
15314
|
+
var PRESSURE_HIGH_TOKENS = 15e4;
|
|
15293
15315
|
var calibratedMaxContext = 0;
|
|
15294
15316
|
function calibrateFromCompaction(lastInputTokens) {
|
|
15295
15317
|
if (lastInputTokens <= 0) return;
|
|
@@ -15341,20 +15363,23 @@ function extractTokensFromMessages(messages) {
|
|
|
15341
15363
|
return total;
|
|
15342
15364
|
}
|
|
15343
15365
|
function extractInputTokensFromMessages(messages) {
|
|
15366
|
+
let best = 0;
|
|
15344
15367
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
15345
15368
|
const msg = messages[i];
|
|
15346
15369
|
for (const part of msg.parts) {
|
|
15347
15370
|
if (typeof part !== "object" || part === null) continue;
|
|
15348
15371
|
const p = part;
|
|
15349
15372
|
if (p["type"] === "step-finish") {
|
|
15350
|
-
const tokens = p
|
|
15351
|
-
|
|
15352
|
-
|
|
15353
|
-
|
|
15373
|
+
const tokens = p;
|
|
15374
|
+
const input = tokens.tokens?.input ?? 0;
|
|
15375
|
+
const cached2 = tokens.tokens?.cached ?? 0;
|
|
15376
|
+
const total = input + cached2;
|
|
15377
|
+
if (total > best) best = total;
|
|
15378
|
+
if (best > 0) return best;
|
|
15354
15379
|
}
|
|
15355
15380
|
}
|
|
15356
15381
|
}
|
|
15357
|
-
return
|
|
15382
|
+
return best;
|
|
15358
15383
|
}
|
|
15359
15384
|
function detectPressure(messages, modelContextWindow) {
|
|
15360
15385
|
const ctx = maxContextFrom(modelContextWindow || 0);
|
|
@@ -15362,8 +15387,8 @@ function detectPressure(messages, modelContextWindow) {
|
|
|
15362
15387
|
const estimated = inputTokens > 0 ? inputTokens : extractTokensFromMessages(messages);
|
|
15363
15388
|
const ratio = Math.min(estimated / ctx, 1);
|
|
15364
15389
|
let level;
|
|
15365
|
-
if (
|
|
15366
|
-
else if (
|
|
15390
|
+
if (estimated >= PRESSURE_HIGH_TOKENS) level = "high";
|
|
15391
|
+
else if (estimated >= PRESSURE_MEDIUM_TOKENS) level = "medium";
|
|
15367
15392
|
else level = "low";
|
|
15368
15393
|
return { level, ratio, estimatedTokens: estimated, maxContext: ctx };
|
|
15369
15394
|
}
|
|
@@ -15386,17 +15411,17 @@ function buildNudgeText(level) {
|
|
|
15386
15411
|
var MEMORY_NUDGE_COOLDOWN = 3;
|
|
15387
15412
|
var DECISION_PATTERNS = [
|
|
15388
15413
|
/\b(?:decided|decision|chose|chosen|picked|selected)\b/i,
|
|
15389
|
-
|
|
15414
|
+
/(?:采用|选择|决定|确定|选用)/,
|
|
15390
15415
|
/\b(?:use|using|go with|went with)\b.*\b(?:because|since|due to)\b/i
|
|
15391
15416
|
];
|
|
15392
15417
|
var CONSTRAINT_PATTERNS = [
|
|
15393
15418
|
/\b(?:must not|cannot|should not|do not|never|always)\b/i,
|
|
15394
15419
|
/\b(?:constraint|restriction|limitation|requirement)\b/i,
|
|
15395
|
-
|
|
15420
|
+
/(?:不能|必须|禁止|约束|限制|要求|务必)/
|
|
15396
15421
|
];
|
|
15397
15422
|
var ERROR_FIX_PATTERNS = [
|
|
15398
15423
|
/\b(?:fix|fixed|resolve|resolved|patch|corrected)\b/i,
|
|
15399
|
-
|
|
15424
|
+
/(?:修复|修复了|解决|解决了)/,
|
|
15400
15425
|
/\b(?:the (?:bug|error|issue) (?:was|is)|root cause)\b/i
|
|
15401
15426
|
];
|
|
15402
15427
|
function detectMemoryNudge(messages, messagesSinceLastNudge) {
|
|
@@ -15410,13 +15435,14 @@ function detectMemoryNudge(messages, messagesSinceLastNudge) {
|
|
|
15410
15435
|
const hasRecentToolError = recentMessages.some(
|
|
15411
15436
|
(m) => m.parts.some((p) => p.type === "tool" && p.state?.status === "error")
|
|
15412
15437
|
);
|
|
15438
|
+
const recentAll = recentUserText + "\n" + recentAssistantText;
|
|
15413
15439
|
if (hasRecentToolError && ERROR_FIX_PATTERNS.some((p) => p.test(recentAssistantText))) {
|
|
15414
15440
|
return { injected: true, type: "gotcha" };
|
|
15415
15441
|
}
|
|
15416
|
-
if (CONSTRAINT_PATTERNS.some((p) => p.test(
|
|
15442
|
+
if (CONSTRAINT_PATTERNS.some((p) => p.test(recentAll))) {
|
|
15417
15443
|
return { injected: true, type: "constraint" };
|
|
15418
15444
|
}
|
|
15419
|
-
if (DECISION_PATTERNS.some((p) => p.test(
|
|
15445
|
+
if (DECISION_PATTERNS.some((p) => p.test(recentAll))) {
|
|
15420
15446
|
return { injected: true, type: "decision" };
|
|
15421
15447
|
}
|
|
15422
15448
|
return { injected: false, type: null };
|
|
@@ -15627,6 +15653,8 @@ var PROTECTED_TOOLS = /* @__PURE__ */ new Set([
|
|
|
15627
15653
|
var NEVER_DEDUP = /* @__PURE__ */ new Set(["read", "bash", "grep", "glob", "find", "search"]);
|
|
15628
15654
|
var ERROR_PURGE_TURN_THRESHOLD = 4;
|
|
15629
15655
|
var PROTECTED_HEAD_SINGLE = 2;
|
|
15656
|
+
var ASSISTANT_COMPRESS_MIN_LENGTH = 500;
|
|
15657
|
+
var ASSISTANT_COMPRESS_SAVINGS_RATIO = 0.6;
|
|
15630
15658
|
function simpleHash(s) {
|
|
15631
15659
|
const len = s.length;
|
|
15632
15660
|
const sampleSize = 500;
|
|
@@ -15640,12 +15668,32 @@ function simpleHash(s) {
|
|
|
15640
15668
|
}
|
|
15641
15669
|
return `${len}:${h.toString(36)}`;
|
|
15642
15670
|
}
|
|
15671
|
+
function compressAssistantText(text) {
|
|
15672
|
+
if (text.length < ASSISTANT_COMPRESS_MIN_LENGTH) return text;
|
|
15673
|
+
const lines = text.split("\n");
|
|
15674
|
+
const head = 3;
|
|
15675
|
+
const tail = 3;
|
|
15676
|
+
const kept = [];
|
|
15677
|
+
for (let i = 0; i < lines.length; i++) {
|
|
15678
|
+
const line = lines[i];
|
|
15679
|
+
if (i < head || i >= lines.length - tail) {
|
|
15680
|
+
kept.push(line);
|
|
15681
|
+
continue;
|
|
15682
|
+
}
|
|
15683
|
+
if (/^#{1,3}\s/.test(line) || /error|fail|warning|critical|important/i.test(line) || /^\s*[-*]\s/.test(line) || /^\s*\d+\.\s/.test(line) || line.trim().startsWith("```") || /^\/[^\s:]+/.test(line)) {
|
|
15684
|
+
kept.push(line);
|
|
15685
|
+
}
|
|
15686
|
+
}
|
|
15687
|
+
const result = kept.join("\n");
|
|
15688
|
+
return result.length < text.length * ASSISTANT_COMPRESS_SAVINGS_RATIO ? result : text;
|
|
15689
|
+
}
|
|
15643
15690
|
function singlePassCompress(messages, state, protectedTail) {
|
|
15644
15691
|
const stats = {
|
|
15645
15692
|
toolDedup: 0,
|
|
15646
15693
|
errorPurge: 0,
|
|
15647
15694
|
toolOutputCompressed: 0,
|
|
15648
15695
|
jsonCrushed: 0,
|
|
15696
|
+
assistantCompressed: 0,
|
|
15649
15697
|
ccrStored: 0
|
|
15650
15698
|
};
|
|
15651
15699
|
const totalMessages = messages.length;
|
|
@@ -15725,6 +15773,21 @@ function singlePassCompress(messages, state, protectedTail) {
|
|
|
15725
15773
|
}
|
|
15726
15774
|
}
|
|
15727
15775
|
}
|
|
15776
|
+
if (i < protectedTail && msg.info.role === "assistant") {
|
|
15777
|
+
for (let j = msg.parts.length - 1; j >= 0; j--) {
|
|
15778
|
+
const part = msg.parts[j];
|
|
15779
|
+
if (typeof part !== "object" || part === null) continue;
|
|
15780
|
+
const p = part;
|
|
15781
|
+
if (p["type"] !== "text") continue;
|
|
15782
|
+
const text = p["text"];
|
|
15783
|
+
if (typeof text !== "string") continue;
|
|
15784
|
+
const compressed = compressAssistantText(text);
|
|
15785
|
+
if (compressed !== text) {
|
|
15786
|
+
p["text"] = compressed;
|
|
15787
|
+
stats.assistantCompressed++;
|
|
15788
|
+
}
|
|
15789
|
+
}
|
|
15790
|
+
}
|
|
15728
15791
|
}
|
|
15729
15792
|
return stats;
|
|
15730
15793
|
}
|
|
@@ -15766,27 +15829,30 @@ function runCompressionPipeline(ctx) {
|
|
|
15766
15829
|
errorPurge: spStats.errorPurge,
|
|
15767
15830
|
toolOutputCompressed: spStats.toolOutputCompressed,
|
|
15768
15831
|
jsonCrushed: spStats.jsonCrushed,
|
|
15832
|
+
assistantCompressed: spStats.assistantCompressed,
|
|
15769
15833
|
ccrStored: spStats.ccrStored,
|
|
15770
15834
|
nudgeInjected: false,
|
|
15771
15835
|
pressureLevel: pressure.level,
|
|
15772
15836
|
estimatedTokens: pressure.estimatedTokens
|
|
15773
15837
|
};
|
|
15774
15838
|
const sid = sessionID || "default";
|
|
15775
|
-
const
|
|
15776
|
-
|
|
15839
|
+
const currentMsgCount = messages.length;
|
|
15840
|
+
const pressureSince = state.messagesSinceLastNudge(sid, currentMsgCount);
|
|
15841
|
+
if (shouldInjectNudge(pressure.level, pressureSince)) {
|
|
15777
15842
|
if (injectIntoLastAssistant(messages, buildNudgeText(pressure.level))) {
|
|
15778
15843
|
stats.nudgeInjected = true;
|
|
15779
|
-
state.recordNudge(sid,
|
|
15844
|
+
state.recordNudge(sid, currentMsgCount);
|
|
15780
15845
|
}
|
|
15781
15846
|
}
|
|
15782
|
-
const
|
|
15847
|
+
const memorySince = state.messagesSinceLastMemoryNudge(sid, currentMsgCount);
|
|
15848
|
+
const memoryNudge = detectMemoryNudge(messages, memorySince);
|
|
15783
15849
|
if (memoryNudge.injected) {
|
|
15784
15850
|
if (injectIntoLastAssistant(messages, buildMemoryNudge(memoryNudge.type))) {
|
|
15785
|
-
state.
|
|
15851
|
+
state.recordMemoryNudge(sid, currentMsgCount);
|
|
15786
15852
|
logger?.debug("compress: memory nudge", { type: memoryNudge.type });
|
|
15787
15853
|
}
|
|
15788
15854
|
}
|
|
15789
|
-
const active = stats.toolDedup > 0 || stats.errorPurge > 0 || stats.toolOutputCompressed > 0 || stats.jsonCrushed > 0 || stats.nudgeInjected;
|
|
15855
|
+
const active = stats.toolDedup > 0 || stats.errorPurge > 0 || stats.toolOutputCompressed > 0 || stats.jsonCrushed > 0 || stats.assistantCompressed > 0 || stats.nudgeInjected;
|
|
15790
15856
|
if (active) {
|
|
15791
15857
|
logger?.debug("compress: pipeline result", { ...stats });
|
|
15792
15858
|
} else {
|
|
@@ -15976,7 +16042,7 @@ function createMessagesTransformHandler(state, logger) {
|
|
|
15976
16042
|
logger
|
|
15977
16043
|
});
|
|
15978
16044
|
const ds = pipelineResult.stats;
|
|
15979
|
-
if (ds.toolDedup > 0 || ds.errorPurge > 0 || ds.toolOutputCompressed > 0 || ds.jsonCrushed > 0 || ds.nudgeInjected) {
|
|
16045
|
+
if (ds.toolDedup > 0 || ds.errorPurge > 0 || ds.toolOutputCompressed > 0 || ds.jsonCrushed > 0 || ds.assistantCompressed > 0 || ds.nudgeInjected) {
|
|
15980
16046
|
logger?.debug("messages.transform: deep compression", { ...ds });
|
|
15981
16047
|
state.mergeNotify({
|
|
15982
16048
|
compression: stats,
|
|
@@ -16116,6 +16182,29 @@ function createNotifyHandler(client, logger) {
|
|
|
16116
16182
|
};
|
|
16117
16183
|
}
|
|
16118
16184
|
|
|
16185
|
+
// src/shared/model-limits.ts
|
|
16186
|
+
var KNOWN_MODEL_LIMITS = {
|
|
16187
|
+
"deepseek-v4-pro": 1e6,
|
|
16188
|
+
"deepseek-v4": 1e6,
|
|
16189
|
+
"deepseek-v3": 64e3,
|
|
16190
|
+
"deepseek-r1": 64e3,
|
|
16191
|
+
"claude-opus-4": 2e5,
|
|
16192
|
+
"claude-sonnet-4": 2e5,
|
|
16193
|
+
"gpt-4o": 128e3,
|
|
16194
|
+
"o1": 2e5,
|
|
16195
|
+
"o3-mini": 2e5,
|
|
16196
|
+
"gemini-2.5-pro": 1e6,
|
|
16197
|
+
"gemini-2.5-flash": 1e6,
|
|
16198
|
+
"qwen-max": 131072
|
|
16199
|
+
};
|
|
16200
|
+
function lookupModelLimit(modelID) {
|
|
16201
|
+
if (KNOWN_MODEL_LIMITS[modelID]) return KNOWN_MODEL_LIMITS[modelID];
|
|
16202
|
+
for (const [key, limit] of Object.entries(KNOWN_MODEL_LIMITS)) {
|
|
16203
|
+
if (modelID.includes(key)) return limit;
|
|
16204
|
+
}
|
|
16205
|
+
return void 0;
|
|
16206
|
+
}
|
|
16207
|
+
|
|
16119
16208
|
// src/extract/enrich.ts
|
|
16120
16209
|
import { stat } from "fs/promises";
|
|
16121
16210
|
|
|
@@ -16447,11 +16536,14 @@ var deepMemoryPlugin = async (input) => {
|
|
|
16447
16536
|
const defaultModel = configResult.data?.model;
|
|
16448
16537
|
if (typeof defaultModel === "string" && defaultModel.includes("/")) {
|
|
16449
16538
|
const slashIdx = defaultModel.indexOf("/");
|
|
16450
|
-
|
|
16451
|
-
|
|
16452
|
-
|
|
16453
|
-
|
|
16454
|
-
|
|
16539
|
+
const providerID = defaultModel.slice(0, slashIdx);
|
|
16540
|
+
const modelID = defaultModel.slice(slashIdx + 1);
|
|
16541
|
+
state.recordFallbackModel({ providerID, modelID });
|
|
16542
|
+
const limit = lookupModelLimit(modelID);
|
|
16543
|
+
if (limit) {
|
|
16544
|
+
state.setModelContextWindow(limit);
|
|
16545
|
+
logger.debug("resolved model context window", { modelID, limit });
|
|
16546
|
+
}
|
|
16455
16547
|
}
|
|
16456
16548
|
}).catch((err) => {
|
|
16457
16549
|
logger.debug("config.get failed, dream/distill will omit model", {
|