mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-10 03:00:44 +00:00
fix(agents): scope claude-cli fallback seed and pair summary with boundary
Addresses review on #72069: - Codex P1 ("Gate Claude prelude seeding by source provider"): the guard checked the *current* fallback candidate but not the failed attempt. A session that still carried a stale cliSessionBindings["claude-cli"] from an unrelated past run would inject Claude transcript context into a fallback chain that started on a different provider (e.g. openai -> openai-codex), leaking irrelevant prior conversation. Plumb `originalProvider` (the user-requested provider for the chain) through to runAgentAttempt and require `isClaudeCliProvider(originalProvider)` before reading Claude history. - Codex P2 ("Prefer latest compact boundary when summary is missing"): the resolver always preferred the most recent explicit summary, so a later compaction without its own summary entry (rare crash case) paired stale summary text with post-latest-boundary turns. Restructure readClaudeCliFallbackSeed to queue summaries into pendingSummary and flush each boundary's pair atomically. A boundary with no preceding summary now correctly falls back to the boundary's own content rather than serving an older summary alongside fresh turns. - Greptile P2 (newest-first break vs sparse coverage): the formatFallbackTurns walk intentionally stops on the first oversized turn so the prelude stays a contiguous "what was happening just before the failure" window. Document the design choice inline so a future maintainer doesn't reflexively change it to skip-and-continue. Tests: - New gateway cases for the boundary-without-summary edge case and for trailing summaries written without a paired boundary. - existing 33 attempt-execution + 14 cli-session-history tests still pass; broader src/agents/command suite stays green (63/63).
This commit is contained in:
@@ -189,9 +189,15 @@ function formatFallbackTurns(
|
||||
if (turns.length === 0 || remainingBudget <= 0) {
|
||||
return { text: "", consumed: 0 };
|
||||
}
|
||||
// Walk newest -> oldest, prepending lines until we exceed the budget.
|
||||
// Stops at the oldest turn we can include in full so we never deliver a
|
||||
// truncated mid-turn fragment to the fallback model.
|
||||
// Walk newest -> oldest, prepending lines until one does not fit.
|
||||
//
|
||||
// We stop on the FIRST oversized turn instead of skipping it and then
|
||||
// continuing into older ones. The fallback prelude is a "most recent
|
||||
// contiguous window" summary — what was happening just before the
|
||||
// failed attempt — so a non-contiguous slice (newest + something from
|
||||
// 20 turns ago, gap in the middle) would mislead the fallback model
|
||||
// about the actual flow. Sparse coverage is worse than fewer turns:
|
||||
// greptile flagged this as a P2 on #72069; behavior is intentional.
|
||||
const lines: string[] = [];
|
||||
let consumed = 0;
|
||||
for (let i = turns.length - 1; i >= 0; i -= 1) {
|
||||
@@ -209,9 +215,6 @@ function formatFallbackTurns(
|
||||
}
|
||||
const line = `${role}: ${text}`;
|
||||
if (consumed + line.length + 1 > remainingBudget) {
|
||||
// Skip this turn rather than chop it; if even the most recent turn
|
||||
// is too large to include cleanly, stop emitting (the prelude is a
|
||||
// best-effort sketch, not a transcript).
|
||||
break;
|
||||
}
|
||||
lines.unshift(line);
|
||||
|
||||
@@ -234,6 +234,15 @@ export async function persistCliTurnTranscript(params: {
|
||||
export function runAgentAttempt(params: {
|
||||
providerOverride: string;
|
||||
modelOverride: string;
|
||||
/**
|
||||
* The provider the user originally requested for this turn (i.e. the
|
||||
* primary candidate of the fallback chain). Used to scope claude-cli
|
||||
* fallback context seeding to chains that actually started on claude-cli;
|
||||
* a stale `cliSessionBindings["claude-cli"]` from an unrelated past run
|
||||
* must not contaminate fallbacks that started on another provider
|
||||
* (Codex review #72069 P1).
|
||||
*/
|
||||
originalProvider: string;
|
||||
cfg: OpenClawConfig;
|
||||
sessionEntry: SessionEntry | undefined;
|
||||
sessionId: string;
|
||||
@@ -267,8 +276,15 @@ export function runAgentAttempt(params: {
|
||||
// Harvest a compacted context (Claude's own `/compact` summary plus the
|
||||
// most recent post-boundary turns) and prepend it to the retry prompt.
|
||||
// This mirrors what Claude Code itself replays after compaction.
|
||||
//
|
||||
// Gate explicitly on `originalProvider === "claude-cli"`: if the user-
|
||||
// requested provider for this run was not claude-cli, any claude-cli
|
||||
// session binding on the entry is stale state from an earlier run and
|
||||
// must not bleed into this fallback chain.
|
||||
const claudeCliFallbackPrelude =
|
||||
params.isFallbackRetry && !isClaudeCliProvider(params.providerOverride)
|
||||
params.isFallbackRetry &&
|
||||
isClaudeCliProvider(params.originalProvider) &&
|
||||
!isClaudeCliProvider(params.providerOverride)
|
||||
? buildClaudeCliFallbackContextPrelude({
|
||||
cliSessionId: getCliSessionBinding(params.sessionEntry, "claude-cli")?.sessionId,
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user