From a0cd053fd923b9cfb87bb4ad1de180a3db595dc1 Mon Sep 17 00:00:00 2001 From: Dai Ha Date: Fri, 14 Aug 2026 21:45:28 +0200 Subject: [PATCH 1/2] CB-560: mark architect members present --- .../java/dev/ltms/bridged/auth/Principal.java | 12 +++++++++++- .../main/java/dev/ltms/bridged/mcp/BridgeMcp.java | 15 +++++++++++---- .../java/dev/ltms/bridged/mcp/BridgeMcpTest.java | 11 +++++++++++ 3 files changed, 33 insertions(+), 5 deletions(-) diff --git a/bridged/src/main/java/dev/ltms/bridged/auth/Principal.java b/bridged/src/main/java/dev/ltms/bridged/auth/Principal.java index 6341e22..32b49d9 100644 --- a/bridged/src/main/java/dev/ltms/bridged/auth/Principal.java +++ b/bridged/src/main/java/dev/ltms/bridged/auth/Principal.java @@ -48,7 +48,7 @@ public record Principal(Role role, String terminal, long pid, String name) { * made a lead unaddressable: {@link #ownsSession} could never be true for it, so * {@code bridge_reply} was refused and one lead could send to another but never be answered. * The terminal now means "which pane is this caller", the presence map keys on - * {@link #isWorker()} instead, and a lead is a peer that can both send and receive. + * {@link #isSpawnedMember()} instead, and a lead is a peer that can both send and receive. */ public static Principal leader(String name, String terminal, long pid) { return new Principal(Role.PRIMARY, terminal, pid, name); @@ -85,6 +85,16 @@ public record Principal(Role role, String terminal, long pid, String name) { return role == Role.WORKER; } + /** + * Whether this caller is a spawned member with its own pane. + * + *

Both workers and architects are spawned members. A lead is excluded because recording it + * as present would count it as an available member in the roster. + */ + public boolean isSpawnedMember() { + return role == Role.WORKER || role == Role.ARCHITECT; + } + public boolean isAnonymous() { return role == Role.ANONYMOUS; } diff --git a/bridged/src/main/java/dev/ltms/bridged/mcp/BridgeMcp.java b/bridged/src/main/java/dev/ltms/bridged/mcp/BridgeMcp.java index 7af04b3..ee9046d 100644 --- a/bridged/src/main/java/dev/ltms/bridged/mcp/BridgeMcp.java +++ b/bridged/src/main/java/dev/ltms/bridged/mcp/BridgeMcp.java @@ -109,10 +109,10 @@ public final class BridgeMcp { ? callers.resolve(req.getRemoteAddr(), req.getRemotePort(), req.getHeader("Authorization")) : legacyPrincipal(identity, req.getRemoteAddr(), req.getRemotePort()); - // CB-532: guard on the ROLE, not on the terminal being null. A named lead now - // carries its pane too, and enrolling a lead in the worker presence map would - // have it counted as an available worker. - if (p.isWorker()) presence.markPresent(p.terminal()); + // CB-532: guard on the ROLE, not on the terminal being null. This excludes a + // lead, which carries its pane too, while including every spawned member role. + // Enrolling a lead would count it as an available member in the roster. + markSpawnedMemberPresent(p, presence); return McpTransportContext.create(Map.of( CALLER_TERMINAL, orEmpty(p.terminal()), CALLER_PID, Long.toString(p.pid()), @@ -365,6 +365,13 @@ public final class BridgeMcp { return transport; } + /** Mark a connected spawned member available for the injector readiness gate. */ + static void markSpawnedMemberPresent(Principal caller, MemberPresence presence) { + if (caller.isSpawnedMember()) { + presence.markPresent(caller.terminal()); + } + } + /** Graceful shutdown of the MCP server. */ public void close() { server.closeGracefully(); diff --git a/bridged/src/test/java/dev/ltms/bridged/mcp/BridgeMcpTest.java b/bridged/src/test/java/dev/ltms/bridged/mcp/BridgeMcpTest.java index 94a4737..874e8da 100644 --- a/bridged/src/test/java/dev/ltms/bridged/mcp/BridgeMcpTest.java +++ b/bridged/src/test/java/dev/ltms/bridged/mcp/BridgeMcpTest.java @@ -12,6 +12,7 @@ import dev.ltms.bridged.msg.Rendezvous; import dev.ltms.bridged.session.FakeWorktrees; import dev.ltms.bridged.session.SessionManager; import dev.ltms.bridged.peer.MemberRole; +import dev.ltms.bridged.inject.MemberPresence; import dev.ltms.bridged.session.MemberSession; import dev.ltms.bridged.session.WorktreeRequest; import dev.ltms.bridged.member.ClaudeCodeLauncher; @@ -404,6 +405,16 @@ class BridgeMcpTest { assertDoesNotThrow(() -> messages.ackReply("term_a", msgId)); } + @Test + void anArchitectMcpCallerIsPresent() { + Principal architect = Principal.architect("lead-designer", "term_design", 400); + MemberPresence presence = new MemberPresence(); + + BridgeMcp.markSpawnedMemberPresent(architect, presence); + + assertTrue(presence.isPresent("term_design")); + } + @Test void statusReportsLiveAgentStatus() { FakeHerdr blocked = new FakeHerdr().agentStatus("blocked"); From fa0612859b89736bb144f7983e21ec10c6a5f0e4 Mon Sep 17 00:00:00 2001 From: Dai Ha Date: Fri, 14 Aug 2026 21:48:58 +0200 Subject: [PATCH 2/2] CB-560: document spawned member presence --- .../src/main/java/dev/ltms/bridged/Bridged.java | 15 ++++++++------- .../java/dev/ltms/bridged/mcp/BridgeMcpTest.java | 16 +++++++++++++++- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/bridged/src/main/java/dev/ltms/bridged/Bridged.java b/bridged/src/main/java/dev/ltms/bridged/Bridged.java index 87be519..3cecfd0 100644 --- a/bridged/src/main/java/dev/ltms/bridged/Bridged.java +++ b/bridged/src/main/java/dev/ltms/bridged/Bridged.java @@ -428,19 +428,20 @@ public final class Bridged { } /** - * The {@link Injector}'s readiness gate (CB-534): a target is deliverable if it is a worker whose - * agent has connected the bridge MCP, or a lead. + * The {@link Injector}'s readiness gate (CB-534): a target is deliverable if it is a spawned + * member whose agent has connected the bridge MCP, or a lead. * - *

The gate exists for one reason — to hold a delivery out of a spawned worker's boot + *

The gate exists for one reason — to hold a delivery out of a spawned member's boot * window, where herdr already reports {@code idle} but the TUI would drop an injected paste. That * hazard is a property of spawning. A lead is never spawned: the operator started it and named it * (or labelled its tab) only once it was up, so there is no boot window to guard. * *

A lead is also never enrolled in {@link MemberPresence} — {@code BridgeMcp} marks presence - * only for a worker, deliberately, since that map doubles as the worker roster's availability - * signal and a lead counted there would show up as an available worker. So without the second - * disjunct a lead is permanently un-deliverable: every lead→lead send sat on the gate for - * {@code READINESS_GRACE_POLLS} (~60s) and then failed having never been typed into the pane. + * for every spawned member (worker and architect), deliberately, since that map doubles as the + * member roster's availability signal and a lead counted there would show up as an available + * member. So without the second disjunct a lead is permanently un-deliverable: every + * lead→lead send sat on the gate for {@code READINESS_GRACE_POLLS} (~60s) and then failed + * having never been typed into the pane. * *

The lead set is read through the supplier on each call rather than snapshotted, so a lead * discovered by {@code leadScan} after startup becomes deliverable without a restart. diff --git a/bridged/src/test/java/dev/ltms/bridged/mcp/BridgeMcpTest.java b/bridged/src/test/java/dev/ltms/bridged/mcp/BridgeMcpTest.java index 874e8da..ab264ae 100644 --- a/bridged/src/test/java/dev/ltms/bridged/mcp/BridgeMcpTest.java +++ b/bridged/src/test/java/dev/ltms/bridged/mcp/BridgeMcpTest.java @@ -406,15 +406,29 @@ class BridgeMcpTest { } @Test - void anArchitectMcpCallerIsPresent() { + void spawnedMembersAreMarkedPresent() { + Principal worker = Principal.worker("term_worker", 200); Principal architect = Principal.architect("lead-designer", "term_design", 400); MemberPresence presence = new MemberPresence(); + BridgeMcp.markSpawnedMemberPresent(worker, presence); BridgeMcp.markSpawnedMemberPresent(architect, presence); + assertTrue(presence.isPresent("term_worker")); assertTrue(presence.isPresent("term_design")); } + @Test + void nonMembersAreNotMarkedPresent() { + Principal lead = Principal.leader("opus", "term_lead", 100); + MemberPresence presence = new MemberPresence(); + + BridgeMcp.markSpawnedMemberPresent(lead, presence); + BridgeMcp.markSpawnedMemberPresent(Principal.anonymous(), presence); + + assertFalse(presence.isPresent("term_lead")); + } + @Test void statusReportsLiveAgentStatus() { FakeHerdr blocked = new FakeHerdr().agentStatus("blocked");