Skip to content

Add W365 Computer Use sample agent#305

Open
desmarest wants to merge 1 commit into
mainfrom
users/bertd/w365-computer-use-sample
Open

Add W365 Computer Use sample agent#305
desmarest wants to merge 1 commit into
mainfrom
users/bertd/w365-computer-use-sample

Conversation

@desmarest
Copy link
Copy Markdown

Summary

Adds dotnet/w365-computer-use/ as a new sample alongside agent-framework, autonomous, and semantic-kernel.

The sample demonstrates an Agent 365 agent that controls a Windows 365 Cloud PC via the W365 Computer Use MCP server using Azure OpenAI's computer-use model (computer-use-preview and gpt-5.4 / gpt-5.4-mini are both supported).

Highlights

  • Intent classifier skips the W365 session-acquisition handshake on non-CUA messages (chit-chat, mail/calendar function-tool requests) so the Cloud PC pool isn't burned for trivial turns.
  • Full CUA loop translates model computer_call actions into MCP tool invocations, captures screenshots, and feeds them back to the model until it emits OnTaskComplete or EndSession.
  • Per-prompt OneDrive screenshot folders ({yyyy-MM-dd}/{HHmmss}_{slug}) with org-scoped sharing links surfaced to the user inline.
  • Filters ATG's synthetic Error sentinel tool out of the LLM tool list to avoid Azure OpenAI invalid_function_parameters rejections, while still reading its description for user-facing error messaging.
  • README walks through local Playground testing (with appsettings.Development.json ClientSecret override) and full Azure App Service production deployment (resource creation, App Service config with AuthType=ClientSecret + AuthorityEndpoint, dotnet publish + az webapp deploy, Bot Channels Registration in Foundry, blueprint linking, and a365 publish for Teams app upload).
  • a365.config.example.json committed as a placeholder template since the CLI no longer ships a config init command.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 5, 2026

⚠️ Deprecation Warning: The deny-licenses option is deprecated for possible removal in the next major release. For more information, see issue 997.

Dependency Review

The following issues were found:
  • ✅ 0 vulnerable package(s)
  • ✅ 0 package(s) with incompatible licenses
  • ✅ 0 package(s) with invalid SPDX license definitions
  • ⚠️ 2 package(s) with unknown licenses.
See the Details below.

License Issues

dotnet/w365-computer-use/sample-agent/W365ComputerUseSample.csproj

PackageVersionLicenseIssue Type
Microsoft.Agents.Authentication.MsalNullUnknown License
Microsoft.Agents.Hosting.AspNetCoreNullUnknown License
Denied Licenses: GPL-3.0-only, AGPL-3.0-only

OpenSSF Scorecard

Scorecard details
PackageVersionScoreDetails
nuget/OpenTelemetry.Exporter.OpenTelemetryProtocol 1.12.0 🟢 8.3
Details
CheckScoreReason
Dependency-Update-Tool🟢 10update tool detected
Maintained🟢 1030 commit(s) and 25 issue activity found in the last 90 days -- score normalized to 10
Code-Review🟢 10all changesets reviewed
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Token-Permissions🟢 10GitHub workflow tokens follow principle of least privilege
Binary-Artifacts🟢 10no binaries found in the repo
Pinned-Dependencies🟢 10all dependencies are pinned
CII-Best-Practices🟢 5badge detected: Passing
Signed-Releases⚠️ 0Project has not signed or included provenance with any releases.
Vulnerabilities🟢 100 existing vulnerabilities detected
Packaging🟢 10packaging workflow detected
Branch-Protection🟢 5branch protection is not maximal on development and all release branches
License🟢 10license file detected
SAST🟢 10SAST tool is run on all commits
Fuzzing⚠️ 0project is not fuzzed
Security-Policy🟢 10security policy file detected
CI-Tests🟢 1030 out of 30 merged PRs checked by a CI test -- score normalized to 10
Contributors🟢 10project has 37 contributing companies or organizations
nuget/Microsoft.Agents.A365.Observability.Extensions.AgentFramework 0.1.72-beta UnknownUnknown
nuget/Microsoft.Agents.A365.Tooling.Extensions.AgentFramework 0.1.72-beta UnknownUnknown
nuget/Microsoft.Agents.AI 1.0.0-preview.251113.1 UnknownUnknown
nuget/Microsoft.Agents.Authentication.Msal UnknownUnknown
nuget/Microsoft.Agents.Hosting.AspNetCore UnknownUnknown
nuget/Microsoft.Extensions.Http.Resilience 9.9.0 🟢 6.7
Details
CheckScoreReason
Code-Review🟢 10all changesets reviewed
Security-Policy🟢 10security policy file detected
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Maintained🟢 1030 commit(s) and 16 issue activity found in the last 90 days -- score normalized to 10
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Token-Permissions⚠️ 0detected GitHub workflow tokens with excessive permissions
Packaging⚠️ -1packaging workflow not detected
License🟢 10license file detected
Branch-Protection🟢 5branch protection is not maximal on development and all release branches
Signed-Releases⚠️ -1no releases found
Binary-Artifacts🟢 10no binaries found in the repo
Pinned-Dependencies🟢 9dependency not pinned by hash detected -- score normalized to 9
Fuzzing⚠️ 0project is not fuzzed
SAST⚠️ 0SAST tool is not run on all commits -- score normalized to 0
nuget/Microsoft.Extensions.ServiceDiscovery 9.5.0 UnknownUnknown
nuget/ModelContextProtocol 0.2.0-preview.3 UnknownUnknown
nuget/OpenTelemetry.Extensions.Hosting 1.12.0 🟢 8.3
Details
CheckScoreReason
Dependency-Update-Tool🟢 10update tool detected
Maintained🟢 1030 commit(s) and 25 issue activity found in the last 90 days -- score normalized to 10
Code-Review🟢 10all changesets reviewed
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Token-Permissions🟢 10GitHub workflow tokens follow principle of least privilege
Binary-Artifacts🟢 10no binaries found in the repo
Pinned-Dependencies🟢 10all dependencies are pinned
CII-Best-Practices🟢 5badge detected: Passing
Signed-Releases⚠️ 0Project has not signed or included provenance with any releases.
Vulnerabilities🟢 100 existing vulnerabilities detected
Packaging🟢 10packaging workflow detected
Branch-Protection🟢 5branch protection is not maximal on development and all release branches
License🟢 10license file detected
SAST🟢 10SAST tool is run on all commits
Fuzzing⚠️ 0project is not fuzzed
Security-Policy🟢 10security policy file detected
CI-Tests🟢 1030 out of 30 merged PRs checked by a CI test -- score normalized to 10
Contributors🟢 10project has 37 contributing companies or organizations
nuget/OpenTelemetry.Instrumentation.AspNetCore 1.12.0 🟢 8.3
Details
CheckScoreReason
Maintained🟢 1030 commit(s) and 25 issue activity found in the last 90 days -- score normalized to 10
Dependency-Update-Tool🟢 10update tool detected
Code-Review🟢 10all changesets reviewed
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Token-Permissions🟢 10GitHub workflow tokens follow principle of least privilege
Binary-Artifacts🟢 10no binaries found in the repo
CII-Best-Practices🟢 5badge detected: Passing
Pinned-Dependencies🟢 9dependency not pinned by hash detected -- score normalized to 9
Vulnerabilities🟢 100 existing vulnerabilities detected
Signed-Releases⚠️ 0Project has not signed or included provenance with any releases.
Packaging🟢 10packaging workflow detected
SAST🟢 10SAST tool is run on all commits
License🟢 10license file detected
Security-Policy🟢 10security policy file detected
Fuzzing⚠️ 0project is not fuzzed
Branch-Protection🟢 5branch protection is not maximal on development and all release branches
CI-Tests🟢 1030 out of 30 merged PRs checked by a CI test -- score normalized to 10
Contributors🟢 10project has 23 contributing companies or organizations
nuget/OpenTelemetry.Instrumentation.Http 1.12.0 🟢 8.3
Details
CheckScoreReason
Maintained🟢 1030 commit(s) and 25 issue activity found in the last 90 days -- score normalized to 10
Dependency-Update-Tool🟢 10update tool detected
Code-Review🟢 10all changesets reviewed
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Token-Permissions🟢 10GitHub workflow tokens follow principle of least privilege
Binary-Artifacts🟢 10no binaries found in the repo
CII-Best-Practices🟢 5badge detected: Passing
Pinned-Dependencies🟢 9dependency not pinned by hash detected -- score normalized to 9
Vulnerabilities🟢 100 existing vulnerabilities detected
Signed-Releases⚠️ 0Project has not signed or included provenance with any releases.
Packaging🟢 10packaging workflow detected
SAST🟢 10SAST tool is run on all commits
License🟢 10license file detected
Security-Policy🟢 10security policy file detected
Fuzzing⚠️ 0project is not fuzzed
Branch-Protection🟢 5branch protection is not maximal on development and all release branches
CI-Tests🟢 1030 out of 30 merged PRs checked by a CI test -- score normalized to 10
Contributors🟢 10project has 23 contributing companies or organizations
nuget/OpenTelemetry.Instrumentation.Runtime 1.12.0 🟢 8.3
Details
CheckScoreReason
Maintained🟢 1030 commit(s) and 25 issue activity found in the last 90 days -- score normalized to 10
Dependency-Update-Tool🟢 10update tool detected
Code-Review🟢 10all changesets reviewed
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Token-Permissions🟢 10GitHub workflow tokens follow principle of least privilege
Binary-Artifacts🟢 10no binaries found in the repo
CII-Best-Practices🟢 5badge detected: Passing
Pinned-Dependencies🟢 9dependency not pinned by hash detected -- score normalized to 9
Vulnerabilities🟢 100 existing vulnerabilities detected
Signed-Releases⚠️ 0Project has not signed or included provenance with any releases.
Packaging🟢 10packaging workflow detected
SAST🟢 10SAST tool is run on all commits
License🟢 10license file detected
Security-Policy🟢 10security policy file detected
Fuzzing⚠️ 0project is not fuzzed
Branch-Protection🟢 5branch protection is not maximal on development and all release branches
CI-Tests🟢 1030 out of 30 merged PRs checked by a CI test -- score normalized to 10
Contributors🟢 10project has 23 contributing companies or organizations

Scanned Files

  • dotnet/w365-computer-use/sample-agent/W365ComputerUseSample.csproj

return;
}

string[] parts = authorizationHeader?.Split(' ')!;
{
context.Options.TokenValidationParameters.ConfigurationManager = _openIdMetadataCache.GetOrAdd(validationOptions.AzureBotServiceOpenIdMetadataUrl, key =>
{
return new ConfigurationManager<OpenIdConnectConfiguration>(validationOptions.AzureBotServiceOpenIdMetadataUrl, new OpenIdConnectConfigurationRetriever(), new HttpClient())
{
context.Options.TokenValidationParameters.ConfigurationManager = _openIdMetadataCache.GetOrAdd(validationOptions.OpenIdMetadataUrl, key =>
{
return new ConfigurationManager<OpenIdConnectConfiguration>(validationOptions.OpenIdMetadataUrl, new OpenIdConnectConfigurationRetriever(), new HttpClient())
private static async Task<(string agentId, string tenantId)> ResolveTenantAndAgentId(ITurnContext turnContext, UserAuthorization authSystem, string authHandlerName)
{
string agentId = "";
if (turnContext.Activity.IsAgenticRequest())
Comment on lines +368 to +375
if (type == "function_call" || type == "function_call_output")
{
if (item.TryGetProperty("call_id", out var idProp)
&& cuaOnlyCallIds.Contains(idProp.GetString() ?? string.Empty))
{
return false;
}
}
Comment on lines +214 to +218
catch (Exception ex)
{
_logger.LogWarning(ex, "CUA intent classifier threw — defaulting to needsCua=true.");
return true;
}
Comment on lines +506 to +509
catch (Exception ex)
{
logger.LogWarning(ex, "Failed to end W365 session");
}
Comment on lines +47 to +50
catch (Exception ex)
{
logger?.LogWarning("There was an error registering for observability: {Message}", ex.Message);
}
Comment on lines +865 to +868
catch (Exception ex)
{
logger.LogWarning(ex, "Best-effort EndSession during recovery failed");
}
Comment on lines +931 to +935
catch (Exception ex)
{
_logger.LogError(ex, "Function call {Name} threw. call_id={CallId}", name, callId);
return CreateFunctionOutput(callId, $"Error: {ex.Message}");
}
@desmarest desmarest marked this pull request as ready for review May 5, 2026 23:46
@desmarest desmarest requested a review from a team as a code owner May 5, 2026 23:46
Copilot AI review requested due to automatic review settings May 5, 2026 23:46
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a new .NET sample agent (dotnet/w365-computer-use/) that demonstrates running a “computer use” loop against a Windows 365 Cloud PC via the W365 Computer Use MCP server, including optional function-tool routing and OneDrive screenshot upload/sharing.

Changes:

  • Introduces a new Agent Framework sample project/solution with configuration, local/production setup docs, and deployment guidance.
  • Implements a CUA orchestrator that translates computer_call actions into MCP tool invocations, captures screenshots, and iterates until completion/end-session.
  • Adds basic OpenTelemetry wiring + custom Activity/Meter helpers and an ASP.NET JWT token validation helper.

Reviewed changes

Copilot reviewed 19 out of 19 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
dotnet/w365-computer-use/W365ComputerUseSample.sln New solution file wiring up the sample-agent project.
dotnet/w365-computer-use/sample-agent/W365ComputerUseSample.csproj New web project with Agent Framework + MCP + OTEL dependencies.
dotnet/w365-computer-use/sample-agent/ToolingManifest.json Declares the W365 MCP server metadata (reference material for tooling).
dotnet/w365-computer-use/sample-agent/telemetry/AgentMetrics.cs Adds custom Activity/Meter helpers for agent/http operations (contains a critical async/finalization bug).
dotnet/w365-computer-use/sample-agent/telemetry/A365OtelWrapper.cs Wraps observed operations with baggage + observability token cache registration (has agentId fallback bug).
dotnet/w365-computer-use/sample-agent/ServiceExtensions.cs Adds OpenTelemetry resource/tracing/metrics configuration for the sample.
dotnet/w365-computer-use/sample-agent/README.md End-to-end documentation (setup, Playground testing, production deployment, troubleshooting).
dotnet/w365-computer-use/sample-agent/Properties/launchSettings.json Local dev profile for http://localhost:3978.
dotnet/w365-computer-use/sample-agent/Program.cs ASP.NET host setup, /api/messages + /api/health endpoints, and agent registration.
dotnet/w365-computer-use/sample-agent/nuget.config Pins package source configuration to nuget.org.
dotnet/w365-computer-use/sample-agent/ComputerUse/Models/ComputerUseModels.cs DTOs for Azure OpenAI Responses “computer use” request/response/tool definitions.
dotnet/w365-computer-use/sample-agent/ComputerUse/ICuaModelProvider.cs Abstraction for sending serialized Responses API requests.
dotnet/w365-computer-use/sample-agent/ComputerUse/ComputerUseOrchestrator.cs Core CUA loop, action→MCP mapping, screenshot capture, OneDrive upload/share (has a couple concrete runtime/URL/cancellation issues).
dotnet/w365-computer-use/sample-agent/ComputerUse/AzureOpenAIModelProvider.cs Azure OpenAI Responses API client using API key auth.
dotnet/w365-computer-use/sample-agent/AspNetExtensions.cs Adds JWT bearer validation helper (contains token parsing robustness issues).
dotnet/w365-computer-use/sample-agent/appsettings.json Default configuration with placeholders for AOAI + connection + screenshot settings.
dotnet/w365-computer-use/sample-agent/Agent/MyAgent.cs Agent turn handling: intent classification fast-path, tool loading, CUA orchestration, streaming updates (has async-callback misuse).
dotnet/w365-computer-use/sample-agent/a365.config.example.json Example a365 CLI config template with placeholders.
dotnet/w365-computer-use/sample-agent/.gitignore Ignores local overrides, screenshots, and deploy artifacts for this sample.

Comment on lines +86 to +110
public static Task InvokeObservedAgentOperation(string operationName, ITurnContext context, Func<Task> func)
{
MessageProcessedCounter.Add(1);
var activity = InitializeMessageHandlingActivity(operationName, context);
var stopwatch = Stopwatch.StartNew();
try
{
return func();
}
catch (Exception ex)
{
activity?.SetStatus(ActivityStatusCode.Error, ex.Message);
activity?.AddEvent(new ActivityEvent("exception", DateTimeOffset.UtcNow, new()
{
["exception.type"] = ex.GetType().FullName,
["exception.message"] = ex.Message,
["exception.stacktrace"] = ex.StackTrace
}));
throw;
}
finally
{
stopwatch.Stop();
FinalizeMessageHandlingActivity(activity, context, stopwatch.ElapsedMilliseconds, true);
}
Comment on lines +119 to +128
if (parts.Length != 2 || parts[0] != "Bearer")
{
context.Options.TokenValidationParameters.ConfigurationManager ??= options.ConfigurationManager as BaseConfigurationManager;
await Task.CompletedTask.ConfigureAwait(false);
return;
}

JwtSecurityToken token = new(parts[1]);
string issuer = token.Claims.FirstOrDefault(claim => claim.Type == AuthenticationConstants.IssuerClaim)?.Value!;

Comment on lines +71 to +73
agentId = agentId ?? Guid.Empty.ToString();
string? tempTenantId = turnContext?.Activity?.Conversation?.TenantId ?? turnContext?.Activity?.Recipient?.TenantId;
string tenantId = tempTenantId ?? Guid.Empty.ToString();
if (_toolType == "computer" && session.ConversationHistory.Count == 0 && session.SessionStarted)
{
var initialScreenshot = await CaptureScreenshotAsync(w365Tools, session.W365SessionId, cancellationToken);
var initialName = $"{conversationId[..8]}_{++session.ScreenshotCounter:D3}_initial";
Comment on lines +1010 to +1012
var driveBase = string.IsNullOrEmpty(_oneDriveUserId)
? "https://graph.microsoft.com/v1.0/me/drive"
: $"https://graph.microsoft.com/v1.0/users/{_oneDriveUserId}/drive";
Comment on lines +1018 to +1024
using var request = new HttpRequestMessage(HttpMethod.Put, url);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", graphAccessToken);
request.Content = new ByteArrayContent(Convert.FromBase64String(base64Data));
request.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("image/png");

var response = await _httpClient.SendAsync(request);
if (response.IsSuccessStatusCode)
Comment on lines +162 to +173
var (_, nonCuaAdditionalTools) = await GetToolsAsync(turnContext, ToolAuthHandlerName);
var directResponse = await _orchestrator.RunAsync(
conversationId,
userText,
w365Tools: [],
additionalTools: nonCuaAdditionalTools,
graphAccessToken: null,
onStatusUpdate: status => turnContext.StreamingResponse.QueueInformativeUpdateAsync(status).ConfigureAwait(false),
onCuaStarting: null,
onFolderLinkReady: null,
includeCuaTool: false,
cancellationToken: cancellationToken);
Comment on lines +209 to +216
var response = await _orchestrator.RunAsync(
conversationId,
userText,
w365Tools,
additionalTools: additionalTools,
graphAccessToken: graphToken,
onStatusUpdate: status => turnContext.StreamingResponse.QueueInformativeUpdateAsync(status).ConfigureAwait(false),
onCuaStarting: async (isNewSession) =>
Adds dotnet/w365-computer-use/ as a new sample alongside agent-framework,
autonomous, and semantic-kernel. The sample demonstrates an Agent 365
agent that controls a Windows 365 Cloud PC via the W365 Computer Use MCP
server using Azure OpenAI's computer-use model.

Highlights:
- Intent classifier skips the W365 session-acquisition handshake on
  non-CUA messages (chit-chat, mail/calendar function-tool requests).
- Full CUA loop translates model computer_call actions to MCP tool
  invocations, captures screenshots, and feeds them back to the model
  until it emits OnTaskComplete or EndSession.
- Per-prompt OneDrive screenshot folders ({yyyy-MM-dd}/{HHmmss}_{slug})
  with org-scoped sharing links surfaced to the user.
- Filters ATG's synthetic "Error" sentinel tool out of the LLM tool list
  to avoid Azure OpenAI invalid_function_parameters rejections, while
  still reading its description for user-facing error messaging.
- Supports both computer-use-preview and gpt-5.4 / gpt-5.4-mini.
@desmarest desmarest force-pushed the users/bertd/w365-computer-use-sample branch from ffe7c23 to 8725549 Compare May 6, 2026 18:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants