diff --git a/apps/backend/internal/orchestrator/event_handlers_workflow.go b/apps/backend/internal/orchestrator/event_handlers_workflow.go index 0dfaf27c7d..7c885c5a40 100644 --- a/apps/backend/internal/orchestrator/event_handlers_workflow.go +++ b/apps/backend/internal/orchestrator/event_handlers_workflow.go @@ -548,6 +548,19 @@ func (s *Service) autoStartStepPrompt( // Record a user message so the auto-start prompt is visible in chat history. s.recordAutoStartMessage(ctx, taskID, sessionID, prompt, planMode) + // If the session is in CREATED state, the agent was never started (e.g. workspace-only + // preparation from a blocked auto-start). PromptTask will reject CREATED sessions, + // so use StartCreatedSession which properly launches the agent on the prepared workspace. + // Pass skipMessageRecord=true since recordAutoStartMessage above already recorded it. + if session.State == models.TaskSessionStateCreated { + s.logger.Info("auto-start: session is CREATED, launching agent via StartCreatedSession", + zap.String("task_id", taskID), + zap.String("session_id", sessionID), + zap.String("step_name", stepName)) + _, err := s.StartCreatedSession(ctx, taskID, sessionID, session.AgentProfileID, prompt, true, planMode, nil) + return err + } + const maxRetryAttempts = 5 for attempt := 1; attempt <= maxRetryAttempts; attempt++ { _, err := s.PromptTask(ctx, taskID, sessionID, prompt, "", planMode, nil) diff --git a/apps/web/components/task/chat/message-renderer.tsx b/apps/web/components/task/chat/message-renderer.tsx index 8963c4b64a..791dcbf8ab 100644 --- a/apps/web/components/task/chat/message-renderer.tsx +++ b/apps/web/components/task/chat/message-renderer.tsx @@ -7,6 +7,7 @@ import type { Message, TaskSessionState, TaskState } from "@/lib/types/http"; import type { ToolCallMetadata } from "@/components/task/chat/types"; import { launchSession } from "@/lib/services/session-launch-service"; import { buildStartCreatedRequest } from "@/lib/services/session-launch-helpers"; +import { useAppStore } from "@/components/state-provider"; import { ChatMessage } from "@/components/task/chat/messages/chat-message"; import { PermissionRequestMessage } from "@/components/task/chat/messages/permission-request-message"; import { StatusMessage } from "@/components/task/chat/messages/status-message"; @@ -39,6 +40,10 @@ type AdapterContext = { function TaskDescriptionStartButton({ taskId, sessionId }: { taskId: string; sessionId: string }) { const [isStarting, setIsStarting] = useState(false); + const prepareStatus = useAppStore( + (state) => state.prepareProgress.bySessionId[sessionId]?.status ?? null, + ); + const handleStart = useCallback(async () => { setIsStarting(true); try { @@ -51,6 +56,9 @@ function TaskDescriptionStartButton({ taskId, sessionId }: { taskId: string; ses } }, [taskId, sessionId]); + // Hide while environment is being prepared + if (prepareStatus === "preparing") return null; + return (