2a815ee64e
Spike decided the worker south side in favour of herdr's native agent.*
namespace over pane+send_text. Proven against live herdr 0.7.0:
agent.start takes a first-class env map that reaches the process
environment (ANTHROPIC_BASE_URL confirmed via agent.read), and herdr
tracks each worker's Claude session UUID itself.
- AgentControl: start/send/read/get/status/list + pane.close over agent.*.
- Agent/AgentStatus: projection of herdr agent records (session UUID,
injectable status gate).
- WorkerService: build worker env (base_url/token/model/config dir),
assertWorker BEFORE any herdr call, then agent.start.
- REST: GET /agents (discovery by session UUID), POST /workers (201, or
403 subscription_boundary), DELETE /workers/{paneId}.
- Full stack smoke-tested live: POST->guard->agent.start->new pane,
GET /agents lists it, DELETE closes it.
- Tests: 21 unit/acceptance + 4 contract (incl. a live end-to-end probe
spawn that proves env injection and cleans up its pane).
No agent.stop in herdr (use pane.close); herdr id must be a string;
one request per connection — all pinned by contract tests.
64 lines
2.5 KiB
Java
64 lines
2.5 KiB
Java
package dev.ltms.bridged.herdr;
|
|
|
|
import org.junit.jupiter.api.Tag;
|
|
import org.junit.jupiter.api.Test;
|
|
|
|
import java.nio.file.Files;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
|
|
import static org.junit.jupiter.api.Assertions.*;
|
|
import static org.junit.jupiter.api.Assumptions.assumeTrue;
|
|
|
|
/**
|
|
* Contract test for the {@code agent.*} south side against a REAL herdr, locking in
|
|
* the CB-102 spike findings. It spawns a HARMLESS probe command (never {@code claude},
|
|
* so no subscription/token involvement), proves the {@code env} map reaches the process
|
|
* environment, exercises status/read, and always tears the pane down.
|
|
*
|
|
* <p>Tagged {@code contract}; run with {@code mvn test -Pcontract}.
|
|
*/
|
|
@Tag("contract")
|
|
class AgentControlContractTest {
|
|
|
|
private boolean noSocket() {
|
|
return !Files.exists(UnixSocketHerdrClient.defaultSocketPath());
|
|
}
|
|
|
|
@Test
|
|
void startInjectsEnvThenReadAndClose() throws Exception {
|
|
assumeTrue(!noSocket(), "no herdr socket — skipping");
|
|
try (UnixSocketHerdrClient herdr = UnixSocketHerdrClient.connect()) {
|
|
AgentControl agents = new AgentControl(herdr);
|
|
|
|
Agent probe = agents.start(
|
|
"__contract__",
|
|
List.of("bash", "-c", "printf 'PROBE_BASE=[%s]\\n' \"$ANTHROPIC_BASE_URL\"; sleep 20"),
|
|
Map.of("ANTHROPIC_BASE_URL", "http://gx00.gw:8000"));
|
|
|
|
assertNotNull(probe.terminalId());
|
|
assertNotNull(probe.paneId());
|
|
try {
|
|
// Give the shell a moment to print, then confirm env reached the process.
|
|
Thread.sleep(800);
|
|
String visible = agents.read(probe.terminalId(), "visible");
|
|
assertTrue(visible.contains("PROBE_BASE=[http://gx00.gw:8000]"),
|
|
"env map must reach the process; saw: " + visible);
|
|
|
|
// Status is queryable; the probe appears in the agent list.
|
|
assertNotNull(agents.status(probe.terminalId()));
|
|
assertTrue(agents.list().stream()
|
|
.anyMatch(a -> probe.terminalId().equals(a.terminalId())),
|
|
"spawned probe should appear in agent.list");
|
|
} finally {
|
|
agents.close(probe.paneId());
|
|
}
|
|
|
|
// After close the pane is gone.
|
|
assertFalse(agents.list().stream()
|
|
.anyMatch(a -> probe.terminalId().equals(a.terminalId())),
|
|
"closed probe should no longer be listed");
|
|
}
|
|
}
|
|
}
|