using System.Text.Json; using ClawdDotNet.Core.State; using ClawdDotNet.Core.Tasks; using ClawdDotNet.Core.Tests.Infrastructure; using ClawdDotNet.Core.Tools; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Shouldly; namespace ClawdDotNet.Core.Tests.Tasks; /// /// Der tool_job-Dispatch — ersetzt den ToolJobScheduler. Ein fälliger Poll-Task tickt einen /// und weckt den Agenten nur, wenn der Tick etwas meldet. /// public sealed class ToolJobDispatchTests { private static TaskItem PollTask(string agent = "agent-a") => new() { Id = "tj-1", Title = "Poll", Type = TaskItemType.ToolJob, ToolName = "PollTool", JobTypeId = "poll", Assignee = "@" + agent }; private static EngineTaskDispatcher Dispatcher(EngineFixture fixture) => new(fixture.Engine, () => fixture.Agents, "test-instance", fixture.Registry, fixture.StateStore, NullLoggerFactory.Instance); [Fact] public async Task Ein_Poll_ohne_Fund_weckt_den_Agenten_nicht() { var tool = new FakeJobTool(ToolJobResult.NoAction("nichts Neues")); var fixture = new EngineFixture().WithTool(tool); fixture.AddAgent("agent-a", "PollTool"); var ok = await Dispatcher(fixture).DispatchAsync(PollTask(), default); ok.ShouldBeTrue("ein Poll ohne Fund ist kein Fehler"); tool.Ticks.ShouldBe(1); fixture.Client.ReceivedRequests.ShouldBeEmpty("kein Agent-Lauf, wenn nichts zu wecken war"); } [Fact] public async Task Ein_Poll_mit_Fund_weckt_den_Agenten() { var tool = new FakeJobTool(ToolJobResult.Wake("Neue Nachricht eingetroffen")); var fixture = new EngineFixture().WithTool(tool); fixture.AddAgent("agent-a", "PollTool"); fixture.Client.RespondsWithText("verarbeitet"); var ok = await Dispatcher(fixture).DispatchAsync(PollTask(), default); ok.ShouldBeTrue(); tool.Ticks.ShouldBe(1); fixture.Client.ReceivedRequests.ShouldNotBeEmpty("der Agent wurde geweckt"); } [Fact] public async Task Ein_unbekanntes_Poll_Tool_meldet_einen_Fehler() { var fixture = new EngineFixture(); // PollTool nicht registriert fixture.AddAgent("agent-a"); (await Dispatcher(fixture).DispatchAsync(PollTask(), default)).ShouldBeFalse(); } /// Ein Tool, das zugleich Poll-Provider ist — zählt Ticks, liefert ein festes Ergebnis. private sealed class FakeJobTool(ToolJobResult result) : IAgentTool, IToolJobProvider { public int Ticks { get; private set; } public string Name => "PollTool"; public string Description => "Test-Poll"; public JsonElement InputSchema { get; } = JsonDocument.Parse("""{ "type": "object" }""").RootElement.Clone(); public Task ExecuteAsync(JsonElement input, AgentToolContext context, CancellationToken ct) => Task.FromResult(ToolResult.Ok("ok")); public IReadOnlyList GetJobDefinitions() => []; public Task ExecuteJobAsync( string jobTypeId, IReadOnlyDictionary toolConfig, IStateStore stateStore, ILogger logger, CancellationToken ct, string? agentId = null, string? workspacePath = null) { Ticks++; return Task.FromResult(result); } } }