@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/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: false,
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
- const content = fs5.readFileSync(notesPath, "utf8");
1233
- if (content.trim().length === 0) {
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 = content.split("\n").filter((l) => l.trim()).length;
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
- logger?.debug("auto-dream: MEMORY.md missing or too small, skipping", {
1245
- sessionID: info.id
1246
- });
1247
- return;
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: false,
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 = 128e3;
15311
+ var FALLBACK_MAX_CONTEXT = 1e6;
15288
15312
  var OPENCODE_COMPACTION_RATIO = 0.75;
15289
- var THRESHOLDS = {
15290
- medium: 0.2,
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.tokens;
15351
- if (tokens?.input && tokens.input > 0) {
15352
- return tokens.input;
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 0;
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 (ratio >= THRESHOLDS.high) level = "high";
15366
- else if (ratio >= THRESHOLDS.medium) level = "medium";
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
- /\b(?:采用|选择|决定|确定|选用)\b/,
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
- /\b(?:不能|必须|禁止|约束|限制|要求|务必)\b/
15420
+ /(?:不能|必须|禁止|约束|限制|要求|务必)/
15396
15421
  ];
15397
15422
  var ERROR_FIX_PATTERNS = [
15398
15423
  /\b(?:fix|fixed|resolve|resolved|patch|corrected)\b/i,
15399
- /\b(?:修复|修复了|解决|解决了)\b/,
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(recentUserText))) {
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(recentAssistantText))) {
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 messagesSinceNudge = state.messagesSinceLastNudge(sid, messages.length);
15776
- if (shouldInjectNudge(pressure.level, messagesSinceNudge)) {
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, messages.length);
15844
+ state.recordNudge(sid, currentMsgCount);
15780
15845
  }
15781
15846
  }
15782
- const memoryNudge = detectMemoryNudge(messages, state.messagesSinceLastNudge(sid, messages.length));
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.recordNudge(sid, messages.length);
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
- state.recordFallbackModel({
16451
- providerID: defaultModel.slice(0, slashIdx),
16452
- modelID: defaultModel.slice(slashIdx + 1)
16453
- });
16454
- logger.debug("resolved fallback model from config", { defaultModel });
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", {