@@ -120,10 +120,6 @@ import {
120120} from "../prompt-cache-observability.js" ;
121121import { resolveCacheRetention } from "../prompt-cache-retention.js" ;
122122import { sanitizeSessionHistory , validateReplayTurns } from "../replay-history.js" ;
123- import {
124- PREEMPTIVE_OVERFLOW_ERROR_TEXT ,
125- shouldPreemptivelyCompactBeforePrompt ,
126- } from "./preemptive-compaction.js" ;
127123import {
128124 clearActiveEmbeddedRun ,
129125 type EmbeddedPiQueueHandle ,
@@ -149,6 +145,7 @@ import {
149145import { dropThinkingBlocks } from "../thinking.js" ;
150146import { collectAllowedToolNames } from "../tool-name-allowlist.js" ;
151147import { installToolResultContextGuard } from "../tool-result-context-guard.js" ;
148+ import { truncateOversizedToolResultsInSessionManager } from "../tool-result-truncation.js" ;
152149import {
153150 logProviderToolSchemaDiagnostics ,
154151 normalizeProviderToolSchemas ,
@@ -210,6 +207,10 @@ import { pruneProcessedHistoryImages } from "./history-image-prune.js";
210207import { detectAndLoadPromptImages } from "./images.js" ;
211208import { buildAttemptReplayMetadata } from "./incomplete-turn.js" ;
212209import { resolveLlmIdleTimeoutMs , streamWithIdleTimeout } from "./llm-idle-timeout.js" ;
210+ import {
211+ PREEMPTIVE_OVERFLOW_ERROR_TEXT ,
212+ shouldPreemptivelyCompactBeforePrompt ,
213+ } from "./preemptive-compaction.js" ;
213214import type { EmbeddedRunAttemptParams , EmbeddedRunAttemptResult } from "./types.js" ;
214215
215216export {
@@ -1525,8 +1526,10 @@ export async function runEmbeddedAttempt(
15251526 const hookAgentId = sessionAgentId ;
15261527
15271528 let promptError : unknown = null ;
1529+ let preflightRecovery : EmbeddedRunAttemptResult [ "preflightRecovery" ] ;
15281530 let promptErrorSource : "prompt" | "compaction" | "precheck" | null = null ;
15291531 let prePromptMessageCount = activeSession . messages . length ;
1532+ let skipPromptSubmission = false ;
15301533 try {
15311534 const promptStartedAt = Date . now ( ) ;
15321535
@@ -1773,32 +1776,81 @@ export async function runEmbeddedAttempt(
17731776 contextTokenBudget : params . contextTokenBudget ,
17741777 reserveTokens,
17751778 } ) ;
1779+ if ( preemptiveCompaction . route === "truncate_tool_results_only" ) {
1780+ const truncationResult = truncateOversizedToolResultsInSessionManager ( {
1781+ sessionManager,
1782+ contextWindowTokens : params . contextTokenBudget ,
1783+ sessionFile : params . sessionFile ,
1784+ sessionId : params . sessionId ,
1785+ sessionKey : params . sessionKey ,
1786+ } ) ;
1787+ if ( truncationResult . truncated ) {
1788+ preflightRecovery = {
1789+ route : "truncate_tool_results_only" ,
1790+ handled : true ,
1791+ truncatedCount : truncationResult . truncatedCount ,
1792+ } ;
1793+ log . info (
1794+ `[context-overflow-precheck] early tool-result truncation succeeded for ` +
1795+ `${ params . provider } /${ params . modelId } route=${ preemptiveCompaction . route } ` +
1796+ `truncatedCount=${ truncationResult . truncatedCount } ` +
1797+ `estimatedPromptTokens=${ preemptiveCompaction . estimatedPromptTokens } ` +
1798+ `promptBudgetBeforeReserve=${ preemptiveCompaction . promptBudgetBeforeReserve } ` +
1799+ `overflowTokens=${ preemptiveCompaction . overflowTokens } ` +
1800+ `toolResultReducibleChars=${ preemptiveCompaction . toolResultReducibleChars } ` +
1801+ `sessionFile=${ params . sessionFile } ` ,
1802+ ) ;
1803+ skipPromptSubmission = true ;
1804+ }
1805+ if ( ! skipPromptSubmission ) {
1806+ log . warn (
1807+ `[context-overflow-precheck] early tool-result truncation did not help for ` +
1808+ `${ params . provider } /${ params . modelId } ; falling back to compaction ` +
1809+ `reason=${ truncationResult . reason ?? "unknown" } sessionFile=${ params . sessionFile } ` ,
1810+ ) ;
1811+ preflightRecovery = { route : "compact_only" } ;
1812+ promptError = new Error ( PREEMPTIVE_OVERFLOW_ERROR_TEXT ) ;
1813+ promptErrorSource = "precheck" ;
1814+ skipPromptSubmission = true ;
1815+ }
1816+ }
17761817 if ( preemptiveCompaction . shouldCompact ) {
1818+ preflightRecovery =
1819+ preemptiveCompaction . route === "compact_then_truncate"
1820+ ? { route : "compact_then_truncate" }
1821+ : { route : "compact_only" } ;
17771822 promptError = new Error ( PREEMPTIVE_OVERFLOW_ERROR_TEXT ) ;
17781823 promptErrorSource = "precheck" ;
17791824 log . warn (
17801825 `[context-overflow-precheck] sessionKey=${ params . sessionKey ?? params . sessionId } ` +
17811826 `provider=${ params . provider } /${ params . modelId } ` +
1827+ `route=${ preemptiveCompaction . route } ` +
17821828 `estimatedPromptTokens=${ preemptiveCompaction . estimatedPromptTokens } ` +
17831829 `promptBudgetBeforeReserve=${ preemptiveCompaction . promptBudgetBeforeReserve } ` +
1830+ `overflowTokens=${ preemptiveCompaction . overflowTokens } ` +
1831+ `toolResultReducibleChars=${ preemptiveCompaction . toolResultReducibleChars } ` +
17841832 `reserveTokens=${ reserveTokens } sessionFile=${ params . sessionFile } ` ,
17851833 ) ;
1786- return ;
1834+ skipPromptSubmission = true ;
17871835 }
17881836
1789- const btwSnapshotMessages = activeSession . messages . slice ( - MAX_BTW_SNAPSHOT_MESSAGES ) ;
1790- updateActiveEmbeddedRunSnapshot ( params . sessionId , {
1791- transcriptLeafId,
1792- messages : btwSnapshotMessages ,
1793- inFlightPrompt : effectivePrompt ,
1794- } ) ;
1837+ if ( ! skipPromptSubmission ) {
1838+ const btwSnapshotMessages = activeSession . messages . slice ( - MAX_BTW_SNAPSHOT_MESSAGES ) ;
1839+ updateActiveEmbeddedRunSnapshot ( params . sessionId , {
1840+ transcriptLeafId,
1841+ messages : btwSnapshotMessages ,
1842+ inFlightPrompt : effectivePrompt ,
1843+ } ) ;
17951844
1796- // Only pass images option if there are actually images to pass
1797- // This avoids potential issues with models that don't expect the images parameter
1798- if ( imageResult . images . length > 0 ) {
1799- await abortable ( activeSession . prompt ( effectivePrompt , { images : imageResult . images } ) ) ;
1800- } else {
1801- await abortable ( activeSession . prompt ( effectivePrompt ) ) ;
1845+ // Only pass images option if there are actually images to pass
1846+ // This avoids potential issues with models that don't expect the images parameter
1847+ if ( imageResult . images . length > 0 ) {
1848+ await abortable (
1849+ activeSession . prompt ( effectivePrompt , { images : imageResult . images } ) ,
1850+ ) ;
1851+ } else {
1852+ await abortable ( activeSession . prompt ( effectivePrompt ) ) ;
1853+ }
18021854 }
18031855 } catch ( err ) {
18041856 // Yield-triggered abort is intentional — treat as clean stop, not error.
@@ -2160,6 +2212,7 @@ export async function runEmbeddedAttempt(
21602212 timedOut,
21612213 timedOutDuringCompaction,
21622214 promptError,
2215+ preflightRecovery,
21632216 sessionIdUsed,
21642217 bootstrapPromptWarningSignaturesSeen : bootstrapPromptWarning . warningSignaturesSeen ,
21652218 bootstrapPromptWarningSignature : bootstrapPromptWarning . signature ,
0 commit comments