Skip to content
Draft
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
149 changes: 148 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,11 @@
"type": "boolean",
"default": false,
"description": "Skip retrieving agent metadata to your DX project when publishing the authoring bundle to your org."
},
"salesforce.agentforceDX.multiSession.enabled": {
"type": "boolean",
"default": false,
"description": "Enable the multi-session preview UI (preview). Displays active sessions in the sidebar and opens each session in its own editor tab."
}
}
},
Expand All @@ -155,7 +160,12 @@
"id": "sf.agent.combined.view",
"name": "%agentforce_dx%",
"type": "webview",
"when": "sf:project_opened"
"when": "sf:project_opened && !agentforceDX:multiSession"
},
{
"id": "sf.agent.sessionList.view",
"name": "Agentforce DX Sessions",
"when": "sf:project_opened && agentforceDX:multiSession"
}
]
},
Expand All @@ -168,6 +178,13 @@
}
]
},
"viewsWelcome": [
{
"view": "sf.agent.sessionList.view",
"contents": "No active agent sessions.\n[+ New session](command:sf.agent.sessionList.newSession)",
"when": "agentforceDX:multiSession"
}
],
"menus": {
"sf.agent.combined.view.restartMenu": [
{
Expand Down Expand Up @@ -247,6 +264,43 @@
"command": "sf.agent.test.view.toggleGeneratedData.off",
"when": "view == sf.agent.test.view && !agentforceDX:showGeneratedData",
"group": "navigation@4"
},
{
"command": "sf.agent.sessionList.newSession",
"when": "view == sf.agent.sessionList.view",
"group": "navigation@1"
},
{
"command": "sf.agent.sessionList.activateVersion",
"when": "view == sf.agent.sessionList.view",
"group": "navigation@2"
},
{
"command": "sf.agent.sessionList.refreshAgents",
"when": "view == sf.agent.sessionList.view",
"group": "navigation@3"
}
],
"editor/title": [
{
"command": "afdx.session.activateVersion",
"when": "activeWebviewPanelId == 'afdx.sessionPanel' && agentforceDX:multiSession && agentforceDX:agentSelected && !agentforceDX:isScriptAgent && !agentforceDX:sessionStarting",
"group": "navigation@0"
},
{
"command": "afdx.session.stop",
"when": "activeWebviewPanelId == 'afdx.sessionPanel' && agentforceDX:multiSession && (agentforceDX:sessionActive || agentforceDX:sessionStarting)",
"group": "navigation@1"
},
{
"command": "afdx.session.restart",
"when": "activeWebviewPanelId == 'afdx.sessionPanel' && agentforceDX:multiSession && agentforceDX:sessionActive && agentforceDX:isScriptAgent",
"group": "navigation@2"
},
{
"command": "afdx.session.toggleDebug",
"when": "activeWebviewPanelId == 'afdx.sessionPanel' && agentforceDX:multiSession && agentforceDX:sessionActive && agentforceDX:isLiveMode",
"group": "navigation@3"
}
],
"view/item/context": [
Expand All @@ -258,6 +312,11 @@
{
"command": "sf.agent.test.view.goToTestResults",
"when": "view == sf.agent.test.view && viewItem =~ /(agentTest|agentTestGroup)(_Pass|_Skip|\\b)/"
},
{
"command": "sf.agent.sessionList.close",
"when": "view == sf.agent.sessionList.view && viewItem =~ /^afdxSession:/",
"group": "inline"
}
],
"editor/context": [
Expand Down Expand Up @@ -356,6 +415,46 @@
"command": "salesforcedx-vscode-agents.createAiAuthoringBundle",
"when": "sf:project_opened"
},
{
"command": "sf.agent.sessionList.newSession",
"when": "sf:project_opened && agentforceDX:multiSession"
},
{
"command": "sf.agent.sessionList.refreshAgents",
"when": "false"
},
{
"command": "sf.agent.sessionList.reveal",
"when": "false"
},
{
"command": "sf.agent.sessionList.close",
"when": "false"
},
{
"command": "sf.agent.sessionList.activateVersion",
"when": "sf:project_opened && agentforceDX:multiSession"
},
{
"command": "afdx.session.stop",
"when": "agentforceDX:multiSession"
},
{
"command": "afdx.session.restart",
"when": "agentforceDX:multiSession"
},
{
"command": "afdx.session.recompileAndRestart",
"when": "agentforceDX:multiSession"
},
{
"command": "afdx.session.toggleDebug",
"when": "agentforceDX:multiSession"
},
{
"command": "afdx.session.activateVersion",
"when": "agentforceDX:multiSession"
},
{
"command": "sf.agent.test.view.goToTestResults",
"when": "false"
Expand Down Expand Up @@ -574,6 +673,54 @@
"command": "salesforcedx-vscode-agents.createAiAuthoringBundle",
"title": "AFDX: Create Agent",
"icon": "$(add)"
},
{
"command": "sf.agent.sessionList.newSession",
"title": "New Session",
"icon": "$(add)"
},
{
"command": "sf.agent.sessionList.refreshAgents",
"title": "Refresh Agents",
"icon": "$(refresh)"
},
{
"command": "sf.agent.sessionList.reveal",
"title": "Open Session"
},
{
"command": "sf.agent.sessionList.close",
"title": "Close Session",
"icon": "$(close)"
},
{
"command": "afdx.session.stop",
"title": "Stop Session",
"icon": "$(stop-circle)"
},
{
"command": "afdx.session.restart",
"title": "Restart Session",
"icon": "$(debug-rerun)"
},
{
"command": "afdx.session.recompileAndRestart",
"title": "Recompile & Restart"
},
{
"command": "afdx.session.toggleDebug",
"title": "Toggle Apex Debug",
"icon": "$(bug)"
},
{
"command": "afdx.session.activateVersion",
"title": "Activate Version (Focused Session)",
"icon": "$(versions)"
},
{
"command": "sf.agent.sessionList.activateVersion",
"title": "Activate Agent Version",
"icon": "$(versions)"
}
]
}
Expand Down
9 changes: 9 additions & 0 deletions src/commands/previewAgent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { SfError, SfProject } from '@salesforce/core';
import { AgentCombinedViewProvider } from '../views/agentCombinedViewProvider';
import { Agent, AgentSource } from '@salesforce/agents';
import { Logger } from '../utils/logger';
import { isMultiSessionEnabled } from '../views/multiSession/settings';

export const registerPreviewAgentCommand = () => {
return vscode.commands.registerCommand(Commands.previewAgent, async (uri?: vscode.Uri) => {
Expand All @@ -26,6 +27,14 @@ export const registerPreviewAgentCommand = () => {
// Clear previous output
logger.clear();

// When multi-session UI is enabled, delegate to the panel-based flow so the
// user lands in a dedicated editor tab instead of the single sidebar webview.
if (isMultiSessionEnabled()) {
const targetUri = uri ?? vscode.Uri.file(filePath);
await vscode.commands.executeCommand('sf.agent.sessionList.openFromUri', targetUri);
return;
}

try {
// Open the Agent Preview panel
const provider = AgentCombinedViewProvider.getInstance();
Expand Down
27 changes: 27 additions & 0 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import { AgentSource } from '@salesforce/agents';
import { getTestOutlineProvider } from './views/testOutlineProvider';
import { AgentTestRunner } from './views/testRunner';
import { toggleGeneratedDataOn, toggleGeneratedDataOff } from './commands/toggleGeneratedData';
import { publishMultiSessionContext, onDidChangeMultiSessionEnabled, isMultiSessionEnabled } from './views/multiSession/settings';
import { registerMultiSessionSurface } from './views/multiSession';

// Export the provider instance for testing purposes
let agentCombinedViewProviderInstance: AgentCombinedViewProvider | undefined;
Expand Down Expand Up @@ -54,6 +56,14 @@ export async function activate(context: vscode.ExtensionContext) {
// Set initial value
updateSkipRetrieveEnv();

// Publish multi-session feature-flag context so `when` clauses can gate views/menus.
await publishMultiSessionContext();
context.subscriptions.push(
onDidChangeMultiSessionEnabled(async () => {
await publishMultiSessionContext();
})
);

// Register commands before initializing `testRunner`
const disposables: vscode.Disposable[] = [];
disposables.push(commands.registerOpenAgentInOrgCommand());
Expand All @@ -68,6 +78,13 @@ export async function activate(context: vscode.ExtensionContext) {
const agentCombinedViewDisposable = registerAgentCombinedView(context);
context.subscriptions.push(agentCombinedViewDisposable);

// Register the multi-session surface only when the flag is enabled. When the user
// toggles the setting, VS Code needs to reload for contributed views to reflect it,
// but commands/providers gated here are registered lazily at activation.
if (isMultiSessionEnabled()) {
context.subscriptions.push(registerMultiSessionSurface(context));
}

// Update the test view without blocking activation
setTimeout(() => {
void getTestOutlineProvider().refresh();
Expand Down Expand Up @@ -250,6 +267,11 @@ const registerAgentCombinedView = (context: vscode.ExtensionContext): vscode.Dis

// Command for selecting and running an agent
const selectAndRunAgent = async () => {
if (isMultiSessionEnabled()) {
await vscode.commands.executeCommand('sf.agent.sessionList.newSession');
return;
}

const selectedAgent = await showAgentPicker('Select an agent to run');

if (selectedAgent) {
Expand Down Expand Up @@ -284,6 +306,11 @@ const registerAgentCombinedView = (context: vscode.ExtensionContext): vscode.Dis
// Register start agent alias command
disposables.push(
vscode.commands.registerCommand('sf.agent.startAgent', async () => {
if (isMultiSessionEnabled()) {
await vscode.commands.executeCommand('sf.agent.sessionList.newSession');
return;
}

const currentAgentId = provider.getCurrentAgentId();

if (currentAgentId) {
Expand Down
6 changes: 3 additions & 3 deletions src/views/agentCombined/handlers/webviewMessageHandlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { CoreExtensionService } from '../../../services/coreExtensionService';
import type { TraceHistoryEntry } from '../../../utils/traceHistory';
import type { AgentMessage } from '../types';
import type { AgentViewState } from '../state';
import type { WebviewMessageSender } from './webviewMessageSender';
import type { WebviewHost, WebviewMessageSender } from './webviewMessageSender';
import type { SessionManager } from '../session';
import type { HistoryManager } from '../history';
import type { ApexDebugManager } from '../debugging';
Expand All @@ -25,7 +25,7 @@ export class WebviewMessageHandlers {
private readonly historyManager: HistoryManager,
private readonly apexDebugManager: ApexDebugManager,
private readonly context: vscode.ExtensionContext,
private readonly webviewView: vscode.WebviewView
private readonly webviewHost: WebviewHost
) {
this.logger = new Logger(CoreExtensionService.getChannelService());
}
Expand Down Expand Up @@ -138,7 +138,7 @@ export class WebviewMessageHandlers {
this.state.currentAgentSource = agentSource;

const isLiveMode = data?.isLiveMode ?? false;
await this.sessionManager.startSession(agentId, agentSource, isLiveMode, this.webviewView);
await this.sessionManager.startSession(agentId, agentSource, isLiveMode, this.webviewHost);
}

private async handleSetApexDebugging(message: AgentMessage): Promise<void> {
Expand Down
17 changes: 12 additions & 5 deletions src/views/agentCombined/handlers/webviewMessageSender.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,30 @@ import type { AgentViewState } from '../state/agentViewState';
import type { TraceHistoryEntry } from '../../../utils/traceHistory';
import type { JsonTokenColors } from '../../../utils/themeColors';

/**
* Any host that exposes a `webview` — both `vscode.WebviewView` and `vscode.WebviewPanel` qualify.
*/
export interface WebviewHost {
webview: vscode.Webview;
}

/**
* Handles all outgoing messages to the webview
*/
export class WebviewMessageSender {
private webviewView?: vscode.WebviewView;
private host?: WebviewHost;

constructor(private readonly state: AgentViewState) {}

setWebview(webviewView: vscode.WebviewView): void {
this.webviewView = webviewView;
setWebview(host: WebviewHost): void {
this.host = host;
}

private postMessage(command: string, data?: unknown): void {
if (!this.webviewView) {
if (!this.host) {
return;
}
this.webviewView.webview.postMessage({ command, data });
this.host.webview.postMessage({ command, data });
}

// Session messages
Expand Down
4 changes: 2 additions & 2 deletions src/views/agentCombined/session/sessionManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ export class SessionManager {
agentId: string,
agentSource: AgentSource,
isLiveMode?: boolean,
webviewView?: any
webviewHost?: unknown
): Promise<void> {
if (!webviewView) {
if (!webviewHost) {
throw new Error('Webview is not ready. Please ensure the view is visible.');
}

Expand Down
Loading
Loading