Skip to content
Open
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
1 change: 0 additions & 1 deletion .eslintignore

This file was deleted.

18 changes: 0 additions & 18 deletions .eslintrc.cjs

This file was deleted.

4 changes: 4 additions & 0 deletions .github/workflows/onRelease.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,13 @@ jobs:
- uses: salesforcecli/github-workflows/.github/actions/getPreReleaseTag@main
id: distTag
npm:
permissions:
id-token: write
contents: read
uses: salesforcecli/github-workflows/.github/workflows/npmPublish.yml@main
needs: [getDistTag]
with:
trustedPublishing: true
ctc: true
sign: true
tag: ${{ needs.getDistTag.outputs.tag || 'latest' }}
Expand Down
9 changes: 9 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import salesforce from 'eslint-config-salesforce-typescript';
import sfPlugin from 'eslint-plugin-sf-plugin';
import eslintReact from '@eslint-react/eslint-plugin';

export default [
...salesforce,
...sfPlugin.configs.recommended,
{ files: ['**/*.ts', '**/*.tsx'], ...eslintReact.configs['recommended-typescript'] },
];
18 changes: 11 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,22 +28,25 @@
"yaml": "^2.9.0"
},
"devDependencies": {
"@eslint-react/eslint-plugin": "^5.26.3",
"@oclif/plugin-command-snapshot": "^6.0.0",
"@oclif/test": "^5.0.0",
"@salesforce/cli-plugins-testkit": "^5.3.66",
"@salesforce/dev-scripts": "^11.0.4",
"@salesforce/dev-scripts": "^15.0.0",
"@salesforce/plugin-command-reference": "^3.1.132",
"@types/chai": "^4.3.17",
"@types/inquirer": "^9.0.10",
"@types/mocha": "^10.0.10",
"@types/node": "^18",
"@types/react": "^19.3.0",
"eslint-config-xo": "^0.50.0",
"eslint-config-xo-react": "^0.27.0",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^4.6.2",
"eslint-plugin-sf-plugin": "^1.20.33",
"@types/sinon": "^10.0.20",
"eslint": "^10.4.0",
"eslint-config-salesforce-typescript": "^6.0.0",
"eslint-plugin-sf-plugin": "^3.0.1",
"esmock": "^2.7.6",
"oclif": "^5.0.0",
"ts-node": "^10.9.2",
"typescript": "^5.9.3"
"typescript": "^6.0.0"
},
"engines": {
"node": ">=22.0.0"
Expand Down Expand Up @@ -179,6 +182,7 @@
"src/**/*.ts",
"test/**/*.ts",
"messages/**",
"**/eslint.config.*",
"**/.eslint*",
"**/tsconfig.json"
],
Expand Down
2 changes: 1 addition & 1 deletion src/agentActivation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ export const getAgentForActivation = async (config: {
}): Promise<ProductionAgent> => {
const { targetOrg, status, apiNameFlag } = config;

let agentsInOrg: BotMetadata[] = [];
let agentsInOrg: BotMetadata[];
try {
agentsInOrg = await Agent.listRemote(targetOrg.getConnection());
} catch (error) {
Expand Down
2 changes: 1 addition & 1 deletion src/agentTestCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export class AgentTestCache extends TTLConfig<TTLConfig.Options, CacheContents>
return this.get(key);
}

public useIdOrMostRecent(
public resolveIdOrMostRecent(
runId: string | undefined,
useMostRecent: boolean
): { runId: string; name?: string; outputDir?: string; resultFormat?: ResultFormat; runnerType?: TestRunnerType } {
Expand Down
4 changes: 2 additions & 2 deletions src/commands/agent/generate/agent-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ export const FLAGGABLE_PROMPTS = {
const companySite = regExp.test(d) ? d : `https://${d}`;
new URL(companySite);
return true;
} catch (e) {
} catch {
return 'Please enter a valid URL';
}
},
Expand Down Expand Up @@ -224,7 +224,7 @@ export default class AgentCreateSpec extends SfCommand<AgentCreateSpecResult> {
(flags.tone as AgentTone) ??
inputSpec?.tone ??
(flags['full-interview'] ? await promptForFlag(FLAGGABLE_PROMPTS.tone) : 'casual');
tone = validateTone(tone as AgentTone);
tone = validateTone(tone);
// const primaryLanguage =
// flags['primary-language'] ??
// inputSpec?.primaryLanguage ??
Expand Down
4 changes: 2 additions & 2 deletions src/commands/agent/generate/test-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,7 @@ export async function getPluginsAndFunctions(
}),
{}
);
} catch (e) {
} catch {
// do nothing, we were trying to read the old genAiPlanner
}

Expand Down Expand Up @@ -399,7 +399,7 @@ export async function getPluginsAndFunctions(
{}
);
}
} catch (e) {
} catch {
throw new SfError(
`Error parsing GenAiPlannerBundle: ${
parsedBotVersion.BotVersion.conversationDefinitionPlanners.genAiPlannerName ?? subjectName
Expand Down
3 changes: 1 addition & 2 deletions src/commands/agent/mcp/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import {
type McpServerCreateOutput,
type McpServerCreateInput,
type McpServerAuthorizationInput,
type McpAuthType,
} from '@salesforce/agents';

Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
Expand Down Expand Up @@ -77,7 +76,7 @@ export default class ApiCatalogMcpServerCreate extends SfCommand<McpServerCreate
const { flags } = await this.parse(ApiCatalogMcpServerCreate);
const connection = flags['target-org'].getConnection(flags['api-version']);

const authType = flags['auth-type'] as McpAuthType;
const authType = flags['auth-type'];

if (authType === 'OAUTH') {
if (!flags['identity-provider'] || !flags['client-id'] || !flags['client-secret'] || !flags.scope) {
Expand Down
10 changes: 2 additions & 8 deletions src/commands/agent/mcp/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import {
type McpServerOutput,
type McpServerUpdateInput,
type McpServerAuthorizationInput,
type McpAuthType,
} from '@salesforce/agents';

Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
Expand Down Expand Up @@ -89,16 +88,11 @@ export default class ApiCatalogMcpServerUpdate extends SfCommand<ApiCatalogMcpSe
input.serverUrl = flags['server-url'];
}

const authType = flags['auth-type'] as McpAuthType | undefined;
const authType = flags['auth-type'];

if (authType) {
if (authType === 'OAUTH') {
if (
!flags['identity-provider'] ||
!flags['client-id'] ||
!flags['client-secret'] ||
!flags.scope
) {
if (!flags['identity-provider'] || !flags['client-id'] || !flags['client-secret'] || !flags.scope) {
throw new SfError(messages.getMessage('error.missingOauthFields'), 'MissingOauthFields', [], 1);
}

Expand Down
2 changes: 1 addition & 1 deletion src/commands/agent/publish/authoring-bundle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ export default class AgentPublishAuthoringBundle extends SfCommand<AgentPublishA
{
label: 'Agent Name',
type: 'static-key-value',
get: (data) => data?.agentName,
get: (data): string | undefined => data?.agentName,
bold: true,
color: 'cyan',
},
Expand Down
42 changes: 23 additions & 19 deletions src/commands/agent/test/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ const FLAGGABLE_PROMPTS = {
},
};

async function promptUntilUniqueName(connection: Connection, name?: string | undefined): Promise<string | undefined> {
async function promptUntilUniqueName(connection: Connection, name?: string): Promise<string | undefined> {
const apiName = name ?? (await promptForFlag(FLAGGABLE_PROMPTS['api-name']));
const existingDefinitions = await AgentTest.list(connection);
if (existingDefinitions.some((d) => d.fullName === apiName)) {
Expand All @@ -83,6 +83,27 @@ async function promptUntilUniqueName(connection: Connection, name?: string | und
return apiName;
}

const toCreateError = (error: unknown, spec: string): SfError => {
const wrapped = SfError.wrap(error);

if (wrapped.code === 'ENOENT' || wrapped.name === 'ENOENT') {
return new SfError(`Test spec file not found: ${spec}`, 'SpecFileNotFound', [], 2, wrapped);
}

// NGT validateNgtSpec errors are user-fixable spec issues — exit 1, not deploy/network.
if (wrapped.name?.startsWith('ngt')) {
return wrapped;
}

// Deploy failures from the lib are bare SfErrors with the componentFailures text as message
// and no structured code, so message substring is the only available signal.
if (wrapped.message.toLowerCase().includes('deploy') || wrapped.message.toLowerCase().includes('api')) {
return new SfError(`Deployment failed: ${wrapped.message}`, 'DeploymentFailed', [wrapped.message], 4, wrapped);
}

return wrapped;
};

export default class AgentTestCreate extends SfCommand<AgentTestCreateResult> {
public static readonly summary = messages.getMessage('summary');
public static readonly description = messages.getMessage('description');
Expand Down Expand Up @@ -190,24 +211,7 @@ export default class AgentTestCreate extends SfCommand<AgentTestCreateResult> {
path = result.path;
contents = result.contents;
} catch (error) {
const wrapped = SfError.wrap(error);

if (wrapped.code === 'ENOENT' || wrapped.name === 'ENOENT') {
throw new SfError(`Test spec file not found: ${spec}`, 'SpecFileNotFound', [], 2, wrapped);
}

// NGT validateNgtSpec errors are user-fixable spec issues — exit 1, not deploy/network.
if (wrapped.name?.startsWith('ngt')) {
throw wrapped;
}

// Deploy failures from the lib are bare SfErrors with the componentFailures text as message
// and no structured code, so message substring is the only available signal.
if (wrapped.message.toLowerCase().includes('deploy') || wrapped.message.toLowerCase().includes('api')) {
throw new SfError(`Deployment failed: ${wrapped.message}`, 'DeploymentFailed', [wrapped.message], 4, wrapped);
}

throw wrapped;
throw toCreateError(error, spec);
}

if (flags.preview) {
Expand Down
4 changes: 2 additions & 2 deletions src/commands/agent/test/resume.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ export default class AgentTestResume extends SfCommand<AgentTestRunResult> {
let cachedRunnerType;

try {
const cacheEntry = agentTestCache.useIdOrMostRecent(flags['job-id'], flags['use-most-recent']);
const cacheEntry = agentTestCache.resolveIdOrMostRecent(flags['job-id'], flags['use-most-recent']);
name = cacheEntry.name;
runId = cacheEntry.runId;
outputDir = cacheEntry.outputDir;
Expand Down Expand Up @@ -169,7 +169,7 @@ export default class AgentTestResume extends SfCommand<AgentTestRunResult> {
process.exitCode = 1;
}

return { ...response, runId, status: 'COMPLETED' } as AgentTestRunResult;
return { ...response, runId, status: 'COMPLETED' };
}

protected catch(error: Error | SfError | CLIError): Promise<never> {
Expand Down
5 changes: 2 additions & 3 deletions src/commands/agent/test/run-eval.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import {
splitIntoBatches,
type EvalApiResponse,
formatResults,
type ResultFormat,
isYamlTestSpec,
parseTestSpec,
translateTestSpec,
Expand Down Expand Up @@ -190,10 +189,10 @@ export default class AgentTestRunEval extends SfCommand<RunEvalResult> {
);
});

const mergedResponse: EvalApiResponse = { results: allResults as EvalApiResponse['results'] };
const mergedResponse: EvalApiResponse = { results: allResults };

// 7. Format output
this.log(formatResults(mergedResponse, (flags['result-format'] ?? 'human') as ResultFormat));
this.log(formatResults(mergedResponse, flags['result-format'] ?? 'human'));

// 8. Build structured result for --json
const { summary, testSummaries } = buildResultSummary(mergedResponse);
Expand Down
2 changes: 1 addition & 1 deletion src/commands/agent/test/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ export default class AgentTestRun extends SfCommand<AgentTestRunResult> {
process.exitCode = 1;
}

return { ...detailsResponse, status: 'COMPLETED', runId: response.runId } as AgentTestRunResult;
return { ...detailsResponse, status: 'COMPLETED', runId: response.runId };
} else {
this.mso.stop();
this.log(
Expand Down
2 changes: 1 addition & 1 deletion src/commands/agent/trace/read.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ export type AgentTraceReadResult = {
};

const isFunctionStep = (s: PlanStep): s is FunctionStep => s.type === 'FunctionStep';
const asFunctionWithErrors = (s: FunctionStep): FunctionStepWithErrors => s as FunctionStepWithErrors;
const asFunctionWithErrors = (s: FunctionStep): FunctionStepWithErrors => s;

function summarizeTurn(turn: number, planId: string, trace: PlannerResponse): TurnSummary {
const plan = trace.plan;
Expand Down
8 changes: 5 additions & 3 deletions src/components/agent-preview-react.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ function Typing(): React.ReactNode {
setFrame((prev) => (prev + 1) % 4);
}, 350);

return () => clearInterval(timer);
return (): void => clearInterval(timer);
}, []);

const colors = ['grey', 'grey', 'grey'];
Expand Down Expand Up @@ -70,6 +70,7 @@ export function AgentPreviewReact(props: {
const [sessionId, setSessionId] = React.useState('');
const [query, setQuery] = React.useState('');
const [isTyping, setIsTyping] = React.useState(true);
const [typingSince, setTypingSince] = React.useState(() => new Date());
const [sessionEnded, setSessionEnded] = React.useState(false);
const [exitRequested, setExitRequested] = React.useState(false);
const [showSavePrompt, setShowSavePrompt] = React.useState(false);
Expand Down Expand Up @@ -129,7 +130,7 @@ export function AgentPreviewReact(props: {
await agent.end('UserRequest');
}
process.exit(0);
} catch (e) {
} catch {
// in case the agent session never started, calling agent.end will throw an error, but we've already shown the error to the user
process.exit(0);
}
Expand Down Expand Up @@ -268,7 +269,7 @@ export function AgentPreviewReact(props: {
<Box alignItems="flex-start" flexDirection="column">
<Box flexDirection="row" columnGap={1}>
<Text>{name}</Text>
<Text color="grey">{new Date().toLocaleString()}</Text>
<Text color="grey">{typingSince.toLocaleString()}</Text>
</Box>
<Box width={7} borderStyle="round" paddingLeft={1} paddingRight={1}>
<Typing />
Expand Down Expand Up @@ -348,6 +349,7 @@ export function AgentPreviewReact(props: {
try {
// Add the most recent user message to the chat window
setMessages((prev) => [...prev, { role: 'user', content, timestamp: new Date() }]);
setTypingSince(new Date());
setIsTyping(true);
// send() only takes the message, not sessionId
const response = await agent.send(content);
Expand Down
2 changes: 1 addition & 1 deletion src/flags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ export async function getHiddenDirs(projectRoot?: string): Promise<string[]> {
try {
const files = await readdir(rootDir, { withFileTypes: true });
return files.filter((file) => file.isDirectory() && file.name.startsWith('.')).map((file) => file.name);
} catch (error) {
} catch {
return [];
}
}
Expand Down
Loading
Loading