fix(coding-agent): port no-preparation dead-end rescue to the tiered #rescueCompactionDeadEnd API

Main renamed/refactored #tryShakeRescueForDeadEnd + #emitShakeRescueNotice into
the tiered #rescueCompactionDeadEnd (elide, then image drop, notices emitted
internally), so a textually-clean merge of this branch left calls to undefined
private methods. Re-express the !preparation rescue through the new API:
progress = prepareCompaction succeeding on the rewritten branch, skipElide when
falling through from a shake pass (the image tier still gets a chance), and
historyRewritten flagged whenever a tier freed content even without progress.

Restore the baseline dead-end remedy text (image drop is automated now, so the
manual /shake images suggestion is stale) and align the rescue-refit regression
test with main's continuation gating (auto-continue after compaction now
requires an active goal or queued work).
This commit is contained in:
can1357
2026-07-18 20:28:05 +02:00
parent 401fca701b
commit ba385a2946
2 changed files with 32 additions and 23 deletions
@@ -13904,25 +13904,32 @@ export class AgentSession {
// That is exactly the dead-end the elide shake rescues: it reaches
// INSIDE the tail and offloads heavy content to an artifact placeholder,
// shrinking the tail so findCutPoint can then move the cut and leave
// older turns to summarize. Run the same rescue the post-maintenance
// guard uses, then re-prepare on the elided branch and fall through to
// the normal compaction body when it now succeeds (writing a compaction
// entry anchors the stale billed usage so the auto-continue re-check
// cannot re-trip and loop the warning — issue #4786). Skip when we
// already fell through from a shake strategy pass (it tried and found
// nothing) or on the idle timer (it re-checks usage on its own cadence).
let rescued: ShakeResult | undefined;
if (reason !== "idle" && !fallbackFromShake) {
rescued = await this.#tryShakeRescueForDeadEnd(autoCompactionSignal);
if (rescued && !autoCompactionSignal.aborted) {
pathEntriesForCompaction = this.sessionManager.getBranch();
preparation = prepareCompaction(
pathEntriesForCompaction,
compactionSettings,
autoCompactionCandidates,
);
if (preparation) this.#emitShakeRescueNotice(rescued);
}
// older turns to summarize. Run the same tiered rescue the
// post-maintenance guard uses (elide, then image drop), with progress
// defined as "prepareCompaction now succeeds on the rewritten branch",
// and fall through to the normal compaction body when it does (writing
// a compaction entry anchors the stale billed usage so the
// auto-continue re-check cannot re-trip and loop the warning — issue
// #4786). `skipElide` when we already fell through from a shake
// strategy pass (it tried and found nothing); skip entirely on the
// idle timer (it re-checks usage on its own cadence).
let rescueRewroteHistory = false;
if (reason !== "idle") {
await this.#rescueCompactionDeadEnd(autoCompactionSignal, {
skipElide: fallbackFromShake,
hasProgress: () => {
// Only reached when a tier actually freed something, so the
// branch has been rewritten either way.
rescueRewroteHistory = true;
pathEntriesForCompaction = this.sessionManager.getBranch();
preparation = prepareCompaction(
pathEntriesForCompaction,
compactionSettings,
autoCompactionCandidates,
);
return preparation !== undefined;
},
});
}
if (!preparation) {
await this.#emitSessionEvent({
@@ -13946,7 +13953,7 @@ export class AgentSession {
if (noProgressDeadEnd) {
this.emitNotice(
"warning",
compactionDeadEndWarning("clear large tool output, run `/shake images` to drop attached images,"),
compactionDeadEndWarning("shrink it (e.g. clear large tool output)"),
"compaction",
);
}
@@ -13959,7 +13966,7 @@ export class AgentSession {
: noProgressDeadEnd
? COMPACTION_CHECK_BLOCK_AUTOMATIC_CONTINUATION
: COMPACTION_CHECK_NONE;
return rescued ? { ...base, historyRewritten: true } : base;
return rescueRewroteHistory ? { ...base, historyRewritten: true } : base;
}
}
@@ -1345,8 +1345,10 @@ describe("AgentSession auto-compaction progress guard", () => {
// side) and summary compaction cannot start. The dead-end runs the elide
// shake rescue INSIDE the tail; once it frees enough, prepareCompaction is
// retried on the elided branch, now succeeds, and the pass falls through to
// a normal (hook-supplied) compaction that creates headroom and
// auto-continues instead of looping the no-progress warning.
// a normal (hook-supplied) compaction that creates headroom and — with an
// active goal still needing work — auto-continues instead of looping the
// no-progress warning.
activateOngoingGoal("rescue-refit");
const branch = sessionManager.getBranch();
const firstKeptEntryId = branch[branch.length - 1].id;
if (!firstKeptEntryId) throw new Error("seeded entry has no id");