Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions src/rovo-dev/rovoDevChatProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,13 @@ export class RovoDevChatProvider {
}
this._pendingCancellation = true;

// Optimistically unlock the UI immediately
await webview.postMessage({
type: RovoDevProviderMessageType.CompleteMessage,
promptId: this._currentPromptId,
isCancellation: true,
});

try {
const cancelResponse = await this._rovoDevApiClient.cancel();
success = cancelResponse.cancelled || cancelResponse.message === 'No chat in progress';
Expand Down Expand Up @@ -396,6 +403,7 @@ export class RovoDevChatProvider {
await webview.postMessage({
type: RovoDevProviderMessageType.RovoDevResponseMessage,
message: group,
promptId: this._currentPromptId,
});
group = [];
}
Expand Down Expand Up @@ -460,6 +468,7 @@ export class RovoDevChatProvider {
await webview.postMessage({
type: RovoDevProviderMessageType.RovoDevResponseMessage,
message: response,
promptId: this._currentPromptId,
});
break;

Expand All @@ -468,6 +477,7 @@ export class RovoDevChatProvider {
await webview.postMessage({
type: RovoDevProviderMessageType.RovoDevResponseMessage,
message: response,
promptId: this._currentPromptId,
});
}
break;
Expand Down Expand Up @@ -763,6 +773,7 @@ export class RovoDevChatProvider {
text,
enable_deep_plan,
context,
promptId: this._currentPromptId,
});
}

Expand Down
9 changes: 6 additions & 3 deletions src/rovo-dev/rovoDevWebviewProviderMessages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,15 @@ export type RovoDevProviderMessage =
RovoDevProviderMessageType.RovoDevDisabled,
{ reason: RovoDevDisabledReason; detail?: RovoDevEntitlementCheckFailedDetail }
>
| ReducerAction<RovoDevProviderMessageType.SignalPromptSent, RovoDevPrompt & { echoMessage: boolean }>
| ReducerAction<
RovoDevProviderMessageType.SignalPromptSent,
RovoDevPrompt & { echoMessage: boolean; promptId?: string }
>
| ReducerAction<
RovoDevProviderMessageType.RovoDevResponseMessage,
{ message: RovoDevResponseMessageType | RovoDevResponseMessageType[] }
{ message: RovoDevResponseMessageType | RovoDevResponseMessageType[]; promptId?: string }
>
| ReducerAction<RovoDevProviderMessageType.CompleteMessage, { promptId: string }>
| ReducerAction<RovoDevProviderMessageType.CompleteMessage, { promptId: string; isCancellation?: boolean }>
| ReducerAction<RovoDevProviderMessageType.ShowDialog, { message: DialogMessage }>
| ReducerAction<RovoDevProviderMessageType.ClearChat>
| ReducerAction<
Expand Down
58 changes: 53 additions & 5 deletions src/rovo-dev/ui/rovoDevView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ const RovoDevView: React.FC = () => {
const [pendingFilesForFiltering, setPendingFilesForFiltering] = useState<ModifiedFile[] | null>(null);
const [thinkingBlockEnabled, setThinkingBlockEnabled] = useState(true);
const [lastCompletedPromptId, setLastCompletedPromptId] = useState<string | undefined>(undefined);
const [activePromptId, setActivePromptId] = useState<string | undefined>(undefined);
const [queuedPrompt, setQueuedPrompt] = useState<string | undefined>(undefined);
const [isAtlassianUser, setIsAtlassianUser] = useState(false);
const [feedbackType, setFeedbackType] = React.useState<'like' | 'dislike' | undefined>(undefined);

Expand Down Expand Up @@ -268,6 +270,9 @@ const RovoDevView: React.FC = () => {
case RovoDevProviderMessageType.SignalPromptSent:
setIsDeepPlanToggled(event.enable_deep_plan || false);
setPendingToolCallMessage(DEFAULT_LOADING_MESSAGE);
if (event.promptId) {
setActivePromptId(event.promptId);
}
if (event.echoMessage) {
handleAppendResponse({
event_kind: '_RovoDevUserPrompt',
Expand All @@ -278,9 +283,21 @@ const RovoDevView: React.FC = () => {
break;

case RovoDevProviderMessageType.RovoDevResponseMessage:
setCurrentState((prev) =>
prev.state === 'WaitingForPrompt' ? { state: 'GeneratingResponse' } : prev,
);
const messagePromptId = event.promptId;

setCurrentState((prev) => {
// If we are waiting for a prompt, only switch to generating if this is the ACTIVE prompt
if (prev.state === 'WaitingForPrompt') {
if (messagePromptId && activePromptId && messagePromptId === activePromptId) {
return { state: 'GeneratingResponse' };
}
// Legacy fallback: if IDs are missing, assume we should generate
if (!messagePromptId || !activePromptId) {
return { state: 'GeneratingResponse' };
}
}
return prev;
});

const messages = Array.isArray(event.message) ? event.message : [event.message];

Expand All @@ -305,6 +322,12 @@ const RovoDevView: React.FC = () => {
// Signal that we need to send render acknowledgement after this render completes
setLastCompletedPromptId(event.promptId);
}

if (event.isCancellation) {
setActivePromptId(undefined); // Clear active prompt so late messages don't re-lock UI
// Queued prompt will be sent via useEffect below after state updates
}

setSummaryMessageInHistory();
setPendingToolCallMessage('');
setModalDialogs([]);
Expand Down Expand Up @@ -503,7 +526,7 @@ const RovoDevView: React.FC = () => {
break;
}
},
[handleAppendResponse, currentState.state, setSummaryMessageInHistory, clearChatHistory],
[handleAppendResponse, currentState.state, setSummaryMessageInHistory, clearChatHistory, activePromptId],
);

const { postMessage, postMessagePromise, setState } = useMessagingApi<
Expand Down Expand Up @@ -561,6 +584,19 @@ const RovoDevView: React.FC = () => {
return false;
}

// If there's an active prompt (cancellation in progress), queue this prompt
if (activePromptId && currentState.state === 'CancellingResponse') {
setQueuedPrompt(text);
// Show a message to the user that cancellation is still being processed
handleAppendResponse({
event_kind: '_RovoDevDialog',
type: 'info',
title: 'Cancellation in progress',
text: 'Your previous prompt is being cancelled. Your new prompt will be sent once cancellation completes.',
});
return false;
}

const isWaitingForPrompt =
currentState.state === 'WaitingForPrompt' ||
(currentState.state === 'Initializing' && !currentState.isPromptPending);
Expand Down Expand Up @@ -589,7 +625,7 @@ const RovoDevView: React.FC = () => {

return true;
},
[currentState, isDeepPlanToggled, promptContextCollection, postMessage],
[currentState, isDeepPlanToggled, promptContextCollection, postMessage, activePromptId, handleAppendResponse],
);

React.useEffect(() => {
Expand Down Expand Up @@ -617,6 +653,18 @@ const RovoDevView: React.FC = () => {
}
}, [lastCompletedPromptId, currentState.state, postMessage]);

// Send queued prompt after cancellation completes
React.useEffect(() => {
if (queuedPrompt && !activePromptId && currentState.state === 'WaitingForPrompt') {
const promptToSend = queuedPrompt;
setQueuedPrompt(undefined);
// Send on next tick to ensure state updates have propagated
setTimeout(() => {
sendPrompt(promptToSend);
}, 0);
}
}, [queuedPrompt, activePromptId, currentState.state, sendPrompt]);

const executeCodePlan = useCallback(() => {
if (currentState.state !== 'WaitingForPrompt') {
return;
Expand Down
Loading