@bd7pil/opencode-deep-memory 0.5.0 → 0.5.2
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 +260 -269
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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 =
|
|
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
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
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
|
|
419
|
+
recordNudge(sessionID, messageCount) {
|
|
420
|
+
this._lastNudgeMessageCount.set(sessionID, messageCount);
|
|
415
421
|
}
|
|
416
|
-
messagesSinceLastNudge(currentMessageCount) {
|
|
417
|
-
|
|
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;
|
|
@@ -15229,8 +15236,8 @@ function createCompactingHandler(args) {
|
|
|
15229
15236
|
var FALLBACK_MAX_CONTEXT = 128e3;
|
|
15230
15237
|
var OPENCODE_COMPACTION_RATIO = 0.75;
|
|
15231
15238
|
var THRESHOLDS = {
|
|
15232
|
-
medium: 0.
|
|
15233
|
-
high: 0.
|
|
15239
|
+
medium: 0.2,
|
|
15240
|
+
high: 0.4
|
|
15234
15241
|
};
|
|
15235
15242
|
var calibratedMaxContext = 0;
|
|
15236
15243
|
function calibrateFromCompaction(lastInputTokens) {
|
|
@@ -15310,118 +15317,79 @@ function detectPressure(messages, modelContextWindow) {
|
|
|
15310
15317
|
return { level, ratio, estimatedTokens: estimated, maxContext: ctx };
|
|
15311
15318
|
}
|
|
15312
15319
|
|
|
15313
|
-
// src/compress/
|
|
15314
|
-
var
|
|
15315
|
-
|
|
15316
|
-
"
|
|
15317
|
-
|
|
15318
|
-
|
|
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 = 3;
|
|
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
|
|
15335
|
-
|
|
15336
|
-
|
|
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
|
|
15331
|
+
return "";
|
|
15384
15332
|
}
|
|
15385
|
-
|
|
15386
|
-
|
|
15387
|
-
|
|
15388
|
-
|
|
15389
|
-
|
|
15390
|
-
|
|
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
|
|
15393
|
-
|
|
15394
|
-
|
|
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
|
-
|
|
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
|
-
|
|
15400
|
-
|
|
15401
|
-
|
|
15402
|
-
|
|
15403
|
-
|
|
15404
|
-
|
|
15405
|
-
|
|
15406
|
-
|
|
15407
|
-
|
|
15408
|
-
|
|
15409
|
-
|
|
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
|
-
|
|
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
|
|
@@ -15443,7 +15411,7 @@ var DEFAULT_HEAD_LINES = 50;
|
|
|
15443
15411
|
var DEFAULT_TAIL_LINES = 20;
|
|
15444
15412
|
var MAX_LINE_LENGTH = 500;
|
|
15445
15413
|
function compressToolOutput(toolName, output) {
|
|
15446
|
-
if (!output || output.length <
|
|
15414
|
+
if (!output || output.length < 200) return output;
|
|
15447
15415
|
const strategy = TOOL_COMPRESS_STRATEGIES[toolName];
|
|
15448
15416
|
if (strategy) return strategy(output);
|
|
15449
15417
|
return compressGeneric(output);
|
|
@@ -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/
|
|
15665
|
-
|
|
15666
|
-
|
|
15667
|
-
|
|
15668
|
-
|
|
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_SINGLE = 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_SINGLE) return stats;
|
|
15602
|
+
const seen = /* @__PURE__ */ new Map();
|
|
15603
|
+
for (let i = PROTECTED_HEAD_SINGLE; 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 >= 200) {
|
|
15660
|
+
const result = compressToolOutput(toolName, output);
|
|
15661
|
+
if (result.length < output.length * 0.85) {
|
|
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 >= 200 && detectContentType(output) === "json") {
|
|
15669
|
+
const crushed = crushJsonArray(output);
|
|
15670
|
+
if (crushed.length < output.length * 0.85) {
|
|
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
|
-
|
|
15680
|
-
|
|
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,71 +15747,20 @@ 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
|
-
|
|
15710
|
-
|
|
15711
|
-
|
|
15712
|
-
|
|
15713
|
-
|
|
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
|
|
15758
|
+
return false;
|
|
15769
15759
|
}
|
|
15770
15760
|
|
|
15771
15761
|
// src/hooks/messages-transform.ts
|
|
15772
|
-
var
|
|
15773
|
-
var
|
|
15762
|
+
var KEEP_RECENT = 8;
|
|
15763
|
+
var PROTECTED_HEAD = 3;
|
|
15774
15764
|
var SYSTEM_INJECTION_PATTERNS = [
|
|
15775
15765
|
/^$/,
|
|
15776
15766
|
/^<!-- OMO_INTERNAL_INITIATOR -->$/,
|
|
@@ -15854,11 +15844,11 @@ function repairOrphanedToolCalls(messages) {
|
|
|
15854
15844
|
}
|
|
15855
15845
|
}
|
|
15856
15846
|
function createMessagesTransformHandler(state, logger) {
|
|
15857
|
-
return async (
|
|
15847
|
+
return async (input, output) => {
|
|
15858
15848
|
const messages = output.messages;
|
|
15859
|
-
if (messages.length <=
|
|
15860
|
-
if (messages.length <=
|
|
15861
|
-
const protectedTailStart = messages.length -
|
|
15849
|
+
if (messages.length <= KEEP_RECENT) return;
|
|
15850
|
+
if (messages.length <= KEEP_RECENT + PROTECTED_HEAD) return;
|
|
15851
|
+
const protectedTailStart = messages.length - KEEP_RECENT;
|
|
15862
15852
|
const stats = {
|
|
15863
15853
|
reasoning_cleared: 0,
|
|
15864
15854
|
metadata_stripped: 0,
|
|
@@ -15867,7 +15857,7 @@ function createMessagesTransformHandler(state, logger) {
|
|
|
15867
15857
|
thinking_stripped: 0
|
|
15868
15858
|
};
|
|
15869
15859
|
const toRemove = [];
|
|
15870
|
-
for (let i =
|
|
15860
|
+
for (let i = PROTECTED_HEAD; i < protectedTailStart; i++) {
|
|
15871
15861
|
const msg = messages[i];
|
|
15872
15862
|
if (!msg?.parts?.length) continue;
|
|
15873
15863
|
if (msg.info.role === "user") continue;
|
|
@@ -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;
|
|
@@ -15940,15 +15931,15 @@ function createMessagesTransformHandler(state, logger) {
|
|
|
15940
15931
|
compression: stats,
|
|
15941
15932
|
deepCompression: ds,
|
|
15942
15933
|
messageCount: messages.length,
|
|
15943
|
-
protectedHead:
|
|
15944
|
-
protectedTail:
|
|
15934
|
+
protectedHead: PROTECTED_HEAD,
|
|
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
|
-
protectedHead:
|
|
15951
|
-
protectedTail:
|
|
15941
|
+
protectedHead: PROTECTED_HEAD,
|
|
15942
|
+
protectedTail: KEEP_RECENT
|
|
15952
15943
|
});
|
|
15953
15944
|
}
|
|
15954
15945
|
};
|