From ce550a127b7dc972651bd15b9f09979a2c38688f Mon Sep 17 00:00:00 2001 From: tanaka takashi Date: Wed, 12 Aug 2026 00:02:23 +0900 Subject: feat: harden Python workflow and structured planning Replace the uv-specific skill with a portable Python skill and rewrite the default user system prompt as a concise, structured Markdown execution policy. Normalize a single fenced structured-JSON response, retry invalid skill selection with think disabled, validate selected skill output, and make coding plans keep editing and verification in one task. Document the file_edit patch payload, expose that contract in the tool catalog, and convert malformed patch input into a retryable tool failure instead of terminating the agent task. Update configuration and runtime tests for the Python skill, exact tool catalog, structured-output retry, and implementation-and-verification planning guidance. Verified with 82 Agent.Core tests and an agent-mcp Python scheduling scenario with 49 passing unittest cases. --- src/Agent.Windows/Tools/WindowsToolBackend.cs | 28 +++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) (limited to 'src/Agent.Windows/Tools/WindowsToolBackend.cs') diff --git a/src/Agent.Windows/Tools/WindowsToolBackend.cs b/src/Agent.Windows/Tools/WindowsToolBackend.cs index c432894..5cbf853 100644 --- a/src/Agent.Windows/Tools/WindowsToolBackend.cs +++ b/src/Agent.Windows/Tools/WindowsToolBackend.cs @@ -97,6 +97,13 @@ public sealed class WindowsToolBackend(WindowsSandboxSettings settings, ISecurit ErrorCode: ex.Code, ErrorCategory: ex.Category, TargetPath: ex.TargetPath, RequiredPermission: ex.RequiredPermission, Resolution: ex.Resolution)); } + catch (Exception ex) when (ex is ToolValidationException or JsonException or InvalidOperationException) + { + return Task.FromResult(new ToolExecutionResult(false, ex.Message, false, + $"{toolName}:tool.input_invalid:{ex.Message}", RetryAllowed: true, + ErrorCode: "tool.input_invalid", ErrorCategory: "tool", + Resolution: "Correct the Tool arguments using the published schema and retry.")); + } } public async Task EndTaskAsync(Guid taskId, bool success, CancellationToken cancellationToken) @@ -300,12 +307,21 @@ public sealed class WindowsToolBackend(WindowsSandboxSettings settings, ISecurit private static string ApplyPatch(string path, string patch) { string current = File.Exists(path) ? File.ReadAllText(path) : string.Empty; - using JsonDocument document = JsonDocument.Parse(patch); - string oldText = document.RootElement.GetProperty("old_text").GetString()!; - string newText = document.RootElement.GetProperty("new_text").GetString()!; - int first = current.IndexOf(oldText, StringComparison.Ordinal); - if (first < 0 || current.IndexOf(oldText, first + oldText.Length, StringComparison.Ordinal) >= 0) throw new InvalidOperationException("Patch old_text must match exactly once."); - return current[..first] + newText + current[(first + oldText.Length)..]; + JsonDocument document; + try { document = JsonDocument.Parse(patch); } + catch (JsonException) { throw new ToolValidationException("Patch must be a JSON object string containing old_text and new_text; unified diff is unsupported."); } + using (document) + { + if (document.RootElement.ValueKind != JsonValueKind.Object || + !document.RootElement.TryGetProperty("old_text", out JsonElement oldElement) || oldElement.ValueKind != JsonValueKind.String || + !document.RootElement.TryGetProperty("new_text", out JsonElement newElement) || newElement.ValueKind != JsonValueKind.String) + throw new ToolValidationException("Patch must be a JSON object string containing string old_text and new_text fields."); + string oldText = oldElement.GetString()!; + string newText = newElement.GetString()!; + int first = current.IndexOf(oldText, StringComparison.Ordinal); + if (first < 0 || current.IndexOf(oldText, first + oldText.Length, StringComparison.Ordinal) >= 0) throw new InvalidOperationException("Patch old_text must match exactly once."); + return current[..first] + newText + current[(first + oldText.Length)..]; + } } private sealed class TaskContext(TaskResourceJournal journal, TaskAclManager acl, Guid sessionId) -- cgit v1.2.3