From d741ab38b6060c3ecbb8de8227a7770772ec2d1c Mon Sep 17 00:00:00 2001 From: Cristian Dominguez Date: Fri, 6 Jun 2025 13:59:47 -0300 Subject: [PATCH 1/4] feat: add linked connected apps for auth --- command-snapshot.json | 4 ++-- messages/agent.preview.md | 19 ++++++++++--------- src/commands/agent/preview.ts | 19 +++++++++++++------ 3 files changed, 25 insertions(+), 17 deletions(-) diff --git a/command-snapshot.json b/command-snapshot.json index 754056f1..ed14e059 100644 --- a/command-snapshot.json +++ b/command-snapshot.json @@ -55,8 +55,8 @@ "alias": [], "command": "agent:preview", "flagAliases": [], - "flagChars": ["a", "d", "n", "o", "x"], - "flags": ["apex-debug", "api-name", "api-version", "connected-app-user", "flags-dir", "output-dir", "target-org"], + "flagChars": ["d", "n", "o", "x"], + "flags": ["apex-debug", "api-name", "api-version", "app", "flags-dir", "output-dir", "target-org"], "plugin": "@salesforce/plugin-agent" }, { diff --git a/messages/agent.preview.md b/messages/agent.preview.md index 61d7767c..d3245580 100644 --- a/messages/agent.preview.md +++ b/messages/agent.preview.md @@ -28,23 +28,24 @@ Before you use this command, you must complete these steps: 3. Copy the consumer key from your connected app as described in the "Obtain Credentials" section here: https://developer.salesforce.com/docs/einstein/genai/guide/agent-api-get-started.html#obtain-credentials. -4. Set the "SFDX_AUTH_SCOPES" environment variable to "refresh_token sfap_api chatbot_api web api". This step ensures that you get the specific OAuth scopes required by this command. - -5. Using the username of the user you specified as the "Run As" user above, authorize your org using the web server flow, as described in this document: https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev/sfdx_dev_auth_web_flow.htm. +4. Link the connected app to your authenticated user with the required API scopes using the web server flow: +``` +sf org login web --app agent-app --username --client-id --scopes "sfap_api chatbot_api api refresh_token api web" +``` IMPORTANT: You must use the "--client-id " flag of "org login web", where CONNECTED-APP-CONSUMER-KEY is the consumer key you previously copied. This step ensures that the "org login web" command uses your custom connected app, and not the default CLI connected app. Press Enter to skip sharing the client secret. -6. When you run this command to interact with an agent, specify the username you authorized in the preceding step with the --connected-app-user (-a) flag. +5. When you run this command to interact with an agent, specify the org username and linked connected app with the --target-org and --app flags. # flags.api-name.summary API name of the agent you want to interact with. -# flags.connected-app-user.summary +# flags.app.summary -Username or alias of the connected app user that's configured with web-based access tokens to the agent. +Name of the linked connected app or external client app to use for the connection. # flags.output-dir.summary @@ -56,10 +57,10 @@ Enable Apex debug logging during the agent preview conversation. # examples -- Interact with an agent with API name "Resort_Manager" in the org with alias "my-org". Connect to your agent using the alias "my-agent-user"; this alias must point to the username who is authorized using the Web server flow: +- Interact with an agent with API name "Resort_Manager" in the org with alias "my-org" and the linked "agent-app" connected app. - <%= config.bin %> <%= command.id %> --api-name "Resort_Manager" --target-org my-org --connected-app-user my-agent-user + <%= config.bin %> <%= command.id %> --api-name "Resort_Manager" --target-org my-org --app agent-app - Same as the preceding example, but this time save the conversation transcripts to the "./transcripts/my-preview" directory rather than the default "./temp/agent-preview": - <%= config.bin %> <%= command.id %> --api-name "Resort_Manager" --target-org my-org --connected-app-user my-agent-user --output-dir "transcripts/my-preview" + <%= config.bin %> <%= command.id %> --api-name "Resort_Manager" --target-org my-org --app agent-app --output-dir "transcripts/my-preview" diff --git a/src/commands/agent/preview.ts b/src/commands/agent/preview.ts index 92aabd1f..c54bbc2a 100644 --- a/src/commands/agent/preview.ts +++ b/src/commands/agent/preview.ts @@ -7,7 +7,7 @@ import { resolve, join } from 'node:path'; import { SfCommand, Flags } from '@salesforce/sf-plugins-core'; -import { Messages, SfError } from '@salesforce/core'; +import { AuthInfo, Connection, Messages, SfError } from '@salesforce/core'; import React from 'react'; import { render } from 'ink'; import { env } from '@salesforce/kit'; @@ -54,9 +54,8 @@ export default class AgentPreview extends SfCommand { public static readonly flags = { 'target-org': Flags.requiredOrg(), 'api-version': Flags.orgApiVersion(), - 'connected-app-user': Flags.requiredOrg({ - summary: messages.getMessage('flags.connected-app-user.summary'), - char: 'a', + app: Flags.string({ + summary: messages.getMessage('flags.app.summary'), required: true, }), 'api-name': Flags.string({ @@ -78,7 +77,15 @@ export default class AgentPreview extends SfCommand { const { 'api-name': apiNameFlag } = flags; const conn = flags['target-org'].getConnection(flags['api-version']); - const apiConn = flags['connected-app-user'].getConnection(flags['api-version']); + + const authInfo = await AuthInfo.create({ + username: flags['target-org'].getUsername(), + }); + + const jwtConn = await Connection.create({ + authInfo, + app: flags.app, + }); const agentsQuery = await conn.query( 'SELECT Id, DeveloperName, (SELECT Status FROM BotVersions) FROM BotDefinition WHERE IsDeleted = false' @@ -102,7 +109,7 @@ export default class AgentPreview extends SfCommand { } const outputDir = await resolveOutputDir(flags['output-dir'], flags['apex-debug']); - const agentPreview = new Preview(apiConn, selectedAgent.Id); + const agentPreview = new Preview(jwtConn, selectedAgent.Id); agentPreview.toggleApexDebugMode(flags['apex-debug']); const instance = render( From b155fa97a5ffc01e963c7925cccb3c7cae8e3ced Mon Sep 17 00:00:00 2001 From: Cristian Dominguez Date: Wed, 11 Jun 2025 16:54:17 -0300 Subject: [PATCH 2/4] chore: flag rename --- command-snapshot.json | 4 ++-- messages/agent.preview.md | 17 +++++++++-------- src/commands/agent/preview.ts | 8 +++++--- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/command-snapshot.json b/command-snapshot.json index ed14e059..36cd2292 100644 --- a/command-snapshot.json +++ b/command-snapshot.json @@ -55,8 +55,8 @@ "alias": [], "command": "agent:preview", "flagAliases": [], - "flagChars": ["d", "n", "o", "x"], - "flags": ["apex-debug", "api-name", "api-version", "app", "flags-dir", "output-dir", "target-org"], + "flagChars": ["c", "d", "n", "o", "x"], + "flags": ["apex-debug", "api-name", "api-version", "client-app", "flags-dir", "output-dir", "target-org"], "plugin": "@salesforce/plugin-agent" }, { diff --git a/messages/agent.preview.md b/messages/agent.preview.md index d3245580..30211b4c 100644 --- a/messages/agent.preview.md +++ b/messages/agent.preview.md @@ -29,23 +29,24 @@ Before you use this command, you must complete these steps: 3. Copy the consumer key from your connected app as described in the "Obtain Credentials" section here: https://developer.salesforce.com/docs/einstein/genai/guide/agent-api-get-started.html#obtain-credentials. 4. Link the connected app to your authenticated user with the required API scopes using the web server flow: + ``` -sf org login web --app agent-app --username --client-id --scopes "sfap_api chatbot_api api refresh_token api web" +sf org login web --client-app agent-app --username --client-id --scopes "sfap_api chatbot_api api refresh_token api web" ``` - IMPORTANT: You must use the "--client-id " flag of "org login web", where CONNECTED-APP-CONSUMER-KEY is the consumer key you previously copied. This step ensures that the "org login web" command uses your custom connected app, and not the default CLI connected app. +IMPORTANT: You must use the "--client-id " flag of "org login web", where CONNECTED-APP-CONSUMER-KEY is the consumer key you previously copied. This step ensures that the "org login web" command uses your custom connected app, and not the default CLI connected app. - Press Enter to skip sharing the client secret. +Press Enter to skip sharing the client secret. -5. When you run this command to interact with an agent, specify the org username and linked connected app with the --target-org and --app flags. +5. When you run this command to interact with an agent, specify the org username and linked connected app with the --target-org and --client-app flags. # flags.api-name.summary API name of the agent you want to interact with. -# flags.app.summary +# flags.client-app.summary -Name of the linked connected app or external client app to use for the connection. +Name of the linked client app to use for the agent connection. # flags.output-dir.summary @@ -59,8 +60,8 @@ Enable Apex debug logging during the agent preview conversation. - Interact with an agent with API name "Resort_Manager" in the org with alias "my-org" and the linked "agent-app" connected app. - <%= config.bin %> <%= command.id %> --api-name "Resort_Manager" --target-org my-org --app agent-app + <%= config.bin %> <%= command.id %> --api-name "Resort_Manager" --target-org my-org --client-app agent-app - Same as the preceding example, but this time save the conversation transcripts to the "./transcripts/my-preview" directory rather than the default "./temp/agent-preview": - <%= config.bin %> <%= command.id %> --api-name "Resort_Manager" --target-org my-org --app agent-app --output-dir "transcripts/my-preview" + <%= config.bin %> <%= command.id %> --api-name "Resort_Manager" --target-org my-org --client-app agent-app --output-dir "transcripts/my-preview" diff --git a/src/commands/agent/preview.ts b/src/commands/agent/preview.ts index c54bbc2a..c260d1c3 100644 --- a/src/commands/agent/preview.ts +++ b/src/commands/agent/preview.ts @@ -54,9 +54,11 @@ export default class AgentPreview extends SfCommand { public static readonly flags = { 'target-org': Flags.requiredOrg(), 'api-version': Flags.orgApiVersion(), - app: Flags.string({ - summary: messages.getMessage('flags.app.summary'), + 'client-app': Flags.string({ + char: 'c', + summary: messages.getMessage('flags.client-app.summary'), required: true, + dependsOn: ['target-org'], }), 'api-name': Flags.string({ summary: messages.getMessage('flags.api-name.summary'), @@ -84,7 +86,7 @@ export default class AgentPreview extends SfCommand { const jwtConn = await Connection.create({ authInfo, - app: flags.app, + clientApp: flags['client-app'], }); const agentsQuery = await conn.query( From 729c56023ba930a17ad51107a5fa7bb349f5edbb Mon Sep 17 00:00:00 2001 From: Juliet Shackell <63259011+jshackell-sfdc@users.noreply.github.com> Date: Thu, 12 Jun 2025 06:36:24 -0700 Subject: [PATCH 3/4] Update agent.preview.md (#159) --- messages/agent.preview.md | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/messages/agent.preview.md b/messages/agent.preview.md index 30211b4c..d23f5cf4 100644 --- a/messages/agent.preview.md +++ b/messages/agent.preview.md @@ -14,31 +14,27 @@ Find the agent's API name in its main details page in your org's Agent page in S Before you use this command, you must complete these steps: -1. Create a connected app in your org as described in the "Create a Connected App" section here: https://developer.salesforce.com/docs/einstein/genai/guide/agent-api-get-started.html#create-a-connected-app. Do these four additional steps: +1. Using your org's Setup UI, create a connected app in your org as described in the "Create a Connected App" section here: https://developer.salesforce.com/docs/einstein/genai/guide/agent-api-get-started.html#create-a-connected-app. Do these additional steps: a. When specifying the connected app's Callback URL, add this second callback URL on a new line: "http://localhost:1717/OauthRedirect". b. When adding the scopes to the connected app, add "Manage user data via Web browsers (web)". - c. Ensure that the "Require Secret for Web Server Flow" option is not selected. - - d. Make note of the user that you specified as the "Run As" user when updating the Client Credentials Flow section. - 2. Add the connected app to your agent as described in the "Add Connected App to Agent" section here: https://developer.salesforce.com/docs/einstein/genai/guide/agent-api-get-started.html#add-connected-app-to-agent. 3. Copy the consumer key from your connected app as described in the "Obtain Credentials" section here: https://developer.salesforce.com/docs/einstein/genai/guide/agent-api-get-started.html#obtain-credentials. -4. Link the connected app to your authenticated user with the required API scopes using the web server flow: +4. If you haven't already, run the "org login web" CLI command as usual to authorize the development org that contains the agent you want to preview. + +5. Re-run the "org web login" command to link the new connected app to your already-authenticated user. Use the --client-app flag to give the link a name; you can specify any string, but make a note of it because you'll need it later. Use --username to specify the username that you used to log into the org in the previous step. Use --client-id to specify the consumer key you previously copied. Finally, use --scopes as indicated to specify the required API scopes. Here's an example: -``` -sf org login web --client-app agent-app --username --client-id --scopes "sfap_api chatbot_api api refresh_token api web" -``` +sf org login web --client-app agent-app --username --client-id --scopes "sfap_api chatbot_api refresh_token api web" IMPORTANT: You must use the "--client-id " flag of "org login web", where CONNECTED-APP-CONSUMER-KEY is the consumer key you previously copied. This step ensures that the "org login web" command uses your custom connected app, and not the default CLI connected app. -Press Enter to skip sharing the client secret. +6. Press Enter to skip sharing the client secret, then log in with your org username as usual and click Accept. -5. When you run this command to interact with an agent, specify the org username and linked connected app with the --target-org and --client-app flags. +7. Run this command ("agent preview") to interact with an agent by using the --target-org flag to specify the org username or alias as usual and --client-app to specify the linked connected app ("agent-app" in the previous example). Use the "org display" command to get the list of client apps associated with an org. # flags.api-name.summary @@ -46,7 +42,7 @@ API name of the agent you want to interact with. # flags.client-app.summary -Name of the linked client app to use for the agent connection. +Name of the linked client app to use for the agent connection. You must have previously created this link with "org login web --client-app". Run "org display" to see the available linked client apps. # flags.output-dir.summary @@ -58,7 +54,7 @@ Enable Apex debug logging during the agent preview conversation. # examples -- Interact with an agent with API name "Resort_Manager" in the org with alias "my-org" and the linked "agent-app" connected app. +- Interact with an agent with API name "Resort_Manager" in the org with alias "my-org" and the linked "agent-app" connected app: <%= config.bin %> <%= command.id %> --api-name "Resort_Manager" --target-org my-org --client-app agent-app From aa917b5c696a5607461b67af061147dc03f31e85 Mon Sep 17 00:00:00 2001 From: Cristian Dominguez Date: Thu, 12 Jun 2025 13:17:39 -0300 Subject: [PATCH 4/4] chore: bump core --- package.json | 2 +- yarn.lock | 42 +++++++++++++++++++++--------------------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/package.json b/package.json index 432240ec..eae616c1 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "@oclif/core": "^4", "@oclif/multi-stage-output": "^0.7.12", "@salesforce/agents": "0.15.0", - "@salesforce/core": "^8.10.2", + "@salesforce/core": "^8.13.0", "@salesforce/kit": "^3.2.3", "@salesforce/sf-plugins-core": "^12.2.0", "@salesforce/source-deploy-retrieve": "^12.19.3", diff --git a/yarn.lock b/yarn.lock index 275c34da..8d3f25de 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1271,10 +1271,10 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" -"@jsforce/jsforce-node@^3.8.1": - version "3.8.1" - resolved "https://registry.yarnpkg.com/@jsforce/jsforce-node/-/jsforce-node-3.8.1.tgz#482fcf2820b48a6b10930d33550eb4e4cbd1e480" - integrity sha512-+IZZC7VfNjhkTyeAspBc4Z35Y5eAP0RIWQnyfKahsbY/aLjiFRIM9ejl1YbWbrbabf5ODFSUgBGmOiEYLW3f7Q== +"@jsforce/jsforce-node@^3.8.2": + version "3.8.2" + resolved "https://registry.yarnpkg.com/@jsforce/jsforce-node/-/jsforce-node-3.8.2.tgz#68b903f6733ae479086ab02ea4a2de87a7f208eb" + integrity sha512-ewaRr9JnZRW6I28C/TzUnv5p70zMrWsKCq2ovRW6X557/ikdfvA24F9k4cQXZnTG2lZLEfVn+WwdBGEtY7pPnQ== dependencies: "@sindresorhus/is" "^4" base64url "^3.0.1" @@ -1472,12 +1472,12 @@ strip-ansi "6.0.1" ts-retry-promise "^0.8.1" -"@salesforce/core@^8.10.0", "@salesforce/core@^8.10.2", "@salesforce/core@^8.10.3", "@salesforce/core@^8.5.1", "@salesforce/core@^8.8.0", "@salesforce/core@^8.8.5": - version "8.10.3" - resolved "https://registry.yarnpkg.com/@salesforce/core/-/core-8.10.3.tgz#3cc2c99d097757cb4b08dab921254cfa3a00c7c1" - integrity sha512-juqbU304TBrrjb8sZGw+QkeAJISKu4+v2XIMTCxGJoEjs4LLhsyI7/drxCUY+7FNye+veAGeJdn/PCxkKhSgcA== +"@salesforce/core@^8.10.0", "@salesforce/core@^8.10.3", "@salesforce/core@^8.13.0", "@salesforce/core@^8.5.1", "@salesforce/core@^8.8.0", "@salesforce/core@^8.8.5": + version "8.13.0" + resolved "https://registry.yarnpkg.com/@salesforce/core/-/core-8.13.0.tgz#ffc00a776c60a401d4385abfeb6cd7fee0d90f3b" + integrity sha512-FyAn0UGa93D0N++8poeJt7yEaWQH++qxrv/Wf4TjNaUCLoh19g57lrXuos3qDJPr8Ut4x6QjVxEc49XLy+vBkw== dependencies: - "@jsforce/jsforce-node" "^3.8.1" + "@jsforce/jsforce-node" "^3.8.2" "@salesforce/kit" "^3.2.2" "@salesforce/schemas" "^1.9.0" "@salesforce/ts-types" "^2.0.10" @@ -1489,9 +1489,9 @@ js2xmlparser "^4.0.1" jsonwebtoken "9.0.2" jszip "3.10.1" - pino "^9.4.0" + pino "^9.7.0" pino-abstract-transport "^1.2.0" - pino-pretty "^11.2.2" + pino-pretty "^11.3.0" proper-lockfile "^4.1.2" semver "^7.6.3" ts-retry-promise "^0.8.1" @@ -6985,7 +6985,7 @@ pino-abstract-transport@^2.0.0: dependencies: split2 "^4.0.0" -pino-pretty@^11.2.2: +pino-pretty@^11.3.0: version "11.3.0" resolved "https://registry.yarnpkg.com/pino-pretty/-/pino-pretty-11.3.0.tgz#390b3be044cf3d2e9192c7d19d44f6b690468f2e" integrity sha512-oXwn7ICywaZPHmu3epHGU2oJX4nPmKvHvB/bwrJHlGcbEWaVcotkpyVHMKLKmiVryWYByNp0jpgAcXpFJDXJzA== @@ -7010,17 +7010,17 @@ pino-std-serializers@^7.0.0: resolved "https://registry.yarnpkg.com/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz#7c625038b13718dbbd84ab446bd673dc52259e3b" integrity sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA== -pino@^9.4.0: - version "9.6.0" - resolved "https://registry.yarnpkg.com/pino/-/pino-9.6.0.tgz#6bc628159ba0cc81806d286718903b7fc6b13169" - integrity sha512-i85pKRCt4qMjZ1+L7sy2Ag4t1atFcdbEt76+7iRJn1g2BvsnRMGu9p8pivl9fs63M2kF/A0OacFZhTub+m/qMg== +pino@^9.7.0: + version "9.7.0" + resolved "https://registry.yarnpkg.com/pino/-/pino-9.7.0.tgz#ff7cd86eb3103ee620204dbd5ca6ffda8b53f645" + integrity sha512-vnMCM6xZTb1WDmLvtG2lE/2p+t9hDEIvTWJsu6FejkE62vB7gDhvzrpFR4Cw2to+9JNQxVnkAKVPA1KPB98vWg== dependencies: atomic-sleep "^1.0.0" fast-redact "^3.1.1" on-exit-leak-free "^2.1.0" pino-abstract-transport "^2.0.0" pino-std-serializers "^7.0.0" - process-warning "^4.0.0" + process-warning "^5.0.0" quick-format-unescaped "^4.0.3" real-require "^0.2.0" safe-stable-stringify "^2.3.1" @@ -7079,10 +7079,10 @@ process-on-spawn@^1.0.0: dependencies: fromentries "^1.2.0" -process-warning@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/process-warning/-/process-warning-4.0.1.tgz#5c1db66007c67c756e4e09eb170cdece15da32fb" - integrity sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q== +process-warning@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/process-warning/-/process-warning-5.0.0.tgz#566e0bf79d1dff30a72d8bbbe9e8ecefe8d378d7" + integrity sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA== process@^0.11.10: version "0.11.10"