@bd7pil/opencode-deep-memory 0.4.1 → 0.4.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 +130 -19
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -836,7 +836,7 @@ ${lines.join("\n")}
|
|
|
836
836
|
}
|
|
837
837
|
|
|
838
838
|
// src/inject/system-payload.ts
|
|
839
|
-
var TOOL_HINT = 'Memory tools available: memory_search, memory_store, memory_forget. Guidelines
|
|
839
|
+
var TOOL_HINT = 'Memory tools available: memory_search, memory_store, memory_forget, memory_expand, deep_expand. Guidelines:\n (1) BEFORE making ANY technical decision, search: memory_search(query="decision OR decided OR chose OR \u9009\u62E9 OR \u51B3\u5B9A", scope="project")\n (2) BEFORE fixing an error, search for known pitfalls: memory_search(query="gotcha OR error OR bug OR \u5751 OR \u9519\u8BEF", scope="project")\n (3) AFTER fixing an error, store it: memory_store(type="gotcha", content="[error]: ... \u2192 [fix]: ...", scope="project")\n (4) WHEN user states a constraint/rule, store it: memory_store(type="constraint", content="...", scope="project")\n (5) WHEN a technical decision is made, store it: memory_store(type="decision", content="[decision]: ... \u2192 [reason]: ...", scope="project")';
|
|
840
840
|
async function composeSystemPayload(opts) {
|
|
841
841
|
const { state, sessionID, projectPath, mode, searchService, userQuery, logger, tracker } = opts;
|
|
842
842
|
const agent = sessionID ? state.agentOf(sessionID) : void 0;
|
|
@@ -15294,7 +15294,21 @@ function detectPressure(messages, modelId) {
|
|
|
15294
15294
|
}
|
|
15295
15295
|
|
|
15296
15296
|
// src/compress/dedup.ts
|
|
15297
|
-
var PROTECTED_TOOLS = /* @__PURE__ */ new Set([
|
|
15297
|
+
var PROTECTED_TOOLS = /* @__PURE__ */ new Set([
|
|
15298
|
+
"question",
|
|
15299
|
+
"edit",
|
|
15300
|
+
"write",
|
|
15301
|
+
"todowrite",
|
|
15302
|
+
"todoread",
|
|
15303
|
+
"memory_store",
|
|
15304
|
+
"memory_search",
|
|
15305
|
+
"memory_forget",
|
|
15306
|
+
"memory_expand",
|
|
15307
|
+
"deep_expand"
|
|
15308
|
+
]);
|
|
15309
|
+
var NEVER_DEDUP = /* @__PURE__ */ new Set(["read", "bash", "grep", "glob", "find", "search"]);
|
|
15310
|
+
var KEEP_RECENT = 8;
|
|
15311
|
+
var PROTECTED_HEAD = 3;
|
|
15298
15312
|
function createToolSignature(tool5, args) {
|
|
15299
15313
|
if (!args) return tool5;
|
|
15300
15314
|
const sorted = Object.keys(args).sort().map((k) => `${k}:${JSON.stringify(args[k])}`).join(",");
|
|
@@ -15302,8 +15316,12 @@ function createToolSignature(tool5, args) {
|
|
|
15302
15316
|
}
|
|
15303
15317
|
function deduplicateToolOutputs(messages, state) {
|
|
15304
15318
|
let deduped = 0;
|
|
15319
|
+
const totalMessages = messages.length;
|
|
15320
|
+
if (totalMessages <= KEEP_RECENT + PROTECTED_HEAD) return 0;
|
|
15321
|
+
const protectedTailStart = totalMessages - KEEP_RECENT;
|
|
15305
15322
|
const seen = /* @__PURE__ */ new Map();
|
|
15306
|
-
for (
|
|
15323
|
+
for (let i = PROTECTED_HEAD; i < protectedTailStart; i++) {
|
|
15324
|
+
const msg = messages[i];
|
|
15307
15325
|
for (const part of msg.parts) {
|
|
15308
15326
|
if (typeof part !== "object" || part === null) continue;
|
|
15309
15327
|
const p = part;
|
|
@@ -15312,24 +15330,48 @@ function deduplicateToolOutputs(messages, state) {
|
|
|
15312
15330
|
const callID = p["callID"];
|
|
15313
15331
|
if (!toolName || !callID) continue;
|
|
15314
15332
|
if (PROTECTED_TOOLS.has(toolName)) continue;
|
|
15333
|
+
if (NEVER_DEDUP.has(toolName)) continue;
|
|
15315
15334
|
const status = p["state"]?.["status"];
|
|
15316
15335
|
if (status !== "completed") continue;
|
|
15317
15336
|
const toolState = p["state"];
|
|
15337
|
+
const output = toolState["output"];
|
|
15338
|
+
if (typeof output !== "string") continue;
|
|
15339
|
+
if (output === "[superseded by duplicate call]") continue;
|
|
15340
|
+
if (output.includes("[ccr:")) continue;
|
|
15318
15341
|
const input = toolState["input"];
|
|
15319
15342
|
const signature = createToolSignature(toolName, input);
|
|
15343
|
+
const outputHash = simpleHash(output);
|
|
15320
15344
|
const existing = seen.get(signature);
|
|
15321
|
-
if (existing
|
|
15322
|
-
|
|
15323
|
-
|
|
15324
|
-
|
|
15345
|
+
if (existing) {
|
|
15346
|
+
if (existing.outputHash === outputHash) {
|
|
15347
|
+
const prevMsg = messages[existing.msgIdx];
|
|
15348
|
+
for (const prevPart of prevMsg.parts) {
|
|
15349
|
+
if (typeof prevPart !== "object" || prevPart === null) continue;
|
|
15350
|
+
const pp = prevPart;
|
|
15351
|
+
if (pp["type"] !== "tool") continue;
|
|
15352
|
+
const ppState = pp["state"];
|
|
15353
|
+
if (ppState?.["output"] === "[superseded by duplicate call]") continue;
|
|
15354
|
+
if (typeof ppState?.["output"] === "string" && simpleHash(ppState["output"]) === outputHash) {
|
|
15355
|
+
ppState["output"] = "[superseded by duplicate call]";
|
|
15356
|
+
deduped++;
|
|
15357
|
+
}
|
|
15358
|
+
}
|
|
15359
|
+
}
|
|
15360
|
+
seen.set(signature, { msgIdx: i, outputHash });
|
|
15325
15361
|
} else {
|
|
15326
|
-
seen.set(signature,
|
|
15327
|
-
state.recordToolSignature(callID, signature);
|
|
15362
|
+
seen.set(signature, { msgIdx: i, outputHash });
|
|
15328
15363
|
}
|
|
15329
15364
|
}
|
|
15330
15365
|
}
|
|
15331
15366
|
return deduped;
|
|
15332
15367
|
}
|
|
15368
|
+
function simpleHash(s) {
|
|
15369
|
+
let h = 0;
|
|
15370
|
+
for (let i = 0; i < Math.min(s.length, 1e3); i++) {
|
|
15371
|
+
h = h * 31 + s.charCodeAt(i) | 0;
|
|
15372
|
+
}
|
|
15373
|
+
return h.toString(36);
|
|
15374
|
+
}
|
|
15333
15375
|
|
|
15334
15376
|
// src/compress/error-purge.ts
|
|
15335
15377
|
var ERROR_PURGE_TURN_THRESHOLD = 4;
|
|
@@ -15568,6 +15610,60 @@ function buildNudgeText(level) {
|
|
|
15568
15610
|
return "";
|
|
15569
15611
|
}
|
|
15570
15612
|
|
|
15613
|
+
// src/compress/memory-nudge.ts
|
|
15614
|
+
var MEMORY_NUDGE_COOLDOWN = 3;
|
|
15615
|
+
var DECISION_PATTERNS = [
|
|
15616
|
+
/\b(?:decided|decision|chose|chosen|picked|selected)\b/i,
|
|
15617
|
+
/\b(?:采用|选择|决定|确定|选用)\b/,
|
|
15618
|
+
/\b(?:use|using|go with|went with)\b.*\b(?:because|since|due to)\b/i
|
|
15619
|
+
];
|
|
15620
|
+
var CONSTRAINT_PATTERNS = [
|
|
15621
|
+
/\b(?:must not|cannot|should not|do not|never|always)\b/i,
|
|
15622
|
+
/\b(?:constraint|restriction|limitation|requirement)\b/i,
|
|
15623
|
+
/\b(?:不能|必须|禁止|约束|限制|要求|务必)\b/
|
|
15624
|
+
];
|
|
15625
|
+
var ERROR_FIX_PATTERNS = [
|
|
15626
|
+
/\b(?:fix|fixed|resolve|resolved|patch|corrected)\b/i,
|
|
15627
|
+
/\b(?:修复|修复了|解决|解决了)\b/,
|
|
15628
|
+
/\b(?:the (?:bug|error|issue) (?:was|is)|root cause)\b/i
|
|
15629
|
+
];
|
|
15630
|
+
function detectMemoryNudge(messages, messagesSinceLastNudge) {
|
|
15631
|
+
if (messagesSinceLastNudge < MEMORY_NUDGE_COOLDOWN) {
|
|
15632
|
+
return { injected: false, type: null };
|
|
15633
|
+
}
|
|
15634
|
+
const protectedTail = Math.max(0, messages.length - 3);
|
|
15635
|
+
const recentMessages = messages.slice(protectedTail);
|
|
15636
|
+
const recentAssistantText = recentMessages.filter((m) => m.info.role === "assistant").flatMap((m) => m.parts.filter((p) => p.type === "text").map((p) => p.text || "")).join("\n");
|
|
15637
|
+
const recentUserText = recentMessages.filter((m) => m.info.role === "user").flatMap((m) => m.parts.filter((p) => p.type === "text").map((p) => p.text || "")).join("\n");
|
|
15638
|
+
const hasRecentToolError = recentMessages.some(
|
|
15639
|
+
(m) => m.parts.some((p) => p.type === "tool" && p.state?.status === "error")
|
|
15640
|
+
);
|
|
15641
|
+
if (hasRecentToolError && ERROR_FIX_PATTERNS.some((p) => p.test(recentAssistantText))) {
|
|
15642
|
+
return { injected: true, type: "gotcha" };
|
|
15643
|
+
}
|
|
15644
|
+
if (CONSTRAINT_PATTERNS.some((p) => p.test(recentUserText))) {
|
|
15645
|
+
return { injected: true, type: "constraint" };
|
|
15646
|
+
}
|
|
15647
|
+
if (DECISION_PATTERNS.some((p) => p.test(recentAssistantText))) {
|
|
15648
|
+
return { injected: true, type: "decision" };
|
|
15649
|
+
}
|
|
15650
|
+
return { injected: false, type: null };
|
|
15651
|
+
}
|
|
15652
|
+
function buildMemoryNudge(type) {
|
|
15653
|
+
switch (type) {
|
|
15654
|
+
case "gotcha":
|
|
15655
|
+
return `
|
|
15656
|
+
<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>`;
|
|
15657
|
+
case "constraint":
|
|
15658
|
+
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>';
|
|
15659
|
+
case "decision":
|
|
15660
|
+
return `
|
|
15661
|
+
<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>`;
|
|
15662
|
+
default:
|
|
15663
|
+
return "";
|
|
15664
|
+
}
|
|
15665
|
+
}
|
|
15666
|
+
|
|
15571
15667
|
// src/compress/detector.ts
|
|
15572
15668
|
function detectContentType(content) {
|
|
15573
15669
|
const trimmed = content.trimStart();
|
|
@@ -15628,6 +15724,21 @@ function runCompressionPipeline(ctx) {
|
|
|
15628
15724
|
}
|
|
15629
15725
|
}
|
|
15630
15726
|
}
|
|
15727
|
+
const memoryNudge = detectMemoryNudge(messages, state.messagesSinceLastNudge(messages.length));
|
|
15728
|
+
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
|
+
}
|
|
15740
|
+
}
|
|
15741
|
+
}
|
|
15631
15742
|
const active = stats.toolDedup > 0 || stats.errorPurge > 0 || stats.toolOutputCompressed > 0 || stats.jsonCrushed > 0 || stats.messagePruned > 0 || stats.nudgeInjected;
|
|
15632
15743
|
if (active) {
|
|
15633
15744
|
logger?.debug("compress: pipeline result", { ...stats });
|
|
@@ -15690,8 +15801,8 @@ function crushJsonToolOutputs(messages, state) {
|
|
|
15690
15801
|
}
|
|
15691
15802
|
|
|
15692
15803
|
// src/hooks/messages-transform.ts
|
|
15693
|
-
var
|
|
15694
|
-
var
|
|
15804
|
+
var KEEP_RECENT2 = 8;
|
|
15805
|
+
var PROTECTED_HEAD2 = 3;
|
|
15695
15806
|
var SYSTEM_INJECTION_PATTERNS = [
|
|
15696
15807
|
/^$/,
|
|
15697
15808
|
/^<!-- OMO_INTERNAL_INITIATOR -->$/,
|
|
@@ -15777,9 +15888,9 @@ function repairOrphanedToolCalls(messages) {
|
|
|
15777
15888
|
function createMessagesTransformHandler(state, logger) {
|
|
15778
15889
|
return async (_input, output) => {
|
|
15779
15890
|
const messages = output.messages;
|
|
15780
|
-
if (messages.length <=
|
|
15781
|
-
if (messages.length <=
|
|
15782
|
-
const protectedTailStart = messages.length -
|
|
15891
|
+
if (messages.length <= KEEP_RECENT2) return;
|
|
15892
|
+
if (messages.length <= KEEP_RECENT2 + PROTECTED_HEAD2) return;
|
|
15893
|
+
const protectedTailStart = messages.length - KEEP_RECENT2;
|
|
15783
15894
|
const stats = {
|
|
15784
15895
|
reasoning_cleared: 0,
|
|
15785
15896
|
metadata_stripped: 0,
|
|
@@ -15787,7 +15898,7 @@ function createMessagesTransformHandler(state, logger) {
|
|
|
15787
15898
|
tool_errors_truncated: 0,
|
|
15788
15899
|
thinking_stripped: 0
|
|
15789
15900
|
};
|
|
15790
|
-
for (let i =
|
|
15901
|
+
for (let i = PROTECTED_HEAD2; i < protectedTailStart; i++) {
|
|
15791
15902
|
const msg = messages[i];
|
|
15792
15903
|
if (!msg?.parts?.length) continue;
|
|
15793
15904
|
if (msg.info.role === "user") continue;
|
|
@@ -15859,15 +15970,15 @@ function createMessagesTransformHandler(state, logger) {
|
|
|
15859
15970
|
compression: stats,
|
|
15860
15971
|
deepCompression: ds,
|
|
15861
15972
|
messageCount: messages.length,
|
|
15862
|
-
protectedHead:
|
|
15863
|
-
protectedTail:
|
|
15973
|
+
protectedHead: PROTECTED_HEAD2,
|
|
15974
|
+
protectedTail: KEEP_RECENT2
|
|
15864
15975
|
});
|
|
15865
15976
|
} else if (Object.values(stats).some((v) => v > 0)) {
|
|
15866
15977
|
state.mergeNotify({
|
|
15867
15978
|
compression: stats,
|
|
15868
15979
|
messageCount: messages.length,
|
|
15869
|
-
protectedHead:
|
|
15870
|
-
protectedTail:
|
|
15980
|
+
protectedHead: PROTECTED_HEAD2,
|
|
15981
|
+
protectedTail: KEEP_RECENT2
|
|
15871
15982
|
});
|
|
15872
15983
|
}
|
|
15873
15984
|
};
|