diff --git a/fleetd/src/test/java/dev/ltms/fleet/mcp/FleetMcpTest.java b/fleetd/src/test/java/dev/ltms/fleet/mcp/FleetMcpTest.java index 0664118..94e74ac 100644 --- a/fleetd/src/test/java/dev/ltms/fleet/mcp/FleetMcpTest.java +++ b/fleetd/src/test/java/dev/ltms/fleet/mcp/FleetMcpTest.java @@ -7,6 +7,7 @@ import dev.ltms.fleet.auth.Role; import dev.ltms.fleet.config.FleetConfig; import dev.ltms.fleet.guard.SubscriptionGuard; import dev.ltms.fleet.herdr.AgentControl; +import dev.ltms.fleet.herdr.AgentStatus; import dev.ltms.fleet.herdr.FakeHerdr; import dev.ltms.fleet.herdr.PaneLocator; import dev.ltms.fleet.herdr.WorkspaceControl; @@ -57,9 +58,10 @@ class FleetMcpTest { private final FakeHerdr herdr = new FakeHerdr(); private final AgentControl agents = new AgentControl(herdr); + private final Injector injector = new Injector(agents); private final Rendezvous rendezvous = new Rendezvous(); private final InMemoryReplyInbox inbox = new InMemoryReplyInbox(); - private final MessageService messages = new MessageService(agents, new Injector(agents), rendezvous, inbox); + private final MessageService messages = new MessageService(agents, injector, rendezvous, inbox); @BeforeEach void setUp() { @@ -298,6 +300,35 @@ class FleetMcpTest { assertTrue(textOf(res).contains("no reply"), "got: " + textOf(res)); } + /** + * fleetd #571 (ticket CORRECTION 5): {@code formatReply}'s {@code TIMED_OUT_UNCONFIRMED} arm is + * the one message whose whole job is to stop a caller retrying a delivery that may already have + * arrived. Pin that its wording is actually distinct from the queued/working arm's retry + * invitation — a mutation that swapped this arm's text for that one still passed every other + * test in this suite, because nothing asserted the specific wording. + */ + @Test + void sendTimesOutWithAnUnconfirmedNoteNotARetryInvitation() throws Exception { + herdr.agentSendFailsWith("send_failed"); + CompletableFuture send = CompletableFuture.supplyAsync( + () -> FleetMcp.send(messages, T, "hi", 150L, null, Set.of())); + long deadline = System.currentTimeMillis() + 2000; + while (!rendezvous.isWaiting(T) && System.currentTimeMillis() < deadline) { + //noinspection BusyWait + Thread.sleep(5); + } + assertTrue(rendezvous.isWaiting(T), "send should have opened its rendezvous waiter"); + injector.onStatus(T, AgentStatus.IDLE); // triggers the failing delivery attempt -> ATTEMPTED + + McpSchema.CallToolResult res = send.get(5, TimeUnit.SECONDS); + String text = textOf(res); + assertNotEquals(Boolean.TRUE, res.isError(), "a timeout is informational, not a tool error"); + assertTrue(text.contains("delivery unconfirmed"), "got: " + text); + assertFalse(text.contains("retry or poll status"), + "an unconfirmed delivery must not carry the queued/working arm's retry invitation — " + + "a resend here can double-deliver the same brief: got " + text); + } + @Test void sendRejectsMissingArgs() { assertTrue(FleetMcp.send(messages, null, "hi", null, null, Set.of()).isError());