Skip to content

fix(desktop): align usage activity with model calls - #3697

Merged
M4n5ter merged 6 commits into
apache:mainfrom
liuxiaocs7:fix/align-usage-request-counts
Aug 27, 2026
Merged

fix(desktop): align usage activity with model calls#3697
M4n5ter merged 6 commits into
apache:mainfrom
liuxiaocs7:fix/align-usage-request-counts

Conversation

@liuxiaocs7

Copy link
Copy Markdown
Member

Summary

  • source Usage Statistics from the selected Runtime Host's canonical Usage authority, matching Daily Review's model-call accounting
  • label the combined model/tool table as an activity log and preserve aborted or unknown fields
  • load every model and tool activity page and discard stale results when the selected Host or range changes
  • seed canonical Usage records for the Desktop fixture and add regression coverage for the 151 model calls + 171 tool calls case

Fixes #3695
Refs #2128

Verification

  • node --test apps/desktop/dist/main/__tests__/runtime-host-usage-ipc-main.test.js apps/desktop/dist/main/__tests__/desktop-session-projection.test.js apps/desktop/dist/main/__tests__/settings-resource-state.test.js apps/desktop/dist/main/__tests__/runtime-host-settings-generation.test.js — 28 passed
  • npm --workspace @maka/desktop run typecheck — passed
  • npx biome check <16 changed files> — passed
  • npm --workspace @maka/desktop run build:renderer — passed
  • npm --workspace @maka/desktop run build-storybook — passed
  • node scripts/asf-license-headers.mjs check — the changed files pass; the repository-wide command reports only pre-existing untracked docs/contribution/ files, which are not part of this PR

AI use

Select exactly one:

  • No generative tool made a substantive contribution
  • Generative tooling made a substantive contribution

Tool(s) and scope: OpenAI Codex diagnosed the divergent Usage data paths, implemented the Runtime Host adapter and UI/type updates, resolved the rebase conflict, and added regression tests and fixture coverage.

Checklist

  • Tests cover the change and fail without it
  • Lint, format, typecheck and the affected suites pass locally

Does this PR entail a change in behavior?

  • Yes — described under Summary above
  • No

@liuxiaocs7
liuxiaocs7 force-pushed the fix/align-usage-request-counts branch 2 times, most recently from 55cb537 to d56b43d Compare August 25, 2026 05:49
@Astro-Han

Copy link
Copy Markdown
Contributor

Sourcing Usage from the Host authority is the right move, and the pagination guards (offset echo, strictly advancing nextOffset, last-page rows.length === total) are solid. Nothing below blocks the merge.

[P2] Loading every page costs the Host O(N²)

usage-pricing-coordinator.ts:161 reads telemetry.logs(input.query, 0, offset + limit) — every page re-reads the whole prefix. Under it, sqlite-usage-store.ts:326 readToolRows() takes no arguments and runs SELECT record_json FROM usage_tool_invocations with no WHERE, decoding the entire tool table per page. Each page also runs one catchUpModelCallProjection repair pass.

At 10k records that is 100 pages × tens of thousands of JSON.parse + decode. The previous implementation read session files once in the Desktop main process, off the Host entirely. I have not measured whether this actually blocks concurrent Host requests, so I am not claiming that part — the read amplification alone is enough to want a range-export read instead of paging.

Related: MAX_ACTIVITY_RECORDS (:51) throws after all that work is paid, and invalidUsageProjection() says "Runtime Host returned an invalid Usage projection" for what is a client-side cap. A truncate-and-say-so would serve better than an error, and paging should stop at the cap rather than past it. That branch has no test — MAX_ACTIVITY_RECORDS appears zero times in runtime-host-usage-ipc-main.test.ts.

[P2] provenance is dropped, so cost can read low with no signal

loadUsageStats keeps only the numeric summary fields. But usage-ledger-merge.ts is explicit that unreadable records are "Real calls whose cost is now unknown and is missing from the totals" and pending repairs "missing from these numbers".

Session Inspector already handles this: estimatedSessionCost refuses to treat totalCostUsd as authoritative when pricedAttempts === 0, and hasUnavailableSessionUsage flags unreadable/pending. Usage Statistics now shows a lower number than Session Inspector for the same data, and there is no way to tell.

[P2] The fixture comment says canonical; the fixture writes legacy

scenarios-usage.ts:30 is new in this PR:

// while \usageStatsRecords` seeds the Runtime Host's canonical usage surface.`

But e2e-fixture.ts:253-255 only calls usage.telemetry.recordLlmCall and recordToolInvocation — the legacy telemetry tables. The record types are literally Parameters<TelemetryIndexWriter['recordLlmCall']>[0]. No canonical model-call ledger is seeded, so every E2E and Storybook screenshot goes down the legacy branch of mergeUsageLogs.

The headline claim of this PR is canonical alignment, and nothing end-to-end exercises it. The unit tests do not either — the client is replaced wholesale (as unknown as DesktopRuntimeHostClient, three sites), so "151 model calls" is a number the test feeds the adapter, proving it pages correctly rather than that the Host reports 151.

[P2] After a reconnect the page silently shows zero

selectTarget(key, epoch) returns true on an epoch change and clears state via setUsageStats(null) (settings-surface.tsx:706-712). But the refetch effect depends on selectedRuntimeHostKey (:781), and runtimeHostSettingsKey is ${profileId}:${hostId} — no epoch. Reconnect to the same Host process (hostId stable, epoch+1) and the state is cleared without a refetch.

usage-settings-page.tsx:172-176 then renders stats?.summary.totalCostUsd ?? 0, so the cards read $0.00 and "0 model calls" — indistinguishable from real data. Needs a manual refresh to recover. [P3] The same ?? 0 also makes every range switch flash zeros, since there is no loading state and the render gate requires usageStats.range === settings.usage.range.

[P3] Summary and logs are read concurrently, then hard-asserted

Promise.all over four reads, then summaryResult.summary.totalRequests !== llmLogs.length throws. All three Host reads independently trigger catchUpModelCallProjection. After a Host restart with pending repairs, the summary read can land before the catch-up commits and the logs read after — counts disagree, the whole page errors, four cards show 0. A manual refresh fixes it, so this is minor, but the first open after a restart is the common case for it. Degrading (use the logs count, label it) beats erroring.

[P3] Provider grouping changed meaning, and the fixture hides it

aggregateModelLogs groups on log.providerId, which production writes as target.connection.providerType (execution-model-authority.ts:496). The old path grouped on provider: header.llmConnectionSlug (usage-stats-store.ts:96). Two connections to the same provider now collapse into one row — and LlmUsageLogProjection.connectionSlug exists but is discarded.

The fixture writes providerId: session.llmConnectionSlug, so the screenshots look identical to the old behaviour. Either say so in the description or keep grouping on connectionSlug; right now the change is invisible to review.


Reviewed with Claude Code; I verified each finding against production code and set the severities.

简体中文

把 Usage 的来源换成 Host 权威是对的方向,分页守卫(offset 回显、nextOffset 严格递增、末页 rows.length === total)也写得扎实。下面几条都不阻塞合并。

[P2] 加载全部分页在 Host 端是 O(N²)

usage-pricing-coordinator.ts:161telemetry.logs(input.query, 0, offset + limit)——每翻一页重读整个前缀。下面一层 sqlite-usage-store.ts:326readToolRows() 不接参数,SELECT record_json FROM usage_tool_invocations 连 WHERE 都没有,每页解码整张工具表。每页还额外跑一次 catchUpModelCallProjection 修复 pass。

1 万条记录就是 100 页 × 数万次 JSON.parse + decode。改动前是 Desktop 主进程一次性读会话文件,完全不占 Host。我没有实测它是否真的阻塞 Host 的并发请求,所以不主张那一点——单是读放大就足以让人想要一个区间导出读法,而不是翻页。

相关:MAX_ACTIVITY_RECORDS:51)是在付完全部代价之后才抛,而 invalidUsageProjection() 的文案是「Runtime Host returned an invalid Usage projection」——这是客户端自设的上限。截断并说明会比报错更好,翻页也该在上限处停下而不是越过它。这条分支没有测试:MAX_ACTIVITY_RECORDSruntime-host-usage-ipc-main.test.ts 里出现零次。

[P2] 丢掉 provenance,费用可能偏低且无任何提示

loadUsageStats 只保留了 summary 的数值字段。而 usage-ledger-merge.ts 写得很明确:unreadable 记录是「Real calls whose cost is now unknown and is missing from the totals」,pendingRepairs 的花费「missing from these numbers」。

Session Inspector 已经按这个处理了:estimatedSessionCostpricedAttempts === 0 时拒绝把 totalCostUsd 当权威,hasUnavailableSessionUsage 在 unreadable/pending 非零时打标。现在同一份数据,使用统计显示的数字会低于 Session Inspector,而且看不出来。

[P2] fixture 的注释说 canonical,实现写的是 legacy

scenarios-usage.ts:30 是本 PR 新增的一行,说 usageStatsRecords seed 了 Host 的 canonical usage surface。但 e2e-fixture.ts:253-255 只调了 usage.telemetry.recordLlmCallrecordToolInvocation——都是 legacy telemetry 表,记录类型本身就是 Parameters<TelemetryIndexWriter['recordLlmCall']>[0]。没有 seed 任何 canonical model-call ledger,所以全部 E2E 和 Storybook 截图走的都是 mergeUsageLogs 的 legacy 分支。

这个 PR 的核心主张是 canonical 对齐,而端到端没有任何东西在验证它。单测同样不验证——client 被整体替换(as unknown as DesktopRuntimeHostClient,三处),所以「151 model calls」是测试喂给适配器的数字,证明的是翻页正确,不是 Host 会报 151。

[P2] 重连之后页面静默显示零

selectTarget(key, epoch) 在 epoch 变化时返回 true 并 setUsageStats(null)settings-surface.tsx:706-712)。但重拉的 effect 依赖 selectedRuntimeHostKey:781),而 runtimeHostSettingsKey${profileId}:${hostId},不含 epoch。重连到同一个 Host 进程(hostId 不变、epoch+1)→ 状态被清空但不会重拉。

usage-settings-page.tsx:172-176 随后渲染 stats?.summary.totalCostUsd ?? 0,卡片显示 $0.00 和「0 次模型调用」——和真实数据无法区分,要手动刷新才能恢复。[P3] 同一个 ?? 0 也让每次切换时间范围都闪一下零:没有加载态,而渲染门控要求 usageStats.range === settings.usage.range

[P3] summary 和 logs 并发读,然后硬断言

Promise.all 四路并发,随后 summaryResult.summary.totalRequests !== llmLogs.length 直接抛。三个 Host 读都会各自触发 catchUpModelCallProjection。Host 重启后存在 pendingRepairs 时,summary 的读可能落在 catch-up 提交之前、logs 的读落在之后——计数不等,整页报错,四个卡片全 0。手动刷新能好,所以不严重,但重启后第一次打开正是它的常见场景。降级处理(用 logs 的计数并标注)比报错好。

[P3] 供应商分组的含义变了,而 fixture 掩盖了它

aggregateModelLogslog.providerId 分组,生产写入的是 target.connection.providerTypeexecution-model-authority.ts:496)。旧路径按 provider: header.llmConnectionSlug 分组(usage-stats-store.ts:96)。同一个 provider 的两个连接现在会合并成一行——而 LlmUsageLogProjection.connectionSlug 明明存在却被丢弃。

fixture 写的是 providerId: session.llmConnectionSlug,所以截图和旧行为看起来一模一样。要么在描述里说明,要么保持按 connectionSlug 分组;现在这个变化在评审中是隐形的。


本次评审用了 Claude Code 辅助;每条我都在生产代码里核对过,严重程度由我判定。

liuxiaocs7 added a commit to liuxiaocs7/maka that referenced this pull request Aug 25, 2026
…tch, degrade, canonical fixture)

Follow-up to the Astro-Han review on apache#3697. Findings addressed:

- Provenance is no longer dropped: loadUsageStats carries the canonical
  summary provenance onto UsageStats, and the page mirrors Session
  Inspector — the total-cost card shows "cost unavailable" instead of a
  misleading $0.00 when nothing was priced, and an incompleteness banner
  appears when records are unreadable/pending or the log was truncated.
  Shared estimatedUsageCost/hasUnavailableUsage helpers live in core.

- Reconnect no longer shows a silent zero: the usage refetch effect and
  the stored/gating key now include the selected Host's lifecycle epoch,
  so a same-hostId in-place replacement refetches instead of clearing to
  a stale null. Summary cards render an em dash while unloaded rather
  than fabricating 0 / $0.00.

- Degrade instead of erroring: a summary/logs count mismatch (e.g. a
  catch-up race after a Host restart) keeps the canonical total and marks
  the activity list incomplete rather than throwing the whole page.

- Provider breakdown groups by connection slug, not raw provider type, so
  two connections to the same provider stay two rows.

- MAX_ACTIVITY_RECORDS truncates with a logsTruncated signal instead of
  raising "invalid Usage projection"; paging stops at the cap.

- Per-page catch-up removed: readCanonicalUsage only repairs on the first
  page (summary always repairs, so provenance stays honest), cutting a
  projection write per activity page.

- E2E fixture now seeds the CANONICAL model-call ledger via AgentRun
  model_call_attempt_recorded events (tools stay on legacy telemetry), so
  the canonical merge branch is actually exercised end to end; the
  misleading "canonical" comment is corrected.

Tests: rewrite the reconcile test to assert graceful degrade; add
truncation, provenance-passthrough, and connection-grouping cases; add
core coverage for the new provenance presentation helpers.

Note: the O(N^2) store read is only partially mitigated here (first-page
catch-up + cap). Pushing pagination into SQL is left as a follow-up.
@liuxiaocs7
liuxiaocs7 force-pushed the fix/align-usage-request-counts branch from 2ba51cf to 39a66f0 Compare August 25, 2026 14:39
liuxiaocs7 added a commit to liuxiaocs7/maka that referenced this pull request Aug 25, 2026
…tch, degrade, canonical fixture)

Follow-up to the Astro-Han review on apache#3697. Findings addressed:

- Provenance is no longer dropped: loadUsageStats carries the canonical
  summary provenance onto UsageStats, and the page mirrors Session
  Inspector — the total-cost card shows "cost unavailable" instead of a
  misleading $0.00 when nothing was priced, and an incompleteness banner
  appears when records are unreadable/pending or the log was truncated.
  Shared estimatedUsageCost/hasUnavailableUsage helpers live in core.

- Reconnect no longer shows a silent zero: the usage refetch effect and
  the stored/gating key now include the selected Host's lifecycle epoch,
  so a same-hostId in-place replacement refetches instead of clearing to
  a stale null. Summary cards render an em dash while unloaded rather
  than fabricating 0 / $0.00.

- Degrade instead of erroring: a summary/logs count mismatch (e.g. a
  catch-up race after a Host restart) keeps the canonical total and marks
  the activity list incomplete rather than throwing the whole page.

- Provider breakdown groups by connection slug, not raw provider type, so
  two connections to the same provider stay two rows.

- MAX_ACTIVITY_RECORDS truncates with a logsTruncated signal instead of
  raising "invalid Usage projection"; paging stops at the cap.

- Per-page catch-up removed: readCanonicalUsage only repairs on the first
  page (summary always repairs, so provenance stays honest), cutting a
  projection write per activity page.

- E2E fixture now seeds the CANONICAL model-call ledger via AgentRun
  model_call_attempt_recorded events (tools stay on legacy telemetry), so
  the canonical merge branch is actually exercised end to end; the
  misleading "canonical" comment is corrected.

Tests: rewrite the reconcile test to assert graceful degrade; add
truncation, provenance-passthrough, and connection-grouping cases; add
core coverage for the new provenance presentation helpers.

Note: the O(N^2) store read is only partially mitigated here (first-page
catch-up + cap). Pushing pagination into SQL is left as a follow-up.
@liuxiaocs7
liuxiaocs7 force-pushed the fix/align-usage-request-counts branch from 39a66f0 to 63cd47c Compare August 25, 2026 15:22
M4n5ter pushed a commit to liuxiaocs7/maka that referenced this pull request Aug 26, 2026
…tch, degrade, canonical fixture)

Follow-up to the Astro-Han review on apache#3697. Findings addressed:

- Provenance is no longer dropped: loadUsageStats carries the canonical
  summary provenance onto UsageStats, and the page mirrors Session
  Inspector — the total-cost card shows "cost unavailable" instead of a
  misleading $0.00 when nothing was priced, and an incompleteness banner
  appears when records are unreadable/pending or the log was truncated.
  Shared estimatedUsageCost/hasUnavailableUsage helpers live in core.

- Reconnect no longer shows a silent zero: the usage refetch effect and
  the stored/gating key now include the selected Host's lifecycle epoch,
  so a same-hostId in-place replacement refetches instead of clearing to
  a stale null. Summary cards render an em dash while unloaded rather
  than fabricating 0 / $0.00.

- Degrade instead of erroring: a summary/logs count mismatch (e.g. a
  catch-up race after a Host restart) keeps the canonical total and marks
  the activity list incomplete rather than throwing the whole page.

- Provider breakdown groups by connection slug, not raw provider type, so
  two connections to the same provider stay two rows.

- MAX_ACTIVITY_RECORDS truncates with a logsTruncated signal instead of
  raising "invalid Usage projection"; paging stops at the cap.

- Per-page catch-up removed: readCanonicalUsage only repairs on the first
  page (summary always repairs, so provenance stays honest), cutting a
  projection write per activity page.

- E2E fixture now seeds the CANONICAL model-call ledger via AgentRun
  model_call_attempt_recorded events (tools stay on legacy telemetry), so
  the canonical merge branch is actually exercised end to end; the
  misleading "canonical" comment is corrected.

Tests: rewrite the reconcile test to assert graceful degrade; add
truncation, provenance-passthrough, and connection-grouping cases; add
core coverage for the new provenance presentation helpers.

Note: the O(N^2) store read is only partially mitigated here (first-page
catch-up + cap). Pushing pagination into SQL is left as a follow-up.
@M4n5ter
M4n5ter force-pushed the fix/align-usage-request-counts branch from 63cd47c to 378f505 Compare August 26, 2026 09:17
M4n5ter pushed a commit to liuxiaocs7/maka that referenced this pull request Aug 26, 2026
…tch, degrade, canonical fixture)

Follow-up to the Astro-Han review on apache#3697. Findings addressed:

- Provenance is no longer dropped: loadUsageStats carries the canonical
  summary provenance onto UsageStats, and the page mirrors Session
  Inspector — the total-cost card shows "cost unavailable" instead of a
  misleading $0.00 when nothing was priced, and an incompleteness banner
  appears when records are unreadable/pending or the log was truncated.
  Shared estimatedUsageCost/hasUnavailableUsage helpers live in core.

- Reconnect no longer shows a silent zero: the usage refetch effect and
  the stored/gating key now include the selected Host's lifecycle epoch,
  so a same-hostId in-place replacement refetches instead of clearing to
  a stale null. Summary cards render an em dash while unloaded rather
  than fabricating 0 / $0.00.

- Degrade instead of erroring: a summary/logs count mismatch (e.g. a
  catch-up race after a Host restart) keeps the canonical total and marks
  the activity list incomplete rather than throwing the whole page.

- Provider breakdown groups by connection slug, not raw provider type, so
  two connections to the same provider stay two rows.

- MAX_ACTIVITY_RECORDS truncates with a logsTruncated signal instead of
  raising "invalid Usage projection"; paging stops at the cap.

- Per-page catch-up removed: readCanonicalUsage only repairs on the first
  page (summary always repairs, so provenance stays honest), cutting a
  projection write per activity page.

- E2E fixture now seeds the CANONICAL model-call ledger via AgentRun
  model_call_attempt_recorded events (tools stay on legacy telemetry), so
  the canonical merge branch is actually exercised end to end; the
  misleading "canonical" comment is corrected.

Tests: rewrite the reconcile test to assert graceful degrade; add
truncation, provenance-passthrough, and connection-grouping cases; add
core coverage for the new provenance presentation helpers.

Note: the O(N^2) store read is only partially mitigated here (first-page
catch-up + cap). Pushing pagination into SQL is left as a follow-up.
@M4n5ter
M4n5ter force-pushed the fix/align-usage-request-counts branch from 378f505 to 55e523b Compare August 26, 2026 09:39
M4n5ter pushed a commit to liuxiaocs7/maka that referenced this pull request Aug 26, 2026
…tch, degrade, canonical fixture)

Follow-up to the Astro-Han review on apache#3697. Findings addressed:

- Provenance is no longer dropped: loadUsageStats carries the canonical
  summary provenance onto UsageStats, and the page mirrors Session
  Inspector — the total-cost card shows "cost unavailable" instead of a
  misleading $0.00 when nothing was priced, and an incompleteness banner
  appears when records are unreadable/pending or the log was truncated.
  Shared estimatedUsageCost/hasUnavailableUsage helpers live in core.

- Reconnect no longer shows a silent zero: the usage refetch effect and
  the stored/gating key now include the selected Host's lifecycle epoch,
  so a same-hostId in-place replacement refetches instead of clearing to
  a stale null. Summary cards render an em dash while unloaded rather
  than fabricating 0 / $0.00.

- Degrade instead of erroring: a summary/logs count mismatch (e.g. a
  catch-up race after a Host restart) keeps the canonical total and marks
  the activity list incomplete rather than throwing the whole page.

- Provider breakdown groups by connection slug, not raw provider type, so
  two connections to the same provider stay two rows.

- MAX_ACTIVITY_RECORDS truncates with a logsTruncated signal instead of
  raising "invalid Usage projection"; paging stops at the cap.

- Per-page catch-up removed: readCanonicalUsage only repairs on the first
  page (summary always repairs, so provenance stays honest), cutting a
  projection write per activity page.

- E2E fixture now seeds the CANONICAL model-call ledger via AgentRun
  model_call_attempt_recorded events (tools stay on legacy telemetry), so
  the canonical merge branch is actually exercised end to end; the
  misleading "canonical" comment is corrected.

Tests: rewrite the reconcile test to assert graceful degrade; add
truncation, provenance-passthrough, and connection-grouping cases; add
core coverage for the new provenance presentation helpers.

Note: the O(N^2) store read is only partially mitigated here (first-page
catch-up + cap). Pushing pagination into SQL is left as a follow-up.
@M4n5ter
M4n5ter force-pushed the fix/align-usage-request-counts branch from 55e523b to 38b0e9b Compare August 26, 2026 09:46
M4n5ter pushed a commit to liuxiaocs7/maka that referenced this pull request Aug 26, 2026
…tch, degrade, canonical fixture)

Follow-up to the Astro-Han review on apache#3697. Findings addressed:

- Provenance is no longer dropped: loadUsageStats carries the canonical
  summary provenance onto UsageStats, and the page mirrors Session
  Inspector — the total-cost card shows "cost unavailable" instead of a
  misleading $0.00 when nothing was priced, and an incompleteness banner
  appears when records are unreadable/pending or the log was truncated.
  Shared estimatedUsageCost/hasUnavailableUsage helpers live in core.

- Reconnect no longer shows a silent zero: the usage refetch effect and
  the stored/gating key now include the selected Host's lifecycle epoch,
  so a same-hostId in-place replacement refetches instead of clearing to
  a stale null. Summary cards render an em dash while unloaded rather
  than fabricating 0 / $0.00.

- Degrade instead of erroring: a summary/logs count mismatch (e.g. a
  catch-up race after a Host restart) keeps the canonical total and marks
  the activity list incomplete rather than throwing the whole page.

- Provider breakdown groups by connection slug, not raw provider type, so
  two connections to the same provider stay two rows.

- MAX_ACTIVITY_RECORDS truncates with a logsTruncated signal instead of
  raising "invalid Usage projection"; paging stops at the cap.

- Per-page catch-up removed: readCanonicalUsage only repairs on the first
  page (summary always repairs, so provenance stays honest), cutting a
  projection write per activity page.

- E2E fixture now seeds the CANONICAL model-call ledger via AgentRun
  model_call_attempt_recorded events (tools stay on legacy telemetry), so
  the canonical merge branch is actually exercised end to end; the
  misleading "canonical" comment is corrected.

Tests: rewrite the reconcile test to assert graceful degrade; add
truncation, provenance-passthrough, and connection-grouping cases; add
core coverage for the new provenance presentation helpers.

Note: the O(N^2) store read is only partially mitigated here (first-page
catch-up + cap). Pushing pagination into SQL is left as a follow-up.
@M4n5ter
M4n5ter force-pushed the fix/align-usage-request-counts branch from 38b0e9b to aa2e5f1 Compare August 26, 2026 09:53
liuxiaocs7 added a commit to liuxiaocs7/maka that referenced this pull request Aug 26, 2026
…tch, degrade, canonical fixture)

Follow-up to the Astro-Han review on apache#3697. Findings addressed:

- Provenance is no longer dropped: loadUsageStats carries the canonical
  summary provenance onto UsageStats, and the page mirrors Session
  Inspector — the total-cost card shows "cost unavailable" instead of a
  misleading $0.00 when nothing was priced, and an incompleteness banner
  appears when records are unreadable/pending or the log was truncated.
  Shared estimatedUsageCost/hasUnavailableUsage helpers live in core.

- Reconnect no longer shows a silent zero: the usage refetch effect and
  the stored/gating key now include the selected Host's lifecycle epoch,
  so a same-hostId in-place replacement refetches instead of clearing to
  a stale null. Summary cards render an em dash while unloaded rather
  than fabricating 0 / $0.00.

- Degrade instead of erroring: a summary/logs count mismatch (e.g. a
  catch-up race after a Host restart) keeps the canonical total and marks
  the activity list incomplete rather than throwing the whole page.

- Provider breakdown groups by connection slug, not raw provider type, so
  two connections to the same provider stay two rows.

- MAX_ACTIVITY_RECORDS truncates with a logsTruncated signal instead of
  raising "invalid Usage projection"; paging stops at the cap.

- Per-page catch-up removed: readCanonicalUsage only repairs on the first
  page (summary always repairs, so provenance stays honest), cutting a
  projection write per activity page.

- E2E fixture now seeds the CANONICAL model-call ledger via AgentRun
  model_call_attempt_recorded events (tools stay on legacy telemetry), so
  the canonical merge branch is actually exercised end to end; the
  misleading "canonical" comment is corrected.

Tests: rewrite the reconcile test to assert graceful degrade; add
truncation, provenance-passthrough, and connection-grouping cases; add
core coverage for the new provenance presentation helpers.

Note: the O(N^2) store read is only partially mitigated here (first-page
catch-up + cap). Pushing pagination into SQL is left as a follow-up.
@liuxiaocs7
liuxiaocs7 force-pushed the fix/align-usage-request-counts branch from f8d35be to 6989087 Compare August 26, 2026 12:13
@M4n5ter

M4n5ter commented Aug 27, 2026

Copy link
Copy Markdown
Member

Relationship note: PRs #3697 and #3761 both close #3695 and overlap in the Usage Statistics migration, including runtime-host-usage-ipc-main.ts, the preload bridge, the Usage Settings UI/copy/navigation/surface, and packages/core/src/settings.ts. PR #3697 is broader (21 changed files); #3761 is narrower (14 changed files) and also contains the Result-unwrapping/session-effect changes. Their bodies and comments do not say whether #3761 supersedes #3697, stacks on it, or is an independent alternative. Please document the intended relationship, including which PR owns the shared files and issue closure, before merging either path.

This comment records the relationship only; it is not a merge decision.


Automated review note posted by @未开智选手. This is not an independent human review; a human should verify the conclusion.

Read usage summary and activity from the Runtime Host canonical usage authority so Usage Statistics and Daily Review share the same model-call count. Keep tool calls visible as separately labelled activity and page both sources completely.

Generated-by: OpenAI Codex
The UsageLongTail/UsageNarrow play asserted findByRole('tab', { name:
usageCopy.tabs[0] }) with an exact string. The tab renders a count badge
via endContent, which Astryx folds into the accessible name (e.g.
'活动记录 5'), so the exact match never resolved and the storybook
render smoke failed both stories. Match the label as a prefix RegExp,
consistent with how accessibility-coverage queries workbar tabs.
The UsageLongTail/UsageNarrow play asserted findByRole('tab', ...), but
Astryx's TabList renders a <nav> of <button> tabs — there is no ARIA
`tab` role anywhere (its own TabList tests reach tabs via getByRole
'button'). So the query never matched, regardless of the name matcher;
the earlier prefix-RegExp fix only touched the name and left the wrong
role in place, so both stories kept failing the render smoke.

Query the tab by `button` with the same label-prefix RegExp (the count
badge folds into the accessible name after the label, e.g. '活动记录 5').
Verified against @astryxdesign/core@0.4.5: TabList => role navigation,
Tab => role button, zero role="tab"; getByRole('button', {name:
/^活动记录/}) uniquely resolves the requests tab.
…tch, degrade, canonical fixture)

Follow-up to the Astro-Han review on apache#3697. Findings addressed:

- Provenance is no longer dropped: loadUsageStats carries the canonical
  summary provenance onto UsageStats, and the page mirrors Session
  Inspector — the total-cost card shows "cost unavailable" instead of a
  misleading $0.00 when nothing was priced, and an incompleteness banner
  appears when records are unreadable/pending or the log was truncated.
  Shared estimatedUsageCost/hasUnavailableUsage helpers live in core.

- Reconnect no longer shows a silent zero: the usage refetch effect and
  the stored/gating key now include the selected Host's lifecycle epoch,
  so a same-hostId in-place replacement refetches instead of clearing to
  a stale null. Summary cards render an em dash while unloaded rather
  than fabricating 0 / $0.00.

- Degrade instead of erroring: a summary/logs count mismatch (e.g. a
  catch-up race after a Host restart) keeps the canonical total and marks
  the activity list incomplete rather than throwing the whole page.

- Provider breakdown groups by connection slug, not raw provider type, so
  two connections to the same provider stay two rows.

- MAX_ACTIVITY_RECORDS truncates with a logsTruncated signal instead of
  raising "invalid Usage projection"; paging stops at the cap.

- Per-page catch-up removed: readCanonicalUsage only repairs on the first
  page (summary always repairs, so provenance stays honest), cutting a
  projection write per activity page.

- E2E fixture now seeds the CANONICAL model-call ledger via AgentRun
  model_call_attempt_recorded events (tools stay on legacy telemetry), so
  the canonical merge branch is actually exercised end to end; the
  misleading "canonical" comment is corrected.

Tests: rewrite the reconcile test to assert graceful degrade; add
truncation, provenance-passthrough, and connection-grouping cases; add
core coverage for the new provenance presentation helpers.

Note: the O(N^2) store read is only partially mitigated here (first-page
catch-up + cap). Pushing pagination into SQL is left as a follow-up.
@M4n5ter
M4n5ter force-pushed the fix/align-usage-request-counts branch from 6989087 to 436c934 Compare August 27, 2026 02:46

@M4n5ter M4n5ter left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed exact head 436c934c97dfd0f7ba0ede8d594e2b14e299b1a1 after its rebase onto current main. The six rebased patches are patch-equivalent to the previously published branch. The change removes the legacy local Usage owner, keeps Runtime Host as the single data authority, and preserves incomplete/unpriced provenance instead of fabricating zeroes. The complete repository build, 58 focused tests, and Biome checks passed locally. I found no blocking issue; the inline P2 is a recoverable loading failure and can be fixed as a follow-up.


Posted by an automated review agent operated by @M4n5ter. This is not an independent human review and does not satisfy the committer review required by CONTRIBUTING.md. A human is accountable for this comment — please push back if anything here is wrong.

简体中文

本条评论由 @M4n5ter 运行的自动化审查程序发出。它不构成 CONTRIBUTING.md 所要求的独立人类审查,也不能替代人类审查。有人类对本条评论负责,如有错误请直接指出。

throw invalidUsageProjection();
}
total ??= result.total;
if (result.total !== total) throw invalidUsageProjection();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Please make pagination tolerate a projection catch-up between pages. The query freezes its time range, but recovery can still materialize older model-call records whose timestamps fall inside that range after page 1 has been returned. If the first page reports total = 101 and a pending projection is committed before page 2, the next response reports total = 102; this guard turns the whole Usage load into invalid Usage projection. I reproduced that exact 101→102 sequence through the production IPC handler. Existing tests only cover a stable multi-page total and a one-page summary/log mismatch, so neither reaches this branch. This is recoverable by refreshing and therefore does not block approval, but a revision-pinned page snapshot or one bounded restart of the paged read would prevent the transient failure.


Posted by an automated review agent operated by @M4n5ter. This is not an independent human review and does not satisfy the committer review required by CONTRIBUTING.md. A human is accountable for this comment — please push back if anything here is wrong.

简体中文

本条评论由 @M4n5ter 运行的自动化审查程序发出。它不构成 CONTRIBUTING.md 所要求的独立人类审查,也不能替代人类审查。有人类对本条评论负责,如有错误请直接指出。

@M4n5ter
M4n5ter requested a review from jackwener August 27, 2026 02:48
@github-actions github-actions Bot added the effort/XL Over 1000 readable lines label Aug 27, 2026
@M4n5ter
M4n5ter merged commit 566ed7d into apache:main Aug 27, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

effort/XL Over 1000 readable lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(desktop): align Usage Statistics counts with Daily Review

3 participants