diff --git a/desktop/src/components/controls/ReasoningEffortPopover.test.tsx b/desktop/src/components/controls/ReasoningEffortPopover.test.tsx
index 3896960c..09ef39c8 100644
--- a/desktop/src/components/controls/ReasoningEffortPopover.test.tsx
+++ b/desktop/src/components/controls/ReasoningEffortPopover.test.tsx
@@ -47,7 +47,10 @@ describe('ReasoningEffortPopover', () => {
expect(popover).toHaveStyle({ width: '280px' })
expect(popover).toHaveClass('px-4', 'pb-4', 'pt-3.5')
expect(popover.querySelectorAll('svg')).toHaveLength(0)
- expect(screen.getByTestId('reasoning-effort-label')).toHaveClass('mb-3', 'text-[15px]')
+ expect(screen.getByTestId('reasoning-effort-header')).toHaveClass('mb-3', 'justify-between')
+ expect(screen.getByTestId('reasoning-effort-label')).toHaveClass('text-[15px]')
+ expect(screen.getByTestId('reasoning-effort-context-label')).toHaveClass('text-[11px]')
+ expect(screen.getByTestId('reasoning-effort-context-label')).toHaveTextContent('推理强度')
expect(screen.getByRole('slider', { name: '推理强度' })).toHaveClass('h-11')
expect(screen.getByTestId('reasoning-effort-track')).toHaveClass('h-[30px]')
expect(screen.getByTestId('reasoning-effort-thumb')).toHaveClass('h-10', 'w-10')
@@ -63,7 +66,8 @@ describe('ReasoningEffortPopover', () => {
expect(slider).toHaveAttribute('aria-valuetext', '极高')
expect(screen.getAllByTestId('reasoning-effort-stop')).toHaveLength(5)
expect(screen.getByText('极高')).toBeInTheDocument()
- expect(screen.getByTestId('reasoning-effort-fill')).toHaveClass('bg-[#3798f7]')
+ expect(screen.getByTestId('reasoning-effort-fill')).toHaveClass('bg-[var(--color-brand)]')
+ expect(slider).toHaveClass('focus-visible:ring-[var(--color-brand)]')
})
it('selects a discrete stop from the track', () => {
diff --git a/desktop/src/components/controls/ReasoningEffortPopover.tsx b/desktop/src/components/controls/ReasoningEffortPopover.tsx
index c26dff8f..cfaf3cd9 100644
--- a/desktop/src/components/controls/ReasoningEffortPopover.tsx
+++ b/desktop/src/components/controls/ReasoningEffortPopover.tsx
@@ -111,10 +111,21 @@ export function ReasoningEffortPopover({
style={{ bottom: position.bottom, left: position.left, width: position.width }}
>
- {labels[value]}
+
+ {labels[value]}
+
+
+ {ariaLabel}
+
selectFromClientX(event.clientX)}
onPointerDown={(event) => {
draggingRef.current = true
@@ -175,7 +186,7 @@ export function ReasoningEffortPopover({
>
diff --git a/desktop/src/stores/sessionRuntimeStore.ts b/desktop/src/stores/sessionRuntimeStore.ts
index 0a0baee7..d6d864f0 100644
--- a/desktop/src/stores/sessionRuntimeStore.ts
+++ b/desktop/src/stores/sessionRuntimeStore.ts
@@ -1,5 +1,6 @@
import { create } from 'zustand'
import type { RuntimeSelection } from '../types/runtime'
+import type { SessionListItem } from '../types/session'
const STORAGE_KEY = 'cc-haha-session-runtime'
@@ -10,6 +11,7 @@ type SessionRuntimeStore = {
setSelection: (key: string, selection: RuntimeSelection) => void
clearSelection: (key: string) => void
moveSelection: (fromKey: string, toKey: string) => void
+ syncFromSessions: (sessions: SessionListItem[]) => void
}
function loadSelections(): Record {
@@ -66,4 +68,30 @@ export const useSessionRuntimeStore = create((set) => ({
persistSelections(selections)
return { selections }
}),
+
+ syncFromSessions: (sessions) =>
+ set((state) => {
+ let selections = state.selections
+ for (const session of sessions) {
+ if (!session.runtimeModelId || session.runtimeProviderId === undefined) continue
+ const selection: RuntimeSelection = {
+ providerId: session.runtimeProviderId,
+ modelId: session.runtimeModelId,
+ ...(session.effortLevel ? { effortLevel: session.effortLevel } : {}),
+ }
+ const current = selections[session.id]
+ if (
+ current?.providerId === selection.providerId &&
+ current.modelId === selection.modelId &&
+ current.effortLevel === selection.effortLevel
+ ) {
+ continue
+ }
+ if (selections === state.selections) selections = { ...state.selections }
+ selections[session.id] = selection
+ }
+ if (selections === state.selections) return state
+ persistSelections(selections)
+ return { selections }
+ }),
}))
diff --git a/desktop/src/stores/sessionStore.test.ts b/desktop/src/stores/sessionStore.test.ts
index 8d3fab12..427cc53d 100644
--- a/desktop/src/stores/sessionStore.test.ts
+++ b/desktop/src/stores/sessionStore.test.ts
@@ -25,6 +25,7 @@ vi.mock('../lib/recentProjectsCache', () => ({
}))
import { useSessionStore } from './sessionStore'
+import { useSessionRuntimeStore } from './sessionRuntimeStore'
import { useSettingsStore } from './settingsStore'
import { useTabStore } from './tabStore'
@@ -73,12 +74,14 @@ describe('sessionStore', () => {
})
useSettingsStore.setState({ permissionMode: 'default' })
useTabStore.setState({ tabs: [], activeTabId: null })
+ useSessionRuntimeStore.setState({ selections: {} })
})
afterEach(() => {
useSessionStore.setState(initialState)
useSettingsStore.setState({ permissionMode: 'default' })
useTabStore.setState({ tabs: [], activeTabId: null })
+ useSessionRuntimeStore.setState({ selections: {} })
})
it('returns a new session id before the background refresh completes', async () => {
@@ -165,6 +168,31 @@ describe('sessionStore', () => {
expect(useTabStore.getState().tabs[0]?.title).toBe('使用bash写一个shell,随便写点什么东西')
})
+ it('syncs transcript runtime metadata before a session is opened from the sidebar', async () => {
+ useSessionRuntimeStore.getState().setSelection('session-runtime-1', {
+ providerId: null,
+ modelId: 'gpt-5.4',
+ effortLevel: 'max',
+ })
+ listMock.mockResolvedValue({
+ sessions: [{
+ ...makeSession('session-runtime-1', '2026-07-13T05:57:05.818Z'),
+ runtimeProviderId: 'provider-latest',
+ runtimeModelId: 'anthropic/claude-opus-4.7',
+ effortLevel: 'max',
+ }],
+ total: 1,
+ })
+
+ await useSessionStore.getState().fetchSessions()
+
+ expect(useSessionRuntimeStore.getState().selections['session-runtime-1']).toEqual({
+ providerId: 'provider-latest',
+ modelId: 'anthropic/claude-opus-4.7',
+ effortLevel: 'max',
+ })
+ })
+
it('updates a session message count without changing other metadata', () => {
useSessionStore.setState({
sessions: [makeSession('session-count-1', '2026-05-07T00:00:00.000Z', 'Working session')],
diff --git a/desktop/src/stores/sessionStore.ts b/desktop/src/stores/sessionStore.ts
index 6c37935d..4cf0b02b 100644
--- a/desktop/src/stores/sessionStore.ts
+++ b/desktop/src/stores/sessionStore.ts
@@ -68,6 +68,7 @@ export const useSessionStore = create((set, get) => ({
try {
const { sessions: raw } = await sessionsApi.list(buildSessionListParams(project))
if (requestId !== fetchSessionsRequestId) return
+ useSessionRuntimeStore.getState().syncFromSessions(raw)
let syncedSessions: SessionListItem[] = []
set((state) => {
const sessions = mergeSessionList(raw, state.sessions)
diff --git a/desktop/src/stores/tabStore.test.ts b/desktop/src/stores/tabStore.test.ts
index 498a3148..bec25425 100644
--- a/desktop/src/stores/tabStore.test.ts
+++ b/desktop/src/stores/tabStore.test.ts
@@ -1,5 +1,6 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { sessionsApi } from '../api/sessions'
+import { useSessionRuntimeStore } from './sessionRuntimeStore'
import { SETTINGS_TAB_ID, MARKET_TAB_ID, useTabStore } from './tabStore'
vi.mock('../api/sessions', () => ({
@@ -12,6 +13,7 @@ describe('tabStore', () => {
beforeEach(() => {
useTabStore.setState({ tabs: [], activeTabId: null })
localStorage.clear()
+ useSessionRuntimeStore.setState({ selections: {} })
vi.mocked(sessionsApi.list).mockResolvedValue({ sessions: [] } as never)
})
@@ -196,6 +198,36 @@ describe('tabStore', () => {
expect(useTabStore.getState().activeTabId).toBe(MARKET_TAB_ID)
})
+ it('hydrates restored tabs with authoritative transcript runtime metadata', async () => {
+ useSessionRuntimeStore.getState().setSelection('session-1', {
+ providerId: null,
+ modelId: 'gpt-5.4',
+ effortLevel: 'max',
+ })
+ localStorage.setItem('cc-haha-open-tabs', JSON.stringify({
+ openTabs: [{ sessionId: 'session-1', title: 'Runtime session', type: 'session' }],
+ activeTabId: 'session-1',
+ }))
+ vi.mocked(sessionsApi.list).mockResolvedValue({
+ sessions: [{
+ id: 'session-1',
+ title: 'Runtime session',
+ runtimeProviderId: 'provider-latest',
+ runtimeModelId: 'anthropic/claude-opus-4.7',
+ effortLevel: 'max',
+ }],
+ total: 1,
+ } as never)
+
+ await useTabStore.getState().restoreTabs()
+
+ expect(useSessionRuntimeStore.getState().selections['session-1']).toEqual({
+ providerId: 'provider-latest',
+ modelId: 'anthropic/claude-opus-4.7',
+ effortLevel: 'max',
+ })
+ })
+
it('canonicalizes mismatched persisted special tab ids and types during restore', async () => {
localStorage.setItem('cc-haha-open-tabs', JSON.stringify({
openTabs: [
diff --git a/desktop/src/stores/tabStore.ts b/desktop/src/stores/tabStore.ts
index 4c52a137..0900714d 100644
--- a/desktop/src/stores/tabStore.ts
+++ b/desktop/src/stores/tabStore.ts
@@ -2,6 +2,7 @@ import { create } from 'zustand'
import { sessionsApi } from '../api/sessions'
import { dropSession as dropVirtualHeightSession } from '../components/chat/virtualHeightCache'
import { destroyTerminalRuntime } from '../lib/terminalRuntime'
+import { useSessionRuntimeStore } from './sessionRuntimeStore'
const TAB_STORAGE_KEY = 'cc-haha-open-tabs'
@@ -351,6 +352,7 @@ export const useTabStore = create((set, get) => ({
) {
return
}
+ useSessionRuntimeStore.getState().syncFromSessions(sessions)
const existingIds = new Set(sessions.map((s) => s.id))
const validTabs: Tab[] = data.openTabs
diff --git a/desktop/src/types/session.ts b/desktop/src/types/session.ts
index f4de90e9..122eb2f3 100644
--- a/desktop/src/types/session.ts
+++ b/desktop/src/types/session.ts
@@ -1,5 +1,7 @@
// Source: src/server/services/sessionService.ts
+import type { ReasoningEffortLevel } from './settings'
+
export type SessionListItem = {
id: string
title: string
@@ -11,6 +13,9 @@ export type SessionListItem = {
workDir: string | null
workDirExists: boolean
permissionMode?: string
+ runtimeProviderId?: string | null
+ runtimeModelId?: string
+ effortLevel?: ReasoningEffortLevel
}
export type MessageUsage = {
diff --git a/src/server/__tests__/sessions.test.ts b/src/server/__tests__/sessions.test.ts
index 1551ad59..93f68dfd 100644
--- a/src/server/__tests__/sessions.test.ts
+++ b/src/server/__tests__/sessions.test.ts
@@ -1686,6 +1686,29 @@ describe('SessionService', () => {
expect((await service.getSessionLaunchInfo(sessionId))?.permissionMode).toBe('auto')
})
+ it('should expose the latest runtime selection in the session list', async () => {
+ const workDir = '/tmp/runtime-list-metadata'
+ const sessionId = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee'
+ await writeSessionFile(sanitizePath(workDir), sessionId, [
+ makeSnapshotEntry(),
+ {
+ ...makeSessionMetaEntry(workDir),
+ runtimeProviderId: 'provider-latest',
+ runtimeModelId: 'anthropic/claude-opus-4.7',
+ effortLevel: 'max',
+ },
+ makeUserEntry('Use the latest runtime metadata'),
+ ])
+
+ const listed = (await service.listSessions()).sessions.find((session) => session.id === sessionId)
+
+ expect(listed).toMatchObject({
+ runtimeProviderId: 'provider-latest',
+ runtimeModelId: 'anthropic/claude-opus-4.7',
+ effortLevel: 'max',
+ })
+ })
+
it('should not append duplicate runtime metadata when it already matches', async () => {
const workDir = '/tmp/runtime-idempotent'
const sessionId = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee'
diff --git a/src/server/services/sessionService.ts b/src/server/services/sessionService.ts
index 929291e6..b82c4807 100644
--- a/src/server/services/sessionService.ts
+++ b/src/server/services/sessionService.ts
@@ -61,6 +61,9 @@ export type SessionListItem = {
workDir: string | null
workDirExists: boolean
permissionMode?: string
+ runtimeProviderId?: string | null
+ runtimeModelId?: string
+ effortLevel?: string
}
export type DeleteSessionFailure = {
@@ -2459,6 +2462,11 @@ export class SessionService {
workDir,
workDirExists,
permissionMode: summary.permissionMode,
+ ...(summary.runtimeProviderId !== undefined
+ ? { runtimeProviderId: summary.runtimeProviderId }
+ : {}),
+ ...(summary.runtimeModelId ? { runtimeModelId: summary.runtimeModelId } : {}),
+ ...(summary.effortLevel ? { effortLevel: summary.effortLevel } : {}),
})
} catch {
// Skip unreadable files