diff --git a/packages/cli/src/serve/routes/usage-stats.ts b/packages/cli/src/serve/routes/usage-stats.ts index 68c29e67d2..e608087f74 100644 --- a/packages/cli/src/serve/routes/usage-stats.ts +++ b/packages/cli/src/serve/routes/usage-stats.ts @@ -8,16 +8,17 @@ * Read-only usage-dashboard surface behind the Web Shell Daemon Status * "统计 / Usage" tab. Serves the selected range's (`today`/`week`/`month`) * flattened token totals plus a trailing per-day heatmap, computed by core's - * `buildUsageDashboard` from the durable local usage history (global `~/.qwen`, - * cross-project) — the same source the TUI `/stats` command reads. The history - * load runs read-only (`persistRebuild: false`), so serving a GET never writes - * to `~/.qwen`, even when the persisted file is missing and the loader falls - * back to transcript replay. + * `buildUsageDashboard` from the local usage history (global `~/.qwen`, + * cross-project). Uses `loadUsageHistoryWithLive`, which unions the durable + * `usage_record.jsonl` (written only by the TUI `/clear` path) with a replay of + * recent transcripts — so daemon / Web Shell sessions and any in-progress + * session are counted here, unlike the TUI `/stats` view. The load is + * read-only (never writes `~/.qwen`). * * Open GET (no `mutate` gate), consistent with `GET /daemon/status` and * `GET /scheduled-tasks`: it exposes only aggregate local usage counts. * - * The heavy step — `loadUsageHistory` can replay every project's transcripts — + * The heavy step — `loadUsageHistoryWithLive` can replay recent transcripts — * is cached once (range-independent), so toggling Today/7D/30D re-aggregates * cheaply from a single disk read instead of re-loading per range, and * concurrent requests coalesce onto the in-flight load. @@ -26,7 +27,7 @@ import type { Application } from 'express'; import { buildUsageDashboard, - loadUsageHistory, + loadUsageHistoryWithLive, type UsageSummaryRecord, } from '@qwen-code/qwen-code-core'; import { writeStderrLine } from '../../utils/stdioHelpers.js'; @@ -67,9 +68,7 @@ export function registerUsageStatsRoutes( app: Application, deps: RegisterUsageStatsRoutesDeps = {}, ): void { - const loadHistory = - deps.loadHistory ?? - (() => loadUsageHistory(undefined, { persistRebuild: false })); + const loadHistory = deps.loadHistory ?? (() => loadUsageHistoryWithLive()); const ttlMs = deps.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS; // Closure-scoped, range-independent history cache (one daemon per process). diff --git a/packages/cli/src/ui/components/AutoAcceptIndicator.test.tsx b/packages/cli/src/ui/components/AutoAcceptIndicator.test.tsx new file mode 100644 index 0000000000..e9f58f49ad --- /dev/null +++ b/packages/cli/src/ui/components/AutoAcceptIndicator.test.tsx @@ -0,0 +1,49 @@ +/** + * @license + * Copyright 2025 Qwen Team + * SPDX-License-Identifier: Apache-2.0 + */ + +import { render } from 'ink-testing-library'; +import { describe, it, expect } from 'vitest'; +import { AutoAcceptIndicator } from './AutoAcceptIndicator.js'; +import { ApprovalMode } from '@qwen-code/qwen-code-core'; + +describe('', () => { + it('renders DEFAULT mode with pause badge and Ask permissions text', () => { + const { lastFrame } = render( + , + ); + const frame = lastFrame()!; + expect(frame).toContain('⏸'); + expect(frame).toContain('Ask permissions'); + }); + + it('renders PLAN mode indicator', () => { + const { lastFrame } = render( + , + ); + expect(lastFrame()).toContain('plan mode'); + }); + + it('renders AUTO_EDIT mode indicator', () => { + const { lastFrame } = render( + , + ); + expect(lastFrame()).toContain('auto-accept edits'); + }); + + it('renders AUTO mode indicator', () => { + const { lastFrame } = render( + , + ); + expect(lastFrame()).toContain('auto mode (classifier-evaluated)'); + }); + + it('renders YOLO mode indicator', () => { + const { lastFrame } = render( + , + ); + expect(lastFrame()).toContain('YOLO mode'); + }); +}); diff --git a/packages/cli/src/ui/components/AutoAcceptIndicator.tsx b/packages/cli/src/ui/components/AutoAcceptIndicator.tsx index 60ffd126a9..caa9234c17 100644 --- a/packages/cli/src/ui/components/AutoAcceptIndicator.tsx +++ b/packages/cli/src/ui/components/AutoAcceptIndicator.tsx @@ -45,6 +45,9 @@ export const AutoAcceptIndicator: React.FC = ({ subText = cycleText; break; case ApprovalMode.DEFAULT: + textContent = `⏸ ${t('Ask permissions')}`; + subText = cycleText; + break; default: break; } diff --git a/packages/cli/src/ui/components/Footer.test.tsx b/packages/cli/src/ui/components/Footer.test.tsx index ba71e4527a..66321a80e3 100644 --- a/packages/cli/src/ui/components/Footer.test.tsx +++ b/packages/cli/src/ui/components/Footer.test.tsx @@ -8,6 +8,7 @@ import { render } from 'ink-testing-library'; import { act } from '@testing-library/react'; import { describe, it, expect, vi, beforeEach } from 'vitest'; import { Footer } from './Footer.js'; +import { ApprovalMode } from '@qwen-code/qwen-code-core'; import * as useTerminalSize from '../hooks/useTerminalSize.js'; import * as useStatusLineModule from '../hooks/useStatusLine.js'; import { type UIState, UIStateContext } from '../contexts/UIStateContext.js'; @@ -233,6 +234,19 @@ describe('