Follow up after stop (vibe-kanban 92c931e6) (#224)

I want the user to be able to continue a task after he pressed the stop attempt button via the follow-up functionality
This commit is contained in:
Alex Netsch
2025-07-18 10:02:36 +01:00
committed by GitHub
parent 48e608993a
commit 8057aca176
8 changed files with 88 additions and 52 deletions

View File

@@ -102,7 +102,7 @@ export function TaskCard({
<CheckCircle className="h-3 w-3 text-green-500" />
)}
{/* Failed Indicator */}
{task.has_failed_attempt && !task.has_merged_attempt && (
{task.last_attempt_failed && !task.has_merged_attempt && (
<XCircle className="h-3 w-3 text-red-500" />
)}
{/* Actions Menu */}

View File

@@ -78,12 +78,33 @@ function LogsTab() {
// When setup failed or was stopped
if (isSetupFailed || isSetupStopped) {
const setupProcess = executionState.setup_process_id
let setupProcess = executionState.setup_process_id
? attemptData.runningProcessDetails[executionState.setup_process_id]
: Object.values(attemptData.runningProcessDetails).find(
(process) => process.process_type === 'setupscript'
);
// If not found in runningProcessDetails, try to find in processes array
if (!setupProcess) {
const setupSummary = attemptData.processes.find(
(process) => process.process_type === 'setupscript'
);
if (setupSummary) {
setupProcess = Object.values(attemptData.runningProcessDetails).find(
(process) => process.id === setupSummary.id
);
if (!setupProcess) {
setupProcess = {
...setupSummary,
stdout: null,
stderr: null,
} as any;
}
}
}
return (
<div className="h-full overflow-y-auto">
<div className="mb-4">
@@ -106,38 +127,15 @@ function LogsTab() {
);
}
// When coding agent failed or was stopped
if (isCodingAgentFailed || isCodingAgentStopped) {
const codingAgentProcess = executionState.coding_agent_process_id
? attemptData.runningProcessDetails[
executionState.coding_agent_process_id
]
: Object.values(attemptData.runningProcessDetails).find(
(process) => process.process_type === 'codingagent'
);
return (
<div className="h-full overflow-y-auto">
<div className="mb-4">
<p
className={`text-lg font-semibold mb-2 ${isCodingAgentFailed ? 'text-destructive' : ''}`}
>
{isCodingAgentFailed
? 'Coding Agent Failed'
: 'Coding Agent Stopped'}
</p>
{isCodingAgentFailed && (
<p className="text-muted-foreground mb-4">
The coding agent encountered an error. Error details below:
</p>
)}
</div>
{codingAgentProcess && (
<NormalizedConversationViewer executionProcess={codingAgentProcess} />
)}
</div>
);
// When coding agent is in any state (running, complete, failed, stopped)
if (
isCodingAgentRunning ||
isCodingAgentComplete ||
isCodingAgentFailed ||
isCodingAgentStopped ||
hasChanges
) {
return <Conversation />;
}
// When setup is complete but coding agent hasn't started, show waiting state

View File

@@ -171,6 +171,21 @@ function Conversation() {
allEntries,
]);
// Check if we should show the status banner - only if the most recent process failed/stopped
const getMostRecentProcess = () => {
if (followUpLogs.length > 0) {
// Sort by creation time or use last in array as most recent
return followUpLogs[followUpLogs.length - 1];
}
return mainCodingAgentLog;
};
const mostRecentProcess = getMostRecentProcess();
const showStatusBanner =
mostRecentProcess &&
(mostRecentProcess.status === 'failed' ||
mostRecentProcess.status === 'killed');
return (
<div
ref={scrollContainerRef}
@@ -209,6 +224,28 @@ function Conversation() {
className="py-8"
/>
)}
{/* Status banner for failed/stopped states - shown at bottom */}
{showStatusBanner && mostRecentProcess && (
<div className="mt-4 p-4 rounded-lg border">
<p
className={`text-lg font-semibold mb-2 ${
mostRecentProcess.status === 'failed'
? 'text-destructive'
: 'text-orange-600'
}`}
>
{mostRecentProcess.status === 'failed'
? 'Coding Agent Failed'
: 'Coding Agent Stopped'}
</p>
<p className="text-muted-foreground">
{mostRecentProcess.status === 'failed'
? 'The coding agent encountered an error.'
: 'The coding agent was stopped.'}
</p>
</div>
)}
</div>
);
}

View File

@@ -32,12 +32,13 @@ export function TaskFollowUpSection() {
return false;
}
const completedCodingAgentProcesses = attemptData.processes.filter(
const completedOrKilledCodingAgentProcesses = attemptData.processes.filter(
(process) =>
process.process_type === 'codingagent' && process.status === 'completed'
process.process_type === 'codingagent' &&
(process.status === 'completed' || process.status === 'killed')
);
return completedCodingAgentProcesses.length > 0;
return completedOrKilledCodingAgentProcesses.length > 0;
}, [
selectedAttempt,
attemptData.processes,
@@ -81,7 +82,7 @@ export function TaskFollowUpSection() {
)}
<div className="flex gap-2 items-start">
<FileSearchTextarea
placeholder="Ask a follow-up question... Type @ to search files."
placeholder="Continue working on this task... Type @ to search files."
value={followUpMessage}
onChange={(value) => {
setFollowUpMessage(value);