Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/Cli/Microsoft.Maui.Cli/DevFlow/Mcp/Tools/AgentTools.cs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ public static async Task<string> Capabilities(
var agent = await session.GetAgentClientAsync(agentPort);
var capabilities = await agent.GetCapabilitiesAsync();
if (capabilities.ValueKind == System.Text.Json.JsonValueKind.Undefined)
return "Agent not responding. Is the app running?";
return "Unable to retrieve capabilities. The agent may not be running, or may not support this feature (older version).";
return CliJson.SerializeUntyped(capabilities, indented: false);
}

Expand Down
13 changes: 10 additions & 3 deletions src/Cli/Microsoft.Maui.Cli/DevFlow/Mcp/Tools/BatchTools.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,15 @@ public static async Task<string> Batch(
actions.Add(obj);
}

var agent = await session.GetAgentClientAsync(agentPort);
var result = await agent.BatchAsync(actions, continueOnError);
return CliJson.SerializeUntyped(result, indented: false);
try
{
var agent = await session.GetAgentClientAsync(agentPort);
var result = await agent.BatchAsync(actions, continueOnError);
return CliJson.SerializeUntyped(result, indented: false);
}
catch (Exception ex) when (ex is HttpRequestException or OperationCanceledException or System.Text.Json.JsonException)
{
return $"Batch request failed: {ex.Message}. Verify the app is running and the agent supports the batch endpoint.";
}
}
}
35 changes: 21 additions & 14 deletions src/DevFlow/Microsoft.Maui.DevFlow.Driver/AgentClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -169,14 +169,17 @@ public async Task<bool> KeyAsync(string key, string? elementId = null, string? t

public async Task<bool> GestureAsync(string type, string? elementId = null, string? direction = null, double? distance = null, int? durationMs = null)
{
return await PostActionAsync($"{UiApi}/actions/gesture", new JsonObject
var payload = new JsonObject
{
["elementId"] = elementId,
["type"] = type,
["direction"] = direction,
["distance"] = distance,
["durationMs"] = durationMs
});
["type"] = type
};

if (elementId is not null) payload["elementId"] = elementId;
if (direction is not null) payload["direction"] = direction;
if (distance.HasValue) payload["distance"] = distance.Value;
if (durationMs.HasValue) payload["durationMs"] = durationMs.Value;

Comment thread
jfversluis marked this conversation as resolved.
return await PostActionAsync($"{UiApi}/actions/gesture", payload);
Comment on lines 170 to +182
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

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

This change fixes a real serialization/deserialization issue (null distance/durationMs breaks the agent’s non-nullable DTO), but it isn’t covered by tests. There are existing AgentClient HTTP-shape tests (e.g., Tap/Fill/Batch) in Microsoft.Maui.DevFlow.Tests; please add a similar test for GestureAsync verifying that when distance/durationMs are null they are omitted from the POST body (and included when provided).

Copilot uses AI. Check for mistakes.
}

public async Task<JsonElement> BatchAsync(IEnumerable<JsonObject> actions, bool continueOnError = false)
Expand Down Expand Up @@ -204,16 +207,20 @@ public async Task<bool> ScrollAsync(string? elementId = null, double deltaX = 0,
{
var url = $"{UiApi}/actions/scroll";
if (window != null) url += $"?window={window}";
return await PostActionAsync(url, new JsonObject

var payload = new JsonObject
{
["elementId"] = elementId,
["deltaX"] = deltaX,
["deltaY"] = deltaY,
["animated"] = animated,
["itemIndex"] = itemIndex,
["groupIndex"] = groupIndex,
["scrollToPosition"] = scrollToPosition
});
["animated"] = animated
};

if (elementId is not null) payload["elementId"] = elementId;
if (itemIndex.HasValue) payload["itemIndex"] = itemIndex.Value;
if (groupIndex.HasValue) payload["groupIndex"] = groupIndex.Value;
if (scrollToPosition is not null) payload["scrollToPosition"] = scrollToPosition;

return await PostActionAsync(url, payload);
}

/// <summary>
Expand Down
Loading