agenr 0.9.18 → 0.9.19

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/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.9.19 (2026-02-28)
4
+
5
+ ### Features
6
+ - OpenClaw plugin: store nudging - injects a system nudge when the
7
+ agent has not called agenr_store in 8+ turns (configurable via
8
+ storeNudge plugin config). Nudges are spaced by the threshold
9
+ interval, capped at 3 per session. (#290)
10
+
3
11
  ## 0.9.18 (2026-02-28)
4
12
 
5
13
  ### Features
@@ -659,7 +659,9 @@ function getMidSessionState(key) {
659
659
  recentMessages: new RecentMessagesBuffer(),
660
660
  lastRecallQuery: null,
661
661
  recalledIds: /* @__PURE__ */ new Set(),
662
- turnCount: 0
662
+ turnCount: 0,
663
+ lastStoreTurn: 0,
664
+ nudgeCount: 0
663
665
  };
664
666
  }
665
667
  const existing = midSessionStates.get(normalizedKey);
@@ -670,11 +672,20 @@ function getMidSessionState(key) {
670
672
  recentMessages: new RecentMessagesBuffer(),
671
673
  lastRecallQuery: null,
672
674
  recalledIds: /* @__PURE__ */ new Set(),
673
- turnCount: 0
675
+ turnCount: 0,
676
+ lastStoreTurn: 0,
677
+ nudgeCount: 0
674
678
  };
675
679
  midSessionStates.set(normalizedKey, nextState);
676
680
  return nextState;
677
681
  }
682
+ function markStoreCall(key) {
683
+ if (typeof key !== "string" || key.trim().length === 0) {
684
+ return;
685
+ }
686
+ const state = getMidSessionState(key);
687
+ state.lastStoreTurn = state.turnCount;
688
+ }
678
689
  function clearMidSessionStates() {
679
690
  midSessionStates.clear();
680
691
  }
@@ -1630,7 +1641,10 @@ var MAX_RECALLED_SESSIONS = 200;
1630
1641
  var DEFAULT_MID_SESSION_NORMAL_LIMIT = 5;
1631
1642
  var DEFAULT_MID_SESSION_COMPLEX_LIMIT = 8;
1632
1643
  var DEFAULT_MID_SESSION_QUERY_SIMILARITY_THRESHOLD = 0.85;
1644
+ var DEFAULT_STORE_NUDGE_THRESHOLD = 8;
1645
+ var DEFAULT_STORE_NUDGE_MAX_PER_SESSION = 3;
1633
1646
  var sessionRecalledEntries = /* @__PURE__ */ new Map();
1647
+ var sessionRef = { current: "" };
1634
1648
  function isDebugEnabled(config) {
1635
1649
  return process.env.AGENR_DEBUG === "1" || config?.debug === true;
1636
1650
  }
@@ -1711,6 +1725,19 @@ function resolveMidSessionSimilarityThreshold(raw) {
1711
1725
  }
1712
1726
  return raw;
1713
1727
  }
1728
+ function resolveStoreNudgeConfig(config) {
1729
+ return {
1730
+ enabled: config?.storeNudge?.enabled !== false,
1731
+ threshold: resolveMidSessionLimit(
1732
+ config?.storeNudge?.threshold,
1733
+ DEFAULT_STORE_NUDGE_THRESHOLD
1734
+ ),
1735
+ maxPerSession: resolveMidSessionLimit(
1736
+ config?.storeNudge?.maxPerSession,
1737
+ DEFAULT_STORE_NUDGE_MAX_PER_SESSION
1738
+ )
1739
+ };
1740
+ }
1714
1741
  function getRecallEntryId(item) {
1715
1742
  if (typeof item !== "object" || item === null) {
1716
1743
  return null;
@@ -2499,6 +2526,7 @@ var testingApi = {
2499
2526
  clearMidSessionStates();
2500
2527
  handoffSeenSessionIds.clear();
2501
2528
  sessionRecalledEntries.clear();
2529
+ sessionRef.current = "";
2502
2530
  },
2503
2531
  readSessionsJson,
2504
2532
  readAndParseSessionJsonl,
@@ -2530,6 +2558,7 @@ var plugin = {
2530
2558
  "before_prompt_build",
2531
2559
  async (event, ctx) => {
2532
2560
  try {
2561
+ sessionRef.current = "";
2533
2562
  const sessionKey = ctx.sessionKey ?? "";
2534
2563
  if (!sessionKey) {
2535
2564
  return;
@@ -2541,12 +2570,18 @@ var plugin = {
2541
2570
  return;
2542
2571
  }
2543
2572
  const dedupeKey = ctx.sessionId ?? sessionKey;
2573
+ sessionRef.current = dedupeKey || sessionKey;
2544
2574
  const agenrPath = resolveAgenrPath(config);
2545
2575
  const budget = resolveBudget(config);
2546
2576
  const project = config?.project?.trim() || void 0;
2547
2577
  let markdown;
2548
2578
  let midSessionMarkdown;
2549
2579
  const isFirstInSession = dedupeKey ? !hasSeenSession(dedupeKey) : true;
2580
+ const stateKey = dedupeKey || sessionKey;
2581
+ const state = isFirstInSession ? void 0 : getMidSessionState(stateKey);
2582
+ if (state) {
2583
+ state.turnCount += 1;
2584
+ }
2550
2585
  if (isFirstInSession) {
2551
2586
  debugLog(debug, "session-start", `sessionKey=${sessionKey} dedupeKey=${dedupeKey} isFirst=true`);
2552
2587
  if (dedupeKey) {
@@ -2702,10 +2737,7 @@ ${formatted.trim()}`);
2702
2737
  } else {
2703
2738
  debugLog(debug, "prompt-build", `sessionKey=${sessionKey} isFirst=false`);
2704
2739
  }
2705
- if (!isFirstInSession && config?.midSessionRecall?.enabled !== false) {
2706
- const stateKey = dedupeKey || sessionKey;
2707
- const state = getMidSessionState(stateKey);
2708
- state.turnCount += 1;
2740
+ if (state && config?.midSessionRecall?.enabled !== false) {
2709
2741
  const rawPrompt = typeof event.prompt === "string" ? stripPromptMetadata(event.prompt) : "";
2710
2742
  const userMessage = rawPrompt.trim();
2711
2743
  if (userMessage) {
@@ -2783,23 +2815,46 @@ ${formatted.trim()}`);
2783
2815
  const db = await ensurePluginDb(config);
2784
2816
  const candidateSignal = await checkSignals(db, sessionKey, signalConfig);
2785
2817
  debugLog(debug, "signals", `check result=${candidateSignal ? "found" : "none"}`);
2786
- const state = sessionSignalState.get(sessionKey) ?? { lastSignalAt: 0, signalCount: 0 };
2787
- const isCooldown = signalConfig.cooldownMs > 0 && Date.now() - state.lastSignalAt < signalConfig.cooldownMs;
2788
- const isMaxReached = signalConfig.maxPerSession > 0 && state.signalCount >= signalConfig.maxPerSession;
2818
+ const state2 = sessionSignalState.get(sessionKey) ?? { lastSignalAt: 0, signalCount: 0 };
2819
+ const isCooldown = signalConfig.cooldownMs > 0 && Date.now() - state2.lastSignalAt < signalConfig.cooldownMs;
2820
+ const isMaxReached = signalConfig.maxPerSession > 0 && state2.signalCount >= signalConfig.maxPerSession;
2789
2821
  if (candidateSignal && !isCooldown && !isMaxReached) {
2790
2822
  sessionSignalState.set(sessionKey, {
2791
2823
  lastSignalAt: Date.now(),
2792
- signalCount: state.signalCount + 1
2824
+ signalCount: state2.signalCount + 1
2793
2825
  });
2794
2826
  signal = candidateSignal;
2795
2827
  }
2796
2828
  debugLog(debug, "signals", `cooldown=${isCooldown} maxReached=${isMaxReached} injected=${!!signal}`);
2797
2829
  }
2798
- const prependContext = [markdown, midSessionMarkdown, signal].filter(Boolean).join("\n\n");
2830
+ let storeNudge;
2831
+ if (!isFirstInSession && state) {
2832
+ const nudgeConfig = resolveStoreNudgeConfig(config);
2833
+ if (nudgeConfig.enabled) {
2834
+ const gap = state.turnCount - state.lastStoreTurn;
2835
+ debugLog(
2836
+ debug,
2837
+ "store-nudge",
2838
+ `check gap=${gap} threshold=${nudgeConfig.threshold} nudgeCount=${state.nudgeCount} maxPerSession=${nudgeConfig.maxPerSession}`
2839
+ );
2840
+ if (gap >= nudgeConfig.threshold && state.nudgeCount < nudgeConfig.maxPerSession) {
2841
+ storeNudge = "[MEMORY CHECK] You have not stored any knowledge recently. Review the conversation for decisions, preferences, lessons, or facts worth remembering.";
2842
+ state.nudgeCount += 1;
2843
+ state.lastStoreTurn = state.turnCount;
2844
+ debugLog(debug, "store-nudge", `injecting nudge #${state.nudgeCount}`);
2845
+ } else {
2846
+ const reason = gap < nudgeConfig.threshold ? "gap_below_threshold" : "max_reached";
2847
+ debugLog(debug, "store-nudge", `skipped reason=${reason}`);
2848
+ }
2849
+ } else {
2850
+ debugLog(debug, "store-nudge", "skipped reason=disabled");
2851
+ }
2852
+ }
2853
+ const prependContext = [markdown, midSessionMarkdown, signal, storeNudge].filter(Boolean).join("\n\n");
2799
2854
  debugLog(
2800
2855
  debug,
2801
2856
  "prompt-build",
2802
- `prependContext total chars=${prependContext.length} (recall=${markdown?.length ?? 0} midSession=${midSessionMarkdown?.length ?? 0} signal=${signal?.length ?? 0})`
2857
+ `prependContext total chars=${prependContext.length} (recall=${markdown?.length ?? 0} midSession=${midSessionMarkdown?.length ?? 0} signal=${signal?.length ?? 0} nudge=${storeNudge?.length ?? 0})`
2803
2858
  );
2804
2859
  if (!prependContext) {
2805
2860
  return;
@@ -2914,6 +2969,10 @@ ${formatted.trim()}`);
2914
2969
  if (resetKey) {
2915
2970
  clearMidSessionState(resetKey);
2916
2971
  }
2972
+ const resetSessionId = event.context?.sessionEntry?.sessionId?.trim() ?? "";
2973
+ if (resetSessionId && resetSessionId !== resetKey) {
2974
+ clearMidSessionState(resetSessionId);
2975
+ }
2917
2976
  }
2918
2977
  const sessionKey = event.sessionKey;
2919
2978
  if (!sessionKey) {
@@ -3069,6 +3128,11 @@ ${formatted.trim()}`);
3069
3128
  if (runtimeConfig?.enabled === false) {
3070
3129
  return makeDisabledToolResult();
3071
3130
  }
3131
+ const key = sessionRef.current;
3132
+ if (key) {
3133
+ markStoreCall(key);
3134
+ debugLog(debug, "store-nudge", `store detected session=${key}`);
3135
+ }
3072
3136
  const agenrPath = resolveAgenrPath(runtimeConfig);
3073
3137
  const defaultProject = runtimeConfig?.project?.trim() || void 0;
3074
3138
  const toolConfig = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agenr",
3
- "version": "0.9.18",
3
+ "version": "0.9.19",
4
4
  "openclaw": {
5
5
  "extensions": [
6
6
  "dist/openclaw-plugin/index.js"