1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
//! **The conversation list** one workspace answers with (yog's
//! `docs/REMOTE.md` §8, §9.7) — the middle pane's rows.
//!
//! **Two names, and they are not interchangeable.** [`ConvRow::display`] is
//! what the row paints; [`ConvRow::name`] is what a later act may **address**,
//! and it is absent exactly when the engine has a name it will not answer to.
//! [`ConvRow::root_id`] is the address that always works, so a seat that
//! stayed with the id can never post a gesture the engine will refuse.
//!
//! **[`ConvRow::uncertain`] is not derivable from the state beside it.** The
//! engine's classifier answers a state *and* whether it could observe one, and
//! the two are separate facts: a conversation it cannot probe reads as settled
//! and is not known to be. A seat that dropped the flag would paint a definite
//! reading of something nothing looked at — which is also the shape this
//! window's own pending row wears (`crate::ui::model::claim`).
//!
//! **What this build does not carry.** The row also spells the stop cascade's
//! two gates, the strict child count, a bound ball, an alignment verdict, the
//! in-flight class and — since REMOTE §9.9 — `last_active_unix`, the absolute
//! stamp beside the [`ConvRow::age_secs`] distance this pane already paints.
//! Each belongs to a control or a badge that does not exist here yet; rung 4
//! of [`super`]'s policy rides them through unread, and each arrives with the
//! surface that paints it. The stamp is the plainest case of that rule: this
//! seat paints *how long ago* and nothing paints *when*, and carrying an
//! absolute time to hold it would be storing a fact no glass spends.
use serde_json::{Map, Value};
use super::fields;
/// This reply's kind token.
pub(crate) const KIND: &str = "conversations";
/// One conversation as the list paints it.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ConvRow {
/// The conversation root's id — the address that always resolves.
pub root_id: String,
/// What the row is labelled with.
pub display: String,
/// **The addressable name**, when the engine has one it will answer to.
/// Absent where it holds a name that is display-only, which is a name no
/// stored fact backs: handing it over would be handing over a target the
/// engine is going to refuse.
pub name: Option<String>,
/// The badge state, aggregated over the whole subtree.
pub state: AgentState,
/// **Whether the engine could observe the state above.** A conversation it
/// cannot probe — no lock to read, no step to frame — answers a state
/// anyway and flags it here, so a reading nothing witnessed never paints as
/// a definite one.
pub uncertain: bool,
/// The row's first-line preview.
pub preview: String,
/// How long since the conversation last moved. Signed, because clock skew
/// between two machines is a fact and not an error.
pub age_secs: i64,
/// Attention-bearing members under it.
pub attention: u64,
/// How many conversations the subtree holds.
pub members: u64,
/// How far the row hangs under its conversation root — the list's indent.
pub depth: u64,
/// How solidly the row paints. Not derivable from
/// [`state`](Self::state): a row can be settled and provisional at once.
pub tone: Tone,
/// **Why this conversation's latest model call failed**, in one clause
/// (REMOTE §9.10, PROTOCOL 3) — the words behind a [`Tone::Bad`] row.
///
/// `None` for every conversation whose latest call did not fail, which is
/// nearly all of them; the field is *absent* on the wire rather than
/// `null`, so a reader never has to tell **no failure** from **a failure
/// with nothing to say**. A `bad` tone with no clause beside it therefore
/// reads as exactly the third thing it is: a call that failed and left no
/// words.
///
/// **The clause, not the whole.** A row is a glance, so the wire carries
/// the provider's first sentence capped; the adapter's own output is one
/// query deeper and belongs to a surface this seat does not have.
///
/// It is carried and never derived from [`tone`](Self::tone), nor the tone
/// from it. The hue is the sighting and the words are the explanation —
/// two facts the engine states separately, and a seat that computed either
/// from the other would be holding a second opinion about a reading it did
/// not take.
pub failure: Option<String>,
}
/// The badge state of a conversation. **Rung 3**: an unknown word keeps its
/// spelling rather than becoming one of the four, because painting an
/// unrecognised state as `Quiescent` would tell an operator nothing is
/// happening on the strength of a word this build has never seen.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AgentState {
/// A driver is held on it.
Live,
/// It is streaming.
InFlight,
/// Settled.
Quiescent,
/// Stopped.
Stopped,
/// A word this build does not know, verbatim.
Unknown(String),
}
/// How solidly a row paints, on the same terms.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Tone {
Plain,
Weak,
Good,
Bad,
Live,
InFlight,
/// A word this build does not know, verbatim.
Unknown(String),
}
impl AgentState {
/// The word the badge paints.
pub fn label(&self) -> String {
match self {
Self::Live => "live".to_owned(),
Self::InFlight => IN_FLIGHT.to_owned(),
Self::Quiescent => "quiescent".to_owned(),
Self::Stopped => "stopped".to_owned(),
Self::Unknown(word) => word.clone(),
}
}
/// **One state token, total by construction** — and `pub(crate)` because
/// the decision queue's rows wear the same vocabulary (`super::queue`).
/// One home for the four words, so the two readings cannot drift.
pub(crate) fn of(word: &str) -> Self {
match word {
"live" => Self::Live,
IN_FLIGHT => Self::InFlight,
"quiescent" => Self::Quiescent,
"stopped" => Self::Stopped,
other => Self::Unknown(other.to_owned()),
}
}
}
impl Tone {
/// The word this tone is written as — the one an unstyled row falls back
/// to painting.
pub fn label(&self) -> String {
match self {
Self::Plain => "plain".to_owned(),
Self::Weak => "weak".to_owned(),
Self::Good => "good".to_owned(),
Self::Bad => "bad".to_owned(),
Self::Live => "live".to_owned(),
Self::InFlight => IN_FLIGHT.to_owned(),
Self::Unknown(word) => word.clone(),
}
}
/// One tone token, total by construction.
fn of(word: &str) -> Self {
match word {
"plain" => Self::Plain,
"weak" => Self::Weak,
"good" => Self::Good,
"bad" => Self::Bad,
"live" => Self::Live,
IN_FLIGHT => Self::InFlight,
other => Self::Unknown(other.to_owned()),
}
}
}
/// The one token both enums spell, written once so they cannot disagree.
const IN_FLIGHT: &str = "in-flight";
/// Read one row.
pub(crate) fn row(v: &Value) -> Result<ConvRow, String> {
let o: &Map<String, Value> = v.as_object().ok_or("conversation row: not an object")?;
Ok(ConvRow {
root_id: fields::text(o, "root_id")?,
display: fields::text(o, "display")?,
name: fields::opt_text(o, "name")?,
state: AgentState::of(&fields::text(o, "state")?),
uncertain: fields::flag(o, "uncertain")?,
preview: fields::text(o, "preview")?,
age_secs: fields::secs(o, "age_secs")?,
attention: fields::count(o, "attention")?,
members: fields::count(o, "members")?,
depth: fields::count(o, "depth")?,
tone: Tone::of(&fields::text(o, "tone")?),
failure: fields::opt_text(o, "failure")?,
})
}
#[cfg(test)]
mod tests;