pipeline: add activity timeout and waiting indicator for local models
Agentic steps now have a 5-minute inactivity timeout (non-Claude-Code models only) so stalled local LLM requests fail with a clear error instead of hanging forever. A "Waiting for model" indicator is emitted every 10s and shown in the pipeline UI stats bar, clearing as soon as the model starts responding. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+6
@@ -690,6 +690,12 @@ const PipelineRunner = ({ taskDirName, context, cwd }: PipelineRunnerProps) => {
|
||||
<span>{formatElapsed(pipeline.elapsed)}</span>
|
||||
{totalTokens > 0 && <span>{totalTokens.toLocaleString()} tok</span>}
|
||||
{rc.totalUSD > 0 && <span>${rc.totalUSD.toFixed(3)}</span>}
|
||||
{pipeline.waitingStatus && (
|
||||
<span className="ml-auto flex items-center gap-1.5 text-amber-500">
|
||||
<Loader2 className="h-3 w-3 animate-spin" />
|
||||
Waiting for model ({formatElapsed(pipeline.waitingStatus.elapsed)})
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
+12
-1
@@ -36,6 +36,7 @@ type ServerMessage =
|
||||
| { jobId: string; type: 'step:complete'; stepIndex: number; cost?: { inputTokens: number; outputTokens: number; totalUSD: number } }
|
||||
| { jobId: string; type: 'step:skip'; stepIndex: number; label: string; reason: string }
|
||||
| { jobId: string; type: 'step:parallel'; stepIndex: number; taskName: string; iterations: string[]; concurrency: number }
|
||||
| { jobId: string; type: 'step:waiting'; stepIndex: number; iterationLabel?: string; elapsed: number }
|
||||
| { jobId: string; type: 'iteration:start'; stepIndex: number; label: string }
|
||||
| { jobId: string; type: 'iteration:complete'; stepIndex: number; label: string; cost?: { inputTokens: number; outputTokens: number; totalUSD: number } }
|
||||
| { jobId: string; type: 'iteration:error'; stepIndex: number; label: string; error: string }
|
||||
@@ -64,6 +65,7 @@ export function usePipelineRunner() {
|
||||
const [hasError, setHasError] = useState(false);
|
||||
const [skippedItems, setSkippedItems] = useState<Array<{ label: string; reason: string }>>([]);
|
||||
const [elapsed, setElapsed] = useState(0);
|
||||
const [waitingStatus, setWaitingStatus] = useState<{ stepIndex: number; elapsed: number; iterationLabel?: string } | null>(null);
|
||||
const wsRef = useRef<WebSocket | null>(null);
|
||||
const streamBufferRef = useRef('');
|
||||
const startTimeRef = useRef<number>(0);
|
||||
@@ -120,6 +122,7 @@ export function usePipelineRunner() {
|
||||
flushStream();
|
||||
setMessages([]);
|
||||
setParallelStep(null);
|
||||
setWaitingStatus(null);
|
||||
inParallelRef.current = false;
|
||||
setCurrentStep({
|
||||
taskName: msg.taskName,
|
||||
@@ -130,6 +133,7 @@ export function usePipelineRunner() {
|
||||
|
||||
case 'step:complete':
|
||||
flushStream();
|
||||
setWaitingStatus(null);
|
||||
setCurrentStep((prev) => prev ? { ...prev, status: 'complete', cost: msg.cost } : null);
|
||||
if (msg.cost) addCost(msg.cost);
|
||||
break;
|
||||
@@ -138,6 +142,10 @@ export function usePipelineRunner() {
|
||||
setSkippedItems((prev) => [...prev, { label: msg.label, reason: msg.reason }]);
|
||||
break;
|
||||
|
||||
case 'step:waiting':
|
||||
setWaitingStatus({ stepIndex: msg.stepIndex, elapsed: msg.elapsed, iterationLabel: msg.iterationLabel });
|
||||
break;
|
||||
|
||||
case 'step:parallel':
|
||||
flushStream();
|
||||
setMessages([]);
|
||||
@@ -191,6 +199,7 @@ export function usePipelineRunner() {
|
||||
case 'assistant:delta':
|
||||
// Skip messages from parallel sub-agents (shown in iteration grid instead)
|
||||
if (inParallelRef.current && msg.iterationLabel) break;
|
||||
setWaitingStatus(null);
|
||||
streamBufferRef.current += msg.text;
|
||||
setStreamingText(streamBufferRef.current);
|
||||
break;
|
||||
@@ -208,6 +217,7 @@ export function usePipelineRunner() {
|
||||
|
||||
case 'tool:start':
|
||||
if (inParallelRef.current && msg.iterationLabel) break;
|
||||
setWaitingStatus(null);
|
||||
flushStream();
|
||||
setMessages((prev) => [
|
||||
...prev,
|
||||
@@ -302,6 +312,7 @@ export function usePipelineRunner() {
|
||||
setSkippedItems([]);
|
||||
setCurrentStep(null);
|
||||
setParallelStep(null);
|
||||
setWaitingStatus(null);
|
||||
setElapsed(0);
|
||||
setJobId(null);
|
||||
jobIdRef.current = null;
|
||||
@@ -323,6 +334,6 @@ export function usePipelineRunner() {
|
||||
|
||||
return {
|
||||
phase, isConnected, jobId, steps, currentStep, parallelStep, messages, streamingText,
|
||||
totalCost, runningCost, hasError, skippedItems, elapsed, run, stop,
|
||||
totalCost, runningCost, hasError, skippedItems, elapsed, waitingStatus, run, stop,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user