From 7e54fcba453707f6d579a1a6600081f6f3807e18 Mon Sep 17 00:00:00 2001 From: Sam <122809235+msft-sam@users.noreply.github.com> Date: Wed, 4 Dec 2024 12:44:28 +0100 Subject: [PATCH 1/4] [Copilot] Copilot capabilities can depend on privacy notices (#2443) #### Summary In addition to using AOAI, Copilot capabilities may need to integrate with other services that depend on a privacy notice. This PR adds a new event capabilities can subscribe to which provide a list of required privacy notices that must be accepted before the capability can be enabled. #### Work Item(s) Fixes [AB#502565](https://dynamicssmb2.visualstudio.com/1fcb79e7-ab07-432a-a3c6-6cf5a88ba4a5/_workitems/edit/502565) --- .../Copilot/CopilotCapEarlyPreview.Page.al | 10 ++++-- .../src/Copilot/CopilotCapabilitiesGA.Page.al | 11 ++++-- .../CopilotCapabilitiesPreview.Page.al | 11 ++++-- .../src/Copilot/CopilotCapability.Codeunit.al | 11 ++++++ .../Copilot/CopilotCapabilityImpl.Codeunit.al | 16 ++++++++- .../CopilotCapabilityInstall.Codeunit.al | 19 +++++++++++ .../AI/src/Copilot/CopilotSettings.Table.al | 34 +++++++++++++++++++ .../Privacy Notice/src/PrivacyNotices.Page.al | 1 + .../src/SystemPrivacyNoticeReg.Codeunit.al | 30 ++++++++++++---- 9 files changed, 127 insertions(+), 16 deletions(-) diff --git a/src/System Application/App/AI/src/Copilot/CopilotCapEarlyPreview.Page.al b/src/System Application/App/AI/src/Copilot/CopilotCapEarlyPreview.Page.al index 4d3c8eadd0..f322325fb9 100644 --- a/src/System Application/App/AI/src/Copilot/CopilotCapEarlyPreview.Page.al +++ b/src/System Application/App/AI/src/Copilot/CopilotCapEarlyPreview.Page.al @@ -35,7 +35,7 @@ page 7770 "Copilot Cap. Early Preview" Editable = false; Width = 30; } - field(Status; Rec.Status) + field(Status; Rec.EvaluateStatus()) { ApplicationArea = All; Caption = 'Status'; @@ -90,6 +90,8 @@ page 7770 "Copilot Cap. Early Preview" trigger OnAction() begin if Dialog.Confirm(ActivateEarlyPreviewTxt, false) then begin + if not Rec.EnsurePrivacyNoticesApproved() then + exit; Rec.Status := Rec.Status::Active; Rec.Modify(true); @@ -169,17 +171,19 @@ page 7770 "Copilot Cap. Early Preview" local procedure SetStatusStyle() begin - if (Rec.Status = Rec.Status::Active) then + if (Rec.EvaluateStatus() = Rec.Status::Active) then StatusStyleExpr := 'Favorable' else StatusStyleExpr := ''; end; local procedure SetActionsEnabled() + var + CopilotCapability: Codeunit "Copilot Capability"; begin if CopilotCapabilityImpl.IsAdmin() then begin ActionsEnabled := (Rec.Capability.AsInteger() <> 0) and DataMovementEnabled; - CapabilityEnabled := Rec.Status = Rec.Status::Active; + CapabilityEnabled := CopilotCapability.IsCapabilityActive(Rec.Capability, Rec."App Id"); end else begin ActionsEnabled := false; diff --git a/src/System Application/App/AI/src/Copilot/CopilotCapabilitiesGA.Page.al b/src/System Application/App/AI/src/Copilot/CopilotCapabilitiesGA.Page.al index c0fbbc521a..b76b8e4350 100644 --- a/src/System Application/App/AI/src/Copilot/CopilotCapabilitiesGA.Page.al +++ b/src/System Application/App/AI/src/Copilot/CopilotCapabilitiesGA.Page.al @@ -35,7 +35,7 @@ page 7774 "Copilot Capabilities GA" Editable = false; Width = 30; } - field(Status; Rec.Status) + field(Status; Rec.EvaluateStatus()) { ApplicationArea = All; Caption = 'Status'; @@ -89,6 +89,9 @@ page 7774 "Copilot Capabilities GA" trigger OnAction() begin + if not Rec.EnsurePrivacyNoticesApproved() then + exit; + Rec.Status := Rec.Status::Active; Rec.Modify(true); @@ -179,17 +182,19 @@ page 7774 "Copilot Capabilities GA" local procedure SetStatusStyle() begin - if (Rec.Status = Rec.Status::Active) then + if (Rec.EvaluateStatus() = Rec.Status::Active) then StatusStyleExpr := 'Favorable' else StatusStyleExpr := ''; end; local procedure SetActionsEnabled() + var + CopilotCapability: Codeunit "Copilot Capability"; begin if CopilotCapabilityImpl.IsAdmin() then begin ActionsEnabled := (Rec.Capability.AsInteger() <> 0) and DataMovementEnabled; - CapabilityEnabled := Rec.Status = Rec.Status::Active; + CapabilityEnabled := CopilotCapability.IsCapabilityActive(Rec.Capability, Rec."App Id"); end else begin ActionsEnabled := false; diff --git a/src/System Application/App/AI/src/Copilot/CopilotCapabilitiesPreview.Page.al b/src/System Application/App/AI/src/Copilot/CopilotCapabilitiesPreview.Page.al index dc3da9694c..38e90454e1 100644 --- a/src/System Application/App/AI/src/Copilot/CopilotCapabilitiesPreview.Page.al +++ b/src/System Application/App/AI/src/Copilot/CopilotCapabilitiesPreview.Page.al @@ -35,7 +35,7 @@ page 7773 "Copilot Capabilities Preview" Editable = false; Width = 30; } - field(Status; Rec.Status) + field(Status; Rec.EvaluateStatus()) { ApplicationArea = All; Caption = 'Status'; @@ -89,6 +89,9 @@ page 7773 "Copilot Capabilities Preview" trigger OnAction() begin + if not Rec.EnsurePrivacyNoticesApproved() then + exit; + Rec.Status := Rec.Status::Active; Rec.Modify(true); @@ -166,17 +169,19 @@ page 7773 "Copilot Capabilities Preview" local procedure SetStatusStyle() begin - if (Rec.Status = Rec.Status::Active) then + if (Rec.EvaluateStatus() = Rec.Status::Active) then StatusStyleExpr := 'Favorable' else StatusStyleExpr := ''; end; local procedure SetActionsEnabled() + var + CopilotCapability: Codeunit "Copilot Capability"; begin if CopilotCapabilityImpl.IsAdmin() then begin ActionsEnabled := (Rec.Capability.AsInteger() <> 0) and DataMovementEnabled; - CapabilityEnabled := Rec.Status = Rec.Status::Active; + CapabilityEnabled := CopilotCapability.IsCapabilityActive(Rec.Capability, Rec."App Id"); end else begin ActionsEnabled := false; diff --git a/src/System Application/App/AI/src/Copilot/CopilotCapability.Codeunit.al b/src/System Application/App/AI/src/Copilot/CopilotCapability.Codeunit.al index 5f4b18bec3..0f6215c940 100644 --- a/src/System Application/App/AI/src/Copilot/CopilotCapability.Codeunit.al +++ b/src/System Application/App/AI/src/Copilot/CopilotCapability.Codeunit.al @@ -118,4 +118,15 @@ codeunit 7773 "Copilot Capability" begin exit(CopilotCapabilityImpl.IsCapabilityActive(CopilotCapability, AppId)); end; + + /// + /// Event that is raised to specify if a Copilot capability depends on any additional privacy notices. + /// + /// The capability. + /// The app id associated with the capability. + /// The list of required privacy notices for the capability to be enabled. + [IntegrationEvent(false, false)] + procedure OnGetRequiredPrivacyNotices(CopilotCapability: Enum "Copilot Capability"; AppId: Guid; var RequiredPrivacyNotices: List of [Code[50]]) + begin + end; } \ No newline at end of file diff --git a/src/System Application/App/AI/src/Copilot/CopilotCapabilityImpl.Codeunit.al b/src/System Application/App/AI/src/Copilot/CopilotCapabilityImpl.Codeunit.al index 20f2117a1e..30504e7049 100644 --- a/src/System Application/App/AI/src/Copilot/CopilotCapabilityImpl.Codeunit.al +++ b/src/System Application/App/AI/src/Copilot/CopilotCapabilityImpl.Codeunit.al @@ -133,13 +133,27 @@ codeunit 7774 "Copilot Capability Impl" procedure IsCapabilityActive(CopilotCapability: Enum "Copilot Capability"; AppId: Guid): Boolean var CopilotSettings: Record "Copilot Settings"; + CopilotCapabilityCU: Codeunit "Copilot Capability"; + PrivacyNotice: Codeunit "Privacy Notice"; + RequiredPrivacyNotices: List of [Code[50]]; + RequiredPrivacyNotice: Code[50]; begin CopilotSettings.ReadIsolation(IsolationLevel::ReadCommitted); CopilotSettings.SetLoadFields(Status); if not CopilotSettings.Get(CopilotCapability, AppId) then exit(false); - exit(CopilotSettings.Status = Enum::"Copilot Status"::Active); + CopilotCapabilityCU.OnGetRequiredPrivacyNotices(CopilotCapability, AppId, RequiredPrivacyNotices); + + if (CopilotSettings.Status <> Enum::"Copilot Status"::Active) or (RequiredPrivacyNotices.Count() <= 0) then + exit(CopilotSettings.Status = Enum::"Copilot Status"::Active); + + // check privacy notices + foreach RequiredPrivacyNotice in RequiredPrivacyNotices do + if (PrivacyNotice.GetPrivacyNoticeApprovalState(RequiredPrivacyNotice, true) <> Enum::"Privacy Notice Approval State"::Agreed) then + exit(false); + + exit(true); end; procedure SendActivateTelemetry(CopilotCapability: Enum "Copilot Capability"; AppId: Guid) diff --git a/src/System Application/App/AI/src/Copilot/CopilotCapabilityInstall.Codeunit.al b/src/System Application/App/AI/src/Copilot/CopilotCapabilityInstall.Codeunit.al index 3e236a2e6e..3e8832d3c4 100644 --- a/src/System Application/App/AI/src/Copilot/CopilotCapabilityInstall.Codeunit.al +++ b/src/System Application/App/AI/src/Copilot/CopilotCapabilityInstall.Codeunit.al @@ -5,6 +5,7 @@ namespace System.AI; using System.Environment; +using System.Privacy; codeunit 7760 "Copilot Capability Install" { @@ -53,4 +54,22 @@ codeunit 7760 "Copilot Capability Install" begin RegisterCapabilities(); end; + + [EventSubscriber(ObjectType::Codeunit, Codeunit::"Copilot Capability", 'OnGetRequiredPrivacyNotices', '', false, false)] + local procedure OnGetRequiredPrivacyNotices(CopilotCapability: Enum "Copilot Capability"; AppId: Guid; var RequiredPrivacyNotices: List of [Code[50]]) + var + SystemPrivacyNoticeReg: Codeunit "System Privacy Notice Reg."; + ModuleInfo: ModuleInfo; + begin + NavApp.GetCurrentModuleInfo(ModuleInfo); + + if AppId <> ModuleInfo.Id then + exit; + + if CopilotCapability <> Enum::"Copilot Capability"::Chat then + exit; + + if not RequiredPrivacyNotices.Contains(SystemPrivacyNoticeReg.GetMicrosoftLearnID()) then + RequiredPrivacyNotices.Add(SystemPrivacyNoticeReg.GetMicrosoftLearnID()); + end; } \ No newline at end of file diff --git a/src/System Application/App/AI/src/Copilot/CopilotSettings.Table.al b/src/System Application/App/AI/src/Copilot/CopilotSettings.Table.al index 2f2644e30b..9ee6c06bfb 100644 --- a/src/System Application/App/AI/src/Copilot/CopilotSettings.Table.al +++ b/src/System Application/App/AI/src/Copilot/CopilotSettings.Table.al @@ -4,6 +4,8 @@ // ------------------------------------------------------------------------------------------------ namespace System.AI; +using System.Privacy; + /// /// Table to keep track of each Copilot Capability settings. /// @@ -51,4 +53,36 @@ table 7775 "Copilot Settings" Clustered = true; } } + + procedure EvaluateStatus(): Enum "Copilot Status" + var + CopilotCapability: Codeunit "Copilot Capability"; + begin + if Rec.Status <> Rec.Status::Active then + exit(Rec.Status); + + if CopilotCapability.IsCapabilityActive(Rec.Capability, Rec."App Id") then + exit(Rec.Status::Active) + else + exit(Rec.Status::Inactive); + end; + + procedure EnsurePrivacyNoticesApproved(): Boolean + var + CopilotCapability: Codeunit "Copilot Capability"; + PrivacyNotice: Codeunit "Privacy Notice"; + RequiredPrivacyNotices: List of [Code[50]]; + RequiredPrivacyNotice: Code[50]; + begin + CopilotCapability.OnGetRequiredPrivacyNotices(Rec.Capability, Rec."App Id", RequiredPrivacyNotices); + + if RequiredPrivacyNotices.Count() <= 0 then + exit(true); + + foreach RequiredPrivacyNotice in RequiredPrivacyNotices do + if not PrivacyNotice.ConfirmPrivacyNoticeApproval(RequiredPrivacyNotice, true) then + exit(false); + + exit(true); + end; } \ No newline at end of file diff --git a/src/System Application/App/Privacy Notice/src/PrivacyNotices.Page.al b/src/System Application/App/Privacy Notice/src/PrivacyNotices.Page.al index a6ded490cf..c951c4e682 100644 --- a/src/System Application/App/Privacy Notice/src/PrivacyNotices.Page.al +++ b/src/System Application/App/Privacy Notice/src/PrivacyNotices.Page.al @@ -159,6 +159,7 @@ page 1565 "Privacy Notices" trigger OnAfterGetRecord() begin + Rec.CalcFields(Rec.Enabled, Rec.Disabled); Accepted := Rec.Enabled; Rejected := Rec.Disabled; UserDecides := not (Accepted or Rejected); diff --git a/src/System Application/App/Privacy Notice/src/SystemPrivacyNoticeReg.Codeunit.al b/src/System Application/App/Privacy Notice/src/SystemPrivacyNoticeReg.Codeunit.al index 71d654afe8..7445673304 100644 --- a/src/System Application/App/Privacy Notice/src/SystemPrivacyNoticeReg.Codeunit.al +++ b/src/System Application/App/Privacy Notice/src/SystemPrivacyNoticeReg.Codeunit.al @@ -5,9 +5,11 @@ namespace System.Privacy; +/// +/// This codeunit registers platform level privacy notices and provides procedures to consistently reference the privacy notices. +/// codeunit 1566 "System Privacy Notice Reg." { - Access = Internal; InherentEntitlements = X; InherentPermissions = X; @@ -17,11 +19,6 @@ codeunit 1566 "System Privacy Notice Reg." PowerAutomateLabelTxt: Label 'Microsoft Power Automate', Locked = true; // Product names are not translated and it's important this entry exists. MicrosoftLearnTxt: Label 'Microsoft Learn', Locked = true; // Product names are not translated and it's important this entry exists. - procedure GetMicrosoftLearnID(): Text - begin - exit(MicrosoftLearnTxt); - end; - [EventSubscriber(ObjectType::Codeunit, Codeunit::"Privacy Notice", OnRegisterPrivacyNotices, '', false, false)] local procedure CreatePrivacyNoticeRegistrations(var TempPrivacyNotice: Record "Privacy Notice" temporary) begin @@ -37,16 +34,37 @@ codeunit 1566 "System Privacy Notice Reg." if not TempPrivacyNotice.Insert() then; end; + /// + /// Gets the Microsoft Learn privacy notice identifier. + /// + /// The privacy notice id for Microsoft Learn. + procedure GetMicrosoftLearnID(): Code[50] + begin + exit(MicrosoftLearnTxt); + end; + + /// + /// Gets the Microsoft Teams privacy notice identifier. + /// + /// The privacy notice id for Microsoft Teams. procedure GetTeamsPrivacyNoticeId(): Code[50] begin exit(MicrosoftTeamsTxt); end; + /// + /// Gets the Power Automate privacy notice identifier. + /// + /// The privacy notice id for Power Automate. procedure GetPowerAutomatePrivacyNoticeId(): Code[50] begin exit(PowerAutomateIdTxt); end; + /// + /// Gets the Power Automate privacy notice name. + /// + /// The privacy notice name for Power Automate. procedure GetPowerAutomatePrivacyNoticeName(): Code[250] begin exit(PowerAutomateLabelTxt); From f316f0df9d9f1d2bab89f3a73e1ae83580264171 Mon Sep 17 00:00:00 2001 From: bcbuild-github-agent <137281497+bcbuild-github-agent@users.noreply.github.com> Date: Wed, 4 Dec 2024 03:48:55 -0800 Subject: [PATCH 2/4] [main] Update AL-Go System Files from microsoft/AL-Go-PTE@preview - bbc88d84c6c206b79a63ce535897e97c394d7059 (#2447) ## preview Note that when using the preview version of AL-Go for GitHub, we recommend you Update your AL-Go system files, as soon as possible when informed that an update is available. ### Issues - Issue 1296 Make property "appFolders" optional Related to [AB#420000](https://dynamicssmb2.visualstudio.com/1fcb79e7-ab07-432a-a3c6-6cf5a88ba4a5/_workitems/edit/420000) Co-authored-by: bcbuild-github-agent --- .github/AL-Go-Settings.json | 2 +- .github/RELEASENOTES.copy.md | 8 ++++ .github/workflows/CICD.yaml | 40 +++++++++---------- .../DeployReferenceDocumentation.yaml | 10 ++--- .github/workflows/IncrementVersionNumber.yaml | 12 +++--- .github/workflows/PullRequestHandler.yaml | 14 +++---- .github/workflows/Troubleshooting.yaml | 2 +- .../workflows/UpdateGitHubGoSystemFiles.yaml | 12 +++--- .github/workflows/_BuildALGoProject.yaml | 18 ++++----- .../.AL-Go/cloudDevEnv.ps1 | 6 +-- .../.AL-Go/localDevEnv.ps1 | 6 +-- .../.AL-Go/cloudDevEnv.ps1 | 6 +-- .../.AL-Go/localDevEnv.ps1 | 6 +-- .../.AL-Go/cloudDevEnv.ps1 | 6 +-- .../.AL-Go/localDevEnv.ps1 | 6 +-- .../.AL-Go/cloudDevEnv.ps1 | 6 +-- .../.AL-Go/localDevEnv.ps1 | 6 +-- .../.AL-Go/cloudDevEnv.ps1 | 6 +-- .../.AL-Go/localDevEnv.ps1 | 6 +-- .../System Application/.AL-Go/cloudDevEnv.ps1 | 6 +-- .../System Application/.AL-Go/localDevEnv.ps1 | 6 +-- .../.AL-Go/cloudDevEnv.ps1 | 6 +-- .../.AL-Go/localDevEnv.ps1 | 6 +-- 23 files changed, 105 insertions(+), 97 deletions(-) diff --git a/.github/AL-Go-Settings.json b/.github/AL-Go-Settings.json index 648d69b8b8..a95eb0ebf4 100644 --- a/.github/AL-Go-Settings.json +++ b/.github/AL-Go-Settings.json @@ -73,5 +73,5 @@ ] }, "UpdateALGoSystemFilesEnvironment": "Official-Build", - "templateSha": "50903624159826257124c0f799a1b4add9b0260b" + "templateSha": "bbc88d84c6c206b79a63ce535897e97c394d7059" } diff --git a/.github/RELEASENOTES.copy.md b/.github/RELEASENOTES.copy.md index a7d36bd611..8fb684f578 100644 --- a/.github/RELEASENOTES.copy.md +++ b/.github/RELEASENOTES.copy.md @@ -1,3 +1,11 @@ +## preview + +Note that when using the preview version of AL-Go for GitHub, we recommend you Update your AL-Go system files, as soon as possible when informed that an update is available. + +### Issues + +- Issue 1296 Make property "appFolders" optional + ## v6.1 ### Issues diff --git a/.github/workflows/CICD.yaml b/.github/workflows/CICD.yaml index f1702fdf5c..e8b9907ce7 100644 --- a/.github/workflows/CICD.yaml +++ b/.github/workflows/CICD.yaml @@ -45,7 +45,7 @@ jobs: workflowDepth: ${{ steps.DetermineWorkflowDepth.outputs.WorkflowDepth }} steps: - name: Dump Workflow Information - uses: microsoft/AL-Go-Actions/DumpWorkflowInfo@v6.1 + uses: microsoft/AL-Go/Actions/DumpWorkflowInfo@9dbee17f636ca9ba2649b45adc2b65f4fd66af84 with: shell: powershell @@ -57,13 +57,13 @@ jobs: - name: Initialize the workflow id: init - uses: microsoft/AL-Go-Actions/WorkflowInitialize@v6.1 + uses: microsoft/AL-Go/Actions/WorkflowInitialize@9dbee17f636ca9ba2649b45adc2b65f4fd66af84 with: shell: powershell - name: Read settings id: ReadSettings - uses: microsoft/AL-Go-Actions/ReadSettings@v6.1 + uses: microsoft/AL-Go/Actions/ReadSettings@9dbee17f636ca9ba2649b45adc2b65f4fd66af84 with: shell: powershell get: type, powerPlatformSolutionFolder @@ -75,7 +75,7 @@ jobs: - name: Determine Projects To Build id: determineProjectsToBuild - uses: microsoft/AL-Go-Actions/DetermineProjectsToBuild@v6.1 + uses: microsoft/AL-Go/Actions/DetermineProjectsToBuild@9dbee17f636ca9ba2649b45adc2b65f4fd66af84 with: shell: powershell maxBuildDepth: ${{ env.workflowDepth }} @@ -88,7 +88,7 @@ jobs: - name: Determine Delivery Target Secrets id: DetermineDeliveryTargetSecrets - uses: microsoft/AL-Go-Actions/DetermineDeliveryTargets@v6.1 + uses: microsoft/AL-Go/Actions/DetermineDeliveryTargets@9dbee17f636ca9ba2649b45adc2b65f4fd66af84 with: shell: powershell projectsJson: '${{ steps.determineProjectsToBuild.outputs.ProjectsJson }}' @@ -96,7 +96,7 @@ jobs: - name: Read secrets id: ReadSecrets - uses: microsoft/AL-Go-Actions/ReadSecrets@v6.1 + uses: microsoft/AL-Go/Actions/ReadSecrets@9dbee17f636ca9ba2649b45adc2b65f4fd66af84 with: shell: powershell gitHubSecrets: ${{ toJson(secrets) }} @@ -104,7 +104,7 @@ jobs: - name: Determine Delivery Targets id: DetermineDeliveryTargets - uses: microsoft/AL-Go-Actions/DetermineDeliveryTargets@v6.1 + uses: microsoft/AL-Go/Actions/DetermineDeliveryTargets@9dbee17f636ca9ba2649b45adc2b65f4fd66af84 env: Secrets: '${{ steps.ReadSecrets.outputs.Secrets }}' with: @@ -114,7 +114,7 @@ jobs: - name: Determine Deployment Environments id: DetermineDeploymentEnvironments - uses: microsoft/AL-Go-Actions/DetermineDeploymentEnvironments@v6.1 + uses: microsoft/AL-Go/Actions/DetermineDeploymentEnvironments@9dbee17f636ca9ba2649b45adc2b65f4fd66af84 env: GITHUB_TOKEN: ${{ github.token }} with: @@ -130,13 +130,13 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Read settings - uses: microsoft/AL-Go-Actions/ReadSettings@v6.1 + uses: microsoft/AL-Go/Actions/ReadSettings@9dbee17f636ca9ba2649b45adc2b65f4fd66af84 with: shell: powershell get: templateUrl - name: Check for updates to AL-Go system files - uses: microsoft/AL-Go-Actions/CheckForUpdates@v6.1 + uses: microsoft/AL-Go/Actions/CheckForUpdates@9dbee17f636ca9ba2649b45adc2b65f4fd66af84 with: shell: powershell templateUrl: ${{ env.templateUrl }} @@ -211,7 +211,7 @@ jobs: path: '.artifacts' - name: Read settings - uses: microsoft/AL-Go-Actions/ReadSettings@v6.1 + uses: microsoft/AL-Go/Actions/ReadSettings@9dbee17f636ca9ba2649b45adc2b65f4fd66af84 with: shell: powershell @@ -220,7 +220,7 @@ jobs: uses: actions/configure-pages@983d7736d9b0ae728b81ab479565c72886d7745b # v5.0.0 - name: Build Reference Documentation - uses: microsoft/AL-Go-Actions/BuildReferenceDocumentation@v6.1 + uses: microsoft/AL-Go/Actions/BuildReferenceDocumentation@9dbee17f636ca9ba2649b45adc2b65f4fd66af84 with: shell: powershell artifacts: '.artifacts' @@ -257,7 +257,7 @@ jobs: path: '.artifacts' - name: Read settings - uses: microsoft/AL-Go-Actions/ReadSettings@v6.1 + uses: microsoft/AL-Go/Actions/ReadSettings@9dbee17f636ca9ba2649b45adc2b65f4fd66af84 with: shell: ${{ matrix.shell }} get: type,powerPlatformSolutionFolder @@ -271,7 +271,7 @@ jobs: - name: Read secrets id: ReadSecrets - uses: microsoft/AL-Go-Actions/ReadSecrets@v6.1 + uses: microsoft/AL-Go/Actions/ReadSecrets@9dbee17f636ca9ba2649b45adc2b65f4fd66af84 with: shell: ${{ matrix.shell }} gitHubSecrets: ${{ toJson(secrets) }} @@ -279,7 +279,7 @@ jobs: - name: Deploy to Business Central id: Deploy - uses: microsoft/AL-Go-Actions/Deploy@v6.1 + uses: microsoft/AL-Go/Actions/Deploy@9dbee17f636ca9ba2649b45adc2b65f4fd66af84 env: Secrets: '${{ steps.ReadSecrets.outputs.Secrets }}' with: @@ -291,7 +291,7 @@ jobs: - name: Deploy to Power Platform if: env.type == 'PTE' && env.powerPlatformSolutionFolder != '' - uses: microsoft/AL-Go-Actions/DeployPowerPlatform@v6.1 + uses: microsoft/AL-Go/Actions/DeployPowerPlatform@9dbee17f636ca9ba2649b45adc2b65f4fd66af84 env: Secrets: '${{ steps.ReadSecrets.outputs.Secrets }}' with: @@ -319,20 +319,20 @@ jobs: path: '.artifacts' - name: Read settings - uses: microsoft/AL-Go-Actions/ReadSettings@v6.1 + uses: microsoft/AL-Go/Actions/ReadSettings@9dbee17f636ca9ba2649b45adc2b65f4fd66af84 with: shell: powershell - name: Read secrets id: ReadSecrets - uses: microsoft/AL-Go-Actions/ReadSecrets@v6.1 + uses: microsoft/AL-Go/Actions/ReadSecrets@9dbee17f636ca9ba2649b45adc2b65f4fd66af84 with: shell: powershell gitHubSecrets: ${{ toJson(secrets) }} getSecrets: '${{ matrix.deliveryTarget }}Context' - name: Deliver - uses: microsoft/AL-Go-Actions/Deliver@v6.1 + uses: microsoft/AL-Go/Actions/Deliver@9dbee17f636ca9ba2649b45adc2b65f4fd66af84 env: Secrets: '${{ steps.ReadSecrets.outputs.Secrets }}' with: @@ -352,7 +352,7 @@ jobs: - name: Finalize the workflow id: PostProcess - uses: microsoft/AL-Go-Actions/WorkflowPostProcess@v6.1 + uses: microsoft/AL-Go/Actions/WorkflowPostProcess@9dbee17f636ca9ba2649b45adc2b65f4fd66af84 env: GITHUB_TOKEN: ${{ github.token }} with: diff --git a/.github/workflows/DeployReferenceDocumentation.yaml b/.github/workflows/DeployReferenceDocumentation.yaml index f194e65760..2a7c2f2820 100644 --- a/.github/workflows/DeployReferenceDocumentation.yaml +++ b/.github/workflows/DeployReferenceDocumentation.yaml @@ -30,18 +30,18 @@ jobs: - name: Initialize the workflow id: init - uses: microsoft/AL-Go-Actions/WorkflowInitialize@v6.1 + uses: microsoft/AL-Go/Actions/WorkflowInitialize@9dbee17f636ca9ba2649b45adc2b65f4fd66af84 with: shell: powershell - name: Read settings - uses: microsoft/AL-Go-Actions/ReadSettings@v6.1 + uses: microsoft/AL-Go/Actions/ReadSettings@9dbee17f636ca9ba2649b45adc2b65f4fd66af84 with: shell: powershell - name: Determine Deployment Environments id: DetermineDeploymentEnvironments - uses: microsoft/AL-Go-Actions/DetermineDeploymentEnvironments@v6.1 + uses: microsoft/AL-Go/Actions/DetermineDeploymentEnvironments@9dbee17f636ca9ba2649b45adc2b65f4fd66af84 env: GITHUB_TOKEN: ${{ github.token }} with: @@ -54,7 +54,7 @@ jobs: uses: actions/configure-pages@983d7736d9b0ae728b81ab479565c72886d7745b # v5.0.0 - name: Build Reference Documentation - uses: microsoft/AL-Go-Actions/BuildReferenceDocumentation@v6.1 + uses: microsoft/AL-Go/Actions/BuildReferenceDocumentation@9dbee17f636ca9ba2649b45adc2b65f4fd66af84 with: shell: powershell artifacts: 'latest' @@ -71,7 +71,7 @@ jobs: - name: Finalize the workflow if: always() - uses: microsoft/AL-Go-Actions/WorkflowPostProcess@v6.1 + uses: microsoft/AL-Go/Actions/WorkflowPostProcess@9dbee17f636ca9ba2649b45adc2b65f4fd66af84 env: GITHUB_TOKEN: ${{ github.token }} with: diff --git a/.github/workflows/IncrementVersionNumber.yaml b/.github/workflows/IncrementVersionNumber.yaml index 479fce3d05..0043d02541 100644 --- a/.github/workflows/IncrementVersionNumber.yaml +++ b/.github/workflows/IncrementVersionNumber.yaml @@ -41,7 +41,7 @@ jobs: runs-on: [ windows-latest ] steps: - name: Dump Workflow Information - uses: microsoft/AL-Go-Actions/DumpWorkflowInfo@v6.1 + uses: microsoft/AL-Go/Actions/DumpWorkflowInfo@9dbee17f636ca9ba2649b45adc2b65f4fd66af84 with: shell: powershell @@ -50,18 +50,18 @@ jobs: - name: Initialize the workflow id: init - uses: microsoft/AL-Go-Actions/WorkflowInitialize@v6.1 + uses: microsoft/AL-Go/Actions/WorkflowInitialize@9dbee17f636ca9ba2649b45adc2b65f4fd66af84 with: shell: powershell - name: Read settings - uses: microsoft/AL-Go-Actions/ReadSettings@v6.1 + uses: microsoft/AL-Go/Actions/ReadSettings@9dbee17f636ca9ba2649b45adc2b65f4fd66af84 with: shell: powershell - name: Read secrets id: ReadSecrets - uses: microsoft/AL-Go-Actions/ReadSecrets@v6.1 + uses: microsoft/AL-Go/Actions/ReadSecrets@9dbee17f636ca9ba2649b45adc2b65f4fd66af84 with: shell: powershell gitHubSecrets: ${{ toJson(secrets) }} @@ -69,7 +69,7 @@ jobs: useGhTokenWorkflowForPush: '${{ github.event.inputs.useGhTokenWorkflow }}' - name: Increment Version Number - uses: microsoft/AL-Go-Actions/IncrementVersionNumber@v6.1 + uses: microsoft/AL-Go/Actions/IncrementVersionNumber@9dbee17f636ca9ba2649b45adc2b65f4fd66af84 with: shell: powershell token: ${{ steps.ReadSecrets.outputs.TokenForPush }} @@ -79,7 +79,7 @@ jobs: - name: Finalize the workflow if: always() - uses: microsoft/AL-Go-Actions/WorkflowPostProcess@v6.1 + uses: microsoft/AL-Go/Actions/WorkflowPostProcess@9dbee17f636ca9ba2649b45adc2b65f4fd66af84 env: GITHUB_TOKEN: ${{ github.token }} with: diff --git a/.github/workflows/PullRequestHandler.yaml b/.github/workflows/PullRequestHandler.yaml index 695ae0b143..edd893058c 100644 --- a/.github/workflows/PullRequestHandler.yaml +++ b/.github/workflows/PullRequestHandler.yaml @@ -28,7 +28,7 @@ jobs: if: (github.event.pull_request.base.repo.full_name != github.event.pull_request.head.repo.full_name) && (github.event_name != 'pull_request') runs-on: windows-latest steps: - - uses: microsoft/AL-Go-Actions/VerifyPRChanges@v6.1 + - uses: microsoft/AL-Go/Actions/VerifyPRChanges@9dbee17f636ca9ba2649b45adc2b65f4fd66af84 Initialization: needs: [ PregateCheck ] @@ -43,7 +43,7 @@ jobs: telemetryScopeJson: ${{ steps.init.outputs.telemetryScopeJson }} steps: - name: Dump Workflow Information - uses: microsoft/AL-Go-Actions/DumpWorkflowInfo@v6.1 + uses: microsoft/AL-Go/Actions/DumpWorkflowInfo@9dbee17f636ca9ba2649b45adc2b65f4fd66af84 with: shell: powershell @@ -55,13 +55,13 @@ jobs: - name: Initialize the workflow id: init - uses: microsoft/AL-Go-Actions/WorkflowInitialize@v6.1 + uses: microsoft/AL-Go/Actions/WorkflowInitialize@9dbee17f636ca9ba2649b45adc2b65f4fd66af84 with: shell: powershell - name: Read settings id: ReadSettings - uses: microsoft/AL-Go-Actions/ReadSettings@v6.1 + uses: microsoft/AL-Go/Actions/ReadSettings@9dbee17f636ca9ba2649b45adc2b65f4fd66af84 with: shell: powershell @@ -72,7 +72,7 @@ jobs: - name: Determine Projects To Build id: determineProjectsToBuild - uses: microsoft/AL-Go-Actions/DetermineProjectsToBuild@v6.1 + uses: microsoft/AL-Go/Actions/DetermineProjectsToBuild@9dbee17f636ca9ba2649b45adc2b65f4fd66af84 with: shell: powershell maxBuildDepth: ${{ env.workflowDepth }} @@ -131,7 +131,7 @@ jobs: steps: - name: Pull Request Status Check id: PullRequestStatusCheck - uses: microsoft/AL-Go-Actions/PullRequestStatusCheck@v6.1 + uses: microsoft/AL-Go/Actions/PullRequestStatusCheck@9dbee17f636ca9ba2649b45adc2b65f4fd66af84 env: GITHUB_TOKEN: ${{ github.token }} with: @@ -139,7 +139,7 @@ jobs: - name: Finalize the workflow id: PostProcess - uses: microsoft/AL-Go-Actions/WorkflowPostProcess@v6.1 + uses: microsoft/AL-Go/Actions/WorkflowPostProcess@9dbee17f636ca9ba2649b45adc2b65f4fd66af84 if: success() || failure() env: GITHUB_TOKEN: ${{ github.token }} diff --git a/.github/workflows/Troubleshooting.yaml b/.github/workflows/Troubleshooting.yaml index 0337b67d1d..7f226aaded 100644 --- a/.github/workflows/Troubleshooting.yaml +++ b/.github/workflows/Troubleshooting.yaml @@ -30,7 +30,7 @@ jobs: lfs: true - name: Troubleshooting - uses: microsoft/AL-Go-Actions/Troubleshooting@v6.1 + uses: microsoft/AL-Go/Actions/Troubleshooting@9dbee17f636ca9ba2649b45adc2b65f4fd66af84 with: shell: powershell gitHubSecrets: ${{ toJson(secrets) }} diff --git a/.github/workflows/UpdateGitHubGoSystemFiles.yaml b/.github/workflows/UpdateGitHubGoSystemFiles.yaml index 6d57e5e6fa..e903ea509b 100644 --- a/.github/workflows/UpdateGitHubGoSystemFiles.yaml +++ b/.github/workflows/UpdateGitHubGoSystemFiles.yaml @@ -37,7 +37,7 @@ jobs: runs-on: [ windows-latest ] steps: - name: Dump Workflow Information - uses: microsoft/AL-Go-Actions/DumpWorkflowInfo@v6.1 + uses: microsoft/AL-Go/Actions/DumpWorkflowInfo@9dbee17f636ca9ba2649b45adc2b65f4fd66af84 with: shell: powershell @@ -46,19 +46,19 @@ jobs: - name: Initialize the workflow id: init - uses: microsoft/AL-Go-Actions/WorkflowInitialize@v6.1 + uses: microsoft/AL-Go/Actions/WorkflowInitialize@9dbee17f636ca9ba2649b45adc2b65f4fd66af84 with: shell: powershell - name: Read settings - uses: microsoft/AL-Go-Actions/ReadSettings@v6.1 + uses: microsoft/AL-Go/Actions/ReadSettings@9dbee17f636ca9ba2649b45adc2b65f4fd66af84 with: shell: powershell get: templateUrl - name: Read secrets id: ReadSecrets - uses: microsoft/AL-Go-Actions/ReadSecrets@v6.1 + uses: microsoft/AL-Go/Actions/ReadSecrets@9dbee17f636ca9ba2649b45adc2b65f4fd66af84 with: shell: powershell gitHubSecrets: ${{ toJson(secrets) }} @@ -94,7 +94,7 @@ jobs: Add-Content -Encoding UTF8 -Path $env:GITHUB_ENV -Value "downloadLatest=$downloadLatest" - name: Update AL-Go system files - uses: microsoft/AL-Go-Actions/CheckForUpdates@v6.1 + uses: microsoft/AL-Go/Actions/CheckForUpdates@9dbee17f636ca9ba2649b45adc2b65f4fd66af84 with: shell: powershell token: ${{ fromJson(steps.ReadSecrets.outputs.Secrets).ghTokenWorkflow }} @@ -105,7 +105,7 @@ jobs: - name: Finalize the workflow if: always() - uses: microsoft/AL-Go-Actions/WorkflowPostProcess@v6.1 + uses: microsoft/AL-Go/Actions/WorkflowPostProcess@9dbee17f636ca9ba2649b45adc2b65f4fd66af84 env: GITHUB_TOKEN: ${{ github.token }} with: diff --git a/.github/workflows/_BuildALGoProject.yaml b/.github/workflows/_BuildALGoProject.yaml index 8aec2e83f1..2b2aed1375 100644 --- a/.github/workflows/_BuildALGoProject.yaml +++ b/.github/workflows/_BuildALGoProject.yaml @@ -94,7 +94,7 @@ jobs: submodules: recursive - name: Read settings - uses: microsoft/AL-Go-Actions/ReadSettings@v6.1 + uses: microsoft/AL-Go/Actions/ReadSettings@9dbee17f636ca9ba2649b45adc2b65f4fd66af84 with: shell: ${{ inputs.shell }} project: ${{ inputs.project }} @@ -103,14 +103,14 @@ jobs: - name: Read secrets id: ReadSecrets if: github.event_name != 'pull_request' - uses: microsoft/AL-Go-Actions/ReadSecrets@v6.1 + uses: microsoft/AL-Go/Actions/ReadSecrets@9dbee17f636ca9ba2649b45adc2b65f4fd66af84 with: shell: ${{ inputs.shell }} gitHubSecrets: ${{ toJson(secrets) }} getSecrets: '${{ inputs.secrets }},appDependencySecrets,AZURE_CREDENTIALS' - name: Determine ArtifactUrl - uses: microsoft/AL-Go-Actions/DetermineArtifactUrl@v6.1 + uses: microsoft/AL-Go/Actions/DetermineArtifactUrl@9dbee17f636ca9ba2649b45adc2b65f4fd66af84 id: determineArtifactUrl with: shell: ${{ inputs.shell }} @@ -125,7 +125,7 @@ jobs: - name: Download Project Dependencies id: DownloadProjectDependencies - uses: microsoft/AL-Go-Actions/DownloadProjectDependencies@v6.1 + uses: microsoft/AL-Go/Actions/DownloadProjectDependencies@9dbee17f636ca9ba2649b45adc2b65f4fd66af84 env: Secrets: '${{ steps.ReadSecrets.outputs.Secrets }}' with: @@ -136,7 +136,7 @@ jobs: baselineWorkflowRunId: ${{ inputs.baselineWorkflowRunId }} - name: Build - uses: microsoft/AL-Go-Actions/RunPipeline@v6.1 + uses: microsoft/AL-Go/Actions/RunPipeline@9dbee17f636ca9ba2649b45adc2b65f4fd66af84 env: Secrets: '${{ steps.ReadSecrets.outputs.Secrets }}' BuildMode: ${{ inputs.buildMode }} @@ -151,7 +151,7 @@ jobs: - name: Sign if: inputs.signArtifacts && env.doNotSignApps == 'False' && (env.keyVaultCodesignCertificateName != '' || (fromJson(env.trustedSigning).Endpoint != '' && fromJson(env.trustedSigning).Account != '' && fromJson(env.trustedSigning).CertificateProfile != '')) id: sign - uses: microsoft/AL-Go-Actions/Sign@v6.1 + uses: microsoft/AL-Go/Actions/Sign@9dbee17f636ca9ba2649b45adc2b65f4fd66af84 with: shell: ${{ inputs.shell }} azureCredentialsJson: '${{ fromJson(steps.ReadSecrets.outputs.Secrets).AZURE_CREDENTIALS }}' @@ -159,7 +159,7 @@ jobs: - name: Calculate Artifact names id: calculateArtifactsNames - uses: microsoft/AL-Go-Actions/CalculateArtifactNames@v6.1 + uses: microsoft/AL-Go/Actions/CalculateArtifactNames@9dbee17f636ca9ba2649b45adc2b65f4fd66af84 if: success() || failure() with: shell: ${{ inputs.shell }} @@ -269,14 +269,14 @@ jobs: - name: Analyze Test Results id: analyzeTestResults if: (success() || failure()) && env.doNotRunTests == 'False' - uses: microsoft/AL-Go-Actions/AnalyzeTests@v6.1 + uses: microsoft/AL-Go/Actions/AnalyzeTests@9dbee17f636ca9ba2649b45adc2b65f4fd66af84 with: shell: ${{ inputs.shell }} project: ${{ inputs.project }} - name: Cleanup if: always() - uses: microsoft/AL-Go-Actions/PipelineCleanup@v6.1 + uses: microsoft/AL-Go/Actions/PipelineCleanup@9dbee17f636ca9ba2649b45adc2b65f4fd66af84 with: shell: ${{ inputs.shell }} project: ${{ inputs.project }} diff --git a/build/projects/Business Foundation/.AL-Go/cloudDevEnv.ps1 b/build/projects/Business Foundation/.AL-Go/cloudDevEnv.ps1 index 1afe6090dd..84dc46252f 100644 --- a/build/projects/Business Foundation/.AL-Go/cloudDevEnv.ps1 +++ b/build/projects/Business Foundation/.AL-Go/cloudDevEnv.ps1 @@ -42,9 +42,9 @@ Write-Host -ForegroundColor Yellow @' $tmpFolder = Join-Path ([System.IO.Path]::GetTempPath()) "$([Guid]::NewGuid().ToString())" New-Item -Path $tmpFolder -ItemType Directory -Force | Out-Null -$GitHubHelperPath = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/v6.1/Github-Helper.psm1' -folder $tmpFolder -$ALGoHelperPath = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/v6.1/AL-Go-Helper.ps1' -folder $tmpFolder -DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/v6.1/Packages.json' -folder $tmpFolder | Out-Null +$GitHubHelperPath = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go/9dbee17f636ca9ba2649b45adc2b65f4fd66af84/Actions/Github-Helper.psm1' -folder $tmpFolder +$ALGoHelperPath = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go/9dbee17f636ca9ba2649b45adc2b65f4fd66af84/Actions/AL-Go-Helper.ps1' -folder $tmpFolder +DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go/9dbee17f636ca9ba2649b45adc2b65f4fd66af84/Actions/Packages.json' -folder $tmpFolder | Out-Null Import-Module $GitHubHelperPath . $ALGoHelperPath -local diff --git a/build/projects/Business Foundation/.AL-Go/localDevEnv.ps1 b/build/projects/Business Foundation/.AL-Go/localDevEnv.ps1 index 358732d795..e1daa615a3 100644 --- a/build/projects/Business Foundation/.AL-Go/localDevEnv.ps1 +++ b/build/projects/Business Foundation/.AL-Go/localDevEnv.ps1 @@ -46,9 +46,9 @@ Write-Host -ForegroundColor Yellow @' $tmpFolder = Join-Path ([System.IO.Path]::GetTempPath()) "$([Guid]::NewGuid().ToString())" New-Item -Path $tmpFolder -ItemType Directory -Force | Out-Null -$GitHubHelperPath = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/v6.1/Github-Helper.psm1' -folder $tmpFolder -$ALGoHelperPath = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/v6.1/AL-Go-Helper.ps1' -folder $tmpFolder -DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/v6.1/Packages.json' -folder $tmpFolder | Out-Null +$GitHubHelperPath = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go/9dbee17f636ca9ba2649b45adc2b65f4fd66af84/Actions/Github-Helper.psm1' -folder $tmpFolder +$ALGoHelperPath = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go/9dbee17f636ca9ba2649b45adc2b65f4fd66af84/Actions/AL-Go-Helper.ps1' -folder $tmpFolder +DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go/9dbee17f636ca9ba2649b45adc2b65f4fd66af84/Actions/Packages.json' -folder $tmpFolder | Out-Null Import-Module $GitHubHelperPath . $ALGoHelperPath -local diff --git a/build/projects/Performance Toolkit/.AL-Go/cloudDevEnv.ps1 b/build/projects/Performance Toolkit/.AL-Go/cloudDevEnv.ps1 index 1afe6090dd..84dc46252f 100644 --- a/build/projects/Performance Toolkit/.AL-Go/cloudDevEnv.ps1 +++ b/build/projects/Performance Toolkit/.AL-Go/cloudDevEnv.ps1 @@ -42,9 +42,9 @@ Write-Host -ForegroundColor Yellow @' $tmpFolder = Join-Path ([System.IO.Path]::GetTempPath()) "$([Guid]::NewGuid().ToString())" New-Item -Path $tmpFolder -ItemType Directory -Force | Out-Null -$GitHubHelperPath = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/v6.1/Github-Helper.psm1' -folder $tmpFolder -$ALGoHelperPath = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/v6.1/AL-Go-Helper.ps1' -folder $tmpFolder -DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/v6.1/Packages.json' -folder $tmpFolder | Out-Null +$GitHubHelperPath = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go/9dbee17f636ca9ba2649b45adc2b65f4fd66af84/Actions/Github-Helper.psm1' -folder $tmpFolder +$ALGoHelperPath = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go/9dbee17f636ca9ba2649b45adc2b65f4fd66af84/Actions/AL-Go-Helper.ps1' -folder $tmpFolder +DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go/9dbee17f636ca9ba2649b45adc2b65f4fd66af84/Actions/Packages.json' -folder $tmpFolder | Out-Null Import-Module $GitHubHelperPath . $ALGoHelperPath -local diff --git a/build/projects/Performance Toolkit/.AL-Go/localDevEnv.ps1 b/build/projects/Performance Toolkit/.AL-Go/localDevEnv.ps1 index 358732d795..e1daa615a3 100644 --- a/build/projects/Performance Toolkit/.AL-Go/localDevEnv.ps1 +++ b/build/projects/Performance Toolkit/.AL-Go/localDevEnv.ps1 @@ -46,9 +46,9 @@ Write-Host -ForegroundColor Yellow @' $tmpFolder = Join-Path ([System.IO.Path]::GetTempPath()) "$([Guid]::NewGuid().ToString())" New-Item -Path $tmpFolder -ItemType Directory -Force | Out-Null -$GitHubHelperPath = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/v6.1/Github-Helper.psm1' -folder $tmpFolder -$ALGoHelperPath = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/v6.1/AL-Go-Helper.ps1' -folder $tmpFolder -DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/v6.1/Packages.json' -folder $tmpFolder | Out-Null +$GitHubHelperPath = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go/9dbee17f636ca9ba2649b45adc2b65f4fd66af84/Actions/Github-Helper.psm1' -folder $tmpFolder +$ALGoHelperPath = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go/9dbee17f636ca9ba2649b45adc2b65f4fd66af84/Actions/AL-Go-Helper.ps1' -folder $tmpFolder +DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go/9dbee17f636ca9ba2649b45adc2b65f4fd66af84/Actions/Packages.json' -folder $tmpFolder | Out-Null Import-Module $GitHubHelperPath . $ALGoHelperPath -local diff --git a/build/projects/System Application Modules/.AL-Go/cloudDevEnv.ps1 b/build/projects/System Application Modules/.AL-Go/cloudDevEnv.ps1 index 1afe6090dd..84dc46252f 100644 --- a/build/projects/System Application Modules/.AL-Go/cloudDevEnv.ps1 +++ b/build/projects/System Application Modules/.AL-Go/cloudDevEnv.ps1 @@ -42,9 +42,9 @@ Write-Host -ForegroundColor Yellow @' $tmpFolder = Join-Path ([System.IO.Path]::GetTempPath()) "$([Guid]::NewGuid().ToString())" New-Item -Path $tmpFolder -ItemType Directory -Force | Out-Null -$GitHubHelperPath = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/v6.1/Github-Helper.psm1' -folder $tmpFolder -$ALGoHelperPath = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/v6.1/AL-Go-Helper.ps1' -folder $tmpFolder -DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/v6.1/Packages.json' -folder $tmpFolder | Out-Null +$GitHubHelperPath = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go/9dbee17f636ca9ba2649b45adc2b65f4fd66af84/Actions/Github-Helper.psm1' -folder $tmpFolder +$ALGoHelperPath = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go/9dbee17f636ca9ba2649b45adc2b65f4fd66af84/Actions/AL-Go-Helper.ps1' -folder $tmpFolder +DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go/9dbee17f636ca9ba2649b45adc2b65f4fd66af84/Actions/Packages.json' -folder $tmpFolder | Out-Null Import-Module $GitHubHelperPath . $ALGoHelperPath -local diff --git a/build/projects/System Application Modules/.AL-Go/localDevEnv.ps1 b/build/projects/System Application Modules/.AL-Go/localDevEnv.ps1 index 358732d795..e1daa615a3 100644 --- a/build/projects/System Application Modules/.AL-Go/localDevEnv.ps1 +++ b/build/projects/System Application Modules/.AL-Go/localDevEnv.ps1 @@ -46,9 +46,9 @@ Write-Host -ForegroundColor Yellow @' $tmpFolder = Join-Path ([System.IO.Path]::GetTempPath()) "$([Guid]::NewGuid().ToString())" New-Item -Path $tmpFolder -ItemType Directory -Force | Out-Null -$GitHubHelperPath = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/v6.1/Github-Helper.psm1' -folder $tmpFolder -$ALGoHelperPath = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/v6.1/AL-Go-Helper.ps1' -folder $tmpFolder -DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/v6.1/Packages.json' -folder $tmpFolder | Out-Null +$GitHubHelperPath = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go/9dbee17f636ca9ba2649b45adc2b65f4fd66af84/Actions/Github-Helper.psm1' -folder $tmpFolder +$ALGoHelperPath = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go/9dbee17f636ca9ba2649b45adc2b65f4fd66af84/Actions/AL-Go-Helper.ps1' -folder $tmpFolder +DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go/9dbee17f636ca9ba2649b45adc2b65f4fd66af84/Actions/Packages.json' -folder $tmpFolder | Out-Null Import-Module $GitHubHelperPath . $ALGoHelperPath -local diff --git a/build/projects/System Application Tests (No Isolation)/.AL-Go/cloudDevEnv.ps1 b/build/projects/System Application Tests (No Isolation)/.AL-Go/cloudDevEnv.ps1 index 1afe6090dd..84dc46252f 100644 --- a/build/projects/System Application Tests (No Isolation)/.AL-Go/cloudDevEnv.ps1 +++ b/build/projects/System Application Tests (No Isolation)/.AL-Go/cloudDevEnv.ps1 @@ -42,9 +42,9 @@ Write-Host -ForegroundColor Yellow @' $tmpFolder = Join-Path ([System.IO.Path]::GetTempPath()) "$([Guid]::NewGuid().ToString())" New-Item -Path $tmpFolder -ItemType Directory -Force | Out-Null -$GitHubHelperPath = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/v6.1/Github-Helper.psm1' -folder $tmpFolder -$ALGoHelperPath = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/v6.1/AL-Go-Helper.ps1' -folder $tmpFolder -DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/v6.1/Packages.json' -folder $tmpFolder | Out-Null +$GitHubHelperPath = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go/9dbee17f636ca9ba2649b45adc2b65f4fd66af84/Actions/Github-Helper.psm1' -folder $tmpFolder +$ALGoHelperPath = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go/9dbee17f636ca9ba2649b45adc2b65f4fd66af84/Actions/AL-Go-Helper.ps1' -folder $tmpFolder +DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go/9dbee17f636ca9ba2649b45adc2b65f4fd66af84/Actions/Packages.json' -folder $tmpFolder | Out-Null Import-Module $GitHubHelperPath . $ALGoHelperPath -local diff --git a/build/projects/System Application Tests (No Isolation)/.AL-Go/localDevEnv.ps1 b/build/projects/System Application Tests (No Isolation)/.AL-Go/localDevEnv.ps1 index 358732d795..e1daa615a3 100644 --- a/build/projects/System Application Tests (No Isolation)/.AL-Go/localDevEnv.ps1 +++ b/build/projects/System Application Tests (No Isolation)/.AL-Go/localDevEnv.ps1 @@ -46,9 +46,9 @@ Write-Host -ForegroundColor Yellow @' $tmpFolder = Join-Path ([System.IO.Path]::GetTempPath()) "$([Guid]::NewGuid().ToString())" New-Item -Path $tmpFolder -ItemType Directory -Force | Out-Null -$GitHubHelperPath = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/v6.1/Github-Helper.psm1' -folder $tmpFolder -$ALGoHelperPath = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/v6.1/AL-Go-Helper.ps1' -folder $tmpFolder -DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/v6.1/Packages.json' -folder $tmpFolder | Out-Null +$GitHubHelperPath = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go/9dbee17f636ca9ba2649b45adc2b65f4fd66af84/Actions/Github-Helper.psm1' -folder $tmpFolder +$ALGoHelperPath = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go/9dbee17f636ca9ba2649b45adc2b65f4fd66af84/Actions/AL-Go-Helper.ps1' -folder $tmpFolder +DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go/9dbee17f636ca9ba2649b45adc2b65f4fd66af84/Actions/Packages.json' -folder $tmpFolder | Out-Null Import-Module $GitHubHelperPath . $ALGoHelperPath -local diff --git a/build/projects/System Application Tests/.AL-Go/cloudDevEnv.ps1 b/build/projects/System Application Tests/.AL-Go/cloudDevEnv.ps1 index 1afe6090dd..84dc46252f 100644 --- a/build/projects/System Application Tests/.AL-Go/cloudDevEnv.ps1 +++ b/build/projects/System Application Tests/.AL-Go/cloudDevEnv.ps1 @@ -42,9 +42,9 @@ Write-Host -ForegroundColor Yellow @' $tmpFolder = Join-Path ([System.IO.Path]::GetTempPath()) "$([Guid]::NewGuid().ToString())" New-Item -Path $tmpFolder -ItemType Directory -Force | Out-Null -$GitHubHelperPath = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/v6.1/Github-Helper.psm1' -folder $tmpFolder -$ALGoHelperPath = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/v6.1/AL-Go-Helper.ps1' -folder $tmpFolder -DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/v6.1/Packages.json' -folder $tmpFolder | Out-Null +$GitHubHelperPath = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go/9dbee17f636ca9ba2649b45adc2b65f4fd66af84/Actions/Github-Helper.psm1' -folder $tmpFolder +$ALGoHelperPath = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go/9dbee17f636ca9ba2649b45adc2b65f4fd66af84/Actions/AL-Go-Helper.ps1' -folder $tmpFolder +DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go/9dbee17f636ca9ba2649b45adc2b65f4fd66af84/Actions/Packages.json' -folder $tmpFolder | Out-Null Import-Module $GitHubHelperPath . $ALGoHelperPath -local diff --git a/build/projects/System Application Tests/.AL-Go/localDevEnv.ps1 b/build/projects/System Application Tests/.AL-Go/localDevEnv.ps1 index 358732d795..e1daa615a3 100644 --- a/build/projects/System Application Tests/.AL-Go/localDevEnv.ps1 +++ b/build/projects/System Application Tests/.AL-Go/localDevEnv.ps1 @@ -46,9 +46,9 @@ Write-Host -ForegroundColor Yellow @' $tmpFolder = Join-Path ([System.IO.Path]::GetTempPath()) "$([Guid]::NewGuid().ToString())" New-Item -Path $tmpFolder -ItemType Directory -Force | Out-Null -$GitHubHelperPath = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/v6.1/Github-Helper.psm1' -folder $tmpFolder -$ALGoHelperPath = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/v6.1/AL-Go-Helper.ps1' -folder $tmpFolder -DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/v6.1/Packages.json' -folder $tmpFolder | Out-Null +$GitHubHelperPath = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go/9dbee17f636ca9ba2649b45adc2b65f4fd66af84/Actions/Github-Helper.psm1' -folder $tmpFolder +$ALGoHelperPath = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go/9dbee17f636ca9ba2649b45adc2b65f4fd66af84/Actions/AL-Go-Helper.ps1' -folder $tmpFolder +DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go/9dbee17f636ca9ba2649b45adc2b65f4fd66af84/Actions/Packages.json' -folder $tmpFolder | Out-Null Import-Module $GitHubHelperPath . $ALGoHelperPath -local diff --git a/build/projects/System Application/.AL-Go/cloudDevEnv.ps1 b/build/projects/System Application/.AL-Go/cloudDevEnv.ps1 index 1afe6090dd..84dc46252f 100644 --- a/build/projects/System Application/.AL-Go/cloudDevEnv.ps1 +++ b/build/projects/System Application/.AL-Go/cloudDevEnv.ps1 @@ -42,9 +42,9 @@ Write-Host -ForegroundColor Yellow @' $tmpFolder = Join-Path ([System.IO.Path]::GetTempPath()) "$([Guid]::NewGuid().ToString())" New-Item -Path $tmpFolder -ItemType Directory -Force | Out-Null -$GitHubHelperPath = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/v6.1/Github-Helper.psm1' -folder $tmpFolder -$ALGoHelperPath = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/v6.1/AL-Go-Helper.ps1' -folder $tmpFolder -DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/v6.1/Packages.json' -folder $tmpFolder | Out-Null +$GitHubHelperPath = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go/9dbee17f636ca9ba2649b45adc2b65f4fd66af84/Actions/Github-Helper.psm1' -folder $tmpFolder +$ALGoHelperPath = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go/9dbee17f636ca9ba2649b45adc2b65f4fd66af84/Actions/AL-Go-Helper.ps1' -folder $tmpFolder +DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go/9dbee17f636ca9ba2649b45adc2b65f4fd66af84/Actions/Packages.json' -folder $tmpFolder | Out-Null Import-Module $GitHubHelperPath . $ALGoHelperPath -local diff --git a/build/projects/System Application/.AL-Go/localDevEnv.ps1 b/build/projects/System Application/.AL-Go/localDevEnv.ps1 index 358732d795..e1daa615a3 100644 --- a/build/projects/System Application/.AL-Go/localDevEnv.ps1 +++ b/build/projects/System Application/.AL-Go/localDevEnv.ps1 @@ -46,9 +46,9 @@ Write-Host -ForegroundColor Yellow @' $tmpFolder = Join-Path ([System.IO.Path]::GetTempPath()) "$([Guid]::NewGuid().ToString())" New-Item -Path $tmpFolder -ItemType Directory -Force | Out-Null -$GitHubHelperPath = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/v6.1/Github-Helper.psm1' -folder $tmpFolder -$ALGoHelperPath = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/v6.1/AL-Go-Helper.ps1' -folder $tmpFolder -DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/v6.1/Packages.json' -folder $tmpFolder | Out-Null +$GitHubHelperPath = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go/9dbee17f636ca9ba2649b45adc2b65f4fd66af84/Actions/Github-Helper.psm1' -folder $tmpFolder +$ALGoHelperPath = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go/9dbee17f636ca9ba2649b45adc2b65f4fd66af84/Actions/AL-Go-Helper.ps1' -folder $tmpFolder +DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go/9dbee17f636ca9ba2649b45adc2b65f4fd66af84/Actions/Packages.json' -folder $tmpFolder | Out-Null Import-Module $GitHubHelperPath . $ALGoHelperPath -local diff --git a/build/projects/Test Stability Tools/.AL-Go/cloudDevEnv.ps1 b/build/projects/Test Stability Tools/.AL-Go/cloudDevEnv.ps1 index 1afe6090dd..84dc46252f 100644 --- a/build/projects/Test Stability Tools/.AL-Go/cloudDevEnv.ps1 +++ b/build/projects/Test Stability Tools/.AL-Go/cloudDevEnv.ps1 @@ -42,9 +42,9 @@ Write-Host -ForegroundColor Yellow @' $tmpFolder = Join-Path ([System.IO.Path]::GetTempPath()) "$([Guid]::NewGuid().ToString())" New-Item -Path $tmpFolder -ItemType Directory -Force | Out-Null -$GitHubHelperPath = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/v6.1/Github-Helper.psm1' -folder $tmpFolder -$ALGoHelperPath = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/v6.1/AL-Go-Helper.ps1' -folder $tmpFolder -DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/v6.1/Packages.json' -folder $tmpFolder | Out-Null +$GitHubHelperPath = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go/9dbee17f636ca9ba2649b45adc2b65f4fd66af84/Actions/Github-Helper.psm1' -folder $tmpFolder +$ALGoHelperPath = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go/9dbee17f636ca9ba2649b45adc2b65f4fd66af84/Actions/AL-Go-Helper.ps1' -folder $tmpFolder +DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go/9dbee17f636ca9ba2649b45adc2b65f4fd66af84/Actions/Packages.json' -folder $tmpFolder | Out-Null Import-Module $GitHubHelperPath . $ALGoHelperPath -local diff --git a/build/projects/Test Stability Tools/.AL-Go/localDevEnv.ps1 b/build/projects/Test Stability Tools/.AL-Go/localDevEnv.ps1 index 358732d795..e1daa615a3 100644 --- a/build/projects/Test Stability Tools/.AL-Go/localDevEnv.ps1 +++ b/build/projects/Test Stability Tools/.AL-Go/localDevEnv.ps1 @@ -46,9 +46,9 @@ Write-Host -ForegroundColor Yellow @' $tmpFolder = Join-Path ([System.IO.Path]::GetTempPath()) "$([Guid]::NewGuid().ToString())" New-Item -Path $tmpFolder -ItemType Directory -Force | Out-Null -$GitHubHelperPath = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/v6.1/Github-Helper.psm1' -folder $tmpFolder -$ALGoHelperPath = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/v6.1/AL-Go-Helper.ps1' -folder $tmpFolder -DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/v6.1/Packages.json' -folder $tmpFolder | Out-Null +$GitHubHelperPath = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go/9dbee17f636ca9ba2649b45adc2b65f4fd66af84/Actions/Github-Helper.psm1' -folder $tmpFolder +$ALGoHelperPath = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go/9dbee17f636ca9ba2649b45adc2b65f4fd66af84/Actions/AL-Go-Helper.ps1' -folder $tmpFolder +DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go/9dbee17f636ca9ba2649b45adc2b65f4fd66af84/Actions/Packages.json' -folder $tmpFolder | Out-Null Import-Module $GitHubHelperPath . $ALGoHelperPath -local From 26af2eabb957ac9bc8242857b05733d423216368 Mon Sep 17 00:00:00 2001 From: Nikola Kukrika <39086991+nikolakukrika@users.noreply.github.com> Date: Wed, 4 Dec 2024 14:49:05 +0100 Subject: [PATCH 3/4] Agents - Introduce agent system app (#2459) #### Summary Introduce Agents System app #### Work Item(s) Fixes [AB#559557](https://dynamicssmb2.visualstudio.com/1fcb79e7-ab07-432a-a3c6-6cf5a88ba4a5/_workitems/edit/559557) --- .../App/Agent/ExtensionLogo.png | Bin 0 -> 2408 bytes .../Interaction/AgentMessage.Codeunit.al | 75 +++ .../Interaction/AgentMessageImpl.Codeunit.al | 80 ++++ .../Agent/Interaction/AgentTask.Codeunit.al | 41 ++ .../Interaction/AgentTaskImpl.Codeunit.al | 202 +++++++++ .../Agent/Interaction/AgentTaskList.Page.al | 187 ++++++++ .../Interaction/AgentTaskMessageCard.Page.al | 167 +++++++ .../Interaction/AgentTaskMessageList.Page.al | 95 ++++ .../Interaction/AgentTaskStepList.Page.al | 81 ++++ .../Permissions/AgentObjects.PermissionSet.al | 23 + .../App/Agent/Setup/Agent.Codeunit.al | 174 +++++++ .../Agent/Setup/AgentAccessControl.Page.al | 134 ++++++ .../App/Agent/Setup/AgentCard.Page.al | 228 ++++++++++ .../App/Agent/Setup/AgentImpl.Codeunit.al | 429 ++++++++++++++++++ .../App/Agent/Setup/AgentList.Page.al | 93 ++++ .../Setup/SelectAgentAccessControl.Page.al | 179 ++++++++ .../App/Agent/TaskPane/TaskDetails.Page.al | 132 ++++++ .../App/Agent/TaskPane/TaskTimeline.Page.al | 270 +++++++++++ .../App/Agent/TaskPane/Tasks.Page.al | 131 ++++++ src/System Application/App/Agent/app.json | 48 ++ src/System Application/App/app.json | 5 + 21 files changed, 2774 insertions(+) create mode 100644 src/System Application/App/Agent/ExtensionLogo.png create mode 100644 src/System Application/App/Agent/Interaction/AgentMessage.Codeunit.al create mode 100644 src/System Application/App/Agent/Interaction/AgentMessageImpl.Codeunit.al create mode 100644 src/System Application/App/Agent/Interaction/AgentTask.Codeunit.al create mode 100644 src/System Application/App/Agent/Interaction/AgentTaskImpl.Codeunit.al create mode 100644 src/System Application/App/Agent/Interaction/AgentTaskList.Page.al create mode 100644 src/System Application/App/Agent/Interaction/AgentTaskMessageCard.Page.al create mode 100644 src/System Application/App/Agent/Interaction/AgentTaskMessageList.Page.al create mode 100644 src/System Application/App/Agent/Interaction/AgentTaskStepList.Page.al create mode 100644 src/System Application/App/Agent/Permissions/AgentObjects.PermissionSet.al create mode 100644 src/System Application/App/Agent/Setup/Agent.Codeunit.al create mode 100644 src/System Application/App/Agent/Setup/AgentAccessControl.Page.al create mode 100644 src/System Application/App/Agent/Setup/AgentCard.Page.al create mode 100644 src/System Application/App/Agent/Setup/AgentImpl.Codeunit.al create mode 100644 src/System Application/App/Agent/Setup/AgentList.Page.al create mode 100644 src/System Application/App/Agent/Setup/SelectAgentAccessControl.Page.al create mode 100644 src/System Application/App/Agent/TaskPane/TaskDetails.Page.al create mode 100644 src/System Application/App/Agent/TaskPane/TaskTimeline.Page.al create mode 100644 src/System Application/App/Agent/TaskPane/Tasks.Page.al create mode 100644 src/System Application/App/Agent/app.json diff --git a/src/System Application/App/Agent/ExtensionLogo.png b/src/System Application/App/Agent/ExtensionLogo.png new file mode 100644 index 0000000000000000000000000000000000000000..f53be3156b9b6164dfc7e90c0043ef0bdd1ddc9f GIT binary patch literal 2408 zcmaJ@c~lc=67K|p5CM&Hh(I_L0nu<|89=~9xraqA<(MS{ivlY?b-2+CB*75y3?3sQ z4q+7pQ4R$~%#sfAfZ-e!L0mcv0RYem@b?Y_02D%^08I^KiAk+aL>8?d{G-zVVD&oHi+VAdD?@l21H5y%O3qs%KE8bD!`EEZw++`UO;#&;JgV~>&KV$zfB~>T2tY>zdO$e< zU;rC{jQWyAA8s)N4n#Ru;Zn?SNf&v#i)!?ImM9Wfn~MPi^#EC_4`_mDdO|@giaZK| z5kNyfyg2HMqQiD78HeV!7td;zCdit!vk<#b+QYs2P1Gyg^CL<*yYS&j9yK+N30{WX z)ogHzUB8jX1Rq78@CG*wG=`V<cZQ@3S7pbl9 zfz=OCITl1;X(u1_!zz2H9;okhe~5hluts_O2@IxlfA;m3>XV;Bef`gBj-{uBARCHW z0>xZrU%y&5J5i1}J}wX$C~CRW=vo&f2>iV#4qS!7ZH)V-`G}#L#+#jgL@V`g|JIv< zf+B%-NB^DIwcN4!v8akQ7aFS<3H6zJ2!g18Nn^h^Y`CFv5GI|R_oN4sUv_xP+;bMK z=FL8(Z^UJt7KPd%LFj{<14!~|P@s%KmlnFR3updu!amFI-!Lfr1-+`{aJ*E6H-DV9 zknuLyIhS47_0;3^c+_JsS>g6xImca}1Ss+5vlVI;bF4UBR1c5PaK)WNZERjgo>xW4P%XdaA9Q9!7$MAmqA7IMx*0F|Zuo2{%^7{2Tko5XGX=6q{; zl~DJCHDsuGL!h@X!S3mtDXMpjk7z*v&cJ4Kk@S(uR#9`6{LS}IjC041U21W475&eH zrgDd)toR_O>~UY~nZg3}Am+E`8$TR@IBg&0=@k(B2|Srg!zXGIHqlt-@q65{lSYdB zXd4%@Q?X>H_mq$GW9_yehG?(y+-@W_R3>RU{m}Xxm!4CPc%Ka(69Q^3SZcW6|a6AB3t{jt{imO~$i#$oi~8l3C?tE0DIBn79lul`ZPavnAxn@#Y{d zlF`#{CI6I0;!+F)%T^H<8!pVdW7*qfM`xa;X~4V_p4;N1mo1B+nTjVy-wvt5sn0it z`eKHu&c~(16U6_#y4?Y~MkOLivZF`pX0BhFUgk1kku^svJv5PJuF9Sr-9=YI=}rg_ zRt$C00>`P|GNUD;I?kwX1^n~2UM$VHlH=$QQ(@=qQDB2oXA)FNI|+N0MFUt z9h_z?9-eZdYCanZb?r_>?y_`>FTJ)w@v-VbvXS|_L`_{`=oR;$Y-Nqx=_8Eu`U?Af zIz_2n1)Fx>Lk@X6B`g$fwAXkyqB~vN=J9TK5u_(4SVmxSgw}SoFE1-p&W0!?S zLPjZcvvM#gIEq+iBNs{JOpj%f(s&+kK97F}Yx*Uo4KqHqqaQg(r4%)Px9T2C zLG0uZe@kc2+9p-0N{6g}9qks-GW8%Dfl+W+>vy7jHIsH36OwpvE+wBdE%r!A(qz&; zVv4v;S%!)NCWn$qQL^X7z2RbuN&)yD-9Z^HZM-!Zt<&7q+nSM8Qg8{Yv|MeTg{=~qda4zeCzW;oFZU06H1_%Y+ERLai;WG(RV6I5KGgyYB)LKsV;s?C(SyQ)cq54P z$pG&O)z<+Md&Zge^jfCbO#*!|aNO3i?gIYhf{n~4xXtts2CiH8x^{YQoyJ5zd+NLE zRO(heKF_M?{w+Ug6|uYNAy|gaZd6|uFIEJ3vIV4;jA0#L*F1EGNp`>GqD7?*P?Lo# zS!3&i3TYiFxO5$;9-ET8HL1Uv8NR0T|C7a^OZ_p8d^xzN=q;Z}OV)&j TThioJs%?PJ9`Cx{ae4m*g9kO? literal 0 HcmV?d00001 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": [], From 01e2808b01d8e799924841d7bbffe24afc1a92fb Mon Sep 17 00:00:00 2001 From: bcbuild-github-agent <137281497+bcbuild-github-agent@users.noreply.github.com> Date: Wed, 4 Dec 2024 07:32:19 -0800 Subject: [PATCH 4/4] [main] Update BCArtifact version. New value: 26.0.27602.0 (#2460) This PR contains the following changes: - Update BCArtifact version. New value: 26.0.27602.0 [AB#539394](https://dynamicssmb2.visualstudio.com/1fcb79e7-ab07-432a-a3c6-6cf5a88ba4a5/_workitems/edit/539394) Co-authored-by: gggdttt --- .github/AL-Go-Settings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/AL-Go-Settings.json b/.github/AL-Go-Settings.json index a95eb0ebf4..3ec7d5001c 100644 --- a/.github/AL-Go-Settings.json +++ b/.github/AL-Go-Settings.json @@ -5,7 +5,7 @@ "runs-on": "windows-latest", "cacheImageName": "", "UsePsSession": false, - "artifact": "https://bcinsider-fvh2ekdjecfjd6gk.b02.azurefd.net/sandbox/26.0.27531.0/base", + "artifact": "https://bcinsider-fvh2ekdjecfjd6gk.b02.azurefd.net/sandbox/26.0.27602.0/base", "country": "base", "useProjectDependencies": true, "repoVersion": "26.0",