diff --git a/README.md b/README.md index 5b9b45f..be077be 100644 --- a/README.md +++ b/README.md @@ -63,11 +63,11 @@ Click **Context Insights / 上下文洞察** in the sidebar's panel list — the | Section | The question it answers | | --- | --- | -| **KPI band** | How much am I using — sessions, billed tokens, estimated cost, and cache-hit rate over the picked range (7d / 30d / all). | +| **KPI band** | How much am I using — sessions, billed tokens, estimated cost, cache-hit rate, turns, tool runs, and active time over the picked range (7d / 30d / all), each figure folded by the day its step started. | | **7-day usage chart** | When did I spend what — each of the last 7 days' billed tokens and estimated cost as a bar pair, folded per turn off the sessions' own request times (not the session's last-active day). | | **Activity heatmap** | When do I actually work — the last 8 weeks of daily billed tokens; click a day to filter the sessions that were active on it. | | **Settings entry** | One row under the heatmap — the same guarded jump to this plugin's preferences as the Context tab's Plugin Info card. | -| **Context Composition** | Where the context windows went, summed over the range's sessions. | +| **Context Composition** | Where the picked range's billed tokens went, split by the Context tab's composition categories. | | **Session cards** | Each session's profile: composition ring, billed tokens, turns, cost, and its workspace-group / project breadcrumb — sorted by recency, tokens, or context size, searchable, grouped by workspace. A card click opens the session. | diff --git a/README_zh.md b/README_zh.md index 02869e9..c8b35ba 100644 --- a/README_zh.md +++ b/README_zh.md @@ -63,11 +63,11 @@ dsh plugin --profile web update dsh-context@latest | 区块 | 解答的问题 | | --- | --- | -| **KPI 栏** | 我的使用量如何——所选时间范围(7 天 / 30 天 / 全部)内的会话数、计费 Token、预估成本和缓存命中率。 | +| **KPI 栏** | 我的使用量如何——所选时间范围(7 天 / 30 天 / 全部)内的会话数、计费 Token、预估成本、缓存命中率、轮次、工具执行和活跃时长,均按 step 发起日归因。 | | **7 天使用量图表** | 每天的花费和用量如何——最近 7 天每天的计费 Token 和预估成本以成对柱状图展示,按会话自身各请求的时间汇总(而不是按会话最后活跃日期)。 | | **活动热力图** | 我实际在什么时候工作——最近 8 周每日的计费 Token;点击某天即可筛选当天活跃的会话。 | | **设置入口** | 热力图下方的一行快捷入口——与 Context 标签页 Plugin Info 卡片中通往此插件偏好设置的受保护跳转相同。 | -| **Context Composition** | 所选时间范围内各会话的上下文窗口都由什么组成。 | +| **Context Composition** | 所选时间范围内的计费 Token 按 Context 标签页的组成分类拆分。 | | **会话卡片** | 每个会话的概况:组成环形图、计费 Token、轮次、成本以及工作区分组 / 项目路径;可按最近活动、Token 数或上下文大小排序、搜索、按工作区分组。点击卡片即可打开会话。 | ## 📊 Context 标签页 diff --git a/docs/compatibility.md b/docs/compatibility.md index 6fa5a5e..24cfb23 100644 --- a/docs/compatibility.md +++ b/docs/compatibility.md @@ -59,6 +59,7 @@ Last verified: **2026-10-20** (dsh-context `0.73.3`, dsh `0.1.7-rc.2` / `0.2.0-r | `src/client/reserve.ts` | `packages/compaction/compaction-basic/src/config.ts` — only the shipped defaults (`thresholdRatio` 0.8, `headroomTokens` 65536): preset realms make a deployment's policy unreadable; when `contextWindow − reservedOutput − headroomTokens` is non-positive the backend refuses (`TargetPressureConfigError`), so the bar paints no masks | - **Limitation — declared divergence:** the timing card's wait/generation boundary opens at the first OBSERVABLE instant (the first token, or an earlier `block-start` marker the token packing could not stamp), not at the meter's first-token anchor — so marker-tiled decode buckets and the split never double-count one window. +- **Limitation — daily-ledger attribution:** `contextTimeline.cost` prices at the settlement instant while the daily ledger prices at the step-initiation instant, so a tariff-boundary step can split its peak/off buckets differently between the lifetime total and the KPI band; the ledger also retains 400 days (`MAX_KEPT_DAYS` in `src/host/activity.ts`). ## 5 · Client surface diff --git a/src/client/components/heatmap.tsx b/src/client/components/heatmap.tsx index de56621..dc097f7 100644 --- a/src/client/components/heatmap.tsx +++ b/src/client/components/heatmap.tsx @@ -3,11 +3,11 @@ * column where a month begins — the window's opening column always labeled — * and a Less→More key for the depth steps) over the merged daily * ledger (overview.ts). Cell depth is the selected metric's share of the - * window's maximum — distinct active sessions or settlement steps; a session - * can spread its steps thinly across many days while another day stacks a - * run, so each metric scales on its OWN maximum — in four steps; a day with data is a button whose click + * window's maximum — distinct active sessions or billed token volume; a day + * can run one long session while another stacks many, so each metric scales + * on its OWN maximum — in four steps; a day with data is a button whose click * pins the session list to that day (click again to release). Cells carry - * their tip (the date, the day's active sessions, and its steps) in + * their tip (the date, the day's active sessions, and its tokens) in * `data-lc-tip`, served by the page's portaled hover tip (hoverTip.tsx) — * instant on hover, where a native `title` lags a second behind. The grid is computed from the injected `today` key, so * the layout is deterministic in tests and follows the browser's local calendar at runtime. */ @@ -17,15 +17,15 @@ import { dayKeyOf, shiftDayKey, sundayOfWeek } from '../../shared/days' import { type DayTotals } from '../overview' import type { ViewKit } from '../viewkit' -/** The heatmap's depth metric: the day's distinct active sessions or its settlement steps. */ -export type HeatMetric = 'sessions' | 'steps' +/** The heatmap's depth metric: the day's distinct active sessions or its billed tokens. */ +export type HeatMetric = 'sessions' | 'tokens' export interface HeatmapProps { /** Merged ledger (day key → that day's figures). */ days: Record /** Columns to draw (default 8 — two months). */ weeks?: number - /** The metric pricing the cells' depth (default steps). */ + /** The metric pricing the cells' depth (default tokens). */ metric?: HeatMetric /** The pinned day key, when the list is filtered to a day. */ selected?: string | null @@ -79,16 +79,16 @@ function levelOf(tokens: number, max: number): number { } export function makeHeatmap(kit: ViewKit): (props: HeatmapProps) => ReactElement { - const { t } = kit + const { t, fmt } = kit return function Heatmap(props: HeatmapProps): ReactElement { const weeks = props.weeks ?? 8 - const metric = props.metric ?? 'steps' + const metric = props.metric ?? 'tokens' const columns = gridOf(props.today, weeks) if (columns === null) return
{t('ov.heat.empty')}
// Join the ledger onto the grid and price the depth scale. Widened: a day-key read can miss at runtime. const byKey: Record = props.days let maxSessions = 0 - let maxSteps = 0 + let maxTokens = 0 let any = false for (const column of columns) { for (const cell of column) { @@ -96,16 +96,16 @@ export function makeHeatmap(kit: ViewKit): (props: HeatmapProps) => ReactElement if (entry === undefined || (entry.tokens <= 0 && entry.requests <= 0)) continue cell.entry = entry any = true - // Independent maxima: sessions and steps shape the window's days - // differently (one long session drops a step a day; another day - // stacks a run), so neither metric scales off the other's peak. + // Independent maxima: sessions and tokens shape the window's days + // differently (one long session drops a block of tokens a day; another + // day stacks many sessions), so neither metric scales off the other's peak. if (entry.sessions > maxSessions) maxSessions = entry.sessions - if (entry.requests > maxSteps) maxSteps = entry.requests + if (entry.tokens > maxTokens) maxTokens = entry.tokens } } if (!any) return
{t('ov.heat.empty')}
- const max = metric === 'sessions' ? maxSessions : maxSteps - const metricValueOf = (entry: DayTotals): number => (metric === 'sessions' ? entry.sessions : entry.requests) + const max = metric === 'sessions' ? maxSessions : maxTokens + const metricValueOf = (entry: DayTotals): number => (metric === 'sessions' ? entry.sessions : entry.tokens) // Keyed BY the Sunday-first row index (0=Sun..6=Sat) — the label sits on // the row it names, and no row arithmetic can shift it. const weekdayLabels: Record = { 1: t('ov.heat.wd.1'), 3: t('ov.heat.wd.3'), 5: t('ov.heat.wd.5') } @@ -135,7 +135,7 @@ export function makeHeatmap(kit: ViewKit): (props: HeatmapProps) => ReactElement return } const picked = props.selected === cell.key - const label = `${cell.key}\n${t('ov.heat.sessions', { n: cell.entry.sessions })}\n${t('ov.heat.steps', { n: cell.entry.requests })}` + const label = `${cell.key}\n${t('ov.heat.sessions', { n: cell.entry.sessions })}\n${t('ov.heat.tokens', { n: fmt(cell.entry.tokens) })}` return (