fix codex unresponsive

This commit is contained in:
Arjun 2026-06-30 21:57:05 +05:30 committed by arkml
parent f2b5c6b1ab
commit e6ff631191
8 changed files with 135 additions and 10 deletions

View file

@ -103,7 +103,8 @@ export function CodeChat({
onOpenDiff: (path: string) => void
}) {
const {
items, liveText, isProcessing, pendingPermission, pendingToolPermissions, pendingAskHumans,
items, liveText, isProcessing, compactionStatus, contextUsage,
pendingPermission, pendingToolPermissions, pendingAskHumans,
loading, send, stop, resolvePermission, respondToToolPermission, respondToAskHuman,
} = useCodeChat(session)
const [draft, setDraft] = useState('')
@ -111,6 +112,9 @@ export function CodeChat({
const textareaRef = useRef<HTMLTextAreaElement>(null)
const busy = isProcessing || status === 'working' || status === 'needs-you'
const contextUsedPercent = contextUsage
? Math.min(100, Math.round((contextUsage.used / contextUsage.size) * 100))
: null
// Attached file PATHS — like dragging a file into the Claude Code CLI, the
// agent receives paths and reads the files itself with its own tools.
const [attachments, setAttachments] = useState<string[]>([])
@ -188,7 +192,10 @@ export function CodeChat({
<Terminal className="size-3.5 shrink-0 text-muted-foreground" />
<div className="min-w-0 flex-1">
<div className="truncate text-sm font-medium">{session.title}</div>
<div className="text-[11px] text-muted-foreground">{AGENT_LABEL[session.agent]} direct</div>
<div className="text-[11px] text-muted-foreground">
{AGENT_LABEL[session.agent]} direct
{contextUsedPercent != null ? ` · ${contextUsedPercent}% context used` : ''}
</div>
</div>
</div>
@ -239,9 +246,19 @@ export function CodeChat({
/>
))}
{busy && !pendingPermission && pendingToolPermissions.size === 0 && pendingAskHumans.size === 0 && (
<Shimmer className="text-sm">
{stopping ? 'Stopping…' : `${AGENT_LABEL[session.agent]} is working…`}
</Shimmer>
compactionStatus === 'stalled' ? (
<div className="text-sm text-amber-600">
Context compaction is taking longer than expected. You can stop and retry in a fresh session.
</div>
) : (
<Shimmer className="text-sm">
{stopping
? 'Stopping…'
: compactionStatus === 'running'
? 'Compacting context…'
: `${AGENT_LABEL[session.agent]} is working…`}
</Shimmer>
)
)}
</ConversationContent>
<ConversationScrollButton />

View file

@ -41,6 +41,10 @@ export interface PendingCodePermission {
const DIRECT_PREFIX = 'direct-'
const STRUCTURAL_EVENTS = new Set(['tool_call', 'tool_call_update', 'plan', 'permission'])
const COMPACTION_TITLE = 'Compacting context'
const COMPACTION_STALLED_MS = 90_000
export type CompactionStatus = 'idle' | 'running' | 'stalled'
function messageText(content: unknown): string {
if (typeof content === 'string') return content
@ -62,6 +66,8 @@ export function useCodeChat(session: CodeSession | null) {
const [items, setItems] = useState<CodeChatItem[]>([])
const [liveText, setLiveText] = useState('')
const [isProcessing, setIsProcessing] = useState(false)
const [compactionStatus, setCompactionStatus] = useState<CompactionStatus>('idle')
const [contextUsage, setContextUsage] = useState<{ used: number; size: number } | null>(null)
const [pendingPermission, setPendingPermission] = useState<PendingCodePermission | null>(null)
// Rowboat-mode copilot gates, same as the main chat: pre-tool-call permission
// requests and ask-human questions. Keyed by toolCallId.
@ -69,6 +75,7 @@ export function useCodeChat(session: CodeSession | null) {
const [pendingAskHumans, setPendingAskHumans] = useState<Map<string, z.infer<typeof AskHumanRequestEvent>>>(new Map())
const [loading, setLoading] = useState(false)
const seenMessageIdsRef = useRef<Set<string>>(new Set())
const compactionToolIdRef = useRef<string | null>(null)
const applyCodeRunEvent = useCallback((toolCallId: string, event: CodeRunEvent) => {
if (toolCallId.startsWith(DIRECT_PREFIX)) {
@ -99,6 +106,9 @@ export function useCodeChat(session: CodeSession | null) {
if (!sessionId) {
setItems([])
setLiveText('')
setCompactionStatus('idle')
setContextUsage(null)
compactionToolIdRef.current = null
setPendingPermission(null)
return
}
@ -106,6 +116,9 @@ export function useCodeChat(session: CodeSession | null) {
setLoading(true)
setItems([])
setLiveText('')
setCompactionStatus('idle')
setContextUsage(null)
compactionToolIdRef.current = null
setPendingPermission(null)
setPendingToolPermissions(new Map())
setPendingAskHumans(new Map())
@ -217,6 +230,12 @@ export function useCodeChat(session: CodeSession | null) {
return () => { cancelled = true }
}, [sessionId])
useEffect(() => {
if (compactionStatus !== 'running') return
const timer = window.setTimeout(() => setCompactionStatus('stalled'), COMPACTION_STALLED_MS)
return () => window.clearTimeout(timer)
}, [compactionStatus])
// Live event stream.
useEffect(() => {
if (!sessionId) return
@ -230,6 +249,8 @@ export function useCodeChat(session: CodeSession | null) {
break
case 'run-processing-end':
setIsProcessing(false)
setCompactionStatus('idle')
compactionToolIdRef.current = null
setPendingPermission(null)
// Anything still streaming that never landed as a message (e.g. the
// turn errored) is flushed so the text isn't lost.
@ -247,6 +268,8 @@ export function useCodeChat(session: CodeSession | null) {
break
case 'run-stopped':
setIsProcessing(false)
setCompactionStatus('idle')
compactionToolIdRef.current = null
setPendingPermission(null)
setPendingToolPermissions(new Map())
setPendingAskHumans(new Map())
@ -348,6 +371,19 @@ export function useCodeChat(session: CodeSession | null) {
break
case 'code-run-event': {
setIsProcessing(true)
if (event.event.type === 'usage') {
setContextUsage({ used: event.event.used, size: event.event.size })
}
if (event.event.type === 'tool_call' && event.event.title === COMPACTION_TITLE) {
compactionToolIdRef.current = event.event.id ?? null
setCompactionStatus('running')
}
if (event.event.type === 'tool_call_update'
&& event.event.id != null
&& event.event.id === compactionToolIdRef.current) {
compactionToolIdRef.current = null
setCompactionStatus('idle')
}
if (event.event.type === 'message' && event.event.role === 'agent' && event.toolCallId.startsWith(DIRECT_PREFIX)) {
const text = event.event.text
setLiveText((prev) => prev + text)
@ -460,6 +496,8 @@ export function useCodeChat(session: CodeSession | null) {
items,
liveText,
isProcessing,
compactionStatus,
contextUsage,
pendingPermission,
pendingToolPermissions,
pendingAskHumans,

View file

@ -9,6 +9,7 @@ import {
Pencil,
Search,
ShieldQuestion,
Sparkles,
Terminal,
Trash2,
Wrench,
@ -87,7 +88,8 @@ export function reduceEvents(events: CodeRunEvent[]): Row[] {
return rows
}
function toolKindIcon(kind?: string) {
function toolKindIcon(kind?: string, title?: string) {
if (title === 'Compacting context') return <Sparkles className="size-3.5 shrink-0 text-muted-foreground" />
switch (kind) {
case 'read': return <Eye className="size-3.5 shrink-0 text-muted-foreground" />
case 'edit': return <Pencil className="size-3.5 shrink-0 text-muted-foreground" />
@ -137,7 +139,7 @@ export function CodingRunTimeline({
{running
? <Loader className="size-3.5 shrink-0 animate-spin text-muted-foreground" />
: <CheckCircle2 className="size-3.5 shrink-0 text-green-600" />}
{toolKindIcon(row.toolKind)}
{toolKindIcon(row.toolKind, row.title)}
<span className="truncate text-foreground/90">{row.title ?? row.toolKind ?? 'Tool call'}</span>
</div>
{row.diffs.length > 0 && (