@caupulican/pi-adaptative 0.80.71 → 0.80.73

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.
@@ -38,6 +38,7 @@ import { MemoryManager } from "./memory/memory-manager.js";
38
38
  import { FileStoreProvider } from "./memory/providers/file-store.js";
39
39
  import { TranscriptRecallProvider } from "./memory/providers/transcript-recall.js";
40
40
  import { compactToolResultDetailsForRetention } from "./message-retention.js";
41
+ import { createCustomMessage } from "./messages.js";
41
42
  import { resolveProfileModelSettings } from "./model-resolver.js";
42
43
  import { expandPromptTemplate } from "./prompt-templates.js";
43
44
  import { stripResourceProfileBlocks } from "./resource-profile-blocks.js";
@@ -127,6 +128,12 @@ export class AgentSession {
127
128
  _effectivenessTracker = new EffectivenessTracker();
128
129
  /** R8: registry for deployment-supplied gateway channels + schedulers (lifecycle driven by the host runner). */
129
130
  _gatewayRegistry = new GatewayRegistry();
131
+ /** Cache for getSpawnedUsage(), keyed by session entry count (Bug #22 — avoid O(N) per render frame). */
132
+ _spawnedUsageCache;
133
+ /** Set on dispose so in-flight background reflection bails instead of writing to a dead session (Bug #21). */
134
+ _disposed = false;
135
+ /** Aborts in-flight background reflection completions on dispose (Bug #21). */
136
+ _reflectionAbort = new AbortController();
130
137
  _isChildSession;
131
138
  /** Memory providers registered by extensions via pi.registerMemoryProvider, applied on (re)init. */
132
139
  _pendingMemoryProviders = [];
@@ -676,6 +683,15 @@ export class AgentSession {
676
683
  this.agent.abort();
677
684
  // R8: stop any deployment-registered gateway channels / schedulers.
678
685
  void this._gatewayRegistry.stop().catch(() => { });
686
+ // Bug #21: abort any in-flight background reflection so it cannot keep spending tokens or
687
+ // write memory/skills against this now-disposed session.
688
+ this._disposed = true;
689
+ this._reflectionAbort.abort();
690
+ // Bug #20: clear the hooks this session installed on the shared agent so their closures stop
691
+ // pinning this (deactivated) session — and all its history/maps — in memory if the agent
692
+ // instance outlives the session.
693
+ this.agent.afterToolCall = undefined;
694
+ this.agent.transformContext = undefined;
679
695
  }
680
696
  catch {
681
697
  // Dispose must succeed even if an abort hook throws.
@@ -1111,7 +1127,11 @@ export class AgentSession {
1111
1127
  if (recall) {
1112
1128
  injectedRecall = recall;
1113
1129
  recallQuery = expandedText;
1114
- messages.push({ role: "user", content: [{ type: "text", text: recall }], timestamp: Date.now() });
1130
+ // Inject as a GC-managed custom context message (role "custom", customType
1131
+ // "memory_context"), NOT a persisted user message: the semantic-memory context-GC packs
1132
+ // stale recall pages so they don't accumulate forever (Bug #7), and the transcript index
1133
+ // only re-reads user/assistant text so recalled snippets can't recirculate (Bug #10).
1134
+ messages.push(createCustomMessage("memory_context", recall, false, undefined, new Date().toISOString()));
1115
1135
  }
1116
1136
  }
1117
1137
  catch {
@@ -3296,8 +3316,15 @@ export class AgentSession {
3296
3316
  };
3297
3317
  return this.sessionManager.appendCustomEntry(SPAWNED_USAGE_CUSTOM_TYPE, report);
3298
3318
  }
3299
- /** Aggregate all recorded spawned-usage reports (see {@link addSpawnedUsage}). */
3319
+ /**
3320
+ * Aggregate all recorded spawned-usage reports (see {@link addSpawnedUsage}). Cached by the session
3321
+ * entry count so the interactive footer (which calls this every render frame) is O(1) between turns
3322
+ * instead of an O(N) scan on every keystroke (Bug #22). Recomputes only when entries change.
3323
+ */
3300
3324
  getSpawnedUsage() {
3325
+ const entryCount = this.sessionManager.getEntryCount?.() ?? this.sessionManager.getEntries().length;
3326
+ if (this._spawnedUsageCache?.entryCount === entryCount)
3327
+ return this._spawnedUsageCache.totals;
3301
3328
  let cost = 0;
3302
3329
  let reports = 0;
3303
3330
  for (const entry of this.sessionManager.getEntries()) {
@@ -3309,7 +3336,9 @@ export class AgentSession {
3309
3336
  cost += data.usage.cost.total;
3310
3337
  reports += 1;
3311
3338
  }
3312
- return { cost, reports };
3339
+ const totals = { cost, reports };
3340
+ this._spawnedUsageCache = { entryCount, totals };
3341
+ return totals;
3313
3342
  }
3314
3343
  /**
3315
3344
  * Run a one-shot LLM completion fully ISOLATED from the main session — the load-bearing
@@ -3383,18 +3412,23 @@ export class AgentSession {
3383
3412
  * is best-effort: a model/parse error yields no writes, never throws into the caller.
3384
3413
  */
3385
3414
  async runReflectionPass(input) {
3386
- if (this._isChildSession)
3415
+ if (this._isChildSession || this._disposed)
3387
3416
  return null;
3388
3417
  const plan = decideDemand(input.signals);
3389
3418
  if (plan.act === "skip")
3390
3419
  return null;
3420
+ // Bug #21: tie this background pass to the session lifetime. Disposing the session aborts the
3421
+ // in-flight completion (input.signal can add a more specific abort).
3422
+ const signal = input.signal
3423
+ ? AbortSignal.any([input.signal, this._reflectionAbort.signal])
3424
+ : this._reflectionAbort.signal;
3391
3425
  const complete = (systemPrompt, userPrompt) => this.runIsolatedCompletion({
3392
3426
  systemPrompt,
3393
3427
  messages: [{ role: "user", content: [{ type: "text", text: userPrompt }], timestamp: Date.now() }],
3394
3428
  model: input.model,
3395
3429
  thinkingLevel: input.thinkingLevel ?? "low",
3396
3430
  maxTokens: plan.tokenBudget,
3397
- signal: input.signal,
3431
+ signal,
3398
3432
  });
3399
3433
  const result = await new ReflectionEngine().reflect({
3400
3434
  recentTurnText: input.recentTurnText,
@@ -3404,8 +3438,12 @@ export class AgentSession {
3404
3438
  plan,
3405
3439
  complete,
3406
3440
  });
3441
+ // Bug #21: if the session was disposed while the completion was in flight, do NOT write memory
3442
+ // or skills against the dead session.
3443
+ if (this._disposed)
3444
+ return result;
3407
3445
  for (const write of result.writes) {
3408
- await this._applyReflectionWrite(write, input.signal);
3446
+ await this._applyReflectionWrite(write, signal);
3409
3447
  }
3410
3448
  // Account the reflection's spend so it surfaces in the footer roll-up (net-token visibility).
3411
3449
  // Idempotent on reportId so a retried/duplicated pass cannot double-count.