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).
Local default oxlint did not run --type-aware so the warning was missed
on the initial commit; CI surfaced it via check-lint. Hoist the heading
into a named const so its length is read directly without the assertion.
When a claude-cli attempt failed with a fallbackable error (e.g. a 402
billing limit), the next candidate -- typically a non-CLI provider --
ran with no prior conversation context. Claude Code keeps its own
JSONL session under ~/.claude/projects/, but the fallback runner only
sees what OpenClaw assembles from its own transcript, which is empty
for claude-cli sessions. The fallback model therefore behaved as if
the conversation just started, even though Claude later resumed fine.
Resolution mirrors what Claude Code itself does on resume after
compaction: prefer the explicit `/compact` summary, then append the
most recent post-boundary turns up to a char budget. Concretely:
- `readClaudeCliFallbackSeed` (gateway): walks the Claude JSONL with
awareness of `type: "summary"` and `type: "system",
subtype: "compact_boundary"` entries. Pre-boundary turns are dropped
(they are represented by the summary); post-boundary turns become
the recent-window. Multiple compactions are handled by preferring
the latest summary. Path safety reuses the existing
`resolveClaudeCliSessionFilePath` validation.
- `formatClaudeCliFallbackPrelude` / `buildClaudeCliFallbackContext\
Prelude` (agents helpers): format the harvested seed into a labeled
prelude. Tool blocks are coalesced to compact "(tool call: name)" /
"(tool result: …)" hints to keep the prompt budget honest. Newest
turns are kept first when truncating; the summary is clearly
labeled "(truncated)" if it overflows.
- `resolveFallbackRetryPrompt`: gains an optional
`priorContextPrelude` that prepends before the existing retry
marker. Empty/whitespace preludes are ignored; first-attempt prompts
are unchanged.
- `runAgentAttempt`: builds the prelude when `isFallbackRetry === true`
AND the new candidate is non-claude-cli AND a Claude-cli session
binding is present. Same-provider fallbacks (claude-cli to
claude-cli) are unaffected because Claude's own --resume still works.
Verified the new tests (12 in cli-session-history, 12 added to
attempt-execution) catch the regression: removing the prelude prepend
in resolveFallbackRetryPrompt makes both new prelude cases fail,
restoring the original cold-start behavior.
References:
- https://code.claude.com/docs/en/how-claude-code-works
- "Inside Claude Code: The Session File Format"
https://databunny.medium.com/inside-claude-code-the-session-file-format-and-how-to-inspect-it-b9998e66d56b
Clean up local Claude stdio one-shot runs before returning from embedded `openclaw agent --local`, including bundle MCP loopback teardown for local process resources.
Keeps gateway-owned MCP loopback cleanup internal to the Gateway, documents the local-vs-gateway behavior, and aligns the stale OpenAI provider-runtime fixture with the current unsupported Codex mini route.
Keep WebChat runtime context available to the model while persisting only the transcript-facing user prompt across gateway, CLI, queued follow-up, and embedded Pi paths.
Adds regression coverage for history sanitization, CLI transcript persistence, media-only auto-reply prompts, and embedded Pi prompt rewrite against a real SessionManager file.
Co-authored-by: 91wan <91wan@users.noreply.github.com>
Verify Claude CLI session transcripts before reuse and clear phantom bindings with transcript-missing instead of passing stale --resume ids.\n\nFixes #70177.
Persist stale CLI session clearing through the session-store merge path and add regression coverage for Claude binding removal.\n\nThanks @HFConsultant.
The bug: three persist sites accumulated cost instead of snapshotting
it like tokens. This caused cost to be inflated 1x-72x on multi-persist
sessions because the same cumulative usage was added repeatedly.
Root cause: persistSessionUsageUpdate, updateSessionStoreAfterAgentRun,
and the cron isolated-agent run path all used:
estimatedCostUsd = existingCost + runCost
But runCost was already computed from cumulative run usage, so this
added the same cost repeatedly on redundant persists.
Fix: snapshot cost directly like tokens already do:
estimatedCostUsd = runCost
Files affected:
- src/auto-reply/reply/session-usage.ts
- src/agents/command/session-store.ts
- src/cron/isolated-agent/run.ts
Tests added:
- session-store.test.ts: verify cost is snapshotted, not accumulated
- session.test.ts: updated existing test to verify snapshot behavior
Fixes#69347
* fix(agents): preserve session totalTokens when provider omits usage data
Fixes#67667
When a provider (e.g. MiniMax via Anthropic endpoint) does not return
usage data in its API response, hasNonzeroUsage() is false and the
entire totalTokens update block in persistSessionAfterRun is skipped.
This resets totalTokens to undefined, causing /status to show 0%
context usage even after compaction has calculated real token counts.
The fix preserves the previous totalTokens value when the current run
has no usage data, marking it as stale (totalTokensFresh: false) so
display layers know it is from a prior run. This is strictly better
than null — the user sees the last known context usage instead of 0%.
* ci: retrigger after flaky gateway shutdown test
* test(agents): port totalTokens regression test to withTempSessionStore helper post-rebase
* fix(status): surface preserved stale session totals
* fix: surface preserved stale session totals (#67695) (thanks @stainlu)
---------
Co-authored-by: Ayaan Zaidi <hi@obviy.us>