From 9e1a9b59b1d09212d5b7330b2c274a12a458a618 Mon Sep 17 00:00:00 2001 From: dibanez Date: Fri, 14 Aug 2026 23:06:56 +0200 Subject: [PATCH 1/2] fix(ui): stop the context meter from counting every internal round-trip The token breakdown of an assistant message accumulates across every API round-trip inside the turn: each tool call re-reads the whole cached prompt, so input/cache.read add up to several times the context window. Every context-usage surface summed those fields, which is why the meter could read 330% of a 1M window whose real fill was 232,872 tokens (23.3%), and why reopening an older session jumps the readout (#2562). The server reports the final round-trip's window as tokens.total (optional in the message schema; opencode 1.18.18 returns it, verified against its live /session/:id/message API). Prefer it everywhere the window fill is displayed and fall back to summing only when the server did not send it: contextTokensFromBreakdown in tokenUtils now owns that rule, and the context store extractor, sync store getter, work status panel, context sidebar, VS Code layout, mini chat, and mobile metadata all use it instead of their own inline sums. Fixes #2562 --- CHANGELOG.md | 1 + .../ui/src/apps/MobileSessionMetadata.tsx | 6 ++ .../chat/work-status/contextUsage.test.ts | 24 ++++++- .../chat/work-status/contextUsage.ts | 20 +++--- .../components/layout/ContextSidebarTab.tsx | 7 +- .../ui/src/components/layout/VSCodeLayout.tsx | 5 +- .../components/mini-chat/MiniChatLayout.tsx | 7 +- .../ui/src/stores/utils/tokenUtils.test.ts | 72 ++++++++++++++++++- packages/ui/src/stores/utils/tokenUtils.ts | 31 +++++++- packages/ui/src/sync/session-ui-store.ts | 7 +- packages/vscode/CHANGELOG.md | 1 + 11 files changed, 156 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 46731e19..6f206cb6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ All notable changes to this project will be documented in this file. - Chat: new chats no longer start against a deleted last worktree directory; they fall back to the active project instead of saving the first message and never starting. - Chat: opening a busy subagent in the context panel now shows its history instead of only the working-status line (thanks to @makeittech). - Chat: saved chats in the context panel open again instead of staying blank. +- Chat: the context meter no longer climbs over 100% (330% readouts) after turns with many tool calls and no longer jumps when reopening an older session; it now shows what the window actually holds, everywhere the value appears — header, context sidebar, work status panel, mini chat, and mobile. - Projects: project names now match the folder name exactly, so `.ssh` and `opencode-claude` are no longer shown as `.Ssh` and `Opencode Claude` in the sidebar, window title, settings and notifications; names you renamed yourself are kept. - Settings: the session retention action you pick is now saved instead of being dropped (thanks to @Gautam0507). - Browser: typing a comment on a page no longer triggers app shortcuts. diff --git a/packages/ui/src/apps/MobileSessionMetadata.tsx b/packages/ui/src/apps/MobileSessionMetadata.tsx index 9ffe0d66..3d239d1f 100644 --- a/packages/ui/src/apps/MobileSessionMetadata.tsx +++ b/packages/ui/src/apps/MobileSessionMetadata.tsx @@ -388,6 +388,7 @@ export const MobileSessionMetadataButton = React.memo(function MobileSessionMeta for (let i = activeSessionMessages.length - 1; i >= 0; i -= 1) { const message = activeSessionMessages[i] as typeof activeSessionMessages[number] & { tokens?: { + total?: unknown; input?: unknown; output?: unknown; reasoning?: unknown; @@ -395,6 +396,11 @@ export const MobileSessionMetadataButton = React.memo(function MobileSessionMeta }; }; if (message.role !== 'assistant' || !message.tokens) continue; + // Multi-step turns accumulate the fields across API round-trips, so + // summing them overstates the window. The server-reported total is the + // final round-trip's window; sum only when the server did not send it. + const reportedTotal = getTokenCount(message.tokens.total); + if (reportedTotal > 0) return reportedTotal; const total = getTokenCount(message.tokens.input) + getTokenCount(message.tokens.output) + getTokenCount(message.tokens.reasoning) diff --git a/packages/ui/src/components/chat/work-status/contextUsage.test.ts b/packages/ui/src/components/chat/work-status/contextUsage.test.ts index c2e0a6e4..34f538f6 100644 --- a/packages/ui/src/components/chat/work-status/contextUsage.test.ts +++ b/packages/ui/src/components/chat/work-status/contextUsage.test.ts @@ -14,8 +14,8 @@ describe('computeContextUsage', () => { }); test('reports the latest turn rather than a sum across turns', () => { - // Each assistant turn reports the whole window it saw, so adding them up - // would report several times the real fill. + // A turn's tokens describe that turn's window, so adding turns up would + // report several times the real fill. const usage = computeContextUsage( [ assistant({ input: 400, output: 0, reasoning: 0 }, 'old'), @@ -61,4 +61,24 @@ describe('computeContextUsage', () => { const usage = computeContextUsage([assistant({ input: 10 })], 100); expect(usage?.totalTokens).toBe(10); }); + + test('prefers the server-reported total over summing round-trip fields', () => { + // Real payload from opencode 1.18.18: ~14 tool-call round-trips accumulated + // cache.read to 3.29M while the 1M window really held 232,872. Summing + // rendered 330.6%; the reported total renders the real 23.3%. + const usage = computeContextUsage( + [assistant({ total: 232_872, input: 0, output: 14_523, reasoning: 0, cache: { read: 3_291_956, write: 0 } })], + 1_000_000, + ); + expect(usage?.totalTokens).toBe(232_872); + expect(usage?.percent.toFixed(4)).toBe('23.2872'); + }); + + test('selects a message whose only signal is the reported total', () => { + const usage = computeContextUsage( + [assistant({ total: 5_000, input: 0, output: 0, reasoning: 0 })], + 100_000, + ); + expect(usage?.totalTokens).toBe(5_000); + }); }); diff --git a/packages/ui/src/components/chat/work-status/contextUsage.ts b/packages/ui/src/components/chat/work-status/contextUsage.ts index c30d57f6..920d6963 100644 --- a/packages/ui/src/components/chat/work-status/contextUsage.ts +++ b/packages/ui/src/components/chat/work-status/contextUsage.ts @@ -13,7 +13,11 @@ * global read to race with. */ +import { contextTokensFromBreakdown } from '@/stores/utils/tokenUtils'; + type MessageTokens = { + /** Server-reported window of the turn's final round-trip; absent on older servers. */ + total?: number; input?: number; output?: number; reasoning?: number; @@ -37,18 +41,12 @@ type WorkStatusContextUsage = { /** The store's own fallback when a model exposes no context limit. */ export const DEFAULT_CONTEXT_LIMIT = 200_000; -const sumTokens = (tokens: MessageTokens): number => ( - (tokens.input ?? 0) - + (tokens.output ?? 0) - + (tokens.reasoning ?? 0) - + (tokens.cache?.read ?? 0) - + (tokens.cache?.write ?? 0) -); - /** * Usage from the newest assistant message that reported a non-zero token count. - * Each assistant turn reports the whole window it saw, so the latest one is the - * current fill — not a sum across turns. + * The latest turn describes the current fill — not a sum across turns. Within + * a turn, the server-reported `total` is the final round-trip's window; + * summing the breakdown fields instead overstates multi-step turns, whose + * input/cache fields accumulate across round-trips. */ export const computeContextUsage = ( messages: readonly MessageLike[], @@ -60,7 +58,7 @@ export const computeContextUsage = ( const message = messages[index]; if (message?.role !== 'assistant' || !message.tokens) continue; - const totalTokens = sumTokens(message.tokens); + const totalTokens = contextTokensFromBreakdown(message.tokens); if (totalTokens <= 0) continue; const limit = contextLimit > 0 ? contextLimit : DEFAULT_CONTEXT_LIMIT; diff --git a/packages/ui/src/components/layout/ContextSidebarTab.tsx b/packages/ui/src/components/layout/ContextSidebarTab.tsx index 1e08bd0b..3f41e331 100644 --- a/packages/ui/src/components/layout/ContextSidebarTab.tsx +++ b/packages/ui/src/components/layout/ContextSidebarTab.tsx @@ -92,6 +92,7 @@ const extractTokenBreakdown = (message: SessionMessage): TokenBreakdown => { } const breakdown = source as { + total?: unknown; input?: unknown; output?: unknown; reasoning?: unknown; @@ -103,6 +104,10 @@ const extractTokenBreakdown = (message: SessionMessage): TokenBreakdown => { const reasoning = toNonNegativeNumber(breakdown.reasoning); const cacheRead = toNonNegativeNumber(breakdown.cache?.read); const cacheWrite = toNonNegativeNumber(breakdown.cache?.write); + // Multi-step turns accumulate the fields across API round-trips (every tool + // call re-reads the whole cached prompt), so summing them overstates the + // window. The server-reported total is the final round-trip's window. + const reportedTotal = toNonNegativeNumber(breakdown.total); return { input, @@ -110,7 +115,7 @@ const extractTokenBreakdown = (message: SessionMessage): TokenBreakdown => { reasoning, cacheRead, cacheWrite, - total: input + output + reasoning + cacheRead + cacheWrite, + total: reportedTotal > 0 ? reportedTotal : input + output + reasoning + cacheRead + cacheWrite, }; }; diff --git a/packages/ui/src/components/layout/VSCodeLayout.tsx b/packages/ui/src/components/layout/VSCodeLayout.tsx index de1f1772..0b592fe0 100644 --- a/packages/ui/src/components/layout/VSCodeLayout.tsx +++ b/packages/ui/src/components/layout/VSCodeLayout.tsx @@ -8,6 +8,7 @@ import { useViewportStore } from '@/sync/viewport-store'; import { useSessions, useDirectorySync, useSessionMessages, useSessionMessagesResolved } from '@/sync/sync-context'; import { useConfigStore } from '@/stores/useConfigStore'; import { resolveGlobalSessionDirectory, useGlobalSessionsStore } from '@/stores/useGlobalSessionsStore'; +import { contextTokensFromBreakdown } from '@/stores/utils/tokenUtils'; import { ContextUsageDisplay } from '@/components/ui/ContextUsageDisplay'; import { McpDropdown } from '@/components/mcp/McpDropdown'; import { ArchiveAllDropdown } from '@/components/session/ArchiveAllDropdown'; @@ -702,7 +703,7 @@ const VSCodeHeader: React.FC = ({ title, showBack, onBack, on } if (!lastTokens && message.tokens) { - const total = message.tokens.input + message.tokens.output + message.tokens.reasoning + (message.tokens.cache?.read ?? 0) + (message.tokens.cache?.write ?? 0); + const total = contextTokensFromBreakdown(message.tokens); if (total > 0) { lastTokens = message.tokens; lastMessageId = (currentSessionMessages[i] as { id?: string }).id; @@ -730,7 +731,7 @@ const VSCodeHeader: React.FC = ({ title, showBack, onBack, on } const lastTokens = headerMessageSummary.lastTokens; - const totalTokens = lastTokens.input + lastTokens.output + lastTokens.reasoning + (lastTokens.cache?.read ?? 0) + (lastTokens.cache?.write ?? 0); + const totalTokens = contextTokensFromBreakdown(lastTokens); const thresholdLimit = contextLimit > 0 ? contextLimit : 200000; const percentage = contextLimit > 0 ? Math.round((totalTokens / contextLimit) * 100) : 0; const normalizedOutput = outputLimit > 0 ? Math.round((lastTokens.output / outputLimit) * 100) : undefined; diff --git a/packages/ui/src/components/mini-chat/MiniChatLayout.tsx b/packages/ui/src/components/mini-chat/MiniChatLayout.tsx index 4eea6d8f..a32779a1 100644 --- a/packages/ui/src/components/mini-chat/MiniChatLayout.tsx +++ b/packages/ui/src/components/mini-chat/MiniChatLayout.tsx @@ -18,6 +18,7 @@ import { useGitBranchLabel, useGitStore } from '@/stores/useGitStore'; import { useConfigStore } from '@/stores/useConfigStore'; import { Icon } from "@/components/icon/Icon"; import { useRuntimeAPIs } from '@/hooks/useRuntimeAPIs'; +import { contextTokensFromBreakdown } from '@/stores/utils/tokenUtils'; import type { SessionContextUsage } from '@/stores/types/sessionTypes'; type MiniChatMode = 'session' | 'draft'; @@ -157,7 +158,7 @@ const MiniChatHeader: React.FC<{ mode: MiniChatMode }> = ({ mode }) => { return null; } - type AssistantTokens = { input: number; output: number; reasoning: number; cache: { read: number; write: number } }; + type AssistantTokens = { total?: number; input: number; output: number; reasoning: number; cache: { read: number; write: number } }; let lastTokens: AssistantTokens | undefined; let lastMessageId: string | undefined; @@ -166,7 +167,7 @@ const MiniChatHeader: React.FC<{ mode: MiniChatMode }> = ({ mode }) => { if (message.role !== 'assistant') continue; const tokens = (message as { tokens?: AssistantTokens }).tokens; if (!tokens) continue; - const total = tokens.input + tokens.output + tokens.reasoning + (tokens.cache?.read ?? 0) + (tokens.cache?.write ?? 0); + const total = contextTokensFromBreakdown(tokens); if (total > 0) { lastTokens = tokens; lastMessageId = message.id; @@ -178,7 +179,7 @@ const MiniChatHeader: React.FC<{ mode: MiniChatMode }> = ({ mode }) => { return null; } - const totalTokens = lastTokens.input + lastTokens.output + lastTokens.reasoning + (lastTokens.cache?.read ?? 0) + (lastTokens.cache?.write ?? 0); + const totalTokens = contextTokensFromBreakdown(lastTokens); const thresholdLimit = contextLimit > 0 ? contextLimit : 200000; const percentage = contextLimit > 0 ? Math.round((totalTokens / contextLimit) * 100) : 0; const normalizedOutput = outputLimit > 0 ? Math.round((lastTokens.output / outputLimit) * 100) : undefined; diff --git a/packages/ui/src/stores/utils/tokenUtils.test.ts b/packages/ui/src/stores/utils/tokenUtils.test.ts index 63d8cf6d..494fa711 100644 --- a/packages/ui/src/stores/utils/tokenUtils.test.ts +++ b/packages/ui/src/stores/utils/tokenUtils.test.ts @@ -1,5 +1,11 @@ import { describe, expect, test } from "bun:test" -import { computeCacheHitRate, sumTokenBreakdown } from "./tokenUtils" +import type { Message, Part } from "@opencode-ai/sdk/v2" +import { computeCacheHitRate, contextTokensFromBreakdown, extractTokensFromMessage, sumTokenBreakdown } from "./tokenUtils" + +const assistantMessage = (tokens: unknown): { info: Message; parts: Part[] } => ({ + info: { tokens } as unknown as Message, + parts: [], +}) describe("computeCacheHitRate", () => { test("returns zero and hasInput=false for null input", () => { @@ -95,3 +101,67 @@ describe("sumTokenBreakdown (regression)", () => { expect(sumTokenBreakdown(undefined)).toBe(0) }) }) + +describe("contextTokensFromBreakdown", () => { + test("prefers the server-reported total over the summed fields", () => { + const breakdown = { total: 500, input: 100, output: 50, reasoning: 20, cache: { read: 800, write: 20 } } + expect(contextTokensFromBreakdown(breakdown)).toBe(500) + }) + + test("real multi-step turn: summing overstates a 1M window 14x, the total matches it", () => { + // Captured from opencode 1.18.18 (/session/:id/message) after a turn with + // ~14 tool-call round-trips. Every round-trip re-reads the whole cached + // prompt, so cache.read accumulates to 3.29M while the window really held + // 232,872. Summing rendered the context meter at 330.6% of a 1M window. + const breakdown = { total: 232_872, input: 0, output: 14_523, reasoning: 0, cache: { read: 3_291_956, write: 0 } } + expect(contextTokensFromBreakdown(breakdown)).toBe(232_872) + expect(sumTokenBreakdown(breakdown)).toBe(3_306_479) + }) + + test("single-step turn: the total and the summed fields agree", () => { + // Captured from the same server: one round-trip, nothing accumulates. + const breakdown = { total: 117_714, input: 1_116, output: 87, reasoning: 543, cache: { read: 115_968, write: 0 } } + expect(contextTokensFromBreakdown(breakdown)).toBe(sumTokenBreakdown(breakdown)) + }) + + test("falls back to summing when the server sends no total (older servers)", () => { + expect(contextTokensFromBreakdown({ input: 100, output: 50, reasoning: 20, cache: { read: 80, write: 20 } })).toBe(270) + }) + + test("falls back to summing when the total is zero or not a finite number", () => { + expect(contextTokensFromBreakdown({ total: 0, input: 40 })).toBe(40) + expect(contextTokensFromBreakdown({ total: Number.NaN, input: 40 })).toBe(40) + }) + + test("handles null and undefined", () => { + expect(contextTokensFromBreakdown(null)).toBe(0) + expect(contextTokensFromBreakdown(undefined)).toBe(0) + }) +}) + +describe("extractTokensFromMessage", () => { + test("uses the reported total from the message info breakdown", () => { + const message = assistantMessage({ total: 232_872, input: 0, output: 14_523, reasoning: 0, cache: { read: 3_291_956, write: 0 } }) + expect(extractTokensFromMessage(message)).toBe(232_872) + }) + + test("sums the info breakdown when no total is reported", () => { + expect(extractTokensFromMessage(assistantMessage({ input: 100, output: 50, reasoning: 20, cache: { read: 80, write: 20 } }))).toBe(270) + }) + + test("returns plain numeric tokens as-is", () => { + expect(extractTokensFromMessage(assistantMessage(1234))).toBe(1234) + }) + + test("prefers the reported total when tokens live on a part", () => { + const message: { info: Message; parts: Part[] } = { + info: {} as Message, + parts: [{ tokens: { total: 500, input: 2_000 } } as unknown as Part], + } + expect(extractTokensFromMessage(message)).toBe(500) + }) + + test("returns 0 when neither info nor parts carry tokens", () => { + expect(extractTokensFromMessage({ info: {} as Message, parts: [] })).toBe(0) + }) +}) diff --git a/packages/ui/src/stores/utils/tokenUtils.ts b/packages/ui/src/stores/utils/tokenUtils.ts index 4b944449..0f89ec3b 100644 --- a/packages/ui/src/stores/utils/tokenUtils.ts +++ b/packages/ui/src/stores/utils/tokenUtils.ts @@ -1,6 +1,8 @@ import type { Message, Part } from "@opencode-ai/sdk/v2"; type TokenBreakdown = { + /** Server-reported window of the turn's final round-trip. Optional in the schema; absent on older servers. */ + total?: number; input?: number; output?: number; reasoning?: number; @@ -24,6 +26,31 @@ export const sumTokenBreakdown = (breakdown: TokenBreakdown | null | undefined): return inputTokens + outputTokens + reasoningTokens + cacheReadTokens + cacheWriteTokens; }; +/** + * Tokens the context window actually holds, from one message's token payload. + * + * The breakdown fields accumulate across every API round-trip inside a single + * assistant turn: each tool call re-reads the whole (cached) prompt, so on a + * multi-step turn `cache.read` alone can add up to several times the context + * window (observed on opencode 1.18.18: cache.read 3,291,956 on a turn whose + * 1M window really held 232,872 — rendered as a 330% context readout). The + * server reports the final round-trip's window as `tokens.total` (optional in + * the message schema, absent on older servers). Prefer it; fall back to + * summing the fields only when the server did not send it. + */ +export const contextTokensFromBreakdown = (breakdown: TokenBreakdown | null | undefined): number => { + if (!breakdown || typeof breakdown !== 'object') { + return 0; + } + + const reportedTotal = breakdown.total; + if (typeof reportedTotal === 'number' && Number.isFinite(reportedTotal) && reportedTotal > 0) { + return reportedTotal; + } + + return sumTokenBreakdown(breakdown); +}; + export const extractTokensFromMessage = (message: { info: Message; parts: Part[] }): number => { const tokens = (message.info as { tokens?: number | TokenBreakdown }).tokens; @@ -32,7 +59,7 @@ export const extractTokensFromMessage = (message: { info: Message; parts: Part[] } if (tokens && typeof tokens === 'object') { - return sumTokenBreakdown(tokens); + return contextTokensFromBreakdown(tokens); } const tokenPart = message.parts.find( @@ -47,7 +74,7 @@ export const extractTokensFromMessage = (message: { info: Message; parts: Part[] return tokenPart.tokens; } - return sumTokenBreakdown(tokenPart.tokens); + return contextTokensFromBreakdown(tokenPart.tokens); }; type CacheHitRateResult = { diff --git a/packages/ui/src/sync/session-ui-store.ts b/packages/ui/src/sync/session-ui-store.ts index 0fc4ebb1..6df5d456 100644 --- a/packages/ui/src/sync/session-ui-store.ts +++ b/packages/ui/src/sync/session-ui-store.ts @@ -86,6 +86,7 @@ import { getRuntimeKey } from "@/lib/runtime-switch" import { clearLastActiveSession, persistLastActiveSession, readLastActiveSession } from "./last-session-cache" import { persistWorktreeTopology, readPersistedWorktreeTopology } from "./worktree-topology-cache" import { rememberRuntimeLiveStatus } from "./runtime-live-memory" +import { contextTokensFromBreakdown } from "@/stores/utils/tokenUtils" export type { AttachedFile } @@ -1173,7 +1174,7 @@ export const useSessionUIStore = create()((set, get) => ({ const messages = getSyncMessages(sessionId) if (messages.length === 0) return null - type AssistantTokens = { input: number; output: number; reasoning: number; cache: { read: number; write: number } } + type AssistantTokens = { total?: number; input: number; output: number; reasoning: number; cache: { read: number; write: number } } let lastTokens: AssistantTokens | undefined let lastMessageId: string | undefined for (let i = messages.length - 1; i >= 0; i--) { @@ -1181,7 +1182,7 @@ export const useSessionUIStore = create()((set, get) => ({ if (msg.role !== "assistant") continue const tokens = (msg as { tokens?: AssistantTokens }).tokens if (!tokens) continue - const total = tokens.input + tokens.output + tokens.reasoning + (tokens.cache?.read ?? 0) + (tokens.cache?.write ?? 0) + const total = contextTokensFromBreakdown(tokens) if (total > 0) { lastTokens = tokens lastMessageId = msg.id @@ -1191,7 +1192,7 @@ export const useSessionUIStore = create()((set, get) => ({ if (!lastTokens) return null - const totalTokens = lastTokens.input + lastTokens.output + lastTokens.reasoning + (lastTokens.cache?.read ?? 0) + (lastTokens.cache?.write ?? 0) + const totalTokens = contextTokensFromBreakdown(lastTokens) const thresholdLimit = contextLimit > 0 ? contextLimit : 200000 const percentage = contextLimit > 0 ? Math.round((totalTokens / contextLimit) * 100) : 0 const normalizedOutput = outputLimit > 0 ? Math.round((lastTokens.output / outputLimit) * 100) : undefined diff --git a/packages/vscode/CHANGELOG.md b/packages/vscode/CHANGELOG.md index 44f43241..78719fbe 100644 --- a/packages/vscode/CHANGELOG.md +++ b/packages/vscode/CHANGELOG.md @@ -2,6 +2,7 @@ - Chat: new chats no longer start against a deleted last worktree directory; they fall back to the active project instead of saving the first message and never starting. - Chat: opening a busy subagent in the context panel now shows its history instead of only the working-status line (thanks to @makeittech). +- The context usage readout no longer climbs over 100% after turns with many tool calls and no longer jumps when reopening an older session; it now shows what the window actually holds. - Projects: project names now match the folder name exactly, so `.ssh` and `opencode-claude` are no longer shown as `.Ssh` and `Opencode Claude`; names you renamed yourself are kept. - Skills Catalog: the source is now named ClawHub instead of "ClawdHub" (thanks to @makeittech). From 450d30b3eef91803a6a895687ba37bdb28cd01a1 Mon Sep 17 00:00:00 2001 From: dibanez Date: Fri, 14 Aug 2026 23:34:03 +0200 Subject: [PATCH 2/2] docs(changelog): credit the context meter fix contributor Requested by review: the changelog-authoring skill requires inline contributor credit for non-owner contributors. --- CHANGELOG.md | 2 +- packages/vscode/CHANGELOG.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f206cb6..abbf518d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ All notable changes to this project will be documented in this file. - Chat: new chats no longer start against a deleted last worktree directory; they fall back to the active project instead of saving the first message and never starting. - Chat: opening a busy subagent in the context panel now shows its history instead of only the working-status line (thanks to @makeittech). - Chat: saved chats in the context panel open again instead of staying blank. -- Chat: the context meter no longer climbs over 100% (330% readouts) after turns with many tool calls and no longer jumps when reopening an older session; it now shows what the window actually holds, everywhere the value appears — header, context sidebar, work status panel, mini chat, and mobile. +- Chat: the context meter no longer climbs over 100% (330% readouts) after turns with many tool calls and no longer jumps when reopening an older session; it now shows what the window actually holds, everywhere the value appears — header, context sidebar, work status panel, mini chat, and mobile (thanks to @pocharlies). - Projects: project names now match the folder name exactly, so `.ssh` and `opencode-claude` are no longer shown as `.Ssh` and `Opencode Claude` in the sidebar, window title, settings and notifications; names you renamed yourself are kept. - Settings: the session retention action you pick is now saved instead of being dropped (thanks to @Gautam0507). - Browser: typing a comment on a page no longer triggers app shortcuts. diff --git a/packages/vscode/CHANGELOG.md b/packages/vscode/CHANGELOG.md index 78719fbe..fefde588 100644 --- a/packages/vscode/CHANGELOG.md +++ b/packages/vscode/CHANGELOG.md @@ -2,7 +2,7 @@ - Chat: new chats no longer start against a deleted last worktree directory; they fall back to the active project instead of saving the first message and never starting. - Chat: opening a busy subagent in the context panel now shows its history instead of only the working-status line (thanks to @makeittech). -- The context usage readout no longer climbs over 100% after turns with many tool calls and no longer jumps when reopening an older session; it now shows what the window actually holds. +- The context usage readout no longer climbs over 100% after turns with many tool calls and no longer jumps when reopening an older session; it now shows what the window actually holds (thanks to @pocharlies). - Projects: project names now match the folder name exactly, so `.ssh` and `opencode-claude` are no longer shown as `.Ssh` and `Opencode Claude`; names you renamed yourself are kept. - Skills Catalog: the source is now named ClawHub instead of "ClawdHub" (thanks to @makeittech).