Skip to content
11 changes: 10 additions & 1 deletion command-snapshot.json
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,16 @@
"command": "agent:preview:start",
"flagAliases": [],
"flagChars": ["n", "o"],
"flags": ["api-name", "api-version", "authoring-bundle", "flags-dir", "json", "target-org", "use-live-actions"],
"flags": [
"api-name",
"api-version",
"authoring-bundle",
"flags-dir",
"json",
"simulate-actions",
"target-org",
"use-live-actions"
],
"plugin": "@salesforce/plugin-agent"
},
{
Expand Down
19 changes: 14 additions & 5 deletions messages/agent.preview.start.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,12 @@ This command outputs a session ID that you then use with the "agent preview send

Identify the agent you want to start previewing with either the --authoring-bundle flag to specify a local authoring bundle's API name or --api-name to specify an activated published agent's API name. To find either API name, navigate to your package directory in your DX project. The API name of an authoring bundle is the same as its directory name under the "aiAuthoringBundles" metadata directory. Similarly, the published agent's API name is the same as its directory name under the "Bots" metadata directory.

When starting a preview session using the authoring bundle, which contains the agent's Agent Script file, the preview uses mocked actions by default. Specify --use-live-actions for live mode, which uses the real Apex classes, flows, etc, in the org for the actions.
When starting a preview session with --authoring-bundle, you must explicitly specify the execution mode using one of these flags:

- --use-live-actions: Executes real Apex classes, flows, and other actions in the org. This surfaces compile and validation errors during preview.
- --simulate-actions: Uses AI to simulate action execution without calling real implementations.

Published agents (--api-name) always use live actions. The mode flags are optional and have no effect for published agents.

# flags.api-name.summary

Expand All @@ -20,7 +25,11 @@ API name of the authoring bundle metadata component that contains the agent's Ag

# flags.use-live-actions.summary

Use real actions in the org; if not specified, preview uses AI to simulate (mock) actions.
Execute real actions in the org (Apex classes, flows, etc.). Required with --authoring-bundle.

# flags.simulate-actions.summary

Use AI to simulate action execution instead of calling real actions. Required with --authoring-bundle.

# output.sessionId

Expand All @@ -40,14 +49,14 @@ Failed to start preview session: %s

# examples

- Start a programmatic agent preview session by specifying an authoring bundle; uses mocked actions by default. Use the org with alias "my-dev-org":
- Start a programmatic agent preview session by specifying an authoring bundle; use simulated actions. Use the org with alias "my-dev-org":

<%= config.bin %> <%= command.id %> --authoring-bundle My_Agent_Bundle --target-org my-dev-org
<%= config.bin %> <%= command.id %> --authoring-bundle My_Agent_Bundle --target-org my-dev-org --simulate-actions

- Similar to previous example but use live actions and the default org:

<%= config.bin %> <%= command.id %> --authoring-bundle My_Agent_Bundle --use-live-actions

- Start a preview session with an activated published agent:
- Start a preview session with an activated published agent (always uses live actions):

<%= config.bin %> <%= command.id %> --api-name My_Published_Agent
28 changes: 23 additions & 5 deletions src/commands/agent/preview/start.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
*/

import { Flags, SfCommand, toHelpSection } from '@salesforce/sf-plugins-core';
import { Lifecycle, Messages, SfError, EnvironmentVariable } from '@salesforce/core';
import { EnvironmentVariable, Lifecycle, Messages, SfError } from '@salesforce/core';
import { Agent, ProductionAgent, ScriptAgent } from '@salesforce/agents';
import { createCache } from '../../../previewSessionStore.js';
import { COMPILATION_API_EXIT_CODES } from '../../../common.js';
Expand Down Expand Up @@ -61,14 +61,29 @@ export default class AgentPreviewStart extends SfCommand<AgentPreviewStartResult
}),
'use-live-actions': Flags.boolean({
summary: messages.getMessage('flags.use-live-actions.summary'),
default: false,
exclusive: ['simulate-actions'],
}),
'simulate-actions': Flags.boolean({
summary: messages.getMessage('flags.simulate-actions.summary'),
exclusive: ['use-live-actions'],
}),
};

public async run(): Promise<AgentPreviewStartResult> {
const { flags } = await this.parse(AgentPreviewStart);

// Validate: authoring-bundle requires exactly one mode flag
// (mutual exclusion of mode flags handled by 'exclusive' in flag definitions)
if (flags['authoring-bundle'] && !flags['use-live-actions'] && !flags['simulate-actions']) {
throw new SfError(
'When using --authoring-bundle, you must specify either --use-live-actions or --simulate-actions.',
'MissingModeFlag'
);
}

const conn = flags['target-org'].getConnection(flags['api-version']);
const useLiveActions = flags['use-live-actions'];
const simulateActions = flags['simulate-actions'];
const agentIdentifier = flags['authoring-bundle'] ?? flags['api-name']!;

// Track telemetry for agent initialization
Expand Down Expand Up @@ -112,13 +127,16 @@ export default class AgentPreviewStart extends SfCommand<AgentPreviewStartResult
throw wrapped;
}

// Set mode for authoring bundles based on which flag was specified
// (mutual exclusion enforced by flag definitions - can't have both)
if (agent instanceof ScriptAgent) {
agent.preview.setMockMode(useLiveActions ? 'Live Test' : 'Mock');
agent.preview.setMockMode(simulateActions ? 'Mock' : 'Live Test');
}

if (useLiveActions && agent instanceof ProductionAgent) {
// Warn if mode flags are used with published agents (they have no effect)
if (agent instanceof ProductionAgent && (useLiveActions || simulateActions)) {
void Lifecycle.getInstance().emitWarning(
'Published agents always use real actions; --use-live-actions has no effect for published agents.'
'Published agents always use real actions; --use-live-actions and --simulate-actions have no effect for published agents.'
);
}

Expand Down
44 changes: 42 additions & 2 deletions test/commands/agent/preview/start.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,14 @@ describe('agent preview start', () => {
});

describe('setMockMode', () => {
it('should call setMockMode with "Mock" when --use-live-actions is not set', async () => {
await AgentPreviewStart.run(['--authoring-bundle', 'MyAgent', '--target-org', 'test@org.com']);
it('should call setMockMode with "Mock" when --simulate-actions is set', async () => {
await AgentPreviewStart.run([
'--authoring-bundle',
'MyAgent',
'--simulate-actions',
'--target-org',
'test@org.com',
]);

expect(setMockModeStub.calledOnce).to.be.true;
expect(setMockModeStub.firstCall.args[0]).to.equal('Mock');
Expand All @@ -96,5 +102,39 @@ describe('agent preview start', () => {
expect(setMockModeStub.calledOnce).to.be.true;
expect(setMockModeStub.firstCall.args[0]).to.equal('Live Test');
});

it('should throw error when using --authoring-bundle without mode flag', async () => {
try {
await AgentPreviewStart.run(['--authoring-bundle', 'MyAgent', '--target-org', 'test@org.com']);
expect.fail('Should have thrown an error');
} catch (error: unknown) {
expect((error as Error).message).to.include('must specify either --use-live-actions or --simulate-actions');
}
});

it('should throw error when using both mode flags together', async () => {
try {
await AgentPreviewStart.run([
'--authoring-bundle',
'MyAgent',
'--use-live-actions',
'--simulate-actions',
'--target-org',
'test@org.com',
]);
expect.fail('Should have thrown an error');
} catch (error: unknown) {
expect((error as Error).message).to.match(/cannot also be provided when using/i);
}
});

it('should throw error when neither --api-name nor --authoring-bundle is provided', async () => {
try {
await AgentPreviewStart.run(['--use-live-actions', '--target-org', 'test@org.com']);
expect.fail('Should have thrown an error');
} catch (error: unknown) {
expect((error as Error).message).to.match(/exactly one|api-name|authoring-bundle/i);
}
});
});
});
11 changes: 8 additions & 3 deletions test/nuts/z3.agent.preview.nut.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,17 @@ describe('agent preview', function () {

it('should fail when authoring bundle does not exist', async () => {
const invalidBundle = 'NonExistent_Bundle';
execCmd(`agent preview --authoring-bundle ${invalidBundle} --target-org ${getUsername()}`, { ensureExitCode: 1 });
execCmd(
`agent preview start --authoring-bundle ${invalidBundle} --simulate-actions --target-org ${getUsername()}`,
{ ensureExitCode: 1 }
);
});

it('should fail when api-name does not exist in org', async () => {
const invalidApiName = 'NonExistent_Agent_12345';
execCmd(`agent preview --api-name ${invalidApiName} --target-org ${getUsername()}`, { ensureExitCode: 1 });
execCmd(`agent preview start --api-name ${invalidApiName} --target-org ${getUsername()}`, {
ensureExitCode: 1,
});
});

describe('using preview start/send/end commands', () => {
Expand All @@ -53,7 +58,7 @@ describe('agent preview', function () {
const targetOrg = getUsername();

const startResult = execCmd<AgentPreviewStartResult>(
`agent preview start --authoring-bundle ${bundleApiName} --target-org ${targetOrg} --json`
`agent preview start --authoring-bundle ${bundleApiName} --simulate-actions --target-org ${targetOrg} --json`
).jsonOutput?.result;
expect(startResult?.sessionId).to.be.a('string');
const sessionId = startResult!.sessionId;
Expand Down
Loading