Skip to content
Merged
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
18 changes: 9 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -180,47 +180,47 @@
"view/title": [
{
"command": "sf.agent.combined.view.exportConversation",
"when": "view == sf.agent.combined.view && agentforceDX:agentSelected && !agentforceDX:sessionStarting && !agentforceDX:sessionStopping && !agentforceDX:sessionError && agentforceDX:hasConversationData",
"when": "view == sf.agent.combined.view && !agentforceDX:authError && agentforceDX:agentSelected && !agentforceDX:sessionStarting && !agentforceDX:sessionStopping && !agentforceDX:sessionError && agentforceDX:hasConversationData",
"group": "navigation@3"
},
{
"command": "salesforcedx-vscode-agents.activateAgent",
"when": "view == sf.agent.combined.view && !agentforceDX:sessionActive && !agentforceDX:sessionStarting && !agentforceDX:sessionStopping && !agentforceDX:canResetAgentView && !agentforceDX:agentSelected",
"when": "view == sf.agent.combined.view && !agentforceDX:authError && !agentforceDX:sessionActive && !agentforceDX:sessionStarting && !agentforceDX:sessionStopping && !agentforceDX:canResetAgentView && !agentforceDX:agentSelected",
"group": "navigation@1"
},
{
"command": "sf.agent.combined.view.activateVersion",
"when": "view == sf.agent.combined.view && agentforceDX:agentSelected && !agentforceDX:isScriptAgent && !agentforceDX:sessionActive && !agentforceDX:sessionStarting && !agentforceDX:sessionStopping && !agentforceDX:sessionError",
"when": "view == sf.agent.combined.view && !agentforceDX:authError && agentforceDX:agentSelected && !agentforceDX:isScriptAgent && !agentforceDX:sessionActive && !agentforceDX:sessionStarting && !agentforceDX:sessionStopping && !agentforceDX:sessionError",
"group": "navigation@2"
},
{
"command": "sf.agent.combined.view.clearLoadedSession",
"when": "view == sf.agent.combined.view && agentforceDX:hasLoadedSession && !agentforceDX:sessionActive && !agentforceDX:sessionStarting && !agentforceDX:sessionStopping && !agentforceDX:canResetAgentView && !agentforceDX:sessionError",
"when": "view == sf.agent.combined.view && !agentforceDX:authError && agentforceDX:hasLoadedSession && !agentforceDX:sessionActive && !agentforceDX:sessionStarting && !agentforceDX:sessionStopping && !agentforceDX:canResetAgentView && !agentforceDX:sessionError",
"group": "navigation@4"
},
{
"command": "sf.agent.combined.view.refreshAgents",
"when": "view == sf.agent.combined.view && !agentforceDX:sessionActive && !agentforceDX:sessionStarting && !agentforceDX:sessionStopping && !agentforceDX:canResetAgentView",
"when": "view == sf.agent.combined.view && !agentforceDX:authError && !agentforceDX:sessionActive && !agentforceDX:sessionStarting && !agentforceDX:sessionStopping && !agentforceDX:canResetAgentView",
"group": "navigation@0"
},
{
"command": "sf.agent.combined.view.resetAgentView",
"when": "view == sf.agent.combined.view && !agentforceDX:sessionActive && !agentforceDX:sessionStarting && !agentforceDX:sessionStopping && agentforceDX:agentSelected && agentforceDX:canResetAgentView",
"when": "view == sf.agent.combined.view && !agentforceDX:authError && !agentforceDX:sessionActive && !agentforceDX:sessionStarting && !agentforceDX:sessionStopping && agentforceDX:agentSelected && agentforceDX:canResetAgentView",
"group": "navigation@0"
},
{
"submenu": "sf.agent.combined.view.restartMenu",
"when": "view == sf.agent.combined.view && agentforceDX:sessionActive && !agentforceDX:sessionStarting && !agentforceDX:sessionStopping && agentforceDX:agentSelected && agentforceDX:isScriptAgent",
"when": "view == sf.agent.combined.view && !agentforceDX:authError && agentforceDX:sessionActive && !agentforceDX:sessionStarting && !agentforceDX:sessionStopping && agentforceDX:agentSelected && agentforceDX:isScriptAgent",
"group": "navigation@-3"
},
{
"command": "sf.agent.combined.view.debug",
"when": "view == sf.agent.combined.view && agentforceDX:sessionActive && !agentforceDX:sessionStarting && !agentforceDX:sessionStopping && !agentforceDX:debugMode && agentforceDX:isLiveMode",
"when": "view == sf.agent.combined.view && !agentforceDX:authError && agentforceDX:sessionActive && !agentforceDX:sessionStarting && !agentforceDX:sessionStopping && !agentforceDX:debugMode && agentforceDX:isLiveMode",
"group": "navigation@-2"
},
{
"command": "sf.agent.combined.view.debugStop",
"when": "view == sf.agent.combined.view && agentforceDX:sessionActive && !agentforceDX:sessionStarting && !agentforceDX:sessionStopping && agentforceDX:debugMode && agentforceDX:isLiveMode",
"when": "view == sf.agent.combined.view && !agentforceDX:authError && agentforceDX:sessionActive && !agentforceDX:sessionStarting && !agentforceDX:sessionStopping && agentforceDX:debugMode && agentforceDX:isLiveMode",
"group": "navigation@-2"
},
{
Expand Down
9 changes: 9 additions & 0 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,15 @@ const registerAgentCombinedView = (context: vscode.ExtensionContext): vscode.Dis
console.error('Could not set up org change listener:', err.message);
});

// Re-fetch agents when window regains focus while in auth error state
disposables.push(
vscode.window.onDidChangeWindowState(async state => {
if (state.focused && provider.hasAuthError) {
await provider.refreshAvailableAgents();
}
})
);

// Shared helper for selecting an agent from a quick pick
const showAgentPicker = async (
placeHolder: string
Expand Down
55 changes: 52 additions & 3 deletions src/views/agentCombined/handlers/webviewMessageHandlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export class WebviewMessageHandlers {
openTraceJson: async msg => await this.handleOpenTraceJson(msg),
getConfiguration: async msg => await this.handleGetConfiguration(msg),
executeCommand: async msg => await this.handleExecuteCommand(msg),
openUrl: async msg => await this.handleOpenUrl(msg),
setSelectedAgentId: async msg => await this.handleSetSelectedAgentId(msg),
setLiveMode: async msg => await this.handleSetLiveMode(msg),
getInitialLiveMode: async () => await this.handleGetInitialLiveMode(),
Expand Down Expand Up @@ -253,8 +254,10 @@ export class WebviewMessageHandlers {
}

private async handleGetAvailableAgents(): Promise<void> {
let instanceUrl: string | undefined;
try {
const conn = await CoreExtensionService.getDefaultConnection();
instanceUrl = conn.instanceUrl;
const project = SfProject.getInstance();
const allAgents = await Agent.listPreviewable(conn, project);

Expand Down Expand Up @@ -312,6 +315,7 @@ export class WebviewMessageHandlers {
this.messageSender.sendAvailableAgents(agentsWithVersions, selectAgentId);

// Update context for command visibility
await this.state.setAuthError(false);
await this.state.setHasAgents(mappedAgents.length > 0);

// Clear the pending/current agent IDs after use
Expand All @@ -322,7 +326,43 @@ export class WebviewMessageHandlers {
} catch (err) {
console.error('Error getting available agents from org:', err);
this.state.pendingSelectAgentId = undefined;
this.messageSender.sendAvailableAgents([], undefined);

const errorMessage = err instanceof Error ? err.message : String(err);
const errorName = err instanceof Error ? err.name : '';
const fullError = `${errorName}: ${errorMessage}`;

const isAuthError =
errorName === 'RefreshTokenAuthError' ||
fullError.includes('RefreshTokenAuthError') ||
fullError.includes('authentication failure') ||
fullError.includes('expired') ||
fullError.includes('INVALID_CROSS_REFERENCE_KEY') ||
fullError.includes('invalid cross reference id') ||
fullError.includes('INVALID_SESSION_ID') ||
fullError.includes('No default org configured');

const isFeatureNotEnabled =
fullError.includes('INVALID_TYPE') && fullError.includes('BotDefinition');

if (isFeatureNotEnabled) {
const setupUrl = instanceUrl
? `${instanceUrl}/lightning/setup/EinsteinCopilot/home`
: undefined;
this.messageSender.sendAuthError(
'Agentforce is not enabled',
'This org doesn\'t have Agentforce enabled. You can enable it or switch to another org.',
setupUrl
);
await this.state.setAuthError(true);
} else if (isAuthError) {
this.messageSender.sendAuthError(
'Unable to connect to org',
'Set a new default org or re-authenticate to continue.'
);
await this.state.setAuthError(true);
} else {
this.messageSender.sendAvailableAgents([], undefined);
}
await this.state.setHasAgents(false);
}
}
Expand Down Expand Up @@ -370,10 +410,19 @@ export class WebviewMessageHandlers {
}

private async handleExecuteCommand(message: AgentMessage): Promise<void> {
const data = message.data as { commandId?: string } | undefined;
const data = message.data as { commandId?: string; args?: unknown[] } | undefined;
const commandId = data?.commandId;
if (commandId && typeof commandId === 'string') {
await vscode.commands.executeCommand(commandId);
const args = Array.isArray(data?.args) ? data.args : [];
await vscode.commands.executeCommand(commandId, ...args);
}
}

private async handleOpenUrl(message: AgentMessage): Promise<void> {
const data = message.data as { url?: string } | undefined;
const url = data?.url;
if (url && typeof url === 'string') {
await vscode.env.openExternal(vscode.Uri.parse(url));
}
}

Expand Down
6 changes: 6 additions & 0 deletions src/views/agentCombined/handlers/webviewMessageSender.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,12 @@ export class WebviewMessageSender {
this.postMessage('error', { message: sanitizedMessage, details: sanitizedDetails });
}

sendAuthError(message: string, details?: string, setupUrl?: string): void {
const sanitizedMessage = this.stripHtmlTags(message);
const sanitizedDetails = details ? this.stripHtmlTags(details) : undefined;
this.postMessage('authError', { message: sanitizedMessage, details: sanitizedDetails, setupUrl });
}

sendDebugLogError(message: string): void {
this.postMessage('debugLogError', { message });
}
Expand Down
12 changes: 12 additions & 0 deletions src/views/agentCombined/state/agentViewState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ export class AgentViewState {
private _currentAgentActiveVersion?: number;
private _agentVersionsCache = new Map<string, Array<{ VersionNumber: number; Status: string }>>();

// Error state
private _hasAuthError = false;

// Mode state
private _isApexDebuggingEnabled = false;
private _isLiveMode = false;
Expand Down Expand Up @@ -255,6 +258,15 @@ export class AgentViewState {
await vscode.commands.executeCommand('setContext', 'agentforceDX:hasAgents', hasAgents);
}

get hasAuthError(): boolean {
return this._hasAuthError;
}

async setAuthError(hasError: boolean): Promise<void> {
this._hasAuthError = hasError;
await vscode.commands.executeCommand('setContext', 'agentforceDX:authError', hasError);
}

getExportDirectory(): string | undefined {
return this.context.workspaceState.get<string>(AgentViewState.EXPORT_DIR_KEY);
}
Expand Down
4 changes: 4 additions & 0 deletions src/views/agentCombinedViewProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,10 @@ export class AgentCombinedViewProvider implements vscode.WebviewViewProvider {
/**
* Gets the currently selected agent ID
*/
public get hasAuthError(): boolean {
return this.state.hasAuthError;
}

public getCurrentAgentId(): string | undefined {
return this.state.currentAgentId;
}
Expand Down
Loading
Loading