@caupulican/pi-adaptative 0.81.2 → 0.81.4

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.
Files changed (115) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/dist/core/agent-session.d.ts +2 -23
  3. package/dist/core/agent-session.d.ts.map +1 -1
  4. package/dist/core/agent-session.js +139 -147
  5. package/dist/core/agent-session.js.map +1 -1
  6. package/dist/core/background-lane-controller.d.ts +2 -0
  7. package/dist/core/background-lane-controller.d.ts.map +1 -1
  8. package/dist/core/background-lane-controller.js +3 -0
  9. package/dist/core/background-lane-controller.js.map +1 -1
  10. package/dist/core/billing-failover-controller.d.ts +38 -0
  11. package/dist/core/billing-failover-controller.d.ts.map +1 -0
  12. package/dist/core/billing-failover-controller.js +78 -0
  13. package/dist/core/billing-failover-controller.js.map +1 -0
  14. package/dist/core/billing-failover.d.ts +25 -0
  15. package/dist/core/billing-failover.d.ts.map +1 -0
  16. package/dist/core/billing-failover.js +21 -0
  17. package/dist/core/billing-failover.js.map +1 -0
  18. package/dist/core/compaction-support.d.ts +10 -1
  19. package/dist/core/compaction-support.d.ts.map +1 -1
  20. package/dist/core/compaction-support.js +68 -9
  21. package/dist/core/compaction-support.js.map +1 -1
  22. package/dist/core/context-pipeline.d.ts +1 -0
  23. package/dist/core/context-pipeline.d.ts.map +1 -1
  24. package/dist/core/context-pipeline.js +51 -14
  25. package/dist/core/context-pipeline.js.map +1 -1
  26. package/dist/core/export-html/index.d.ts.map +1 -1
  27. package/dist/core/export-html/index.js +9 -9
  28. package/dist/core/export-html/index.js.map +1 -1
  29. package/dist/core/failure-corpus.d.ts +49 -0
  30. package/dist/core/failure-corpus.d.ts.map +1 -0
  31. package/dist/core/failure-corpus.js +61 -0
  32. package/dist/core/failure-corpus.js.map +1 -0
  33. package/dist/core/model-router/config-diagnostics.d.ts +1 -1
  34. package/dist/core/model-router/config-diagnostics.d.ts.map +1 -1
  35. package/dist/core/model-router/config-diagnostics.js +29 -1
  36. package/dist/core/model-router/config-diagnostics.js.map +1 -1
  37. package/dist/core/model-router/fitness-gate.d.ts +25 -0
  38. package/dist/core/model-router/fitness-gate.d.ts.map +1 -0
  39. package/dist/core/model-router/fitness-gate.js +34 -0
  40. package/dist/core/model-router/fitness-gate.js.map +1 -0
  41. package/dist/core/model-router/status.d.ts +29 -1
  42. package/dist/core/model-router/status.d.ts.map +1 -1
  43. package/dist/core/model-router/status.js +31 -4
  44. package/dist/core/model-router/status.js.map +1 -1
  45. package/dist/core/model-router-controller.d.ts +10 -1
  46. package/dist/core/model-router-controller.d.ts.map +1 -1
  47. package/dist/core/model-router-controller.js +110 -27
  48. package/dist/core/model-router-controller.js.map +1 -1
  49. package/dist/core/models/default-model-suggestions.d.ts.map +1 -1
  50. package/dist/core/models/default-model-suggestions.js +18 -0
  51. package/dist/core/models/default-model-suggestions.js.map +1 -1
  52. package/dist/core/runtime-builder.d.ts +16 -0
  53. package/dist/core/runtime-builder.d.ts.map +1 -1
  54. package/dist/core/runtime-builder.js +88 -1
  55. package/dist/core/runtime-builder.js.map +1 -1
  56. package/dist/core/scout-controller.d.ts +44 -0
  57. package/dist/core/scout-controller.d.ts.map +1 -0
  58. package/dist/core/scout-controller.js +165 -0
  59. package/dist/core/scout-controller.js.map +1 -0
  60. package/dist/core/settings-manager.d.ts +17 -0
  61. package/dist/core/settings-manager.d.ts.map +1 -1
  62. package/dist/core/settings-manager.js +47 -3
  63. package/dist/core/settings-manager.js.map +1 -1
  64. package/dist/core/slash-commands.d.ts.map +1 -1
  65. package/dist/core/slash-commands.js +4 -0
  66. package/dist/core/slash-commands.js.map +1 -1
  67. package/dist/core/tools/bash.d.ts.map +1 -1
  68. package/dist/core/tools/bash.js +6 -8
  69. package/dist/core/tools/bash.js.map +1 -1
  70. package/dist/core/tools/context-scout.d.ts +19 -0
  71. package/dist/core/tools/context-scout.d.ts.map +1 -0
  72. package/dist/core/tools/context-scout.js +47 -0
  73. package/dist/core/tools/context-scout.js.map +1 -0
  74. package/dist/core/tools/index.d.ts +1 -0
  75. package/dist/core/tools/index.d.ts.map +1 -1
  76. package/dist/core/tools/index.js +1 -0
  77. package/dist/core/tools/index.js.map +1 -1
  78. package/dist/main.d.ts.map +1 -1
  79. package/dist/main.js +1 -1
  80. package/dist/main.js.map +1 -1
  81. package/dist/modes/interactive/components/fitness-role-selector.d.ts +1 -1
  82. package/dist/modes/interactive/components/fitness-role-selector.d.ts.map +1 -1
  83. package/dist/modes/interactive/components/fitness-role-selector.js +5 -0
  84. package/dist/modes/interactive/components/fitness-role-selector.js.map +1 -1
  85. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  86. package/dist/modes/interactive/components/settings-selector.js +77 -7
  87. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  88. package/dist/modes/interactive/interactive-mode.d.ts +1 -0
  89. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  90. package/dist/modes/interactive/interactive-mode.js +13 -0
  91. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  92. package/dist/modes/interactive/local-model-commands.d.ts +5 -2
  93. package/dist/modes/interactive/local-model-commands.d.ts.map +1 -1
  94. package/dist/modes/interactive/local-model-commands.js +129 -1
  95. package/dist/modes/interactive/local-model-commands.js.map +1 -1
  96. package/dist/modes/interactive/session-flow-commands.d.ts +16 -0
  97. package/dist/modes/interactive/session-flow-commands.d.ts.map +1 -1
  98. package/dist/modes/interactive/session-flow-commands.js +32 -0
  99. package/dist/modes/interactive/session-flow-commands.js.map +1 -1
  100. package/docs/bug-ledger.md +113 -0
  101. package/docs/index.md +1 -0
  102. package/docs/models.md +9 -0
  103. package/docs/providers.md +14 -0
  104. package/docs/scout.md +73 -0
  105. package/docs/settings.md +16 -3
  106. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  107. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  108. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  109. package/examples/extensions/sandbox/package-lock.json +2 -2
  110. package/examples/extensions/sandbox/package.json +1 -1
  111. package/examples/extensions/with-deps/package-lock.json +2 -2
  112. package/examples/extensions/with-deps/package.json +1 -1
  113. package/examples/sdk/12-full-control.ts +1 -1
  114. package/npm-shrinkwrap.json +12 -12
  115. package/package.json +4 -4
@@ -1,21 +1,7 @@
1
- /**
2
- * AgentSession - Core abstraction for agent lifecycle and session management.
3
- *
4
- * This class is shared between all run modes (interactive, print, rpc).
5
- * It encapsulates:
6
- * - Agent state access
7
- * - Event subscription with automatic session persistence
8
- * - Model and thinking level management
9
- * - Compaction (manual and auto)
10
- * - Bash execution
11
- * - Session switching and branching
12
- *
13
- * Modes use this class and add their own I/O layer on top.
14
- */
15
1
  import { readFileSync } from "node:fs";
16
2
  import { basename, dirname, join } from "node:path";
17
3
  import { classifyFailure, compactToolResultDetailsForRetention, computeRetryDelayMs, createCustomMessage, DEFAULT_RETRY_POLICY, RetryController, sleepAbortable, withStreamIdleWatchdog, } from "@caupulican/pi-agent-core";
18
- import { calculateContextTokens, compact, estimateContextTokens, getLatestCompactionEntry, prepareCompaction, shouldCompact, } from "@caupulican/pi-agent-core/node";
4
+ import { calculateContextTokens, compact, createDeterministicCompaction, estimateContextTokens, getLatestCompactionEntry, prepareCompaction, runCompactionLoop, shouldCompact, } from "@caupulican/pi-agent-core/node";
19
5
  import { cleanupSessionResources, isContextOverflow, streamSimple } from "@caupulican/pi-ai";
20
6
  import { getAgentDir } from "../config.js";
21
7
  import { stripFrontmatter } from "../utils/frontmatter.js";
@@ -24,11 +10,13 @@ import { buildForegroundEnvelope, formatForegroundEnvelopeObservation } from "./
24
10
  import { AutonomyTelemetry } from "./autonomy-telemetry.js";
25
11
  import { BackgroundLaneController } from "./background-lane-controller.js";
26
12
  import { BashExecutionController } from "./bash-execution-controller.js";
13
+ import { BillingFailoverController, ExhaustedProviderRegistry } from "./billing-failover-controller.js";
27
14
  import { CompactionSupport } from "./compaction-support.js";
28
15
  import { buildContextCompositionReport, formatContextCompositionDashboard, } from "./context/context-composition.js";
29
16
  import { ContextPipeline } from "./context-pipeline.js";
30
17
  import { downgradeReasoning, estimateTurnCostUsd, evaluateCostGuard } from "./cost-guard.js";
31
18
  import { appendWorkerResultSnapshot, getWorkerResultSnapshots } from "./delegation/session-worker-result.js";
19
+ import { FailureCorpusRecorder } from "./failure-corpus.js";
32
20
  import { GatewayRegistry } from "./gateways/channel-provider.js";
33
21
  import { GoalLoopController } from "./goal-loop-controller.js";
34
22
  import { buildGoalRuntimeSnapshot, } from "./goals/goal-runtime-snapshot.js";
@@ -116,52 +104,34 @@ export class AgentSession {
116
104
  _unsubscribeAgent;
117
105
  _eventListeners = [];
118
106
  _extensionsChangedListeners = [];
119
- /** Tracks pending steering messages for UI display. Removed when delivered. */
120
107
  _steeringMessages = [];
121
- /** Tracks pending follow-up messages for UI display. Removed when delivered. */
122
108
  _followUpMessages = [];
123
- /** Tracks extension slash commands queued while the agent is streaming. */
124
109
  _queuedExtensionCommands = [];
125
- /** Messages queued to be included with the next user prompt as context ("asides"). */
126
110
  _pendingNextTurnMessages = [];
127
- /** Serializes prompt() submissions made while streaming so queued steering/follow-ups keep user-typed FIFO order. */
128
111
  _streamingPromptSubmissionTail = Promise.resolve();
129
112
  /**
130
113
  * The last tool set requested via setActiveToolsByName BEFORE model-capability filtering, so
131
114
  * switching from a small-window model back to a large one restores the full requested set.
132
115
  */
133
116
  _requestedActiveToolNames;
134
- // Compaction/context hygiene state
135
117
  _compactionAbortController = undefined;
136
118
  _autoCompactionAbortController = undefined;
137
119
  _overflowRecoveryAttempted = false;
138
120
  _unboundToolGrantWarnings = [];
139
- // Branch summarization state
140
121
  _branchSummaryAbortController = undefined;
141
- // Auto-retry driver — owns the attempt counter and abortable backoff (reliability kernel).
142
122
  _retryController;
143
- // Bash execution state
144
- /** User-facing model + thinking-level selection (see model-selection-controller.ts). */
145
123
  _modelSelection;
146
- /** Standalone bash-command execution (see bash-execution-controller.ts); owns the bash abort
147
- * controller and the streaming-deferred pending-message queue. */
148
124
  _bash;
149
- /** Resource-profile tool/extension gating + reload-time profile model re-apply (see profile-filter-controller.ts). */
150
125
  _profileFilter;
151
- /** Agent tool-call gate: escalation, autonomy gating, extension hooks, untrusted boundary (see tool-gate-controller.ts). */
152
126
  _toolGate;
153
- // Extension system
154
127
  _extensionRunner;
155
128
  _turnIndex = 0;
156
- /** G7: per-turn foreground CapabilityEnvelope auto-built for visibility (observe-only; not enforced). */
157
129
  _currentForegroundEnvelope;
158
130
  _resourceLoader;
159
131
  _customTools;
160
132
  _cwd;
161
133
  _agentDir;
162
134
  _collectWorkspaceSources;
163
- /** Owns the cached per-server OllamaRuntime instances and the local-model router readiness gate
164
- * (see local-runtime-controller.ts). */
165
135
  _localRuntimeController;
166
136
  /** Assembles the session's base system prompt from live session state (see
167
137
  * system-prompt-builder.ts); owns the paired _baseSystemPromptOptions. */
@@ -177,7 +147,6 @@ export class AgentSession {
177
147
  * latest retrieval/prompt-inclusion reports, the reload-safe MemoryManager, the recall
178
148
  * effectiveness tracker, and the extension-contributed pending providers. */
179
149
  _memory;
180
- /** Summarizer model/auth/settings resolution (see compaction-support.ts). */
181
150
  _compactionSupport;
182
151
  /** Per-turn context-shaping subsystem (see context-pipeline.ts); owns the latest
183
152
  * audit/policy/correlation/enforcement/gc reports, the brain-curation sidecar + its skip reasons,
@@ -190,28 +159,22 @@ export class AgentSession {
190
159
  _toolProfileFilter;
191
160
  _isExplicitModel;
192
161
  _isExplicitThinking;
193
- /** R8: registry for deployment-supplied gateway channels + schedulers (lifecycle driven by the host runner). */
194
162
  _gatewayRegistry = new GatewayRegistry();
195
- /** Cache for getSpawnedUsage(), keyed by session entry count (Bug #22 — avoid O(N) per render frame). */
196
163
  /** Usage/cost/stats accounting, /context estimate, and session export (see session-analytics.ts);
197
164
  * owns the spawned-usage and daily-usage memo caches. */
198
165
  _analytics;
199
- /** In-file session-tree branch switching + fork-selector reads (see session-tree-navigator.ts). */
200
166
  _treeNavigator;
201
- /** Latest proactive cost-guard decision (#34), for the host UI to surface. Undefined when disabled. */
202
167
  _lastCostGuardDecision;
203
- /** One-shot latch so the cost guard downgrades reasoning once per over-threshold episode, not every call. */
204
168
  _costGuardDowngraded = false;
205
169
  /** Per-turn model-router subsystem (see model-router-controller.ts); owns the transient route/intent,
206
170
  * the cheap-turn session buffer, the escalation/retry flags, and the sticky last-decision/skip-reason
207
171
  * used by the status report. Its parallel routed drive path delegates every turn back to
208
172
  * {@link _runAgentPrompt} so the drive loop stays host-side. */
209
173
  _modelRouter;
210
- /** Lazily-built skill curator (#32) over `<agentDir>/skills`. */
174
+ _billingFailover;
175
+ _failureCorpus;
211
176
  _skillCuratorInstance;
212
- /** Set on dispose so in-flight background reflection bails instead of writing to a dead session (Bug #21). */
213
177
  _disposed = false;
214
- /** Aborts in-flight background reflection completions on dispose (Bug #21). */
215
178
  _reflectionAbort = new AbortController();
216
179
  /** Native reflection engine + learning-apply/rollback path (see reflection-controller.ts); owns no
217
180
  * session state, applies durable writes through the bundled memory tool and the session log. */
@@ -229,7 +192,6 @@ export class AgentSession {
229
192
  _extensionShutdownHandler;
230
193
  _extensionErrorListener;
231
194
  _extensionErrorUnsubscriber;
232
- // Model registry for API key resolution
233
195
  _modelRegistry;
234
196
  /** Tool-registry assembly + the self-modification-safe extension reload (see runtime-builder.ts);
235
197
  * owns the base/wrapped tool definitions, the live tool registry, and the per-tool prompt
@@ -314,6 +276,7 @@ export class AgentSession {
314
276
  getLearningDecisionSnapshots: () => this.getLearningDecisionSnapshots(),
315
277
  getLearningAuditRecords: () => this.getLearningAuditRecords(),
316
278
  });
279
+ this._modelRegistry = config.modelRegistry;
317
280
  this._backgroundLanes = new BackgroundLaneController({
318
281
  isDisposed: () => this._disposed,
319
282
  isChildSession: () => this._isChildSession,
@@ -323,6 +286,7 @@ export class AgentSession {
323
286
  getSessionManager: () => this.sessionManager,
324
287
  getSettingsManager: () => this.settingsManager,
325
288
  getModelRegistry: () => this._modelRegistry,
289
+ isModelExhausted: (model) => this._billingFailover.isExhausted(`${model.provider}/${model.id}`),
326
290
  getModel: () => this.model ?? undefined,
327
291
  getCapabilityEnvelope: () => this.capabilityEnvelope,
328
292
  getModelCapabilityProfile: () => this.getModelCapabilityProfile(),
@@ -353,6 +317,12 @@ export class AgentSession {
353
317
  getModelRegistry: () => this._modelRegistry,
354
318
  isRawStream: () => this._isRawStreamSimple(this.agent.streamFn),
355
319
  getRequiredRequestAuth: (model) => this._getRequiredRequestAuth(model),
320
+ isModelExhausted: (ref) => this._billingFailover.isExhausted(ref),
321
+ getStoredFitnessReport: (ref) => this.getStoredFitnessReports().find((entry) => entry.model === ref)?.report,
322
+ // Live context is an over-estimate of the span to summarize (includes the kept tail) —
323
+ // conservative in the safe direction for the summarizer capacity check.
324
+ estimateSummarizationInputTokens: () => this._pipeline.estimateCurrentContextTokens(this.agent.state.messages),
325
+ emitWarning: (message) => this._emit({ type: "warning", message }),
356
326
  });
357
327
  this._pipeline = new ContextPipeline({
358
328
  getTurnIndex: () => this._turnIndex,
@@ -367,12 +337,25 @@ export class AgentSession {
367
337
  addSpawnedUsage: (usage, opts) => this.addSpawnedUsage(usage, opts),
368
338
  runIsolatedCompletion: (opts) => this.runIsolatedCompletion(opts),
369
339
  });
340
+ this._failureCorpus = new FailureCorpusRecorder({
341
+ filePath: join(this._agentDir, "state", "failure-corpus.jsonl"),
342
+ });
343
+ this._billingFailover = new BillingFailoverController({
344
+ agent: this.agent,
345
+ modelRegistry: this._modelRegistry,
346
+ exhausted: new ExhaustedProviderRegistry(),
347
+ subscriptionHop: this.settingsManager.getFailoverSettings().subscriptionHop,
348
+ emit: (event) => this._emit(event),
349
+ recordFailure: (args) => this._failureCorpus.record(args),
350
+ });
370
351
  this._modelRouter = new ModelRouterController({
371
352
  getAgent: () => this.agent,
372
353
  getModel: () => this.model ?? undefined,
373
354
  getSettingsManager: () => this.settingsManager,
374
355
  getSessionManager: () => this.sessionManager,
375
356
  getModelRegistry: () => this._modelRegistry,
357
+ isModelExhausted: (model) => this._billingFailover.isExhausted(`${model.provider}/${model.id}`),
358
+ getFailoverStatus: () => ({ ...this._billingFailover.getStatus(), failureStats: this._failureCorpus.stats() }),
376
359
  getAgentDir: () => this._agentDir,
377
360
  getReflectionSignal: () => this._reflectionAbort.signal,
378
361
  getBaseSystemPrompt: () => this._baseSystemPrompt,
@@ -407,7 +390,6 @@ export class AgentSession {
407
390
  getGoalRuntimeSnapshot: (settings) => this.getGoalRuntimeSnapshot(settings),
408
391
  prompt: (text, options) => this.prompt(text, options),
409
392
  });
410
- this._modelRegistry = config.modelRegistry;
411
393
  this._extensionRunnerRef = config.extensionRunnerRef;
412
394
  this._initialActiveToolNames = config.initialActiveToolNames;
413
395
  this._allowedToolNames = config.allowedToolNames ? new Set(config.allowedToolNames) : undefined;
@@ -423,9 +405,11 @@ export class AgentSession {
423
405
  this._runtimeBuilder = new RuntimeBuilder({
424
406
  getAgent: () => this.agent,
425
407
  getCwd: () => this._cwd,
408
+ getAgentDir: () => this._agentDir,
426
409
  getSessionManager: () => this.sessionManager,
427
410
  getSettingsManager: () => this.settingsManager,
428
411
  getModelRegistry: () => this._modelRegistry,
412
+ isModelExhausted: (model) => this._billingFailover.isExhausted(`${model.provider}/${model.id}`),
429
413
  getResourceLoader: () => this._resourceLoader,
430
414
  getExtensionRunner: () => this._extensionRunner,
431
415
  setExtensionRunner: (runner) => {
@@ -1530,7 +1514,6 @@ export class AgentSession {
1530
1514
  async _ensureRouteModelReady(resolved) {
1531
1515
  return this._localRuntimeController.ensureRouteModelReady(resolved);
1532
1516
  }
1533
- /** Model-router status + config diagnostics report. Delegates to {@link ModelRouterController}. */
1534
1517
  getModelRouterStatus(formatLabel) {
1535
1518
  return this._modelRouter.getStatus(formatLabel);
1536
1519
  }
@@ -1543,6 +1526,8 @@ export class AgentSession {
1543
1526
  if (this._isRetryableError(msg) && (await this._prepareRetry(msg))) {
1544
1527
  return true;
1545
1528
  }
1529
+ if (await this._billingFailover.handleAssistantError(msg))
1530
+ return false;
1546
1531
  if (msg.stopReason === "error" && this._retryController.attempt > 0) {
1547
1532
  this._emit({
1548
1533
  type: "auto_retry_end",
@@ -2190,7 +2175,7 @@ export class AgentSession {
2190
2175
  else {
2191
2176
  // Generate compaction result
2192
2177
  const compactionSignal = this._compactionAbortController.signal;
2193
- const result = await this._compactWithRetry(() => compact(preparation, compactionModel, apiKey, headers, customInstructions, compactionSignal, compactionThinkingLevel, this.agent.streamFn, this._buildCompactionPreDigest()), compactionSignal);
2178
+ const result = await this._compactWithRetry(() => compact(preparation, compactionModel, apiKey, headers, customInstructions, compactionSignal, compactionThinkingLevel, this.agent.streamFn, this._buildCompactionPreDigest()), compactionSignal, compactionModel.provider);
2194
2179
  summary = result.summary;
2195
2180
  firstKeptEntryId = result.firstKeptEntryId;
2196
2181
  tokensBefore = result.tokensBefore;
@@ -2388,10 +2373,16 @@ export class AgentSession {
2388
2373
  */
2389
2374
  async _runAutoCompaction(reason, willRetry) {
2390
2375
  const settings = this._getAdaptedCompactionSettings();
2376
+ const model = this.model;
2391
2377
  this._emit({ type: "compaction_start", reason });
2378
+ const hadQueuedMessages = this.agent.hasQueuedMessages();
2392
2379
  this._autoCompactionAbortController = new AbortController();
2380
+ const signal = this._autoCompactionAbortController.signal;
2381
+ let fromExtension = false;
2382
+ let lastCompaction;
2383
+ let extensionCancelled = false;
2393
2384
  try {
2394
- if (!this.model) {
2385
+ if (!model) {
2395
2386
  this._emit({
2396
2387
  type: "compaction_end",
2397
2388
  reason,
@@ -2402,113 +2393,107 @@ export class AgentSession {
2402
2393
  });
2403
2394
  return false;
2404
2395
  }
2405
- // Summarize with the configured/default compaction model. If its auth turns out unusable at
2406
- // request time (e.g. expired token), fall back to the session model — the one manual /compact
2407
- // demonstrably works with before giving up.
2408
- const resolvedCompaction = await this._resolveCompactionModelAndAuth(this._resolveCompactionModel(this.model), this.model);
2409
- if (resolvedCompaction.failure) {
2410
- this._emit({
2411
- type: "compaction_end",
2412
- reason,
2413
- result: undefined,
2414
- aborted: false,
2415
- willRetry: false,
2416
- errorMessage: `Auto-compaction failed: ${resolvedCompaction.failure}`,
2417
- });
2418
- return false;
2419
- }
2420
- const compactionModel = resolvedCompaction.model;
2421
- const compactionThinkingLevel = this._resolveCompactionThinkingLevel(compactionModel, this.model);
2422
- const apiKey = resolvedCompaction.apiKey;
2423
- const headers = resolvedCompaction.headers;
2424
- const pathEntries = this.sessionManager.getBranch();
2425
- const preparation = prepareCompaction(pathEntries, settings);
2426
- if (!preparation) {
2427
- const lastEntry = pathEntries[pathEntries.length - 1];
2396
+ const contextWindow = model.contextWindow;
2397
+ const triggerThreshold = Math.max(0, contextWindow - settings.reserveTokens);
2398
+ const margin = Math.max(0, Math.floor(0.01 * contextWindow));
2399
+ let measureReads = 0;
2400
+ const outcome = await runCompactionLoop({
2401
+ getBranch: () => this.sessionManager.getBranch(),
2402
+ measureLiveTokens: () => {
2403
+ const measured = this._pipeline.estimateCurrentContextTokens(this.agent.state.messages);
2404
+ return measureReads++ === 0 ? Math.max(measured, triggerThreshold + 1) : measured;
2405
+ },
2406
+ getTriggerThreshold: () => triggerThreshold,
2407
+ getMargin: () => margin,
2408
+ getBaseKeepRecentTokens: () => settings.keepRecentTokens,
2409
+ resolveModelAndAuth: async (modelTier) => this._resolveCompactionModelAndAuth(modelTier === "session" ? model : this._resolveCompactionModel(model), model),
2410
+ summarizeAndVerify: async (params, compactModel, apiKey, headers, branchEntries) => {
2411
+ fromExtension = false;
2412
+ const adaptedSettings = { ...settings, keepRecentTokens: params.keepRecentTokens };
2413
+ const preparation = prepareCompaction(branchEntries, adaptedSettings);
2414
+ if (!preparation) {
2415
+ throw new Error("already compacted");
2416
+ }
2417
+ const compactionThinkingLevel = this._resolveCompactionThinkingLevel(compactModel, model);
2418
+ if (this._extensionRunner.hasHandlers("session_before_compact")) {
2419
+ const extensionResult = (await this._extensionRunner.emit({
2420
+ type: "session_before_compact",
2421
+ preparation,
2422
+ branchEntries,
2423
+ customInstructions: undefined,
2424
+ signal,
2425
+ }));
2426
+ if (extensionResult?.cancel) {
2427
+ extensionCancelled = true;
2428
+ throw new Error("auto-compaction-cancelled");
2429
+ }
2430
+ if (extensionResult?.compaction) {
2431
+ fromExtension = true;
2432
+ return { result: extensionResult.compaction };
2433
+ }
2434
+ }
2435
+ const compactResult = await this._compactWithRetry(() => compact(preparation, compactModel, apiKey, headers, undefined, signal, compactionThinkingLevel, this.agent.streamFn, this._buildCompactionPreDigest(), { chunked: params.chunked }), signal, compactModel.provider);
2436
+ return { result: compactResult };
2437
+ },
2438
+ buildDeterministicCheckpoint: async () => {
2439
+ const branch = this.sessionManager.getBranch();
2440
+ const preparation = prepareCompaction(branch, {
2441
+ ...settings,
2442
+ });
2443
+ if (!preparation) {
2444
+ throw new Error("already compacted");
2445
+ }
2446
+ fromExtension = false;
2447
+ return { result: createDeterministicCompaction(preparation) };
2448
+ },
2449
+ apply: async (result) => {
2450
+ lastCompaction = result;
2451
+ this.sessionManager.appendCompaction(result.summary, result.firstKeptEntryId, result.tokensBefore, result.details, fromExtension);
2452
+ this.agent.state.messages = this.sessionManager.buildSessionContext().messages;
2453
+ const newEntries = this.sessionManager.getEntries();
2454
+ const savedCompactionEntry = newEntries.find((entry) => entry.type === "compaction" && entry.summary === result.summary);
2455
+ if (this._extensionRunner && savedCompactionEntry) {
2456
+ await this._extensionRunner.emit({
2457
+ type: "session_compact",
2458
+ compactionEntry: savedCompactionEntry,
2459
+ fromExtension,
2460
+ });
2461
+ }
2462
+ },
2463
+ onTransition: ({ cycle, cause }) => {
2464
+ this._emit({
2465
+ type: "warning",
2466
+ message: `auto-compaction cycle ${cycle}: ${cause} — retrying from step 0`,
2467
+ });
2468
+ },
2469
+ signal,
2470
+ });
2471
+ if (outcome.kind === "skip") {
2428
2472
  this._emit({
2429
2473
  type: "compaction_end",
2430
2474
  reason,
2431
2475
  result: undefined,
2432
2476
  aborted: false,
2433
2477
  willRetry: false,
2434
- skipReason: lastEntry?.type === "compaction" ? "already compacted" : "nothing to compact (session too small)",
2478
+ skipReason: outcome.reason,
2435
2479
  });
2436
- return false;
2480
+ return hadQueuedMessages || this.agent.hasQueuedMessages();
2437
2481
  }
2438
- let extensionCompaction;
2439
- let fromExtension = false;
2440
- if (this._extensionRunner.hasHandlers("session_before_compact")) {
2441
- const extensionResult = (await this._extensionRunner.emit({
2442
- type: "session_before_compact",
2443
- preparation,
2444
- branchEntries: pathEntries,
2445
- customInstructions: undefined,
2446
- signal: this._autoCompactionAbortController.signal,
2447
- }));
2448
- if (extensionResult?.cancel) {
2449
- this._emit({
2450
- type: "compaction_end",
2451
- reason,
2452
- result: undefined,
2453
- aborted: true,
2454
- willRetry: false,
2455
- });
2482
+ if (outcome.kind === "failed") {
2483
+ if (outcome.reason === "aborted") {
2484
+ this._emit({ type: "compaction_end", reason, result: undefined, aborted: true, willRetry: false });
2456
2485
  return false;
2457
2486
  }
2458
- if (extensionResult?.compaction) {
2459
- extensionCompaction = extensionResult.compaction;
2460
- fromExtension = true;
2461
- }
2487
+ throw new Error(outcome.reason);
2462
2488
  }
2463
- let summary;
2464
- let firstKeptEntryId;
2465
- let tokensBefore;
2466
- let details;
2467
- if (extensionCompaction) {
2468
- // Extension provided compaction content
2469
- summary = extensionCompaction.summary;
2470
- firstKeptEntryId = extensionCompaction.firstKeptEntryId;
2471
- tokensBefore = extensionCompaction.tokensBefore;
2472
- details = extensionCompaction.details;
2473
- }
2474
- else {
2475
- // Generate compaction result
2476
- const autoCompactionSignal = this._autoCompactionAbortController.signal;
2477
- const compactResult = await this._compactWithRetry(() => compact(preparation, compactionModel, apiKey, headers, undefined, autoCompactionSignal, compactionThinkingLevel, this.agent.streamFn, this._buildCompactionPreDigest()), autoCompactionSignal);
2478
- summary = compactResult.summary;
2479
- firstKeptEntryId = compactResult.firstKeptEntryId;
2480
- tokensBefore = compactResult.tokensBefore;
2481
- details = compactResult.details;
2482
- }
2483
- if (this._autoCompactionAbortController.signal.aborted) {
2484
- this._emit({
2485
- type: "compaction_end",
2486
- reason,
2487
- result: undefined,
2488
- aborted: true,
2489
- willRetry: false,
2490
- });
2489
+ if (extensionCancelled || signal.aborted) {
2490
+ this._emit({ type: "compaction_end", reason, result: undefined, aborted: true, willRetry: false });
2491
2491
  return false;
2492
2492
  }
2493
- this.sessionManager.appendCompaction(summary, firstKeptEntryId, tokensBefore, details, fromExtension);
2494
- const newEntries = this.sessionManager.getEntries();
2495
- const sessionContext = this.sessionManager.buildSessionContext();
2496
- this.agent.state.messages = sessionContext.messages;
2497
- // Get the saved compaction entry for the extension event
2498
- const savedCompactionEntry = newEntries.find((e) => e.type === "compaction" && e.summary === summary);
2499
- if (this._extensionRunner && savedCompactionEntry) {
2500
- await this._extensionRunner.emit({
2501
- type: "session_compact",
2502
- compactionEntry: savedCompactionEntry,
2503
- fromExtension,
2504
- });
2493
+ const result = outcome.kind === "success" ? outcome.result : lastCompaction;
2494
+ if (!result) {
2495
+ throw new Error("Auto-compaction succeeded without a result");
2505
2496
  }
2506
- const result = {
2507
- summary,
2508
- firstKeptEntryId,
2509
- tokensBefore,
2510
- details,
2511
- };
2512
2497
  this._emit({ type: "compaction_end", reason, result, aborted: false, willRetry });
2513
2498
  if (willRetry) {
2514
2499
  const messages = this.agent.state.messages;
@@ -2518,9 +2503,7 @@ export class AgentSession {
2518
2503
  }
2519
2504
  return true;
2520
2505
  }
2521
- // Auto-compaction can complete while follow-up/steering/custom messages are waiting.
2522
- // Continue once so queued messages are delivered.
2523
- return this.agent.hasQueuedMessages();
2506
+ return hadQueuedMessages || this.agent.hasQueuedMessages();
2524
2507
  }
2525
2508
  catch (error) {
2526
2509
  const errorMessage = error instanceof Error ? error.message : "compaction failed";
@@ -2548,7 +2531,7 @@ export class AgentSession {
2548
2531
  * same failure via auto-retry. Caller aborts are never retried; sleepAbortable rejects
2549
2532
  * with the abort reason if the signal fires mid-backoff.
2550
2533
  */
2551
- async _compactWithRetry(run, signal) {
2534
+ async _compactWithRetry(run, signal, provider) {
2552
2535
  const retrySettings = this.settingsManager.getRetrySettings();
2553
2536
  const maxAttempts = retrySettings.enabled ? Math.max(1, retrySettings.maxRetries + 1) : 1;
2554
2537
  const policy = {
@@ -2565,7 +2548,8 @@ export class AgentSession {
2565
2548
  if (signal.aborted || attempt >= maxAttempts)
2566
2549
  throw error;
2567
2550
  const message = error instanceof Error ? error.message : String(error);
2568
- const classified = classifyFailure({ message });
2551
+ const classified = classifyFailure({ message, provider });
2552
+ this._failureCorpus.record({ provider, message, classified });
2569
2553
  if (!classified.retryable)
2570
2554
  throw error;
2571
2555
  await sleepAbortable(computeRetryDelayMs(policy, attempt, { retryAfterMs: classified.retryAfterMs }), signal);
@@ -2851,10 +2835,18 @@ export class AgentSession {
2851
2835
  if (message.stopReason !== "error" || !message.errorMessage)
2852
2836
  return false;
2853
2837
  const contextWindow = this.model?.contextWindow ?? 0;
2854
- return classifyFailure({
2838
+ const classified = classifyFailure({
2855
2839
  message: message.errorMessage,
2856
2840
  contextOverflow: isContextOverflow(message, contextWindow),
2857
- }).retryable;
2841
+ provider: message.provider,
2842
+ });
2843
+ this._failureCorpus.record({
2844
+ provider: message.provider,
2845
+ modelId: message.model,
2846
+ message: message.errorMessage,
2847
+ classified,
2848
+ });
2849
+ return classified.retryable;
2858
2850
  }
2859
2851
  /**
2860
2852
  * Prepare a retryable error for continuation with exponential backoff.