mirror of
https://github.com/microsoft/vscode.git
synced 2025-12-20 02:08:47 +00:00
Generically exit sidebar chat after delegating (#280384)
* Initial plan * Add delegation event to exit panel chat when chatSessions API delegates to new session Co-authored-by: joshspicer <23246594+joshspicer@users.noreply.github.com> * Fix: Store viewModel reference to avoid potential null reference during delegation exit Co-authored-by: joshspicer <23246594+joshspicer@users.noreply.github.com> * fix * tidy --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: joshspicer <23246594+joshspicer@users.noreply.github.com>
This commit is contained in:
@@ -40,7 +40,6 @@ import { IChatWidgetService } from '../chat.js';
|
||||
import { CHAT_SETUP_ACTION_ID } from './chatActions.js';
|
||||
import { PromptFileVariableKind, toPromptFileVariableEntry } from '../../common/chatVariableEntries.js';
|
||||
import { NEW_CHAT_SESSION_ACTION_ID } from '../chatSessions/common.js';
|
||||
import { isResponseVM } from '../../common/chatViewModel.js';
|
||||
|
||||
export const enum ActionLocation {
|
||||
ChatWidget = 'chatWidget',
|
||||
@@ -285,57 +284,7 @@ class CreateRemoteAgentJobAction {
|
||||
});
|
||||
|
||||
if (requestData) {
|
||||
await requestData.responseCompletePromise;
|
||||
|
||||
const checkAndClose = () => {
|
||||
const items = widget.viewModel?.getItems() ?? [];
|
||||
const lastItem = items[items.length - 1];
|
||||
|
||||
if (lastItem && isResponseVM(lastItem) && lastItem.isComplete && !lastItem.model.isPendingConfirmation.get()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
if (checkAndClose()) {
|
||||
await widget.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
// Monitor subsequent responses when pending confirmations block us from closing
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
let disposed = false;
|
||||
let disposable: IDisposable | undefined;
|
||||
let timeout: ReturnType<typeof setTimeout> | undefined;
|
||||
const cleanup = () => {
|
||||
if (!disposed) {
|
||||
disposed = true;
|
||||
if (timeout !== undefined) {
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
if (disposable) {
|
||||
disposable.dispose();
|
||||
}
|
||||
}
|
||||
};
|
||||
try {
|
||||
disposable = widget.viewModel!.onDidChange(() => {
|
||||
if (checkAndClose()) {
|
||||
cleanup();
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
timeout = setTimeout(() => {
|
||||
cleanup();
|
||||
resolve();
|
||||
}, 30_000); // 30 second timeout
|
||||
} catch (e) {
|
||||
cleanup();
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
|
||||
await widget.clear();
|
||||
await widget.handleDelegationExitIfNeeded(requestData.agent);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error creating remote coding agent job', e);
|
||||
|
||||
@@ -262,6 +262,7 @@ export interface IChatWidget {
|
||||
clear(): Promise<void>;
|
||||
getViewState(): IChatModelInputState | undefined;
|
||||
lockToCodingAgent(name: string, displayName: string, agentId?: string): void;
|
||||
handleDelegationExitIfNeeded(agent: IChatAgentData | undefined): Promise<void>;
|
||||
|
||||
delegateScrollFromMouseWheelEvent(event: IMouseWheelEvent): void;
|
||||
}
|
||||
|
||||
@@ -699,6 +699,15 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ
|
||||
.filter(contribution => this._isContributionAvailable(contribution));
|
||||
}
|
||||
|
||||
getChatSessionContribution(chatSessionType: string): IChatSessionsExtensionPoint | undefined {
|
||||
const contribution = this._contributions.get(chatSessionType)?.contribution;
|
||||
if (!contribution) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return this._isContributionAvailable(contribution) ? contribution : undefined;
|
||||
}
|
||||
|
||||
getAllChatSessionItemProviders(): IChatSessionItemProvider[] {
|
||||
return [...this._itemsProviders.values()].filter(provider => {
|
||||
// Check if the provider's corresponding contribution is available
|
||||
|
||||
@@ -1317,46 +1317,7 @@ export class ChatWidget extends Disposable implements IChatWidget {
|
||||
this.input.setValue(`@${agentId} ${promptToUse}`, false);
|
||||
this.input.focus();
|
||||
// Auto-submit for delegated chat sessions
|
||||
this.acceptInput().then(async (response) => {
|
||||
if (!response || !this.viewModel) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Wait for response to complete without any user-pending confirmations
|
||||
const checkForComplete = () => {
|
||||
const items = this.viewModel?.getItems() ?? [];
|
||||
const lastItem = items[items.length - 1];
|
||||
if (lastItem && isResponseVM(lastItem) && lastItem.model && lastItem.isComplete && !lastItem.model.isPendingConfirmation.get()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
if (checkForComplete()) {
|
||||
await this.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
await new Promise<void>(resolve => {
|
||||
const disposable = this.viewModel!.onDidChange(() => {
|
||||
if (checkForComplete()) {
|
||||
cleanup();
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
const timeout = setTimeout(() => {
|
||||
cleanup();
|
||||
resolve();
|
||||
}, 30000); // 30 second timeout
|
||||
const cleanup = () => {
|
||||
clearTimeout(timeout);
|
||||
disposable.dispose();
|
||||
};
|
||||
});
|
||||
|
||||
// Clear parent editor
|
||||
await this.clear();
|
||||
}).catch(e => this.logService.error('Failed to handle handoff continueOn', e));
|
||||
this.acceptInput().catch(e => this.logService.error('Failed to handle handoff continueOn', e));
|
||||
} else if (handoff.agent) {
|
||||
// Regular handoff to specified agent
|
||||
this._switchToAgentByName(handoff.agent);
|
||||
@@ -1371,6 +1332,87 @@ export class ChatWidget extends Disposable implements IChatWidget {
|
||||
}
|
||||
}
|
||||
|
||||
async handleDelegationExitIfNeeded(agent: IChatAgentData | undefined): Promise<void> {
|
||||
if (!this._shouldExitAfterDelegation(agent)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await this._handleDelegationExit();
|
||||
} catch (e) {
|
||||
this.logService.error('Failed to handle delegation exit', e);
|
||||
}
|
||||
}
|
||||
|
||||
private _shouldExitAfterDelegation(agent: IChatAgentData | undefined): boolean {
|
||||
if (!agent) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isIChatViewViewContext(this.viewContext)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const contribution = this.chatSessionsService.getChatSessionContribution(agent.id);
|
||||
if (!contribution) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (contribution.canDelegate !== true) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the exit of the panel chat when a delegation to another session occurs.
|
||||
* Waits for the response to complete and any pending confirmations to be resolved,
|
||||
* then clears the widget.
|
||||
*/
|
||||
private async _handleDelegationExit(): Promise<void> {
|
||||
const viewModel = this.viewModel;
|
||||
if (!viewModel) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if response is already complete without pending confirmations
|
||||
const checkForComplete = () => {
|
||||
const items = viewModel.getItems();
|
||||
const lastItem = items[items.length - 1];
|
||||
if (lastItem && isResponseVM(lastItem) && lastItem.model && lastItem.isComplete && !lastItem.model.isPendingConfirmation.get()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
if (checkForComplete()) {
|
||||
await this.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
// Wait for response to complete with a timeout
|
||||
await new Promise<void>(resolve => {
|
||||
const disposable = viewModel.onDidChange(() => {
|
||||
if (checkForComplete()) {
|
||||
cleanup();
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
const timeout = setTimeout(() => {
|
||||
cleanup();
|
||||
resolve();
|
||||
}, 30_000); // 30 second timeout
|
||||
const cleanup = () => {
|
||||
clearTimeout(timeout);
|
||||
disposable.dispose();
|
||||
};
|
||||
});
|
||||
|
||||
// Clear the widget after delegation completes
|
||||
await this.clear();
|
||||
}
|
||||
|
||||
setVisible(visible: boolean): void {
|
||||
const wasVisible = this._visible;
|
||||
this._visible = visible;
|
||||
@@ -2288,6 +2330,7 @@ export class ChatWidget extends Disposable implements IChatWidget {
|
||||
|
||||
this.input.acceptInput(isUserQuery);
|
||||
this._onDidSubmitAgent.fire({ agent: result.agent, slashCommand: result.slashCommand });
|
||||
this.handleDelegationExitIfNeeded(result.agent);
|
||||
this.currentRequest = result.responseCompletePromise.then(() => {
|
||||
const responses = this.viewModel?.getItems().filter(isResponseVM);
|
||||
const lastResponse = responses?.[responses.length - 1];
|
||||
|
||||
@@ -162,6 +162,8 @@ export interface IChatSessionsService {
|
||||
readonly onDidChangeAvailability: Event<void>;
|
||||
readonly onDidChangeInProgress: Event<void>;
|
||||
|
||||
getChatSessionContribution(chatSessionType: string): IChatSessionsExtensionPoint | undefined;
|
||||
|
||||
registerChatSessionItemProvider(provider: IChatSessionItemProvider): IDisposable;
|
||||
activateChatSessionItemProvider(chatSessionType: string): Promise<IChatSessionItemProvider | undefined>;
|
||||
getAllChatSessionItemProviders(): IChatSessionItemProvider[];
|
||||
|
||||
@@ -70,6 +70,10 @@ export class MockChatSessionsService implements IChatSessionsService {
|
||||
return this.contributions;
|
||||
}
|
||||
|
||||
getChatSessionContribution(chatSessionType: string): IChatSessionsExtensionPoint | undefined {
|
||||
return this.contributions.find(contrib => contrib.type === chatSessionType);
|
||||
}
|
||||
|
||||
setContributions(contributions: IChatSessionsExtensionPoint[]): void {
|
||||
this.contributions = contributions;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user