@bd7pil/opencode-deep-memory 0.4.2 → 0.4.4

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
@@ -390,6 +390,10 @@ var PluginState = class {
390
390
  return this._toolSignatures.get(callID);
391
391
  }
392
392
  ccStore(hash2, entry) {
393
+ const now = Date.now();
394
+ for (const [k, v] of this._ccrCache) {
395
+ if (now - v.createdAt > 3e5) this._ccrCache.delete(k);
396
+ }
393
397
  if (this._ccrCache.size > 200) {
394
398
  const oldest = [...this._ccrCache.entries()].sort((a, b) => a[1].createdAt - b[1].createdAt).slice(0, 50);
395
399
  for (const [k] of oldest) this._ccrCache.delete(k);
@@ -15215,19 +15219,21 @@ function createCompactingHandler(args) {
15215
15219
  }
15216
15220
 
15217
15221
  // src/compress/pressure.ts
15218
- var DEFAULT_MAX_CONTEXT = 128e3;
15222
+ var FALLBACK_MAX_CONTEXT = 128e3;
15223
+ var OPENCODE_COMPACTION_RATIO = 0.75;
15219
15224
  var THRESHOLDS = {
15220
15225
  medium: 0.3,
15221
15226
  high: 0.5
15222
15227
  };
15223
- var MODEL_CONTEXT_LIMITS = {
15224
- "deepseek-chat": 64e3,
15225
- "deepseek-reasoner": 64e3,
15226
- "mimo-v2.5-pro": 128e3,
15227
- "mimo-v2.5": 128e3,
15228
- "claude-sonnet-4-20250514": 2e5,
15229
- "gpt-4o": 128e3
15230
- };
15228
+ var calibratedMaxContext = 0;
15229
+ function calibrateFromCompaction(lastInputTokens) {
15230
+ if (lastInputTokens <= 0) return;
15231
+ const derived = Math.round(lastInputTokens / OPENCODE_COMPACTION_RATIO);
15232
+ calibratedMaxContext = derived;
15233
+ }
15234
+ function getCalibratedMaxContext() {
15235
+ return calibratedMaxContext;
15236
+ }
15231
15237
  function estimateTokens2(text) {
15232
15238
  let cjk = 0;
15233
15239
  let other = 0;
@@ -15281,8 +15287,8 @@ function extractInputTokensFromMessages(messages) {
15281
15287
  }
15282
15288
  return 0;
15283
15289
  }
15284
- function detectPressure(messages, modelId) {
15285
- const maxContext = (modelId ? MODEL_CONTEXT_LIMITS[modelId] : void 0) ?? DEFAULT_MAX_CONTEXT;
15290
+ function detectPressure(messages) {
15291
+ const maxContext = calibratedMaxContext || FALLBACK_MAX_CONTEXT;
15286
15292
  const inputTokens = extractInputTokensFromMessages(messages);
15287
15293
  const estimated = inputTokens > 0 ? inputTokens : extractTokensFromMessages(messages);
15288
15294
  const ratio = Math.min(estimated / maxContext, 1);
@@ -15290,7 +15296,7 @@ function detectPressure(messages, modelId) {
15290
15296
  if (ratio >= THRESHOLDS.high) level = "high";
15291
15297
  else if (ratio >= THRESHOLDS.medium) level = "medium";
15292
15298
  else level = "low";
15293
- return { level, ratio, estimatedTokens: estimated };
15299
+ return { level, ratio, estimatedTokens: estimated, maxContext };
15294
15300
  }
15295
15301
 
15296
15302
  // src/compress/dedup.ts
@@ -15366,11 +15372,17 @@ function deduplicateToolOutputs(messages, state) {
15366
15372
  return deduped;
15367
15373
  }
15368
15374
  function simpleHash(s) {
15369
- let h = 0;
15370
- for (let i = 0; i < Math.min(s.length, 1e3); i++) {
15375
+ const len = s.length;
15376
+ const sampleSize = 500;
15377
+ let h = len;
15378
+ for (let i = 0; i < Math.min(len, sampleSize); i++) {
15379
+ h = h * 31 + s.charCodeAt(i) | 0;
15380
+ }
15381
+ const tailStart = Math.max(sampleSize, len - sampleSize);
15382
+ for (let i = tailStart; i < len; i++) {
15371
15383
  h = h * 31 + s.charCodeAt(i) | 0;
15372
15384
  }
15373
- return h.toString(36);
15385
+ return `${len}:${h.toString(36)}`;
15374
15386
  }
15375
15387
 
15376
15388
  // src/compress/error-purge.ts
@@ -15562,7 +15574,8 @@ function pruneOldMessages(messages) {
15562
15574
  if (p["type"] !== "text" || typeof p["text"] !== "string") continue;
15563
15575
  const text = p["text"];
15564
15576
  if (text.length < 500) continue;
15565
- if (text === "[cleared]" || text === "[stripped]" || text.startsWith("[compressed")) continue;
15577
+ if (text === "[cleared]" || text === "[stripped]") continue;
15578
+ if (text.includes("[compressed from")) continue;
15566
15579
  const keyInfo = extractKeyInfo(text);
15567
15580
  if (keyInfo.length < text.length * 0.6) {
15568
15581
  p["text"] = keyInfo + "\n[compressed from " + text.length + " chars]";
@@ -15678,7 +15691,7 @@ function detectContentType(content) {
15678
15691
  if (/Traceback \(most recent call last\)|at \S+\.\S+\(|Error: |Exception: |TypeError: |ReferenceError: /m.test(content)) return "error-trace";
15679
15692
  if (/<[a-z][\s\S]*>/i.test(content) && /<(html|div|span|body|head|script|style)[\s>]/i.test(content)) return "html";
15680
15693
  const lines = content.split("\n");
15681
- const logLineCount = lines.filter((l) => /^\d{4}-\d{2}-\d{2}|^\[\d{4}|ERROR|WARN|INFO|DEBUG|FATAL|TRACE/.test(l)).length;
15694
+ const logLineCount = lines.filter((l) => /^\s*(\d{4}-\d{2}-\d{2}|\[\d{4}|ERROR\b|WARN\b|INFO\b|DEBUG\b|FATAL\b|TRACE\b)/.test(l)).length;
15682
15695
  if (lines.length > 5 && logLineCount / lines.length > 0.3) return "log";
15683
15696
  const codePatterns = /\b(function |class |def |import |from .+ import|const |let |var |export |interface |type |struct |fn |func |pub |private |protected )\b/;
15684
15697
  const codeLines = lines.filter((l) => codePatterns.test(l)).length;
@@ -15689,7 +15702,7 @@ function detectContentType(content) {
15689
15702
  // src/compress/index.ts
15690
15703
  function runCompressionPipeline(ctx) {
15691
15704
  const { messages, state, logger } = ctx;
15692
- const pressure = detectPressure(messages, ctx.modelId);
15705
+ const pressure = detectPressure(messages);
15693
15706
  state.recordInputTokens(pressure.estimatedTokens);
15694
15707
  const stats = {
15695
15708
  toolDedup: 0,
@@ -15704,39 +15717,23 @@ function runCompressionPipeline(ctx) {
15704
15717
  };
15705
15718
  stats.toolDedup = deduplicateToolOutputs(messages, state);
15706
15719
  stats.errorPurge = purgeOldErrors(messages);
15707
- stats.toolOutputCompressed = compressOldToolOutputs(messages, state);
15708
15720
  stats.jsonCrushed = crushJsonToolOutputs(messages, state);
15721
+ stats.toolOutputCompressed = compressOldToolOutputs(messages, state);
15709
15722
  if (pressure.level === "medium" || pressure.level === "high") {
15710
15723
  stats.messagePruned = pruneOldMessages(messages);
15711
15724
  }
15712
15725
  const messagesSinceNudge = state.messagesSinceLastNudge(messages.length);
15713
15726
  if (shouldInjectNudge(pressure.level, messagesSinceNudge)) {
15714
- const lastMsg = messages[messages.length - 1];
15715
- if (lastMsg) {
15716
- const textParts = lastMsg.parts.filter(
15717
- (p) => typeof p === "object" && p !== null && p.type === "text"
15718
- );
15719
- const lastTextPart = textParts[textParts.length - 1];
15720
- if (lastTextPart && typeof lastTextPart.text === "string") {
15721
- lastTextPart.text += buildNudgeText(pressure.level);
15722
- stats.nudgeInjected = true;
15723
- state.recordNudge(messages.length);
15724
- }
15727
+ if (injectIntoLastAssistant(messages, buildNudgeText(pressure.level))) {
15728
+ stats.nudgeInjected = true;
15729
+ state.recordNudge(messages.length);
15725
15730
  }
15726
15731
  }
15727
15732
  const memoryNudge = detectMemoryNudge(messages, state.messagesSinceLastNudge(messages.length));
15728
15733
  if (memoryNudge.injected) {
15729
- const lastMsg = messages[messages.length - 1];
15730
- if (lastMsg) {
15731
- const textParts = lastMsg.parts.filter(
15732
- (p) => typeof p === "object" && p !== null && p.type === "text"
15733
- );
15734
- const lastTextPart = textParts[textParts.length - 1];
15735
- if (lastTextPart && typeof lastTextPart.text === "string") {
15736
- lastTextPart.text += buildMemoryNudge(memoryNudge.type);
15737
- state.recordNudge(messages.length);
15738
- logger?.debug("compress: memory nudge", { type: memoryNudge.type });
15739
- }
15734
+ if (injectIntoLastAssistant(messages, buildMemoryNudge(memoryNudge.type))) {
15735
+ state.recordNudge(messages.length);
15736
+ logger?.debug("compress: memory nudge", { type: memoryNudge.type });
15740
15737
  }
15741
15738
  }
15742
15739
  const active = stats.toolDedup > 0 || stats.errorPurge > 0 || stats.toolOutputCompressed > 0 || stats.jsonCrushed > 0 || stats.messagePruned > 0 || stats.nudgeInjected;
@@ -15747,6 +15744,21 @@ function runCompressionPipeline(ctx) {
15747
15744
  }
15748
15745
  return { stats };
15749
15746
  }
15747
+ function injectIntoLastAssistant(messages, text) {
15748
+ for (let i = messages.length - 1; i >= 0; i--) {
15749
+ const msg = messages[i];
15750
+ if (msg.info.role !== "assistant") continue;
15751
+ const textParts = msg.parts.filter(
15752
+ (p) => typeof p === "object" && p !== null && p.type === "text"
15753
+ );
15754
+ const lastTextPart = textParts[textParts.length - 1];
15755
+ if (lastTextPart && typeof lastTextPart.text === "string") {
15756
+ lastTextPart.text += text;
15757
+ return true;
15758
+ }
15759
+ }
15760
+ return false;
15761
+ }
15750
15762
  function compressOldToolOutputs(messages, state) {
15751
15763
  let compressed = 0;
15752
15764
  const protectedTail = messages.length - 8;
@@ -15759,7 +15771,6 @@ function compressOldToolOutputs(messages, state) {
15759
15771
  if (p.state?.status !== "completed") continue;
15760
15772
  if (!p.state.output) continue;
15761
15773
  if (p.state.output === "[superseded by duplicate call]") continue;
15762
- if (p.state.output.startsWith("[compressed")) continue;
15763
15774
  if (p.state.output.includes("[ccr:")) continue;
15764
15775
  const toolName = p.tool || "unknown";
15765
15776
  const output = p.state.output;
@@ -15784,7 +15795,6 @@ function crushJsonToolOutputs(messages, state) {
15784
15795
  if (p.type !== "tool") continue;
15785
15796
  if (p.state?.status !== "completed") continue;
15786
15797
  if (!p.state.output) continue;
15787
- if (p.state.output.startsWith("[compressed")) continue;
15788
15798
  if (p.state.output.startsWith("[superseded")) continue;
15789
15799
  if (p.state.output.includes("[ccr:")) continue;
15790
15800
  if (detectContentType(p.state.output) !== "json") continue;
@@ -16549,6 +16559,15 @@ var deepMemoryPlugin = async (input) => {
16549
16559
  if (event.type === "session.compacted") {
16550
16560
  const compactedSessionID = event.properties.sessionID;
16551
16561
  logger.info("event session.compacted", { sessionID: compactedSessionID });
16562
+ const lastTokens = state.lastInputTokens();
16563
+ if (lastTokens > 0) {
16564
+ calibrateFromCompaction(lastTokens);
16565
+ logger.info("pressure calibrated", {
16566
+ trigger: "compaction",
16567
+ lastInputTokens: lastTokens,
16568
+ derivedMaxContext: getCalibratedMaxContext()
16569
+ });
16570
+ }
16552
16571
  try {
16553
16572
  const auditLogDir = projectMemoryDir(projectPath);
16554
16573
  await mkdir4(auditLogDir, { recursive: true });