Watch
0
0
Fork
You've already forked hyperhive
0

swarm-controller: replay a subagent's newest rows before its live tail

A subagent's terminal opened directly showed nothing until the subagent
published again: the relay was a core subscription, though its rows sit
in term-sub-<agent> for 24 hours. It now goes through the agent route's
replay-then-tail, so it opens with the subagent's newest 100 rows and
resumes after Last-Event-ID.

- term_stream.rs: the replay takes a stream name and a filter subject
  instead of an agent, and `replay_or_live` (replay, else the live core
  subscription) is the one fn both routes call. When the filter is one of
  several subjects in the stream, a fresh reader's consumer starts at the
  oldest row and drops all but the newest REPLAY_ROWS it counts as
  pending, since the stream's newest sequences may belong to sibling
  subagents. A single-subject stream (term-main-<agent>) picks its start
  with replay_start exactly as before. Tests for both subagent cases.
- subagent_term.rs: the relay calls replay_or_live on the subagent's
  subject in term-sub-<agent> and reads Last-Event-ID; module doc and
  OpenAPI text no longer say "live only".
- main.rs: the handler test passes the new headers argument.
- swarm-queue-client agent_term.rs: replay_consumer_config takes the
  filter subject instead of the agent.
- swarm-queue-client subagent_term.rs: consumer_create_grant, the
  extended CONSUMER.CREATE form with a subagent subject as filter.
- swarm-nats-auth policy.rs: the controller holds that grant; the agent
  grant's `$SWARM.term.*` filter is two tokens short of a subagent
  subject, and the test that refused it now admits it.
- docs/swarm/ui.md, README.md: a subagent terminal opens with its newest
  rows.

Fixes #5527
This commit is contained in:
atlas 2026-10-18 02:11:02 +02:00 • committed by mara
commit d9c586ef94
8 changed files with 272 additions and 71 deletions

View file

@ -135,17 +135,21 @@ pub async fn open_or_create(
}
}
/// The consumer that replays `agent`'s rows from `start_sequence` and then
/// follows new ones, delivered to `deliver_subject` in stream order.
/// The consumer that replays the rows on `filter_subject` from
/// `start_sequence` and then follows new ones, delivered to `deliver_subject`
/// in stream order. The filter is an agent's [`stream_subject`] or one
/// subagent's [`crate::subagent_term::subject`].
///
/// Ephemeral and unacknowledged: the server drops it once nothing listens
/// on `deliver_subject`, so a reader that goes away needs no delete grant.
/// `name` must be set, because the server only takes the filter from the
/// subject [`consumer_create_grant`] covers when the create names a consumer.
/// subject [`consumer_create_grant`] (or
/// [`crate::subagent_term::consumer_create_grant`]) covers when the create
/// names a consumer.
#[cfg(feature = "agent-term")]
#[must_use]
pub fn replay_consumer_config(
agent: &str,
filter_subject: String,
name: String,
deliver_subject: String,
start_sequence: u64,
@ -153,7 +157,7 @@ pub fn replay_consumer_config(
async_nats::jetstream::consumer::push::Config {
name: Some(name),
deliver_subject,
filter_subject: stream_subject(agent),
filter_subject,
deliver_policy: async_nats::jetstream::consumer::DeliverPolicy::ByStartSequence {
start_sequence,
},
@ -269,7 +273,12 @@ mod tests {
#[cfg(feature = "agent-term")]
#[test]
fn the_consumer_replays_before_it_follows() {
let cfg = replay_consumer_config("iris", "r1".to_owned(), "_INBOX.x".to_owned(), 901);
let cfg = replay_consumer_config(
stream_subject("iris"),
"r1".to_owned(),
"_INBOX.x".to_owned(),
901,
);
assert_eq!(
cfg.deliver_policy,
async_nats::jetstream::consumer::DeliverPolicy::ByStartSequence {