yog 0.0.1

yog: a balls-oriented session manager for lernie loops (egui frontend)
Documentation
//! The §7.3 **no-response wound**: a step whose driver died before the model
//! produced anything.
//!
//! Without this state such a step reads as a quiet one — `Framing::Killed`
//! paints the same ash "stopped" badge a mid-stream kill gets, over a
//! `0 attempts · 0 tok` row that looks like nothing happened, while STORIES S0
//! step 4 promises "any step failure is a rendered fact" and §7.3 that a failed
//! action is never stderr-only.
//!
//! It is two observations — the same pair §3.5 already composes for agent
//! state, nothing stored (§5.1 #13):
//!
//! - **Unanswered on disk** — the step's `response.json` carries no bytes
//!   (absent or zero-length) *and* its `meta.json` is absent. lernie writes
//!   `meta.json` only after the model call returns (ARCH §2.3), so the pair
//!   says exactly: the call emitted nothing and the step never settled.
//! - **Nobody driving** — a live driver's newest step is legitimately
//!   unanswered for the moments between opening `response.json` and the first
//!   streamed event, so the wound is claimed only of an agent whose lock is
//!   free (§3.5). Never a false definite (§10).
//!
//! Only the newest step can be the one a driver is filling, so the liveness
//! observation gates that step alone — an earlier unanswered step is
//! unambiguous, and stays rendered as the place the conversation died.
//!
//! Deliberately *not* a reproduction hatch (§8.4 `yog exec`): see §14's
//! rejection — the driver's own words are already captured in its stderr sink
//! (§8.1) and rendered on the ops surface, so the wound names where the cause
//! lives instead of asking the operator to re-create it.

use std::path::Path;

use crate::git_tree::AgentState;

/// The sentence yog renders at the wound — the §7.3 rendered fact for this
/// class. Used verbatim beside the Steps row and composed into the §11
/// Altitude-1 banner, so both surfaces say one thing.
pub const NO_RESPONSE: &str = "driver produced no response";

/// The disk half: the step emitted nothing and never settled. `meta_present`
/// is the existence of `meta.json` — its *bytes*, not its parse, since a
/// malformed meta still means the step settled.
pub(super) fn unanswered(response: &[u8], meta_present: bool) -> bool {
    response.is_empty() && !meta_present
}

/// Is a driver at work on this agent (§3.5)? Then its newest step is allowed
/// to be unanswered — a model call in flight, not a wound.
pub(super) fn driven(state: AgentState) -> bool {
    matches!(state, AgentState::Live | AgentState::InFlight)
}

/// Whether the agent's **latest** step is the wound — the §11 Altitude-1
/// banner's predicate, read through [`super::build`] (the one owner of the
/// per-step flag) exactly as the Login banner reads its own.
pub fn latest_step_no_response(workspace: &Path, agent_id: &str, state: AgentState) -> bool {
    super::build(workspace, agent_id, state)
        .steps
        .last()
        .is_some_and(|step| step.no_response)
}