diff --git a/src/System Application/App/Agent/ExtensionLogo.png b/src/System Application/App/Agent/ExtensionLogo.png
new file mode 100644
index 0000000000..f53be3156b
Binary files /dev/null and b/src/System Application/App/Agent/ExtensionLogo.png differ
diff --git a/src/System Application/App/Agent/Interaction/AgentMessage.Codeunit.al b/src/System Application/App/Agent/Interaction/AgentMessage.Codeunit.al
new file mode 100644
index 0000000000..1ec3011219
--- /dev/null
+++ b/src/System Application/App/Agent/Interaction/AgentMessage.Codeunit.al
@@ -0,0 +1,75 @@
+// ------------------------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+// ------------------------------------------------------------------------------------------------
+
+namespace System.Agents;
+
+codeunit 4307 "Agent Message"
+{
+ InherentEntitlements = X;
+ InherentPermissions = X;
+
+ ///
+ /// Get the message text for the given agent task message.
+ ///
+ /// Agent task message.
+ /// The body of the agent task message.
+ [Scope('OnPrem')]
+ procedure GetText(var AgentTaskMessage: Record "Agent Task Message"): Text
+ var
+ AgentMessageImpl: Codeunit "Agent Message Impl.";
+ begin
+ exit(AgentMessageImpl.GetMessageText(AgentTaskMessage));
+ end;
+
+ ///
+ /// Updates the message text.
+ ///
+ /// The message record to update.
+ /// New message text to set.
+ [Scope('OnPrem')]
+ procedure UpdateText(var AgentTaskMessage: Record "Agent Task Message"; NewMessageText: Text)
+ var
+ AgentMessageImpl: Codeunit "Agent Message Impl.";
+ begin
+ AgentMessageImpl.UpdateText(AgentTaskMessage, NewMessageText);
+ end;
+
+ ///
+ /// Check if it is possible to edit the message.
+ ///
+ /// Agent task message to verify.
+ /// If it is possible to change the message.
+ [Scope('OnPrem')]
+ procedure IsEditable(var AgentTaskMessage: Record "Agent Task Message"): Boolean
+ var
+ AgentMessageImpl: Codeunit "Agent Message Impl.";
+ begin
+ exit(AgentMessageImpl.IsMessageEditable(AgentTaskMessage));
+ end;
+
+ ///
+ /// Sets the message status to sent.
+ ///
+ /// Agent task message to update status.
+ [Scope('OnPrem')]
+ procedure SetStatusToSent(var AgentTaskMessage: Record "Agent Task Message")
+ var
+ AgentMessageImpl: Codeunit "Agent Message Impl.";
+ begin
+ AgentMessageImpl.SetStatusToSent(AgentTaskMessage);
+ end;
+
+ ///
+ /// Downloads the attachments for a specific message.
+ ///
+ /// Message to download attachments for.
+ [Scope('OnPrem')]
+ procedure DownloadAttachments(var AgentTaskMessage: Record "Agent Task Message")
+ var
+ AgentMessageImpl: Codeunit "Agent Message Impl.";
+ begin
+ AgentMessageImpl.DownloadAttachments(AgentTaskMessage);
+ end;
+}
\ No newline at end of file
diff --git a/src/System Application/App/Agent/Interaction/AgentMessageImpl.Codeunit.al b/src/System Application/App/Agent/Interaction/AgentMessageImpl.Codeunit.al
new file mode 100644
index 0000000000..17a727fd55
--- /dev/null
+++ b/src/System Application/App/Agent/Interaction/AgentMessageImpl.Codeunit.al
@@ -0,0 +1,80 @@
+// ------------------------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+// ------------------------------------------------------------------------------------------------
+
+namespace System.Agents;
+
+codeunit 4308 "Agent Message Impl."
+{
+ Access = Internal;
+ InherentEntitlements = X;
+ InherentPermissions = X;
+
+ procedure GetMessageText(var AgentTaskMessage: Record "Agent Task Message"): Text
+ var
+ AgentTaskImpl: Codeunit "Agent Task Impl.";
+ ContentInStream: InStream;
+ ContentText: Text;
+ begin
+ AgentTaskMessage.CalcFields(Content);
+ AgentTaskMessage.Content.CreateInStream(ContentInStream, AgentTaskImpl.GetDefaultEncoding());
+ ContentInStream.Read(ContentText);
+ exit(ContentText);
+ end;
+
+ procedure UpdateText(var AgentTaskMessage: Record "Agent Task Message"; NewMessageText: Text)
+ var
+ AgentTaskImpl: Codeunit "Agent Task Impl.";
+ ContentOutStream: OutStream;
+ begin
+ Clear(AgentTaskMessage.Content);
+ AgentTaskMessage.Content.CreateOutStream(ContentOutStream, AgentTaskImpl.GetDefaultEncoding());
+ ContentOutStream.Write(NewMessageText);
+ AgentTaskMessage.Modify(true);
+ end;
+
+ procedure IsMessageEditable(var AgentTaskMessage: Record "Agent Task Message"): Boolean
+ begin
+ if AgentTaskMessage.Type <> AgentTaskMessage.Type::Output then
+ exit(false);
+
+ exit(AgentTaskMessage.Status = AgentTaskMessage.Status::Draft);
+ end;
+
+ procedure SetStatusToSent(var AgentTaskMessage: Record "Agent Task Message")
+ begin
+ UpdateAgentTaskMessageStatus(AgentTaskMessage, AgentTaskMessage.Status::Sent);
+ end;
+
+ procedure DownloadAttachments(var AgentTaskMessage: Record "Agent Task Message")
+ var
+ AgentTaskFile: Record "Agent Task File";
+ AgentTaskMessageAttachment: Record "Agent Task Message Attachment";
+ AgentTaskImpl: Codeunit "Agent Task Impl.";
+ InStream: InStream;
+ FileName: Text;
+ DownloadDialogTitleLbl: Label 'Download Email Attachment';
+ begin
+ AgentTaskMessageAttachment.SetRange("Task ID", AgentTaskMessage."Task ID");
+ AgentTaskMessageAttachment.SetRange("Message ID", AgentTaskMessage.ID);
+ if not AgentTaskMessageAttachment.FindSet() then
+ exit;
+
+ repeat
+ if not AgentTaskFile.Get(AgentTaskMessageAttachment."Task ID", AgentTaskMessageAttachment."File ID") then
+ exit;
+
+ FileName := AgentTaskFile."File Name";
+ AgentTaskFile.CalcFields(Content);
+ AgentTaskFile.Content.CreateInStream(InStream, AgentTaskImpl.GetDefaultEncoding());
+ File.DownloadFromStream(InStream, DownloadDialogTitleLbl, '', '', FileName);
+ until AgentTaskMessageAttachment.Next() = 0;
+ end;
+
+ procedure UpdateAgentTaskMessageStatus(var AgentTaskMessage: Record "Agent Task Message"; Status: Option)
+ begin
+ AgentTaskMessage.Status := Status;
+ AgentTaskMessage.Modify(true);
+ end;
+}
\ No newline at end of file
diff --git a/src/System Application/App/Agent/Interaction/AgentTask.Codeunit.al b/src/System Application/App/Agent/Interaction/AgentTask.Codeunit.al
new file mode 100644
index 0000000000..d2571da29b
--- /dev/null
+++ b/src/System Application/App/Agent/Interaction/AgentTask.Codeunit.al
@@ -0,0 +1,41 @@
+// ------------------------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+// ------------------------------------------------------------------------------------------------
+
+namespace System.Agents;
+
+codeunit 4303 "Agent Task"
+{
+ InherentEntitlements = X;
+ InherentPermissions = X;
+
+ ///
+ /// Check if a task exists for the given agent user and conversation
+ ///
+ /// The user security ID of the agent.
+ /// The conversation ID to check.
+ /// True if task exists, false if not.
+ [Scope('OnPrem')]
+ procedure TaskExists(AgentUserSecurityId: Guid; ConversationId: Text): Boolean
+ var
+ AgentTaskImpl: Codeunit "Agent Task Impl.";
+ begin
+ exit(AgentTaskImpl.TaskExists(AgentUserSecurityId, ConversationId));
+ end;
+
+ ///
+ /// Create a new task message for the given agent user and conversation.
+ /// If task does not exist, it will be created.
+ ///
+ /// Specifies from address.
+ /// The message text for the task.
+ /// Current Agent Task to which the message will be added.
+ [Scope('OnPrem')]
+ procedure CreateTaskMessage(From: Text[250]; MessageText: Text; ExternalMessageId: Text[2048]; var CurrentAgentTask: Record "Agent Task")
+ var
+ AgentTaskImpl: Codeunit "Agent Task Impl.";
+ begin
+ AgentTaskImpl.CreateTaskMessage(From, MessageText, ExternalMessageId, CurrentAgentTask);
+ end;
+}
\ No newline at end of file
diff --git a/src/System Application/App/Agent/Interaction/AgentTaskImpl.Codeunit.al b/src/System Application/App/Agent/Interaction/AgentTaskImpl.Codeunit.al
new file mode 100644
index 0000000000..539b10fb6d
--- /dev/null
+++ b/src/System Application/App/Agent/Interaction/AgentTaskImpl.Codeunit.al
@@ -0,0 +1,202 @@
+// ------------------------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+// ------------------------------------------------------------------------------------------------
+
+namespace System.Agents;
+
+using System.Integration;
+using System.Environment;
+
+codeunit 4300 "Agent Task Impl."
+{
+ Access = Internal;
+ InherentEntitlements = X;
+ InherentPermissions = X;
+
+ procedure SetMessageText(var AgentTaskMessage: Record "Agent Task Message"; MessageText: Text)
+ var
+ ContentOutStream: OutStream;
+ begin
+ Clear(AgentTaskMessage.Content);
+ AgentTaskMessage.Content.CreateOutStream(ContentOutStream, GetDefaultEncoding());
+ ContentOutStream.Write(MessageText);
+ AgentTaskMessage.Modify(true);
+ end;
+
+ procedure GetStepsDoneCount(var AgentTask: Record "Agent Task"): Integer
+ var
+ AgentTaskStep: Record "Agent Task Step";
+ begin
+ AgentTaskStep.SetRange("Task ID", AgentTask."ID");
+ AgentTaskStep.ReadIsolation := IsolationLevel::ReadCommitted;
+ exit(AgentTaskStep.Count());
+ end;
+
+ procedure GetDetailsForAgentTaskStep(var AgentTaskStep: Record "Agent Task Step"): Text
+ var
+ ContentInStream: InStream;
+ ContentText: Text;
+ begin
+ AgentTaskStep.CalcFields(Details);
+ AgentTaskStep.Details.CreateInStream(ContentInStream, GetDefaultEncoding());
+ ContentInStream.Read(ContentText);
+ exit(ContentText);
+ end;
+
+ procedure ShowTaskSteps(var AgentTask: Record "Agent Task")
+ var
+ AgentTaskStep: Record "Agent Task Step";
+ begin
+ AgentTaskStep.SetRange("Task ID", AgentTask.ID);
+ Page.Run(Page::"Agent Task Step List", AgentTaskStep);
+ end;
+
+ procedure CreateTaskMessage(From: Text[250]; MessageText: Text; var CurrentAgentTask: Record "Agent Task")
+ begin
+ CreateTaskMessage(From, MessageText, '', CurrentAgentTask);
+ end;
+
+ procedure CreateTaskMessage(From: Text[250]; MessageText: Text; ExternalMessageId: Text[2048]; var CurrentAgentTask: Record "Agent Task")
+ var
+ AgentTask: Record "Agent Task";
+ AgentTaskMessage: Record "Agent Task Message";
+ begin
+ if MessageText = '' then
+ Error(MessageTextMustBeProvidedErr);
+
+ if not AgentTask.Get(CurrentAgentTask.RecordId) then begin
+ AgentTask."Agent User Security ID" := CurrentAgentTask."Agent User Security ID";
+ AgentTask."Created By" := UserSecurityId();
+ AgentTask."Needs Attention" := false;
+ AgentTask.Status := AgentTask.Status::Paused;
+ AgentTask.Title := CurrentAgentTask.Title;
+ AgentTask."External ID" := CurrentAgentTask."External ID";
+ AgentTask.Insert();
+ end;
+
+ AgentTaskMessage."Task ID" := AgentTask.ID;
+ AgentTaskMessage."Type" := AgentTaskMessage."Type"::Input;
+ AgentTaskMessage."External ID" := ExternalMessageId;
+ AgentTaskMessage.From := From;
+ AgentTaskMessage.Insert();
+
+ SetMessageText(AgentTaskMessage, MessageText);
+
+ // Only change the status if the task is in a status where it can be started again.
+ // If the task is running, we should not change the state, as platform will pickup a new message automatically.
+ if ((AgentTask.Status = AgentTask.Status::Paused) or (AgentTask.Status = AgentTask.Status::Completed)) then begin
+ AgentTask.Status := AgentTask.Status::Ready;
+ AgentTask.Modify(true);
+ end;
+ end;
+
+ procedure CreateUserInterventionTaskStep(UserInterventionRequestStep: Record "Agent Task Step")
+ begin
+ CreateUserInterventionTaskStep(UserInterventionRequestStep, '', -1);
+ end;
+
+ procedure CreateUserInterventionTaskStep(UserInterventionRequestStep: Record "Agent Task Step"; UserInput: Text)
+ begin
+ CreateUserInterventionTaskStep(UserInterventionRequestStep, UserInput, -1);
+ end;
+
+ procedure CreateUserInterventionTaskStep(UserInterventionRequestStep: Record "Agent Task Step"; SelectedSuggestionId: Integer)
+ begin
+ CreateUserInterventionTaskStep(UserInterventionRequestStep, '', SelectedSuggestionId);
+ end;
+
+ procedure CreateUserInterventionTaskStep(UserInterventionRequestStep: Record "Agent Task Step"; UserInput: Text; SelectedSuggestionId: Integer)
+ var
+ AgentTask: Record "Agent Task";
+ AgentTaskStep: Record "Agent Task Step";
+ DetailsOutStream: OutStream;
+ DetailsJson: JsonObject;
+ begin
+ AgentTask.Get(UserInterventionRequestStep."Task ID");
+
+ AgentTaskStep."Task ID" := AgentTask.ID;
+ AgentTaskStep."Type" := AgentTaskStep."Type"::"User Intervention";
+ AgentTaskStep.Description := 'User intervention';
+ DetailsJson.Add('interventionRequestStepNumber', UserInterventionRequestStep."Step Number");
+ if UserInput <> '' then
+ DetailsJson.Add('userInput', UserInput);
+ if SelectedSuggestionId >= 0 then
+ DetailsJson.Add('selectedSuggestionId', SelectedSuggestionId);
+ AgentTaskStep.CalcFields(Details);
+ Clear(AgentTaskStep.Details);
+ AgentTaskStep.Details.CreateOutStream(DetailsOutStream, GetDefaultEncoding());
+ DetailsJson.WriteTo(DetailsOutStream);
+ AgentTaskStep.Insert();
+ end;
+
+ procedure StopTask(var AgentTask: Record "Agent Task"; AgentTaskStatus: enum "Agent Task Status"; UserConfirm: Boolean)
+ begin
+ if ((AgentTask.Status = AgentTaskStatus) and (AgentTask."Needs Attention" = false)) then
+ exit; // Task is already stopped and does not need attention.
+
+ if UserConfirm then
+ if not Confirm(AreYouSureThatYouWantToStopTheTaskQst) then
+ exit;
+
+ AgentTask.Status := AgentTaskStatus;
+ AgentTask."Needs Attention" := false;
+ AgentTask.Modify(true);
+ end;
+
+ procedure RestartTask(var AgentTask: Record "Agent Task"; UserConfirm: Boolean)
+ begin
+ if UserConfirm then
+ if not Confirm(AreYouSureThatYouWantToRestartTheTaskQst) then
+ exit;
+
+ AgentTask."Needs Attention" := false;
+ AgentTask.Status := AgentTask.Status::Ready;
+ AgentTask.Modify(true);
+ end;
+
+ procedure TaskExists(AgentUserSecurityID: Guid; ConversationId: Text): Boolean
+ var
+ AgentTask: Record "Agent Task";
+ begin
+ AgentTask.SetRange("Agent User Security ID", AgentUserSecurityID);
+ AgentTask.ReadIsolation(IsolationLevel::ReadCommitted);
+ AgentTask.SetRange("External ID", ConversationId);
+ exit(not AgentTask.IsEmpty());
+ end;
+
+ procedure GetDefaultEncoding(): TextEncoding
+ begin
+ exit(TextEncoding::UTF8);
+ end;
+
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"System Action Triggers", GetAgentTaskMessagePageId, '', true, true)]
+ local procedure OnGetAgentTaskMessagePageId(var PageId: Integer)
+ begin
+ PageId := Page::"Agent Task Message Card";
+ end;
+
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"System Action Triggers", GetPageSummary, '', true, true)]
+ local procedure OnGetGetPageSummary(PageId: Integer; Bookmark: Text; var Summary: Text)
+ var
+ PageSummaryParameters: Record "Page Summary Parameters";
+ PageSummaryProvider: Codeunit "Page Summary Provider";
+ begin
+ if PageId = 0 then begin
+ Summary := '';
+ exit;
+ end;
+
+ PageSummaryParameters."Page ID" := PageId;
+#pragma warning disable AA0139
+ PageSummaryParameters.Bookmark := Bookmark;
+#pragma warning restore AA0139
+ PageSummaryParameters."Include Binary Data" := false;
+ Summary := PageSummaryProvider.GetPageSummary(PageSummaryParameters);
+ end;
+
+ var
+ MessageTextMustBeProvidedErr: Label 'You must provide a message text.';
+ AreYouSureThatYouWantToRestartTheTaskQst: Label 'Are you sure that you want to restart the task?';
+ AreYouSureThatYouWantToStopTheTaskQst: Label 'Are you sure that you want to stop the task?';
+}
\ No newline at end of file
diff --git a/src/System Application/App/Agent/Interaction/AgentTaskList.Page.al b/src/System Application/App/Agent/Interaction/AgentTaskList.Page.al
new file mode 100644
index 0000000000..85c2b144ad
--- /dev/null
+++ b/src/System Application/App/Agent/Interaction/AgentTaskList.Page.al
@@ -0,0 +1,187 @@
+// ------------------------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+// ------------------------------------------------------------------------------------------------
+
+namespace System.Agents;
+
+page 4300 "Agent Task List"
+{
+ PageType = List;
+ ApplicationArea = All;
+ UsageCategory = Administration;
+ SourceTable = "Agent Task";
+ Caption = 'Agent Tasks';
+ InsertAllowed = false;
+ ModifyAllowed = false;
+ DeleteAllowed = false;
+ AdditionalSearchTerms = 'Agent Tasks, Agent Task, Agent, Agent Log, Agent Logs';
+ SourceTableView = sorting("Last Step Timestamp") order(descending);
+ InherentEntitlements = X;
+ InherentPermissions = X;
+
+ layout
+ {
+ area(Content)
+ {
+ repeater(AgentConversations)
+ {
+ field(TaskID; Rec.ID)
+ {
+ Caption = 'Task ID';
+ }
+ field(Title; Rec.Title)
+ {
+ Caption = 'Title';
+ }
+ field(LastStepTimestamp; Rec."Last Step Timestamp")
+ {
+ Caption = 'Last Updated';
+ }
+ field(LastStepNumber; Rec."Last Step Number")
+ {
+ }
+ field(Status; Rec.Status)
+ {
+ Caption = 'Status';
+ ToolTip = 'Specifies the status of the agent task.';
+ }
+ field(NeedsAttention; Rec."Needs Attention")
+ {
+ Caption = 'Needs Attention';
+ ToolTip = 'Specifies whether the task needs attention.';
+ }
+ field(CreatedAt; Rec.SystemCreatedAt)
+ {
+ Caption = 'Created at';
+ ToolTip = 'Specifies the date and time when the agent task was created.';
+ }
+ field(ID; Rec.ID)
+ {
+ Caption = 'ID';
+ trigger OnDrillDown()
+ begin
+ ShowTaskMessages();
+ end;
+ }
+ field(NumberOfStepsDone; NumberOfStepsDone)
+ {
+ Caption = 'Steps Done';
+ ToolTip = 'Specifies the number of steps that have been done for the specific task.';
+
+ trigger OnDrillDown()
+ var
+ AgentTaskImpl: Codeunit "Agent Task Impl.";
+ begin
+ AgentTaskImpl.ShowTaskSteps(Rec);
+ end;
+ }
+ field("Created By"; Rec."Created By Full Name")
+ {
+ Caption = 'Created by';
+ Tooltip = 'Specifies the full name of the user that created the agent task.';
+ }
+ field("Agent Display Name"; Rec."Agent Display Name")
+ {
+ Caption = 'Agent';
+ ToolTip = 'Specifies the agent that is associated with the task.';
+ }
+ field(CreatedByID; Rec."Created By")
+ {
+ Visible = false;
+ }
+ field(AgentUserSecurityID; Rec."Agent User Security ID")
+ {
+ Visible = false;
+ }
+ }
+ }
+ }
+ actions
+ {
+ area(Processing)
+ {
+ action(ViewTaskMessage)
+ {
+ ApplicationArea = All;
+ Caption = 'View messages';
+ ToolTip = 'Show messages for the selected task.';
+ Image = ShowList;
+
+ trigger OnAction()
+ begin
+ ShowTaskMessages();
+ end;
+ }
+ action(ViewTaskSteps)
+ {
+ ApplicationArea = All;
+ Caption = 'View steps';
+ ToolTip = 'Show steps for the selected task.';
+ Image = TaskList;
+
+ trigger OnAction()
+ var
+ AgentTaskImpl: Codeunit "Agent Task Impl.";
+ begin
+ AgentTaskImpl.ShowTaskSteps(Rec);
+ end;
+ }
+ action(Stop)
+ {
+ ApplicationArea = All;
+ Caption = 'Stop';
+ ToolTip = 'Stop the selected task.';
+ Image = Stop;
+
+ trigger OnAction()
+ var
+ AgentTaskImpl: Codeunit "Agent Task Impl.";
+ begin
+ AgentTaskImpl.StopTask(Rec, Rec."Status"::"Stopped by User", true);
+ CurrPage.Update(false);
+ end;
+ }
+ }
+ area(Promoted)
+ {
+ group(Category_Process)
+ {
+ actionref(ViewTaskMessage_Promoted; ViewTaskMessage)
+ {
+ }
+ actionref(ViewTaskSteps_Promoted; ViewTaskSteps)
+ {
+ }
+ }
+ }
+ }
+
+ trigger OnAfterGetRecord()
+ begin
+ UpdateControls();
+ end;
+
+ trigger OnAfterGetCurrRecord()
+ begin
+ UpdateControls();
+ end;
+
+ local procedure UpdateControls()
+ var
+ AgentTaskImpl: Codeunit "Agent Task Impl.";
+ begin
+ NumberOfStepsDone := AgentTaskImpl.GetStepsDoneCount(Rec);
+ end;
+
+ local procedure ShowTaskMessages()
+ var
+ AgentTaskMessage: Record "Agent Task Message";
+ begin
+ AgentTaskMessage.SetRange("Task ID", Rec.ID);
+ Page.Run(Page::"Agent Task Message List", AgentTaskMessage);
+ end;
+
+ var
+ NumberOfStepsDone: Integer;
+}
\ No newline at end of file
diff --git a/src/System Application/App/Agent/Interaction/AgentTaskMessageCard.Page.al b/src/System Application/App/Agent/Interaction/AgentTaskMessageCard.Page.al
new file mode 100644
index 0000000000..24c02a6c2e
--- /dev/null
+++ b/src/System Application/App/Agent/Interaction/AgentTaskMessageCard.Page.al
@@ -0,0 +1,167 @@
+// ------------------------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+// ------------------------------------------------------------------------------------------------
+
+namespace System.Agents;
+
+page 4308 "Agent Task Message Card"
+{
+ PageType = Card;
+ ApplicationArea = All;
+ SourceTable = "Agent Task Message";
+ InsertAllowed = false;
+ ModifyAllowed = true;
+ DeleteAllowed = false;
+ Caption = 'Agent Task Message';
+ DataCaptionExpression = '';
+ Extensible = false;
+ InherentEntitlements = X;
+ InherentPermissions = X;
+
+ layout
+ {
+ area(Content)
+ {
+ group(General)
+ {
+ field(LastModifiedAt; Rec.SystemModifiedAt)
+ {
+ Caption = 'Last modified at';
+ ToolTip = 'Specifies the date and time when the message was last modified.';
+ }
+ field(CreatedAt; Rec.SystemCreatedAt)
+ {
+ Caption = 'Created at';
+ ToolTip = 'Specifies the date and time when the message was created.';
+ }
+ field(TaskID; Rec."Task Id")
+ {
+ Caption = 'Task ID';
+ Visible = false;
+ }
+ field(MessageID; Rec."ID")
+ {
+ Caption = 'ID';
+ Visible = false;
+ }
+ field(MessageType; Rec.Type)
+ {
+ Caption = 'Type';
+ }
+ field(MessageFrom; Rec.From)
+ {
+ Visible = Rec.Type = Rec.Type::Input;
+ Caption = 'From';
+ Editable = false;
+ }
+ field(Status; Rec.Status)
+ {
+ Caption = 'Status';
+ Editable = false;
+ }
+ field(AttachmentsCount; AttachmentsCount)
+ {
+ Caption = 'Attachments';
+ ToolTip = 'Specifies the number of attachments that are associated with the message.';
+ Editable = false;
+ }
+ }
+
+ group(Message)
+ {
+ Caption = 'Message';
+ Editable = IsMessageEditable;
+ field(MessageText; GlobalMessageText)
+ {
+ ShowCaption = false;
+ Caption = 'Message';
+ ToolTip = 'Specifies the message text.';
+ MultiLine = true;
+ ExtendedDatatype = RichContent;
+ Editable = IsMessageEditable;
+
+ trigger OnValidate()
+ var
+ AgentMessage: Codeunit "Agent Message";
+ begin
+ AgentMessage.UpdateText(Rec, GlobalMessageText);
+ end;
+
+ }
+ }
+ }
+
+ }
+
+ actions
+ {
+ area(Processing)
+ {
+ action(DownloadAttachment)
+ {
+ ApplicationArea = All;
+ Caption = 'Download attachments';
+ ToolTip = 'Download the attachment.';
+ Image = Download;
+ Enabled = AttachmentsCount > 0;
+
+ trigger OnAction()
+ begin
+ DownloadAttachments();
+ end;
+ }
+ }
+ area(Promoted)
+ {
+ group(Category_Process)
+ {
+ actionref(DownloadAttachment_Promoted; DownloadAttachment)
+ {
+ }
+ }
+ }
+ }
+
+ trigger OnAfterGetRecord()
+ begin
+ UpdateControls();
+ end;
+
+ trigger OnAfterGetCurrRecord()
+ begin
+ UpdateControls();
+ end;
+
+ local procedure UpdateControls()
+ var
+ AgentTaskMessageAttachment: Record "Agent Task Message Attachment";
+ AgentMessage: Codeunit "Agent Message";
+ begin
+ GlobalMessageText := AgentMessage.GetText(Rec);
+ IsMessageEditable := AgentMessage.IsEditable(Rec);
+
+ AgentTaskMessageAttachment.SetRange("Task ID", Rec."Task ID");
+ AgentTaskMessageAttachment.SetRange("Message ID", Rec.ID);
+
+ AttachmentsCount := AgentTaskMessageAttachment.Count();
+ if Rec.Type = Rec.Type::Output then
+ CurrPage.Caption(OutgoingMessageTxt);
+ if Rec.Type = Rec.Type::Input then
+ CurrPage.Caption(IncomingMessageTxt);
+ end;
+
+ local procedure DownloadAttachments()
+ var
+ AgentMessage: Codeunit "Agent Message";
+ begin
+ AgentMessage.DownloadAttachments(Rec);
+ end;
+
+ var
+ GlobalMessageText: Text;
+ IsMessageEditable: Boolean;
+ AttachmentsCount: Integer;
+ OutgoingMessageTxt: Label 'Outgoing message';
+ IncomingMessageTxt: Label 'Incoming message';
+}
\ No newline at end of file
diff --git a/src/System Application/App/Agent/Interaction/AgentTaskMessageList.Page.al b/src/System Application/App/Agent/Interaction/AgentTaskMessageList.Page.al
new file mode 100644
index 0000000000..ad3a375cc2
--- /dev/null
+++ b/src/System Application/App/Agent/Interaction/AgentTaskMessageList.Page.al
@@ -0,0 +1,95 @@
+// ------------------------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+// ------------------------------------------------------------------------------------------------
+
+namespace System.Agents;
+
+page 4301 "Agent Task Message List"
+{
+ PageType = List;
+ ApplicationArea = All;
+ Caption = 'Agent Task Messages';
+ UsageCategory = Administration;
+ SourceTable = "Agent Task Message";
+ CardPageId = "Agent Task Message Card";
+ InsertAllowed = false;
+ ModifyAllowed = false;
+ DeleteAllowed = false;
+ Editable = false;
+ InherentEntitlements = X;
+ InherentPermissions = X;
+
+ layout
+ {
+ area(Content)
+ {
+ repeater(GroupName)
+ {
+ field(LastModifiedAt; Rec.SystemModifiedAt)
+ {
+ Caption = 'Last modified at';
+ ToolTip = 'Specifies the date and time when the message was last modified.';
+ }
+ field(CreatedAt; Rec.SystemCreatedAt)
+ {
+ Caption = 'Created at';
+ ToolTip = 'Specifies the date and time when the message was created.';
+ }
+ field(Status; Rec.Status)
+ {
+ Caption = 'Status';
+ BlankZero = true;
+ BlankNumbers = BlankZero;
+ }
+ field("Created By Full Name"; Rec."Created By Full Name")
+ {
+ Caption = 'Created by';
+ }
+ field(MessageType; Rec.Type)
+ {
+ Caption = 'Type';
+ }
+ field(MessageText; GlobalMessageText)
+ {
+ Caption = 'Message';
+ ToolTip = 'Specifies the message text.';
+
+ trigger OnDrillDown()
+ begin
+ Message(GlobalMessageText);
+ end;
+ }
+ field(TaskID; Rec."Task Id")
+ {
+ Visible = false;
+ Caption = 'Task ID';
+ }
+ field(MessageId; Rec."ID")
+ {
+ Caption = 'ID';
+ }
+ }
+ }
+ }
+
+ trigger OnAfterGetRecord()
+ begin
+ UpdateControls();
+ end;
+
+ trigger OnAfterGetCurrRecord()
+ begin
+ UpdateControls();
+ end;
+
+ local procedure UpdateControls()
+ var
+ AgentMessageImpl: Codeunit "Agent Message Impl.";
+ begin
+ GlobalMessageText := AgentMessageImpl.GetMessageText(Rec);
+ end;
+
+ var
+ GlobalMessageText: Text;
+}
\ No newline at end of file
diff --git a/src/System Application/App/Agent/Interaction/AgentTaskStepList.Page.al b/src/System Application/App/Agent/Interaction/AgentTaskStepList.Page.al
new file mode 100644
index 0000000000..22c61dda7e
--- /dev/null
+++ b/src/System Application/App/Agent/Interaction/AgentTaskStepList.Page.al
@@ -0,0 +1,81 @@
+// ------------------------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+// ------------------------------------------------------------------------------------------------
+
+namespace System.Agents;
+
+page 4303 "Agent Task Step List"
+{
+ PageType = List;
+ ApplicationArea = All;
+ UsageCategory = Administration;
+ SourceTable = "Agent Task Step";
+ Caption = 'Agent Task Steps';
+ InsertAllowed = false;
+ ModifyAllowed = false;
+ DeleteAllowed = false;
+ Editable = false;
+ SourceTableView = sorting("Step Number") order(descending);
+ Extensible = false;
+ InherentEntitlements = X;
+ InherentPermissions = X;
+
+ layout
+ {
+ area(Content)
+ {
+ repeater(AgentConversationActionLog)
+ {
+ field(StepNumber; Rec."Step Number")
+ {
+ Caption = 'Step Number';
+ }
+ field(TaskID; Rec."Task ID")
+ {
+ Visible = false;
+ Caption = 'Task ID';
+ }
+ field(Description; Rec.Description)
+ {
+ Caption = 'Description';
+ }
+ field(Details; DetailsTxt)
+ {
+ Caption = 'Details';
+ ToolTip = 'Specifies the step details.';
+
+ trigger OnDrillDown()
+ begin
+ Message(DetailsTxt);
+ end;
+ }
+ field("User Full Name"; Rec."User Full Name")
+ {
+ Caption = 'User Full Name';
+ Tooltip = 'Specifies the full name of the user that was involved in performing the step..';
+ }
+ }
+ }
+ }
+
+ trigger OnAfterGetRecord()
+ begin
+ UpdateControls();
+ end;
+
+ trigger OnAfterGetCurrRecord()
+ begin
+ UpdateControls();
+ end;
+
+ local procedure UpdateControls()
+ var
+ AgentTaskImpl: Codeunit "Agent Task Impl.";
+ begin
+ DetailsTxt := AgentTaskImpl.GetDetailsForAgentTaskStep(Rec);
+ end;
+
+ var
+ DetailsTxt: Text;
+}
\ No newline at end of file
diff --git a/src/System Application/App/Agent/Permissions/AgentObjects.PermissionSet.al b/src/System Application/App/Agent/Permissions/AgentObjects.PermissionSet.al
new file mode 100644
index 0000000000..4d8e02fc41
--- /dev/null
+++ b/src/System Application/App/Agent/Permissions/AgentObjects.PermissionSet.al
@@ -0,0 +1,23 @@
+// ------------------------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+// ------------------------------------------------------------------------------------------------
+
+namespace System.Agents;
+
+permissionset 4300 "Agent - Objects"
+{
+ Access = Internal;
+ Assignable = false;
+
+ Permissions = codeunit "Agent Task Impl." = X,
+ page "Agent Access Control" = X,
+ page "Agent Card" = X,
+ page "Agent List" = X,
+ page "Agent Task List" = X,
+ page "Agent Task Message Card" = X,
+ page "Agent Task Message List" = X,
+ page "Agent Task Step List" = X,
+ codeunit "Agent Impl." = X,
+ codeunit "Agent Task" = X;
+}
\ No newline at end of file
diff --git a/src/System Application/App/Agent/Setup/Agent.Codeunit.al b/src/System Application/App/Agent/Setup/Agent.Codeunit.al
new file mode 100644
index 0000000000..edf43ef83e
--- /dev/null
+++ b/src/System Application/App/Agent/Setup/Agent.Codeunit.al
@@ -0,0 +1,174 @@
+// ------------------------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+// ------------------------------------------------------------------------------------------------
+
+namespace System.Agents;
+
+using System.Reflection;
+using System.Security.AccessControl;
+
+codeunit 4321 Agent
+{
+ InherentEntitlements = X;
+ InherentPermissions = X;
+
+ ///
+ /// Creates a new agent.
+ /// The agent will be in the disabled state, with the users that can interact with the agent setup.
+ ///
+ /// The metadata provider of the agent.
+ /// User name for the agent.
+ /// Display name for the agent.
+ /// Instructions for the agent that will be used to complete the tasks.
+ /// The list of users that can configure or interact with the agent.
+ /// The ID of the agent.
+#pragma warning disable AS0026
+ [Scope('OnPrem')]
+ procedure Create(AgentMetadataProvider: Enum "Agent Metadata Provider"; UserName: Code[50]; UserDisplayName: Text[80]; var TempAgentAccessControl: Record "Agent Access Control" temporary): Guid
+#pragma warning restore AS0026
+ var
+ AgentImpl: Codeunit "Agent Impl.";
+ begin
+ exit(AgentImpl.CreateAgent(AgentMetadataProvider, UserName, UserDisplayName, TempAgentAccessControl));
+ end;
+
+ ///
+ /// Activates the agent
+ ///
+ /// The user security ID of the agent.
+ [Scope('OnPrem')]
+ procedure Activate(AgentUserSecurityID: Guid)
+ var
+ AgentImpl: Codeunit "Agent Impl.";
+ begin
+ AgentImpl.Activate(AgentUserSecurityID);
+ end;
+
+ ///
+ /// Deactivates the agent
+ ///
+ /// The user security ID of the agent.
+ [Scope('OnPrem')]
+ procedure Deactivate(AgentUserSecurityID: Guid)
+ var
+ AgentImpl: Codeunit "Agent Impl.";
+ begin
+ AgentImpl.Deactivate(AgentUserSecurityID);
+ end;
+
+ ///
+ /// Get the display name of the agent.
+ ///
+ /// The user security ID of the agent.
+ [Scope('OnPrem')]
+ procedure GetDisplayName(AgentUserSecurityID: Guid): Text[80]
+ var
+ AgentImpl: Codeunit "Agent Impl.";
+ begin
+ exit(AgentImpl.GetDisplayName(AgentUserSecurityID));
+ end;
+
+ ///
+ /// Get the user name of the agent.
+ ///
+ /// The user security ID of the agent.
+ [Scope('OnPrem')]
+ procedure GetUserName(AgentUserSecurityID: Guid): Code[50]
+ var
+ AgentImpl: Codeunit "Agent Impl.";
+ begin
+ exit(AgentImpl.GetUserName(AgentUserSecurityID));
+ end;
+
+ ///
+ /// Sets the display name of the agent.
+ ///
+ /// The user security ID of the agent.
+ /// The display name of the agent.
+ [Scope('OnPrem')]
+ procedure SetDisplayName(AgentUserSecurityID: Guid; DisplayName: Text[80])
+ var
+ AgentImpl: Codeunit "Agent Impl.";
+ begin
+ AgentImpl.SetDisplayName(AgentUserSecurityID, DisplayName);
+ end;
+
+ ///
+ /// Set the instructions which agent will use to complete the tasks.
+ ///
+ /// The agent which instructions will be set.
+ /// Instructions for the agent that will be used to complete the tasks.
+ [Scope('OnPrem')]
+ procedure SetInstructions(AgentUserSecurityID: Guid; Instructions: SecretText)
+ var
+ AgentImpl: Codeunit "Agent Impl.";
+ begin
+ AgentImpl.SetInstructions(AgentUserSecurityID, Instructions);
+ end;
+
+ ///
+ /// Checks if the agent is active.
+ ///
+ /// The user security ID of the agent.
+ /// If the agent is active.
+ [Scope('OnPrem')]
+ procedure IsActive(AgentUserSecurityID: Guid): Boolean
+ var
+ AgentImpl: Codeunit "Agent Impl.";
+ begin
+ exit(AgentImpl.IsActive(AgentUserSecurityID));
+ end;
+
+ ///
+ /// Assigns the permission set to the agent.
+ ///
+ /// The user security ID of the agent.
+ /// Profile to set to the agent.
+ [Scope('OnPrem')]
+ procedure SetProfile(AgentUserSecurityID: Guid; var AllProfile: Record "All Profile")
+ var
+ AgentImpl: Codeunit "Agent Impl.";
+ begin
+ AgentImpl.SetProfile(AgentUserSecurityID, AllProfile);
+ end;
+
+ ///
+ /// Assigns the permission set to the agent.
+ ///
+ /// The user security ID of the agent.
+ /// Permission sets to assign
+ [Scope('OnPrem')]
+ procedure AssignPermissionSet(AgentUserSecurityID: Guid; var AggregatePermissionSet: Record "Aggregate Permission Set")
+ var
+ AgentImpl: Codeunit "Agent Impl.";
+ begin
+ AgentImpl.AssignPermissionSets(AgentUserSecurityID, CompanyName(), AggregatePermissionSet);
+ end;
+
+ ///
+ /// Gets the users that can manage or give tasks to the agent.
+ ///
+ /// Security ID of the agent.
+ /// List of users that can manage or give tasks to the agent.
+ [Scope('OnPrem')]
+ procedure GetUserAccess(AgentUserSecurityID: Guid; var TempAgentAccessControl: Record "Agent Access Control" temporary)
+ var
+ AgentImpl: Codeunit "Agent Impl.";
+ begin
+ AgentImpl.GetUserAccess(AgentUserSecurityID, TempAgentAccessControl);
+ end;
+
+ ///
+ /// Sets the users that can manage or give tasks to the agent. Existing set of users will be replaced with a new set.
+ ///
+ /// Security ID of the agent.
+ /// List of users that can manage or give tasks to the agent.
+ [Scope('OnPrem')]
+ procedure UpdateAccess(AgentUserSecurityID: Guid; var TempAgentAccessControl: Record "Agent Access Control" temporary)
+ var
+ AgentImpl: Codeunit "Agent Impl.";
+ begin
+ AgentImpl.UpdateAgentAccessControl(AgentUserSecurityID, TempAgentAccessControl);
+ end;
+}
\ No newline at end of file
diff --git a/src/System Application/App/Agent/Setup/AgentAccessControl.Page.al b/src/System Application/App/Agent/Setup/AgentAccessControl.Page.al
new file mode 100644
index 0000000000..f9cc77b28a
--- /dev/null
+++ b/src/System Application/App/Agent/Setup/AgentAccessControl.Page.al
@@ -0,0 +1,134 @@
+// ------------------------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+// ------------------------------------------------------------------------------------------------
+
+namespace System.Agents;
+
+using System.Security.AccessControl;
+
+page 4320 "Agent Access Control"
+{
+ PageType = ListPart;
+ ApplicationArea = All;
+ SourceTable = "Agent Access Control";
+ Caption = 'Agent Access Control';
+ MultipleNewLines = false;
+ Extensible = false;
+ InherentEntitlements = X;
+ InherentPermissions = X;
+
+ layout
+ {
+ area(Content)
+ {
+ repeater(Main)
+ {
+ field(UserName; UserName)
+ {
+ Caption = 'User Name';
+ ToolTip = 'Specifies the name of the User that can access the agent.';
+ TableRelation = User;
+
+ trigger OnValidate()
+ begin
+ ValidateUserName(UserName);
+ end;
+ }
+ field(UserFullName; UserFullName)
+ {
+ Caption = 'User Full Name';
+ ToolTip = 'Specifies the Full Name of the User that can access the agent.';
+ Editable = false;
+ }
+ field(CanConfigureAgent; Rec."Can Configure Agent")
+ {
+ Caption = 'Can Configure';
+ Tooltip = 'Specifies whether the user can configure the agent.';
+
+ trigger OnValidate()
+ var
+ AgentImpl: Codeunit "Agent Impl.";
+ begin
+ if not Rec."Can Configure Agent" then
+ AgentImpl.VerifyOwnerExists(Rec);
+ end;
+ }
+ }
+ }
+ }
+
+ trigger OnAfterGetRecord()
+ begin
+ UpdateGlobalVariables();
+ end;
+
+ trigger OnAfterGetCurrRecord()
+ begin
+ UpdateGlobalVariables();
+ end;
+
+ trigger OnDeleteRecord(): Boolean
+ var
+ AgentImpl: Codeunit "Agent Impl.";
+ begin
+ AgentImpl.VerifyOwnerExists(Rec);
+ end;
+
+ local procedure ValidateUserName(NewUserName: Text)
+ var
+ User: Record "User";
+ UserGuid: Guid;
+ begin
+ if Evaluate(UserGuid, NewUserName) then begin
+ User.Get(UserGuid);
+ UpdateUser(User."User Security ID");
+ UpdateGlobalVariables();
+ exit;
+ end;
+
+ User.SetRange("User Name", NewUserName);
+ if not User.FindFirst() then begin
+ User.SetFilter("User Name", '@*''''' + NewUserName + '''''*');
+ User.FindFirst();
+ end;
+
+ UpdateUser(User."User Security ID");
+ UpdateGlobalVariables();
+ end;
+
+ local procedure UpdateUser(NewUserID: Guid)
+ var
+ RecordExists: Boolean;
+ begin
+ RecordExists := Rec.Find();
+
+ if RecordExists then
+ Error(CannotUpdateUserErr);
+
+ Rec."User Security ID" := NewUserID;
+ Rec.Insert(true);
+ end;
+
+ local procedure UpdateGlobalVariables()
+ var
+ User: Record "User";
+ begin
+ Clear(UserFullName);
+ Clear(UserName);
+
+ if IsNullGuid(Rec."User Security ID") then
+ exit;
+
+ if not User.Get(Rec."User Security ID") then
+ exit;
+
+ UserName := User."User Name";
+ UserFullName := User."Full Name";
+ end;
+
+ var
+ UserFullName: Text[80];
+ UserName: Code[50];
+ CannotUpdateUserErr: Label 'You cannot change the User. Delete and create the entry again.';
+}
\ No newline at end of file
diff --git a/src/System Application/App/Agent/Setup/AgentCard.Page.al b/src/System Application/App/Agent/Setup/AgentCard.Page.al
new file mode 100644
index 0000000000..1ec4a99eb6
--- /dev/null
+++ b/src/System Application/App/Agent/Setup/AgentCard.Page.al
@@ -0,0 +1,228 @@
+// ------------------------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+// ------------------------------------------------------------------------------------------------
+
+namespace System.Agents;
+
+using System.Security.User;
+using System.Environment.Configuration;
+
+page 4315 "Agent Card"
+{
+ PageType = Card;
+ ApplicationArea = All;
+ SourceTable = Agent;
+ Caption = 'Agent Card';
+ RefreshOnActivate = true;
+ DataCaptionExpression = Rec."User Name";
+ InsertAllowed = false;
+ DeleteAllowed = false;
+ InherentEntitlements = X;
+ InherentPermissions = X;
+
+ layout
+ {
+ area(Content)
+ {
+ group(General)
+ {
+ Caption = 'General';
+ field("Agent Metadata Provider"; Rec."Agent Metadata Provider")
+ {
+ ShowMandatory = true;
+ ApplicationArea = Basic, Suite;
+ Caption = 'Type';
+ Tooltip = 'Specifies the type of the agent.';
+ Editable = false;
+ }
+ field(UserName; Rec."User Name")
+ {
+ ShowMandatory = true;
+ ApplicationArea = Basic, Suite;
+ Caption = 'User Name';
+ Tooltip = 'Specifies the name of the user that is associated with the agent.';
+ Editable = false;
+ }
+
+ field(DisplayName; Rec."Display Name")
+ {
+ ShowMandatory = true;
+ ApplicationArea = Basic, Suite;
+ Caption = 'Display Name';
+ Tooltip = 'Specifies the display name of the user that is associated with the agent.';
+ Editable = false;
+ }
+ group(UserSettingsGroup)
+ {
+ ShowCaption = false;
+ field(AgentProfile; ProfileDisplayName)
+ {
+ ApplicationArea = Basic, Suite;
+ Caption = 'Profile';
+ ToolTip = 'Specifies the profile that is associated with the agent.';
+ Editable = false;
+
+ trigger OnAssistEdit()
+ var
+ AgentImpl: Codeunit "Agent Impl.";
+ begin
+ if AgentImpl.ProfileLookup(UserSettingsRecord) then
+ AgentImpl.UpdateAgentUserSettings(UserSettingsRecord);
+ end;
+ }
+ }
+ field(State; Rec.State)
+ {
+ ApplicationArea = Basic, Suite;
+ Importance = Standard;
+ Caption = 'State';
+ ToolTip = 'Specifies if the agent is enabled or disabled.';
+
+ trigger OnValidate()
+ begin
+ ChangeState();
+ UpdateControls();
+ end;
+ }
+ }
+
+ part(Permissions; "User Subform")
+ {
+ Editable = ControlsEditable;
+ ApplicationArea = Basic, Suite;
+ Caption = 'Agent Permission Sets';
+ SubPageLink = "User Security ID" = field("User Security ID");
+ }
+ part(UserAccess; "Agent Access Control")
+ {
+ Editable = ControlsEditable;
+ ApplicationArea = Basic, Suite;
+ Caption = 'User Access';
+ SubPageLink = "Agent User Security ID" = field("User Security ID");
+ }
+ }
+ }
+ actions
+ {
+ area(Navigation)
+ {
+ action(AgentSetup)
+ {
+ ApplicationArea = Basic, Suite;
+ Caption = 'Setup';
+ ToolTip = 'Set up agent';
+ Image = SetupLines;
+
+ trigger OnAction()
+ begin
+ OpenSetupPage();
+ end;
+ }
+ action(UserSettingsAction)
+ {
+ ApplicationArea = Basic, Suite;
+ Caption = 'User Settings';
+ ToolTip = 'Set up the profile and regional settings for the agent.';
+ Image = SetupLines;
+
+ trigger OnAction()
+ var
+ UserSettings: Codeunit "User Settings";
+ begin
+ Rec.TestField("User Security ID");
+ UserSettings.GetUserSettings(Rec."User Security ID", UserSettingsRecord);
+ Page.RunModal(Page::"User Settings", UserSettingsRecord);
+ end;
+ }
+ action(AgentTasks)
+ {
+ ApplicationArea = All;
+ Caption = 'Agent Tasks';
+ ToolTip = 'View agent tasks';
+ Image = Log;
+
+ trigger OnAction()
+ var
+ AgentTask: Record "Agent Task";
+ begin
+ AgentTask.SetRange("Agent User Security ID", Rec."User Security ID");
+ Page.Run(Page::"Agent Task List", AgentTask);
+ end;
+ }
+ }
+ area(Promoted)
+ {
+ group(Category_Process)
+ {
+ actionref(AgentSetup_Promoted; AgentSetup)
+ {
+ }
+ actionref(UserSettings_Promoted; UserSettingsAction)
+ {
+ }
+ actionref(AgentTasks_Promoted; AgentTasks)
+ {
+ }
+ }
+ }
+ }
+
+ local procedure UpdateControls()
+ var
+ AgentImpl: Codeunit "Agent Impl.";
+ UserSettings: Codeunit "User Settings";
+ begin
+ if not IsNullGuid(Rec."User Security ID") then begin
+ UserSettings.GetUserSettings(Rec."User Security ID", UserSettingsRecord);
+ ProfileDisplayName := AgentImpl.GetProfileName(UserSettingsRecord.Scope, UserSettingsRecord."App ID", UserSettingsRecord."Profile ID");
+ end;
+
+ ControlsEditable := Rec.State = Rec.State::Disabled;
+ end;
+
+ local procedure ChangeState()
+ var
+ ConfirmOpenSetupPage: Boolean;
+ begin
+ if Rec."Setup Page ID" = 0 then
+ exit;
+
+ if Rec.State = Rec.State::Disabled then
+ exit;
+
+ ConfirmOpenSetupPage := false;
+
+ if GuiAllowed() then
+ ConfirmOpenSetupPage := Confirm(OpenConfigurationPageQst);
+
+ if not ConfirmOpenSetupPage then
+ Error(YouCannotEnableAgentWithoutUsingConfigurationPageErr);
+
+ Rec.Find();
+ OpenSetupPage();
+ end;
+
+ trigger OnAfterGetCurrRecord()
+ begin
+ UpdateControls();
+ end;
+
+ local procedure OpenSetupPage()
+ var
+ TempAgent: Record Agent temporary;
+ begin
+ TempAgent.Copy(Rec);
+ TempAgent.Insert();
+ Page.RunModal(Rec."Setup Page ID", TempAgent);
+ CurrPage.Update(false);
+ end;
+
+
+ var
+ UserSettingsRecord: Record "User Settings";
+ ProfileDisplayName: Text;
+ ControlsEditable: Boolean;
+ OpenConfigurationPageQst: Label 'To activate the agent, use the setup page. Would you like to open this page now?';
+ YouCannotEnableAgentWithoutUsingConfigurationPageErr: Label 'You can''t activate the agent from this page. Use the action to set up and activate the agent.';
+}
\ 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
new file mode 100644
index 0000000000..8682edfa92
--- /dev/null
+++ b/src/System Application/App/Agent/Setup/AgentImpl.Codeunit.al
@@ -0,0 +1,429 @@
+// ------------------------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+// ------------------------------------------------------------------------------------------------
+
+namespace System.Agents;
+
+using System.Environment.Configuration;
+using System.Reflection;
+using System.Environment;
+using System.Security.AccessControl;
+
+codeunit 4301 "Agent Impl."
+{
+ Access = Internal;
+ InherentEntitlements = X;
+ InherentPermissions = X;
+ Permissions = tabledata Agent = rim,
+ tabledata "All Profile" = r,
+ tabledata Company = r,
+ tabledata "Agent Access Control" = d,
+ tabledata "Application User Settings" = rim,
+ tabledata User = r,
+ tabledata "User Personalization" = rim;
+
+ internal procedure CreateAgent(AgentMetadataProvider: Enum "Agent Metadata Provider"; AgentUserName: Code[50]; AgentUserDisplayName: Text[80]; var TempAgentAccessControl: Record "Agent Access Control" temporary): Guid
+ var
+ Agent: Record Agent;
+ begin
+ Agent."Agent Metadata Provider" := AgentMetadataProvider;
+ Agent."User Name" := AgentUserName;
+ Agent."Display Name" := AgentUserDisplayName;
+ Agent.Insert(true);
+
+ if TempAgentAccessControl.IsEmpty() then
+ GetUserAccess(Agent, TempAgentAccessControl, true);
+
+ AssignCompany(Agent."User Security ID", CompanyName());
+ UpdateAgentAccessControl(TempAgentAccessControl, Agent);
+
+ exit(Agent."User Security ID");
+ end;
+
+ internal procedure Activate(AgentUserSecurityID: Guid)
+ begin
+ ChangeAgentState(AgentUserSecurityID, true);
+ end;
+
+ internal procedure Deactivate(AgentUserSecurityID: Guid)
+ begin
+ ChangeAgentState(AgentUserSecurityID, false);
+ end;
+
+ [NonDebuggable]
+ internal procedure SetInstructions(AgentUserSecurityID: Guid; Instructions: SecretText)
+ var
+ Agent: Record Agent;
+ InstructionsOutStream: OutStream;
+ begin
+ Agent.Get(AgentUserSecurityID);
+ Clear(Agent.Instructions);
+ Agent.Instructions.CreateOutStream(InstructionsOutStream, GetDefaultEncoding());
+ InstructionsOutStream.Write(Instructions.Unwrap());
+ Agent.Modify(true);
+ end;
+
+ internal procedure GetInstructions(var Agent: Record Agent): Text
+ var
+ InstructionsInStream: InStream;
+ InstructionsText: Text;
+ begin
+ if IsNullGuid(Agent."User Security ID") then
+ exit;
+
+ Agent.CalcFields(Instructions);
+ if not Agent.Instructions.HasValue() then
+ exit('');
+
+ Agent.Instructions.CreateInStream(InstructionsInStream, GetDefaultEncoding());
+ InstructionsInStream.Read(InstructionsText);
+ exit(InstructionsText);
+ end;
+
+ internal procedure InsertCurrentOwnerIfNoOwnersDefined(var Agent: Record Agent; var AgentAccessControl: Record "Agent Access Control")
+ begin
+ SetOwnerFilters(AgentAccessControl);
+ AgentAccessControl.SetRange("Agent User Security ID", Agent."User Security ID");
+ if not AgentAccessControl.IsEmpty() then
+ exit;
+ InsertCurrentOwner(Agent."User Security ID", AgentAccessControl);
+ end;
+
+ internal procedure InsertCurrentOwner(AgentUserSecurityID: Guid; var AgentAccessControl: Record "Agent Access Control")
+ begin
+ AgentAccessControl."Can Configure Agent" := true;
+ AgentAccessControl."Agent User Security ID" := AgentUserSecurityID;
+ AgentAccessControl."User Security ID" := UserSecurityId();
+ AgentAccessControl.Insert();
+ end;
+
+ internal procedure VerifyOwnerExists(AgentAccessControlModified: Record "Agent Access Control")
+ var
+ ExistingAgentAccessControl: Record "Agent Access Control";
+ begin
+ if (AgentAccessControlModified."Can Configure Agent") then
+ exit;
+
+ SetOwnerFilters(ExistingAgentAccessControl);
+ ExistingAgentAccessControl.SetFilter("User Security ID", '<>%1', AgentAccessControlModified."User Security ID");
+ ExistingAgentAccessControl.SetRange("Agent User Security ID", AgentAccessControlModified."Agent User Security ID");
+
+ if ExistingAgentAccessControl.IsEmpty() then
+ Error(OneOwnerMustBeDefinedForAgentErr);
+ end;
+
+ internal procedure GetUserAccess(AgentUserSecurityID: Guid; var TempAgentAccessControl: Record "Agent Access Control" temporary)
+ var
+ Agent: Record Agent;
+ begin
+ GetAgent(Agent, AgentUserSecurityID);
+
+ GetUserAccess(Agent, TempAgentAccessControl, false);
+ end;
+
+ local procedure GetUserAccess(var Agent: Record Agent; var TempAgentAccessControl: Record "Agent Access Control" temporary; InsertCurrentUserAsOwner: Boolean)
+ var
+ AgentAccessControl: Record "Agent Access Control";
+ begin
+ TempAgentAccessControl.Reset();
+ TempAgentAccessControl.DeleteAll();
+
+ AgentAccessControl.SetRange("Agent User Security ID", Agent."User Security ID");
+ if AgentAccessControl.IsEmpty() then begin
+ if not InsertCurrentUserAsOwner then
+ exit;
+
+ InsertCurrentOwnerIfNoOwnersDefined(Agent, TempAgentAccessControl);
+ exit;
+ end;
+
+ AgentAccessControl.FindSet();
+ repeat
+ TempAgentAccessControl.Copy(AgentAccessControl);
+ TempAgentAccessControl.Insert();
+ until AgentAccessControl.Next() = 0;
+ end;
+
+ internal procedure SetProfile(AgentUserSecurityID: Guid; var AllProfile: Record "All Profile")
+ var
+ Agent: Record Agent;
+ UserSettingsRecord: Record "User Settings";
+ UserSettings: Codeunit "User Settings";
+ begin
+ GetAgent(Agent, AgentUserSecurityID);
+
+ UserSettings.GetUserSettings(Agent."User Security ID", UserSettingsRecord);
+ UpdateProfile(AllProfile, UserSettingsRecord);
+ UpdateAgentUserSettings(UserSettingsRecord);
+ end;
+
+ internal procedure AssignCompany(AgentUserSecurityID: Guid; CompanyName: Text)
+ var
+ Agent: Record Agent;
+ UserSettingsRecord: Record "User Settings";
+ UserSettings: Codeunit "User Settings";
+ begin
+ GetAgent(Agent, AgentUserSecurityID);
+
+ UserSettings.GetUserSettings(Agent."User Security ID", UserSettingsRecord);
+#pragma warning disable AA0139
+ UserSettingsRecord.Company := CompanyName();
+#pragma warning restore AA0139
+ UpdateAgentUserSettings(UserSettingsRecord);
+ end;
+
+ internal procedure GetUserName(AgentUserSecurityID: Guid): Code[50]
+ var
+ Agent: Record Agent;
+ begin
+ GetAgent(Agent, AgentUserSecurityID);
+
+ exit(Agent."User Name");
+ end;
+
+ internal procedure GetDisplayName(AgentUserSecurityID: Guid): Text[80]
+ var
+ Agent: Record Agent;
+ begin
+ GetAgent(Agent, AgentUserSecurityID);
+
+ exit(Agent."Display Name")
+ end;
+
+ internal procedure SetDisplayName(AgentUserSecurityID: Guid; DisplayName: Text[80])
+ var
+ Agent: Record Agent;
+ begin
+ GetAgent(Agent, AgentUserSecurityID);
+
+ Agent."Display Name" := DisplayName;
+ Agent.Modify(true);
+ end;
+
+ internal procedure IsActive(AgentUserSecurityID: Guid): Boolean
+ var
+ Agent: Record Agent;
+ begin
+ GetAgent(Agent, AgentUserSecurityID);
+
+ exit(Agent.State = Agent.State::Enabled);
+ end;
+
+ internal procedure UpdateAgentAccessControl(AgentUserSecurityID: Guid; var TempAgentAccessControl: Record "Agent Access Control" temporary)
+ var
+ Agent: Record Agent;
+ begin
+ if not Agent.Get(AgentUserSecurityID) then
+ Error(AgentDoesNotExistErr);
+
+ UpdateAgentAccessControl(TempAgentAccessControl, Agent);
+ end;
+
+ # Region TODO: Update System App signatures to use the codeunit 9175 "User Settings Impl."
+ internal procedure UpdateAgentUserSettings(NewUserSettings: Record "User Settings")
+ var
+ UserPersonalization: Record "User Personalization";
+ begin
+ UserPersonalization.Get(NewUserSettings."User Security ID");
+
+ UserPersonalization."Language ID" := NewUserSettings."Language ID";
+ UserPersonalization."Locale ID" := NewUserSettings."Locale ID";
+ UserPersonalization.Company := NewUserSettings.Company;
+ UserPersonalization."Time Zone" := NewUserSettings."Time Zone";
+ UserPersonalization."Profile ID" := NewUserSettings."Profile ID";
+#pragma warning disable AL0432 // All profiles are now in the tenant scope
+ UserPersonalization.Scope := NewUserSettings.Scope;
+#pragma warning restore AL0432
+ UserPersonalization."App ID" := NewUserSettings."App ID";
+ UserPersonalization.Modify();
+ end;
+
+ procedure ProfileLookup(var UserSettingsRec: Record "User Settings"): Boolean
+ var
+ TempAllProfile: Record "All Profile" temporary;
+ begin
+ PopulateProfiles(TempAllProfile);
+
+ if TempAllProfile.Get(UserSettingsRec.Scope, UserSettingsRec."App ID", UserSettingsRec."Profile ID") then;
+ if Page.RunModal(Page::Roles, TempAllProfile) = Action::LookupOK then begin
+ UpdateProfile(TempAllProfile, UserSettingsRec);
+ exit(true);
+ end;
+ exit(false);
+ end;
+
+ internal procedure UpdateProfile(var TempAllProfile: Record "All Profile" temporary; var UserSettingsRec: Record "User Settings")
+ begin
+ UserSettingsRec."Profile ID" := TempAllProfile."Profile ID";
+ UserSettingsRec."App ID" := TempAllProfile."App ID";
+ UserSettingsRec.Scope := TempAllProfile.Scope;
+ end;
+
+ procedure PopulateProfiles(var TempAllProfile: Record "All Profile" temporary)
+ var
+ AllProfile: Record "All Profile";
+ DescriptionFilterTxt: Label 'Navigation menu only.';
+ UserCreatedAppNameTxt: Label '(User-created)';
+ begin
+ TempAllProfile.Reset();
+ TempAllProfile.DeleteAll();
+ AllProfile.SetRange(Enabled, true);
+ AllProfile.SetFilter(Description, '<> %1', DescriptionFilterTxt);
+ if AllProfile.FindSet() then
+ repeat
+ TempAllProfile := AllProfile;
+ if IsNullGuid(TempAllProfile."App ID") then
+ TempAllProfile."App Name" := UserCreatedAppNameTxt;
+ TempAllProfile.Insert();
+ until AllProfile.Next() = 0;
+ end;
+
+ procedure GetProfileName(Scope: Option System,Tenant; AppID: Guid; ProfileID: Code[30]) ProfileName: Text
+ var
+ AllProfile: Record "All Profile";
+ begin
+ // If current profile has been changed, then find it and update the description; else, get the default
+ if not AllProfile.Get(Scope, AppID, ProfileID) then
+ exit;
+
+ ProfileName := AllProfile.Caption;
+ end;
+
+ internal procedure AssignPermissionSets(var UserSID: Guid; PermissionCompanyName: Text; var AggregatePermissionSet: Record "Aggregate Permission Set")
+ var
+ AccessControl: Record "Access Control";
+ begin
+ if not AggregatePermissionSet.FindSet() then
+ exit;
+
+ repeat
+ AccessControl."App ID" := AggregatePermissionSet."App ID";
+ AccessControl."User Security ID" := UserSID;
+ AccessControl."Role ID" := AggregatePermissionSet."Role ID";
+ AccessControl.Scope := AggregatePermissionSet.Scope;
+#pragma warning disable AA0139
+ AccessControl."Company Name" := PermissionCompanyName;
+#pragma warning restore AA0139
+ AccessControl.Insert();
+ until AggregatePermissionSet.Next() = 0;
+ end;
+ #endregion
+
+ local procedure GetAgent(var Agent: Record Agent; UserSecurityID: Guid)
+ begin
+ Agent.SetAutoCalcFields(Instructions);
+ if not Agent.Get(UserSecurityID) then
+ Error(AgentDoesNotExistErr);
+ end;
+
+ local procedure ChangeAgentState(UserSecurityID: Guid; Enabled: Boolean)
+ var
+ Agent: Record Agent;
+
+ begin
+ GetAgent(Agent, UserSecurityId);
+
+ if Enabled then
+ Agent.State := Agent.State::Enabled
+ else
+ Agent.State := Agent.State::Disabled;
+
+ Agent.Modify();
+ end;
+
+ local procedure UpdateAgentAccessControl(var TempAgentAccessControl: Record "Agent Access Control" temporary; var Agent: Record Agent)
+ begin
+ // We must delete or update the user doing the change the last to avoid removing permissions that are needed to commit the change
+ UpdateUsersOtherThanMainUser(TempAgentAccessControl, Agent);
+
+ // Update the user at the end
+ UpdateUserDoingTheChange(TempAgentAccessControl, Agent);
+ end;
+
+ local procedure UpdateUsersOtherThanMainUser(var TempAgentAccessControl: Record "Agent Access Control" temporary; var Agent: Record Agent)
+ var
+ AgentAccessControl: Record "Agent Access Control";
+ begin
+ AgentAccessControl.SetRange("Agent User Security ID", Agent."User Security ID");
+ AgentAccessControl.SetFilter("User Security ID", '<>%1', UserSecurityId());
+ if AgentAccessControl.FindSet() then
+ repeat
+ if not TempAgentAccessControl.Get(AgentAccessControl."Agent User Security ID", AgentAccessControl."User Security ID") then
+ AgentAccessControl.Delete(true);
+ until AgentAccessControl.Next() = 0;
+
+ AgentAccessControl.Reset();
+ TempAgentAccessControl.Reset();
+ TempAgentAccessControl.SetFilter("User Security ID", '<>%1', UserSecurityId());
+ if not TempAgentAccessControl.FindSet() then
+ exit;
+
+ repeat
+ if AgentAccessControl.Get(Agent."User Security ID", TempAgentAccessControl."User Security ID") then begin
+ AgentAccessControl.TransferFields(TempAgentAccessControl, true);
+ AgentAccessControl."Agent User Security ID" := Agent."User Security ID";
+ AgentAccessControl.Modify();
+ end else begin
+ AgentAccessControl.TransferFields(TempAgentAccessControl, true);
+ AgentAccessControl."Agent User Security ID" := Agent."User Security ID";
+ AgentAccessControl.Insert();
+ end;
+ until TempAgentAccessControl.Next() = 0;
+ end;
+
+ local procedure UpdateUserDoingTheChange(var TempAgentAccessControl: Record "Agent Access Control" temporary; var Agent: Record Agent)
+ var
+ AgentAccessControl: Record "Agent Access Control";
+ begin
+ TempAgentAccessControl.SetFilter("User Security ID", UserSecurityId());
+ if not TempAgentAccessControl.FindFirst() then begin
+ if AgentAccessControl.Get(Agent."User Security ID", UserSecurityId()) then
+ AgentAccessControl.Delete();
+
+ exit;
+ end;
+
+ if AgentAccessControl.Get(Agent."User Security ID", UserSecurityId()) then begin
+ AgentAccessControl.TransferFields(TempAgentAccessControl, true);
+ AgentAccessControl."Agent User Security ID" := Agent."User Security ID";
+ AgentAccessControl.Modify();
+ exit;
+ end else begin
+ AgentAccessControl.TransferFields(TempAgentAccessControl, true);
+ AgentAccessControl."Agent User Security ID" := Agent."User Security ID";
+ AgentAccessControl.Insert();
+ exit;
+ end;
+ end;
+
+ procedure SelectAgent(var Agent: Record "Agent")
+ begin
+ Agent.SetRange(State, Agent.State::Enabled);
+ if Agent.Count() = 0 then
+ Error(NoActiveAgentsErr);
+
+ if Agent.Count() = 1 then begin
+ Agent.FindFirst();
+ exit;
+ end;
+
+ if not (Page.RunModal(Page::"Agent List", Agent) in [Action::LookupOK, Action::OK]) then
+ Error('');
+ end;
+
+ local procedure SetOwnerFilters(var AgentAccessControl: Record "Agent Access Control")
+ begin
+ AgentAccessControl.SetFilter("Can Configure Agent", '%1', true);
+ end;
+
+ local procedure GetDefaultEncoding(): TextEncoding
+ begin
+ exit(TextEncoding::UTF8);
+ end;
+
+ var
+ OneOwnerMustBeDefinedForAgentErr: Label 'One owner must be defined for the agent.';
+ AgentDoesNotExistErr: Label 'Agent does not exist.';
+ NoActiveAgentsErr: Label 'There are no active agents setup on the system.';
+}
\ No newline at end of file
diff --git a/src/System Application/App/Agent/Setup/AgentList.Page.al b/src/System Application/App/Agent/Setup/AgentList.Page.al
new file mode 100644
index 0000000000..62bb45d7f6
--- /dev/null
+++ b/src/System Application/App/Agent/Setup/AgentList.Page.al
@@ -0,0 +1,93 @@
+// ------------------------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+// ------------------------------------------------------------------------------------------------
+
+namespace System.Agents;
+
+page 4316 "Agent List"
+{
+ PageType = List;
+ ApplicationArea = All;
+ UsageCategory = Administration;
+ SourceTable = "Agent";
+ Caption = 'Agents';
+ CardPageId = "Agent Card";
+ AdditionalSearchTerms = 'Agent, Agents, Copilot, Automation, AI';
+ Editable = false;
+ InsertAllowed = false;
+ DeleteAllowed = false;
+ InherentEntitlements = X;
+ InherentPermissions = X;
+
+ layout
+ {
+ area(Content)
+ {
+ repeater(Main)
+ {
+ field(UserName; Rec."User Name")
+ {
+ Caption = 'User Name';
+ }
+ field(DisplayName; Rec."Display Name")
+ {
+ Caption = 'Display Name';
+ }
+ field(State; Rec.State)
+ {
+ Caption = 'State';
+ }
+ }
+ }
+ }
+ actions
+ {
+ area(Processing)
+ {
+ action(AgentSetup)
+ {
+ ApplicationArea = Basic, Suite;
+ Caption = 'Setup';
+ ToolTip = 'Set up the agent';
+ Image = SetupLines;
+
+ trigger OnAction()
+ var
+ TempAgent: Record Agent temporary;
+ begin
+ TempAgent.Copy(Rec);
+ TempAgent.Insert();
+ Page.RunModal(Rec."Setup Page ID", TempAgent);
+ end;
+ }
+ action(AgentTasks)
+ {
+ ApplicationArea = All;
+ Caption = 'Agent Tasks';
+ ToolTip = 'View agent tasks';
+ Image = Log;
+
+ trigger OnAction()
+ var
+ AgentTask: Record "Agent Task";
+ begin
+ AgentTask.SetRange("Agent User Security ID", Rec."User Security ID");
+ Page.Run(Page::"Agent Task List", AgentTask);
+ end;
+ }
+ }
+ area(Promoted)
+ {
+ group(Category_Process)
+ {
+ actionref(AgentSetup_Promoted; AgentSetup)
+ {
+ }
+ actionref(AgentTasks_Promoted; AgentTasks)
+ {
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/System Application/App/Agent/Setup/SelectAgentAccessControl.Page.al b/src/System Application/App/Agent/Setup/SelectAgentAccessControl.Page.al
new file mode 100644
index 0000000000..5e2b143ecf
--- /dev/null
+++ b/src/System Application/App/Agent/Setup/SelectAgentAccessControl.Page.al
@@ -0,0 +1,179 @@
+// ------------------------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+// ------------------------------------------------------------------------------------------------
+
+namespace System.Agents;
+
+using System.Security.AccessControl;
+
+page 4321 "Select Agent Access Control"
+{
+ PageType = StandardDialog;
+ ApplicationArea = All;
+ SourceTable = "Agent Access Control";
+ SourceTableTemporary = true;
+ Caption = 'Select users to manage tasks and configure the agent';
+ MultipleNewLines = false;
+ Extensible = false;
+ DataCaptionExpression = '';
+ InherentEntitlements = X;
+ InherentPermissions = X;
+
+ layout
+ {
+ area(Content)
+ {
+ repeater(Main)
+ {
+ field(UserName; UserName)
+ {
+ Caption = 'User Name';
+ ToolTip = 'Specifies the name of the User that can access the agent.';
+ TableRelation = User;
+
+ trigger OnValidate()
+ begin
+ ValidateUserName(UserName);
+ CurrPage.Update(true);
+ end;
+ }
+ field(UserFullName; UserFullName)
+ {
+ Caption = 'User Full Name';
+ ToolTip = 'Specifies the Full Name of the User that can access the agent.';
+ Editable = false;
+ }
+ field(CanConfigureAgent; Rec."Can Configure Agent")
+ {
+ Caption = 'Can configure';
+ Tooltip = 'Specifies whether the user can configure the agent.';
+
+ trigger OnValidate()
+ begin
+ if not Rec."Can Configure Agent" then
+ VerifyOwnerExists();
+ end;
+ }
+ }
+ }
+ }
+
+ trigger OnAfterGetRecord()
+ begin
+ UpdateGlobalVariables();
+ end;
+
+ trigger OnAfterGetCurrRecord()
+ begin
+ UpdateGlobalVariables();
+ end;
+
+ trigger OnDeleteRecord(): Boolean
+ begin
+ VerifyOwnerExists();
+ end;
+
+ trigger OnOpenPage()
+ var
+ AgentImpl: Codeunit "Agent Impl.";
+ begin
+ if Rec.GetFilter("Agent User Security ID") <> '' then
+ Evaluate(AgentUserSecurityID, Rec.GetFilter("Agent User Security ID"));
+
+ if Rec.Count() = 0 then
+ AgentImpl.InsertCurrentOwner(Rec."Agent User Security ID", Rec);
+ end;
+
+ local procedure ValidateUserName(NewUserName: Text)
+ var
+ User: Record "User";
+ UserGuid: Guid;
+ begin
+ if Evaluate(UserGuid, NewUserName) then begin
+ User.Get(UserGuid);
+ UpdateUser(User."User Security ID");
+ UpdateGlobalVariables();
+ exit;
+ end;
+
+ User.SetRange("User Name", NewUserName);
+ if not User.FindFirst() then begin
+ User.SetFilter("User Name", '@*''''' + NewUserName + '''''*');
+ User.FindFirst();
+ end;
+
+ UpdateUser(User."User Security ID");
+ UpdateGlobalVariables();
+ end;
+
+ local procedure UpdateUser(NewUserID: Guid)
+ var
+ TempAgentAccessControl: Record "Agent Access Control" temporary;
+ RecordExists: Boolean;
+ begin
+ RecordExists := Rec.Find();
+
+ if RecordExists then begin
+ TempAgentAccessControl.Copy(Rec);
+ Rec.Delete();
+ Rec.Copy(TempAgentAccessControl);
+ end;
+
+ Rec."User Security ID" := NewUserID;
+ Rec."Agent User Security ID" := AgentUserSecurityID;
+ Rec.Insert(true);
+ VerifyOwnerExists();
+ end;
+
+ local procedure UpdateGlobalVariables()
+ var
+ User: Record "User";
+ begin
+ Clear(UserFullName);
+ Clear(UserName);
+
+ if IsNullGuid(Rec."User Security ID") then
+ exit;
+
+ if not User.Get(Rec."User Security ID") then
+ exit;
+
+ UserName := User."User Name";
+ UserFullName := User."Full Name";
+ end;
+
+ [Scope('OnPrem')]
+ procedure GetAgentUserAccess(var TempAgentAccessControl: Record "Agent Access Control" temporary)
+ begin
+ TempAgentAccessControl.Reset();
+ TempAgentAccessControl.DeleteAll();
+
+ if Rec.FindSet() then
+ repeat
+ TempAgentAccessControl.Copy(Rec);
+ TempAgentAccessControl.Insert();
+ until Rec.Next() = 0;
+ end;
+
+ local procedure VerifyOwnerExists()
+ var
+ TempAgentAccessControl: Record "Agent Access Control" temporary;
+ begin
+ TempAgentAccessControl.Copy(Rec);
+ Rec.SetFilter("Can Configure Agent", '%1', true);
+ Rec.SetFilter("User Security ID", '<>%1', Rec."User Security ID");
+ if Rec.IsEmpty() then begin
+ Rec.Copy(TempAgentAccessControl);
+ Error(OneOwnerMustBeDefinedForAgentErr);
+ end;
+
+ Rec.Copy(TempAgentAccessControl);
+ end;
+
+ var
+ UserFullName: Text[80];
+ UserName: Code[50];
+ AgentUserSecurityID: Guid;
+ OneOwnerMustBeDefinedForAgentErr: Label 'One owner must be defined for the agent.';
+}
\ No newline at end of file
diff --git a/src/System Application/App/Agent/TaskPane/TaskDetails.Page.al b/src/System Application/App/Agent/TaskPane/TaskDetails.Page.al
new file mode 100644
index 0000000000..de3f567f62
--- /dev/null
+++ b/src/System Application/App/Agent/TaskPane/TaskDetails.Page.al
@@ -0,0 +1,132 @@
+// ------------------------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+// ------------------------------------------------------------------------------------------------
+
+namespace System.Agents;
+
+page 4313 "Task Details"
+{
+ PageType = ListPart;
+ ApplicationArea = All;
+ SourceTable = "Agent Task Timeline Entry Step";
+ Caption = 'Agent Task Timeline Entry Step';
+ Editable = false;
+ InsertAllowed = false;
+ ModifyAllowed = false;
+ DeleteAllowed = false;
+ Extensible = false;
+ InherentEntitlements = X;
+ InherentPermissions = X;
+
+ layout
+ {
+ area(content)
+ {
+ repeater(Steps)
+ {
+ field(ClientContext; ClientContext)
+ {
+ Caption = 'Client Context';
+ ToolTip = 'Specifies the client context.';
+ }
+ }
+ }
+ }
+
+ actions
+ {
+ area(Processing)
+ {
+
+#pragma warning disable AW0005
+ action(Confirm)
+#pragma warning restore AW0005
+ {
+ Caption = 'Confirm';
+ ToolTip = 'Confirms the timeline entry.';
+
+ trigger OnAction()
+ begin
+ AddUserInterventionTaskStep();
+ end;
+ }
+#pragma warning disable AW0005
+ action(DiscardStep)
+#pragma warning restore AW0005
+ {
+ Caption = 'Discard step';
+ ToolTip = 'Discard the timeline entry.';
+ trigger OnAction()
+ begin
+ SkipStep();
+ end;
+ }
+ }
+ }
+
+ trigger OnAfterGetRecord()
+ begin
+ SetClientContext();
+ end;
+
+ local procedure SetClientContext()
+ var
+ InStream: InStream;
+ begin
+ // Clear old value
+ Clear(ClientContext);
+
+ if Rec.CalcFields("Client Context") then
+ if Rec."Client Context".HasValue() then begin
+ Rec."Client Context".CreateInStream(InStream);
+ ClientContext.Read(InStream);
+ end;
+ end;
+
+ local procedure AddUserInterventionTaskStep()
+ var
+ UserInterventionRequestStep: Record "Agent Task Step";
+ TaskTimelineEntry: Record "Agent Task Timeline Entry";
+ UserInput: Text;
+ begin
+ TaskTimelineEntry.SetRange("Task ID", Rec."Task ID");
+ TaskTimelineEntry.SetRange(ID, Rec."Timeline Entry ID");
+ TaskTimelineEntry.SetRange("Last Step Type", TaskTimelineEntry."Last Step Type"::"User Intervention Request");
+ if TaskTimelineEntry.FindLast() then begin
+ case TaskTimelineEntry."User Intervention Request Type" of
+ TaskTimelineEntry."User Intervention Request Type"::ReviewMessage:
+ UserInput := '';
+ else
+ UserInput := UserMessage; //ToDo: Will be implemented when we have a message field.
+ end;
+ if UserInterventionRequestStep.Get(TaskTimelineEntry."Task ID", TaskTimelineEntry."Last Step Number") then
+ AgentTaskImpl.CreateUserInterventionTaskStep(UserInterventionRequestStep, UserInput);
+ end;
+ end;
+
+ local procedure SkipStep()
+ var
+ TaskTimelineEntry: Record "Agent Task Timeline Entry";
+ AgentTaskMessage: Record "Agent Task Message";
+ begin
+
+ if not TaskTimelineEntry.Get(Rec."Task ID", Rec."Timeline Entry ID") then
+ exit;
+
+ case TaskTimelineEntry.Type of
+ TaskTimelineEntry.Type::OutputMessage:
+ if AgentTaskMessage.Get(TaskTimelineEntry."Primary Page Record ID") then begin
+ AgentTaskMessage.Status := AgentTaskMessage.Status::Discarded;
+ AgentTaskMessage.Modify(true);
+ end;
+ end;
+ end;
+
+ var
+ AgentTaskImpl: Codeunit "Agent Task Impl.";
+ ClientContext: BigText;
+ UserMessage: Text;
+}
+
+
diff --git a/src/System Application/App/Agent/TaskPane/TaskTimeline.Page.al b/src/System Application/App/Agent/TaskPane/TaskTimeline.Page.al
new file mode 100644
index 0000000000..ad7eb7fb1e
--- /dev/null
+++ b/src/System Application/App/Agent/TaskPane/TaskTimeline.Page.al
@@ -0,0 +1,270 @@
+// ------------------------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+// ------------------------------------------------------------------------------------------------
+
+namespace System.Agents;
+
+using System.Security.AccessControl;
+
+page 4307 "Task Timeline"
+{
+ PageType = ListPart;
+ ApplicationArea = All;
+ SourceTable = "Agent Task Timeline Entry";
+ Caption = 'Agent Task Timeline';
+ InsertAllowed = false;
+ DeleteAllowed = false;
+ Extensible = false;
+ InherentEntitlements = X;
+ InherentPermissions = X;
+
+ layout
+ {
+ area(content)
+ {
+ field(SelectedSuggestionId; SelectedSuggestionId)
+ {
+ Editable = true;
+ Caption = 'Selected Suggestion ID';
+ ToolTip = 'Specifies the selected suggestion ID for the user intervention request.';
+ }
+ repeater(TaskTimeline)
+ {
+ field(Header; Rec.Title)
+ {
+ Caption = 'Header';
+ ToolTip = 'Specifies the header of the timeline entry.';
+ }
+ field(Summary; GlobalPageSummary)
+ {
+ Caption = 'Summary';
+ ToolTip = 'Specifies the summary of the timeline entry.';
+ }
+ field(PrimaryPageQuery; GlobalPageQuery)
+ {
+ Caption = 'Primary Page Query';
+ ToolTip = 'Specifies the primary page query of the timeline entry.';
+ }
+ field(Description; GlobalDescription)
+ {
+ Caption = 'Description';
+ ToolTip = 'Specifies the description of the timeline entry.';
+ }
+ field(Category; Rec.Category)
+ {
+ }
+ field(Type; Rec.Type)
+ {
+ }
+ field(ConfirmationStatus; ConfirmationStatusOption)
+ {
+ Caption = 'Confirmation Status';
+ ToolTip = 'Specifies the confirmation status of the timeline entry.';
+ OptionCaption = ' ,ConfirmationNotRequired,ReviewConfirmationRequired,ReviewConfirmed,StopConfirmationRequired,StopConfirmed,Discarded';
+ }
+ field(ConfirmedBy; GlobalConfirmedBy)
+ {
+ Caption = 'Confirmed By';
+ ToolTip = 'Specifies the user who confirmed the timeline entry.';
+ }
+ field(ConfirmedAt; GlobalConfirmedAt)
+ {
+ Caption = 'Confirmed At';
+ ToolTip = 'Specifies the date and time when the timeline entry was confirmed.';
+ }
+ field(Annotations; GlobalAnnotations)
+ {
+ Caption = 'Annotations';
+ Tooltip = 'Specifies the annotations for the timeline entry, such as additional messages to surface to the user.';
+ }
+ field(Importance; Rec.Importance)
+ {
+ }
+ field(UserInterventionRequestType; Rec."User Intervention Request Type")
+ {
+ Caption = 'User Intervention Request Type';
+ ToolTip = 'Specifies the type of user intervention request when this entry is an intervention request.';
+ }
+ field(Suggestions; GlobalSuggestions)
+ {
+ Caption = 'Suggestions';
+ ToolTip = 'Specifies the suggestions for the user intervention request.';
+ }
+ field(CreatedAt; Rec.SystemCreatedAt)
+ {
+ Caption = 'First Step Created At';
+ ToolTip = 'Specifies the date and time when the timeline entry was created.';
+ }
+ }
+ }
+ }
+ actions
+ {
+ area(Processing)
+ {
+#pragma warning disable AW0005
+ action(Send)
+#pragma warning restore AW0005
+ {
+ Caption = 'Send';
+ ToolTip = 'Sends the selected instructions to the agent.';
+ Scope = Repeater;
+ trigger OnAction()
+ var
+ UserInterventionRequestStep: Record "Agent Task Step";
+ AgentTaskImpl: Codeunit "Agent Task Impl.";
+ SelectedSuggestionIdInt: Integer;
+ begin
+ if UserInterventionRequestStep.Get(Rec."Task ID", Rec."Last Step Number") then
+ if UserInterventionRequestStep.Type = "Agent Task Step Type"::"User Intervention Request" then
+ if Evaluate(SelectedSuggestionIdInt, SelectedSuggestionId) then
+ AgentTaskImpl.CreateUserInterventionTaskStep(UserInterventionRequestStep, SelectedSuggestionIdInt);
+ end;
+ }
+
+#pragma warning disable AW0005
+ action(Retry)
+#pragma warning restore AW0005
+ {
+ Caption = 'Retry';
+ ToolTip = 'Retries the task.';
+ Scope = Repeater;
+ trigger OnAction()
+ var
+ UserInterventionRequestStep: Record "Agent Task Step";
+ AgentTaskImpl: Codeunit "Agent Task Impl.";
+ begin
+ if UserInterventionRequestStep.Get(Rec."Task ID", Rec."Last Step Number") then
+ if UserInterventionRequestStep.Type = "Agent Task Step Type"::"User Intervention Request" then
+ AgentTaskImpl.CreateUserInterventionTaskStep(UserInterventionRequestStep);
+ end;
+
+ }
+ }
+ }
+
+ trigger OnOpenPage()
+ begin
+ SelectedSuggestionId := '';
+ Rec.SetRange(Importance, Rec.Importance::Primary);
+ end;
+
+ trigger OnAfterGetRecord()
+ begin
+ SetTaskTimelineDetails();
+ end;
+
+ local procedure SetTaskTimelineDetails()
+ var
+ InStream: InStream;
+ ConfirmationStepType: Enum "Agent Task Step Type";
+ StepNumber: Integer;
+ begin
+ // Clear old values
+ GlobalConfirmedBy := '';
+ GlobalConfirmedAt := 0DT;
+ Clear(GlobalPageSummary);
+ Clear(GlobalPageQuery);
+ Clear(GlobalAnnotations);
+ Clear(GlobalSuggestions);
+
+ GlobalDescription := Rec.Description;
+
+ if Rec.CalcFields("Primary Page Summary", "Primary Page Query", "Annotations") then begin
+ if Rec."Primary Page Summary".HasValue then begin
+ Rec."Primary Page Summary".CreateInStream(InStream, TextEncoding::UTF8);
+ GlobalPageSummary.Read(InStream);
+ Clear(InStream);
+ end;
+ if Rec."Primary Page Query".HasValue then begin
+ Rec."Primary Page Query".CreateInStream(InStream, TextEncoding::UTF8);
+ GlobalPageQuery.Read(InStream);
+ Clear(InStream);
+ end;
+ if Rec."Annotations".HasValue then begin
+ Rec."Annotations".CreateInStream(InStream, TextEncoding::UTF8);
+ GlobalAnnotations.Read(InStream);
+ Clear(InStream);
+ end;
+ end;
+
+ if Rec.Type = Rec.Type::UserInterventionRequest then begin
+ Rec.CalcFields("Suggestions");
+ if Rec.Suggestions.HasValue then begin
+ Rec.Suggestions.CreateInStream(InStream, TextEncoding::UTF8);
+ GlobalSuggestions.Read(InStream);
+ Clear(InStream);
+ end;
+ end;
+
+ ConfirmationStatusOption := ConfirmationStatusOption::ConfirmationNotRequired;
+ StepNumber := Rec."Last Step Number";
+ if (Rec."Last Step Type" <> "Agent Task Step Type"::Stop) and (Rec."Last User Intervention Step" > 0) then
+ StepNumber := Rec."Last User Intervention Step"
+ else
+ if Rec."Last Step Type" <> "Agent Task Step Type"::Stop then
+ // We know that there is no user intervention step for this timeline entry, and the last step is not a stop step.
+ exit;
+ if not TryGetConfirmationDetails(StepNumber, GlobalConfirmedBy, GlobalConfirmedAt, ConfirmationStepType) then
+ exit;
+
+ case
+ ConfirmationStepType of
+ "Agent Task Step Type"::"User Intervention Request":
+ ConfirmationStatusOption := ConfirmationStatusOption::ReviewConfirmationRequired;
+ "Agent Task Step Type"::"User Intervention":
+ ConfirmationStatusOption := ConfirmationStatusOption::ReviewConfirmed;
+ "Agent Task Step Type"::Stop:
+ ConfirmationStatusOption := ConfirmationStatusOption::StopConfirmed;
+ else
+ ConfirmationStatusOption := ConfirmationStatusOption::ConfirmationNotRequired;
+ end;
+ end;
+
+ local procedure TryGetConfirmationDetails(StepNumber: Integer; var By: Text[250]; var At: DateTime; var ConfirmationStepType: Enum "Agent Task Step Type"): Boolean
+ var
+ TaskTimelineEntryStep: Record "Agent Task Timeline Entry Step";
+ User: Record User;
+ begin
+ if StepNumber <= 0 then
+ exit(false);
+
+ TaskTimelineEntryStep.SetRange("Task ID", Rec."Task ID");
+ TaskTimelineEntryStep.SetRange("Timeline Entry ID", Rec.ID);
+ TaskTimelineEntryStep.SetRange("Step Number", StepNumber);
+ if not TaskTimelineEntryStep.FindLast() then
+ exit(false);
+
+ ConfirmationStepType := TaskTimelineEntryStep.Type;
+ if TaskTimelineEntryStep.Type = "Agent Task Step Type"::"User Intervention Request" then
+ exit(true);
+
+ if ((TaskTimelineEntryStep.Type <> "Agent Task Step Type"::"User Intervention") and
+ (TaskTimelineEntryStep.Type <> "Agent Task Step Type"::Stop)) then
+ exit(false);
+
+ User.SetRange("User Security ID", TaskTimelineEntryStep."User Security ID");
+ if User.FindFirst() then
+ if User."Full Name" <> '' then
+ By := User."Full Name"
+ else
+ By := User."User Name";
+
+ At := Rec.SystemModifiedAt;
+ exit(true);
+ end;
+
+ var
+ GlobalPageSummary: BigText;
+ GlobalPageQuery: BigText;
+ GlobalAnnotations: BigText;
+ GlobalSuggestions: BigText;
+ GlobalDescription: Text[2048];
+ GlobalConfirmedBy: Text[250];
+ GlobalConfirmedAt: DateTime;
+ ConfirmationStatusOption: Option " ",ConfirmationNotRequired,ReviewConfirmationRequired,ReviewConfirmed,StopConfirmationRequired,StopConfirmed,Discarded;
+ SelectedSuggestionId: Text[3];
+}
+
+
diff --git a/src/System Application/App/Agent/TaskPane/Tasks.Page.al b/src/System Application/App/Agent/TaskPane/Tasks.Page.al
new file mode 100644
index 0000000000..92ed3dbf1b
--- /dev/null
+++ b/src/System Application/App/Agent/TaskPane/Tasks.Page.al
@@ -0,0 +1,131 @@
+// ------------------------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+// ------------------------------------------------------------------------------------------------
+
+namespace System.Agents;
+
+page 4306 Tasks
+{
+ PageType = ListPlus;
+ ApplicationArea = All;
+ SourceTable = "Agent Task Pane Entry";
+ Caption = 'Agent Tasks';
+ Editable = true;
+ InsertAllowed = false;
+ DeleteAllowed = false;
+ Extensible = false;
+ SourceTableView = sorting("Last Step Timestamp") order(descending);
+ InherentEntitlements = X;
+ InherentPermissions = X;
+
+ layout
+ {
+ area(Content)
+ {
+ repeater(AgentTasks)
+ {
+ Editable = false;
+ field(TaskId; Rec."Task ID")
+ {
+ }
+ field(TaskNeedsAttention; Rec."Needs Attention")
+ {
+ }
+ field(TaskIndicator; Rec.Indicator)
+ {
+ }
+ field(TaskStatus; Rec.Status)
+ {
+ }
+ field(TaskHeader; Rec.Title)
+ {
+ Caption = 'Header';
+ ToolTip = 'Specifies the header of the task.';
+ }
+ field(TaskSummary; TaskSummary)
+ {
+ Caption = 'Summary';
+ ToolTip = 'Specifies the summary of the task.';
+ }
+ field(TaskStartedOn; Rec.SystemCreatedAt)
+ {
+ Caption = 'Started On';
+ ToolTip = 'Specifies the date and time when the task was started.';
+ }
+ field(TaskLastStepCompletedOn; Rec."Last Step Timestamp")
+ {
+ Caption = 'Last Step Completed On';
+ }
+ field(TaskStepType; Rec."Current Entry Type")
+ {
+ Caption = 'Step Type';
+ ToolTip = 'Specifies the type of the last step.';
+ }
+ }
+ }
+
+ area(FactBoxes)
+ {
+ part(Timeline; "Task Timeline")
+ {
+ SubPageLink = "Task ID" = field("Task ID");
+ UpdatePropagation = Both;
+ Editable = true;
+ }
+
+ part(Details; "Task Details")
+ {
+ Provider = Timeline;
+ SubPageLink = "Task ID" = field("Task ID"), "Timeline Entry ID" = field(ID);
+ Editable = true;
+ }
+ }
+ }
+ actions
+ {
+ area(Processing)
+ {
+#pragma warning disable AW0005
+ action(StopTask)
+#pragma warning restore AW0005
+ {
+ Caption = 'Stop task';
+ ToolTip = 'Stops the task.';
+ Enabled = true;
+ Scope = Repeater;
+ trigger OnAction()
+ var
+ AgentTask: Record "Agent Task";
+ AgentTaskImpl: Codeunit "Agent Task Impl.";
+ begin
+ AgentTask.Get(Rec."Task ID");
+ AgentTaskImpl.StopTask(AgentTask, AgentTask."Status"::"Stopped by User", false);
+ end;
+ }
+ }
+ }
+
+ trigger OnAfterGetRecord()
+ begin
+ SetTaskDetails();
+ end;
+
+ local procedure SetTaskDetails()
+ var
+ InStream: InStream;
+ begin
+ // Clear old values
+ Clear(TaskSummary);
+
+ Rec.CalcFields("Summary");
+ if Rec."Summary".HasValue() then begin
+ Rec."Summary".CreateInStream(InStream);
+ TaskSummary.Read(InStream);
+ end;
+ end;
+
+ var
+ TaskSummary: BigText;
+}
+
diff --git a/src/System Application/App/Agent/app.json b/src/System Application/App/Agent/app.json
new file mode 100644
index 0000000000..d7e8f3f883
--- /dev/null
+++ b/src/System Application/App/Agent/app.json
@@ -0,0 +1,48 @@
+{
+ "id": "95f7c0f8-d9b8-4538-80f9-6e17f6f7b91a",
+ "name": "Agent",
+ "publisher": "Microsoft",
+ "brief": "Enables managing of agents",
+ "description": "Provides functionality for setting up, enabling and disabling, interacting and auditing agents.",
+ "version": "26.0.0.0",
+ "privacyStatement": "https://go.microsoft.com/fwlink/?LinkId=724009",
+ "EULA": "https://go.microsoft.com/fwlink/?LinkId=847985",
+ "help": "https://go.microsoft.com/fwlink/?linkid=868966",
+ "url": "https://go.microsoft.com/fwlink/?LinkId=724011",
+ "contextSensitiveHelpUrl": "https://go.microsoft.com/fwlink/?linkid=868966",
+ "logo": "ExtensionLogo.png",
+ "screenshots": [],
+ "platform": "26.0.0.0",
+ "target": "OnPrem",
+ "idRanges": [
+ {
+ "from": 3000,
+ "to": 5000
+ }
+ ],
+ "dependencies": [
+ {
+ "id": "dd4b9f8a-b018-4f69-a614-efdb744c5330",
+ "name": "Page Summary Provider",
+ "publisher": "Microsoft",
+ "version": "26.0.0.0"
+ },
+ {
+ "id": "7b9b59f5-a68d-4271-b11a-0d3b9c0938dd",
+ "name": "User Settings",
+ "publisher": "Microsoft",
+ "version": "26.0.0.0"
+ },
+ {
+ "id": "c56e3ef4-7ab0-4636-ae87-013a62f12213",
+ "name": "User Permissions",
+ "publisher": "Microsoft",
+ "version": "26.0.0.0"
+ }
+ ],
+ "propagateDependencies": true,
+ "features": [
+ "TranslationFile",
+ "NoImplicitWith"
+ ]
+}
\ No newline at end of file
diff --git a/src/System Application/App/app.json b/src/System Application/App/app.json
index d75700c225..76e348f19a 100644
--- a/src/System Application/App/app.json
+++ b/src/System Application/App/app.json
@@ -21,6 +21,11 @@
"id": "9856ae4f-d1a7-46ef-89bb-6ef056398228",
"name": "System Application Test Library",
"publisher": "Microsoft"
+ },
+ {
+ "id": "1a3ac64b-0e25-2345-8e9b-eab2a74b9e9a",
+ "name": "Agent Developer Toolkit",
+ "publisher": "Microsoft"
}
],
"screenshots": [],