@caupulican/pi-adaptative 0.80.72 → 0.80.74

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.
@@ -408,6 +408,17 @@ export class InteractiveMode {
408
408
  pendingClipboardImages = [];
409
409
  clipboardImageCounter = 0;
410
410
  loadingAnimation = undefined;
411
+ // Native-reflection debounce: prevents back-to-back/overlapping background reflection passes (cost
412
+ // guard). `_nativeReflectionInFlight` blocks a second pass while one runs; `_lastNativeReflectionAt`
413
+ // enforces a minimum gap between passes. A debounce-skipped turn's text is BUFFERED in
414
+ // `_pendingReflectionText` (not dropped) and folded into the next pass, so no corrective feedback is
415
+ // lost — reflection sees only the current turn's messages, so dropping a skipped turn would lose its
416
+ // learning entirely (bug #29).
417
+ _nativeReflectionInFlight = false;
418
+ _lastNativeReflectionAt = 0;
419
+ _pendingReflectionText = [];
420
+ static NATIVE_REFLECTION_MIN_INTERVAL_MS = 45_000;
421
+ static PENDING_REFLECTION_MAX_CHARS = 12_000;
411
422
  workingMessage = undefined;
412
423
  workingVisible = true;
413
424
  workingIndicatorOptions = undefined;
@@ -4831,6 +4842,26 @@ export class InteractiveMode {
4831
4842
  const thinkingLevel = settings.thinkingLevel ?? "low";
4832
4843
  return { model, thinkingLevel };
4833
4844
  }
4845
+ /** Buffer a debounce-skipped turn's text so its learning is folded into the next pass (bug #29). */
4846
+ _bufferPendingReflection(text) {
4847
+ const t = text.trim();
4848
+ if (!t)
4849
+ return;
4850
+ this._pendingReflectionText.push(t);
4851
+ // Bound the buffer so a long skipped streak can't grow unbounded; drop oldest past the budget
4852
+ // (the most recent corrections matter most).
4853
+ let total = this._pendingReflectionText.reduce((n, s) => n + s.length + 1, 0);
4854
+ while (this._pendingReflectionText.length > 1 && total > InteractiveMode.PENDING_REFLECTION_MAX_CHARS) {
4855
+ total -= (this._pendingReflectionText.shift()?.length ?? 0) + 1;
4856
+ }
4857
+ }
4858
+ _drainPendingReflection() {
4859
+ if (this._pendingReflectionText.length === 0)
4860
+ return "";
4861
+ const joined = this._pendingReflectionText.join("\n");
4862
+ this._pendingReflectionText = [];
4863
+ return joined;
4864
+ }
4834
4865
  maybeRunNativeReflection(messages) {
4835
4866
  if (!this.isNativeReflectionEnabled())
4836
4867
  return;
@@ -4855,6 +4886,20 @@ export class InteractiveMode {
4855
4886
  .map((m) => `${String(m.role ?? "")}: ${this.getAgentMessagePlainText(m)}`.trim())
4856
4887
  .filter(Boolean)
4857
4888
  .join("\n");
4889
+ // Debounce (cost guard): never run two background reflection passes at once, and never start one
4890
+ // within the min interval of the last — a multi-turn correction session would otherwise spawn
4891
+ // overlapping passes that re-reason the same task. A skipped turn is NOT dropped: its text is
4892
+ // buffered and folded into the next pass, so the corrective feedback is still learned (bug #29).
4893
+ const now = Date.now();
4894
+ const debounced = this._nativeReflectionInFlight ||
4895
+ now - this._lastNativeReflectionAt < InteractiveMode.NATIVE_REFLECTION_MIN_INTERVAL_MS;
4896
+ if (debounced) {
4897
+ this._bufferPendingReflection(recentTurnText);
4898
+ return;
4899
+ }
4900
+ // Fold any buffered (previously debounced) turns into this pass so nothing learned is lost.
4901
+ const pending = this._drainPendingReflection();
4902
+ const reflectionText = pending ? `${pending}\n${recentTurnText}` : recentTurnText;
4858
4903
  // Stable per-turn id so a duplicate scheduling/retry can't double-count the reflection cost.
4859
4904
  // Messages carry no `id` on the real path (only timestamps), so derive the key from the last
4860
4905
  // message's timestamp + the turn size — present on every real turn, stable across a retry of the
@@ -4865,16 +4910,21 @@ export class InteractiveMode {
4865
4910
  // User-configurable reflection model + thinking (auto-learn settings), restricted to AVAILABLE
4866
4911
  // (authed) models — falls back to the session model when unset or unavailable.
4867
4912
  const { model, thinkingLevel } = this._resolveReflectionModel(settings);
4913
+ this._nativeReflectionInFlight = true;
4914
+ this._lastNativeReflectionAt = now;
4868
4915
  void this.session
4869
4916
  .runReflectionPass({
4870
4917
  signals: { trigger, toolCallCount, hadCorrection, contextHeadroomPct, usefulLately: 0 },
4871
- recentTurnText,
4918
+ recentTurnText: reflectionText,
4872
4919
  reportId,
4873
4920
  model,
4874
4921
  thinkingLevel,
4875
4922
  })
4876
4923
  .catch(() => {
4877
4924
  // best-effort background learning; never disrupt the session
4925
+ })
4926
+ .finally(() => {
4927
+ this._nativeReflectionInFlight = false;
4878
4928
  });
4879
4929
  }
4880
4930
  maybeStartAutoLearn() {