refreshCostUsageCache rewrote the entire .usage-cost-cache.json after every
single scanned session. With ~8190 stale session files and a 108MB cache, that
was O(N * cacheSize) work — sustained CPU burn from repeated 100MB+
JSON.stringify + atomic-replace cycles every refresh.
Checkpoint policy now batches durable writes:
- At most one rewrite per 256 scanned files
- Or one rewrite per 5s of wall time
- Final write only when something actually changed (no-op refresh on a
fully-fresh cache no longer rewrites the file)
Crash safety is preserved: an interrupted refresh still has a recent
checkpoint on disk, and the next run rescans only the unfinished tail
(file size + mtime + pricingFingerprint match).
Validation:
- pnpm vitest run src/infra/session-cost-usage.test.ts (39/39 pass)
- New test 'throttles cache writes during a large stale refresh' confirms
cache renames stay below sessionCount/4 (was ~sessionCount+1) and that
a no-op refresh issues zero cache writes.
- pnpm check:changed (clean)
Beads: openclaw-0zr
* fix: use final-call usage for cron session totals
* fix(cron): preserve usable token snapshots
* fix(cron): derive token snapshots with fallback
* fix(cron): separate session and run token totals
* fix(cron): derive cumulative telemetry totals
* fix(cron): include cached usage in run totals
---------
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
* fix(cron): reject cron expressions with no reachable run time
A structurally valid but never-matching cron expression (for example
"0 0 30 2 *", Feb 30) was accepted at registration and then silently never
fired: the job showed as enabled but never ran, never appeared in the run
log, and was never auto-disabled.
croner constructs these expressions without error and nextRun() returns null,
so computeNextRunAtMs returns undefined with no throw, and the scheduler
treated it as "no work to do".
Add a satisfiability assertion at the shared service validation boundary
(createJob and applyJobPatch) so every create/update path and every caller
(gateway RPC, CLI, and the in-process host-hook and dreaming callers that
bypass the RPC layer) rejects an expression with no upcoming run time.
applyJobPatch only asserts when the patch changes the schedule, so a job that
predates this change can still be disabled or repaired. The thrown message is
classified by the gateway as INVALID_REQUEST.
* fix(cron): validate enabled schedules with service clock
* fix(cron): keep schedule validation service-owned
* fix(cron): validate staggered schedule reachability
---------
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
Gemma 4 models via openai-completions (vLLM, OpenRouter, etc.) were
silently losing in-turn reasoning_content during multi-turn tool replay.
Unlike DeepSeek/Xiaomi which 400 on the missing field, these providers
accept the request but produce degraded tool-call quality — arguments
collapse to {} and identical tool calls repeat.
Root cause: two places explicitly excluded Gemma 4 from reasoning replay:
1. shouldTrustReasoningContentReplayMetadata() in openai-transport-stream
skipped Gemma 4 by model ID, causing sanitizeCompletionsReasoningReplayFields
to strip reasoning_content before sending.
2. buildOpenAICompatibleReplayPolicy() in provider-replay-helpers forced
dropReasoningFromHistory=true for Gemma 4, discarding reasoning blocks
during history sanitization before they reached the transport layer.
Remove both exclusions. Gemma 4 on non-OpenAI providers now preserves
in-turn reasoning through the existing provider/metadata checks that
already gate this correctly for other reasoning models.
Co-authored-by: wangyk <prowangyankun@foxmail.com>
* fix(talk): show OpenAI Realtime WebRTC assistant transcripts
Handle current OpenAI Realtime assistant transcript events on the direct
WebRTC transport (response.output_audio_transcript.delta/done,
response.output_text.*), keep legacy response.audio_transcript.*
compatibility, and dedup duplicate assistant bubbles in the shared
conversation aggregation so both webrtc and gateway-relay benefit.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(talk): drop unsafe transcript rewrite heuristic
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
Verify imported auth profiles and auth runtime state can be read back from the canonical SQLite store before doctor removes legacy JSON auth files.
The migration now keeps legacy JSON in place with a warning when SQLite verification misses expected imported data, avoiding source cleanup before durable readback succeeds.
Verification:
- `scripts/pr review-tests 91740 src/commands/doctor-auth-flat-profiles.test.ts`
- `./node_modules/.bin/oxfmt --check src/commands/doctor-auth-flat-profiles.ts src/commands/doctor-auth-flat-profiles.test.ts`
- `./node_modules/.bin/oxlint src/commands/doctor-auth-flat-profiles.ts src/commands/doctor-auth-flat-profiles.test.ts`
- `scripts/pr-prepare gates 91740` passed `pnpm build` and `pnpm check`; broad local `pnpm test` failed in unrelated baseline shards and was explicitly waived by maintainer.
- Real behavior proof: https://github.com/openclaw/openclaw/actions/runs/27285927515/job/80593035530
Co-authored-by: fuller-stack-dev <263060202+fuller-stack-dev@users.noreply.github.com>
Co-authored-by: Radek Sienkiewicz <velvet-shark@users.noreply.github.com>
Fix QMD watcher ignore handling for explicitly configured roots whose directory names are normally ignored, and prefer the most-specific configured watch root for overlapping collections.
Validated with focused QMD/tooling tests, full core support boundary tests, green CI, and ClawSweeper re-review.
Move Telegram inbound authorization into a single pre-cache gate so unauthorized DM text is never recorded into the reply-chain cache or dispatch-dedupe state before allowlist/pairing checks run. The gate covers fresh messages, edited messages, group edits, and edited channel posts; edits are authorized silently and never trigger pairing challenges, and requireTopic root DMs are dropped before pairing challenges.
Fixes#91209.
Thanks @sallyom!