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 { CHAT_SETUP_ACTION_ID } from './chatActions.js';
|
||||||
import { PromptFileVariableKind, toPromptFileVariableEntry } from '../../common/chatVariableEntries.js';
|
import { PromptFileVariableKind, toPromptFileVariableEntry } from '../../common/chatVariableEntries.js';
|
||||||
import { NEW_CHAT_SESSION_ACTION_ID } from '../chatSessions/common.js';
|
import { NEW_CHAT_SESSION_ACTION_ID } from '../chatSessions/common.js';
|
||||||
import { isResponseVM } from '../../common/chatViewModel.js';
|
|
||||||
|
|
||||||
export const enum ActionLocation {
|
export const enum ActionLocation {
|
||||||
ChatWidget = 'chatWidget',
|
ChatWidget = 'chatWidget',
|
||||||
@@ -285,57 +284,7 @@ class CreateRemoteAgentJobAction {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (requestData) {
|
if (requestData) {
|
||||||
await requestData.responseCompletePromise;
|
await widget.handleDelegationExitIfNeeded(requestData.agent);
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Error creating remote coding agent job', e);
|
console.error('Error creating remote coding agent job', e);
|
||||||
|
|||||||
@@ -262,6 +262,7 @@ export interface IChatWidget {
|
|||||||
clear(): Promise<void>;
|
clear(): Promise<void>;
|
||||||
getViewState(): IChatModelInputState | undefined;
|
getViewState(): IChatModelInputState | undefined;
|
||||||
lockToCodingAgent(name: string, displayName: string, agentId?: string): void;
|
lockToCodingAgent(name: string, displayName: string, agentId?: string): void;
|
||||||
|
handleDelegationExitIfNeeded(agent: IChatAgentData | undefined): Promise<void>;
|
||||||
|
|
||||||
delegateScrollFromMouseWheelEvent(event: IMouseWheelEvent): void;
|
delegateScrollFromMouseWheelEvent(event: IMouseWheelEvent): void;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -699,6 +699,15 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ
|
|||||||
.filter(contribution => this._isContributionAvailable(contribution));
|
.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[] {
|
getAllChatSessionItemProviders(): IChatSessionItemProvider[] {
|
||||||
return [...this._itemsProviders.values()].filter(provider => {
|
return [...this._itemsProviders.values()].filter(provider => {
|
||||||
// Check if the provider's corresponding contribution is available
|
// 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.setValue(`@${agentId} ${promptToUse}`, false);
|
||||||
this.input.focus();
|
this.input.focus();
|
||||||
// Auto-submit for delegated chat sessions
|
// Auto-submit for delegated chat sessions
|
||||||
this.acceptInput().then(async (response) => {
|
this.acceptInput().catch(e => this.logService.error('Failed to handle handoff continueOn', e));
|
||||||
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));
|
|
||||||
} else if (handoff.agent) {
|
} else if (handoff.agent) {
|
||||||
// Regular handoff to specified agent
|
// Regular handoff to specified agent
|
||||||
this._switchToAgentByName(handoff.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 {
|
setVisible(visible: boolean): void {
|
||||||
const wasVisible = this._visible;
|
const wasVisible = this._visible;
|
||||||
this._visible = visible;
|
this._visible = visible;
|
||||||
@@ -2288,6 +2330,7 @@ export class ChatWidget extends Disposable implements IChatWidget {
|
|||||||
|
|
||||||
this.input.acceptInput(isUserQuery);
|
this.input.acceptInput(isUserQuery);
|
||||||
this._onDidSubmitAgent.fire({ agent: result.agent, slashCommand: result.slashCommand });
|
this._onDidSubmitAgent.fire({ agent: result.agent, slashCommand: result.slashCommand });
|
||||||
|
this.handleDelegationExitIfNeeded(result.agent);
|
||||||
this.currentRequest = result.responseCompletePromise.then(() => {
|
this.currentRequest = result.responseCompletePromise.then(() => {
|
||||||
const responses = this.viewModel?.getItems().filter(isResponseVM);
|
const responses = this.viewModel?.getItems().filter(isResponseVM);
|
||||||
const lastResponse = responses?.[responses.length - 1];
|
const lastResponse = responses?.[responses.length - 1];
|
||||||
|
|||||||
@@ -162,6 +162,8 @@ export interface IChatSessionsService {
|
|||||||
readonly onDidChangeAvailability: Event<void>;
|
readonly onDidChangeAvailability: Event<void>;
|
||||||
readonly onDidChangeInProgress: Event<void>;
|
readonly onDidChangeInProgress: Event<void>;
|
||||||
|
|
||||||
|
getChatSessionContribution(chatSessionType: string): IChatSessionsExtensionPoint | undefined;
|
||||||
|
|
||||||
registerChatSessionItemProvider(provider: IChatSessionItemProvider): IDisposable;
|
registerChatSessionItemProvider(provider: IChatSessionItemProvider): IDisposable;
|
||||||
activateChatSessionItemProvider(chatSessionType: string): Promise<IChatSessionItemProvider | undefined>;
|
activateChatSessionItemProvider(chatSessionType: string): Promise<IChatSessionItemProvider | undefined>;
|
||||||
getAllChatSessionItemProviders(): IChatSessionItemProvider[];
|
getAllChatSessionItemProviders(): IChatSessionItemProvider[];
|
||||||
|
|||||||
@@ -70,6 +70,10 @@ export class MockChatSessionsService implements IChatSessionsService {
|
|||||||
return this.contributions;
|
return this.contributions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getChatSessionContribution(chatSessionType: string): IChatSessionsExtensionPoint | undefined {
|
||||||
|
return this.contributions.find(contrib => contrib.type === chatSessionType);
|
||||||
|
}
|
||||||
|
|
||||||
setContributions(contributions: IChatSessionsExtensionPoint[]): void {
|
setContributions(contributions: IChatSessionsExtensionPoint[]): void {
|
||||||
this.contributions = contributions;
|
this.contributions = contributions;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user