Run setup script in parallel with coding agent (vibe-kanban) (#1446)
* The implementation is complete. Here's a summary of all the changes made: ## Summary ### Files Created - `crates/db/migrations/20251206000000_add_parallel_setup_script_to_projects.sql` - Database migration ### Files Modified **Backend (Rust):** - `crates/db/src/models/project.rs` - Added `parallel_setup_script: bool` field to `Project`, `Option<bool>` to `CreateProject`/`UpdateProject`, updated all SQL queries - `crates/services/src/services/container.rs` - Modified `start_attempt()` to support parallel execution when `parallel_setup_script` is true - `crates/server/src/routes/projects.rs` - Updated create/update handlers to include new field - `crates/deployment/src/lib.rs` - Added field to auto-created projects **Frontend (TypeScript/React):** - `frontend/src/pages/settings/ProjectSettings.tsx` - Added checkbox UI for parallel setup script setting - `frontend/src/components/dialogs/projects/ProjectFormDialog.tsx` - Added field to create data - `frontend/src/components/tasks/TaskDetails/preview/NoServerContent.tsx` - Added field to update data **i18n:** - `frontend/src/i18n/locales/en/settings.json` - Added translation strings - `frontend/src/i18n/locales/es/settings.json` - Added Spanish translations - `frontend/src/i18n/locales/ja/settings.json` - Added Japanese translations - `frontend/src/i18n/locales/ko/settings.json` - Added Korean translations **SQLx Cache:** - `crates/db/.sqlx/` - Updated query cache files ### Behavior - When `parallel_setup_script` is `false` (default): Sequential execution (setup → coding agent → cleanup) - When `parallel_setup_script` is `true`: Setup script and coding agent start simultaneously, setup script failure does not stop the coding agent * update useConversationHistory to display multiple running conversation threads at once * The bug fix is complete. Here's a summary of the changes: ## Bug Fix Summary **Problem:** When running in parallel mode, if the setup script finishes before the coding agent, the task incorrectly moves to "InReview" status while the coding agent is still running. **Root Cause:** The `should_finalize()` method returned `true` when `next_action.is_none()`, which is the case for setup scripts in parallel mode. This caused premature task finalization. **Solution:** Modified `should_finalize()` to check if there are other running non-DevServer processes for the same task attempt before finalizing. ### Files Modified 1. **`crates/db/src/models/execution_process.rs`** - Added `has_running_non_dev_server_processes_excluding()` method that accepts an optional `exclude_id` parameter - Refactored existing `has_running_non_dev_server_processes()` to delegate to the new method 2. **`crates/services/src/services/container.rs`** - Changed `should_finalize()` from sync to async - Added check for other running processes before allowing finalization 3. **`crates/local-deployment/src/container.rs`** - Updated call site to use `.await` on the now-async `should_finalize()` * The bug fix is complete. Here's a summary of the changes: **Fixed files:** 1. **`crates/services/src/services/container.rs`** - Simplified `should_finalize()`: - Changed from `async fn` back to `fn` (non-async) - Removed the complex database check for other running processes - Added a simple check: if the run_reason is `SetupScript` AND `next_action.is_none()` (parallel mode), return `false` (don't finalize) 2. **`crates/local-deployment/src/container.rs`** - Removed `.await` from the `should_finalize()` call 3. **`crates/db/src/models/execution_process.rs`** - Cleaned up by removing the unused `has_running_non_dev_server_processes_excluding()` function and simplifying `has_running_non_dev_server_processes()` back to its original form **How the fix works:** - In **parallel mode**: Setup scripts have `next_action: None`, so the new check catches this case and prevents finalization - In **sequential mode**: Setup scripts have `next_action: Some(coding_agent)`, so they pass this check but won't finalize anyway because `next_action.is_none()` returns `false`
This commit is contained in:
committed by
GitHub
parent
7da884bc3a
commit
76877ea631
@@ -50,7 +50,7 @@ interface UseConversationHistoryResult {}
|
||||
const MIN_INITIAL_ENTRIES = 10;
|
||||
const REMAINING_BATCH_SIZE = 50;
|
||||
|
||||
const loadingPatch: PatchTypeWithKey = {
|
||||
const makeLoadingPatch = (executionProcessId: string): PatchTypeWithKey => ({
|
||||
type: 'NORMALIZED_ENTRY',
|
||||
content: {
|
||||
entry_type: {
|
||||
@@ -59,9 +59,9 @@ const loadingPatch: PatchTypeWithKey = {
|
||||
content: '',
|
||||
timestamp: null,
|
||||
},
|
||||
patchKey: 'loading',
|
||||
executionProcessId: '',
|
||||
};
|
||||
patchKey: `${executionProcessId}:loading`,
|
||||
executionProcessId,
|
||||
});
|
||||
|
||||
const nextActionPatch: (
|
||||
failed: boolean,
|
||||
@@ -99,7 +99,7 @@ export const useConversationHistory = ({
|
||||
const executionProcesses = useRef<ExecutionProcess[]>(executionProcessesRaw);
|
||||
const displayedExecutionProcesses = useRef<ExecutionProcessStateStore>({});
|
||||
const loadedInitialEntries = useRef(false);
|
||||
const lastActiveProcessId = useRef<string | null>(null);
|
||||
const streamingProcessIdsRef = useRef<Set<string>>(new Set());
|
||||
const onEntriesUpdatedRef = useRef<OnEntriesUpdated | null>(null);
|
||||
|
||||
const mergeIntoDisplayed = (
|
||||
@@ -191,16 +191,14 @@ export const useConversationHistory = ({
|
||||
.flatMap((p) => p.entries);
|
||||
};
|
||||
|
||||
const getActiveAgentProcess = (): ExecutionProcess | null => {
|
||||
const activeProcesses = executionProcesses?.current.filter(
|
||||
(p) =>
|
||||
p.status === ExecutionProcessStatus.running &&
|
||||
p.run_reason !== 'devserver'
|
||||
const getActiveAgentProcesses = (): ExecutionProcess[] => {
|
||||
return (
|
||||
executionProcesses?.current.filter(
|
||||
(p) =>
|
||||
p.status === ExecutionProcessStatus.running &&
|
||||
p.run_reason !== 'devserver'
|
||||
) ?? []
|
||||
);
|
||||
if (activeProcesses.length > 1) {
|
||||
console.error('More than one active execution process found');
|
||||
}
|
||||
return activeProcesses[0] || null;
|
||||
};
|
||||
|
||||
const flattenEntriesForEmit = useCallback(
|
||||
@@ -312,7 +310,7 @@ export const useConversationHistory = ({
|
||||
}
|
||||
|
||||
if (isProcessRunning && !hasPendingApprovalEntry) {
|
||||
entries.push(loadingPatch);
|
||||
entries.push(makeLoadingPatch(p.executionProcess.id));
|
||||
}
|
||||
} else if (
|
||||
p.executionProcess.executor_action.typ.type === 'ScriptRequest'
|
||||
@@ -625,24 +623,32 @@ export const useConversationHistory = ({
|
||||
]); // include idListKey so new processes trigger reload
|
||||
|
||||
useEffect(() => {
|
||||
const activeProcess = getActiveAgentProcess();
|
||||
if (!activeProcess) return;
|
||||
const activeProcesses = getActiveAgentProcesses();
|
||||
if (activeProcesses.length === 0) return;
|
||||
|
||||
if (!displayedExecutionProcesses.current[activeProcess.id]) {
|
||||
const runningOrInitial =
|
||||
Object.keys(displayedExecutionProcesses.current).length > 1
|
||||
? 'running'
|
||||
: 'initial';
|
||||
ensureProcessVisible(activeProcess);
|
||||
emitEntries(displayedExecutionProcesses.current, runningOrInitial, false);
|
||||
}
|
||||
for (const activeProcess of activeProcesses) {
|
||||
if (!displayedExecutionProcesses.current[activeProcess.id]) {
|
||||
const runningOrInitial =
|
||||
Object.keys(displayedExecutionProcesses.current).length > 1
|
||||
? 'running'
|
||||
: 'initial';
|
||||
ensureProcessVisible(activeProcess);
|
||||
emitEntries(
|
||||
displayedExecutionProcesses.current,
|
||||
runningOrInitial,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
activeProcess.status === ExecutionProcessStatus.running &&
|
||||
lastActiveProcessId.current !== activeProcess.id
|
||||
) {
|
||||
lastActiveProcessId.current = activeProcess.id;
|
||||
loadRunningAndEmitWithBackoff(activeProcess);
|
||||
if (
|
||||
activeProcess.status === ExecutionProcessStatus.running &&
|
||||
!streamingProcessIdsRef.current.has(activeProcess.id)
|
||||
) {
|
||||
streamingProcessIdsRef.current.add(activeProcess.id);
|
||||
loadRunningAndEmitWithBackoff(activeProcess).finally(() => {
|
||||
streamingProcessIdsRef.current.delete(activeProcess.id);
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [
|
||||
attempt.id,
|
||||
@@ -673,7 +679,7 @@ export const useConversationHistory = ({
|
||||
useEffect(() => {
|
||||
displayedExecutionProcesses.current = {};
|
||||
loadedInitialEntries.current = false;
|
||||
lastActiveProcessId.current = null;
|
||||
streamingProcessIdsRef.current.clear();
|
||||
emitEntries(displayedExecutionProcesses.current, 'initial', true);
|
||||
}, [attempt.id, emitEntries]);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user