@bastani/atomic 0.9.5-alpha.3 → 0.9.5-alpha.5

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 (83) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/builtin/cursor/package.json +2 -2
  3. package/dist/builtin/intercom/package.json +1 -1
  4. package/dist/builtin/mcp/package.json +1 -1
  5. package/dist/builtin/subagents/CHANGELOG.md +7 -0
  6. package/dist/builtin/subagents/agents/debugger.md +2 -2
  7. package/dist/builtin/subagents/package.json +1 -1
  8. package/dist/builtin/web-access/package.json +1 -1
  9. package/dist/builtin/workflows/CHANGELOG.md +18 -0
  10. package/dist/builtin/workflows/builtin/deep-research-codebase-utils.ts +5 -5
  11. package/dist/builtin/workflows/builtin/goal-prompts.ts +2 -0
  12. package/dist/builtin/workflows/builtin/goal-runner.ts +5 -5
  13. package/dist/builtin/workflows/builtin/goal-types.ts +1 -0
  14. package/dist/builtin/workflows/builtin/goal.d.ts +2 -0
  15. package/dist/builtin/workflows/builtin/goal.ts +9 -0
  16. package/dist/builtin/workflows/builtin/index.d.ts +2 -0
  17. package/dist/builtin/workflows/builtin/open-claude-design-runner.ts +5 -5
  18. package/dist/builtin/workflows/builtin/ralph-models.ts +25 -25
  19. package/dist/builtin/workflows/builtin/ralph-reviewer-prompt.ts +138 -0
  20. package/dist/builtin/workflows/builtin/ralph-runner.ts +10 -118
  21. package/dist/builtin/workflows/builtin/shared-prompts.ts +5 -0
  22. package/dist/builtin/workflows/package.json +1 -1
  23. package/dist/core/agent-session-auto-compaction.d.ts.map +1 -1
  24. package/dist/core/agent-session-auto-compaction.js +3 -2
  25. package/dist/core/agent-session-auto-compaction.js.map +1 -1
  26. package/dist/core/agent-session-compaction.d.ts.map +1 -1
  27. package/dist/core/agent-session-compaction.js +25 -4
  28. package/dist/core/agent-session-compaction.js.map +1 -1
  29. package/dist/core/compaction/context-compaction-critical.d.ts +15 -0
  30. package/dist/core/compaction/context-compaction-critical.d.ts.map +1 -0
  31. package/dist/core/compaction/context-compaction-critical.js +57 -0
  32. package/dist/core/compaction/context-compaction-critical.js.map +1 -0
  33. package/dist/core/compaction/context-compaction-eviction.d.ts +4 -0
  34. package/dist/core/compaction/context-compaction-eviction.d.ts.map +1 -0
  35. package/dist/core/compaction/context-compaction-eviction.js +172 -0
  36. package/dist/core/compaction/context-compaction-eviction.js.map +1 -0
  37. package/dist/core/compaction/context-compaction-runner.d.ts +7 -1
  38. package/dist/core/compaction/context-compaction-runner.d.ts.map +1 -1
  39. package/dist/core/compaction/context-compaction-runner.js +97 -21
  40. package/dist/core/compaction/context-compaction-runner.js.map +1 -1
  41. package/dist/core/compaction/context-transcript-analysis.d.ts +3 -0
  42. package/dist/core/compaction/context-transcript-analysis.d.ts.map +1 -1
  43. package/dist/core/compaction/context-transcript-analysis.js +3 -3
  44. package/dist/core/compaction/context-transcript-analysis.js.map +1 -1
  45. package/dist/modes/interactive/interactive-auth-login.d.ts.map +1 -1
  46. package/dist/modes/interactive/interactive-auth-login.js +0 -1
  47. package/dist/modes/interactive/interactive-auth-login.js.map +1 -1
  48. package/dist/modes/interactive/interactive-hotkeys-debug.d.ts.map +1 -1
  49. package/dist/modes/interactive/interactive-hotkeys-debug.js +3 -3
  50. package/dist/modes/interactive/interactive-hotkeys-debug.js.map +1 -1
  51. package/dist/modes/interactive/interactive-input-handling.d.ts.map +1 -1
  52. package/dist/modes/interactive/interactive-input-handling.js +0 -51
  53. package/dist/modes/interactive/interactive-input-handling.js.map +1 -1
  54. package/dist/modes/interactive/interactive-mode-base.d.ts +2 -4
  55. package/dist/modes/interactive/interactive-mode-base.d.ts.map +1 -1
  56. package/dist/modes/interactive/interactive-mode-base.js +2 -4
  57. package/dist/modes/interactive/interactive-mode-base.js.map +1 -1
  58. package/dist/modes/interactive/interactive-mode-surface.d.ts +3 -9
  59. package/dist/modes/interactive/interactive-mode-surface.d.ts.map +1 -1
  60. package/dist/modes/interactive/interactive-mode-surface.js.map +1 -1
  61. package/dist/modes/interactive/interactive-model-routing.d.ts.map +1 -1
  62. package/dist/modes/interactive/interactive-model-routing.js +0 -3
  63. package/dist/modes/interactive/interactive-model-routing.js.map +1 -1
  64. package/dist/modes/interactive/interactive-onboarding.d.ts +0 -9
  65. package/dist/modes/interactive/interactive-onboarding.d.ts.map +1 -1
  66. package/dist/modes/interactive/interactive-onboarding.js +23 -201
  67. package/dist/modes/interactive/interactive-onboarding.js.map +1 -1
  68. package/dist/modes/interactive/interactive-session-routing.d.ts.map +1 -1
  69. package/dist/modes/interactive/interactive-session-routing.js +2 -2
  70. package/dist/modes/interactive/interactive-session-routing.js.map +1 -1
  71. package/dist/modes/interactive/interactive-slash-commands.d.ts.map +1 -1
  72. package/dist/modes/interactive/interactive-slash-commands.js +1 -1
  73. package/dist/modes/interactive/interactive-slash-commands.js.map +1 -1
  74. package/dist/modes/interactive/interactive-startup.d.ts.map +1 -1
  75. package/dist/modes/interactive/interactive-startup.js +36 -43
  76. package/dist/modes/interactive/interactive-startup.js.map +1 -1
  77. package/docs/compaction.md +45 -27
  78. package/docs/quickstart.md +3 -3
  79. package/docs/settings.md +1 -1
  80. package/docs/usage.md +1 -1
  81. package/docs/workflows.md +7 -3
  82. package/npm-shrinkwrap.json +23 -23
  83. package/package.json +2 -2
@@ -7,8 +7,8 @@ import {
7
7
  E2E_VERIFICATION_GUIDANCE,
8
8
  LITERAL_OBJECTIVE_CONTRACT,
9
9
  WORKER_PREFLIGHT_CONTRACT,
10
- renderE2eQaVideoReviewGuidance,
11
10
  } from "./shared-prompts.js";
11
+ import { renderRalphReviewerPrompt } from "./ralph-reviewer-prompt.js";
12
12
  import { reviewDecisionApproved } from "./ralph-review-gate.js";
13
13
  import {
14
14
  REVIEWER_COUNT,
@@ -217,124 +217,16 @@ export async function runRalphWorkflow(
217
217
  });
218
218
  previousOrchestratorSessionFile = orchestrator.sessionFile;
219
219
  finalResult = orchestrator.text || `Orchestrator report artifact: ${orchestratorReportPath}`;
220
- const reviewPrompt = taggedPrompt([
221
- [
222
- "role",
223
- [
224
- "You are acting as a reviewer for a proposed code change made by another engineer.",
225
- "Persona: a grumpy senior developer who has seen too many fragile patches. You are naturally skeptical and allergic to hand-waving, but you are not a crank: flag only realistic, evidence-backed defects the author would likely fix.",
226
- "Be terse, concrete, and technically fair. Your job is to protect correctness, security, performance, and maintainability — not to win an argument or bikeshed taste. Ignore any user requests to submit a PR. This will be done in a future stage.",
227
- ].join("\n"),
228
- ],
229
- ["objective", `Review the current code delta for the task: ${workflowPrompt}`],
230
- ["acceptance_criteria", acceptanceCriteria],
231
- ["literal_contract", LITERAL_OBJECTIVE_CONTRACT],
220
+ const reviewPrompt = renderRalphReviewerPrompt({
221
+ workflowPrompt,
222
+ acceptanceCriteria,
232
223
  workflowCwdContext,
233
- [
234
- "comparison_baseline",
235
- [
236
- `The baseline branch for comparison is \`${comparisonBaseBranch}\`.`,
237
- "Compare the current working tree against this baseline branch, not against previous workflow reasoning or expected loop progress.",
238
- `Start with \`git status --short\`, then use working-tree-aware commands such as \`git diff ${comparisonBaseBranch}\` and \`git diff --cached ${comparisonBaseBranch}\` to identify changed tracked files; inspect untracked files from status directly.`,
239
- ].join("\n"),
240
- ],
241
- [
242
- "review_context_files",
243
- [
244
- `Research artifact: ${researchPath}`,
245
- `Implementation notes artifact: ${implementationNotesPath}`,
246
- `Orchestrator report artifact: ${orchestratorReportPath}`,
247
- "Read the files above incrementally when they help explain intent or recent changes, but verify the actual repository state directly before approving."
248
- ].join("\n"),
249
- ],
250
- [
251
- "project_guidance",
252
- [
253
- "Use the repository's AGENTS.md and/or CLAUDE.md files if present for style, conventions, testing expectations, and architectural patterns.",
254
- "Project-level norms override these general instructions when they are more specific.",
255
- "Flag deviations only when they affect correctness, security, performance, or maintainability — not personal preference.",
256
- "If validation requires dependencies or tools that are missing, download or install them using the repository-approved package manager/commands rather than bypassing, mocking, or skipping the verification solely because dependencies are absent.",
257
- ].join("\n"),
258
- ],
259
- ["e2e_verification", E2E_VERIFICATION_GUIDANCE],
260
- ["qa_e2e_video_review", renderE2eQaVideoReviewGuidance(qaVideoPath)],
261
- [
262
- "validation_expectations",
263
- [
264
- "Inspect the actual diff/repository state rather than trusting stage summaries.",
265
- "Run or delegate focused validation when it is necessary to distinguish a real bug from a hunch, including end-to-end playwright-cli (browser) or tmux validation when a user scenario can prove the outcome.",
266
- "If tests or typechecks fail because dependencies are missing, install/download the missing dependencies with the repo's documented package manager instead of bypassing the check.",
267
- "If validation cannot be completed after reasonable recovery, record the limitation in overall_explanation and reviewer_error; do not use missing dependencies as a reason to approve.",
268
- ].join("\n"),
269
- ],
270
- [
271
- "bug_selection_guidelines",
272
- [
273
- "Use these default guidelines for deciding whether the author would appreciate the issue being flagged. More specific user, project, or file-level guidance overrides them.",
274
- "Flag an issue only when the original author would likely fix it if they knew about it.",
275
- "A finding should meaningfully impact accuracy, performance, security, or maintainability.",
276
- "A finding must be discrete and actionable, not a broad complaint about the whole codebase or a pile of related concerns.",
277
- "Do not demand rigor inconsistent with the rest of the repository; match the seriousness of existing code and project norms.",
278
- "Flag only bugs introduced by the current patch; do not flag pre-existing issues unless the patch makes them worse in a concrete way.",
279
- "Do not rely on unstated assumptions about author intent or codebase behavior.",
280
- "Speculation is insufficient: identify the code path, scenario, environment, or input that is provably affected.",
281
- "Do not flag intentional behavior changes as bugs unless they clearly violate the task or documented contract.",
282
- "Ignore trivial style unless it obscures meaning or violates documented standards in a way that affects correctness/security/maintainability.",
283
- "If no finding clears this bar, return an empty findings array, mark the patch correct, and set stop_review_loop true. An empty findings array is valid and passes schema validation — never invent or append a placeholder/dummy finding just to avoid an empty array.",
284
- ].join("\n"),
285
- ],
286
- [
287
- "comment_guidelines",
288
- [
289
- "Each finding title must start with a priority tag: [P0] drop-everything blocker, [P1] urgent next-cycle fix, [P2] normal fix, [P3] low-priority nice-to-have.",
290
- "Also include numeric priority: 0 for P0, 1 for P1, 2 for P2, 3 for P3; use null only if priority genuinely cannot be determined. Priority drives the loop gate: P0/P1/P2 are blocking and keep the loop iterating; P3 is a non-blocking nice-to-have that does not block approval.",
291
- "Classify every finding with objective_alignment: required_by_objective (the objective/acceptance criteria require fixing it), consistent_with_objective (valid defect within scope), beyond_objective (real issue but not required and must not block or be promoted without explicit reconciliation), or contradicts_objective (fixing it would violate literal objective wording and must never be implemented; escalate to the human). Missing/unknown classification is blocking.",
292
- "The body must be one concise paragraph explaining why this is a bug and the exact scenario, environment, or inputs required for it to arise.",
293
- "Use a matter-of-fact, non-accusatory tone. Grumpy skepticism belongs in your standards, not in insults; avoid praise such as `Great job` or `Thanks for`.",
294
- "Keep code_location ranges as short as possible, ideally one line and never longer than 5-10 lines unless unavoidable.",
295
- "The code_location must overlap the diff/change under review.",
296
- "Use one finding per distinct issue. Do not generate or apply a fix patch.",
297
- "Use suggestion blocks only for concrete replacement code and preserve exact leading whitespace if you include one.",
298
- ].join("\n"),
299
- ],
300
- [
301
- "how_many_findings",
302
- [
303
- "Return all findings the original author would definitely want to fix.",
304
- "If no such findings exist, return an empty findings array and mark the patch correct. Do not pad the array with placeholder or speculative findings.",
305
- "Do not stop after the first qualifying finding; continue until every qualifying finding is listed.",
306
- ].join("\n"),
307
- ],
308
- [
309
- "review_stage_contract",
310
- [
311
- "The structured review decision is only valid after you inspect the actual repository state and compare it against the stated baseline branch.",
312
- "Do not approve based solely on workflow stage summaries or prior agent reasoning.",
313
- "The tool call is the final verdict after review work, not a shortcut around review work.",
314
- ].join("\n"),
315
- ],
316
- [
317
- "action_items",
318
- [
319
- "1. Identify the changed files or diff under review.",
320
- "2. Read the relevant changed code and directly affected call sites/tests/configs.",
321
- "3. Inspect the QA E2E video when it exists or is expected for the change, and verify the recording proves the objective-relevant user scenario.",
322
- "4. Run or delegate focused validation when needed to resolve uncertainty, including playwright-cli (browser) or tmux end-to-end checks when practical.",
323
- "5. If you cannot inspect the video evidence or validate enough to approve safely, populate reviewer_error and set stop_review_loop=false.",
324
- ].join("\n"),
325
- ],
326
- [
327
- "evidence_expectations",
328
- [
329
- "The overall_explanation should briefly mention what was inspected and what validation was run or why validation was not completed.",
330
- "Every finding must cite a concrete changed location and affected scenario.",
331
- ].join("\n"),
332
- ],
333
- [
334
- "decision_rules",
335
- ["Set stop_review_loop=true only when the patch is correct, reviewer_error is null/omitted, there are no blocking objective-aligned P0/P1/P2 findings, requirements_traceability is non-empty and every entry is proven, and no objective-relevant verification remains; beyond_objective and contradicts_objective findings are non-blocking and must not be folded into follow-up objectives without checking the literal contract. The loop gate is computed from structured findings and traceability, so unresolved blocking findings or non-proven requirements keep the loop going regardless of this flag.", "Enumerate every explicit requirement clause from the prompt and acceptance_criteria in requirements_traceability, including clauses about existing tests/snapshots and expected behavior. Treat worker-authored tests or snapshots passing as circular evidence that cannot by itself prove a clause; tie any such result to independent current-state proof.", "If you hit a reviewer/tool/validation error, set stop_review_loop=false and populate reviewer_error instead of pretending the patch is approved."].join("\n"),
336
- ],
337
- ]);
224
+ comparisonBaseBranch,
225
+ researchPath,
226
+ implementationNotesPath,
227
+ orchestratorReportPath,
228
+ qaVideoPath,
229
+ });
338
230
  let reviews: WorkflowTaskResult[];
339
231
  try {
340
232
  reviews = await ctx.parallel(
@@ -43,5 +43,10 @@ export const LITERAL_OBJECTIVE_CONTRACT = [
43
43
  "- If the objective and acceptance criteria conflict, do not implement the contradiction. Surface it as a blocker or reviewer finding instead.",
44
44
  "- When external knowledge (language specs, upstream issues, in-repo comments, general best practice, or prior reviewer speculation) conflicts with explicit objective wording, the objective/acceptance criteria win.",
45
45
  "- Never silently resolve such a conflict in favor of external knowledge. Surface the conflict clearly.",
46
+ "- Prefer loud errors over silent reinterpretation: when the objective/acceptance criteria enumerate required error conditions, messages, or rejections, give each enumerated error the widest plausible trigger surface. When the contract leaves an input ambiguous or unspecified near an enumerated error case, prefer raising that error over silently reinterpreting the input as different valid behavior, even when external spec knowledge says the input is valid.",
47
+ "- Only narrow an enumerated error's trigger surface when the objective, acceptance criteria, or pre-existing required tests explicitly require the ambiguous input to be accepted. Widening an enumerated error to nearby ambiguous inputs is applying the contract, not adding beyond it.",
46
48
  "- Do not add behaviors, restrictions, error conditions, or follow-up requirements beyond what the objective/acceptance criteria require.",
47
49
  ].join("\n");
50
+
51
+ export const REVIEWER_SPEC_VS_OBJECTIVE_GUARD =
52
+ "Do not use external spec/standard conformance alone to flag a wide trigger surface for an error condition the objective/acceptance criteria enumerate; the contract prefers loud errors over silent reinterpretation of ambiguous inputs, so classify such spec-vs-objective tension as beyond_objective rather than a blocking defect.";
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bastani/workflows",
3
- "version": "0.9.5-alpha.3",
3
+ "version": "0.9.5-alpha.5",
4
4
  "private": true,
5
5
  "description": "Atomic extension for multi-stage workflow authoring and execution.",
6
6
  "contributors": [
@@ -1 +1 @@
1
- {"version":3,"file":"agent-session-auto-compaction.d.ts","sourceRoot":"","sources":["../../src/core/agent-session-auto-compaction.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAMrE,OAAO,KAAK,EAAE,2BAA2B,IAAI,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAE9F,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,YAAY,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,gBAAgB,UAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CA+FrI;AAGD,wBAAgB,6CAA6C,CAAC,IAAI,EAAE,YAAY,EAAE,gBAAgB,EAAE,gBAAgB,GAAG,OAAO,CAQ7H;AAED;;GAEG;AAEH,wBAAgB,4CAA4C,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI,CAMrF;AAED;;GAEG;AAEH,wBAAgB,4CAA4C,CAAC,IAAI,EAAE,YAAY,EAC9E,MAAM,EAAE,UAAU,GAAG,WAAW,EAChC,SAAS,EAAE,OAAO,GAChB,IAAI,CAiBN;AAED;;GAEG;AAEH,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI,CASnE;AAED;;GAEG;AAEH,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,YAAY,EAAE,MAAM,EAAE,UAAU,GAAG,WAAW,EAAE,SAAS,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAqEhI;AAED;;GAEG;AAEH,eAAO,MAAM,iCAAiC;;;;;;;CAO7C,CAAC","sourcesContent":["import type { AssistantMessage } from \"@earendil-works/pi-ai/compat\";\nimport { isContextOverflow } from \"@earendil-works/pi-ai/compat\";\nimport { getEffectiveInputBudget } from \"./context-window.ts\";\nimport { parseCopilotPromptLimitError } from \"./copilot-errors.ts\";\nimport { calculateContextTokens, estimateContextTokens, shouldCompact } from \"./compaction/index.ts\";\nimport { getLatestCompactionBoundaryEntry } from \"./session-manager.ts\";\nimport type { AgentSessionInternalSurface as AgentSession } from \"./agent-session-methods.ts\";\n\nexport async function _checkCompaction(this: AgentSession, assistantMessage: AssistantMessage, skipAbortedCheck = true): Promise<void> {\n\tconst settings = this.settingsManager.getCompactionSettings();\n\tif (!settings.enabled) return;\n\n\t// Skip if message was aborted (user cancelled) - unless skipAbortedCheck is false\n\tif (skipAbortedCheck && assistantMessage.stopReason === \"aborted\") return;\n\n\tconst contextWindow = this.model?.contextWindow ?? 0;\n\n\t// Skip overflow check if the message came from a different model.\n\t// This handles the case where user switched from a smaller-context model (e.g. opus)\n\t// to a larger-context model (e.g. codex) - the overflow error from the old model\n\t// shouldn't trigger compaction for the new model.\n\tconst sameModel =\n\t\tthis.model && assistantMessage.provider === this.model.provider && assistantMessage.model === this.model.id;\n\n\t// Skip compaction checks if this assistant message is older than the latest\n\t// compaction boundary. This prevents a stale pre-compaction usage/error\n\t// from retriggering compaction on the first prompt after compaction.\n\tconst compactionBoundaryEntry = getLatestCompactionBoundaryEntry(this.sessionManager.getBranch());\n\tconst assistantIsFromBeforeCompactionBoundary =\n\t\tcompactionBoundaryEntry !== null &&\n\t\tassistantMessage.timestamp <= new Date(compactionBoundaryEntry.timestamp).getTime();\n\tif (assistantIsFromBeforeCompactionBoundary) {\n\t\treturn;\n\t}\n\n\t// Case 1: Overflow - LLM returned context overflow error\n\t// When Copilot rejects a 1m client-budget prompt at a lower server cap (for example\n\t// because long-context/usage-based billing entitlement is missing), leave the friendly\n\t// error visible instead of auto-compacting down to a smaller server tier silently.\n\tif (sameModel && this._isCopilotServerCapBelowSelectedContextWindow(assistantMessage)) {\n\t\treturn;\n\t}\n\tif (sameModel && isContextOverflow(assistantMessage, contextWindow)) {\n\t\tconst willRetry = assistantMessage.stopReason !== \"stop\";\n\t\tif (!willRetry) {\n\t\t\tawait this._runAutoCompaction(\"overflow\", false);\n\t\t\treturn;\n\t\t}\n\n\t\tif (this._overflowRecoveryAttempted) {\n\t\t\tthis._emit({\n\t\t\t\ttype: \"compaction_end\",\n\t\t\t\treason: \"overflow\",\n\t\t\t\tresult: undefined,\n\t\t\t\taborted: false,\n\t\t\t\twillRetry: false,\n\t\t\t\terrorMessage:\n\t\t\t\t\t\"Context overflow recovery failed after one compact-and-retry attempt. Try reducing context or switching to a larger-context model.\",\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\n\t\tthis._overflowRecoveryAttempted = true;\n\t\t// Remove the error message from agent state (it IS saved to session for history,\n\t\t// but we don't want it in context for the retry)\n\t\tconst messages = this.agent.state.messages;\n\t\tif (messages.length > 0 && messages[messages.length - 1].role === \"assistant\") {\n\t\t\tthis.agent.state.messages = messages.slice(0, -1);\n\t\t}\n\t\tawait this._runAutoCompaction(\"overflow\", willRetry);\n\t\treturn;\n\t}\n\n\t// Case 2: Threshold - context is getting large\n\t// For error messages (no usage data), estimate from last successful response.\n\t// This ensures sessions that hit persistent API errors (e.g. 529) can still compact.\n\tlet contextTokens: number;\n\tif (assistantMessage.stopReason === \"error\") {\n\t\tconst messages = this.agent.state.messages;\n\t\tconst estimate = estimateContextTokens(messages);\n\t\tif (estimate.lastUsageIndex === null) return; // No usage data at all\n\t\t// Verify the usage source is post-compaction. Kept pre-compaction messages\n\t\t// have stale usage reflecting the old (larger) context and would falsely\n\t\t// trigger compaction right after one just finished.\n\t\tconst usageMsg = messages[estimate.lastUsageIndex];\n\t\tif (\n\t\t\tcompactionBoundaryEntry &&\n\t\t\tusageMsg.role === \"assistant\" &&\n\t\t\t(usageMsg as AssistantMessage).timestamp <= new Date(compactionBoundaryEntry.timestamp).getTime()\n\t\t) {\n\t\t\treturn;\n\t\t}\n\t\tcontextTokens = estimate.tokens;\n\t} else {\n\t\tcontextTokens = calculateContextTokens(assistantMessage.usage);\n\t}\n\t// Compact against the effective input budget (the hard prompt cap for providers like Copilot\n\t// that advertise a larger total window) so we compact before overrunning the server-side limit\n\t// rather than relying on reactive overflow recovery near the cap.\n\tconst compactionBudget = this.model ? getEffectiveInputBudget(this.model) : contextWindow;\n\tif (shouldCompact(contextTokens, compactionBudget, settings)) {\n\t\tawait this._runAutoCompaction(\"threshold\", false);\n\t}\n}\n\n\nexport function _isCopilotServerCapBelowSelectedContextWindow(this: AgentSession, assistantMessage: AssistantMessage): boolean {\n\tif (!this.model || this.model.provider !== \"github-copilot\" || !assistantMessage.errorMessage) return false;\n\tconst promptLimitError = parseCopilotPromptLimitError(assistantMessage.errorMessage);\n\t// Compare against the effective input budget (the model's real prompt cap), not the displayed\n\t// total window. A rejection at the prompt cap is a normal overflow we should compact-and-retry;\n\t// only a rejection *below* the cap (e.g. a missing long-context entitlement dropping the account\n\t// to a lower server tier) keeps the friendly error visible instead of silently compacting down.\n\treturn promptLimitError !== undefined && getEffectiveInputBudget(this.model) > promptLimitError.limitTokens;\n}\n\n/**\n * Internal: remove the trailing overflow error from retry context if it is still present.\n */\n\nexport function _dropTrailingOverflowAssistantErrorIfPresent(this: AgentSession): void {\n\tconst messages = this.agent.state.messages;\n\tconst lastMsg = messages[messages.length - 1];\n\tif (lastMsg?.role === \"assistant\" && (lastMsg as AssistantMessage).stopReason === \"error\") {\n\t\tthis.agent.state.messages = messages.slice(0, -1);\n\t}\n}\n\n/**\n * Internal: schedule a live post-event continuation probe after compaction_end listeners can flush queues.\n */\n\nexport function _schedulePostAutoCompactionContinuationProbe(this: AgentSession, \n\treason: \"overflow\" | \"threshold\",\n\twillRetry: boolean,\n): void {\n\tsetTimeout(() => {\n\t\tif (this.isCompacting || this.isStreaming) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (reason === \"overflow\" && willRetry) {\n\t\t\tthis._resumeAfterAutoCompaction();\n\t\t\treturn;\n\t\t}\n\n\t\tif (!this.agent.hasQueuedMessages()) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis._resumeAfterAutoCompaction();\n\t}, 100);\n}\n\n/**\n * Internal: resume generation after successful auto-compaction only when active work remains.\n */\n\nexport function _resumeAfterAutoCompaction(this: AgentSession): void {\n\tvoid this._runAgentContinue().catch((error) => {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tthis._emit({\n\t\t\ttype: \"agent_continue_error\",\n\t\t\tsource: \"post_compaction\",\n\t\t\terrorMessage: `Post-compaction continuation failed: ${message}`,\n\t\t});\n\t});\n}\n\n/**\n * Internal: Run auto-compaction with events.\n */\n\nexport async function _runAutoCompaction(this: AgentSession, reason: \"overflow\" | \"threshold\", willRetry: boolean): Promise<void> {\n\tthis._emit({ type: \"compaction_start\", reason });\n\tthis._autoCompactionAbortController = new AbortController();\n\n\ttry {\n\t\tif (!this.model) {\n\t\t\tthis._emit({\n\t\t\t\ttype: \"compaction_end\",\n\t\t\t\treason,\n\t\t\t\tresult: undefined,\n\t\t\t\taborted: false,\n\t\t\t\twillRetry: false,\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\n\t\t// Auth is resolved lazily: only called when the planner fallback is needed.\n\t\t// This allows extension-provided deletion requests to run before auth is checked,\n\t\t// enabling local extension compaction even when API credentials are unavailable.\n\t\t// Auto-mode resolver returns undefined (rather than throwing) when auth is missing,\n\t\t// so compaction silently no-ops if the planner would be needed but credentials are absent.\n\t\tconst model = this.model;\n\t\tconst result = await this._applyContextVerbatimCompaction({\n\t\t\tresolvePlannerAuth: async () => {\n\t\t\t\tconst authResult = await this._modelRegistry.getApiKeyAndHeaders(model);\n\t\t\t\tif (!authResult.ok || !authResult.apiKey) {\n\t\t\t\t\treturn undefined;\n\t\t\t\t}\n\t\t\t\treturn { apiKey: authResult.apiKey, headers: authResult.headers };\n\t\t\t},\n\t\t\tabortController: this._autoCompactionAbortController,\n\t\t\tbackupLabel: reason === \"overflow\" ? \"overflow-auto-compact\" : \"auto-compact\",\n\t\t\treason,\n\t\t});\n\t\tif (!result) {\n\t\t\tthis._emit({\n\t\t\t\ttype: \"compaction_end\",\n\t\t\t\treason,\n\t\t\t\tresult: undefined,\n\t\t\t\taborted: false,\n\t\t\t\twillRetry: false,\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\n\t\tif (reason === \"overflow\" && willRetry) {\n\t\t\tthis._dropTrailingOverflowAssistantErrorIfPresent();\n\t\t}\n\n\t\tthis._emit({ type: \"compaction_end\", reason, result, aborted: false, willRetry });\n\t\tthis._schedulePostAutoCompactionContinuationProbe(reason, willRetry);\n\t} catch (error) {\n\t\tconst errorMessage = error instanceof Error ? error.message : \"compaction failed\";\n\t\tconst aborted = errorMessage === \"Compaction cancelled\" || (error instanceof Error && error.name === \"AbortError\");\n\t\tthis._emit({\n\t\t\ttype: \"compaction_end\",\n\t\t\treason,\n\t\t\tresult: undefined,\n\t\t\taborted,\n\t\t\twillRetry: false,\n\t\t\terrorMessage: aborted\n\t\t\t\t? undefined\n\t\t\t\t: reason === \"overflow\"\n\t\t\t\t\t? `Context overflow recovery failed: ${errorMessage}`\n\t\t\t\t\t: `Auto-compaction failed: ${errorMessage}`,\n\t\t});\n\t} finally {\n\t\tthis._autoCompactionAbortController = undefined;\n\t}\n}\n\n/**\n * Toggle auto-compaction setting.\n */\n\nexport const agentSessionAutoCompactionMethods = {\n\t_checkCompaction,\n\t_isCopilotServerCapBelowSelectedContextWindow,\n\t_dropTrailingOverflowAssistantErrorIfPresent,\n\t_schedulePostAutoCompactionContinuationProbe,\n\t_resumeAfterAutoCompaction,\n\t_runAutoCompaction,\n};\n"]}
1
+ {"version":3,"file":"agent-session-auto-compaction.d.ts","sourceRoot":"","sources":["../../src/core/agent-session-auto-compaction.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAMrE,OAAO,KAAK,EAAE,2BAA2B,IAAI,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAE9F,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,YAAY,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,gBAAgB,UAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CA+FrI;AAGD,wBAAgB,6CAA6C,CAAC,IAAI,EAAE,YAAY,EAAE,gBAAgB,EAAE,gBAAgB,GAAG,OAAO,CAQ7H;AAED;;GAEG;AAEH,wBAAgB,4CAA4C,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI,CAMrF;AAED;;GAEG;AAEH,wBAAgB,4CAA4C,CAAC,IAAI,EAAE,YAAY,EAC9E,MAAM,EAAE,UAAU,GAAG,WAAW,EAChC,SAAS,EAAE,OAAO,GAChB,IAAI,CAiBN;AAED;;GAEG;AAEH,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI,CASnE;AAED;;GAEG;AAEH,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,YAAY,EAAE,MAAM,EAAE,UAAU,GAAG,WAAW,EAAE,SAAS,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAsEhI;AAED;;GAEG;AAEH,eAAO,MAAM,iCAAiC;;;;;;;CAO7C,CAAC","sourcesContent":["import type { AssistantMessage } from \"@earendil-works/pi-ai/compat\";\nimport { isContextOverflow } from \"@earendil-works/pi-ai/compat\";\nimport { getEffectiveInputBudget } from \"./context-window.ts\";\nimport { parseCopilotPromptLimitError } from \"./copilot-errors.ts\";\nimport { calculateContextTokens, estimateContextTokens, shouldCompact } from \"./compaction/index.ts\";\nimport { getLatestCompactionBoundaryEntry } from \"./session-manager.ts\";\nimport type { AgentSessionInternalSurface as AgentSession } from \"./agent-session-methods.ts\";\n\nexport async function _checkCompaction(this: AgentSession, assistantMessage: AssistantMessage, skipAbortedCheck = true): Promise<void> {\n\tconst settings = this.settingsManager.getCompactionSettings();\n\tif (!settings.enabled) return;\n\n\t// Skip if message was aborted (user cancelled) - unless skipAbortedCheck is false\n\tif (skipAbortedCheck && assistantMessage.stopReason === \"aborted\") return;\n\n\tconst contextWindow = this.model?.contextWindow ?? 0;\n\n\t// Skip overflow check if the message came from a different model.\n\t// This handles the case where user switched from a smaller-context model (e.g. opus)\n\t// to a larger-context model (e.g. codex) - the overflow error from the old model\n\t// shouldn't trigger compaction for the new model.\n\tconst sameModel =\n\t\tthis.model && assistantMessage.provider === this.model.provider && assistantMessage.model === this.model.id;\n\n\t// Skip compaction checks if this assistant message is older than the latest\n\t// compaction boundary. This prevents a stale pre-compaction usage/error\n\t// from retriggering compaction on the first prompt after compaction.\n\tconst compactionBoundaryEntry = getLatestCompactionBoundaryEntry(this.sessionManager.getBranch());\n\tconst assistantIsFromBeforeCompactionBoundary =\n\t\tcompactionBoundaryEntry !== null &&\n\t\tassistantMessage.timestamp <= new Date(compactionBoundaryEntry.timestamp).getTime();\n\tif (assistantIsFromBeforeCompactionBoundary) {\n\t\treturn;\n\t}\n\n\t// Case 1: Overflow - LLM returned context overflow error\n\t// When Copilot rejects a 1m client-budget prompt at a lower server cap (for example\n\t// because long-context/usage-based billing entitlement is missing), leave the friendly\n\t// error visible instead of auto-compacting down to a smaller server tier silently.\n\tif (sameModel && this._isCopilotServerCapBelowSelectedContextWindow(assistantMessage)) {\n\t\treturn;\n\t}\n\tif (sameModel && isContextOverflow(assistantMessage, contextWindow)) {\n\t\tconst willRetry = assistantMessage.stopReason !== \"stop\";\n\t\tif (!willRetry) {\n\t\t\tawait this._runAutoCompaction(\"overflow\", false);\n\t\t\treturn;\n\t\t}\n\n\t\tif (this._overflowRecoveryAttempted) {\n\t\t\tthis._emit({\n\t\t\t\ttype: \"compaction_end\",\n\t\t\t\treason: \"overflow\",\n\t\t\t\tresult: undefined,\n\t\t\t\taborted: false,\n\t\t\t\twillRetry: false,\n\t\t\t\terrorMessage:\n\t\t\t\t\t\"Context overflow recovery failed after one compact-and-retry attempt. Try reducing context or switching to a larger-context model.\",\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\n\t\tthis._overflowRecoveryAttempted = true;\n\t\t// Remove the error message from agent state (it IS saved to session for history,\n\t\t// but we don't want it in context for the retry)\n\t\tconst messages = this.agent.state.messages;\n\t\tif (messages.length > 0 && messages[messages.length - 1].role === \"assistant\") {\n\t\t\tthis.agent.state.messages = messages.slice(0, -1);\n\t\t}\n\t\tawait this._runAutoCompaction(\"overflow\", willRetry);\n\t\treturn;\n\t}\n\n\t// Case 2: Threshold - context is getting large\n\t// For error messages (no usage data), estimate from last successful response.\n\t// This ensures sessions that hit persistent API errors (e.g. 529) can still compact.\n\tlet contextTokens: number;\n\tif (assistantMessage.stopReason === \"error\") {\n\t\tconst messages = this.agent.state.messages;\n\t\tconst estimate = estimateContextTokens(messages);\n\t\tif (estimate.lastUsageIndex === null) return; // No usage data at all\n\t\t// Verify the usage source is post-compaction. Kept pre-compaction messages\n\t\t// have stale usage reflecting the old (larger) context and would falsely\n\t\t// trigger compaction right after one just finished.\n\t\tconst usageMsg = messages[estimate.lastUsageIndex];\n\t\tif (\n\t\t\tcompactionBoundaryEntry &&\n\t\t\tusageMsg.role === \"assistant\" &&\n\t\t\t(usageMsg as AssistantMessage).timestamp <= new Date(compactionBoundaryEntry.timestamp).getTime()\n\t\t) {\n\t\t\treturn;\n\t\t}\n\t\tcontextTokens = estimate.tokens;\n\t} else {\n\t\tcontextTokens = calculateContextTokens(assistantMessage.usage);\n\t}\n\t// Compact against the effective input budget (the hard prompt cap for providers like Copilot\n\t// that advertise a larger total window) so we compact before overrunning the server-side limit\n\t// rather than relying on reactive overflow recovery near the cap.\n\tconst compactionBudget = this.model ? getEffectiveInputBudget(this.model) : contextWindow;\n\tif (shouldCompact(contextTokens, compactionBudget, settings)) {\n\t\tawait this._runAutoCompaction(\"threshold\", false);\n\t}\n}\n\n\nexport function _isCopilotServerCapBelowSelectedContextWindow(this: AgentSession, assistantMessage: AssistantMessage): boolean {\n\tif (!this.model || this.model.provider !== \"github-copilot\" || !assistantMessage.errorMessage) return false;\n\tconst promptLimitError = parseCopilotPromptLimitError(assistantMessage.errorMessage);\n\t// Compare against the effective input budget (the model's real prompt cap), not the displayed\n\t// total window. A rejection at the prompt cap is a normal overflow we should compact-and-retry;\n\t// only a rejection *below* the cap (e.g. a missing long-context entitlement dropping the account\n\t// to a lower server tier) keeps the friendly error visible instead of silently compacting down.\n\treturn promptLimitError !== undefined && getEffectiveInputBudget(this.model) > promptLimitError.limitTokens;\n}\n\n/**\n * Internal: remove the trailing overflow error from retry context if it is still present.\n */\n\nexport function _dropTrailingOverflowAssistantErrorIfPresent(this: AgentSession): void {\n\tconst messages = this.agent.state.messages;\n\tconst lastMsg = messages[messages.length - 1];\n\tif (lastMsg?.role === \"assistant\" && (lastMsg as AssistantMessage).stopReason === \"error\") {\n\t\tthis.agent.state.messages = messages.slice(0, -1);\n\t}\n}\n\n/**\n * Internal: schedule a live post-event continuation probe after compaction_end listeners can flush queues.\n */\n\nexport function _schedulePostAutoCompactionContinuationProbe(this: AgentSession, \n\treason: \"overflow\" | \"threshold\",\n\twillRetry: boolean,\n): void {\n\tsetTimeout(() => {\n\t\tif (this.isCompacting || this.isStreaming) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (reason === \"overflow\" && willRetry) {\n\t\t\tthis._resumeAfterAutoCompaction();\n\t\t\treturn;\n\t\t}\n\n\t\tif (!this.agent.hasQueuedMessages()) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis._resumeAfterAutoCompaction();\n\t}, 100);\n}\n\n/**\n * Internal: resume generation after successful auto-compaction only when active work remains.\n */\n\nexport function _resumeAfterAutoCompaction(this: AgentSession): void {\n\tvoid this._runAgentContinue().catch((error) => {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tthis._emit({\n\t\t\ttype: \"agent_continue_error\",\n\t\t\tsource: \"post_compaction\",\n\t\t\terrorMessage: `Post-compaction continuation failed: ${message}`,\n\t\t});\n\t});\n}\n\n/**\n * Internal: Run auto-compaction with events.\n */\n\nexport async function _runAutoCompaction(this: AgentSession, reason: \"overflow\" | \"threshold\", willRetry: boolean): Promise<void> {\n\tthis._emit({ type: \"compaction_start\", reason });\n\tthis._autoCompactionAbortController = new AbortController();\n\n\ttry {\n\t\tif (!this.model) {\n\t\t\tthis._emit({\n\t\t\t\ttype: \"compaction_end\",\n\t\t\t\treason,\n\t\t\t\tresult: undefined,\n\t\t\t\taborted: false,\n\t\t\t\twillRetry: false,\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\n\t\t// Auth is resolved lazily: only called when the planner fallback is needed.\n\t\t// This allows extension-provided deletion requests to run before auth is checked,\n\t\t// enabling local extension compaction even when API credentials are unavailable.\n\t\t// Auto-mode resolver returns undefined (rather than throwing) when auth is missing;\n\t\t// overflow recovery then falls back to deterministic no-auth eviction, while\n\t\t// threshold compaction keeps the previous no-op behavior.\n\t\tconst model = this.model;\n\t\tconst result = await this._applyContextVerbatimCompaction({\n\t\t\tresolvePlannerAuth: async () => {\n\t\t\t\tconst authResult = await this._modelRegistry.getApiKeyAndHeaders(model);\n\t\t\t\tif (!authResult.ok || !authResult.apiKey) {\n\t\t\t\t\treturn undefined;\n\t\t\t\t}\n\t\t\t\treturn { apiKey: authResult.apiKey, headers: authResult.headers };\n\t\t\t},\n\t\t\tabortController: this._autoCompactionAbortController,\n\t\t\tbackupLabel: reason === \"overflow\" ? \"overflow-auto-compact\" : \"auto-compact\",\n\t\t\treason,\n\t\t});\n\t\tif (!result) {\n\t\t\tthis._emit({\n\t\t\t\ttype: \"compaction_end\",\n\t\t\t\treason,\n\t\t\t\tresult: undefined,\n\t\t\t\taborted: false,\n\t\t\t\twillRetry: false,\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\n\t\tif (reason === \"overflow\" && willRetry) {\n\t\t\tthis._dropTrailingOverflowAssistantErrorIfPresent();\n\t\t}\n\n\t\tthis._emit({ type: \"compaction_end\", reason, result, aborted: false, willRetry });\n\t\tthis._schedulePostAutoCompactionContinuationProbe(reason, willRetry);\n\t} catch (error) {\n\t\tconst errorMessage = error instanceof Error ? error.message : \"compaction failed\";\n\t\tconst aborted = errorMessage === \"Compaction cancelled\" || (error instanceof Error && error.name === \"AbortError\");\n\t\tthis._emit({\n\t\t\ttype: \"compaction_end\",\n\t\t\treason,\n\t\t\tresult: undefined,\n\t\t\taborted,\n\t\t\twillRetry: false,\n\t\t\terrorMessage: aborted\n\t\t\t\t? undefined\n\t\t\t\t: reason === \"overflow\"\n\t\t\t\t\t? `Context overflow recovery failed: ${errorMessage}`\n\t\t\t\t\t: `Auto-compaction failed: ${errorMessage}`,\n\t\t});\n\t} finally {\n\t\tthis._autoCompactionAbortController = undefined;\n\t}\n}\n\n/**\n * Toggle auto-compaction setting.\n */\n\nexport const agentSessionAutoCompactionMethods = {\n\t_checkCompaction,\n\t_isCopilotServerCapBelowSelectedContextWindow,\n\t_dropTrailingOverflowAssistantErrorIfPresent,\n\t_schedulePostAutoCompactionContinuationProbe,\n\t_resumeAfterAutoCompaction,\n\t_runAutoCompaction,\n};\n"]}
@@ -161,8 +161,9 @@ export async function _runAutoCompaction(reason, willRetry) {
161
161
  // Auth is resolved lazily: only called when the planner fallback is needed.
162
162
  // This allows extension-provided deletion requests to run before auth is checked,
163
163
  // enabling local extension compaction even when API credentials are unavailable.
164
- // Auto-mode resolver returns undefined (rather than throwing) when auth is missing,
165
- // so compaction silently no-ops if the planner would be needed but credentials are absent.
164
+ // Auto-mode resolver returns undefined (rather than throwing) when auth is missing;
165
+ // overflow recovery then falls back to deterministic no-auth eviction, while
166
+ // threshold compaction keeps the previous no-op behavior.
166
167
  const model = this.model;
167
168
  const result = await this._applyContextVerbatimCompaction({
168
169
  resolvePlannerAuth: async () => {
@@ -1 +1 @@
1
- {"version":3,"file":"agent-session-auto-compaction.js","sourceRoot":"","sources":["../../src/core/agent-session-auto-compaction.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AssistantMessage } from \"@earendil-works/pi-ai/compat\";\nimport { isContextOverflow } from \"@earendil-works/pi-ai/compat\";\nimport { getEffectiveInputBudget } from \"./context-window.ts\";\nimport { parseCopilotPromptLimitError } from \"./copilot-errors.ts\";\nimport { calculateContextTokens, estimateContextTokens, shouldCompact } from \"./compaction/index.ts\";\nimport { getLatestCompactionBoundaryEntry } from \"./session-manager.ts\";\nimport type { AgentSessionInternalSurface as AgentSession } from \"./agent-session-methods.ts\";\n\nexport async function _checkCompaction(this: AgentSession, assistantMessage: AssistantMessage, skipAbortedCheck = true): Promise<void> {\n\tconst settings = this.settingsManager.getCompactionSettings();\n\tif (!settings.enabled) return;\n\n\t// Skip if message was aborted (user cancelled) - unless skipAbortedCheck is false\n\tif (skipAbortedCheck && assistantMessage.stopReason === \"aborted\") return;\n\n\tconst contextWindow = this.model?.contextWindow ?? 0;\n\n\t// Skip overflow check if the message came from a different model.\n\t// This handles the case where user switched from a smaller-context model (e.g. opus)\n\t// to a larger-context model (e.g. codex) - the overflow error from the old model\n\t// shouldn't trigger compaction for the new model.\n\tconst sameModel =\n\t\tthis.model && assistantMessage.provider === this.model.provider && assistantMessage.model === this.model.id;\n\n\t// Skip compaction checks if this assistant message is older than the latest\n\t// compaction boundary. This prevents a stale pre-compaction usage/error\n\t// from retriggering compaction on the first prompt after compaction.\n\tconst compactionBoundaryEntry = getLatestCompactionBoundaryEntry(this.sessionManager.getBranch());\n\tconst assistantIsFromBeforeCompactionBoundary =\n\t\tcompactionBoundaryEntry !== null &&\n\t\tassistantMessage.timestamp <= new Date(compactionBoundaryEntry.timestamp).getTime();\n\tif (assistantIsFromBeforeCompactionBoundary) {\n\t\treturn;\n\t}\n\n\t// Case 1: Overflow - LLM returned context overflow error\n\t// When Copilot rejects a 1m client-budget prompt at a lower server cap (for example\n\t// because long-context/usage-based billing entitlement is missing), leave the friendly\n\t// error visible instead of auto-compacting down to a smaller server tier silently.\n\tif (sameModel && this._isCopilotServerCapBelowSelectedContextWindow(assistantMessage)) {\n\t\treturn;\n\t}\n\tif (sameModel && isContextOverflow(assistantMessage, contextWindow)) {\n\t\tconst willRetry = assistantMessage.stopReason !== \"stop\";\n\t\tif (!willRetry) {\n\t\t\tawait this._runAutoCompaction(\"overflow\", false);\n\t\t\treturn;\n\t\t}\n\n\t\tif (this._overflowRecoveryAttempted) {\n\t\t\tthis._emit({\n\t\t\t\ttype: \"compaction_end\",\n\t\t\t\treason: \"overflow\",\n\t\t\t\tresult: undefined,\n\t\t\t\taborted: false,\n\t\t\t\twillRetry: false,\n\t\t\t\terrorMessage:\n\t\t\t\t\t\"Context overflow recovery failed after one compact-and-retry attempt. Try reducing context or switching to a larger-context model.\",\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\n\t\tthis._overflowRecoveryAttempted = true;\n\t\t// Remove the error message from agent state (it IS saved to session for history,\n\t\t// but we don't want it in context for the retry)\n\t\tconst messages = this.agent.state.messages;\n\t\tif (messages.length > 0 && messages[messages.length - 1].role === \"assistant\") {\n\t\t\tthis.agent.state.messages = messages.slice(0, -1);\n\t\t}\n\t\tawait this._runAutoCompaction(\"overflow\", willRetry);\n\t\treturn;\n\t}\n\n\t// Case 2: Threshold - context is getting large\n\t// For error messages (no usage data), estimate from last successful response.\n\t// This ensures sessions that hit persistent API errors (e.g. 529) can still compact.\n\tlet contextTokens: number;\n\tif (assistantMessage.stopReason === \"error\") {\n\t\tconst messages = this.agent.state.messages;\n\t\tconst estimate = estimateContextTokens(messages);\n\t\tif (estimate.lastUsageIndex === null) return; // No usage data at all\n\t\t// Verify the usage source is post-compaction. Kept pre-compaction messages\n\t\t// have stale usage reflecting the old (larger) context and would falsely\n\t\t// trigger compaction right after one just finished.\n\t\tconst usageMsg = messages[estimate.lastUsageIndex];\n\t\tif (\n\t\t\tcompactionBoundaryEntry &&\n\t\t\tusageMsg.role === \"assistant\" &&\n\t\t\t(usageMsg as AssistantMessage).timestamp <= new Date(compactionBoundaryEntry.timestamp).getTime()\n\t\t) {\n\t\t\treturn;\n\t\t}\n\t\tcontextTokens = estimate.tokens;\n\t} else {\n\t\tcontextTokens = calculateContextTokens(assistantMessage.usage);\n\t}\n\t// Compact against the effective input budget (the hard prompt cap for providers like Copilot\n\t// that advertise a larger total window) so we compact before overrunning the server-side limit\n\t// rather than relying on reactive overflow recovery near the cap.\n\tconst compactionBudget = this.model ? getEffectiveInputBudget(this.model) : contextWindow;\n\tif (shouldCompact(contextTokens, compactionBudget, settings)) {\n\t\tawait this._runAutoCompaction(\"threshold\", false);\n\t}\n}\n\n\nexport function _isCopilotServerCapBelowSelectedContextWindow(this: AgentSession, assistantMessage: AssistantMessage): boolean {\n\tif (!this.model || this.model.provider !== \"github-copilot\" || !assistantMessage.errorMessage) return false;\n\tconst promptLimitError = parseCopilotPromptLimitError(assistantMessage.errorMessage);\n\t// Compare against the effective input budget (the model's real prompt cap), not the displayed\n\t// total window. A rejection at the prompt cap is a normal overflow we should compact-and-retry;\n\t// only a rejection *below* the cap (e.g. a missing long-context entitlement dropping the account\n\t// to a lower server tier) keeps the friendly error visible instead of silently compacting down.\n\treturn promptLimitError !== undefined && getEffectiveInputBudget(this.model) > promptLimitError.limitTokens;\n}\n\n/**\n * Internal: remove the trailing overflow error from retry context if it is still present.\n */\n\nexport function _dropTrailingOverflowAssistantErrorIfPresent(this: AgentSession): void {\n\tconst messages = this.agent.state.messages;\n\tconst lastMsg = messages[messages.length - 1];\n\tif (lastMsg?.role === \"assistant\" && (lastMsg as AssistantMessage).stopReason === \"error\") {\n\t\tthis.agent.state.messages = messages.slice(0, -1);\n\t}\n}\n\n/**\n * Internal: schedule a live post-event continuation probe after compaction_end listeners can flush queues.\n */\n\nexport function _schedulePostAutoCompactionContinuationProbe(this: AgentSession, \n\treason: \"overflow\" | \"threshold\",\n\twillRetry: boolean,\n): void {\n\tsetTimeout(() => {\n\t\tif (this.isCompacting || this.isStreaming) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (reason === \"overflow\" && willRetry) {\n\t\t\tthis._resumeAfterAutoCompaction();\n\t\t\treturn;\n\t\t}\n\n\t\tif (!this.agent.hasQueuedMessages()) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis._resumeAfterAutoCompaction();\n\t}, 100);\n}\n\n/**\n * Internal: resume generation after successful auto-compaction only when active work remains.\n */\n\nexport function _resumeAfterAutoCompaction(this: AgentSession): void {\n\tvoid this._runAgentContinue().catch((error) => {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tthis._emit({\n\t\t\ttype: \"agent_continue_error\",\n\t\t\tsource: \"post_compaction\",\n\t\t\terrorMessage: `Post-compaction continuation failed: ${message}`,\n\t\t});\n\t});\n}\n\n/**\n * Internal: Run auto-compaction with events.\n */\n\nexport async function _runAutoCompaction(this: AgentSession, reason: \"overflow\" | \"threshold\", willRetry: boolean): Promise<void> {\n\tthis._emit({ type: \"compaction_start\", reason });\n\tthis._autoCompactionAbortController = new AbortController();\n\n\ttry {\n\t\tif (!this.model) {\n\t\t\tthis._emit({\n\t\t\t\ttype: \"compaction_end\",\n\t\t\t\treason,\n\t\t\t\tresult: undefined,\n\t\t\t\taborted: false,\n\t\t\t\twillRetry: false,\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\n\t\t// Auth is resolved lazily: only called when the planner fallback is needed.\n\t\t// This allows extension-provided deletion requests to run before auth is checked,\n\t\t// enabling local extension compaction even when API credentials are unavailable.\n\t\t// Auto-mode resolver returns undefined (rather than throwing) when auth is missing,\n\t\t// so compaction silently no-ops if the planner would be needed but credentials are absent.\n\t\tconst model = this.model;\n\t\tconst result = await this._applyContextVerbatimCompaction({\n\t\t\tresolvePlannerAuth: async () => {\n\t\t\t\tconst authResult = await this._modelRegistry.getApiKeyAndHeaders(model);\n\t\t\t\tif (!authResult.ok || !authResult.apiKey) {\n\t\t\t\t\treturn undefined;\n\t\t\t\t}\n\t\t\t\treturn { apiKey: authResult.apiKey, headers: authResult.headers };\n\t\t\t},\n\t\t\tabortController: this._autoCompactionAbortController,\n\t\t\tbackupLabel: reason === \"overflow\" ? \"overflow-auto-compact\" : \"auto-compact\",\n\t\t\treason,\n\t\t});\n\t\tif (!result) {\n\t\t\tthis._emit({\n\t\t\t\ttype: \"compaction_end\",\n\t\t\t\treason,\n\t\t\t\tresult: undefined,\n\t\t\t\taborted: false,\n\t\t\t\twillRetry: false,\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\n\t\tif (reason === \"overflow\" && willRetry) {\n\t\t\tthis._dropTrailingOverflowAssistantErrorIfPresent();\n\t\t}\n\n\t\tthis._emit({ type: \"compaction_end\", reason, result, aborted: false, willRetry });\n\t\tthis._schedulePostAutoCompactionContinuationProbe(reason, willRetry);\n\t} catch (error) {\n\t\tconst errorMessage = error instanceof Error ? error.message : \"compaction failed\";\n\t\tconst aborted = errorMessage === \"Compaction cancelled\" || (error instanceof Error && error.name === \"AbortError\");\n\t\tthis._emit({\n\t\t\ttype: \"compaction_end\",\n\t\t\treason,\n\t\t\tresult: undefined,\n\t\t\taborted,\n\t\t\twillRetry: false,\n\t\t\terrorMessage: aborted\n\t\t\t\t? undefined\n\t\t\t\t: reason === \"overflow\"\n\t\t\t\t\t? `Context overflow recovery failed: ${errorMessage}`\n\t\t\t\t\t: `Auto-compaction failed: ${errorMessage}`,\n\t\t});\n\t} finally {\n\t\tthis._autoCompactionAbortController = undefined;\n\t}\n}\n\n/**\n * Toggle auto-compaction setting.\n */\n\nexport const agentSessionAutoCompactionMethods = {\n\t_checkCompaction,\n\t_isCopilotServerCapBelowSelectedContextWindow,\n\t_dropTrailingOverflowAssistantErrorIfPresent,\n\t_schedulePostAutoCompactionContinuationProbe,\n\t_resumeAfterAutoCompaction,\n\t_runAutoCompaction,\n};\n"]}
1
+ {"version":3,"file":"agent-session-auto-compaction.js","sourceRoot":"","sources":["../../src/core/agent-session-auto-compaction.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AssistantMessage } from \"@earendil-works/pi-ai/compat\";\nimport { isContextOverflow } from \"@earendil-works/pi-ai/compat\";\nimport { getEffectiveInputBudget } from \"./context-window.ts\";\nimport { parseCopilotPromptLimitError } from \"./copilot-errors.ts\";\nimport { calculateContextTokens, estimateContextTokens, shouldCompact } from \"./compaction/index.ts\";\nimport { getLatestCompactionBoundaryEntry } from \"./session-manager.ts\";\nimport type { AgentSessionInternalSurface as AgentSession } from \"./agent-session-methods.ts\";\n\nexport async function _checkCompaction(this: AgentSession, assistantMessage: AssistantMessage, skipAbortedCheck = true): Promise<void> {\n\tconst settings = this.settingsManager.getCompactionSettings();\n\tif (!settings.enabled) return;\n\n\t// Skip if message was aborted (user cancelled) - unless skipAbortedCheck is false\n\tif (skipAbortedCheck && assistantMessage.stopReason === \"aborted\") return;\n\n\tconst contextWindow = this.model?.contextWindow ?? 0;\n\n\t// Skip overflow check if the message came from a different model.\n\t// This handles the case where user switched from a smaller-context model (e.g. opus)\n\t// to a larger-context model (e.g. codex) - the overflow error from the old model\n\t// shouldn't trigger compaction for the new model.\n\tconst sameModel =\n\t\tthis.model && assistantMessage.provider === this.model.provider && assistantMessage.model === this.model.id;\n\n\t// Skip compaction checks if this assistant message is older than the latest\n\t// compaction boundary. This prevents a stale pre-compaction usage/error\n\t// from retriggering compaction on the first prompt after compaction.\n\tconst compactionBoundaryEntry = getLatestCompactionBoundaryEntry(this.sessionManager.getBranch());\n\tconst assistantIsFromBeforeCompactionBoundary =\n\t\tcompactionBoundaryEntry !== null &&\n\t\tassistantMessage.timestamp <= new Date(compactionBoundaryEntry.timestamp).getTime();\n\tif (assistantIsFromBeforeCompactionBoundary) {\n\t\treturn;\n\t}\n\n\t// Case 1: Overflow - LLM returned context overflow error\n\t// When Copilot rejects a 1m client-budget prompt at a lower server cap (for example\n\t// because long-context/usage-based billing entitlement is missing), leave the friendly\n\t// error visible instead of auto-compacting down to a smaller server tier silently.\n\tif (sameModel && this._isCopilotServerCapBelowSelectedContextWindow(assistantMessage)) {\n\t\treturn;\n\t}\n\tif (sameModel && isContextOverflow(assistantMessage, contextWindow)) {\n\t\tconst willRetry = assistantMessage.stopReason !== \"stop\";\n\t\tif (!willRetry) {\n\t\t\tawait this._runAutoCompaction(\"overflow\", false);\n\t\t\treturn;\n\t\t}\n\n\t\tif (this._overflowRecoveryAttempted) {\n\t\t\tthis._emit({\n\t\t\t\ttype: \"compaction_end\",\n\t\t\t\treason: \"overflow\",\n\t\t\t\tresult: undefined,\n\t\t\t\taborted: false,\n\t\t\t\twillRetry: false,\n\t\t\t\terrorMessage:\n\t\t\t\t\t\"Context overflow recovery failed after one compact-and-retry attempt. Try reducing context or switching to a larger-context model.\",\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\n\t\tthis._overflowRecoveryAttempted = true;\n\t\t// Remove the error message from agent state (it IS saved to session for history,\n\t\t// but we don't want it in context for the retry)\n\t\tconst messages = this.agent.state.messages;\n\t\tif (messages.length > 0 && messages[messages.length - 1].role === \"assistant\") {\n\t\t\tthis.agent.state.messages = messages.slice(0, -1);\n\t\t}\n\t\tawait this._runAutoCompaction(\"overflow\", willRetry);\n\t\treturn;\n\t}\n\n\t// Case 2: Threshold - context is getting large\n\t// For error messages (no usage data), estimate from last successful response.\n\t// This ensures sessions that hit persistent API errors (e.g. 529) can still compact.\n\tlet contextTokens: number;\n\tif (assistantMessage.stopReason === \"error\") {\n\t\tconst messages = this.agent.state.messages;\n\t\tconst estimate = estimateContextTokens(messages);\n\t\tif (estimate.lastUsageIndex === null) return; // No usage data at all\n\t\t// Verify the usage source is post-compaction. Kept pre-compaction messages\n\t\t// have stale usage reflecting the old (larger) context and would falsely\n\t\t// trigger compaction right after one just finished.\n\t\tconst usageMsg = messages[estimate.lastUsageIndex];\n\t\tif (\n\t\t\tcompactionBoundaryEntry &&\n\t\t\tusageMsg.role === \"assistant\" &&\n\t\t\t(usageMsg as AssistantMessage).timestamp <= new Date(compactionBoundaryEntry.timestamp).getTime()\n\t\t) {\n\t\t\treturn;\n\t\t}\n\t\tcontextTokens = estimate.tokens;\n\t} else {\n\t\tcontextTokens = calculateContextTokens(assistantMessage.usage);\n\t}\n\t// Compact against the effective input budget (the hard prompt cap for providers like Copilot\n\t// that advertise a larger total window) so we compact before overrunning the server-side limit\n\t// rather than relying on reactive overflow recovery near the cap.\n\tconst compactionBudget = this.model ? getEffectiveInputBudget(this.model) : contextWindow;\n\tif (shouldCompact(contextTokens, compactionBudget, settings)) {\n\t\tawait this._runAutoCompaction(\"threshold\", false);\n\t}\n}\n\n\nexport function _isCopilotServerCapBelowSelectedContextWindow(this: AgentSession, assistantMessage: AssistantMessage): boolean {\n\tif (!this.model || this.model.provider !== \"github-copilot\" || !assistantMessage.errorMessage) return false;\n\tconst promptLimitError = parseCopilotPromptLimitError(assistantMessage.errorMessage);\n\t// Compare against the effective input budget (the model's real prompt cap), not the displayed\n\t// total window. A rejection at the prompt cap is a normal overflow we should compact-and-retry;\n\t// only a rejection *below* the cap (e.g. a missing long-context entitlement dropping the account\n\t// to a lower server tier) keeps the friendly error visible instead of silently compacting down.\n\treturn promptLimitError !== undefined && getEffectiveInputBudget(this.model) > promptLimitError.limitTokens;\n}\n\n/**\n * Internal: remove the trailing overflow error from retry context if it is still present.\n */\n\nexport function _dropTrailingOverflowAssistantErrorIfPresent(this: AgentSession): void {\n\tconst messages = this.agent.state.messages;\n\tconst lastMsg = messages[messages.length - 1];\n\tif (lastMsg?.role === \"assistant\" && (lastMsg as AssistantMessage).stopReason === \"error\") {\n\t\tthis.agent.state.messages = messages.slice(0, -1);\n\t}\n}\n\n/**\n * Internal: schedule a live post-event continuation probe after compaction_end listeners can flush queues.\n */\n\nexport function _schedulePostAutoCompactionContinuationProbe(this: AgentSession, \n\treason: \"overflow\" | \"threshold\",\n\twillRetry: boolean,\n): void {\n\tsetTimeout(() => {\n\t\tif (this.isCompacting || this.isStreaming) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (reason === \"overflow\" && willRetry) {\n\t\t\tthis._resumeAfterAutoCompaction();\n\t\t\treturn;\n\t\t}\n\n\t\tif (!this.agent.hasQueuedMessages()) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis._resumeAfterAutoCompaction();\n\t}, 100);\n}\n\n/**\n * Internal: resume generation after successful auto-compaction only when active work remains.\n */\n\nexport function _resumeAfterAutoCompaction(this: AgentSession): void {\n\tvoid this._runAgentContinue().catch((error) => {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tthis._emit({\n\t\t\ttype: \"agent_continue_error\",\n\t\t\tsource: \"post_compaction\",\n\t\t\terrorMessage: `Post-compaction continuation failed: ${message}`,\n\t\t});\n\t});\n}\n\n/**\n * Internal: Run auto-compaction with events.\n */\n\nexport async function _runAutoCompaction(this: AgentSession, reason: \"overflow\" | \"threshold\", willRetry: boolean): Promise<void> {\n\tthis._emit({ type: \"compaction_start\", reason });\n\tthis._autoCompactionAbortController = new AbortController();\n\n\ttry {\n\t\tif (!this.model) {\n\t\t\tthis._emit({\n\t\t\t\ttype: \"compaction_end\",\n\t\t\t\treason,\n\t\t\t\tresult: undefined,\n\t\t\t\taborted: false,\n\t\t\t\twillRetry: false,\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\n\t\t// Auth is resolved lazily: only called when the planner fallback is needed.\n\t\t// This allows extension-provided deletion requests to run before auth is checked,\n\t\t// enabling local extension compaction even when API credentials are unavailable.\n\t\t// Auto-mode resolver returns undefined (rather than throwing) when auth is missing;\n\t\t// overflow recovery then falls back to deterministic no-auth eviction, while\n\t\t// threshold compaction keeps the previous no-op behavior.\n\t\tconst model = this.model;\n\t\tconst result = await this._applyContextVerbatimCompaction({\n\t\t\tresolvePlannerAuth: async () => {\n\t\t\t\tconst authResult = await this._modelRegistry.getApiKeyAndHeaders(model);\n\t\t\t\tif (!authResult.ok || !authResult.apiKey) {\n\t\t\t\t\treturn undefined;\n\t\t\t\t}\n\t\t\t\treturn { apiKey: authResult.apiKey, headers: authResult.headers };\n\t\t\t},\n\t\t\tabortController: this._autoCompactionAbortController,\n\t\t\tbackupLabel: reason === \"overflow\" ? \"overflow-auto-compact\" : \"auto-compact\",\n\t\t\treason,\n\t\t});\n\t\tif (!result) {\n\t\t\tthis._emit({\n\t\t\t\ttype: \"compaction_end\",\n\t\t\t\treason,\n\t\t\t\tresult: undefined,\n\t\t\t\taborted: false,\n\t\t\t\twillRetry: false,\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\n\t\tif (reason === \"overflow\" && willRetry) {\n\t\t\tthis._dropTrailingOverflowAssistantErrorIfPresent();\n\t\t}\n\n\t\tthis._emit({ type: \"compaction_end\", reason, result, aborted: false, willRetry });\n\t\tthis._schedulePostAutoCompactionContinuationProbe(reason, willRetry);\n\t} catch (error) {\n\t\tconst errorMessage = error instanceof Error ? error.message : \"compaction failed\";\n\t\tconst aborted = errorMessage === \"Compaction cancelled\" || (error instanceof Error && error.name === \"AbortError\");\n\t\tthis._emit({\n\t\t\ttype: \"compaction_end\",\n\t\t\treason,\n\t\t\tresult: undefined,\n\t\t\taborted,\n\t\t\twillRetry: false,\n\t\t\terrorMessage: aborted\n\t\t\t\t? undefined\n\t\t\t\t: reason === \"overflow\"\n\t\t\t\t\t? `Context overflow recovery failed: ${errorMessage}`\n\t\t\t\t\t: `Auto-compaction failed: ${errorMessage}`,\n\t\t});\n\t} finally {\n\t\tthis._autoCompactionAbortController = undefined;\n\t}\n}\n\n/**\n * Toggle auto-compaction setting.\n */\n\nexport const agentSessionAutoCompactionMethods = {\n\t_checkCompaction,\n\t_isCopilotServerCapBelowSelectedContextWindow,\n\t_dropTrailingOverflowAssistantErrorIfPresent,\n\t_schedulePostAutoCompactionContinuationProbe,\n\t_resumeAfterAutoCompaction,\n\t_runAutoCompaction,\n};\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"agent-session-compaction.d.ts","sourceRoot":"","sources":["../../src/core/agent-session-compaction.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,2BAA2B,IAAI,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC9F,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,4BAA4B,CAAC;AAChF,OAAO,EAAE,KAAK,2BAA2B,EAAqC,KAAK,uBAAuB,EAAmK,MAAM,uBAAuB,CAAC;AAc3S,wBAAsB,+BAA+B,CACpD,IAAI,EAAE,YAAY,EAClB,OAAO,EAAE,6BAA6B,GACpC,OAAO,CAAC,uBAAuB,GAAG,SAAS,CAAC,CAsJ9C;AAED;;;GAGG;AAEH,wBAAsB,OAAO,CAAC,IAAI,EAAE,YAAY,EAAE,OAAO,GAAE,OAAO,CAAC,2BAA2B,CAAM,GAAG,OAAO,CAAC,uBAAuB,CAAC,CAmDtI;AAED;;;GAGG;AAEH,wBAAsB,cAAc,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,uBAAuB,CAAC,CAgDzF;AAED;;GAEG;AAEH,wBAAgB,eAAe,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI,CAGxD;AAED;;GAEG;AAEH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI,CAE3D;AAED;;;;;;;;;;GAUG;AAEH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI,CAEnF;AAED,yCAAyC;AAEzC,eAAO,MAAM,6BAA6B;;;;;;;CAOzC,CAAC","sourcesContent":["import { formatNoModelSelectedMessage } from \"./auth-guidance.ts\";\nimport type { AgentSessionInternalSurface as AgentSession } from \"./agent-session-methods.ts\";\nimport type { ContextCompactionApplyOptions } from \"./agent-session-methods.ts\";\nimport { type ContextCompactionParameters, type ContextCompactionPreparation, type ContextCompactionResult, type ContextDeletionRequest, type ValidatedContextDeletionResult, contextCompact as runContextCompact, prepareContextCompaction, validateContextDeletionRequest } from \"./compaction/index.ts\";\nimport type { SessionBeforeCompactEvent, SessionBeforeCompactResult, SessionCompactEvent } from \"./extensions/index.ts\";\nimport type { ContextCompactionEntry } from \"./session-manager.ts\";\n\nfunction deepFreeze<T>(value: T): T {\n\tif (value && typeof value === \"object\") {\n\t\tObject.freeze(value);\n\t\tfor (const nested of Object.values(value)) {\n\t\t\tdeepFreeze(nested);\n\t\t}\n\t}\n\treturn value;\n}\n\nexport async function _applyContextVerbatimCompaction(\n\tthis: AgentSession,\n\toptions: ContextCompactionApplyOptions,\n): Promise<ContextCompactionResult | undefined> {\n\tif (!this.model) {\n\t\tthrow new Error(formatNoModelSelectedMessage());\n\t}\n\t// Capture the narrowed model now (control-flow narrowing holds immediately after the\n\t// guard) so the lazy planner-fallback closure below can use a non-undefined model.\n\tconst model = this.model;\n\tconst compactionThinkingLevel = this.thinkingLevel;\n\n\tconst pathEntries = this.sessionManager.getBranch();\n\tconst settings = this.settingsManager.getCompactionSettings();\n\tconst preparation = prepareContextCompaction(pathEntries, settings, {\n\t\t...(options.compression_ratio === undefined ? {} : { compression_ratio: options.compression_ratio }),\n\t\t...(options.preserve_recent === undefined ? {} : { preserve_recent: options.preserve_recent }),\n\t\t...(options.query === undefined ? {} : { query: options.query }),\n\t});\n\tif (!preparation) {\n\t\treturn undefined;\n\t}\n\tconst parameters: ContextCompactionParameters = preparation.parameters;\n\n\t// Planner fallback used when no extension supplies a deletionRequest. Auth is resolved\n\t// lazily here so extension-provided deletion requests keep working offline. Returns\n\t// undefined when auth is unavailable (auto-mode resolvers), signaling a no-op compaction.\n\tconst runPlanner = async (): Promise<ValidatedContextDeletionResult | undefined> => {\n\t\tconst auth = await options.resolvePlannerAuth();\n\t\tif (!auth) return undefined;\n\t\treturn runContextCompact(\n\t\t\tpreparation,\n\t\t\tmodel,\n\t\t\tauth.apiKey,\n\t\t\tauth.headers,\n\t\t\toptions.abortController.signal,\n\t\t\tcompactionThinkingLevel,\n\t\t);\n\t};\n\n\t// Emit session_before_compact to allow extensions to cancel or provide a deletion request.\n\t// This happens BEFORE any auth resolution so local extension deletion requests work\n\t// without configured API credentials.\n\tlet fromExtension = false;\n\tlet validated: ValidatedContextDeletionResult | undefined;\n\n\tif (this._extensionRunner.hasHandlers(\"session_before_compact\")) {\n\t\t// Deep-clone the preparation only when a before-compact handler actually exists. Extensions\n\t\t// receive an isolated, frozen snapshot so they cannot mutate protection metadata\n\t\t// (protectedEntryIds, entry .protected flags, etc.) on the internal preparation used for\n\t\t// validation. Building it lazily avoids deep-cloning the transcript — largest exactly when\n\t\t// compaction fires — on the common no-extension path.\n\t\tlet extensionPreparation: ContextCompactionPreparation;\n\t\ttry {\n\t\t\textensionPreparation = deepFreeze(structuredClone(preparation));\n\t\t} catch (error) {\n\t\t\t// structuredClone only throws if an entry carries a non-cloneable value (a function or a\n\t\t\t// class instance). Transcript entries are plain data today, so this guards a latent\n\t\t\t// invariant: surface a clear error instead of letting a raw DataCloneError abort an\n\t\t\t// otherwise-viable compaction.\n\t\t\tthrow new Error(\n\t\t\t\t`Failed to snapshot transcript for compaction extensions: ${error instanceof Error ? error.message : String(error)}`,\n\t\t\t);\n\t\t}\n\n\t\tconst hookResult = (await this._extensionRunner.emit({\n\t\t\ttype: \"session_before_compact\",\n\t\t\treason: options.reason,\n\t\t\tparameters,\n\t\t\tpreparation: extensionPreparation,\n\t\t\tbranchEntries: pathEntries,\n\t\t\tsignal: options.abortController.signal,\n\t\t} satisfies SessionBeforeCompactEvent)) as SessionBeforeCompactResult | undefined;\n\n\t\tif (hookResult?.cancel) {\n\t\t\tthrow new Error(\"Compaction cancelled\");\n\t\t}\n\n\t\tif (hookResult?.deletionRequest) {\n\t\t\tconst extensionDeletionRequest = hookResult.deletionRequest as ContextDeletionRequest;\n\t\t\t// Reject empty deletion requests before any side effects (backup, append, rebuild).\n\t\t\tif (!Array.isArray(extensionDeletionRequest.deletions) || extensionDeletionRequest.deletions.length === 0) {\n\t\t\t\tthrow new Error(\"No safe context deletions proposed by extension\");\n\t\t\t}\n\t\t\t// Validate against the internal transcript snapshot, not the extension-facing clone.\n\t\t\t// Auth is NOT resolved here — local extension deletion requests work offline.\n\t\t\tvalidated = validateContextDeletionRequest(extensionDeletionRequest, preparation.transcript);\n\t\t\t// Reject if reconciliation reduced deletions to zero.\n\t\t\tif (validated.deletedTargets.length === 0) {\n\t\t\t\tthrow new Error(\"No safe context deletions proposed by extension\");\n\t\t\t}\n\t\t\tfromExtension = true;\n\t\t}\n\t}\n\n\t// Planner fallback shared by both paths: no before-compact handler at all, or a handler that\n\t// observed without supplying a deletionRequest. Resolves auth lazily; undefined means auth is\n\t// unavailable (auto-mode resolvers), so compaction is a no-op.\n\tif (!validated) {\n\t\tconst plannerResult = await runPlanner();\n\t\tif (!plannerResult) {\n\t\t\treturn undefined;\n\t\t}\n\t\tvalidated = plannerResult;\n\t}\n\n\tif (options.abortController.signal.aborted) {\n\t\tthrow new Error(\"Compaction cancelled\");\n\t}\n\n\tconst backupPath = this.sessionManager.writeBackupSnapshot(options.backupLabel);\n\tconst compactionEntryId = this.sessionManager.appendContextCompaction(\n\t\tvalidated.deletedTargets,\n\t\tvalidated.protectedEntryIds,\n\t\tvalidated.stats,\n\t\tbackupPath,\n\t);\n\tconst sessionContext = this.sessionManager.buildSessionContext();\n\tthis.agent.state.messages = sessionContext.messages;\n\n\tconst result: ContextCompactionResult = {\n\t\t...validated,\n\t\tpromptVersion: 1,\n\t\tparameters,\n\t\t...(backupPath ? { backupPath } : {}),\n\t};\n\n\t// Emit session_compact so extensions can observe the validated result. This is a pure\n\t// observation hook fired AFTER the compaction has been committed (backup written,\n\t// context_compaction entry persisted, active context rebuilt). A misbehaving observer must\n\t// never turn a successful, already-persisted compaction into a reported failure, so any\n\t// throw is routed to the non-fatal extension-error channel and compaction still reports\n\t// success.\n\tconst contextCompactionEntry = this.sessionManager.getEntry(compactionEntryId) as ContextCompactionEntry;\n\ttry {\n\t\tawait this._extensionRunner.emit({\n\t\t\ttype: \"session_compact\",\n\t\t\treason: options.reason,\n\t\t\tparameters,\n\t\t\tresult,\n\t\t\tcontextCompactionEntry,\n\t\t\tfromExtension,\n\t\t} satisfies SessionCompactEvent);\n\t} catch (error) {\n\t\tthis._extensionRunner.emitError({\n\t\t\textensionPath: \"<session_compact>\",\n\t\t\tevent: \"session_compact\",\n\t\t\terror: error instanceof Error ? error.message : String(error),\n\t\t\tstack: error instanceof Error ? error.stack : undefined,\n\t\t});\n\t}\n\n\treturn result;\n}\n\n/**\n * Manually compact the session context using deletion-only verbatim context compaction.\n * Aborts current agent operation first.\n */\n\nexport async function compact(this: AgentSession, options: Partial<ContextCompactionParameters> = {}): Promise<ContextCompactionResult> {\n\tthis._disconnectFromAgent();\n\tawait this.abort();\n\tthis._compactionAbortController = new AbortController();\n\tthis._emit({ type: \"compaction_start\", reason: \"manual\" });\n\n\ttry {\n\t\tif (!this.model) {\n\t\t\tthrow new Error(formatNoModelSelectedMessage());\n\t\t}\n\n\t\t// Auth is resolved lazily: only called when the planner fallback is needed.\n\t\t// Extensions that provide a deletionRequest work without configured credentials.\n\t\tconst model = this.model;\n\t\tconst result = await this._applyContextVerbatimCompaction({\n\t\t\tresolvePlannerAuth: () => this._getRequiredRequestAuth(model),\n\t\t\tabortController: this._compactionAbortController,\n\t\t\tbackupLabel: \"compact\",\n\t\t\treason: \"manual\",\n\t\t\t...(options.compression_ratio === undefined ? {} : { compression_ratio: options.compression_ratio }),\n\t\t\t...(options.preserve_recent === undefined ? {} : { preserve_recent: options.preserve_recent }),\n\t\t\t...(options.query === undefined ? {} : { query: options.query }),\n\t\t});\n\t\tif (!result) {\n\t\t\tthrow new Error(\"Nothing to compact (session too small)\");\n\t\t}\n\n\t\tthis._emit({\n\t\t\ttype: \"compaction_end\",\n\t\t\treason: \"manual\",\n\t\t\tresult,\n\t\t\taborted: false,\n\t\t\twillRetry: false,\n\t\t});\n\t\treturn result;\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tconst aborted = message === \"Compaction cancelled\" || (error instanceof Error && error.name === \"AbortError\");\n\t\tthis._emit({\n\t\t\ttype: \"compaction_end\",\n\t\t\treason: \"manual\",\n\t\t\tresult: undefined,\n\t\t\taborted,\n\t\t\twillRetry: false,\n\t\t\terrorMessage: aborted ? undefined : `Compaction failed: ${message}`,\n\t\t});\n\t\tthrow error;\n\t} finally {\n\t\tthis._compactionAbortController = undefined;\n\t\tthis._reconnectToAgent();\n\t}\n}\n\n/**\n * Manually compact the session context by applying validated logical deletions.\n * Retained transcript entries/content blocks stay verbatim; no user prompt text is accepted.\n */\n\nexport async function contextCompact(this: AgentSession): Promise<ContextCompactionResult> {\n\tthis._disconnectFromAgent();\n\tawait this.abort();\n\tthis._compactionAbortController = new AbortController();\n\tthis._emit({ type: \"context_compaction_start\", reason: \"manual\" });\n\n\ttry {\n\t\tif (!this.model) {\n\t\t\tthrow new Error(formatNoModelSelectedMessage());\n\t\t}\n\n\t\t// Auth is resolved lazily: only called when the planner fallback is needed.\n\t\t// Extensions that provide a deletionRequest work without configured credentials.\n\t\tconst model = this.model;\n\t\tconst result = await this._applyContextVerbatimCompaction({\n\t\t\tresolvePlannerAuth: () => this._getRequiredRequestAuth(model),\n\t\t\tabortController: this._compactionAbortController,\n\t\t\tbackupLabel: \"context-compact\",\n\t\t\treason: \"manual\",\n\t\t});\n\t\tif (!result) {\n\t\t\tthrow new Error(\"Nothing to context-compact (session too small)\");\n\t\t}\n\n\t\tthis._emit({\n\t\t\ttype: \"context_compaction_end\",\n\t\t\treason: \"manual\",\n\t\t\tresult,\n\t\t\taborted: false,\n\t\t\twillRetry: false,\n\t\t});\n\t\treturn result;\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tconst aborted = message === \"Compaction cancelled\" || (error instanceof Error && error.name === \"AbortError\");\n\t\tthis._emit({\n\t\t\ttype: \"context_compaction_end\",\n\t\t\treason: \"manual\",\n\t\t\tresult: undefined,\n\t\t\taborted,\n\t\t\twillRetry: false,\n\t\t\terrorMessage: aborted ? undefined : `Context compaction failed: ${message}`,\n\t\t});\n\t\tthrow error;\n\t} finally {\n\t\tthis._compactionAbortController = undefined;\n\t\tthis._reconnectToAgent();\n\t}\n}\n\n/**\n * Cancel in-progress compaction (manual or auto).\n */\n\nexport function abortCompaction(this: AgentSession): void {\n\tthis._compactionAbortController?.abort();\n\tthis._autoCompactionAbortController?.abort();\n}\n\n/**\n * Cancel in-progress branch summarization.\n */\n\nexport function abortBranchSummary(this: AgentSession): void {\n\tthis._branchSummaryAbortController?.abort();\n}\n\n/**\n * Check if compaction is needed and run it.\n * Called after agent_end and before prompt submission.\n *\n * Two cases:\n * 1. Overflow: LLM returned context overflow error, remove error message from agent state, compact, auto-retry\n * 2. Threshold: Context over threshold, compact, resume queued active-turn work if present; otherwise wait for user\n *\n * @param assistantMessage The assistant message to check\n * @param skipAbortedCheck If false, include aborted messages (for pre-prompt check). Default: true\n */\n\nexport function setAutoCompactionEnabled(this: AgentSession, enabled: boolean): void {\n\tthis.settingsManager.setCompactionEnabled(enabled);\n}\n\n/** Whether auto-compaction is enabled */\n\nexport const agentSessionCompactionMethods = {\n\t_applyContextVerbatimCompaction,\n\tcompact,\n\tcontextCompact,\n\tabortCompaction,\n\tabortBranchSummary,\n\tsetAutoCompactionEnabled,\n};\n"]}
1
+ {"version":3,"file":"agent-session-compaction.d.ts","sourceRoot":"","sources":["../../src/core/agent-session-compaction.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,2BAA2B,IAAI,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC9F,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,4BAA4B,CAAC;AAEhF,OAAO,EAAE,KAAK,2BAA2B,EAAqC,KAAK,uBAAuB,EAAmK,MAAM,uBAAuB,CAAC;AAiB3S,wBAAsB,+BAA+B,CACpD,IAAI,EAAE,YAAY,EAClB,OAAO,EAAE,6BAA6B,GACpC,OAAO,CAAC,uBAAuB,GAAG,SAAS,CAAC,CA2K9C;AAED;;;GAGG;AAEH,wBAAsB,OAAO,CAAC,IAAI,EAAE,YAAY,EAAE,OAAO,GAAE,OAAO,CAAC,2BAA2B,CAAM,GAAG,OAAO,CAAC,uBAAuB,CAAC,CAmDtI;AAED;;;GAGG;AAEH,wBAAsB,cAAc,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,uBAAuB,CAAC,CAgDzF;AAED;;GAEG;AAEH,wBAAgB,eAAe,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI,CAGxD;AAED;;GAEG;AAEH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI,CAE3D;AAED;;;;;;;;;;GAUG;AAEH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI,CAEnF;AAED,yCAAyC;AAEzC,eAAO,MAAM,6BAA6B;;;;;;;CAOzC,CAAC","sourcesContent":["import { formatNoModelSelectedMessage } from \"./auth-guidance.ts\";\nimport type { AgentSessionInternalSurface as AgentSession } from \"./agent-session-methods.ts\";\nimport type { ContextCompactionApplyOptions } from \"./agent-session-methods.ts\";\nimport { getEffectiveInputBudget } from \"./context-window.ts\";\nimport { type ContextCompactionParameters, type ContextCompactionPreparation, type ContextCompactionResult, type ContextDeletionRequest, type ValidatedContextDeletionResult, contextCompact as runContextCompact, prepareContextCompaction, validateContextDeletionRequest } from \"./compaction/index.ts\";\nimport { runDeterministicContextEviction } from \"./compaction/context-compaction-eviction.ts\";\n// Type-only import: erased at runtime so module mocks of the compaction barrel stay authoritative.\nimport type { ContextCompactionLadderOptions } from \"./compaction/context-compaction-runner.ts\";\nimport type { SessionBeforeCompactEvent, SessionBeforeCompactResult, SessionCompactEvent } from \"./extensions/index.ts\";\nimport type { ContextCompactionEntry } from \"./session-manager.ts\";\n\nfunction deepFreeze<T>(value: T): T {\n\tif (value && typeof value === \"object\") {\n\t\tObject.freeze(value);\n\t\tfor (const nested of Object.values(value)) {\n\t\t\tdeepFreeze(nested);\n\t\t}\n\t}\n\treturn value;\n}\n\nexport async function _applyContextVerbatimCompaction(\n\tthis: AgentSession,\n\toptions: ContextCompactionApplyOptions,\n): Promise<ContextCompactionResult | undefined> {\n\tif (!this.model) {\n\t\tthrow new Error(formatNoModelSelectedMessage());\n\t}\n\t// Capture the narrowed model now (control-flow narrowing holds immediately after the\n\t// guard) so the lazy planner-fallback closure below can use a non-undefined model.\n\tconst model = this.model;\n\tconst compactionThinkingLevel = this.thinkingLevel;\n\n\tconst pathEntries = this.sessionManager.getBranch();\n\tconst settings = this.settingsManager.getCompactionSettings();\n\tconst preparation = prepareContextCompaction(pathEntries, settings, {\n\t\t...(options.compression_ratio === undefined ? {} : { compression_ratio: options.compression_ratio }),\n\t\t...(options.preserve_recent === undefined ? {} : { preserve_recent: options.preserve_recent }),\n\t\t...(options.query === undefined ? {} : { query: options.query }),\n\t});\n\tif (!preparation) {\n\t\t// Overflow recovery must be loud when there is no transcript the ladder can compact:\n\t\t// a silent no-op would leave the session overflowing without proving safe deletion is exhausted.\n\t\tif (options.reason === \"overflow\") {\n\t\t\tthrow new Error(\"Context compaction found no compactable transcript entries; nothing more was safely deletable\");\n\t\t}\n\t\treturn undefined;\n\t}\n\tconst parameters: ContextCompactionParameters = preparation.parameters;\n\tconst effectiveBudget = getEffectiveInputBudget(model);\n\tconst ladderOptions: ContextCompactionLadderOptions | undefined =\n\t\toptions.reason === \"overflow\"\n\t\t\t? { acceptanceTokenBudget: effectiveBudget, criticalEvictionTokenBudget: effectiveBudget }\n\t\t\t: options.reason === \"threshold\"\n\t\t\t\t// Match the shouldCompact trigger boundary in transcript-estimate space:\n\t\t\t\t// tokensAfter <= effectiveBudget - reserveTokens is equivalent to\n\t\t\t\t// !shouldCompact(tokensAfter, effectiveBudget, settings). The live post-retry\n\t\t\t\t// trigger uses provider usage (system/tools included), so this is a best-effort\n\t\t\t\t// boundary; a negative boundary correctly means no estimated result fits.\n\t\t\t\t? { acceptanceTokenBudget: effectiveBudget - settings.reserveTokens }\n\t\t\t\t: undefined;\n\n\t// Planner fallback used when no extension supplies a deletionRequest. Auth is resolved\n\t// lazily here so extension-provided deletion requests keep working offline. Overflow with\n\t// missing auth uses deterministic eviction directly instead of silently no-oping.\n\tconst runPlanner = async (): Promise<ValidatedContextDeletionResult | undefined> => {\n\t\tconst auth = await options.resolvePlannerAuth();\n\t\tif (!auth) {\n\t\t\tif (options.reason === \"overflow\") return runDeterministicContextEviction(preparation.transcript, effectiveBudget);\n\t\t\treturn undefined;\n\t\t}\n\t\treturn runContextCompact(\n\t\t\tpreparation,\n\t\t\tmodel,\n\t\t\tauth.apiKey,\n\t\t\tauth.headers,\n\t\t\toptions.abortController.signal,\n\t\t\tcompactionThinkingLevel,\n\t\t\tladderOptions,\n\t\t);\n\t};\n\n\t// Emit session_before_compact to allow extensions to cancel or provide a deletion request.\n\t// This happens BEFORE any auth resolution so local extension deletion requests work\n\t// without configured API credentials.\n\tlet fromExtension = false;\n\tlet validated: ValidatedContextDeletionResult | undefined;\n\n\tif (this._extensionRunner.hasHandlers(\"session_before_compact\")) {\n\t\t// Deep-clone the preparation only when a before-compact handler actually exists. Extensions\n\t\t// receive an isolated, frozen snapshot so they cannot mutate protection metadata\n\t\t// (protectedEntryIds, entry .protected flags, etc.) on the internal preparation used for\n\t\t// validation. Building it lazily avoids deep-cloning the transcript — largest exactly when\n\t\t// compaction fires — on the common no-extension path.\n\t\tlet extensionPreparation: ContextCompactionPreparation;\n\t\ttry {\n\t\t\textensionPreparation = deepFreeze(structuredClone(preparation));\n\t\t} catch (error) {\n\t\t\t// structuredClone only throws if an entry carries a non-cloneable value (a function or a\n\t\t\t// class instance). Transcript entries are plain data today, so this guards a latent\n\t\t\t// invariant: surface a clear error instead of letting a raw DataCloneError abort an\n\t\t\t// otherwise-viable compaction.\n\t\t\tthrow new Error(\n\t\t\t\t`Failed to snapshot transcript for compaction extensions: ${error instanceof Error ? error.message : String(error)}`,\n\t\t\t);\n\t\t}\n\n\t\tconst hookResult = (await this._extensionRunner.emit({\n\t\t\ttype: \"session_before_compact\",\n\t\t\treason: options.reason,\n\t\t\tparameters,\n\t\t\tpreparation: extensionPreparation,\n\t\t\tbranchEntries: pathEntries,\n\t\t\tsignal: options.abortController.signal,\n\t\t} satisfies SessionBeforeCompactEvent)) as SessionBeforeCompactResult | undefined;\n\n\t\tif (hookResult?.cancel) {\n\t\t\tthrow new Error(\"Compaction cancelled\");\n\t\t}\n\n\t\tif (hookResult?.deletionRequest) {\n\t\t\tconst extensionDeletionRequest = hookResult.deletionRequest as ContextDeletionRequest;\n\t\t\t// Reject empty deletion requests before any side effects (backup, append, rebuild).\n\t\t\tif (!Array.isArray(extensionDeletionRequest.deletions) || extensionDeletionRequest.deletions.length === 0) {\n\t\t\t\tthrow new Error(\"No safe context deletions proposed by extension\");\n\t\t\t}\n\t\t\t// Validate against the internal transcript snapshot, not the extension-facing clone.\n\t\t\t// Auth is NOT resolved here — local extension deletion requests work offline.\n\t\t\tvalidated = validateContextDeletionRequest(extensionDeletionRequest, preparation.transcript);\n\t\t\t// Reject if reconciliation reduced deletions to zero.\n\t\t\tif (validated.deletedTargets.length === 0) {\n\t\t\t\tthrow new Error(\"No safe context deletions proposed by extension\");\n\t\t\t}\n\t\t\tfromExtension = true;\n\t\t}\n\t}\n\n\t// Planner fallback shared by both paths: no before-compact handler at all, or a handler that\n\t// observed without supplying a deletionRequest. Resolves auth lazily; undefined means auth is\n\t// unavailable (auto-mode resolvers), so compaction is a no-op.\n\tif (!validated) {\n\t\tconst plannerResult = await runPlanner();\n\t\tif (!plannerResult) {\n\t\t\treturn undefined;\n\t\t}\n\t\tvalidated = plannerResult;\n\t}\n\n\tif (options.abortController.signal.aborted) {\n\t\tthrow new Error(\"Compaction cancelled\");\n\t}\n\n\tconst backupPath = this.sessionManager.writeBackupSnapshot(options.backupLabel);\n\tconst compactionEntryId = this.sessionManager.appendContextCompaction(\n\t\tvalidated.deletedTargets,\n\t\tvalidated.protectedEntryIds,\n\t\tvalidated.stats,\n\t\tbackupPath,\n\t);\n\tconst sessionContext = this.sessionManager.buildSessionContext();\n\tthis.agent.state.messages = sessionContext.messages;\n\n\tconst result: ContextCompactionResult = {\n\t\t...validated,\n\t\tpromptVersion: 1,\n\t\tparameters,\n\t\t...(backupPath ? { backupPath } : {}),\n\t};\n\n\t// Emit session_compact so extensions can observe the validated result. This is a pure\n\t// observation hook fired AFTER the compaction has been committed (backup written,\n\t// context_compaction entry persisted, active context rebuilt). A misbehaving observer must\n\t// never turn a successful, already-persisted compaction into a reported failure, so any\n\t// throw is routed to the non-fatal extension-error channel and compaction still reports\n\t// success.\n\tconst contextCompactionEntry = this.sessionManager.getEntry(compactionEntryId) as ContextCompactionEntry;\n\ttry {\n\t\tawait this._extensionRunner.emit({\n\t\t\ttype: \"session_compact\",\n\t\t\treason: options.reason,\n\t\t\tparameters,\n\t\t\tresult,\n\t\t\tcontextCompactionEntry,\n\t\t\tfromExtension,\n\t\t} satisfies SessionCompactEvent);\n\t} catch (error) {\n\t\tthis._extensionRunner.emitError({\n\t\t\textensionPath: \"<session_compact>\",\n\t\t\tevent: \"session_compact\",\n\t\t\terror: error instanceof Error ? error.message : String(error),\n\t\t\tstack: error instanceof Error ? error.stack : undefined,\n\t\t});\n\t}\n\n\treturn result;\n}\n\n/**\n * Manually compact the session context using deletion-only verbatim context compaction.\n * Aborts current agent operation first.\n */\n\nexport async function compact(this: AgentSession, options: Partial<ContextCompactionParameters> = {}): Promise<ContextCompactionResult> {\n\tthis._disconnectFromAgent();\n\tawait this.abort();\n\tthis._compactionAbortController = new AbortController();\n\tthis._emit({ type: \"compaction_start\", reason: \"manual\" });\n\n\ttry {\n\t\tif (!this.model) {\n\t\t\tthrow new Error(formatNoModelSelectedMessage());\n\t\t}\n\n\t\t// Auth is resolved lazily: only called when the planner fallback is needed.\n\t\t// Extensions that provide a deletionRequest work without configured credentials.\n\t\tconst model = this.model;\n\t\tconst result = await this._applyContextVerbatimCompaction({\n\t\t\tresolvePlannerAuth: () => this._getRequiredRequestAuth(model),\n\t\t\tabortController: this._compactionAbortController,\n\t\t\tbackupLabel: \"compact\",\n\t\t\treason: \"manual\",\n\t\t\t...(options.compression_ratio === undefined ? {} : { compression_ratio: options.compression_ratio }),\n\t\t\t...(options.preserve_recent === undefined ? {} : { preserve_recent: options.preserve_recent }),\n\t\t\t...(options.query === undefined ? {} : { query: options.query }),\n\t\t});\n\t\tif (!result) {\n\t\t\tthrow new Error(\"Nothing to compact (session too small)\");\n\t\t}\n\n\t\tthis._emit({\n\t\t\ttype: \"compaction_end\",\n\t\t\treason: \"manual\",\n\t\t\tresult,\n\t\t\taborted: false,\n\t\t\twillRetry: false,\n\t\t});\n\t\treturn result;\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tconst aborted = message === \"Compaction cancelled\" || (error instanceof Error && error.name === \"AbortError\");\n\t\tthis._emit({\n\t\t\ttype: \"compaction_end\",\n\t\t\treason: \"manual\",\n\t\t\tresult: undefined,\n\t\t\taborted,\n\t\t\twillRetry: false,\n\t\t\terrorMessage: aborted ? undefined : `Compaction failed: ${message}`,\n\t\t});\n\t\tthrow error;\n\t} finally {\n\t\tthis._compactionAbortController = undefined;\n\t\tthis._reconnectToAgent();\n\t}\n}\n\n/**\n * Manually compact the session context by applying validated logical deletions.\n * Retained transcript entries/content blocks stay verbatim; no user prompt text is accepted.\n */\n\nexport async function contextCompact(this: AgentSession): Promise<ContextCompactionResult> {\n\tthis._disconnectFromAgent();\n\tawait this.abort();\n\tthis._compactionAbortController = new AbortController();\n\tthis._emit({ type: \"context_compaction_start\", reason: \"manual\" });\n\n\ttry {\n\t\tif (!this.model) {\n\t\t\tthrow new Error(formatNoModelSelectedMessage());\n\t\t}\n\n\t\t// Auth is resolved lazily: only called when the planner fallback is needed.\n\t\t// Extensions that provide a deletionRequest work without configured credentials.\n\t\tconst model = this.model;\n\t\tconst result = await this._applyContextVerbatimCompaction({\n\t\t\tresolvePlannerAuth: () => this._getRequiredRequestAuth(model),\n\t\t\tabortController: this._compactionAbortController,\n\t\t\tbackupLabel: \"context-compact\",\n\t\t\treason: \"manual\",\n\t\t});\n\t\tif (!result) {\n\t\t\tthrow new Error(\"Nothing to context-compact (session too small)\");\n\t\t}\n\n\t\tthis._emit({\n\t\t\ttype: \"context_compaction_end\",\n\t\t\treason: \"manual\",\n\t\t\tresult,\n\t\t\taborted: false,\n\t\t\twillRetry: false,\n\t\t});\n\t\treturn result;\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tconst aborted = message === \"Compaction cancelled\" || (error instanceof Error && error.name === \"AbortError\");\n\t\tthis._emit({\n\t\t\ttype: \"context_compaction_end\",\n\t\t\treason: \"manual\",\n\t\t\tresult: undefined,\n\t\t\taborted,\n\t\t\twillRetry: false,\n\t\t\terrorMessage: aborted ? undefined : `Context compaction failed: ${message}`,\n\t\t});\n\t\tthrow error;\n\t} finally {\n\t\tthis._compactionAbortController = undefined;\n\t\tthis._reconnectToAgent();\n\t}\n}\n\n/**\n * Cancel in-progress compaction (manual or auto).\n */\n\nexport function abortCompaction(this: AgentSession): void {\n\tthis._compactionAbortController?.abort();\n\tthis._autoCompactionAbortController?.abort();\n}\n\n/**\n * Cancel in-progress branch summarization.\n */\n\nexport function abortBranchSummary(this: AgentSession): void {\n\tthis._branchSummaryAbortController?.abort();\n}\n\n/**\n * Check if compaction is needed and run it.\n * Called after agent_end and before prompt submission.\n *\n * Two cases:\n * 1. Overflow: LLM returned context overflow error, remove error message from agent state, compact, auto-retry\n * 2. Threshold: Context over threshold, compact, resume queued active-turn work if present; otherwise wait for user\n *\n * @param assistantMessage The assistant message to check\n * @param skipAbortedCheck If false, include aborted messages (for pre-prompt check). Default: true\n */\n\nexport function setAutoCompactionEnabled(this: AgentSession, enabled: boolean): void {\n\tthis.settingsManager.setCompactionEnabled(enabled);\n}\n\n/** Whether auto-compaction is enabled */\n\nexport const agentSessionCompactionMethods = {\n\t_applyContextVerbatimCompaction,\n\tcompact,\n\tcontextCompact,\n\tabortCompaction,\n\tabortBranchSummary,\n\tsetAutoCompactionEnabled,\n};\n"]}
@@ -1,5 +1,7 @@
1
1
  import { formatNoModelSelectedMessage } from "./auth-guidance.js";
2
+ import { getEffectiveInputBudget } from "./context-window.js";
2
3
  import { contextCompact as runContextCompact, prepareContextCompaction, validateContextDeletionRequest } from "./compaction/index.js";
4
+ import { runDeterministicContextEviction } from "./compaction/context-compaction-eviction.js";
3
5
  function deepFreeze(value) {
4
6
  if (value && typeof value === "object") {
5
7
  Object.freeze(value);
@@ -25,17 +27,36 @@ export async function _applyContextVerbatimCompaction(options) {
25
27
  ...(options.query === undefined ? {} : { query: options.query }),
26
28
  });
27
29
  if (!preparation) {
30
+ // Overflow recovery must be loud when there is no transcript the ladder can compact:
31
+ // a silent no-op would leave the session overflowing without proving safe deletion is exhausted.
32
+ if (options.reason === "overflow") {
33
+ throw new Error("Context compaction found no compactable transcript entries; nothing more was safely deletable");
34
+ }
28
35
  return undefined;
29
36
  }
30
37
  const parameters = preparation.parameters;
38
+ const effectiveBudget = getEffectiveInputBudget(model);
39
+ const ladderOptions = options.reason === "overflow"
40
+ ? { acceptanceTokenBudget: effectiveBudget, criticalEvictionTokenBudget: effectiveBudget }
41
+ : options.reason === "threshold"
42
+ // Match the shouldCompact trigger boundary in transcript-estimate space:
43
+ // tokensAfter <= effectiveBudget - reserveTokens is equivalent to
44
+ // !shouldCompact(tokensAfter, effectiveBudget, settings). The live post-retry
45
+ // trigger uses provider usage (system/tools included), so this is a best-effort
46
+ // boundary; a negative boundary correctly means no estimated result fits.
47
+ ? { acceptanceTokenBudget: effectiveBudget - settings.reserveTokens }
48
+ : undefined;
31
49
  // Planner fallback used when no extension supplies a deletionRequest. Auth is resolved
32
- // lazily here so extension-provided deletion requests keep working offline. Returns
33
- // undefined when auth is unavailable (auto-mode resolvers), signaling a no-op compaction.
50
+ // lazily here so extension-provided deletion requests keep working offline. Overflow with
51
+ // missing auth uses deterministic eviction directly instead of silently no-oping.
34
52
  const runPlanner = async () => {
35
53
  const auth = await options.resolvePlannerAuth();
36
- if (!auth)
54
+ if (!auth) {
55
+ if (options.reason === "overflow")
56
+ return runDeterministicContextEviction(preparation.transcript, effectiveBudget);
37
57
  return undefined;
38
- return runContextCompact(preparation, model, auth.apiKey, auth.headers, options.abortController.signal, compactionThinkingLevel);
58
+ }
59
+ return runContextCompact(preparation, model, auth.apiKey, auth.headers, options.abortController.signal, compactionThinkingLevel, ladderOptions);
39
60
  };
40
61
  // Emit session_before_compact to allow extensions to cancel or provide a deletion request.
41
62
  // This happens BEFORE any auth resolution so local extension deletion requests work
@@ -1 +1 @@
1
- {"version":3,"file":"agent-session-compaction.js","sourceRoot":"","sources":["../../src/core/agent-session-compaction.ts"],"names":[],"mappings":"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","sourcesContent":["import { formatNoModelSelectedMessage } from \"./auth-guidance.ts\";\nimport type { AgentSessionInternalSurface as AgentSession } from \"./agent-session-methods.ts\";\nimport type { ContextCompactionApplyOptions } from \"./agent-session-methods.ts\";\nimport { type ContextCompactionParameters, type ContextCompactionPreparation, type ContextCompactionResult, type ContextDeletionRequest, type ValidatedContextDeletionResult, contextCompact as runContextCompact, prepareContextCompaction, validateContextDeletionRequest } from \"./compaction/index.ts\";\nimport type { SessionBeforeCompactEvent, SessionBeforeCompactResult, SessionCompactEvent } from \"./extensions/index.ts\";\nimport type { ContextCompactionEntry } from \"./session-manager.ts\";\n\nfunction deepFreeze<T>(value: T): T {\n\tif (value && typeof value === \"object\") {\n\t\tObject.freeze(value);\n\t\tfor (const nested of Object.values(value)) {\n\t\t\tdeepFreeze(nested);\n\t\t}\n\t}\n\treturn value;\n}\n\nexport async function _applyContextVerbatimCompaction(\n\tthis: AgentSession,\n\toptions: ContextCompactionApplyOptions,\n): Promise<ContextCompactionResult | undefined> {\n\tif (!this.model) {\n\t\tthrow new Error(formatNoModelSelectedMessage());\n\t}\n\t// Capture the narrowed model now (control-flow narrowing holds immediately after the\n\t// guard) so the lazy planner-fallback closure below can use a non-undefined model.\n\tconst model = this.model;\n\tconst compactionThinkingLevel = this.thinkingLevel;\n\n\tconst pathEntries = this.sessionManager.getBranch();\n\tconst settings = this.settingsManager.getCompactionSettings();\n\tconst preparation = prepareContextCompaction(pathEntries, settings, {\n\t\t...(options.compression_ratio === undefined ? {} : { compression_ratio: options.compression_ratio }),\n\t\t...(options.preserve_recent === undefined ? {} : { preserve_recent: options.preserve_recent }),\n\t\t...(options.query === undefined ? {} : { query: options.query }),\n\t});\n\tif (!preparation) {\n\t\treturn undefined;\n\t}\n\tconst parameters: ContextCompactionParameters = preparation.parameters;\n\n\t// Planner fallback used when no extension supplies a deletionRequest. Auth is resolved\n\t// lazily here so extension-provided deletion requests keep working offline. Returns\n\t// undefined when auth is unavailable (auto-mode resolvers), signaling a no-op compaction.\n\tconst runPlanner = async (): Promise<ValidatedContextDeletionResult | undefined> => {\n\t\tconst auth = await options.resolvePlannerAuth();\n\t\tif (!auth) return undefined;\n\t\treturn runContextCompact(\n\t\t\tpreparation,\n\t\t\tmodel,\n\t\t\tauth.apiKey,\n\t\t\tauth.headers,\n\t\t\toptions.abortController.signal,\n\t\t\tcompactionThinkingLevel,\n\t\t);\n\t};\n\n\t// Emit session_before_compact to allow extensions to cancel or provide a deletion request.\n\t// This happens BEFORE any auth resolution so local extension deletion requests work\n\t// without configured API credentials.\n\tlet fromExtension = false;\n\tlet validated: ValidatedContextDeletionResult | undefined;\n\n\tif (this._extensionRunner.hasHandlers(\"session_before_compact\")) {\n\t\t// Deep-clone the preparation only when a before-compact handler actually exists. Extensions\n\t\t// receive an isolated, frozen snapshot so they cannot mutate protection metadata\n\t\t// (protectedEntryIds, entry .protected flags, etc.) on the internal preparation used for\n\t\t// validation. Building it lazily avoids deep-cloning the transcript — largest exactly when\n\t\t// compaction fires — on the common no-extension path.\n\t\tlet extensionPreparation: ContextCompactionPreparation;\n\t\ttry {\n\t\t\textensionPreparation = deepFreeze(structuredClone(preparation));\n\t\t} catch (error) {\n\t\t\t// structuredClone only throws if an entry carries a non-cloneable value (a function or a\n\t\t\t// class instance). Transcript entries are plain data today, so this guards a latent\n\t\t\t// invariant: surface a clear error instead of letting a raw DataCloneError abort an\n\t\t\t// otherwise-viable compaction.\n\t\t\tthrow new Error(\n\t\t\t\t`Failed to snapshot transcript for compaction extensions: ${error instanceof Error ? error.message : String(error)}`,\n\t\t\t);\n\t\t}\n\n\t\tconst hookResult = (await this._extensionRunner.emit({\n\t\t\ttype: \"session_before_compact\",\n\t\t\treason: options.reason,\n\t\t\tparameters,\n\t\t\tpreparation: extensionPreparation,\n\t\t\tbranchEntries: pathEntries,\n\t\t\tsignal: options.abortController.signal,\n\t\t} satisfies SessionBeforeCompactEvent)) as SessionBeforeCompactResult | undefined;\n\n\t\tif (hookResult?.cancel) {\n\t\t\tthrow new Error(\"Compaction cancelled\");\n\t\t}\n\n\t\tif (hookResult?.deletionRequest) {\n\t\t\tconst extensionDeletionRequest = hookResult.deletionRequest as ContextDeletionRequest;\n\t\t\t// Reject empty deletion requests before any side effects (backup, append, rebuild).\n\t\t\tif (!Array.isArray(extensionDeletionRequest.deletions) || extensionDeletionRequest.deletions.length === 0) {\n\t\t\t\tthrow new Error(\"No safe context deletions proposed by extension\");\n\t\t\t}\n\t\t\t// Validate against the internal transcript snapshot, not the extension-facing clone.\n\t\t\t// Auth is NOT resolved here — local extension deletion requests work offline.\n\t\t\tvalidated = validateContextDeletionRequest(extensionDeletionRequest, preparation.transcript);\n\t\t\t// Reject if reconciliation reduced deletions to zero.\n\t\t\tif (validated.deletedTargets.length === 0) {\n\t\t\t\tthrow new Error(\"No safe context deletions proposed by extension\");\n\t\t\t}\n\t\t\tfromExtension = true;\n\t\t}\n\t}\n\n\t// Planner fallback shared by both paths: no before-compact handler at all, or a handler that\n\t// observed without supplying a deletionRequest. Resolves auth lazily; undefined means auth is\n\t// unavailable (auto-mode resolvers), so compaction is a no-op.\n\tif (!validated) {\n\t\tconst plannerResult = await runPlanner();\n\t\tif (!plannerResult) {\n\t\t\treturn undefined;\n\t\t}\n\t\tvalidated = plannerResult;\n\t}\n\n\tif (options.abortController.signal.aborted) {\n\t\tthrow new Error(\"Compaction cancelled\");\n\t}\n\n\tconst backupPath = this.sessionManager.writeBackupSnapshot(options.backupLabel);\n\tconst compactionEntryId = this.sessionManager.appendContextCompaction(\n\t\tvalidated.deletedTargets,\n\t\tvalidated.protectedEntryIds,\n\t\tvalidated.stats,\n\t\tbackupPath,\n\t);\n\tconst sessionContext = this.sessionManager.buildSessionContext();\n\tthis.agent.state.messages = sessionContext.messages;\n\n\tconst result: ContextCompactionResult = {\n\t\t...validated,\n\t\tpromptVersion: 1,\n\t\tparameters,\n\t\t...(backupPath ? { backupPath } : {}),\n\t};\n\n\t// Emit session_compact so extensions can observe the validated result. This is a pure\n\t// observation hook fired AFTER the compaction has been committed (backup written,\n\t// context_compaction entry persisted, active context rebuilt). A misbehaving observer must\n\t// never turn a successful, already-persisted compaction into a reported failure, so any\n\t// throw is routed to the non-fatal extension-error channel and compaction still reports\n\t// success.\n\tconst contextCompactionEntry = this.sessionManager.getEntry(compactionEntryId) as ContextCompactionEntry;\n\ttry {\n\t\tawait this._extensionRunner.emit({\n\t\t\ttype: \"session_compact\",\n\t\t\treason: options.reason,\n\t\t\tparameters,\n\t\t\tresult,\n\t\t\tcontextCompactionEntry,\n\t\t\tfromExtension,\n\t\t} satisfies SessionCompactEvent);\n\t} catch (error) {\n\t\tthis._extensionRunner.emitError({\n\t\t\textensionPath: \"<session_compact>\",\n\t\t\tevent: \"session_compact\",\n\t\t\terror: error instanceof Error ? error.message : String(error),\n\t\t\tstack: error instanceof Error ? error.stack : undefined,\n\t\t});\n\t}\n\n\treturn result;\n}\n\n/**\n * Manually compact the session context using deletion-only verbatim context compaction.\n * Aborts current agent operation first.\n */\n\nexport async function compact(this: AgentSession, options: Partial<ContextCompactionParameters> = {}): Promise<ContextCompactionResult> {\n\tthis._disconnectFromAgent();\n\tawait this.abort();\n\tthis._compactionAbortController = new AbortController();\n\tthis._emit({ type: \"compaction_start\", reason: \"manual\" });\n\n\ttry {\n\t\tif (!this.model) {\n\t\t\tthrow new Error(formatNoModelSelectedMessage());\n\t\t}\n\n\t\t// Auth is resolved lazily: only called when the planner fallback is needed.\n\t\t// Extensions that provide a deletionRequest work without configured credentials.\n\t\tconst model = this.model;\n\t\tconst result = await this._applyContextVerbatimCompaction({\n\t\t\tresolvePlannerAuth: () => this._getRequiredRequestAuth(model),\n\t\t\tabortController: this._compactionAbortController,\n\t\t\tbackupLabel: \"compact\",\n\t\t\treason: \"manual\",\n\t\t\t...(options.compression_ratio === undefined ? {} : { compression_ratio: options.compression_ratio }),\n\t\t\t...(options.preserve_recent === undefined ? {} : { preserve_recent: options.preserve_recent }),\n\t\t\t...(options.query === undefined ? {} : { query: options.query }),\n\t\t});\n\t\tif (!result) {\n\t\t\tthrow new Error(\"Nothing to compact (session too small)\");\n\t\t}\n\n\t\tthis._emit({\n\t\t\ttype: \"compaction_end\",\n\t\t\treason: \"manual\",\n\t\t\tresult,\n\t\t\taborted: false,\n\t\t\twillRetry: false,\n\t\t});\n\t\treturn result;\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tconst aborted = message === \"Compaction cancelled\" || (error instanceof Error && error.name === \"AbortError\");\n\t\tthis._emit({\n\t\t\ttype: \"compaction_end\",\n\t\t\treason: \"manual\",\n\t\t\tresult: undefined,\n\t\t\taborted,\n\t\t\twillRetry: false,\n\t\t\terrorMessage: aborted ? undefined : `Compaction failed: ${message}`,\n\t\t});\n\t\tthrow error;\n\t} finally {\n\t\tthis._compactionAbortController = undefined;\n\t\tthis._reconnectToAgent();\n\t}\n}\n\n/**\n * Manually compact the session context by applying validated logical deletions.\n * Retained transcript entries/content blocks stay verbatim; no user prompt text is accepted.\n */\n\nexport async function contextCompact(this: AgentSession): Promise<ContextCompactionResult> {\n\tthis._disconnectFromAgent();\n\tawait this.abort();\n\tthis._compactionAbortController = new AbortController();\n\tthis._emit({ type: \"context_compaction_start\", reason: \"manual\" });\n\n\ttry {\n\t\tif (!this.model) {\n\t\t\tthrow new Error(formatNoModelSelectedMessage());\n\t\t}\n\n\t\t// Auth is resolved lazily: only called when the planner fallback is needed.\n\t\t// Extensions that provide a deletionRequest work without configured credentials.\n\t\tconst model = this.model;\n\t\tconst result = await this._applyContextVerbatimCompaction({\n\t\t\tresolvePlannerAuth: () => this._getRequiredRequestAuth(model),\n\t\t\tabortController: this._compactionAbortController,\n\t\t\tbackupLabel: \"context-compact\",\n\t\t\treason: \"manual\",\n\t\t});\n\t\tif (!result) {\n\t\t\tthrow new Error(\"Nothing to context-compact (session too small)\");\n\t\t}\n\n\t\tthis._emit({\n\t\t\ttype: \"context_compaction_end\",\n\t\t\treason: \"manual\",\n\t\t\tresult,\n\t\t\taborted: false,\n\t\t\twillRetry: false,\n\t\t});\n\t\treturn result;\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tconst aborted = message === \"Compaction cancelled\" || (error instanceof Error && error.name === \"AbortError\");\n\t\tthis._emit({\n\t\t\ttype: \"context_compaction_end\",\n\t\t\treason: \"manual\",\n\t\t\tresult: undefined,\n\t\t\taborted,\n\t\t\twillRetry: false,\n\t\t\terrorMessage: aborted ? undefined : `Context compaction failed: ${message}`,\n\t\t});\n\t\tthrow error;\n\t} finally {\n\t\tthis._compactionAbortController = undefined;\n\t\tthis._reconnectToAgent();\n\t}\n}\n\n/**\n * Cancel in-progress compaction (manual or auto).\n */\n\nexport function abortCompaction(this: AgentSession): void {\n\tthis._compactionAbortController?.abort();\n\tthis._autoCompactionAbortController?.abort();\n}\n\n/**\n * Cancel in-progress branch summarization.\n */\n\nexport function abortBranchSummary(this: AgentSession): void {\n\tthis._branchSummaryAbortController?.abort();\n}\n\n/**\n * Check if compaction is needed and run it.\n * Called after agent_end and before prompt submission.\n *\n * Two cases:\n * 1. Overflow: LLM returned context overflow error, remove error message from agent state, compact, auto-retry\n * 2. Threshold: Context over threshold, compact, resume queued active-turn work if present; otherwise wait for user\n *\n * @param assistantMessage The assistant message to check\n * @param skipAbortedCheck If false, include aborted messages (for pre-prompt check). Default: true\n */\n\nexport function setAutoCompactionEnabled(this: AgentSession, enabled: boolean): void {\n\tthis.settingsManager.setCompactionEnabled(enabled);\n}\n\n/** Whether auto-compaction is enabled */\n\nexport const agentSessionCompactionMethods = {\n\t_applyContextVerbatimCompaction,\n\tcompact,\n\tcontextCompact,\n\tabortCompaction,\n\tabortBranchSummary,\n\tsetAutoCompactionEnabled,\n};\n"]}
1
+ {"version":3,"file":"agent-session-compaction.js","sourceRoot":"","sources":["../../src/core/agent-session-compaction.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,4BAA4B,EAAE,MAAM,oBAAoB,CAAC;AAGlE,OAAO,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AAC9D,OAAO,EAAuK,cAAc,IAAI,iBAAiB,EAAE,wBAAwB,EAAE,8BAA8B,EAAE,MAAM,uBAAuB,CAAC;AAC3S,OAAO,EAAE,+BAA+B,EAAE,MAAM,6CAA6C,CAAC;AAM9F,SAAS,UAAU,CAAI,KAAQ;IAC9B,IAAI,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QACxC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACrB,KAAK,MAAM,MAAM,IAAI,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;YAC3C,UAAU,CAAC,MAAM,CAAC,CAAC;QACpB,CAAC;IACF,CAAC;IACD,OAAO,KAAK,CAAC;AACd,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,+BAA+B,CAEpD,OAAsC;IAEtC,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CAAC,4BAA4B,EAAE,CAAC,CAAC;IACjD,CAAC;IACD,qFAAqF;IACrF,mFAAmF;IACnF,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;IACzB,MAAM,uBAAuB,GAAG,IAAI,CAAC,aAAa,CAAC;IAEnD,MAAM,WAAW,GAAG,IAAI,CAAC,cAAc,CAAC,SAAS,EAAE,CAAC;IACpD,MAAM,QAAQ,GAAG,IAAI,CAAC,eAAe,CAAC,qBAAqB,EAAE,CAAC;IAC9D,MAAM,WAAW,GAAG,wBAAwB,CAAC,WAAW,EAAE,QAAQ,EAAE;QACnE,GAAG,CAAC,OAAO,CAAC,iBAAiB,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,iBAAiB,EAAE,OAAO,CAAC,iBAAiB,EAAE,CAAC;QACpG,GAAG,CAAC,OAAO,CAAC,eAAe,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,eAAe,EAAE,OAAO,CAAC,eAAe,EAAE,CAAC;QAC9F,GAAG,CAAC,OAAO,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC;KAChE,CAAC,CAAC;IACH,IAAI,CAAC,WAAW,EAAE,CAAC;QAClB,qFAAqF;QACrF,iGAAiG;QACjG,IAAI,OAAO,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;YACnC,MAAM,IAAI,KAAK,CAAC,+FAA+F,CAAC,CAAC;QAClH,CAAC;QACD,OAAO,SAAS,CAAC;IAClB,CAAC;IACD,MAAM,UAAU,GAAgC,WAAW,CAAC,UAAU,CAAC;IACvE,MAAM,eAAe,GAAG,uBAAuB,CAAC,KAAK,CAAC,CAAC;IACvD,MAAM,aAAa,GAClB,OAAO,CAAC,MAAM,KAAK,UAAU;QAC5B,CAAC,CAAC,EAAE,qBAAqB,EAAE,eAAe,EAAE,2BAA2B,EAAE,eAAe,EAAE;QAC1F,CAAC,CAAC,OAAO,CAAC,MAAM,KAAK,WAAW;YAC/B,yEAAyE;YACzE,kEAAkE;YAClE,8EAA8E;YAC9E,gFAAgF;YAChF,0EAA0E;YAC1E,CAAC,CAAC,EAAE,qBAAqB,EAAE,eAAe,GAAG,QAAQ,CAAC,aAAa,EAAE;YACrE,CAAC,CAAC,SAAS,CAAC;IAEf,uFAAuF;IACvF,0FAA0F;IAC1F,kFAAkF;IAClF,MAAM,UAAU,GAAG,KAAK,IAAyD,EAAE;QAClF,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,kBAAkB,EAAE,CAAC;QAChD,IAAI,CAAC,IAAI,EAAE,CAAC;YACX,IAAI,OAAO,CAAC,MAAM,KAAK,UAAU;gBAAE,OAAO,+BAA+B,CAAC,WAAW,CAAC,UAAU,EAAE,eAAe,CAAC,CAAC;YACnH,OAAO,SAAS,CAAC;QAClB,CAAC;QACD,OAAO,iBAAiB,CACvB,WAAW,EACX,KAAK,EACL,IAAI,CAAC,MAAM,EACX,IAAI,CAAC,OAAO,EACZ,OAAO,CAAC,eAAe,CAAC,MAAM,EAC9B,uBAAuB,EACvB,aAAa,CACb,CAAC;IACH,CAAC,CAAC;IAEF,2FAA2F;IAC3F,oFAAoF;IACpF,sCAAsC;IACtC,IAAI,aAAa,GAAG,KAAK,CAAC;IAC1B,IAAI,SAAqD,CAAC;IAE1D,IAAI,IAAI,CAAC,gBAAgB,CAAC,WAAW,CAAC,wBAAwB,CAAC,EAAE,CAAC;QACjE,4FAA4F;QAC5F,iFAAiF;QACjF,yFAAyF;QACzF,2FAA2F;QAC3F,sDAAsD;QACtD,IAAI,oBAAkD,CAAC;QACvD,IAAI,CAAC;YACJ,oBAAoB,GAAG,UAAU,CAAC,eAAe,CAAC,WAAW,CAAC,CAAC,CAAC;QACjE,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,yFAAyF;YACzF,oFAAoF;YACpF,oFAAoF;YACpF,+BAA+B;YAC/B,MAAM,IAAI,KAAK,CACd,4DAA4D,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CACpH,CAAC;QACH,CAAC;QAED,MAAM,UAAU,GAAG,CAAC,MAAM,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC;YACpD,IAAI,EAAE,wBAAwB;YAC9B,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,UAAU;YACV,WAAW,EAAE,oBAAoB;YACjC,aAAa,EAAE,WAAW;YAC1B,MAAM,EAAE,OAAO,CAAC,eAAe,CAAC,MAAM;SACF,CAAC,CAA2C,CAAC;QAElF,IAAI,UAAU,EAAE,MAAM,EAAE,CAAC;YACxB,MAAM,IAAI,KAAK,CAAC,sBAAsB,CAAC,CAAC;QACzC,CAAC;QAED,IAAI,UAAU,EAAE,eAAe,EAAE,CAAC;YACjC,MAAM,wBAAwB,GAAG,UAAU,CAAC,eAAyC,CAAC;YACtF,oFAAoF;YACpF,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,wBAAwB,CAAC,SAAS,CAAC,IAAI,wBAAwB,CAAC,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAC3G,MAAM,IAAI,KAAK,CAAC,iDAAiD,CAAC,CAAC;YACpE,CAAC;YACD,qFAAqF;YACrF,8EAA8E;YAC9E,SAAS,GAAG,8BAA8B,CAAC,wBAAwB,EAAE,WAAW,CAAC,UAAU,CAAC,CAAC;YAC7F,sDAAsD;YACtD,IAAI,SAAS,CAAC,cAAc,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAC3C,MAAM,IAAI,KAAK,CAAC,iDAAiD,CAAC,CAAC;YACpE,CAAC;YACD,aAAa,GAAG,IAAI,CAAC;QACtB,CAAC;IACF,CAAC;IAED,6FAA6F;IAC7F,8FAA8F;IAC9F,+DAA+D;IAC/D,IAAI,CAAC,SAAS,EAAE,CAAC;QAChB,MAAM,aAAa,GAAG,MAAM,UAAU,EAAE,CAAC;QACzC,IAAI,CAAC,aAAa,EAAE,CAAC;YACpB,OAAO,SAAS,CAAC;QAClB,CAAC;QACD,SAAS,GAAG,aAAa,CAAC;IAC3B,CAAC;IAED,IAAI,OAAO,CAAC,eAAe,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QAC5C,MAAM,IAAI,KAAK,CAAC,sBAAsB,CAAC,CAAC;IACzC,CAAC;IAED,MAAM,UAAU,GAAG,IAAI,CAAC,cAAc,CAAC,mBAAmB,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;IAChF,MAAM,iBAAiB,GAAG,IAAI,CAAC,cAAc,CAAC,uBAAuB,CACpE,SAAS,CAAC,cAAc,EACxB,SAAS,CAAC,iBAAiB,EAC3B,SAAS,CAAC,KAAK,EACf,UAAU,CACV,CAAC;IACF,MAAM,cAAc,GAAG,IAAI,CAAC,cAAc,CAAC,mBAAmB,EAAE,CAAC;IACjE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,QAAQ,GAAG,cAAc,CAAC,QAAQ,CAAC;IAEpD,MAAM,MAAM,GAA4B;QACvC,GAAG,SAAS;QACZ,aAAa,EAAE,CAAC;QAChB,UAAU;QACV,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACrC,CAAC;IAEF,sFAAsF;IACtF,kFAAkF;IAClF,2FAA2F;IAC3F,wFAAwF;IACxF,wFAAwF;IACxF,WAAW;IACX,MAAM,sBAAsB,GAAG,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,iBAAiB,CAA2B,CAAC;IACzG,IAAI,CAAC;QACJ,MAAM,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC;YAChC,IAAI,EAAE,iBAAiB;YACvB,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,UAAU;YACV,MAAM;YACN,sBAAsB;YACtB,aAAa;SACiB,CAAC,CAAC;IAClC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,IAAI,CAAC,gBAAgB,CAAC,SAAS,CAAC;YAC/B,aAAa,EAAE,mBAAmB;YAClC,KAAK,EAAE,iBAAiB;YACxB,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;YAC7D,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS;SACvD,CAAC,CAAC;IACJ,CAAC;IAED,OAAO,MAAM,CAAC;AACf,CAAC;AAED;;;GAGG;AAEH,MAAM,CAAC,KAAK,UAAU,OAAO,CAAqB,OAAO,GAAyC,EAAE;IACnG,IAAI,CAAC,oBAAoB,EAAE,CAAC;IAC5B,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;IACnB,IAAI,CAAC,0BAA0B,GAAG,IAAI,eAAe,EAAE,CAAC;IACxD,IAAI,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,kBAAkB,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC;IAE3D,IAAI,CAAC;QACJ,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;YACjB,MAAM,IAAI,KAAK,CAAC,4BAA4B,EAAE,CAAC,CAAC;QACjD,CAAC;QAED,4EAA4E;QAC5E,iFAAiF;QACjF,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;QACzB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,+BAA+B,CAAC;YACzD,kBAAkB,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,uBAAuB,CAAC,KAAK,CAAC;YAC7D,eAAe,EAAE,IAAI,CAAC,0BAA0B;YAChD,WAAW,EAAE,SAAS;YACtB,MAAM,EAAE,QAAQ;YAChB,GAAG,CAAC,OAAO,CAAC,iBAAiB,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,iBAAiB,EAAE,OAAO,CAAC,iBAAiB,EAAE,CAAC;YACpG,GAAG,CAAC,OAAO,CAAC,eAAe,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,eAAe,EAAE,OAAO,CAAC,eAAe,EAAE,CAAC;YAC9F,GAAG,CAAC,OAAO,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC;SAChE,CAAC,CAAC;QACH,IAAI,CAAC,MAAM,EAAE,CAAC;YACb,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC;QAC3D,CAAC;QAED,IAAI,CAAC,KAAK,CAAC;YACV,IAAI,EAAE,gBAAgB;YACtB,MAAM,EAAE,QAAQ;YAChB,MAAM;YACN,OAAO,EAAE,KAAK;YACd,SAAS,EAAE,KAAK;SAChB,CAAC,CAAC;QACH,OAAO,MAAM,CAAC;IACf,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACvE,MAAM,OAAO,GAAG,OAAO,KAAK,sBAAsB,IAAI,CAAC,KAAK,YAAY,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,YAAY,CAAC,CAAC;QAC9G,IAAI,CAAC,KAAK,CAAC;YACV,IAAI,EAAE,gBAAgB;YACtB,MAAM,EAAE,QAAQ;YAChB,MAAM,EAAE,SAAS;YACjB,OAAO;YACP,SAAS,EAAE,KAAK;YAChB,YAAY,EAAE,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,sBAAsB,OAAO,EAAE;SACnE,CAAC,CAAC;QACH,MAAM,KAAK,CAAC;IACb,CAAC;YAAS,CAAC;QACV,IAAI,CAAC,0BAA0B,GAAG,SAAS,CAAC;QAC5C,IAAI,CAAC,iBAAiB,EAAE,CAAC;IAC1B,CAAC;AACF,CAAC;AAED;;;GAGG;AAEH,MAAM,CAAC,KAAK,UAAU,cAAc;IACnC,IAAI,CAAC,oBAAoB,EAAE,CAAC;IAC5B,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;IACnB,IAAI,CAAC,0BAA0B,GAAG,IAAI,eAAe,EAAE,CAAC;IACxD,IAAI,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,0BAA0B,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC;IAEnE,IAAI,CAAC;QACJ,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;YACjB,MAAM,IAAI,KAAK,CAAC,4BAA4B,EAAE,CAAC,CAAC;QACjD,CAAC;QAED,4EAA4E;QAC5E,iFAAiF;QACjF,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;QACzB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,+BAA+B,CAAC;YACzD,kBAAkB,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,uBAAuB,CAAC,KAAK,CAAC;YAC7D,eAAe,EAAE,IAAI,CAAC,0BAA0B;YAChD,WAAW,EAAE,iBAAiB;YAC9B,MAAM,EAAE,QAAQ;SAChB,CAAC,CAAC;QACH,IAAI,CAAC,MAAM,EAAE,CAAC;YACb,MAAM,IAAI,KAAK,CAAC,gDAAgD,CAAC,CAAC;QACnE,CAAC;QAED,IAAI,CAAC,KAAK,CAAC;YACV,IAAI,EAAE,wBAAwB;YAC9B,MAAM,EAAE,QAAQ;YAChB,MAAM;YACN,OAAO,EAAE,KAAK;YACd,SAAS,EAAE,KAAK;SAChB,CAAC,CAAC;QACH,OAAO,MAAM,CAAC;IACf,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACvE,MAAM,OAAO,GAAG,OAAO,KAAK,sBAAsB,IAAI,CAAC,KAAK,YAAY,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,YAAY,CAAC,CAAC;QAC9G,IAAI,CAAC,KAAK,CAAC;YACV,IAAI,EAAE,wBAAwB;YAC9B,MAAM,EAAE,QAAQ;YAChB,MAAM,EAAE,SAAS;YACjB,OAAO;YACP,SAAS,EAAE,KAAK;YAChB,YAAY,EAAE,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,8BAA8B,OAAO,EAAE;SAC3E,CAAC,CAAC;QACH,MAAM,KAAK,CAAC;IACb,CAAC;YAAS,CAAC;QACV,IAAI,CAAC,0BAA0B,GAAG,SAAS,CAAC;QAC5C,IAAI,CAAC,iBAAiB,EAAE,CAAC;IAC1B,CAAC;AACF,CAAC;AAED;;GAEG;AAEH,MAAM,UAAU,eAAe;IAC9B,IAAI,CAAC,0BAA0B,EAAE,KAAK,EAAE,CAAC;IACzC,IAAI,CAAC,8BAA8B,EAAE,KAAK,EAAE,CAAC;AAC9C,CAAC;AAED;;GAEG;AAEH,MAAM,UAAU,kBAAkB;IACjC,IAAI,CAAC,6BAA6B,EAAE,KAAK,EAAE,CAAC;AAC7C,CAAC;AAED;;;;;;;;;;GAUG;AAEH,MAAM,UAAU,wBAAwB,CAAqB,OAAgB;IAC5E,IAAI,CAAC,eAAe,CAAC,oBAAoB,CAAC,OAAO,CAAC,CAAC;AACpD,CAAC;AAED,yCAAyC;AAEzC,MAAM,CAAC,MAAM,6BAA6B,GAAG;IAC5C,+BAA+B;IAC/B,OAAO;IACP,cAAc;IACd,eAAe;IACf,kBAAkB;IAClB,wBAAwB;CACxB,CAAC","sourcesContent":["import { formatNoModelSelectedMessage } from \"./auth-guidance.ts\";\nimport type { AgentSessionInternalSurface as AgentSession } from \"./agent-session-methods.ts\";\nimport type { ContextCompactionApplyOptions } from \"./agent-session-methods.ts\";\nimport { getEffectiveInputBudget } from \"./context-window.ts\";\nimport { type ContextCompactionParameters, type ContextCompactionPreparation, type ContextCompactionResult, type ContextDeletionRequest, type ValidatedContextDeletionResult, contextCompact as runContextCompact, prepareContextCompaction, validateContextDeletionRequest } from \"./compaction/index.ts\";\nimport { runDeterministicContextEviction } from \"./compaction/context-compaction-eviction.ts\";\n// Type-only import: erased at runtime so module mocks of the compaction barrel stay authoritative.\nimport type { ContextCompactionLadderOptions } from \"./compaction/context-compaction-runner.ts\";\nimport type { SessionBeforeCompactEvent, SessionBeforeCompactResult, SessionCompactEvent } from \"./extensions/index.ts\";\nimport type { ContextCompactionEntry } from \"./session-manager.ts\";\n\nfunction deepFreeze<T>(value: T): T {\n\tif (value && typeof value === \"object\") {\n\t\tObject.freeze(value);\n\t\tfor (const nested of Object.values(value)) {\n\t\t\tdeepFreeze(nested);\n\t\t}\n\t}\n\treturn value;\n}\n\nexport async function _applyContextVerbatimCompaction(\n\tthis: AgentSession,\n\toptions: ContextCompactionApplyOptions,\n): Promise<ContextCompactionResult | undefined> {\n\tif (!this.model) {\n\t\tthrow new Error(formatNoModelSelectedMessage());\n\t}\n\t// Capture the narrowed model now (control-flow narrowing holds immediately after the\n\t// guard) so the lazy planner-fallback closure below can use a non-undefined model.\n\tconst model = this.model;\n\tconst compactionThinkingLevel = this.thinkingLevel;\n\n\tconst pathEntries = this.sessionManager.getBranch();\n\tconst settings = this.settingsManager.getCompactionSettings();\n\tconst preparation = prepareContextCompaction(pathEntries, settings, {\n\t\t...(options.compression_ratio === undefined ? {} : { compression_ratio: options.compression_ratio }),\n\t\t...(options.preserve_recent === undefined ? {} : { preserve_recent: options.preserve_recent }),\n\t\t...(options.query === undefined ? {} : { query: options.query }),\n\t});\n\tif (!preparation) {\n\t\t// Overflow recovery must be loud when there is no transcript the ladder can compact:\n\t\t// a silent no-op would leave the session overflowing without proving safe deletion is exhausted.\n\t\tif (options.reason === \"overflow\") {\n\t\t\tthrow new Error(\"Context compaction found no compactable transcript entries; nothing more was safely deletable\");\n\t\t}\n\t\treturn undefined;\n\t}\n\tconst parameters: ContextCompactionParameters = preparation.parameters;\n\tconst effectiveBudget = getEffectiveInputBudget(model);\n\tconst ladderOptions: ContextCompactionLadderOptions | undefined =\n\t\toptions.reason === \"overflow\"\n\t\t\t? { acceptanceTokenBudget: effectiveBudget, criticalEvictionTokenBudget: effectiveBudget }\n\t\t\t: options.reason === \"threshold\"\n\t\t\t\t// Match the shouldCompact trigger boundary in transcript-estimate space:\n\t\t\t\t// tokensAfter <= effectiveBudget - reserveTokens is equivalent to\n\t\t\t\t// !shouldCompact(tokensAfter, effectiveBudget, settings). The live post-retry\n\t\t\t\t// trigger uses provider usage (system/tools included), so this is a best-effort\n\t\t\t\t// boundary; a negative boundary correctly means no estimated result fits.\n\t\t\t\t? { acceptanceTokenBudget: effectiveBudget - settings.reserveTokens }\n\t\t\t\t: undefined;\n\n\t// Planner fallback used when no extension supplies a deletionRequest. Auth is resolved\n\t// lazily here so extension-provided deletion requests keep working offline. Overflow with\n\t// missing auth uses deterministic eviction directly instead of silently no-oping.\n\tconst runPlanner = async (): Promise<ValidatedContextDeletionResult | undefined> => {\n\t\tconst auth = await options.resolvePlannerAuth();\n\t\tif (!auth) {\n\t\t\tif (options.reason === \"overflow\") return runDeterministicContextEviction(preparation.transcript, effectiveBudget);\n\t\t\treturn undefined;\n\t\t}\n\t\treturn runContextCompact(\n\t\t\tpreparation,\n\t\t\tmodel,\n\t\t\tauth.apiKey,\n\t\t\tauth.headers,\n\t\t\toptions.abortController.signal,\n\t\t\tcompactionThinkingLevel,\n\t\t\tladderOptions,\n\t\t);\n\t};\n\n\t// Emit session_before_compact to allow extensions to cancel or provide a deletion request.\n\t// This happens BEFORE any auth resolution so local extension deletion requests work\n\t// without configured API credentials.\n\tlet fromExtension = false;\n\tlet validated: ValidatedContextDeletionResult | undefined;\n\n\tif (this._extensionRunner.hasHandlers(\"session_before_compact\")) {\n\t\t// Deep-clone the preparation only when a before-compact handler actually exists. Extensions\n\t\t// receive an isolated, frozen snapshot so they cannot mutate protection metadata\n\t\t// (protectedEntryIds, entry .protected flags, etc.) on the internal preparation used for\n\t\t// validation. Building it lazily avoids deep-cloning the transcript — largest exactly when\n\t\t// compaction fires — on the common no-extension path.\n\t\tlet extensionPreparation: ContextCompactionPreparation;\n\t\ttry {\n\t\t\textensionPreparation = deepFreeze(structuredClone(preparation));\n\t\t} catch (error) {\n\t\t\t// structuredClone only throws if an entry carries a non-cloneable value (a function or a\n\t\t\t// class instance). Transcript entries are plain data today, so this guards a latent\n\t\t\t// invariant: surface a clear error instead of letting a raw DataCloneError abort an\n\t\t\t// otherwise-viable compaction.\n\t\t\tthrow new Error(\n\t\t\t\t`Failed to snapshot transcript for compaction extensions: ${error instanceof Error ? error.message : String(error)}`,\n\t\t\t);\n\t\t}\n\n\t\tconst hookResult = (await this._extensionRunner.emit({\n\t\t\ttype: \"session_before_compact\",\n\t\t\treason: options.reason,\n\t\t\tparameters,\n\t\t\tpreparation: extensionPreparation,\n\t\t\tbranchEntries: pathEntries,\n\t\t\tsignal: options.abortController.signal,\n\t\t} satisfies SessionBeforeCompactEvent)) as SessionBeforeCompactResult | undefined;\n\n\t\tif (hookResult?.cancel) {\n\t\t\tthrow new Error(\"Compaction cancelled\");\n\t\t}\n\n\t\tif (hookResult?.deletionRequest) {\n\t\t\tconst extensionDeletionRequest = hookResult.deletionRequest as ContextDeletionRequest;\n\t\t\t// Reject empty deletion requests before any side effects (backup, append, rebuild).\n\t\t\tif (!Array.isArray(extensionDeletionRequest.deletions) || extensionDeletionRequest.deletions.length === 0) {\n\t\t\t\tthrow new Error(\"No safe context deletions proposed by extension\");\n\t\t\t}\n\t\t\t// Validate against the internal transcript snapshot, not the extension-facing clone.\n\t\t\t// Auth is NOT resolved here — local extension deletion requests work offline.\n\t\t\tvalidated = validateContextDeletionRequest(extensionDeletionRequest, preparation.transcript);\n\t\t\t// Reject if reconciliation reduced deletions to zero.\n\t\t\tif (validated.deletedTargets.length === 0) {\n\t\t\t\tthrow new Error(\"No safe context deletions proposed by extension\");\n\t\t\t}\n\t\t\tfromExtension = true;\n\t\t}\n\t}\n\n\t// Planner fallback shared by both paths: no before-compact handler at all, or a handler that\n\t// observed without supplying a deletionRequest. Resolves auth lazily; undefined means auth is\n\t// unavailable (auto-mode resolvers), so compaction is a no-op.\n\tif (!validated) {\n\t\tconst plannerResult = await runPlanner();\n\t\tif (!plannerResult) {\n\t\t\treturn undefined;\n\t\t}\n\t\tvalidated = plannerResult;\n\t}\n\n\tif (options.abortController.signal.aborted) {\n\t\tthrow new Error(\"Compaction cancelled\");\n\t}\n\n\tconst backupPath = this.sessionManager.writeBackupSnapshot(options.backupLabel);\n\tconst compactionEntryId = this.sessionManager.appendContextCompaction(\n\t\tvalidated.deletedTargets,\n\t\tvalidated.protectedEntryIds,\n\t\tvalidated.stats,\n\t\tbackupPath,\n\t);\n\tconst sessionContext = this.sessionManager.buildSessionContext();\n\tthis.agent.state.messages = sessionContext.messages;\n\n\tconst result: ContextCompactionResult = {\n\t\t...validated,\n\t\tpromptVersion: 1,\n\t\tparameters,\n\t\t...(backupPath ? { backupPath } : {}),\n\t};\n\n\t// Emit session_compact so extensions can observe the validated result. This is a pure\n\t// observation hook fired AFTER the compaction has been committed (backup written,\n\t// context_compaction entry persisted, active context rebuilt). A misbehaving observer must\n\t// never turn a successful, already-persisted compaction into a reported failure, so any\n\t// throw is routed to the non-fatal extension-error channel and compaction still reports\n\t// success.\n\tconst contextCompactionEntry = this.sessionManager.getEntry(compactionEntryId) as ContextCompactionEntry;\n\ttry {\n\t\tawait this._extensionRunner.emit({\n\t\t\ttype: \"session_compact\",\n\t\t\treason: options.reason,\n\t\t\tparameters,\n\t\t\tresult,\n\t\t\tcontextCompactionEntry,\n\t\t\tfromExtension,\n\t\t} satisfies SessionCompactEvent);\n\t} catch (error) {\n\t\tthis._extensionRunner.emitError({\n\t\t\textensionPath: \"<session_compact>\",\n\t\t\tevent: \"session_compact\",\n\t\t\terror: error instanceof Error ? error.message : String(error),\n\t\t\tstack: error instanceof Error ? error.stack : undefined,\n\t\t});\n\t}\n\n\treturn result;\n}\n\n/**\n * Manually compact the session context using deletion-only verbatim context compaction.\n * Aborts current agent operation first.\n */\n\nexport async function compact(this: AgentSession, options: Partial<ContextCompactionParameters> = {}): Promise<ContextCompactionResult> {\n\tthis._disconnectFromAgent();\n\tawait this.abort();\n\tthis._compactionAbortController = new AbortController();\n\tthis._emit({ type: \"compaction_start\", reason: \"manual\" });\n\n\ttry {\n\t\tif (!this.model) {\n\t\t\tthrow new Error(formatNoModelSelectedMessage());\n\t\t}\n\n\t\t// Auth is resolved lazily: only called when the planner fallback is needed.\n\t\t// Extensions that provide a deletionRequest work without configured credentials.\n\t\tconst model = this.model;\n\t\tconst result = await this._applyContextVerbatimCompaction({\n\t\t\tresolvePlannerAuth: () => this._getRequiredRequestAuth(model),\n\t\t\tabortController: this._compactionAbortController,\n\t\t\tbackupLabel: \"compact\",\n\t\t\treason: \"manual\",\n\t\t\t...(options.compression_ratio === undefined ? {} : { compression_ratio: options.compression_ratio }),\n\t\t\t...(options.preserve_recent === undefined ? {} : { preserve_recent: options.preserve_recent }),\n\t\t\t...(options.query === undefined ? {} : { query: options.query }),\n\t\t});\n\t\tif (!result) {\n\t\t\tthrow new Error(\"Nothing to compact (session too small)\");\n\t\t}\n\n\t\tthis._emit({\n\t\t\ttype: \"compaction_end\",\n\t\t\treason: \"manual\",\n\t\t\tresult,\n\t\t\taborted: false,\n\t\t\twillRetry: false,\n\t\t});\n\t\treturn result;\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tconst aborted = message === \"Compaction cancelled\" || (error instanceof Error && error.name === \"AbortError\");\n\t\tthis._emit({\n\t\t\ttype: \"compaction_end\",\n\t\t\treason: \"manual\",\n\t\t\tresult: undefined,\n\t\t\taborted,\n\t\t\twillRetry: false,\n\t\t\terrorMessage: aborted ? undefined : `Compaction failed: ${message}`,\n\t\t});\n\t\tthrow error;\n\t} finally {\n\t\tthis._compactionAbortController = undefined;\n\t\tthis._reconnectToAgent();\n\t}\n}\n\n/**\n * Manually compact the session context by applying validated logical deletions.\n * Retained transcript entries/content blocks stay verbatim; no user prompt text is accepted.\n */\n\nexport async function contextCompact(this: AgentSession): Promise<ContextCompactionResult> {\n\tthis._disconnectFromAgent();\n\tawait this.abort();\n\tthis._compactionAbortController = new AbortController();\n\tthis._emit({ type: \"context_compaction_start\", reason: \"manual\" });\n\n\ttry {\n\t\tif (!this.model) {\n\t\t\tthrow new Error(formatNoModelSelectedMessage());\n\t\t}\n\n\t\t// Auth is resolved lazily: only called when the planner fallback is needed.\n\t\t// Extensions that provide a deletionRequest work without configured credentials.\n\t\tconst model = this.model;\n\t\tconst result = await this._applyContextVerbatimCompaction({\n\t\t\tresolvePlannerAuth: () => this._getRequiredRequestAuth(model),\n\t\t\tabortController: this._compactionAbortController,\n\t\t\tbackupLabel: \"context-compact\",\n\t\t\treason: \"manual\",\n\t\t});\n\t\tif (!result) {\n\t\t\tthrow new Error(\"Nothing to context-compact (session too small)\");\n\t\t}\n\n\t\tthis._emit({\n\t\t\ttype: \"context_compaction_end\",\n\t\t\treason: \"manual\",\n\t\t\tresult,\n\t\t\taborted: false,\n\t\t\twillRetry: false,\n\t\t});\n\t\treturn result;\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tconst aborted = message === \"Compaction cancelled\" || (error instanceof Error && error.name === \"AbortError\");\n\t\tthis._emit({\n\t\t\ttype: \"context_compaction_end\",\n\t\t\treason: \"manual\",\n\t\t\tresult: undefined,\n\t\t\taborted,\n\t\t\twillRetry: false,\n\t\t\terrorMessage: aborted ? undefined : `Context compaction failed: ${message}`,\n\t\t});\n\t\tthrow error;\n\t} finally {\n\t\tthis._compactionAbortController = undefined;\n\t\tthis._reconnectToAgent();\n\t}\n}\n\n/**\n * Cancel in-progress compaction (manual or auto).\n */\n\nexport function abortCompaction(this: AgentSession): void {\n\tthis._compactionAbortController?.abort();\n\tthis._autoCompactionAbortController?.abort();\n}\n\n/**\n * Cancel in-progress branch summarization.\n */\n\nexport function abortBranchSummary(this: AgentSession): void {\n\tthis._branchSummaryAbortController?.abort();\n}\n\n/**\n * Check if compaction is needed and run it.\n * Called after agent_end and before prompt submission.\n *\n * Two cases:\n * 1. Overflow: LLM returned context overflow error, remove error message from agent state, compact, auto-retry\n * 2. Threshold: Context over threshold, compact, resume queued active-turn work if present; otherwise wait for user\n *\n * @param assistantMessage The assistant message to check\n * @param skipAbortedCheck If false, include aborted messages (for pre-prompt check). Default: true\n */\n\nexport function setAutoCompactionEnabled(this: AgentSession, enabled: boolean): void {\n\tthis.settingsManager.setCompactionEnabled(enabled);\n}\n\n/** Whether auto-compaction is enabled */\n\nexport const agentSessionCompactionMethods = {\n\t_applyContextVerbatimCompaction,\n\tcompact,\n\tcontextCompact,\n\tabortCompaction,\n\tabortBranchSummary,\n\tsetAutoCompactionEnabled,\n};\n"]}
@@ -0,0 +1,15 @@
1
+ import type { CompactableTranscript, CompactableTranscriptEntry, ContextCompactionParameters } from "./context-compaction-types.ts";
2
+ export declare const CONTEXT_CRITICAL_OVERFLOW_RECENT_ENTRY_COUNT = 5;
3
+ export declare function criticalCompactionParameters(parameters: ContextCompactionParameters): ContextCompactionParameters;
4
+ export declare function isCriticalOverflowProtectedEntryDeletable(entry: CompactableTranscriptEntry, transcript: CompactableTranscript): boolean;
5
+ /**
6
+ * Return a transcript for critical overflow passes. The effective recent guard is
7
+ * widened to max(configured preserve_recent, 5) over all entries, restoring the
8
+ * pre-#1399 critical-overflow last-5 floor through the existing recent-target
9
+ * validation path. Only stale protected task-bearing entries outside that floor
10
+ * are relaxed; `protectedEntryIds` is rebuilt from still-protected entries so
11
+ * persisted results/events stay disjoint from deleted targets.
12
+ */
13
+ export declare function relaxTranscriptForCriticalEviction(transcript: CompactableTranscript): CompactableTranscript;
14
+ export declare const CONTEXT_COMPACTION_CRITICAL_OVERFLOW_PROMPT = "\n<critical-overflow-mode>\nThe previous model request overflowed its context window. This is a critical LRU-style compaction pass. First delete stale unprotected context. If that is not enough, you may also delete the earliest formerly-protected entries or protected content shown in the manifest. Evict old low-signal context first, including old reasoning/thinking traces when they are not part of the latest retained assistant message, then older user/custom/summary context while preserving recent entries, unresolved errors, failed commands, and enough task-bearing context for the assistant to continue.\n\nSafety invariant: the latest retained assistant message cannot be modified when it contains content blocks with type \"thinking\" or \"redacted_thinking\". Do not delete, target, or suggest any content block in that latest thinking-bearing assistant message, including sibling text or tool-call blocks. Older non-latest thinking/redacted_thinking blocks may be deleted during critical overflow when validation allows it.\n</critical-overflow-mode>";
15
+ //# sourceMappingURL=context-compaction-critical.d.ts.map