feat(#2569): drive a turn on a local todo signal (serve-loop select)
This commit is contained in:
parent
713d7f424c
commit
86d16efa07
3 changed files with 72 additions and 18 deletions
|
|
@ -171,6 +171,22 @@ fn synthetic_continue() -> hive_sh4re::DeliveredMessage {
|
|||
}
|
||||
}
|
||||
|
||||
/// Synthesize the message that drives a turn when an in-container producer
|
||||
/// upserted a new/changed *todo* over the in-agent socket (loose-ends v2).
|
||||
/// The harness owns the todo store locally and signals the serve loop
|
||||
/// directly — so this wake never touches the broker (no long-poll, no
|
||||
/// marker file). `id = 0` is the same non-broker sentinel as
|
||||
/// [`synthetic_continue`].
|
||||
fn synthetic_todo_message() -> hive_sh4re::DeliveredMessage {
|
||||
hive_sh4re::DeliveredMessage {
|
||||
from: "todo".into(),
|
||||
body: "you have todos — call get_loose_ends to see them".into(),
|
||||
id: 0,
|
||||
redelivered: false,
|
||||
in_reply_to: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Synthetic message that drives the single stop-checkpoint turn when c0re
|
||||
/// signals a graceful stop. The agent gets one final turn to flush durable
|
||||
/// `/state` before the container is stopped; new inbound is already fenced.
|
||||
|
|
@ -210,6 +226,10 @@ enum RecvOutcome {
|
|||
/// one stop-checkpoint turn (flush durable `/state`), reports
|
||||
/// `GracefulStopComplete`, and exits so the container can be stopped.
|
||||
GracefulStop,
|
||||
/// An in-container producer upserted a new/changed todo over the
|
||||
/// in-agent socket; the serve loop drives a `synthetic_todo_message`
|
||||
/// turn. Not a broker message — the harness signalled itself directly.
|
||||
LocalTodo,
|
||||
}
|
||||
|
||||
/// Wire surface abstraction. `AgentSurface` is the only impl — the trait
|
||||
|
|
@ -425,6 +445,26 @@ async fn serve_main<S: Surface>(socket: &Path, poll_ms: u64) -> Result<()> {
|
|||
tracing::error!(error = %e, "web_ui::serve exited with error");
|
||||
}
|
||||
});
|
||||
// In-agent todo socket (loose-ends v2): the harness owns the todo store
|
||||
// locally and serves the in-container producers on `HIVE_AGENT_SOCKET`.
|
||||
// A new/changed upsert fires `todo_wake` so the serve loop drives a turn
|
||||
// directly — no broker round-trip, no marker files. Best-effort: if the
|
||||
// store can't open, the socket just isn't served.
|
||||
let todo_wake = Arc::new(tokio::sync::Notify::new());
|
||||
match todos::Todos::open(&paths::todos_db()) {
|
||||
Ok(store) => {
|
||||
let store = Arc::new(store);
|
||||
let wake = todo_wake.clone();
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = todo_server::run(store, wake).await {
|
||||
tracing::error!(error = %e, "in-agent todo socket exited with error");
|
||||
}
|
||||
});
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!(error = ?e, "open todos db failed — in-agent todo socket disabled");
|
||||
}
|
||||
}
|
||||
if matches!(initial, LoginState::NeedsLogin) {
|
||||
login::wait_for_login(&claude_dir, login_state.clone(), &bus, poll_ms).await;
|
||||
} else {
|
||||
|
|
@ -441,6 +481,7 @@ async fn serve_main<S: Surface>(socket: &Path, poll_ms: u64) -> Result<()> {
|
|||
bus,
|
||||
stats,
|
||||
&files,
|
||||
todo_wake,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
|
@ -462,6 +503,7 @@ async fn serve_loop<S: Surface>(
|
|||
bus: Bus,
|
||||
stats: Option<TurnStats>,
|
||||
files: &turn::TurnFiles,
|
||||
todo_wake: Arc<tokio::sync::Notify>,
|
||||
) -> Result<()> {
|
||||
tracing::info!(socket = %socket.display(), "harness serve");
|
||||
S::requeue_inflight(socket).await;
|
||||
|
|
@ -476,8 +518,20 @@ async fn serve_loop<S: Surface>(
|
|||
loop {
|
||||
let next = match self_continue.take() {
|
||||
Some(msg) => msg,
|
||||
None => match S::recv_next(socket).await {
|
||||
None => match {
|
||||
// Idle wait: race the broker long-poll against a local
|
||||
// todo signal so an in-container producer's upsert drives a
|
||||
// turn without any broker round-trip. `biased` polls the
|
||||
// broker recv first, so a genuinely-ready inbox message is
|
||||
// never dropped in favour of the todo wake.
|
||||
tokio::select! {
|
||||
biased;
|
||||
o = S::recv_next(socket) => o,
|
||||
() = todo_wake.notified() => RecvOutcome::LocalTodo,
|
||||
}
|
||||
} {
|
||||
RecvOutcome::Message(first) => first,
|
||||
RecvOutcome::LocalTodo => synthetic_todo_message(),
|
||||
RecvOutcome::Empty => {
|
||||
// Idle: no message this poll. Service a queued operator
|
||||
// `/compact` here so it runs even when no turn is driving
|
||||
|
|
|
|||
Loading…
Reference in a new issue