+
{content}
)
@@ -278,7 +282,7 @@ export function ToolCallPart({ part, onFileClick, onChildSessionClick }: ToolCal
e.stopPropagation()
onChildSessionClick?.(sessionId)
}}
- className="text-purple-600 dark:text-purple-400 text-xs hover:text-purple-700 dark:hover:text-purple-300 cursor-pointer underline decoration-dotted flex items-center gap-1"
+ className="text-blue-600 dark:text-blue-400 text-xs hover:text-blue-700 dark:hover:text-blue-300 cursor-pointer underline decoration-dotted flex items-center gap-1"
title="View subagent session"
>
diff --git a/frontend/src/components/navigation/MoreDrawer.tsx b/frontend/src/components/navigation/MoreDrawer.tsx
index 894bf31f..306f1173 100644
--- a/frontend/src/components/navigation/MoreDrawer.tsx
+++ b/frontend/src/components/navigation/MoreDrawer.tsx
@@ -30,6 +30,7 @@ export function MoreDrawer({ isOpen, onClose }: MoreDrawerProps) {
const [commandsOpen, setCommandsOpen] = useState(false)
const [mentionFileBrowserOpen, setMentionFileBrowserOpen] = useState(false)
const swipeRef = useRef
(null)
+ const skipHistoryBackOnCloseRef = useRef(false)
const { bind } = useSwipeBack(onClose, { enabled: isOpen, suspendsRouteSwipe: true })
const { logout } = useAuth()
const { data: health } = useServerHealth()
@@ -55,6 +56,7 @@ export function MoreDrawer({ isOpen, onClose }: MoreDrawerProps) {
useEffect(() => {
if (!isOpen) return
+ skipHistoryBackOnCloseRef.current = false
let sentinelActive = true
const baseState = window.history.state
const baseUrl = window.location.href
@@ -68,10 +70,9 @@ export function MoreDrawer({ isOpen, onClose }: MoreDrawerProps) {
return () => {
window.removeEventListener('popstate', onPop)
// Only go back if sentinel is still active AND we haven't navigated away
- if (sentinelActive) {
+ if (sentinelActive && !skipHistoryBackOnCloseRef.current) {
sentinelActive = false
const top = window.history.state as { moreDrawerSentinel?: boolean } | null
- // Only go back if the current URL hasn't changed (i.e., no navigation occurred)
if (top?.moreDrawerSentinel && window.location.href === baseUrl) {
window.history.back()
}
@@ -95,6 +96,7 @@ export function MoreDrawer({ isOpen, onClose }: MoreDrawerProps) {
newParams.delete('mobileTab')
newParams.set('settings', 'open')
newParams.set('tab', 'account')
+ skipHistoryBackOnCloseRef.current = true
navigate({ search: newParams.toString() }, { replace: true })
onClose()
}
@@ -109,11 +111,13 @@ export function MoreDrawer({ isOpen, onClose }: MoreDrawerProps) {
const handleItemClick = (item: ReturnType[0]) => {
if (item.to) {
+ skipHistoryBackOnCloseRef.current = true
navigate(item.to)
} else if (item.dialog) {
const newParams = new URLSearchParams(location.search)
newParams.set('dialog', item.dialog)
newParams.delete('mobileTab')
+ skipHistoryBackOnCloseRef.current = true
navigate({ search: newParams.toString() }, { replace: true })
}
onClose()
diff --git a/frontend/src/hooks/useContextUsage.ts b/frontend/src/hooks/useContextUsage.ts
index d5347cfa..7084b50b 100644
--- a/frontend/src/hooks/useContextUsage.ts
+++ b/frontend/src/hooks/useContextUsage.ts
@@ -1,7 +1,6 @@
import { useMemo } from 'react'
import { useMessages } from './useOpenCode'
import { useQuery } from '@tanstack/react-query'
-import { useModelSelection } from './useModelSelection'
import { fetchWrapper } from '@/api/fetchWrapper'
interface ContextUsage {
@@ -39,8 +38,6 @@ async function fetchProviders(opcodeUrl: string): Promise {
export const useContextUsage = (opcodeUrl: string | null | undefined, sessionID: string | undefined, directory?: string): ContextUsage => {
const { data: messages, isLoading: messagesLoading } = useMessages(opcodeUrl, sessionID, directory)
- const { modelString: globalModelString } = useModelSelection(opcodeUrl, directory)
- const modelString = globalModelString
const { data: providersData } = useQuery({
queryKey: ['providers', opcodeUrl],
@@ -53,8 +50,6 @@ export const useContextUsage = (opcodeUrl: string | null | undefined, sessionID:
})
return useMemo(() => {
- const currentModel = modelString || null
-
const assistantMessages = messages?.filter(msg => msg.info.role === 'assistant') || []
let latestAssistantMessage = assistantMessages[assistantMessages.length - 1]
@@ -66,6 +61,17 @@ export const useContextUsage = (opcodeUrl: string | null | undefined, sessionID:
}
}
+ const currentModel = (() => {
+ if (!latestAssistantMessage || latestAssistantMessage.info.role !== 'assistant') {
+ return null
+ }
+ const msg = latestAssistantMessage.info as { providerID?: string; modelID?: string }
+ if (msg.providerID && msg.modelID) {
+ return `${msg.providerID}/${msg.modelID}`
+ }
+ return null
+ })()
+
let contextLimit: number | null = null
if (currentModel && providersData) {
const [providerId, modelId] = currentModel.split('/')
@@ -103,5 +109,5 @@ export const useContextUsage = (opcodeUrl: string | null | undefined, sessionID:
currentModel,
isLoading: false
}
- }, [messages, messagesLoading, modelString, providersData])
+ }, [messages, messagesLoading, providersData])
}
diff --git a/frontend/src/stores/modelStore.ts b/frontend/src/stores/modelStore.ts
index c8e389af..69108726 100644
--- a/frontend/src/stores/modelStore.ts
+++ b/frontend/src/stores/modelStore.ts
@@ -9,6 +9,7 @@ export interface ModelSelection {
interface ModelStore {
model: ModelSelection | null
+ agentModels: Record
recentModels: ModelSelection[]
favoriteModels: ModelSelection[]
variants: Record
@@ -16,6 +17,8 @@ interface ModelStore {
setModel: (model: ModelSelection) => void
setActiveModel: (model: ModelSelection) => void
+ setAgentModel: (agent: string, model: ModelSelection) => void
+ getAgentModel: (agent: string) => ModelSelection | null
syncModelState: (state: { recent: ModelSelection[], favorite: ModelSelection[], variant: Record }) => void
toggleFavorite: (model: ModelSelection) => void
syncFromConfig: (configModel: string | undefined, force?: boolean) => void
@@ -39,6 +42,7 @@ export const useModelStore = create()(
persist(
(set, get) => ({
model: null,
+ agentModels: {},
recentModels: [],
favoriteModels: [],
variants: {},
@@ -64,6 +68,20 @@ export const useModelStore = create()(
set({ model })
},
+ setAgentModel: (agent: string, model: ModelSelection) => {
+ set((state) => ({
+ agentModels: {
+ ...state.agentModels,
+ [agent]: model,
+ },
+ }))
+ },
+
+ getAgentModel: (agent: string) => {
+ const state = get()
+ return state.agentModels[agent] ?? null
+ },
+
syncModelState: (modelState) => {
set((state) => ({
recentModels: modelState.recent,
@@ -182,6 +200,7 @@ export const useModelStore = create()(
name: 'opencode-model-selection',
partialize: (state) => ({
model: state.model,
+ agentModels: state.agentModels,
recentModels: state.recentModels,
favoriteModels: state.favoriteModels,
variants: state.variants,
From 0a90e9e337d129a0fe9cb17fe8f3498785d952a9 Mon Sep 17 00:00:00 2001
From: Chris Scott <99081550+chriswritescode-dev@users.noreply.github.com>
Date: Mon, 4 May 2026 21:23:05 -0400
Subject: [PATCH 2/2] refactor: improve per-agent model selection and task tool
call status indicators
---
.../src/components/message/PromptInput.tsx | 23 +++++-------
.../src/components/message/ToolCallPart.tsx | 36 ++++++++++++++-----
frontend/src/components/ui/side-drawer.tsx | 2 +-
frontend/src/pages/SessionDetail.tsx | 2 +-
4 files changed, 37 insertions(+), 26 deletions(-)
diff --git a/frontend/src/components/message/PromptInput.tsx b/frontend/src/components/message/PromptInput.tsx
index 3f97faaa..a64d4190 100644
--- a/frontend/src/components/message/PromptInput.tsx
+++ b/frontend/src/components/message/PromptInput.tsx
@@ -243,9 +243,7 @@ export const PromptInput = memo(forwardRef(
queued: true
})
setStoredAgent(sessionID, agentUsed)
- if (sessionModel) {
- setStoredModel({ providerID: sessionModel.providerID, modelID: sessionModel.modelID })
- } else if (model) {
+ if (model) {
setStoredModel({ providerID: model.providerID, modelID: model.modelID })
}
setPrompt('')
@@ -299,9 +297,7 @@ export const PromptInput = memo(forwardRef(
})
setStoredAgent(sessionID, agentUsed)
- if (sessionModel) {
- setStoredModel({ providerID: sessionModel.providerID, modelID: sessionModel.modelID })
- } else if (model) {
+ if (model) {
setStoredModel({ providerID: model.providerID, modelID: model.modelID })
}
setPrompt('')
@@ -1021,20 +1017,17 @@ if (isIOS && isSecureContext && navigator.clipboard && navigator.clipboard.read)
}
}, [clearStoreVariant, sessionAgent.model, sessionAgent.variant, sessionModelSyncKey, setActiveModel, setStoreVariant])
- const sessionModel = sessionAgent.model
- const sessionModelString = sessionModel ? `${sessionModel.providerID}/${sessionModel.modelID}` : null
- const currentModel = sessionModelString || modelString || ''
+ const currentModel = modelString || ''
const displayModelName = useMemo(() => {
- const activeModel = sessionModel || model
- if (!activeModel) {
+ if (!model) {
return currentModel
}
- const provider = providersData?.providers.find((item) => item.id === activeModel.providerID)
- const modelData = provider?.models?.[activeModel.modelID]
+ const provider = providersData?.providers.find((item) => item.id === model.providerID)
+ const modelData = provider?.models?.[model.modelID]
- return modelData ? formatModelName(modelData) : activeModel.modelID || currentModel
- }, [currentModel, sessionModel, model, providersData])
+ return modelData ? formatModelName(modelData) : model.modelID || currentModel
+ }, [currentModel, model, providersData])
const isMobile = useMobile()
const { setShowDialog, hasForSession: hasPermissionsForSession } = usePermissions()
const hasPendingPermissionForSession = hasPermissionsForSession(sessionID)
diff --git a/frontend/src/components/message/ToolCallPart.tsx b/frontend/src/components/message/ToolCallPart.tsx
index 617a2480..77ceae93 100644
--- a/frontend/src/components/message/ToolCallPart.tsx
+++ b/frontend/src/components/message/ToolCallPart.tsx
@@ -2,6 +2,7 @@ import { useState, useRef, useEffect } from 'react'
import type { components } from '@/api/opencode-types'
import { useSettings } from '@/hooks/useSettings'
import { useUserBash } from '@/stores/userBashStore'
+import { useSessionStatusForSession } from '@/stores/sessionStatusStore'
import { usePermissions, useQuestions } from '@/contexts/EventContext'
import { detectFileReferences } from '@/lib/fileReferences'
import { ExternalLink, Loader2 } from 'lucide-react'
@@ -68,6 +69,8 @@ function ClickableJson({ json, onFileClick }: { json: unknown; onFileClick?: (fi
export function ToolCallPart({ part, onFileClick, onChildSessionClick }: ToolCallPartProps) {
const { preferences } = useSettings()
const { userBashCommands } = useUserBash()
+ const taskSessionId = part.tool === 'task' ? getTaskSessionId(part) : undefined
+ const taskSessionStatus = useSessionStatusForSession(taskSessionId)
const { getForCallID: getPermissionForCallID } = usePermissions()
const { getForCallID: getQuestionForCallID } = useQuestions()
const outputRef = useRef(null)
@@ -152,20 +155,35 @@ export function ToolCallPart({ part, onFileClick, onChildSessionClick }: ToolCal
const isFileTool = ['read', 'write', 'edit'].includes(part.tool)
if (part.tool === 'task') {
- const sessionId = getTaskSessionId(part)
+ const sessionId = taskSessionId
const description = previewText || 'Sub-agent task'
- const isRunning = part.state.status === 'running' || part.state.status === 'pending'
+ const status = part.state.status
+
+ const isPending = status === 'pending'
+ const isRunning = status === 'running' && taskSessionStatus.type !== 'idle'
+ const isCompleted = status === 'completed' || (status === 'running' && !!sessionId && taskSessionStatus.type === 'idle')
+ const isError = status === 'error'
+
const content = (
- {isRunning ? (
-
-
-
-
+ {isPending && (
+
+
+
+
- ) : null}
+ )}
+ {isRunning && (
+
+
+
+
+
+ )}
+ {isCompleted &&
✓}
+ {isError &&
✗}
{description}
-
sub-agent
+
sub-agent
{sessionId &&
}
)
diff --git a/frontend/src/components/ui/side-drawer.tsx b/frontend/src/components/ui/side-drawer.tsx
index cea8ac2f..f105d99d 100644
--- a/frontend/src/components/ui/side-drawer.tsx
+++ b/frontend/src/components/ui/side-drawer.tsx
@@ -69,7 +69,7 @@ export function SideDrawer({
/>
-
+
{repoLoading || assistantModeLoading || sessionLoading || messagesLoading ? (