mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-07-15 21:11:08 +02:00
fix codex unresponsive
This commit is contained in:
parent
f2b5c6b1ab
commit
e6ff631191
8 changed files with 135 additions and 10 deletions
|
|
@ -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 />
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 && (
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue