summaryrefslogtreecommitdiff
path: root/src/Agent.Windows/Tools
diff options
context:
space:
mode:
authortanaka takashi <isazaka@gmail.com>2026-08-12 00:02:23 +0900
committertanaka takashi <isazaka@gmail.com>2026-08-12 00:02:23 +0900
commitce550a127b7dc972651bd15b9f09979a2c38688f (patch)
treef4e09d0f64f74a585e015e6f548c5589fd2442ba /src/Agent.Windows/Tools
parent55db81082f9165a3965f860cd618bac4242c56b1 (diff)
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.
Diffstat (limited to 'src/Agent.Windows/Tools')
-rw-r--r--src/Agent.Windows/Tools/WindowsToolBackend.cs28
1 files changed, 22 insertions, 6 deletions
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)