diff --git a/src/Apps/W1/ExpenseAgent/app/src/Setup/Codeunits/EAAgentArchiving.Codeunit.al b/src/Apps/W1/ExpenseAgent/app/src/Setup/Codeunits/EAAgentArchiving.Codeunit.al new file mode 100644 index 00000000000..25d818bf06e --- /dev/null +++ b/src/Apps/W1/ExpenseAgent/app/src/Setup/Codeunits/EAAgentArchiving.Codeunit.al @@ -0,0 +1,20 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +namespace Microsoft.ExpenseAgent; + +using System.Agents; + +codeunit 7104 "EA Agent Archiving" implements IAgentArchiving +{ + Access = Internal; + InherentEntitlements = X; + InherentPermissions = X; + + procedure IsArchivingSupported(): Boolean + begin + exit(false); + end; +} diff --git a/src/Apps/W1/ExpenseAgent/app/src/Setup/EnumExtensions/EAAgentMetadata.EnumExt.al b/src/Apps/W1/ExpenseAgent/app/src/Setup/EnumExtensions/EAAgentMetadata.EnumExt.al index cdb7dfefee6..061d575a942 100644 --- a/src/Apps/W1/ExpenseAgent/app/src/Setup/EnumExtensions/EAAgentMetadata.EnumExt.al +++ b/src/Apps/W1/ExpenseAgent/app/src/Setup/EnumExtensions/EAAgentMetadata.EnumExt.al @@ -13,6 +13,6 @@ enumextension 6998 "EA Agent Metadata" extends "Agent Metadata Provider" value(6998; "Expense Agent") { Caption = 'Expense Agent', Locked = true; - Implementation = IAgentFactory = "EA Metadata Provider", IAgentMetadata = "EA Metadata Provider", IAgentTaskExecution = "EA Agent Task Execution"; + Implementation = IAgentFactory = "EA Metadata Provider", IAgentMetadata = "EA Metadata Provider", IAgentTaskExecution = "EA Agent Task Execution", IAgentArchiving = "EA Agent Archiving"; } } diff --git a/src/Apps/W1/PayablesAgent/app/Integration/PAAgentArchiving.Codeunit.al b/src/Apps/W1/PayablesAgent/app/Integration/PAAgentArchiving.Codeunit.al new file mode 100644 index 00000000000..36fe3bef3d5 --- /dev/null +++ b/src/Apps/W1/PayablesAgent/app/Integration/PAAgentArchiving.Codeunit.al @@ -0,0 +1,20 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +namespace Microsoft.Agent.PayablesAgent; + +using System.Agents; + +codeunit 3319 "PA Agent Archiving" implements IAgentArchiving +{ + Access = Internal; + InherentEntitlements = X; + InherentPermissions = X; + + procedure IsArchivingSupported(): Boolean + begin + exit(false); + end; +} diff --git a/src/Apps/W1/PayablesAgent/app/PAAgentMetadata.EnumExt.al b/src/Apps/W1/PayablesAgent/app/PAAgentMetadata.EnumExt.al index 3a6b1496e0c..0ea2395884e 100644 --- a/src/Apps/W1/PayablesAgent/app/PAAgentMetadata.EnumExt.al +++ b/src/Apps/W1/PayablesAgent/app/PAAgentMetadata.EnumExt.al @@ -13,6 +13,6 @@ enumextension 3304 "PA Agent Metadata" extends "Agent Metadata Provider" value(3303; "Payables Agent") { Caption = 'Payables Agent', Locked = true; - Implementation = IAgentFactory = "Payables Agent", IAgentMetadata = "Payables Agent", IAgentTaskExecution = "PA Agent Task Execution"; + Implementation = IAgentFactory = "Payables Agent", IAgentMetadata = "Payables Agent", IAgentTaskExecution = "PA Agent Task Execution", IAgentArchiving = "PA Agent Archiving"; } } diff --git a/src/System Application/App/Agent/Setup/Agent.Codeunit.al b/src/System Application/App/Agent/Setup/Agent.Codeunit.al index 4f882e0b9e9..b802c4d826b 100644 --- a/src/System Application/App/Agent/Setup/Agent.Codeunit.al +++ b/src/System Application/App/Agent/Setup/Agent.Codeunit.al @@ -61,6 +61,7 @@ codeunit 4321 Agent /// Archives the agent. Archiving removes the agent from active use and cannot be undone. The agent must be inactive (deactivated) before it can be archived. /// /// The user security ID of the agent. + /// An error is raised if the agent's type does not support archiving. Call IsArchivingSupported to check before archiving. procedure Archive(AgentUserSecurityID: Guid) var AgentImpl: Codeunit "Agent Impl."; @@ -69,6 +70,19 @@ codeunit 4321 Agent AgentImpl.Archive(AgentUserSecurityID); end; + /// + /// Checks if the agent type supports archiving. + /// + /// The user security ID of the agent. + /// True if the agent type supports archiving; otherwise false. + procedure IsArchivingSupported(AgentUserSecurityID: Guid): Boolean + var + AgentImpl: Codeunit "Agent Impl."; + begin + FeatureAccessManagement.AgentManagementAllowed(true); + exit(AgentImpl.IsArchivingSupported(AgentUserSecurityID)); + end; + /// /// Checks if the agent is archived. /// diff --git a/src/System Application/App/Agent/Setup/AgentCard.Page.al b/src/System Application/App/Agent/Setup/AgentCard.Page.al index c4d8987ec25..ec0257e8a96 100644 --- a/src/System Application/App/Agent/Setup/AgentCard.Page.al +++ b/src/System Application/App/Agent/Setup/AgentCard.Page.al @@ -151,6 +151,9 @@ page 4315 "Agent Card" Agent: Codeunit Agent; ArchiveConfirmation: Page "Agent Archive Confirmation"; begin + if not Agent.IsArchivingSupported(Rec."User Security ID") then + Error(ArchivingNotSupportedErr, Rec."Agent Metadata Provider"); + if Rec.State <> Rec.State::Disabled then Error(DeactivateBeforeArchivingErr); @@ -324,6 +327,7 @@ page 4315 "Agent Card" YouCannotEnableAgentWithoutUsingConfigurationPageErr: Label 'You can''t activate the agent from this page. Use the action to configure and activate the agent.'; YouDoNotHavePermissionToModifyThisAgentErr: Label 'You do not have permission to modify this agent. Contact your system administrator to update your permissions or to mark you as one of the administrators for the agent.'; DeactivateBeforeArchivingErr: Label 'Deactivate the agent before archiving it.'; + ArchivingNotSupportedErr: Label 'Archiving agents of type ''%1'' is not supported.', Comment = '%1 = the type of the agent.'; AgentArchivedMsg: Label 'The agent has been archived.'; AgentArchivedNotificationMsg: Label 'This agent is archived and can no longer be modified. Its tasks and logs remain available for auditing.'; -} \ No newline at end of file +} diff --git a/src/System Application/App/Agent/Setup/AgentImpl.Codeunit.al b/src/System Application/App/Agent/Setup/AgentImpl.Codeunit.al index 8b14bb3c40a..3c60ce14129 100644 --- a/src/System Application/App/Agent/Setup/AgentImpl.Codeunit.al +++ b/src/System Application/App/Agent/Setup/AgentImpl.Codeunit.al @@ -67,6 +67,9 @@ codeunit 4301 "Agent Impl." if Agent.Substate = Agent.Substate::Archived then exit; // Archiving is terminal; idempotent no-op avoids the platform "archived agent cannot be modified" error on re-archive. + if not IsArchivingSupported(Agent) then + Error(ArchivingNotSupportedErr, Agent."Agent Metadata Provider"); + if Agent.State <> Agent.State::Disabled then Error(DeactivateBeforeArchivingErr); @@ -74,6 +77,27 @@ codeunit 4301 "Agent Impl." Agent.Modify(true); end; + procedure IsArchivingSupported(AgentUserSecurityID: Guid): Boolean + var + Agent: Record Agent; + begin + GetAgent(Agent, AgentUserSecurityID); + + exit(IsArchivingSupported(Agent)); + end; + + local procedure IsArchivingSupported(Agent: Record Agent): Boolean + var + AgentArchiving: Interface IAgentArchiving; + begin + if IsNullGuid(Agent."User Security ID") then + exit(false); + + AgentArchiving := Agent."Agent Metadata Provider"; + + exit(AgentArchiving.IsArchivingSupported()); + end; + procedure IsArchived(AgentUserSecurityID: Guid): Boolean var Agent: Record Agent; @@ -644,6 +668,7 @@ codeunit 4301 "Agent Impl." AgentDoesNotExistErr: Label 'Agent does not exist.'; AgentArchivedCannotBeModifiedErr: Label 'The agent is archived and cannot be modified.'; DeactivateBeforeArchivingErr: Label 'Deactivate the agent before archiving it.'; + ArchivingNotSupportedErr: Label 'Archiving agents of type ''%1'' is not supported.', Comment = '%1 = the type of the agent.'; AutoLbl: Label 'Auto'; NoActiveAgentsErr: Label 'There are no active agents setup on the system.'; NoAgentsAvailableNotificationLbl: Label 'Business Central agents are currently not available in your country.'; diff --git a/src/System Application/App/Agent/Setup/AgentList.Page.al b/src/System Application/App/Agent/Setup/AgentList.Page.al index 4237cb0de76..0f825e1a801 100644 --- a/src/System Application/App/Agent/Setup/AgentList.Page.al +++ b/src/System Application/App/Agent/Setup/AgentList.Page.al @@ -99,6 +99,9 @@ page 4316 "Agent List" if Rec.IsEmpty() then Error(NoAgentSetupErr); + if not Agent.IsArchivingSupported(Rec."User Security ID") then + Error(ArchivingNotSupportedErr, Rec."Agent Metadata Provider"); + if Agent.IsActive(Rec."User Security ID") then Error(DeactivateBeforeArchivingErr); @@ -302,4 +305,5 @@ page 4316 "Agent List" AgentIsArchived: Boolean; NoAgentSetupErr: Label 'No agents have been setup. You must set up an agent first.'; DeactivateBeforeArchivingErr: Label 'Deactivate the agent before archiving it.'; -} \ No newline at end of file + ArchivingNotSupportedErr: Label 'Archiving agents of type ''%1'' is not supported.', Comment = '%1 = the type of the agent.'; +} diff --git a/src/System Application/Test/Agent/src/SDK/AgentTest.Codeunit.al b/src/System Application/Test/Agent/src/SDK/AgentTest.Codeunit.al index 08d454abd54..a28b01df367 100644 --- a/src/System Application/Test/Agent/src/SDK/AgentTest.Codeunit.al +++ b/src/System Application/Test/Agent/src/SDK/AgentTest.Codeunit.al @@ -23,6 +23,7 @@ codeunit 133961 "Agent Test" LibraryVariableStorage: Codeunit "Library - Variable Storage"; DeactivateBeforeArchivingErr: Label 'Deactivate the agent before archiving it.', Locked = true; AgentArchivedCannotBeModifiedErr: Label 'The agent is archived and cannot be modified.', Locked = true; + ArchivingNotSupportedErr: Label 'Archiving agents of type ''%1'' is not supported.', Locked = true; local procedure Initialize() begin @@ -993,6 +994,7 @@ codeunit 133961 "Agent Test" Agent.Deactivate(AgentId); Assert.IsFalse(Agent.IsArchived(AgentId), 'Agent should not be archived initially'); + Assert.IsTrue(Agent.IsArchivingSupported(AgentId), 'Archiving should be supported for a type that does not opt out'); // [WHEN] Archiving the agent Agent.Archive(AgentId); @@ -1005,6 +1007,37 @@ codeunit 133961 "Agent Test" Assert.AreEqual(AgentRecord.Substate::Archived, AgentRecord.Substate, 'Agent substate should be Archived'); end; + [Test] + procedure ArchiveAgentOfUnsupportedTypeErrors() + var + AgentRecord: Record Agent; + TempAgentAccessControl: Record "Agent Access Control" temporary; + Any: Codeunit Any; + AgentId: Guid; + AgentUserName: Code[50]; + begin + Initialize(); + + // [SCENARIO] Archiving an agent whose type opts out of archiving is rejected + + // [GIVEN] An agent of a type that reports archiving as unsupported + AgentUserName := CopyStr(Any.AlphanumericText(MaxStrLen(AgentRecord."User Name")), 1, MaxStrLen(AgentRecord."User Name")); + AgentId := Agent.Create( + "Agent Metadata Provider"::"SDK Mock Agent No Archiving", + AgentUserName, + CopyStr(Any.AlphanumericText(80), 1, 80), + TempAgentAccessControl); + + // [THEN] Archiving is reported as unsupported for that agent + Assert.IsFalse(Agent.IsArchivingSupported(AgentId), 'Archiving should not be supported for this agent type'); + + // [WHEN] Archiving the agent + // [THEN] An error is raised and the agent is not archived + asserterror Agent.Archive(AgentId); + Assert.ExpectedError(StrSubstNo(ArchivingNotSupportedErr, "Agent Metadata Provider"::"SDK Mock Agent No Archiving")); + Assert.IsFalse(Agent.IsArchived(AgentId), 'Agent should not be archived'); + end; + [Test] procedure ArchiveActiveAgentErrors() var diff --git a/src/System Application/Test/Agent/src/Setup/LibraryMockAgent.codeunit.al b/src/System Application/Test/Agent/src/Setup/LibraryMockAgent.codeunit.al index 933e0f04381..a35be59b12f 100644 --- a/src/System Application/Test/Agent/src/Setup/LibraryMockAgent.codeunit.al +++ b/src/System Application/Test/Agent/src/Setup/LibraryMockAgent.codeunit.al @@ -49,7 +49,7 @@ codeunit 133954 "Library Mock Agent" AgentRecord: Record Agent; MockAgentSetup: Record "Mock Agent Setup"; begin - AgentRecord.SetRange("Agent Metadata Provider", AgentRecord."Agent Metadata Provider"::"SDK Mock Agent"); + AgentRecord.SetFilter("Agent Metadata Provider", '%1|%2', AgentRecord."Agent Metadata Provider"::"SDK Mock Agent", AgentRecord."Agent Metadata Provider"::"SDK Mock Agent No Archiving"); if AgentRecord.FindSet() then repeat if MockAgentSetup.Get(AgentRecord."User Security ID") then diff --git a/src/System Application/Test/Agent/src/Setup/MockAgent/Integration/MockAgentMetaProv.Codeunit.al b/src/System Application/Test/Agent/src/Setup/MockAgent/Integration/MockAgentMetaProv.Codeunit.al index fba61972d23..fd5c5b19fef 100644 --- a/src/System Application/Test/Agent/src/Setup/MockAgent/Integration/MockAgentMetaProv.Codeunit.al +++ b/src/System Application/Test/Agent/src/Setup/MockAgent/Integration/MockAgentMetaProv.Codeunit.al @@ -10,7 +10,7 @@ using System.AI; using System.Reflection; using System.Security.AccessControl; -codeunit 133952 "Mock Agent Meta. Prov." implements IAgentMetadata, IAgentFactory +codeunit 133952 "Mock Agent Meta. Prov." implements IAgentMetadata, IAgentFactory, IAgentArchiving { InherentEntitlements = X; InherentPermissions = X; @@ -21,6 +21,11 @@ codeunit 133952 "Mock Agent Meta. Prov." implements IAgentMetadata, IAgentFactor MockAgentSetup: Codeunit "Mock Agent Setup"; MockAgentInitialLbl: Label 'MA', MaxLength = 4; + procedure IsArchivingSupported(): Boolean + begin + exit(false); + end; + procedure GetDefaultInitials(): Text[4] begin exit(MockAgentInitialLbl); diff --git a/src/System Application/Test/Agent/src/Setup/MockAgent/Integration/MockAgentMetaProv.EnumExt.al b/src/System Application/Test/Agent/src/Setup/MockAgent/Integration/MockAgentMetaProv.EnumExt.al index 4a309e58cf5..75888285417 100644 --- a/src/System Application/Test/Agent/src/Setup/MockAgent/Integration/MockAgentMetaProv.EnumExt.al +++ b/src/System Application/Test/Agent/src/Setup/MockAgent/Integration/MockAgentMetaProv.EnumExt.al @@ -13,4 +13,10 @@ enumextension 133952 "Mock Agent Meta. Prov." extends "Agent Metadata Provider" Caption = 'SDK Mock Agent'; Implementation = IAgentFactory = "Mock Agent Meta. Prov.", IAgentMetadata = "Mock Agent Meta. Prov."; } + + value(133955; "SDK Mock Agent No Archiving") + { + Caption = 'SDK Mock Agent No Archiving'; + Implementation = IAgentFactory = "Mock Agent Meta. Prov.", IAgentMetadata = "Mock Agent Meta. Prov.", IAgentArchiving = "Mock Agent Meta. Prov."; + } }