Skip to content
2 changes: 1 addition & 1 deletion command-snapshot.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"command": "agent:activate",
"flagAliases": [],
"flagChars": ["n", "o"],
"flags": ["api-name", "api-version", "flags-dir", "json", "target-org"],
"flags": ["api-name", "api-version", "flags-dir", "json", "target-org", "version"],
"plugin": "@salesforce/plugin-agent"
},
{
Expand Down
18 changes: 12 additions & 6 deletions messages/agent.activate.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,29 @@ Activate an agent in an org.

# description

Activating an agent makes it immediately available to your users. An agent must be active before you can preview it with the "agent preview" CLI command or VS Code.
Activating an agent makes it immediately available to your users. A published agent must be active before you can preview it with the "agent preview" CLI command or VS Code. Agents can have multiple versions; only one version can be active at a time.

You must know the agent's API name to activate it; you can either be prompted for it or you can specify it with the --api-name flag. Find the agent's API name in its Agent Details page of your org's Agentforce Studio UI in Setup.
If you run the command without the --api-name or --version flags, the command provides a list of agent API names and versions for you to choose from. Use the flags to specify the exact agent and version without being prompted. If you use the --json flag and not --version, then the latest agent version is automatically activated.

The value of the --version flag is always a number, corresponding to the "vX" part of the "BotVersion" metadata in your project. For example, if you have a force-app/main/default/bots/My_Agent/v4.botVersion-meta.xml file in your project, then you activate this version with the "--version 4" flag.

# examples

- Activate an agent in your default target org by being prompted:
- Activate an agent in your default target org by being prompted for both its API name and version:

<%= config.bin %> <%= command.id %>

- Activate an agent with API name Resort_Manager in the org with alias "my-org":
- Activate version 2 of an agent with API name Resort_Manager in the org with alias "my-org":

<%= config.bin %> <%= command.id %> --api-name Resort_Manager --target-org my-org
<%= config.bin %> <%= command.id %> --api-name Resort_Manager --version 2 --target-org my-org

# flags.api-name.summary

API name of the agent to activate.
API name of the agent to activate; if not specified, the command provides a list that you choose from.

# flags.version.summary

Version number of the agent to activate; if not specified, the command provides a list that you choose from.

# error.missingRequiredFlags

Expand Down
4 changes: 2 additions & 2 deletions messages/agent.deactivate.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Deactivate an agent in an org.

Deactivating an agent makes it unavailable to your users. To make changes to an agent, such as adding or removing topics or actions, you must deactivate it. You can't preview an agent with the "agent preview" CLI command or VS Code if it's deactivated.

You must know the agent's API name to deactivate it; you can either be prompted for it or you can specify it with the --api-name flag. Find the agent's API name in its Agent Details page of your org's Agentforce Studio UI in Setup.
If you run the command without the --api-name flag, the command provides a list of agent API names for you to choose from. Use the flag to specify the exact agent without being prompted.

# examples

Expand All @@ -20,7 +20,7 @@ You must know the agent's API name to deactivate it; you can either be prompted

# flags.api-name.summary

API name of the agent to deactivate.
API name of the agent to deactivate; if not specified, the command provides a list that you choose from.

# error.missingRequiredFlags

Expand Down
19 changes: 19 additions & 0 deletions schemas/agent-activate.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$ref": "#/definitions/AgentActivateResult",
"definitions": {
"AgentActivateResult": {
"type": "object",
"properties": {
"success": {
"type": "boolean"
},
"version": {
"type": "number"
}
},
"required": ["success", "version"],
"additionalProperties": false
}
}
}
19 changes: 19 additions & 0 deletions schemas/agent-deactivate.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$ref": "#/definitions/AgentActivateResult",
"definitions": {
"AgentActivateResult": {
"type": "object",
"properties": {
"success": {
"type": "boolean"
},
"version": {
"type": "number"
}
},
"required": ["success", "version"],
"additionalProperties": false
}
}
}
82 changes: 79 additions & 3 deletions src/agentActivation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
*/

import { Messages, Org, SfError, SfProject } from '@salesforce/core';
import { Agent, type BotMetadata, ProductionAgent } from '@salesforce/agents';
import { Agent, type BotMetadata, type BotVersionMetadata, ProductionAgent } from '@salesforce/agents';
import { select } from '@inquirer/prompts';

type Choice<Value> = {
Expand All @@ -28,6 +28,11 @@ type AgentValue = {
DeveloperName: string;
};

type VersionChoice = {
version: number;
status: string;
};

Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
const messages = Messages.loadMessages('@salesforce/plugin-agent', 'agent.activation');

Expand All @@ -48,8 +53,10 @@ export const getAgentChoices = (agents: BotMetadata[], status: 'Active' | 'Inact
agents.map((agent) => {
let disabled: string | boolean = false;

const lastBotVersion = agent.BotVersions.records[agent.BotVersions.records.length - 1];
if (lastBotVersion.Status === status) {
// For deactivate (status='Inactive'), check if any version is Active (can be deactivated)
// For activate (status='Active'), check if any version is Inactive (can be activated)
const hasAvailableVersion = agent.BotVersions.records.some((version) => version.Status !== status);
if (!hasAvailableVersion) {
disabled = `(Already ${status})`;
}
if (agentIsUnsupported(agent.DeveloperName)) {
Expand All @@ -66,6 +73,22 @@ export const getAgentChoices = (agents: BotMetadata[], status: 'Active' | 'Inact
};
});

export const getVersionChoices = (
versions: BotVersionMetadata[],
status: 'Active' | 'Inactive'
): Array<Choice<VersionChoice>> =>
versions.map((version) => {
const isTargetStatus = version.Status === status;
return {
name: `Version ${version.VersionNumber}`,
value: {
version: version.VersionNumber,
status: version.Status,
},
disabled: isTargetStatus ? `(Already ${status})` : false,
};
});

export const getAgentForActivation = async (config: {
targetOrg: Org;
status: 'Active' | 'Inactive';
Expand Down Expand Up @@ -110,3 +133,56 @@ export const getAgentForActivation = async (config: {
project: SfProject.getInstance(),
});
};

export const getVersionForActivation = async (config: {
agent: ProductionAgent;
status: 'Active' | 'Inactive';
versionFlag?: number;
jsonEnabled?: boolean;
}): Promise<{ version: number | undefined; warning?: string }> => {
const { agent, status, versionFlag, jsonEnabled } = config;

// If version flag is provided, return it
if (versionFlag !== undefined) {
return { version: versionFlag };
}

// Get bot metadata to access versions
const botMetadata = await agent.getBotMetadata();
// Filter out deleted versions as a defensive measure
const versions = botMetadata.BotVersions.records.filter((v) => !v.IsDeleted);

// If there's only one version, return it
if (versions.length === 1) {
return { version: versions[0].VersionNumber };
}

// Get version choices and filter out disabled ones
const choices = getVersionChoices(versions, status);
const availableChoices = choices.filter((choice) => !choice.disabled);

// If there's only one available choice, return it automatically
if (availableChoices.length === 1) {
return { version: availableChoices[0].value.version };
}

// If JSON mode is enabled, automatically select the latest available version
if (jsonEnabled) {
// Find the latest (highest version number) available version
const latestVersion = availableChoices.reduce((latest, choice) =>
choice.value.version > latest.value.version ? choice : latest
);
return {
version: latestVersion.value.version,
warning: `No version specified, automatically selected latest available version: ${latestVersion.value.version}`,
};
}

// Prompt user to select a version
const versionChoice = await select({
message: 'Select a version',
choices,
});

return { version: versionChoice.version };
};
27 changes: 20 additions & 7 deletions src/commands/agent/activate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,18 @@
*/
import { SfCommand, Flags } from '@salesforce/sf-plugins-core';
import { Messages } from '@salesforce/core';
import { getAgentForActivation } from '../../agentActivation.js';
import { getAgentForActivation, getVersionForActivation } from '../../agentActivation.js';

export type AgentActivateResult = { success: boolean; version: number };

Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
const messages = Messages.loadMessages('@salesforce/plugin-agent', 'agent.activate');

export default class AgentActivate extends SfCommand<void> {
export default class AgentActivate extends SfCommand<AgentActivateResult> {
public static readonly summary = messages.getMessage('summary');
public static readonly description = messages.getMessage('description');
public static readonly examples = messages.getMessages('examples');
public static readonly enableJsonFlag = true;

public static readonly flags = {
'target-org': Flags.requiredOrg(),
Expand All @@ -32,9 +35,10 @@ export default class AgentActivate extends SfCommand<void> {
summary: messages.getMessage('flags.api-name.summary'),
char: 'n',
}),
version: Flags.integer({ summary: messages.getMessage('flags.version.summary') }),
};

public async run(): Promise<void> {
public async run(): Promise<AgentActivateResult> {
const { flags } = await this.parse(AgentActivate);

const apiNameFlag = flags['api-name'];
Expand All @@ -43,11 +47,20 @@ export default class AgentActivate extends SfCommand<void> {
if (!apiNameFlag && this.jsonEnabled()) {
throw messages.createError('error.missingRequiredFlags', ['api-name']);
}

const agent = await getAgentForActivation({ targetOrg, status: 'Active', apiNameFlag });
await agent.activate();
const agentName = (await agent.getBotMetadata()).DeveloperName;
const { version, warning } = await getVersionForActivation({
agent,
status: 'Active',
versionFlag: flags.version,
jsonEnabled: this.jsonEnabled(),
});
const result = await agent.activate(version);
const metadata = await agent.getBotMetadata();

this.log(`Agent ${agentName} activated.`);
this.log(`${metadata.DeveloperName} v${result.VersionNumber} activated.`);
if (warning) {
this.warn(warning);
}
return { success: true, version: result.VersionNumber };
}
}
13 changes: 8 additions & 5 deletions src/commands/agent/deactivate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,16 @@
import { SfCommand, Flags } from '@salesforce/sf-plugins-core';
import { Messages } from '@salesforce/core';
import { getAgentForActivation } from '../../agentActivation.js';
import { AgentActivateResult } from './activate.js';

Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
const messages = Messages.loadMessages('@salesforce/plugin-agent', 'agent.deactivate');

export default class AgentDeactivate extends SfCommand<void> {
export default class AgentDeactivate extends SfCommand<AgentActivateResult> {
public static readonly summary = messages.getMessage('summary');
public static readonly description = messages.getMessage('description');
public static readonly examples = messages.getMessages('examples');
public static enableJsonFlag = true;

public static readonly flags = {
'target-org': Flags.requiredOrg(),
Expand All @@ -34,7 +36,7 @@ export default class AgentDeactivate extends SfCommand<void> {
}),
};

public async run(): Promise<void> {
public async run(): Promise<AgentActivateResult> {
const { flags } = await this.parse(AgentDeactivate);

const apiNameFlag = flags['api-name'];
Expand All @@ -45,9 +47,10 @@ export default class AgentDeactivate extends SfCommand<void> {
}

const agent = await getAgentForActivation({ targetOrg, status: 'Inactive', apiNameFlag });
await agent.deactivate();
const agentName = (await agent.getBotMetadata()).DeveloperName;
const result = await agent.deactivate();
const metadata = await agent.getBotMetadata();

this.log(`Agent ${agentName} deactivated.`);
this.log(`${metadata.DeveloperName} v${result.VersionNumber} deactivated.`);
return { success: true, version: result.VersionNumber };
}
}
Loading
Loading