@bd7pil/opencode-deep-memory 0.5.0 → 0.5.1

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
@@ -260,7 +260,8 @@ var PluginState = class {
260
260
  _toolSignatures = /* @__PURE__ */ new Map();
261
261
  _ccrCache = /* @__PURE__ */ new Map();
262
262
  _lastInputTokens = 0;
263
- _lastNudgeMessageCount = 0;
263
+ _lastNudgeMessageCount = /* @__PURE__ */ new Map();
264
+ _lastCCRCleanup = 0;
264
265
  _modelContextWindow = 0;
265
266
  agentOf(sessionID) {
266
267
  return this._agents.get(sessionID);
@@ -272,6 +273,7 @@ var PluginState = class {
272
273
  this._agents.delete(sessionID);
273
274
  this._models.delete(sessionID);
274
275
  this._lastUserText.delete(sessionID);
276
+ this._lastNudgeMessageCount.delete(sessionID);
275
277
  }
276
278
  recordModel(sessionID, model) {
277
279
  this._models.set(sessionID, model);
@@ -392,12 +394,16 @@ var PluginState = class {
392
394
  }
393
395
  ccStore(hash2, entry) {
394
396
  const now = Date.now();
395
- for (const [k, v] of this._ccrCache) {
396
- if (now - v.createdAt > 3e5) this._ccrCache.delete(k);
397
- }
398
- if (this._ccrCache.size > 200) {
399
- const oldest = [...this._ccrCache.entries()].sort((a, b) => a[1].createdAt - b[1].createdAt).slice(0, 50);
400
- for (const [k] of oldest) this._ccrCache.delete(k);
397
+ if (now - this._lastCCRCleanup > 3e4) {
398
+ for (const [k, v] of this._ccrCache) {
399
+ if (now - v.createdAt > 3e5) this._ccrCache.delete(k);
400
+ }
401
+ if (this._ccrCache.size > 200) {
402
+ const excess = this._ccrCache.size - 150;
403
+ const oldest = [...this._ccrCache.keys()].slice(0, excess);
404
+ for (const k of oldest) this._ccrCache.delete(k);
405
+ }
406
+ this._lastCCRCleanup = now;
401
407
  }
402
408
  this._ccrCache.set(hash2, entry);
403
409
  }
@@ -410,11 +416,12 @@ var PluginState = class {
410
416
  lastInputTokens() {
411
417
  return this._lastInputTokens;
412
418
  }
413
- recordNudge(messageCount) {
414
- this._lastNudgeMessageCount = messageCount;
419
+ recordNudge(sessionID, messageCount) {
420
+ this._lastNudgeMessageCount.set(sessionID, messageCount);
415
421
  }
416
- messagesSinceLastNudge(currentMessageCount) {
417
- return currentMessageCount - this._lastNudgeMessageCount;
422
+ messagesSinceLastNudge(sessionID, currentMessageCount) {
423
+ const last = this._lastNudgeMessageCount.get(sessionID);
424
+ return last != null ? currentMessageCount - last : Number.POSITIVE_INFINITY;
418
425
  }
419
426
  setModelContextWindow(tokens) {
420
427
  if (tokens > 0) this._modelContextWindow = tokens;
@@ -15310,118 +15317,79 @@ function detectPressure(messages, modelContextWindow) {
15310
15317
  return { level, ratio, estimatedTokens: estimated, maxContext: ctx };
15311
15318
  }
15312
15319
 
15313
- // src/compress/dedup.ts
15314
- var PROTECTED_TOOLS = /* @__PURE__ */ new Set([
15315
- "question",
15316
- "edit",
15317
- "write",
15318
- "todowrite",
15319
- "todoread",
15320
- "memory_store",
15321
- "memory_search",
15322
- "memory_forget",
15323
- "memory_expand",
15324
- "deep_expand"
15325
- ]);
15326
- var NEVER_DEDUP = /* @__PURE__ */ new Set(["read", "bash", "grep", "glob", "find", "search"]);
15327
- var KEEP_RECENT = 8;
15328
- var PROTECTED_HEAD = 3;
15329
- function createToolSignature(tool5, args) {
15330
- if (!args) return tool5;
15331
- const sorted = Object.keys(args).sort().map((k) => `${k}:${JSON.stringify(args[k])}`).join(",");
15332
- return `${tool5}::${sorted}`;
15320
+ // src/compress/nudge.ts
15321
+ var NUDGE_COOLDOWN = 5;
15322
+ function shouldInjectNudge(level, messagesSinceLastNudge) {
15323
+ if (level !== "high") return false;
15324
+ if (messagesSinceLastNudge < NUDGE_COOLDOWN) return false;
15325
+ return true;
15333
15326
  }
15334
- function deduplicateToolOutputs(messages, state) {
15335
- let deduped = 0;
15336
- const totalMessages = messages.length;
15337
- if (totalMessages <= KEEP_RECENT + PROTECTED_HEAD) return 0;
15338
- const protectedTailStart = totalMessages - KEEP_RECENT;
15339
- const seen = /* @__PURE__ */ new Map();
15340
- for (let i = PROTECTED_HEAD; i < protectedTailStart; i++) {
15341
- const msg = messages[i];
15342
- for (const part of msg.parts) {
15343
- if (typeof part !== "object" || part === null) continue;
15344
- const p = part;
15345
- if (p["type"] !== "tool") continue;
15346
- const toolName = p["tool"];
15347
- const callID = p["callID"];
15348
- if (!toolName || !callID) continue;
15349
- if (PROTECTED_TOOLS.has(toolName)) continue;
15350
- if (NEVER_DEDUP.has(toolName)) continue;
15351
- const status = p["state"]?.["status"];
15352
- if (status !== "completed") continue;
15353
- const toolState = p["state"];
15354
- const output = toolState["output"];
15355
- if (typeof output !== "string") continue;
15356
- if (output === "[superseded by duplicate call]") continue;
15357
- if (output.includes("[ccr:")) continue;
15358
- const input = toolState["input"];
15359
- const signature = createToolSignature(toolName, input);
15360
- const outputHash = simpleHash(output);
15361
- const existing = seen.get(signature);
15362
- if (existing) {
15363
- if (existing.outputHash === outputHash) {
15364
- const prevMsg = messages[existing.msgIdx];
15365
- for (const prevPart of prevMsg.parts) {
15366
- if (typeof prevPart !== "object" || prevPart === null) continue;
15367
- const pp = prevPart;
15368
- if (pp["type"] !== "tool") continue;
15369
- const ppState = pp["state"];
15370
- if (ppState?.["output"] === "[superseded by duplicate call]") continue;
15371
- if (typeof ppState?.["output"] === "string" && simpleHash(ppState["output"]) === outputHash) {
15372
- ppState["output"] = "[superseded by duplicate call]";
15373
- deduped++;
15374
- }
15375
- }
15376
- }
15377
- seen.set(signature, { msgIdx: i, outputHash });
15378
- } else {
15379
- seen.set(signature, { msgIdx: i, outputHash });
15380
- }
15381
- }
15327
+ function buildNudgeText(level) {
15328
+ if (level === "high") {
15329
+ return '\n<dm-nudge level="high">Context pressure is high. Consider summarizing old completed tasks and moving on. Use memory_store to persist important findings before they are compressed.</dm-nudge>';
15382
15330
  }
15383
- return deduped;
15331
+ return "";
15384
15332
  }
15385
- function simpleHash(s) {
15386
- const len = s.length;
15387
- const sampleSize = 500;
15388
- let h = len;
15389
- for (let i = 0; i < Math.min(len, sampleSize); i++) {
15390
- h = h * 31 + s.charCodeAt(i) | 0;
15333
+
15334
+ // src/compress/memory-nudge.ts
15335
+ var MEMORY_NUDGE_COOLDOWN = 3;
15336
+ var DECISION_PATTERNS = [
15337
+ /\b(?:decided|decision|chose|chosen|picked|selected)\b/i,
15338
+ /\b(?:采用|选择|决定|确定|选用)\b/,
15339
+ /\b(?:use|using|go with|went with)\b.*\b(?:because|since|due to)\b/i
15340
+ ];
15341
+ var CONSTRAINT_PATTERNS = [
15342
+ /\b(?:must not|cannot|should not|do not|never|always)\b/i,
15343
+ /\b(?:constraint|restriction|limitation|requirement)\b/i,
15344
+ /\b(?:不能|必须|禁止|约束|限制|要求|务必)\b/
15345
+ ];
15346
+ var ERROR_FIX_PATTERNS = [
15347
+ /\b(?:fix|fixed|resolve|resolved|patch|corrected)\b/i,
15348
+ /\b(?:修复|修复了|解决|解决了)\b/,
15349
+ /\b(?:the (?:bug|error|issue) (?:was|is)|root cause)\b/i
15350
+ ];
15351
+ function detectMemoryNudge(messages, messagesSinceLastNudge) {
15352
+ if (messagesSinceLastNudge < MEMORY_NUDGE_COOLDOWN) {
15353
+ return { injected: false, type: null };
15391
15354
  }
15392
- const tailStart = Math.max(sampleSize, len - sampleSize);
15393
- for (let i = tailStart; i < len; i++) {
15394
- h = h * 31 + s.charCodeAt(i) | 0;
15355
+ const protectedTail = Math.max(0, messages.length - 3);
15356
+ const recentMessages = messages.slice(protectedTail);
15357
+ const recentAssistantText = recentMessages.filter((m) => m.info.role === "assistant").flatMap((m) => m.parts.filter((p) => p.type === "text").map((p) => p.text || "")).join("\n");
15358
+ const recentUserText = recentMessages.filter((m) => m.info.role === "user").flatMap((m) => m.parts.filter((p) => p.type === "text").map((p) => p.text || "")).join("\n");
15359
+ const hasRecentToolError = recentMessages.some(
15360
+ (m) => m.parts.some((p) => p.type === "tool" && p.state?.status === "error")
15361
+ );
15362
+ if (hasRecentToolError && ERROR_FIX_PATTERNS.some((p) => p.test(recentAssistantText))) {
15363
+ return { injected: true, type: "gotcha" };
15395
15364
  }
15396
- return `${len}:${h.toString(36)}`;
15365
+ if (CONSTRAINT_PATTERNS.some((p) => p.test(recentUserText))) {
15366
+ return { injected: true, type: "constraint" };
15367
+ }
15368
+ if (DECISION_PATTERNS.some((p) => p.test(recentAssistantText))) {
15369
+ return { injected: true, type: "decision" };
15370
+ }
15371
+ return { injected: false, type: null };
15397
15372
  }
15398
-
15399
- // src/compress/error-purge.ts
15400
- var ERROR_PURGE_TURN_THRESHOLD = 4;
15401
- function purgeOldErrors(messages) {
15402
- let purged = 0;
15403
- const totalMessages = messages.length;
15404
- for (let i = 0; i < totalMessages; i++) {
15405
- const msg = messages[i];
15406
- for (const part of msg.parts) {
15407
- if (typeof part !== "object" || part === null) continue;
15408
- const p = part;
15409
- if (p["type"] !== "tool") continue;
15410
- const toolState = p["state"];
15411
- if (toolState?.["status"] !== "error") continue;
15412
- const age = totalMessages - i;
15413
- if (age < ERROR_PURGE_TURN_THRESHOLD) continue;
15414
- if (typeof toolState["input"] === "object" && toolState["input"] !== null) {
15415
- const input = toolState["input"];
15416
- for (const key of Object.keys(input)) {
15417
- if (key === "command" || key === "query" || key === "path" || key === "filePath") continue;
15418
- delete input[key];
15419
- }
15420
- }
15421
- purged++;
15422
- }
15373
+ function buildMemoryNudge(type) {
15374
+ switch (type) {
15375
+ case "gotcha":
15376
+ return `
15377
+ <memory-nudge type="gotcha">You just fixed an error. Use memory_store(type="gotcha") to save what went wrong and how you fixed it, so future sessions don't repeat this mistake.</memory-nudge>`;
15378
+ case "constraint":
15379
+ return '\n<memory-nudge type="constraint">The user expressed a constraint or rule. Use memory_store(type="constraint") to persist it across sessions.</memory-nudge>';
15380
+ case "decision":
15381
+ return `
15382
+ <memory-nudge type="decision">A technical decision was made. Use memory_store(type="decision") to record what was decided and why, so future sessions don't re-decide.</memory-nudge>`;
15383
+ default:
15384
+ return "";
15423
15385
  }
15424
- return purged;
15386
+ }
15387
+
15388
+ // src/compress/dedup.ts
15389
+ function createToolSignature(tool5, args) {
15390
+ if (!args) return tool5;
15391
+ const sorted = Object.keys(args).sort().map((k) => `${k}:${JSON.stringify(args[k])}`).join(",");
15392
+ return `${tool5}::${sorted}`;
15425
15393
  }
15426
15394
 
15427
15395
  // src/compress/tool-compress.ts
@@ -15571,74 +15539,6 @@ function sha2562(data) {
15571
15539
  return createHash3("sha256").update(data).digest("hex");
15572
15540
  }
15573
15541
 
15574
- // src/compress/nudge.ts
15575
- var NUDGE_COOLDOWN = 5;
15576
- function shouldInjectNudge(level, messagesSinceLastNudge) {
15577
- if (level !== "high") return false;
15578
- if (messagesSinceLastNudge < NUDGE_COOLDOWN) return false;
15579
- return true;
15580
- }
15581
- function buildNudgeText(level) {
15582
- if (level === "high") {
15583
- return '\n<dm-nudge level="high">Context pressure is high. Consider summarizing old completed tasks and moving on. Use memory_store to persist important findings before they are compressed.</dm-nudge>';
15584
- }
15585
- return "";
15586
- }
15587
-
15588
- // src/compress/memory-nudge.ts
15589
- var MEMORY_NUDGE_COOLDOWN = 3;
15590
- var DECISION_PATTERNS = [
15591
- /\b(?:decided|decision|chose|chosen|picked|selected)\b/i,
15592
- /\b(?:采用|选择|决定|确定|选用)\b/,
15593
- /\b(?:use|using|go with|went with)\b.*\b(?:because|since|due to)\b/i
15594
- ];
15595
- var CONSTRAINT_PATTERNS = [
15596
- /\b(?:must not|cannot|should not|do not|never|always)\b/i,
15597
- /\b(?:constraint|restriction|limitation|requirement)\b/i,
15598
- /\b(?:不能|必须|禁止|约束|限制|要求|务必)\b/
15599
- ];
15600
- var ERROR_FIX_PATTERNS = [
15601
- /\b(?:fix|fixed|resolve|resolved|patch|corrected)\b/i,
15602
- /\b(?:修复|修复了|解决|解决了)\b/,
15603
- /\b(?:the (?:bug|error|issue) (?:was|is)|root cause)\b/i
15604
- ];
15605
- function detectMemoryNudge(messages, messagesSinceLastNudge) {
15606
- if (messagesSinceLastNudge < MEMORY_NUDGE_COOLDOWN) {
15607
- return { injected: false, type: null };
15608
- }
15609
- const protectedTail = Math.max(0, messages.length - 3);
15610
- const recentMessages = messages.slice(protectedTail);
15611
- const recentAssistantText = recentMessages.filter((m) => m.info.role === "assistant").flatMap((m) => m.parts.filter((p) => p.type === "text").map((p) => p.text || "")).join("\n");
15612
- const recentUserText = recentMessages.filter((m) => m.info.role === "user").flatMap((m) => m.parts.filter((p) => p.type === "text").map((p) => p.text || "")).join("\n");
15613
- const hasRecentToolError = recentMessages.some(
15614
- (m) => m.parts.some((p) => p.type === "tool" && p.state?.status === "error")
15615
- );
15616
- if (hasRecentToolError && ERROR_FIX_PATTERNS.some((p) => p.test(recentAssistantText))) {
15617
- return { injected: true, type: "gotcha" };
15618
- }
15619
- if (CONSTRAINT_PATTERNS.some((p) => p.test(recentUserText))) {
15620
- return { injected: true, type: "constraint" };
15621
- }
15622
- if (DECISION_PATTERNS.some((p) => p.test(recentAssistantText))) {
15623
- return { injected: true, type: "decision" };
15624
- }
15625
- return { injected: false, type: null };
15626
- }
15627
- function buildMemoryNudge(type) {
15628
- switch (type) {
15629
- case "gotcha":
15630
- return `
15631
- <memory-nudge type="gotcha">You just fixed an error. Use memory_store(type="gotcha") to save what went wrong and how you fixed it, so future sessions don't repeat this mistake.</memory-nudge>`;
15632
- case "constraint":
15633
- return '\n<memory-nudge type="constraint">The user expressed a constraint or rule. Use memory_store(type="constraint") to persist it across sessions.</memory-nudge>';
15634
- case "decision":
15635
- return `
15636
- <memory-nudge type="decision">A technical decision was made. Use memory_store(type="decision") to record what was decided and why, so future sessions don't re-decide.</memory-nudge>`;
15637
- default:
15638
- return "";
15639
- }
15640
- }
15641
-
15642
15542
  // src/compress/detector.ts
15643
15543
  function detectContentType(content) {
15644
15544
  const trimmed = content.trimStart();
@@ -15661,36 +15561,177 @@ function detectContentType(content) {
15661
15561
  return "text";
15662
15562
  }
15663
15563
 
15664
- // src/compress/index.ts
15665
- function runCompressionPipeline(ctx) {
15666
- const { messages, state, logger } = ctx;
15667
- const pressure = detectPressure(messages, state.getModelContextWindow());
15668
- state.recordInputTokens(pressure.estimatedTokens);
15564
+ // src/compress/single-pass.ts
15565
+ var PROTECTED_TOOLS = /* @__PURE__ */ new Set([
15566
+ "question",
15567
+ "edit",
15568
+ "write",
15569
+ "todowrite",
15570
+ "memory_store",
15571
+ "memory_search",
15572
+ "memory_forget",
15573
+ "memory_expand",
15574
+ "deep_expand"
15575
+ ]);
15576
+ var NEVER_DEDUP = /* @__PURE__ */ new Set(["read", "bash", "grep", "glob", "find", "search"]);
15577
+ var ERROR_PURGE_TURN_THRESHOLD = 4;
15578
+ var PROTECTED_HEAD = 2;
15579
+ function simpleHash(s) {
15580
+ const len = s.length;
15581
+ const sampleSize = 500;
15582
+ let h = len;
15583
+ for (let i = 0; i < Math.min(len, sampleSize); i++) {
15584
+ h = h * 31 + s.charCodeAt(i) | 0;
15585
+ }
15586
+ const tailStart = Math.max(sampleSize, len - sampleSize);
15587
+ for (let i = tailStart; i < len; i++) {
15588
+ h = h * 31 + s.charCodeAt(i) | 0;
15589
+ }
15590
+ return `${len}:${h.toString(36)}`;
15591
+ }
15592
+ function singlePassCompress(messages, state, protectedTail) {
15669
15593
  const stats = {
15670
15594
  toolDedup: 0,
15671
15595
  errorPurge: 0,
15672
15596
  toolOutputCompressed: 0,
15673
15597
  jsonCrushed: 0,
15674
- ccrStored: 0,
15598
+ ccrStored: 0
15599
+ };
15600
+ const totalMessages = messages.length;
15601
+ if (totalMessages <= PROTECTED_HEAD) return stats;
15602
+ const seen = /* @__PURE__ */ new Map();
15603
+ for (let i = PROTECTED_HEAD; i < totalMessages; i++) {
15604
+ const msg = messages[i];
15605
+ if (!msg?.parts?.length) continue;
15606
+ if (msg.info.role === "user") continue;
15607
+ for (let j = msg.parts.length - 1; j >= 0; j--) {
15608
+ const part = msg.parts[j];
15609
+ if (typeof part !== "object" || part === null) continue;
15610
+ const p = part;
15611
+ if (p["type"] !== "tool") continue;
15612
+ const toolName = p["tool"];
15613
+ const callID = p["callID"];
15614
+ const toolState = p["state"];
15615
+ if (toolState?.["status"] === "error") {
15616
+ const age = totalMessages - i;
15617
+ if (age >= ERROR_PURGE_TURN_THRESHOLD) {
15618
+ if (typeof toolState["input"] === "object" && toolState["input"] !== null) {
15619
+ const input = toolState["input"];
15620
+ for (const key of Object.keys(input)) {
15621
+ if (key === "command" || key === "query" || key === "path" || key === "filePath") continue;
15622
+ delete input[key];
15623
+ }
15624
+ }
15625
+ stats.errorPurge++;
15626
+ }
15627
+ }
15628
+ if (i >= protectedTail) continue;
15629
+ if (!toolName || !callID) continue;
15630
+ if (toolState?.["status"] !== "completed") continue;
15631
+ const output = typeof toolState?.["output"] === "string" ? toolState["output"] : void 0;
15632
+ if (!output) continue;
15633
+ if (output === "[superseded by duplicate call]") continue;
15634
+ if (output.includes("[ccr:")) continue;
15635
+ if (!PROTECTED_TOOLS.has(toolName) && !NEVER_DEDUP.has(toolName)) {
15636
+ const input = toolState["input"];
15637
+ const signature = createToolSignature(toolName, input);
15638
+ const outputHash = simpleHash(output);
15639
+ const existing = seen.get(signature);
15640
+ if (existing) {
15641
+ if (existing.outputHash === outputHash) {
15642
+ const prevMsg = messages[existing.msgIdx];
15643
+ for (const prevPart of prevMsg.parts) {
15644
+ if (typeof prevPart !== "object" || prevPart === null) continue;
15645
+ const pp = prevPart;
15646
+ const ppState = pp["state"];
15647
+ if (ppState?.["output"] === "[superseded by duplicate call]") continue;
15648
+ if (typeof ppState?.["output"] === "string" && simpleHash(ppState["output"]) === outputHash) {
15649
+ ppState["output"] = "[superseded by duplicate call]";
15650
+ stats.toolDedup++;
15651
+ }
15652
+ }
15653
+ }
15654
+ seen.set(signature, { msgIdx: i, outputHash });
15655
+ } else {
15656
+ seen.set(signature, { msgIdx: i, outputHash });
15657
+ }
15658
+ }
15659
+ if (output.length >= 500) {
15660
+ const result = compressToolOutput(toolName, output);
15661
+ if (result.length < output.length * 0.7) {
15662
+ const hash2 = ccrStore(state, output, result, toolName, callID);
15663
+ toolState["output"] = ccrInjectMarker(result, hash2);
15664
+ stats.toolOutputCompressed++;
15665
+ continue;
15666
+ }
15667
+ }
15668
+ if (output.length >= 500 && detectContentType(output) === "json") {
15669
+ const crushed = crushJsonArray(output);
15670
+ if (crushed.length < output.length * 0.7) {
15671
+ const hash2 = ccrStore(state, output, crushed, toolName, callID);
15672
+ toolState["output"] = ccrInjectMarker(crushed, hash2);
15673
+ stats.jsonCrushed++;
15674
+ }
15675
+ }
15676
+ }
15677
+ }
15678
+ return stats;
15679
+ }
15680
+
15681
+ // src/compress/index.ts
15682
+ var KEEP_RECENT_TOKENS = 4e3;
15683
+ function estimateMessageTokens(msg) {
15684
+ let t = 0;
15685
+ for (const part of msg.parts) {
15686
+ if (typeof part !== "object" || part === null) continue;
15687
+ const p = part;
15688
+ const text = typeof p["text"] === "string" ? p["text"] : "";
15689
+ if (p["type"] === "tool") {
15690
+ const s = p["state"];
15691
+ const out = typeof s?.["output"] === "string" ? s["output"] : "";
15692
+ t += Math.ceil((text.length + out.length) / 4);
15693
+ } else {
15694
+ t += Math.ceil(text.length / 4);
15695
+ }
15696
+ }
15697
+ return t;
15698
+ }
15699
+ function computeProtectedTail(messages) {
15700
+ let tokens = 0;
15701
+ for (let i = messages.length - 1; i >= 0; i--) {
15702
+ tokens += estimateMessageTokens(messages[i]);
15703
+ if (tokens >= KEEP_RECENT_TOKENS) return i;
15704
+ }
15705
+ return 0;
15706
+ }
15707
+ function runCompressionPipeline(ctx) {
15708
+ const { messages, state, sessionID, logger } = ctx;
15709
+ const pressure = detectPressure(messages, state.getModelContextWindow());
15710
+ state.recordInputTokens(pressure.estimatedTokens);
15711
+ const protectedTail = computeProtectedTail(messages);
15712
+ const spStats = singlePassCompress(messages, state, protectedTail);
15713
+ const stats = {
15714
+ toolDedup: spStats.toolDedup,
15715
+ errorPurge: spStats.errorPurge,
15716
+ toolOutputCompressed: spStats.toolOutputCompressed,
15717
+ jsonCrushed: spStats.jsonCrushed,
15718
+ ccrStored: spStats.ccrStored,
15675
15719
  nudgeInjected: false,
15676
15720
  pressureLevel: pressure.level,
15677
15721
  estimatedTokens: pressure.estimatedTokens
15678
15722
  };
15679
- stats.toolDedup = deduplicateToolOutputs(messages, state);
15680
- stats.errorPurge = purgeOldErrors(messages);
15681
- stats.jsonCrushed = crushJsonToolOutputs(messages, state);
15682
- stats.toolOutputCompressed = compressOldToolOutputs(messages, state);
15683
- const messagesSinceNudge = state.messagesSinceLastNudge(messages.length);
15723
+ const sid = sessionID || "default";
15724
+ const messagesSinceNudge = state.messagesSinceLastNudge(sid, messages.length);
15684
15725
  if (shouldInjectNudge(pressure.level, messagesSinceNudge)) {
15685
15726
  if (injectIntoLastAssistant(messages, buildNudgeText(pressure.level))) {
15686
15727
  stats.nudgeInjected = true;
15687
- state.recordNudge(messages.length);
15728
+ state.recordNudge(sid, messages.length);
15688
15729
  }
15689
15730
  }
15690
- const memoryNudge = detectMemoryNudge(messages, state.messagesSinceLastNudge(messages.length));
15731
+ const memoryNudge = detectMemoryNudge(messages, state.messagesSinceLastNudge(sid, messages.length));
15691
15732
  if (memoryNudge.injected) {
15692
15733
  if (injectIntoLastAssistant(messages, buildMemoryNudge(memoryNudge.type))) {
15693
- state.recordNudge(messages.length);
15734
+ state.recordNudge(sid, messages.length);
15694
15735
  logger?.debug("compress: memory nudge", { type: memoryNudge.type });
15695
15736
  }
15696
15737
  }
@@ -15706,70 +15747,19 @@ function injectIntoLastAssistant(messages, text) {
15706
15747
  for (let i = messages.length - 1; i >= 0; i--) {
15707
15748
  const msg = messages[i];
15708
15749
  if (msg.info.role !== "assistant") continue;
15709
- const textParts = msg.parts.filter(
15710
- (p) => typeof p === "object" && p !== null && p.type === "text"
15711
- );
15712
- const lastTextPart = textParts[textParts.length - 1];
15713
- if (lastTextPart && typeof lastTextPart.text === "string") {
15714
- lastTextPart.text += text;
15715
- return true;
15716
- }
15717
- }
15718
- return false;
15719
- }
15720
- function compressOldToolOutputs(messages, state) {
15721
- let compressed = 0;
15722
- const protectedTail = messages.length - 8;
15723
- for (let i = 3; i < protectedTail; i++) {
15724
- const msg = messages[i];
15725
- for (const part of msg.parts) {
15726
- if (typeof part !== "object" || part === null) continue;
15727
- const p = part;
15728
- if (p.type !== "tool") continue;
15729
- if (p.state?.status !== "completed") continue;
15730
- if (!p.state.output) continue;
15731
- if (p.state.output === "[superseded by duplicate call]") continue;
15732
- if (p.state.output.includes("[ccr:")) continue;
15733
- const toolName = p.tool || "unknown";
15734
- const output = p.state.output;
15735
- const result = compressToolOutput(toolName, output);
15736
- if (result.length < output.length * 0.7) {
15737
- const hash2 = ccrStore(state, output, result, toolName, p.callID);
15738
- p.state.output = ccrInjectMarker(result, hash2);
15739
- compressed++;
15740
- }
15741
- }
15742
- }
15743
- return compressed;
15744
- }
15745
- function crushJsonToolOutputs(messages, state) {
15746
- let crushed = 0;
15747
- const protectedTail = messages.length - 8;
15748
- for (let i = 3; i < protectedTail; i++) {
15749
- const msg = messages[i];
15750
- for (const part of msg.parts) {
15751
- if (typeof part !== "object" || part === null) continue;
15752
- const p = part;
15753
- if (p.type !== "tool") continue;
15754
- if (p.state?.status !== "completed") continue;
15755
- if (!p.state.output) continue;
15756
- if (p.state.output.startsWith("[superseded")) continue;
15757
- if (p.state.output.includes("[ccr:")) continue;
15758
- if (detectContentType(p.state.output) !== "json") continue;
15759
- const original = p.state.output;
15760
- const crushed_output = crushJsonArray(original);
15761
- if (crushed_output.length < original.length * 0.7) {
15762
- const hash2 = ccrStore(state, original, crushed_output, p.tool, p.callID);
15763
- p.state.output = ccrInjectMarker(crushed_output, hash2);
15764
- crushed++;
15750
+ for (let j = msg.parts.length - 1; j >= 0; j--) {
15751
+ const p = msg.parts[j];
15752
+ if (p["type"] === "text" && typeof p["text"] === "string") {
15753
+ p["text"] += text;
15754
+ return true;
15765
15755
  }
15766
15756
  }
15767
15757
  }
15768
- return crushed;
15758
+ return false;
15769
15759
  }
15770
15760
 
15771
15761
  // src/hooks/messages-transform.ts
15772
- var KEEP_RECENT2 = 8;
15762
+ var KEEP_RECENT = 8;
15773
15763
  var PROTECTED_HEAD2 = 3;
15774
15764
  var SYSTEM_INJECTION_PATTERNS = [
15775
15765
  /^$/,
@@ -15854,11 +15844,11 @@ function repairOrphanedToolCalls(messages) {
15854
15844
  }
15855
15845
  }
15856
15846
  function createMessagesTransformHandler(state, logger) {
15857
- return async (_input, output) => {
15847
+ return async (input, output) => {
15858
15848
  const messages = output.messages;
15859
- if (messages.length <= KEEP_RECENT2) return;
15860
- if (messages.length <= KEEP_RECENT2 + PROTECTED_HEAD2) return;
15861
- const protectedTailStart = messages.length - KEEP_RECENT2;
15849
+ if (messages.length <= KEEP_RECENT) return;
15850
+ if (messages.length <= KEEP_RECENT + PROTECTED_HEAD2) return;
15851
+ const protectedTailStart = messages.length - KEEP_RECENT;
15862
15852
  const stats = {
15863
15853
  reasoning_cleared: 0,
15864
15854
  metadata_stripped: 0,
@@ -15931,6 +15921,7 @@ function createMessagesTransformHandler(state, logger) {
15931
15921
  const pipelineResult = runCompressionPipeline({
15932
15922
  messages: output.messages,
15933
15923
  state,
15924
+ sessionID: input["sessionID"],
15934
15925
  logger
15935
15926
  });
15936
15927
  const ds = pipelineResult.stats;
@@ -15941,14 +15932,14 @@ function createMessagesTransformHandler(state, logger) {
15941
15932
  deepCompression: ds,
15942
15933
  messageCount: messages.length,
15943
15934
  protectedHead: PROTECTED_HEAD2,
15944
- protectedTail: KEEP_RECENT2
15935
+ protectedTail: KEEP_RECENT
15945
15936
  });
15946
15937
  } else if (Object.values(stats).some((v) => v > 0)) {
15947
15938
  state.mergeNotify({
15948
15939
  compression: stats,
15949
15940
  messageCount: messages.length,
15950
15941
  protectedHead: PROTECTED_HEAD2,
15951
- protectedTail: KEEP_RECENT2
15942
+ protectedTail: KEEP_RECENT
15952
15943
  });
15953
15944
  }
15954
15945
  };