@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 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: (1) Use memory_search to recall past decisions before re-deciding. (2) After encountering a tool error and fixing it, use memory_store with type="gotcha" to save the error+fix pair. (3) When the user states a constraint or rule, use memory_store with type="constraint".';
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(["question", "edit", "write", "todowrite", "todoread", "memory_store", "memory_search", "memory_forget"]);
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 (const msg of messages) {
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 && existing !== callID) {
15322
- toolState["output"] = "[superseded by duplicate call]";
15323
- state.recordToolSignature(callID, signature);
15324
- deduped++;
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, callID);
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 KEEP_RECENT = 8;
15694
- var PROTECTED_HEAD = 3;
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 <= KEEP_RECENT) return;
15781
- if (messages.length <= KEEP_RECENT + PROTECTED_HEAD) return;
15782
- const protectedTailStart = messages.length - KEEP_RECENT;
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 = PROTECTED_HEAD; i < protectedTailStart; 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: PROTECTED_HEAD,
15863
- protectedTail: KEEP_RECENT
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: PROTECTED_HEAD,
15870
- protectedTail: KEEP_RECENT
15980
+ protectedHead: PROTECTED_HEAD2,
15981
+ protectedTail: KEEP_RECENT2
15871
15982
  });
15872
15983
  }
15873
15984
  };