From 3ab93b4c76a4ab8ad79906b6703510daa9aabcdb Mon Sep 17 00:00:00 2001 From: Willie Ruemmele Date: Fri, 7 Nov 2025 13:46:45 -0700 Subject: [PATCH 1/6] fix: first implementation working' --- src/client/metadataApiDeploy.ts | 107 ++++++++++++++++++++++++++++++-- 1 file changed, 103 insertions(+), 4 deletions(-) diff --git a/src/client/metadataApiDeploy.ts b/src/client/metadataApiDeploy.ts index 1e2bbb2bf..a22fa16b9 100644 --- a/src/client/metadataApiDeploy.ts +++ b/src/client/metadataApiDeploy.ts @@ -15,6 +15,7 @@ */ import { join, relative, resolve as pathResolve, sep } from 'node:path'; import { format } from 'node:util'; +import { EOL } from 'node:os'; import { isString } from '@salesforce/ts-types'; import JSZip from 'jszip'; import fs from 'graceful-fs'; @@ -23,10 +24,10 @@ import { Messages } from '@salesforce/core/messages'; import { SfError } from '@salesforce/core/sfError'; import { envVars } from '@salesforce/core/envVars'; import { ensureArray } from '@salesforce/kit'; -import { RegistryAccess } from '../registry/registryAccess'; +import { RegistryAccess } from '../registry'; import { ReplacementEvent } from '../convert/types'; -import { MetadataConverter } from '../convert/metadataConverter'; -import { ComponentSet } from '../collections/componentSet'; +import { MetadataConverter } from '../convert'; +import { ComponentSet } from '../collections'; import { MetadataTransfer, MetadataTransferOptions } from './metadataTransfer'; import { AsyncResult, @@ -203,7 +204,105 @@ export class MetadataApiDeploy extends MetadataTransfer< // this is used as the version in the manifest (package.xml). this.components.sourceApiVersion ??= apiVersion; } - + if (this.options.components) { + // we must ensure AiAuthoringBundles compile before deployment + + await Promise.all( + this.options.components + .getSourceComponents() + .filter((element) => element.type.id === 'aiauthoringbundle') + .toArray() + .map(async (aab) => { + // aab.content points to a directory, we need to find the .agent file and read it + if (!aab.content) { + throw new SfError( + messages.getMessage('error_expected_source_files', [aab.fullName, 'aiauthoringbundle']), + 'ExpectedSourceFilesError' + ); + } + + const contentPath = aab.tree.find('content', aab.name, aab.content); + + if (!contentPath) { + // if this didn't exist, they'll have deploy issues anyways, but we can check here for type reasons + throw new SfError(`No .agent file found in directory: ${aab.content}`, 'MissingAgentFileError'); + } + + const agentContent = await fs.promises.readFile(contentPath, 'utf-8'); + + // we need to use a namedJWT connection for this request + const { accessToken, instanceUrl } = connection.getConnectionOptions(); + if (!instanceUrl) { + throw SfError.create({ + name: 'ApiAccessError', + message: 'Missing Instance URL for org connection', + }); + } + if (!accessToken) { + throw SfError.create({ + name: 'ApiAccessError', + message: 'Missing Access Token for org connection', + }); + } + const url = `${instanceUrl}/agentforce/bootstrap/nameduser`; + // For the namdeduser endpoint request to work we need to delete the access token + delete connection.accessToken; + const response = await connection.request<{ + access_token: string; + }>( + { + method: 'GET', + url, + headers: { + 'Content-Type': 'application/json', + Cookie: `sid=${accessToken}`, + }, + }, + { retry: { maxRetries: 3 } } + ); + connection.accessToken = response.access_token; + + // to avoid circular dependencies between libraries, just call the compile endpoint here + const result = await connection.request<{ + // minimal typings here, more is returned, just using what we need + status: 'failure' | 'success'; + errors: Array<{ + description: string; + lineStart: number; + colStart: number; + }>; + }>({ + method: 'POST', + url: 'https://test.api.salesforce.com/einstein/ai-agent/v1.1/authoring/scripts', + headers: { + 'x-client-name': 'afdx', + 'content-type': 'application/json', + }, + body: JSON.stringify({ + assets: [ + { + type: 'AFScript', + name: 'AFScript', + content: agentContent, + }, + ], + afScriptVersion: '1.0.1', + }), + }); + + if (result.status === 'failure') { + throw SfError.create({ + message: `${EOL}${ + result.errors + .map((e) => `${aab.name}.agent: ${e.description} ${e.lineStart}:${e.colStart}`) + .join(EOL) ?? '' + }`, + name: 'AgentCompilationError', + }); + } + }) + ); + } // only do event hooks if source, (NOT a metadata format) deploy if (this.options.components) { await LifecycleInstance.emit('scopedPreDeploy', { From b624de23e51a278a2832a2c730b4e6d30576662e Mon Sep 17 00:00:00 2001 From: Willie Ruemmele Date: Fri, 7 Nov 2025 14:24:40 -0700 Subject: [PATCH 2/6] chore: more performant approach --- src/client/metadataApiDeploy.ts | 14 +++++++------- src/collections/componentSet.ts | 18 ++++++++++++++++++ 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/src/client/metadataApiDeploy.ts b/src/client/metadataApiDeploy.ts index a22fa16b9..e3d2fe6be 100644 --- a/src/client/metadataApiDeploy.ts +++ b/src/client/metadataApiDeploy.ts @@ -206,13 +206,12 @@ export class MetadataApiDeploy extends MetadataTransfer< } if (this.options.components) { // we must ensure AiAuthoringBundles compile before deployment + // Use optimized getter method instead of filtering all components + const aabComponents = this.options.components.getAiAuthoringBundles().toArray(); - await Promise.all( - this.options.components - .getSourceComponents() - .filter((element) => element.type.id === 'aiauthoringbundle') - .toArray() - .map(async (aab) => { + if (aabComponents.length > 0) { + await Promise.all( + aabComponents.map(async (aab) => { // aab.content points to a directory, we need to find the .agent file and read it if (!aab.content) { throw new SfError( @@ -301,7 +300,8 @@ export class MetadataApiDeploy extends MetadataTransfer< }); } }) - ); + ); + } } // only do event hooks if source, (NOT a metadata format) deploy if (this.options.components) { diff --git a/src/collections/componentSet.ts b/src/collections/componentSet.ts index abb022c31..d3bb96b14 100644 --- a/src/collections/componentSet.ts +++ b/src/collections/componentSet.ts @@ -106,6 +106,9 @@ export class ComponentSet extends LazyCollection { // used to store components meant for a "constructive" (not destructive) manifest private manifestComponents = new DecodeableMap>(); + // optimization: track AiAuthoringBundles separately for faster access during compilation check + private aiAuthoringBundles = new Set(); + private destructiveChangesType = DestructiveChangesType.POST; public constructor(components: Iterable = [], registry = new RegistryAccess()) { @@ -527,6 +530,16 @@ export class ComponentSet extends LazyCollection { return new LazyCollection(iter).filter((c) => c instanceof SourceComponent) as LazyCollection; } + /** + * Get all AiAuthoringBundle components in the set. + * This is an optimized method that uses a cached Set of AAB components. + * + * @returns Collection of AiAuthoringBundle source components + */ + public getAiAuthoringBundles(): LazyCollection { + return new LazyCollection(this.aiAuthoringBundles); + } + public add(component: ComponentLike, deletionType?: DestructiveChangesType): void { const key = simpleKey(component); if (!this.components.has(key)) { @@ -556,6 +569,11 @@ export class ComponentSet extends LazyCollection { // we're working with SourceComponents now this.components.get(key)?.set(srcKey, component); + // track AiAuthoringBundles separately for fast access + if (component.type.id === 'aiauthoringbundle') { + this.aiAuthoringBundles.add(component); + } + // Build maps of destructive components and regular components as they are added // as an optimization when building manifests. if (deletionType) { From 7ed503f5f632750503dfb75e5c50a2db8113bd82 Mon Sep 17 00:00:00 2001 From: Willie Ruemmele Date: Fri, 7 Nov 2025 15:11:55 -0700 Subject: [PATCH 3/6] test: add UTs --- src/client/metadataApiDeploy.ts | 1 + test/client/metadataApiDeploy.test.ts | 232 +++++++++++++++++++++++++- 2 files changed, 229 insertions(+), 4 deletions(-) diff --git a/src/client/metadataApiDeploy.ts b/src/client/metadataApiDeploy.ts index e3d2fe6be..36fc51654 100644 --- a/src/client/metadataApiDeploy.ts +++ b/src/client/metadataApiDeploy.ts @@ -272,6 +272,7 @@ export class MetadataApiDeploy extends MetadataTransfer< }>; }>({ method: 'POST', + // this will need to be api.salesforce once changes are in prod url: 'https://test.api.salesforce.com/einstein/ai-agent/v1.1/authoring/scripts', headers: { 'x-client-name': 'afdx', diff --git a/test/client/metadataApiDeploy.test.ts b/test/client/metadataApiDeploy.test.ts index c8da492d7..2eee48ecd 100644 --- a/test/client/metadataApiDeploy.test.ts +++ b/test/client/metadataApiDeploy.test.ts @@ -17,10 +17,11 @@ import { basename, join, sep } from 'node:path'; import { MockTestOrgData, TestContext } from '@salesforce/core/testSetup'; import chai, { assert, expect } from 'chai'; import { AnyJson, ensureString, getString } from '@salesforce/ts-types'; -import { envVars, Lifecycle, Messages, PollingClient, StatusResult } from '@salesforce/core'; +import { Connection, envVars, Lifecycle, Messages, PollingClient, StatusResult } from '@salesforce/core'; import { Duration } from '@salesforce/kit'; import deepEqualInAnyOrder from 'deep-equal-in-any-order'; import * as sinon from 'sinon'; +import fs from 'graceful-fs'; import { ComponentSet, ComponentStatus, @@ -70,15 +71,14 @@ describe('MetadataApiDeploy', () => { describe('Lifecycle', () => { describe('start', () => { - it('should not convert zip, but read from fs'); - it('should not mdapiDir, but generate zip buffer from it'); - it('should convert to metadata format and create zip', async () => { const components = new ComponentSet([matchingContentFile.COMPONENT]); const { operation, convertStub } = await stubMetadataDeploy($$, testOrg, { components, }); + expect(components.getAiAuthoringBundles().toArray()).to.be.empty; + await operation.start(); expect(convertStub.calledWith(components, 'metadata', { type: 'zip' })).to.be.true; @@ -1297,4 +1297,228 @@ describe('MetadataApiDeploy', () => { expect(mdOpts.apiOptions).to.have.property('singlePackage', true); }); }); + + describe('AiAuthoringBundle compilation', () => { + const aabType = registry.types.aiauthoringbundle; + const aabName = 'TestAAB'; + const aabContentDir = join('path', 'to', 'aiAuthoringBundles', aabName); + const agentFileName = `${aabName}.agent`; + const agentContent = 'test agent script content'; + + const createAABComponent = (): SourceComponent => + SourceComponent.createVirtualComponent( + { + name: aabName, + type: aabType, + xml: join(aabContentDir, `${aabName}${META_XML_SUFFIX}`), + content: aabContentDir, + }, + [ + { + dirPath: join('path', 'to', 'aiAuthoringBundles'), + children: [aabName], + }, + { + dirPath: aabContentDir, + children: [agentFileName], + }, + ] + ); + + it('should throw error with correct data when compilation fails', async () => { + const aabComponent = createAABComponent(); + const components = new ComponentSet([aabComponent]); + + // Stub retrieveMaxApiVersion on prototype before getting connection + $$.SANDBOX.stub(Connection.prototype, 'retrieveMaxApiVersion').resolves('60.0'); + const connection = await testOrg.getConnection(); + + const readFileStub = $$.SANDBOX.stub(fs.promises, 'readFile').resolves(agentContent); + + $$.SANDBOX.stub(connection, 'getConnectionOptions').returns({ + accessToken: 'test-access-token', + instanceUrl: 'https://test.salesforce.com', + }); + + const compileErrors = [ + { description: 'Syntax error on line 5', lineStart: 5, colStart: 10 }, + { description: 'Missing token', lineStart: 8, colStart: 15 }, + ]; + + // Configure connection.request stub (already created by TestContext) + let callCount = 0; + (connection.request as sinon.SinonStub).callsFake((request: { url?: string }) => { + callCount++; + if (request.url?.includes('agentforce/bootstrap/nameduser')) { + return Promise.resolve({ access_token: 'named-user-token' }); + } + if (request.url?.includes('einstein/ai-agent')) { + return Promise.resolve({ status: 'failure' as const, errors: compileErrors }); + } + // For other requests, return empty object (deploy stub handles its own requests) + return Promise.resolve({}); + }); + + const { operation } = await stubMetadataDeploy($$, testOrg, { components }); + + try { + await operation.start(); + expect.fail('Should have thrown AgentCompilationError'); + } catch (error: unknown) { + const err = error as { name?: string; message?: string }; + expect(err).to.have.property('name', 'AgentCompilationError'); + expect(err.message).to.include(`${aabName}.agent: Syntax error on line 5 5:10`); + expect(err.message).to.include(`${aabName}.agent: Missing token 8:15`); + } + + expect(readFileStub.calledOnce).to.be.true; + expect(callCount).to.be.at.least(2); + }); + + it('should not throw error when compilation succeeds', async () => { + const aabComponent = createAABComponent(); + const components = new ComponentSet([aabComponent]); + + // Stub retrieveMaxApiVersion on prototype before getting connection + $$.SANDBOX.stub(Connection.prototype, 'retrieveMaxApiVersion').resolves('60.0'); + const connection = await testOrg.getConnection(); + + const readFileStub = $$.SANDBOX.stub(fs.promises, 'readFile').resolves(agentContent); + + $$.SANDBOX.stub(connection, 'getConnectionOptions').returns({ + accessToken: 'test-access-token', + instanceUrl: 'https://test.salesforce.com', + }); + + // Configure connection.request stub (already created by TestContext) + let callCount = 0; + (connection.request as sinon.SinonStub).callsFake((request: { url?: string }) => { + callCount++; + if (request.url?.includes('agentforce/bootstrap/nameduser')) { + return Promise.resolve({ access_token: 'named-user-token' }); + } + if (request.url?.includes('einstein/ai-agent')) { + return Promise.resolve({ status: 'success' as const, errors: [] }); + } + // For other requests, return empty object (deploy stub handles its own requests) + return Promise.resolve({}); + }); + + const { operation } = await stubMetadataDeploy($$, testOrg, { components }); + + // Should not throw + await operation.start(); + + expect(readFileStub.calledOnce).to.be.true; + expect(callCount).to.be.at.least(2); + }); + + it('should not compile when no AABs present in component set', async () => { + const components = new ComponentSet([COMPONENT]); + + // Stub retrieveMaxApiVersion on prototype before getting connection + $$.SANDBOX.stub(Connection.prototype, 'retrieveMaxApiVersion').resolves('60.0'); + const connection = await testOrg.getConnection(); + + const readFileStub = $$.SANDBOX.stub(fs.promises, 'readFile'); + // Track calls to connection.request to verify compilation wasn't attempted + const compileCallCount = { count: 0 }; + (connection.request as sinon.SinonStub).callsFake((request: { url?: string }) => { + const url = request.url ?? ''; + if (url.includes('einstein/ai-agent') || url.includes('agentforce/bootstrap')) { + compileCallCount.count++; + } + // For other requests, return empty object (deploy stub handles its own requests) + return Promise.resolve({}); + }); + + const { operation } = await stubMetadataDeploy($$, testOrg, { components }); + + await operation.start(); + + // Verify compilation endpoints were not called + expect(readFileStub.called).to.be.false; + expect(compileCallCount.count).to.equal(0); + }); + + it('should handle multiple AABs in parallel', async () => { + const aab1 = SourceComponent.createVirtualComponent( + { + name: 'AAB1', + type: aabType, + xml: join('path', 'to', 'aiAuthoringBundles', 'AAB1', `AAB1${META_XML_SUFFIX}`), + content: join('path', 'to', 'aiAuthoringBundles', 'AAB1'), + }, + [ + { + dirPath: join('path', 'to', 'aiAuthoringBundles'), + children: ['AAB1'], + }, + { + dirPath: join('path', 'to', 'aiAuthoringBundles', 'AAB1'), + children: ['AAB1.agent'], + }, + ] + ); + + const aab2 = SourceComponent.createVirtualComponent( + { + name: 'AAB2', + type: aabType, + xml: join('path', 'to', 'aiAuthoringBundles', 'AAB2', `AAB2${META_XML_SUFFIX}`), + content: join('path', 'to', 'aiAuthoringBundles', 'AAB2'), + }, + [ + { + dirPath: join('path', 'to', 'aiAuthoringBundles'), + children: ['AAB2'], + }, + { + dirPath: join('path', 'to', 'aiAuthoringBundles', 'AAB2'), + children: ['AAB2.agent'], + }, + ] + ); + + const components = new ComponentSet([aab1, aab2]); + + // Stub retrieveMaxApiVersion on prototype before getting connection + $$.SANDBOX.stub(Connection.prototype, 'retrieveMaxApiVersion').resolves('60.0'); + const connection = await testOrg.getConnection(); + + const readFileStub = $$.SANDBOX.stub(fs.promises, 'readFile').resolves(agentContent); + + $$.SANDBOX.stub(connection, 'getConnectionOptions').returns({ + accessToken: 'test-access-token', + instanceUrl: 'https://test.salesforce.com', + }); + + // Configure connection.request stub (already created by TestContext) + // Handle multiple AABs: 2 nameduser + 2 compile calls + let namedUserCallCount = 0; + let compileCallCount = 0; + (connection.request as sinon.SinonStub).callsFake((request: { url?: string }) => { + if (request.url?.includes('agentforce/bootstrap/nameduser')) { + namedUserCallCount++; + return Promise.resolve({ access_token: 'named-user-token' }); + } + if (request.url?.includes('einstein/ai-agent')) { + compileCallCount++; + return Promise.resolve({ status: 'success' as const, errors: [] }); + } + // For other requests, return empty object (deploy stub handles its own requests) + return Promise.resolve({}); + }); + + const { operation } = await stubMetadataDeploy($$, testOrg, { components }); + + await operation.start(); + + // Should read both agent files + expect(readFileStub.callCount).to.equal(2); + // Should call compile endpoint twice (once per AAB) and nameduser twice + expect(namedUserCallCount).to.equal(2); + expect(compileCallCount).to.equal(2); + }); + }); }); From 4a535f2fcb696c4fe6989e2f624f56b0c40ba97a Mon Sep 17 00:00:00 2001 From: Willie Ruemmele Date: Fri, 7 Nov 2025 15:54:13 -0700 Subject: [PATCH 4/6] test: cleanups --- test/client/metadataApiDeploy.test.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/test/client/metadataApiDeploy.test.ts b/test/client/metadataApiDeploy.test.ts index 2eee48ecd..8f949ccfe 100644 --- a/test/client/metadataApiDeploy.test.ts +++ b/test/client/metadataApiDeploy.test.ts @@ -1324,13 +1324,11 @@ describe('MetadataApiDeploy', () => { }, ] ); + const aabComponent = createAABComponent(); + const components = new ComponentSet([aabComponent]); it('should throw error with correct data when compilation fails', async () => { - const aabComponent = createAABComponent(); - const components = new ComponentSet([aabComponent]); - - // Stub retrieveMaxApiVersion on prototype before getting connection - $$.SANDBOX.stub(Connection.prototype, 'retrieveMaxApiVersion').resolves('60.0'); + $$.SANDBOX.stub(Connection.prototype, 'retrieveMaxApiVersion').resolves('65.0'); const connection = await testOrg.getConnection(); const readFileStub = $$.SANDBOX.stub(fs.promises, 'readFile').resolves(agentContent); From 1c36da73f83b58a1d7b79691d250aca3982943fd Mon Sep 17 00:00:00 2001 From: Willie Ruemmele Date: Mon, 10 Nov 2025 10:39:58 -0700 Subject: [PATCH 5/6] chore: only auth once, show errors for everything in deploy --- src/client/metadataApiDeploy.ts | 93 ++++++++++++++------------- test/client/metadataApiDeploy.test.ts | 4 +- 2 files changed, 51 insertions(+), 46 deletions(-) diff --git a/src/client/metadataApiDeploy.ts b/src/client/metadataApiDeploy.ts index 36fc51654..2b019d67f 100644 --- a/src/client/metadataApiDeploy.ts +++ b/src/client/metadataApiDeploy.ts @@ -210,7 +210,38 @@ export class MetadataApiDeploy extends MetadataTransfer< const aabComponents = this.options.components.getAiAuthoringBundles().toArray(); if (aabComponents.length > 0) { - await Promise.all( + // we need to use a namedJWT connection for this request + const { accessToken, instanceUrl } = connection.getConnectionOptions(); + if (!instanceUrl) { + throw SfError.create({ + name: 'ApiAccessError', + message: 'Missing Instance URL for org connection', + }); + } + if (!accessToken) { + throw SfError.create({ + name: 'ApiAccessError', + message: 'Missing Access Token for org connection', + }); + } + const url = `${instanceUrl}/agentforce/bootstrap/nameduser`; + // For the namdeduser endpoint request to work we need to delete the access token + delete connection.accessToken; + const response = await connection.request<{ + access_token: string; + }>( + { + method: 'GET', + url, + headers: { + 'Content-Type': 'application/json', + Cookie: `sid=${accessToken}`, + }, + }, + { retry: { maxRetries: 3 } } + ); + connection.accessToken = response.access_token; + const results = await Promise.all( aabComponents.map(async (aab) => { // aab.content points to a directory, we need to find the .agent file and read it if (!aab.content) { @@ -229,38 +260,6 @@ export class MetadataApiDeploy extends MetadataTransfer< const agentContent = await fs.promises.readFile(contentPath, 'utf-8'); - // we need to use a namedJWT connection for this request - const { accessToken, instanceUrl } = connection.getConnectionOptions(); - if (!instanceUrl) { - throw SfError.create({ - name: 'ApiAccessError', - message: 'Missing Instance URL for org connection', - }); - } - if (!accessToken) { - throw SfError.create({ - name: 'ApiAccessError', - message: 'Missing Access Token for org connection', - }); - } - const url = `${instanceUrl}/agentforce/bootstrap/nameduser`; - // For the namdeduser endpoint request to work we need to delete the access token - delete connection.accessToken; - const response = await connection.request<{ - access_token: string; - }>( - { - method: 'GET', - url, - headers: { - 'Content-Type': 'application/json', - Cookie: `sid=${accessToken}`, - }, - }, - { retry: { maxRetries: 3 } } - ); - connection.accessToken = response.access_token; - // to avoid circular dependencies between libraries, just call the compile endpoint here const result = await connection.request<{ // minimal typings here, more is returned, just using what we need @@ -270,6 +269,8 @@ export class MetadataApiDeploy extends MetadataTransfer< lineStart: number; colStart: number; }>; + // name added here for post-processing convenience + name: string; }>({ method: 'POST', // this will need to be api.salesforce once changes are in prod @@ -289,19 +290,23 @@ export class MetadataApiDeploy extends MetadataTransfer< afScriptVersion: '1.0.1', }), }); - - if (result.status === 'failure') { - throw SfError.create({ - message: `${EOL}${ - result.errors - .map((e) => `${aab.name}.agent: ${e.description} ${e.lineStart}:${e.colStart}`) - .join(EOL) ?? '' - }`, - name: 'AgentCompilationError', - }); - } + result.name = aab.name; + return result; }) ); + + const errors = results + .filter((result) => result.status === 'failure') + .map((result) => + result.errors.map((r) => `${result.name}.agent: ${r.description} ${r.lineStart}:${r.colStart}`).join(EOL) + ); + + if (errors.length > 0) { + throw SfError.create({ + message: `${EOL}${errors.join(EOL)}`, + name: 'AgentCompilationError', + }); + } } } // only do event hooks if source, (NOT a metadata format) deploy diff --git a/test/client/metadataApiDeploy.test.ts b/test/client/metadataApiDeploy.test.ts index 8f949ccfe..dd05dbfd8 100644 --- a/test/client/metadataApiDeploy.test.ts +++ b/test/client/metadataApiDeploy.test.ts @@ -1514,8 +1514,8 @@ describe('MetadataApiDeploy', () => { // Should read both agent files expect(readFileStub.callCount).to.equal(2); - // Should call compile endpoint twice (once per AAB) and nameduser twice - expect(namedUserCallCount).to.equal(2); + // Should call compile endpoint twice (once per AAB) and nameduser once + expect(namedUserCallCount).to.equal(1); expect(compileCallCount).to.equal(2); }); }); From 1b5fd2c1cfb7ee94befcb0c032b4ec6596c0ee73 Mon Sep 17 00:00:00 2001 From: Willie Ruemmele Date: Mon, 17 Nov 2025 08:42:08 -0700 Subject: [PATCH 6/6] chore: reset acess token --- src/client/metadataApiDeploy.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/client/metadataApiDeploy.ts b/src/client/metadataApiDeploy.ts index ced0e2151..d3eeec372 100644 --- a/src/client/metadataApiDeploy.ts +++ b/src/client/metadataApiDeploy.ts @@ -307,6 +307,11 @@ export class MetadataApiDeploy extends MetadataTransfer< message: `${EOL}${errors.join(EOL)}`, name: 'AgentCompilationError', }); + } else { + // everything successfully compiled + // stop using named user jwt access token + delete connection.accessToken; + await connection.refreshAuth(); } } }