惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

H
Help Net Security
博客园_首页
酷 壳 – CoolShell
酷 壳 – CoolShell
G
Google Developers Blog
Jina AI
Jina AI
C
Check Point Blog
Apple Machine Learning Research
Apple Machine Learning Research
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
GbyAI
GbyAI
Last Week in AI
Last Week in AI
罗磊的独立博客
Hugging Face - Blog
Hugging Face - Blog
阮一峰的网络日志
阮一峰的网络日志
The Cloudflare Blog
M
MIT News - Artificial intelligence
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
J
Java Code Geeks
WordPress大学
WordPress大学
博客园 - 聂微东
月光博客
月光博客
博客园 - 司徒正美
T
The Blog of Author Tim Ferriss
大猫的无限游戏
大猫的无限游戏
Microsoft Security Blog
Microsoft Security Blog

Recent Commits to openclaw:main

test: merge chat side-result checks · openclaw/openclaw@ddd2c2a test: merge cron history checks · openclaw/openclaw@f7eb746 test: merge responsive navigation shell checks · openclaw/openclaw@c2e4b47 docs(changelog): add codex oauth fixes · openclaw/openclaw@628e6cd test: merge navigation routing cases · openclaw/openclaw@5d8cecb Tests: mock channel registry bundled fallback · openclaw/openclaw@2b08233 Secrets: avoid broad web search discovery for single plugin config · openclaw/openclaw@a464f59 test: merge config view browser checks · openclaw/openclaw@20cf511 fix(status): align oauth health with runtime · openclaw/openclaw@eed7116 feat: add macOS screen snapshots for monitor preview (#67954) thanks … · openclaw/openclaw@f377db1 fix: report shared auth scopes in hello-ok (#67810) thanks @BunsDev · openclaw/openclaw@0b6c39b Auto-reply: avoid eager bundled route fallback · openclaw/openclaw@3ea1bf4 Tests: narrow session binding contract setup · openclaw/openclaw@54e4e16 fix(macOS): enable undo/redo in webchat composer text input (#34962) · openclaw/openclaw@00951dc Tests: speed up channel setup promotion · openclaw/openclaw@82b529a Docs: refresh agent instructions · openclaw/openclaw@5775fe2 fix(auth): serialize OAuth refresh across agents to fix #26322 (#67876) · openclaw/openclaw@8e79080 test: allow ollama public surface boundary test · openclaw/openclaw@7d4f1a6 Docs: add test performance guardrails · openclaw/openclaw@89706d3 Tests: restore context-engine usage proof · openclaw/openclaw@e4c4f95 Tests: slim context engine runtime coverage · openclaw/openclaw@74c198f ci: retry failed custom checkouts · openclaw/openclaw@0ee5baf test: trim duplicate provider auth onboarding cases · openclaw/openclaw@1ffc02e matrix: fix sessions_spawn --thread subagent session spawning (#67643) · openclaw/openclaw@1ce2596 test: reduce auth choice fixture churn · openclaw/openclaw@857b9cd test: mock health status config boundaries · openclaw/openclaw@9d5ab4a test: mock onboard config io boundary · openclaw/openclaw@299694d test: mock legacy state plugin boundaries · openclaw/openclaw@2713089 test: mock channel install boundaries · openclaw/openclaw@b945248 test: mock doctor preview channel boundaries · openclaw/openclaw@b1a3ad4
fix(usage): serve usage from durable transcript aggregate...
Marvinthebor · 2026-05-04 · via Recent Commits to openclaw:main

@@ -13,13 +13,14 @@ import type {

1313

SessionModelUsage,

1414

} from "../../infra/session-cost-usage.js";

1515

import {

16-

loadCostUsageSummary,

16+

loadCostUsageSummaryFromCache,

1717

loadSessionLogs,

18-

loadSessionCostSummary,

18+

loadSessionCostSummaryFromCache,

1919

loadSessionUsageTimeSeries,

2020

discoverAllSessions,

2121

resolveExistingUsageSessionFile,

2222

type DiscoveredSession,

23+

type UsageCacheStatus,

2324

} from "../../infra/session-cost-usage.js";

2425

import { parseAgentSessionKey } from "../../routing/session-key.js";

2526

import { resolvePreferredSessionKeyForSessionIdMatches } from "../../sessions/session-id-resolution.js";

@@ -313,6 +314,7 @@ async function discoverAllSessionsForUsage(params: {

313314

agentId: agent.id,

314315

startMs: params.startMs,

315316

endMs: params.endMs,

317+

includeFirstUserMessage: false,

316318

});

317319

return sessions.map((session) => Object.assign({}, session, { agentId: agent.id }));

318320

}),

@@ -328,7 +330,12 @@ async function loadCostUsageSummaryCached(params: {

328330

const cacheKey = `${params.startMs}-${params.endMs}`;

329331

const now = Date.now();

330332

const cached = costUsageCache.get(cacheKey);

331-

if (cached?.summary && cached.updatedAt && now - cached.updatedAt < COST_USAGE_CACHE_TTL_MS) {

333+

if (

334+

cached?.summary &&

335+

cached.updatedAt &&

336+

now - cached.updatedAt < COST_USAGE_CACHE_TTL_MS &&

337+

cached.summary.cacheStatus?.status !== "refreshing"

338+

) {

332339

return cached.summary;

333340

}

334341

@@ -340,13 +347,18 @@ async function loadCostUsageSummaryCached(params: {

340347

}

341348342349

const entry: CostUsageCacheEntry = cached ?? {};

343-

const inFlight = loadCostUsageSummary({

350+

const inFlight = loadCostUsageSummaryFromCache({

344351

startMs: params.startMs,

345352

endMs: params.endMs,

346353

config: params.config,

354+

requestRefresh: true,

355+

refreshMode: "sync-when-empty",

347356

})

348357

.then((summary) => {

349-

setCostUsageCache(cacheKey, { summary, updatedAt: Date.now() });

358+

setCostUsageCache(cacheKey, {

359+

summary,

360+

updatedAt: summary.cacheStatus?.status === "refreshing" ? undefined : Date.now(),

361+

});

350362

return summary;

351363

})

352364

.catch((err) => {

@@ -372,6 +384,28 @@ async function loadCostUsageSummaryCached(params: {

372384

return await inFlight;

373385

}

374386387+

function mergeUsageCacheStatus(

388+

target: UsageCacheStatus | undefined,

389+

source: UsageCacheStatus,

390+

): UsageCacheStatus {

391+

if (!target) {

392+

return { ...source };

393+

}

394+

const statusRank = { fresh: 0, partial: 1, stale: 2, refreshing: 3 } as const;

395+

return {

396+

status: statusRank[source.status] > statusRank[target.status] ? source.status : target.status,

397+

cachedFiles: target.cachedFiles + source.cachedFiles,

398+

pendingFiles: target.pendingFiles + source.pendingFiles,

399+

staleFiles: target.staleFiles + source.staleFiles,

400+

refreshedAt:

401+

target.refreshedAt === undefined

402+

? source.refreshedAt

403+

: source.refreshedAt === undefined

404+

? target.refreshedAt

405+

: Math.max(target.refreshedAt, source.refreshedAt),

406+

};

407+

}

408+375409

// Exposed for unit tests (kept as a single export to avoid widening the public API surface).

376410

export const __test = {

377411

parseDateParts,

@@ -598,6 +632,7 @@ export const usageHandlers: GatewayRequestHandlers = {

598632

{ date: string; count: number; sum: number; min: number; max: number; p95Max: number }

599633

>();

600634

const modelDailyMap = new Map<string, SessionDailyModelUsage>();

635+

let cacheStatus: UsageCacheStatus | undefined;

601636602637

const emptyTotals = (): CostUsageSummary["totals"] => ({

603638

input: 0,

@@ -631,15 +666,18 @@ export const usageHandlers: GatewayRequestHandlers = {

631666632667

for (const merged of limitedEntries) {

633668

const agentId = parseAgentSessionKey(merged.key)?.agentId;

634-

const usage = await loadSessionCostSummary({

669+

const cachedUsage = await loadSessionCostSummaryFromCache({

635670

sessionId: merged.sessionId,

636671

sessionEntry: merged.storeEntry,

637672

sessionFile: merged.sessionFile,

638673

config,

639674

agentId,

640675

startMs,

641676

endMs,

677+

refreshMode: "sync-when-empty",

642678

});

679+

cacheStatus = mergeUsageCacheStatus(cacheStatus, cachedUsage.cacheStatus);

680+

const usage = cachedUsage.summary;

643681644682

if (usage) {

645683

aggregateTotals.input += usage.input;

@@ -843,6 +881,7 @@ export const usageHandlers: GatewayRequestHandlers = {

843881

sessions,

844882

totals: aggregateTotals,

845883

aggregates,

884+

cacheStatus,

846885

};

847886848887

respond(true, result, undefined);