Smooth cursor onboarding (#1143)
* WIP cursor onboarding
Claude rebase fixes
fmt
WIP
WIP
WIP Add login button
Remove auto login check
Login needed detection
Setup script for login and install
Fix install+login case
Update task to inreview upon failure, cleanup
Feedback in next action box
Use errortype isntead of next action to communicate setup needed
* Add auto retry after install
* Fix setup needed detection
* Fix next_run_reason
* Lint and format
* i18n
* Rename to setup helper
* rename ErrorType to NormalizedEntryError
* Better setupHelpText i18n (vibe-kanban a8dd795e)
frontend/src/i18n/locales/en/tasks.json setupHelpText currently isn't very informative. We should change it to
"{agent_name} isn't setup correctly. Click 'Run Setup' to install it and login." or similar.
* Cursor login should be exempt from the 2 execution process limit (vibe-kanban deca56cd)
frontend/src/components/NormalizedConversation/NextActionCard.tsx
Display run setup even if there are more than 2 execution processes
* Move agent setup to server
This commit is contained in:
@@ -793,6 +793,7 @@ function DisplayConversationEntry({
|
||||
failed={entry.entry_type.failed}
|
||||
execution_processes={entry.entry_type.execution_processes}
|
||||
task={task}
|
||||
needsSetup={entry.entry_type.needs_setup}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
Copy,
|
||||
Check,
|
||||
GitBranch,
|
||||
Settings,
|
||||
} from 'lucide-react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import NiceModal from '@ebay/nice-modal-react';
|
||||
@@ -21,6 +22,7 @@ import { getIdeName } from '@/components/ide/IdeIcon';
|
||||
import { useProject } from '@/contexts/project-context';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { attemptsApi } from '@/lib/api';
|
||||
import { BaseAgentCapability, type BaseCodingAgent } from 'shared/types';
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
@@ -34,6 +36,7 @@ type NextActionCardProps = {
|
||||
failed: boolean;
|
||||
execution_processes: number;
|
||||
task?: any;
|
||||
needsSetup?: boolean;
|
||||
};
|
||||
|
||||
export function NextActionCard({
|
||||
@@ -42,6 +45,7 @@ export function NextActionCard({
|
||||
failed,
|
||||
execution_processes,
|
||||
task,
|
||||
needsSetup,
|
||||
}: NextActionCardProps) {
|
||||
const { t } = useTranslation('tasks');
|
||||
const { config } = useUserSystem();
|
||||
@@ -54,6 +58,7 @@ export function NextActionCard({
|
||||
queryFn: () => attemptsApi.get(attemptId!),
|
||||
enabled: !!attemptId && failed,
|
||||
});
|
||||
const { capabilities } = useUserSystem();
|
||||
|
||||
const openInEditor = useOpenInEditor(attemptId);
|
||||
const { fileCount, added, deleted, error } = useDiffSummary(
|
||||
@@ -116,10 +121,36 @@ export function NextActionCard({
|
||||
});
|
||||
}, [attemptId, task, project?.id]);
|
||||
|
||||
const handleRunSetup = useCallback(async () => {
|
||||
if (!attemptId || !attempt) return;
|
||||
try {
|
||||
await attemptsApi.runAgentSetup(attemptId, {
|
||||
executor_profile_id: {
|
||||
executor: attempt.executor as BaseCodingAgent,
|
||||
variant: null,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to run setup:', error);
|
||||
}
|
||||
}, [attemptId, attempt]);
|
||||
|
||||
const canAutoSetup = !!(
|
||||
attempt?.executor &&
|
||||
capabilities?.[attempt.executor]?.includes(BaseAgentCapability.SETUP_HELPER)
|
||||
);
|
||||
|
||||
const setupHelpText = canAutoSetup
|
||||
? t('attempt.setupHelpText', { agent: attempt?.executor })
|
||||
: null;
|
||||
|
||||
const editorName = getIdeName(config?.editor?.editor_type);
|
||||
|
||||
// Necessary to prevent this component being displayed beyond fold within Virtualised List
|
||||
if ((!failed || execution_processes > 2) && fileCount === 0) {
|
||||
if (
|
||||
(!failed || (execution_processes > 2 && !needsSetup)) &&
|
||||
fileCount === 0
|
||||
) {
|
||||
return <div className="h-24"></div>;
|
||||
}
|
||||
|
||||
@@ -133,8 +164,19 @@ export function NextActionCard({
|
||||
{t('attempt.labels.summaryAndActions')}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Display setup help text when setup is needed */}
|
||||
{needsSetup && setupHelpText && (
|
||||
<div
|
||||
className={`border-x border-t ${failed ? 'border-destructive' : 'border-foreground'} px-3 py-2 flex items-start gap-2`}
|
||||
>
|
||||
<Settings className="h-4 w-4 mt-0.5 flex-shrink-0" />
|
||||
<span className="text-sm">{setupHelpText}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div
|
||||
className={`border px-3 py-2 flex items-center gap-3 min-w-0 ${failed ? 'border-destructive' : 'border-foreground'}`}
|
||||
className={`border px-3 py-2 flex items-center gap-3 min-w-0 ${failed ? 'border-destructive' : 'border-foreground'} ${needsSetup && setupHelpText ? 'border-t-0' : ''}`}
|
||||
>
|
||||
{/* Left: Diff summary */}
|
||||
{!error && (
|
||||
@@ -155,19 +197,33 @@ export function NextActionCard({
|
||||
|
||||
<div className="flex-1" />
|
||||
|
||||
{/* Try Again button */}
|
||||
{failed && execution_processes <= 2 && (
|
||||
<Button
|
||||
variant="destructive"
|
||||
size="sm"
|
||||
onClick={handleTryAgain}
|
||||
disabled={!attempt?.task_id}
|
||||
className="text-sm"
|
||||
aria-label={t('attempt.tryAgain')}
|
||||
>
|
||||
{t('attempt.tryAgain')}
|
||||
</Button>
|
||||
)}
|
||||
{/* Run Setup or Try Again button */}
|
||||
{failed &&
|
||||
(needsSetup ? (
|
||||
<Button
|
||||
variant="default"
|
||||
size="sm"
|
||||
onClick={handleRunSetup}
|
||||
disabled={!attempt}
|
||||
className="text-sm"
|
||||
aria-label={t('attempt.runSetup')}
|
||||
>
|
||||
{t('attempt.runSetup')}
|
||||
</Button>
|
||||
) : (
|
||||
execution_processes <= 2 && (
|
||||
<Button
|
||||
variant="destructive"
|
||||
size="sm"
|
||||
onClick={handleTryAgain}
|
||||
disabled={!attempt?.task_id}
|
||||
className="text-sm"
|
||||
aria-label={t('attempt.tryAgain')}
|
||||
>
|
||||
{t('attempt.tryAgain')}
|
||||
</Button>
|
||||
)
|
||||
))}
|
||||
|
||||
{/* Right: Icon buttons */}
|
||||
{fileCount > 0 && (
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Button } from '@/components/ui/button';
|
||||
import { Pencil } from 'lucide-react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useProcessRetry } from '@/hooks/useProcessRetry';
|
||||
import { TaskAttempt, type BaseAgentCapability } from 'shared/types';
|
||||
import { TaskAttempt, BaseAgentCapability } from 'shared/types';
|
||||
import { useUserSystem } from '@/components/config-provider';
|
||||
import { useDraftStream } from '@/hooks/follow-up/useDraftStream';
|
||||
import { RetryEditorInline } from './RetryEditorInline';
|
||||
@@ -28,7 +28,7 @@ const UserMessage = ({
|
||||
const canFork = !!(
|
||||
taskAttempt?.executor &&
|
||||
capabilities?.[taskAttempt.executor]?.includes(
|
||||
'SESSION_FORK' as BaseAgentCapability
|
||||
BaseAgentCapability.SESSION_FORK
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
@@ -65,14 +65,23 @@ const loadingPatch: PatchTypeWithKey = {
|
||||
|
||||
const nextActionPatch: (
|
||||
failed: boolean,
|
||||
execution_processes: number
|
||||
) => PatchTypeWithKey = (failed, execution_processes) => ({
|
||||
execution_processes: number,
|
||||
needs_setup: boolean,
|
||||
setup_help_text?: string
|
||||
) => PatchTypeWithKey = (
|
||||
failed,
|
||||
execution_processes,
|
||||
needs_setup,
|
||||
setup_help_text
|
||||
) => ({
|
||||
type: 'NORMALIZED_ENTRY',
|
||||
content: {
|
||||
entry_type: {
|
||||
type: 'next_action',
|
||||
failed: failed,
|
||||
execution_processes: execution_processes,
|
||||
needs_setup: needs_setup,
|
||||
setup_help_text: setup_help_text ?? null,
|
||||
},
|
||||
content: '',
|
||||
timestamp: null,
|
||||
@@ -240,6 +249,8 @@ export const useConversationHistory = ({
|
||||
let hasPendingApproval = false;
|
||||
let hasRunningProcess = false;
|
||||
let lastProcessFailedOrKilled = false;
|
||||
let needsSetup = false;
|
||||
let setupHelpText: string | undefined;
|
||||
|
||||
// Create user messages + tool calls for setup/cleanup scripts
|
||||
const allEntries = Object.values(executionProcessState)
|
||||
@@ -317,6 +328,23 @@ export const useConversationHistory = ({
|
||||
index === Object.keys(executionProcessState).length - 1
|
||||
) {
|
||||
lastProcessFailedOrKilled = true;
|
||||
|
||||
// Check if this failed process has a SetupRequired entry
|
||||
const hasSetupRequired = entriesExcludingUser.some((entry) => {
|
||||
if (entry.type !== 'NORMALIZED_ENTRY') return false;
|
||||
if (
|
||||
entry.content.entry_type.type === 'error_message' &&
|
||||
entry.content.entry_type.error_type.type === 'setup_required'
|
||||
) {
|
||||
setupHelpText = entry.content.content;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
if (hasSetupRequired) {
|
||||
needsSetup = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (isProcessRunning && !hasPendingApprovalEntry) {
|
||||
@@ -410,7 +438,9 @@ export const useConversationHistory = ({
|
||||
allEntries.push(
|
||||
nextActionPatch(
|
||||
lastProcessFailedOrKilled,
|
||||
Object.keys(executionProcessState).length
|
||||
Object.keys(executionProcessState).length,
|
||||
needsSetup,
|
||||
setupHelpText
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -181,6 +181,8 @@
|
||||
"diffs": "Diffs",
|
||||
"gitActions": "Git Actions",
|
||||
"tryAgain": "Try Again",
|
||||
"runSetup": "Run Setup",
|
||||
"setupHelpText": "{{agent}} isn't setup correctly. Click 'Run Setup' to install it and login.",
|
||||
"devScriptMissingTooltip": "To start the dev server, add a dev script to this project"
|
||||
},
|
||||
"git": {
|
||||
|
||||
@@ -45,6 +45,8 @@
|
||||
"viewDevLogs": "View dev server logs",
|
||||
"viewHistory": "View attempt history",
|
||||
"tryAgain": "Try Again",
|
||||
"runSetup": "Ejecutar Configuración",
|
||||
"setupHelpText": "{{agent}} no está configurado correctamente. Haz clic en 'Ejecutar Configuración' para instalarlo e iniciar sesión.",
|
||||
"devScriptMissingTooltip": "To start the dev server, add a dev script to this project"
|
||||
},
|
||||
"attemptHeaderActions": {
|
||||
|
||||
@@ -45,6 +45,8 @@
|
||||
"viewDevLogs": "View dev server logs",
|
||||
"viewHistory": "View attempt history",
|
||||
"tryAgain": "Try Again",
|
||||
"runSetup": "セットアップを実行",
|
||||
"setupHelpText": "{{agent}}が正しく設定されていません。「セットアップを実行」をクリックしてインストールとログインを行ってください。",
|
||||
"devScriptMissingTooltip": "To start the dev server, add a dev script to this project"
|
||||
},
|
||||
"attemptHeaderActions": {
|
||||
|
||||
@@ -45,6 +45,8 @@
|
||||
"viewDevLogs": "View dev server logs",
|
||||
"viewHistory": "View attempt history",
|
||||
"tryAgain": "Try Again",
|
||||
"runSetup": "설정 실행",
|
||||
"setupHelpText": "{{agent}}이(가) 올바르게 설정되지 않았습니다. '설정 실행'을 클릭하여 설치하고 로그인하세요.",
|
||||
"devScriptMissingTooltip": "To start the dev server, add a dev script to this project"
|
||||
},
|
||||
"attemptHeaderActions": {
|
||||
|
||||
@@ -47,6 +47,8 @@ import {
|
||||
RebaseTaskAttemptRequest,
|
||||
ChangeTargetBranchRequest,
|
||||
ChangeTargetBranchResponse,
|
||||
RunAgentSetupRequest,
|
||||
RunAgentSetupResponse,
|
||||
} from 'shared/types';
|
||||
|
||||
// Re-export types for convenience
|
||||
@@ -386,6 +388,20 @@ export const attemptsApi = {
|
||||
return handleApiResponse<void>(response);
|
||||
},
|
||||
|
||||
runAgentSetup: async (
|
||||
attemptId: string,
|
||||
data: RunAgentSetupRequest
|
||||
): Promise<RunAgentSetupResponse> => {
|
||||
const response = await makeRequest(
|
||||
`/api/task-attempts/${attemptId}/run-agent-setup`,
|
||||
{
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data),
|
||||
}
|
||||
);
|
||||
return handleApiResponse<RunAgentSetupResponse>(response);
|
||||
},
|
||||
|
||||
getDraft: async (
|
||||
attemptId: string,
|
||||
type: 'follow_up' | 'retry'
|
||||
|
||||
Reference in New Issue
Block a user