From e87b7e7c40a600b9d7107b6014ca3150491cd514 Mon Sep 17 00:00:00 2001 From: Graham Hosking Date: Mon, 16 Mar 2026 10:17:22 +0000 Subject: [PATCH 1/4] Fix publish/setup docs and Node Oryx TypeScript deploy behavior --- .../a365-setup-instructions.md | 22 +++++++++++------ .../Exceptions/NodeBuildFailedException.cs | 5 +++- .../Services/ConfigurationWizardService.cs | 8 +++++-- .../Services/NodeBuilder.cs | 24 +++++++++++++++---- 4 files changed, 45 insertions(+), 14 deletions(-) diff --git a/docs/agent365-guided-setup/a365-setup-instructions.md b/docs/agent365-guided-setup/a365-setup-instructions.md index 1167da32..c4e51c5b 100644 --- a/docs/agent365-guided-setup/a365-setup-instructions.md +++ b/docs/agent365-guided-setup/a365-setup-instructions.md @@ -101,6 +101,8 @@ Required **delegated** Microsoft Graph permissions (all must have **admin consen | `DelegatedPermissionGrant.ReadWrite.All` | Grant delegated permissions | | `Directory.Read.All` | Read directory data | +> ⚠️ **WARNING — Do NOT use "Grant admin consent" in the Entra portal for this app registration.** The `AgentIdentityBlueprint.*` permissions above are beta-only and are not visible in the Entra admin center UI. If a Global Admin clicks "Grant admin consent" in the portal after these permissions have been granted via Graph API, the portal's consent mechanism will **silently delete** the `AgentIdentityBlueprint.*` grants. All permission grants for this app must be managed exclusively via the Graph API `appRoleAssignments` endpoint. If the permissions are accidentally removed, re-run the Graph API grants to restore them. + If the app does not exist, permissions are missing, or admin consent has not been granted, see "What to do if validation fails" below. **If validation fails** (app not found, permissions missing, or no admin consent): @@ -559,12 +561,15 @@ Ask the user: **"Please review and update your manifest.json file with your agen ### Publish the agent manifest -Run `a365 publish`. This step updates the agent's manifest identifiers and publishes the agent package to Microsoft Online Services (specifically, it registers the agent with the Microsoft 365 admin center under your tenant). What this does: +Run `a365 publish`. This command updates the agent's manifest identifiers and packages the manifest files into a zip ready for **manual** upload to the Microsoft 365 Admin Center. What this does: + +- Updates `manifest.json` and `agenticUserTemplateManifest.json` with your agent blueprint ID. +- Creates `manifest/manifest.zip` in your project directory. +- Prints the manual upload URL (Microsoft 365 Admin Center > Agents > All agents > Upload custom agent). -- It takes your project's `manifest.json` (which should define your agent's identity and capabilities) and updates certain identifiers in it (the CLI will inject the Azure AD application blueprint ID where needed). -- It then publishes the agent manifest/package to your tenant's catalog (so that the agent can be "hired" or installed in Teams and other apps). +> **Important:** `a365 publish` does **not** automatically upload to the Microsoft 365 Admin Center. After the command completes, you must upload `manifest.zip` manually through the admin center (a browser-only step). Follow the printed instructions or see the post-deployment section below. -Watch for output messages. Successful publish will indicate that the agent manifest is updated and that you can proceed to create an instance of the agent. If there's an error during publish, read it closely. For example, if the CLI complains about being unable to update some manifest or reach the admin center, ensure your account has the necessary privileges and that the custom app registration has the permissions for `Application.ReadWrite.All` (since publish might call Graph to update applications). Also, ensure your internet connectivity is good. +Watch for output messages indicating the package was created successfully. If there is an error, check that `agentBlueprintId` is populated in your config (run `a365 setup all` first if it is not). ### Deploy the agent code to Azure @@ -620,9 +625,12 @@ Provide the user with the following instructions: Provide the user with the following instructions: -1. Open **Teams > Apps** and search for your agent name -2. Select your agent and click **Request Instance** (or **Create Instance**) -3. Teams sends the request to your tenant admin for approval +> **Note (Frontier preview):** During Frontier preview, the blueprint is discoverable via **Microsoft 365 Copilot > Apps**, not Teams > Apps. The "Agents for your team" category in Teams may not be visible even after publishing. Search for your agent in M365 Copilot to Request or Create an instance. After an instance is created, the agent becomes accessible in Teams chat. + +1. Open **Microsoft 365 Copilot > Apps** and search for your agent name. + *(If not found there, also try Teams > Apps — availability depends on tenant rollout.)* +2. Select your agent and click **Request Instance** (or **Create Instance**). +3. Teams sends the request to your tenant admin for approval. Admins can review and approve requests from the [Microsoft admin center - Requested Agents](https://admin.cloud.microsoft/#/agents/all/requested) page. After approval, Teams creates the agent instance and makes it available. diff --git a/src/Microsoft.Agents.A365.DevTools.Cli/Exceptions/NodeBuildFailedException.cs b/src/Microsoft.Agents.A365.DevTools.Cli/Exceptions/NodeBuildFailedException.cs index fac3c974..6720c50b 100644 --- a/src/Microsoft.Agents.A365.DevTools.Cli/Exceptions/NodeBuildFailedException.cs +++ b/src/Microsoft.Agents.A365.DevTools.Cli/Exceptions/NodeBuildFailedException.cs @@ -22,7 +22,10 @@ public NodeBuildFailedException(string projectDirectory, string? npmErrorOutput) { "Run 'npm run build' locally in the project directory and fix any TypeScript/webpack/build errors.", "Verify that the 'build' script is defined correctly in package.json.", - "If the build depends on environment variables or private packages, ensure those are configured on the machine running 'a365 deploy'.", + "If the error is 'tsc: not found' or similar, move 'typescript' from 'devDependencies' to " + + "'dependencies' in package.json. Azure App Service Oryx runs 'npm install --production' " + + "which skips devDependencies, so build tools like tsc must be in dependencies.", + "If the build depends on environment variables or private packages, ensure those are configured on the machine running 'a365 deploy'.", "After resolving the build issues, rerun 'a365 deploy'." }, context: new Dictionary diff --git a/src/Microsoft.Agents.A365.DevTools.Cli/Services/ConfigurationWizardService.cs b/src/Microsoft.Agents.A365.DevTools.Cli/Services/ConfigurationWizardService.cs index bc796728..406fd5c1 100644 --- a/src/Microsoft.Agents.A365.DevTools.Cli/Services/ConfigurationWizardService.cs +++ b/src/Microsoft.Agents.A365.DevTools.Cli/Services/ConfigurationWizardService.cs @@ -595,8 +595,12 @@ private string PromptForAppServicePlanSku(Agent365Config? existingConfig) Console.WriteLine(" 4. S1 - Standard (auto-scale, staging slots)"); Console.WriteLine(" 5. P1V3 - Premium V3 (high performance)"); Console.WriteLine(); - Console.WriteLine("NOTE: Free tier (F1) is recommended for development and testing."); - Console.WriteLine(" Basic tier (B1) often has zero quota by default - may require quota increase."); + Console.WriteLine("NOTE: B1 (Basic) is the recommended default. It handles the Node.js/TypeScript"); + Console.WriteLine(" Oryx remote build (npm install + tsc) within the startup timeout."); + Console.WriteLine(" F1 (Free) has a 230s cold-start limit that is routinely exceeded by"); + Console.WriteLine(" TypeScript agent projects during remote build — avoid F1 for Node.js/TS."); + Console.WriteLine(" B1 may have zero quota in new subscriptions — if creation fails, request"); + Console.WriteLine(" a quota increase or try a different Azure region."); Console.WriteLine(); var defaultSku = existingConfig?.AppServicePlanSku ?? ConfigConstants.DefaultAppServicePlanSku; diff --git a/src/Microsoft.Agents.A365.DevTools.Cli/Services/NodeBuilder.cs b/src/Microsoft.Agents.A365.DevTools.Cli/Services/NodeBuilder.cs index f179b097..603b8f84 100644 --- a/src/Microsoft.Agents.A365.DevTools.Cli/Services/NodeBuilder.cs +++ b/src/Microsoft.Agents.A365.DevTools.Cli/Services/NodeBuilder.cs @@ -249,10 +249,26 @@ public async Task CreateManifestAsync(string projectDir, string pu var buildValue = buildScript.GetString(); if (!string.IsNullOrWhiteSpace(buildValue)) { - // We always call through npm so it picks up the script from package.json - buildCommand = "npm run build"; - buildRequired = true; - _logger.LogInformation("Detected build script; using Oryx build command: {Command}", buildCommand); + // If a dist/ folder was already produced by the local build, do NOT ask Oryx to + // re-run npm run build on Azure. Azure App Service Oryx runs `npm install --production` + // which skips devDependencies, so tools like `tsc` (commonly in devDependencies) are + // not available, causing the Oryx build to fail with "sh: tsc: not found". + // When dist/ exists the compiled output is already in the publish package. + var distPath = Path.Combine(publishPath, "dist"); + if (Directory.Exists(distPath)) + { + buildCommand = ""; + buildRequired = false; + _logger.LogInformation("dist/ folder found in publish output; skipping Oryx remote build " + + "(TypeScript already compiled locally — avoids tsc-not-found on Azure)."); + } + else + { + // We always call through npm so it picks up the script from package.json + buildCommand = "npm run build"; + buildRequired = true; + _logger.LogInformation("Detected build script; using Oryx build command: {Command}", buildCommand); + } } } else From cbcc69fa047c5823c23806eb23eea5c12e1e1afb Mon Sep 17 00:00:00 2001 From: Sellakumaran Kanagarathnam Date: Fri, 27 Mar 2026 16:23:39 -0700 Subject: [PATCH 2/4] fix: address code review feedback for PR #318 - Fix indentation inconsistencies in NodeBuilder.cs, ConfigurationWizardService.cs, and NodeBuildFailedException.cs - Narrow Oryx skip heuristic: require both dist/ and tsconfig.json to avoid silently skipping remote builds for JavaScript-only projects that use dist/ for webpack output - Add NodeBuilderTests covering: TypeScript+dist (skip), TypeScript without dist (build), JavaScript+dist (build), no build script (no build) - Remove emoji from documentation warning block (cross-platform terminal compatibility) - Add CHANGELOG.md entries for Oryx skip behavior and SKU recommendation change Co-Authored-By: Claude Sonnet 4.6 --- CHANGELOG.md | 2 + .../a365-setup-instructions.md | 2 +- .../Exceptions/NodeBuildFailedException.cs | 8 +- .../Services/ConfigurationWizardService.cs | 12 +- .../Services/NodeBuilder.cs | 44 +++--- .../Services/NodeBuilderTests.cs | 139 ++++++++++++++++++ 6 files changed, 176 insertions(+), 31 deletions(-) create mode 100644 src/Tests/Microsoft.Agents.A365.DevTools.Cli.Tests/Services/NodeBuilderTests.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index c671be19..75d7eb3e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,8 +12,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). - `AppServiceAuthRequirementCheck` — validates App Service deployment token before `a365 deploy` begins, catching revoked grants (AADSTS50173) early ### Changed - `a365 publish` updates manifest IDs, creates `manifest.zip`, and prints concise upload instructions for Microsoft 365 Admin Center (Agents > All agents > Upload custom agent). Interactive prompts only occur in interactive terminals; redirect stdin to suppress them in scripts. +- App Service Plan SKU recommendation updated to B1 (Basic) for Node.js/TypeScript agents; F1 (Free) is now flagged as unsuitable due to the 230s cold-start limit being routinely exceeded during Oryx remote builds (#318) ### Fixed +- Node.js/TypeScript deployments: skip Oryx remote build when `dist/` already exists in the publish output, preventing `tsc: not found` failures caused by Oryx running `npm install --production` which excludes devDependencies (#318) - macOS/Linux: device code fallback when browser authentication is unavailable (#309) - Linux: MSAL fallback when PowerShell `Connect-MgGraph` fails in non-TTY environments (#309) - Admin consent polling no longer times out after 180s — blueprint service principal now resolved with correct MSAL token (#309) diff --git a/docs/agent365-guided-setup/a365-setup-instructions.md b/docs/agent365-guided-setup/a365-setup-instructions.md index c4e51c5b..1f6f7513 100644 --- a/docs/agent365-guided-setup/a365-setup-instructions.md +++ b/docs/agent365-guided-setup/a365-setup-instructions.md @@ -101,7 +101,7 @@ Required **delegated** Microsoft Graph permissions (all must have **admin consen | `DelegatedPermissionGrant.ReadWrite.All` | Grant delegated permissions | | `Directory.Read.All` | Read directory data | -> ⚠️ **WARNING — Do NOT use "Grant admin consent" in the Entra portal for this app registration.** The `AgentIdentityBlueprint.*` permissions above are beta-only and are not visible in the Entra admin center UI. If a Global Admin clicks "Grant admin consent" in the portal after these permissions have been granted via Graph API, the portal's consent mechanism will **silently delete** the `AgentIdentityBlueprint.*` grants. All permission grants for this app must be managed exclusively via the Graph API `appRoleAssignments` endpoint. If the permissions are accidentally removed, re-run the Graph API grants to restore them. +> **WARNING — Do NOT use "Grant admin consent" in the Entra portal for this app registration.** The `AgentIdentityBlueprint.*` permissions above are beta-only and are not visible in the Entra admin center UI. If a Global Admin clicks "Grant admin consent" in the portal after these permissions have been granted via Graph API, the portal's consent mechanism will **silently delete** the `AgentIdentityBlueprint.*` grants. All permission grants for this app must be managed exclusively via the Graph API `appRoleAssignments` endpoint. If the permissions are accidentally removed, re-run the Graph API grants to restore them. If the app does not exist, permissions are missing, or admin consent has not been granted, see "What to do if validation fails" below. diff --git a/src/Microsoft.Agents.A365.DevTools.Cli/Exceptions/NodeBuildFailedException.cs b/src/Microsoft.Agents.A365.DevTools.Cli/Exceptions/NodeBuildFailedException.cs index 6720c50b..e12c577d 100644 --- a/src/Microsoft.Agents.A365.DevTools.Cli/Exceptions/NodeBuildFailedException.cs +++ b/src/Microsoft.Agents.A365.DevTools.Cli/Exceptions/NodeBuildFailedException.cs @@ -22,10 +22,10 @@ public NodeBuildFailedException(string projectDirectory, string? npmErrorOutput) { "Run 'npm run build' locally in the project directory and fix any TypeScript/webpack/build errors.", "Verify that the 'build' script is defined correctly in package.json.", - "If the error is 'tsc: not found' or similar, move 'typescript' from 'devDependencies' to " + - "'dependencies' in package.json. Azure App Service Oryx runs 'npm install --production' " + - "which skips devDependencies, so build tools like tsc must be in dependencies.", - "If the build depends on environment variables or private packages, ensure those are configured on the machine running 'a365 deploy'.", + "If the error is 'tsc: not found' or similar, move 'typescript' from 'devDependencies' to " + + "'dependencies' in package.json. Azure App Service Oryx runs 'npm install --production' " + + "which skips devDependencies, so build tools like tsc must be in dependencies.", + "If the build depends on environment variables or private packages, ensure those are configured on the machine running 'a365 deploy'.", "After resolving the build issues, rerun 'a365 deploy'." }, context: new Dictionary diff --git a/src/Microsoft.Agents.A365.DevTools.Cli/Services/ConfigurationWizardService.cs b/src/Microsoft.Agents.A365.DevTools.Cli/Services/ConfigurationWizardService.cs index 406fd5c1..e20815d4 100644 --- a/src/Microsoft.Agents.A365.DevTools.Cli/Services/ConfigurationWizardService.cs +++ b/src/Microsoft.Agents.A365.DevTools.Cli/Services/ConfigurationWizardService.cs @@ -595,12 +595,12 @@ private string PromptForAppServicePlanSku(Agent365Config? existingConfig) Console.WriteLine(" 4. S1 - Standard (auto-scale, staging slots)"); Console.WriteLine(" 5. P1V3 - Premium V3 (high performance)"); Console.WriteLine(); - Console.WriteLine("NOTE: B1 (Basic) is the recommended default. It handles the Node.js/TypeScript"); - Console.WriteLine(" Oryx remote build (npm install + tsc) within the startup timeout."); - Console.WriteLine(" F1 (Free) has a 230s cold-start limit that is routinely exceeded by"); - Console.WriteLine(" TypeScript agent projects during remote build — avoid F1 for Node.js/TS."); - Console.WriteLine(" B1 may have zero quota in new subscriptions — if creation fails, request"); - Console.WriteLine(" a quota increase or try a different Azure region."); + Console.WriteLine("NOTE: B1 (Basic) is the recommended default. It handles the Node.js/TypeScript"); + Console.WriteLine(" Oryx remote build (npm install + tsc) within the startup timeout."); + Console.WriteLine(" F1 (Free) has a 230s cold-start limit that is routinely exceeded by"); + Console.WriteLine(" TypeScript agent projects during remote build — avoid F1 for Node.js/TS."); + Console.WriteLine(" B1 may have zero quota in new subscriptions — if creation fails, request"); + Console.WriteLine(" a quota increase or try a different Azure region."); Console.WriteLine(); var defaultSku = existingConfig?.AppServicePlanSku ?? ConfigConstants.DefaultAppServicePlanSku; diff --git a/src/Microsoft.Agents.A365.DevTools.Cli/Services/NodeBuilder.cs b/src/Microsoft.Agents.A365.DevTools.Cli/Services/NodeBuilder.cs index 603b8f84..52d16cb0 100644 --- a/src/Microsoft.Agents.A365.DevTools.Cli/Services/NodeBuilder.cs +++ b/src/Microsoft.Agents.A365.DevTools.Cli/Services/NodeBuilder.cs @@ -249,26 +249,30 @@ public async Task CreateManifestAsync(string projectDir, string pu var buildValue = buildScript.GetString(); if (!string.IsNullOrWhiteSpace(buildValue)) { - // If a dist/ folder was already produced by the local build, do NOT ask Oryx to - // re-run npm run build on Azure. Azure App Service Oryx runs `npm install --production` - // which skips devDependencies, so tools like `tsc` (commonly in devDependencies) are - // not available, causing the Oryx build to fail with "sh: tsc: not found". - // When dist/ exists the compiled output is already in the publish package. - var distPath = Path.Combine(publishPath, "dist"); - if (Directory.Exists(distPath)) - { - buildCommand = ""; - buildRequired = false; - _logger.LogInformation("dist/ folder found in publish output; skipping Oryx remote build " + - "(TypeScript already compiled locally — avoids tsc-not-found on Azure)."); - } - else - { - // We always call through npm so it picks up the script from package.json - buildCommand = "npm run build"; - buildRequired = true; - _logger.LogInformation("Detected build script; using Oryx build command: {Command}", buildCommand); - } + // If a TypeScript project's dist/ folder was already produced by the local build, + // do NOT ask Oryx to re-run npm run build on Azure. Azure App Service Oryx runs + // `npm install --production` which skips devDependencies, so tools like `tsc` + // (commonly in devDependencies) are not available, causing the Oryx build to fail + // with "sh: tsc: not found". When dist/ exists the compiled output is already in + // the publish package. We scope this skip to TypeScript projects (tsconfig.json + // present) to avoid silently skipping builds for JavaScript-only projects that + // use dist/ as a webpack/rollup output directory. + var distPath = Path.Combine(publishPath, "dist"); + var tsConfigPath = Path.Combine(projectDir, "tsconfig.json"); + if (Directory.Exists(distPath) && File.Exists(tsConfigPath)) + { + buildCommand = ""; + buildRequired = false; + _logger.LogInformation("dist/ folder found in publish output for TypeScript project; skipping Oryx remote build " + + "(TypeScript already compiled locally — avoids tsc-not-found on Azure)."); + } + else + { + // We always call through npm so it picks up the script from package.json + buildCommand = "npm run build"; + buildRequired = true; + _logger.LogInformation("Detected build script; using Oryx build command: {Command}", buildCommand); + } } } else diff --git a/src/Tests/Microsoft.Agents.A365.DevTools.Cli.Tests/Services/NodeBuilderTests.cs b/src/Tests/Microsoft.Agents.A365.DevTools.Cli.Tests/Services/NodeBuilderTests.cs new file mode 100644 index 00000000..4d5cda27 --- /dev/null +++ b/src/Tests/Microsoft.Agents.A365.DevTools.Cli.Tests/Services/NodeBuilderTests.cs @@ -0,0 +1,139 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using FluentAssertions; +using Microsoft.Agents.A365.DevTools.Cli.Services; +using Microsoft.Extensions.Logging; +using NSubstitute; + +namespace Microsoft.Agents.A365.DevTools.Cli.Tests.Services; + +public class NodeBuilderTests : IDisposable +{ + private readonly ILogger _logger; + private readonly NodeBuilder _builder; + private readonly List _tempDirectories; + + public NodeBuilderTests() + { + _logger = Substitute.For>(); + var executorLogger = Substitute.For>(); + var mockExecutor = Substitute.ForPartsOf(executorLogger); + _builder = new NodeBuilder(_logger, mockExecutor); + _tempDirectories = new List(); + } + + public void Dispose() + { + foreach (var dir in _tempDirectories) + { + if (Directory.Exists(dir)) + Directory.Delete(dir, recursive: true); + } + } + + [Fact] + public async Task CreateManifestAsync_TypeScriptProjectWithDistFolder_SkipsOryxBuild() + { + // Arrange + var projectDir = CreateTempDirectory(); + var publishPath = CreateTempDirectory(); + + WritePackageJson(projectDir, buildScript: "tsc", startScript: "node dist/index.js"); + File.WriteAllText(Path.Combine(projectDir, "tsconfig.json"), "{}"); + Directory.CreateDirectory(Path.Combine(publishPath, "dist")); + + // Act + var manifest = await _builder.CreateManifestAsync(projectDir, publishPath); + + // Assert + manifest.BuildRequired.Should().BeFalse( + because: "when dist/ exists and tsconfig.json is present, TypeScript was compiled locally " + + "and Oryx must not re-run npm run build — Oryx's production install skips devDependencies " + + "so tsc would not be found, causing deployment failure"); + manifest.BuildCommand.Should().BeEmpty( + because: "no build command should be set when the Oryx remote build is skipped"); + } + + [Fact] + public async Task CreateManifestAsync_TypeScriptProjectWithoutDistFolder_UsesOryxBuild() + { + // Arrange + var projectDir = CreateTempDirectory(); + var publishPath = CreateTempDirectory(); + + WritePackageJson(projectDir, buildScript: "tsc", startScript: "node dist/index.js"); + File.WriteAllText(Path.Combine(projectDir, "tsconfig.json"), "{}"); + // No dist/ in publish output — TypeScript not yet compiled + + // Act + var manifest = await _builder.CreateManifestAsync(projectDir, publishPath); + + // Assert + manifest.BuildRequired.Should().BeTrue( + because: "when dist/ is absent the TypeScript project was not pre-compiled so Oryx must run npm run build"); + manifest.BuildCommand.Should().Be("npm run build"); + } + + [Fact] + public async Task CreateManifestAsync_JavaScriptProjectWithDistFolder_UsesOryxBuild() + { + // Arrange + var projectDir = CreateTempDirectory(); + var publishPath = CreateTempDirectory(); + + WritePackageJson(projectDir, buildScript: "webpack", startScript: "node dist/bundle.js"); + // No tsconfig.json — JavaScript-only project with webpack producing dist/ + Directory.CreateDirectory(Path.Combine(publishPath, "dist")); + + // Act + var manifest = await _builder.CreateManifestAsync(projectDir, publishPath); + + // Assert + manifest.BuildRequired.Should().BeTrue( + because: "JavaScript-only projects without tsconfig.json should still use Oryx remote build " + + "even when dist/ exists — skipping would be incorrect since the build script produces the bundle"); + manifest.BuildCommand.Should().Be("npm run build"); + } + + [Fact] + public async Task CreateManifestAsync_WithoutBuildScript_DoesNotSetBuildRequired() + { + // Arrange + var projectDir = CreateTempDirectory(); + var publishPath = CreateTempDirectory(); + + WritePackageJson(projectDir, buildScript: null, startScript: "node server.js"); + + // Act + var manifest = await _builder.CreateManifestAsync(projectDir, publishPath); + + // Assert + manifest.BuildRequired.Should().BeFalse( + because: "no build script in package.json means Oryx only runs npm install, not a build step"); + manifest.BuildCommand.Should().BeEmpty(); + } + + private static void WritePackageJson(string projectDir, string? buildScript, string startScript) + { + var scripts = buildScript is not null + ? $@"""build"": ""{buildScript}"", ""start"": ""{startScript}""" + : $@"""start"": ""{startScript}"""; + + File.WriteAllText(Path.Combine(projectDir, "package.json"), $$""" + { + "scripts": { + {{scripts}} + } + } + """); + } + + private string CreateTempDirectory() + { + var dir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + Directory.CreateDirectory(dir); + _tempDirectories.Add(dir); + return dir; + } +} From 80a1a81c210058e2c8ac223931b46c5db11bc171 Mon Sep 17 00:00:00 2001 From: Sellakumaran Kanagarathnam Date: Fri, 27 Mar 2026 16:26:58 -0700 Subject: [PATCH 3/4] fix: address Copilot PR review comments for PR #318 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - NodeBuilder: fix log message to use ASCII-only and clarify that only the npm run build step is skipped in the Oryx manifest, not the entire Oryx process - NodeBuildFailedException: reword tsc-not-found mitigation to local build context; remove misleading reference to Azure App Service Oryx (this exception is thrown during local builds where Oryx does not apply) - Docs: reconcile admin consent guidance — remediation steps now direct users to apply grants via Graph API only, consistent with the warning above Co-Authored-By: Claude Sonnet 4.6 --- docs/agent365-guided-setup/a365-setup-instructions.md | 2 +- .../Exceptions/NodeBuildFailedException.cs | 6 +++--- .../Services/NodeBuilder.cs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/agent365-guided-setup/a365-setup-instructions.md b/docs/agent365-guided-setup/a365-setup-instructions.md index 1f6f7513..666cbb31 100644 --- a/docs/agent365-guided-setup/a365-setup-instructions.md +++ b/docs/agent365-guided-setup/a365-setup-instructions.md @@ -109,7 +109,7 @@ If the app does not exist, permissions are missing, or admin consent has not bee 1. STOP — do not proceed to run any `a365` CLI commands. 2. Inform the user the custom client app registration is missing or incomplete. -3. Direct the user to the official setup guide: register the app, configure as a Public client with redirect URI `http://localhost:8400`, add all five permissions above, and have a Global Admin grant admin consent. +3. Direct the user to the official setup guide: register the app, configure as a Public client with redirect URI `http://localhost:8400`, add all five permissions above, and apply all permission grants exclusively via the Graph API `appRoleAssignments` endpoint (do NOT use the "Grant admin consent" button in the Entra portal — see warning above). 4. Wait for the user to confirm the app is properly configured, then re-run the same validation command above. Save the `clientAppId` value — it will be used automatically in Step 3 (do NOT ask the user for it again). diff --git a/src/Microsoft.Agents.A365.DevTools.Cli/Exceptions/NodeBuildFailedException.cs b/src/Microsoft.Agents.A365.DevTools.Cli/Exceptions/NodeBuildFailedException.cs index e12c577d..e1f6fb75 100644 --- a/src/Microsoft.Agents.A365.DevTools.Cli/Exceptions/NodeBuildFailedException.cs +++ b/src/Microsoft.Agents.A365.DevTools.Cli/Exceptions/NodeBuildFailedException.cs @@ -22,9 +22,9 @@ public NodeBuildFailedException(string projectDirectory, string? npmErrorOutput) { "Run 'npm run build' locally in the project directory and fix any TypeScript/webpack/build errors.", "Verify that the 'build' script is defined correctly in package.json.", - "If the error is 'tsc: not found' or similar, move 'typescript' from 'devDependencies' to " + - "'dependencies' in package.json. Azure App Service Oryx runs 'npm install --production' " + - "which skips devDependencies, so build tools like tsc must be in dependencies.", + "If the error is 'tsc: not found', ensure TypeScript is installed by running 'npm install' " + + "(not 'npm install --production'). If 'typescript' is listed under 'devDependencies', " + + "it will not be installed when the --production flag is used.", "If the build depends on environment variables or private packages, ensure those are configured on the machine running 'a365 deploy'.", "After resolving the build issues, rerun 'a365 deploy'." }, diff --git a/src/Microsoft.Agents.A365.DevTools.Cli/Services/NodeBuilder.cs b/src/Microsoft.Agents.A365.DevTools.Cli/Services/NodeBuilder.cs index 52d16cb0..56d656c7 100644 --- a/src/Microsoft.Agents.A365.DevTools.Cli/Services/NodeBuilder.cs +++ b/src/Microsoft.Agents.A365.DevTools.Cli/Services/NodeBuilder.cs @@ -263,8 +263,8 @@ public async Task CreateManifestAsync(string projectDir, string pu { buildCommand = ""; buildRequired = false; - _logger.LogInformation("dist/ folder found in publish output for TypeScript project; skipping Oryx remote build " + - "(TypeScript already compiled locally — avoids tsc-not-found on Azure)."); + _logger.LogInformation("dist/ folder found in publish output for TypeScript project; skipping npm run build step in Oryx manifest " + + "(TypeScript compiled locally - Oryx will still run npm install but will not invoke the build script)."); } else { From e348ec4d0dca1f5f63d7f8e5b62f5f0fbb225a33 Mon Sep 17 00:00:00 2001 From: Sellakumaran Kanagarathnam Date: Fri, 27 Mar 2026 17:12:55 -0700 Subject: [PATCH 4/4] fix: address review comments on a365-setup-instructions.md - Remove outdated WARNING about AgentIdentityBlueprint grants being invisible in Entra admin center and silently deleted on consent - Correct API endpoint reference from appRoleAssignments (application permissions) to oauth2PermissionGrants (delegated permissions) - Update Frontier preview note to reflect that both M365 Copilot > Apps and Teams > Apps can surface the agent depending on tenant rollout Co-Authored-By: Claude Sonnet 4.6 --- docs/agent365-guided-setup/a365-setup-instructions.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/agent365-guided-setup/a365-setup-instructions.md b/docs/agent365-guided-setup/a365-setup-instructions.md index 666cbb31..d566e830 100644 --- a/docs/agent365-guided-setup/a365-setup-instructions.md +++ b/docs/agent365-guided-setup/a365-setup-instructions.md @@ -101,7 +101,7 @@ Required **delegated** Microsoft Graph permissions (all must have **admin consen | `DelegatedPermissionGrant.ReadWrite.All` | Grant delegated permissions | | `Directory.Read.All` | Read directory data | -> **WARNING — Do NOT use "Grant admin consent" in the Entra portal for this app registration.** The `AgentIdentityBlueprint.*` permissions above are beta-only and are not visible in the Entra admin center UI. If a Global Admin clicks "Grant admin consent" in the portal after these permissions have been granted via Graph API, the portal's consent mechanism will **silently delete** the `AgentIdentityBlueprint.*` grants. All permission grants for this app must be managed exclusively via the Graph API `appRoleAssignments` endpoint. If the permissions are accidentally removed, re-run the Graph API grants to restore them. +> **NOTE:** The `AgentIdentityBlueprint.*` delegated permission grants are visible in the Entra admin center. All delegated permission grants for this app are managed via the Graph API `oauth2PermissionGrants` endpoint. If the app does not exist, permissions are missing, or admin consent has not been granted, see "What to do if validation fails" below. @@ -109,7 +109,7 @@ If the app does not exist, permissions are missing, or admin consent has not bee 1. STOP — do not proceed to run any `a365` CLI commands. 2. Inform the user the custom client app registration is missing or incomplete. -3. Direct the user to the official setup guide: register the app, configure as a Public client with redirect URI `http://localhost:8400`, add all five permissions above, and apply all permission grants exclusively via the Graph API `appRoleAssignments` endpoint (do NOT use the "Grant admin consent" button in the Entra portal — see warning above). +3. Direct the user to the official setup guide: register the app, configure as a Public client with redirect URI `http://localhost:8400`, add all five permissions above, and apply all delegated permission grants via the Graph API `oauth2PermissionGrants` endpoint. 4. Wait for the user to confirm the app is properly configured, then re-run the same validation command above. Save the `clientAppId` value — it will be used automatically in Step 3 (do NOT ask the user for it again). @@ -625,10 +625,9 @@ Provide the user with the following instructions: Provide the user with the following instructions: -> **Note (Frontier preview):** During Frontier preview, the blueprint is discoverable via **Microsoft 365 Copilot > Apps**, not Teams > Apps. The "Agents for your team" category in Teams may not be visible even after publishing. Search for your agent in M365 Copilot to Request or Create an instance. After an instance is created, the agent becomes accessible in Teams chat. +> **Note (Frontier preview):** During Frontier preview, the blueprint may be discoverable via **Microsoft 365 Copilot > Apps** or **Teams > Apps** depending on tenant rollout. If the agent does not appear in one, try the other. After an instance is created, the agent becomes accessible in Teams chat. -1. Open **Microsoft 365 Copilot > Apps** and search for your agent name. - *(If not found there, also try Teams > Apps — availability depends on tenant rollout.)* +1. Open **Microsoft 365 Copilot > Apps** or **Teams > Apps** and search for your agent name. 2. Select your agent and click **Request Instance** (or **Create Instance**). 3. Teams sends the request to your tenant admin for approval.