dashboard: surface peer questions + operator override

questions pane now shows both operator-targeted threads
(target IS NULL) and agent-to-agent threads (target = some
agent). filter chips above the list: all / @operator / @peer /
per-participant. peer rows get a mauve left rule + a 0V3RR1D3
button that POSTs the same /answer-question endpoint
(OperatorQuestions::answer already permits the operator as
answerer on any target).

wire changes: OperatorQuestions gains pending_all +
recent_answered_all; QuestionAdded + QuestionResolved events
carry target: Option<String>; emit sites drop their
target.is_none() guard. answered-history rows show the
answerer prefix so override answers are auditable at a glance.
This commit is contained in:
müde 2026-05-17 22:06:53 +02:00
parent e7ce35c503
commit a15fafb5de
9 changed files with 187 additions and 71 deletions

View file

@ -243,11 +243,10 @@ impl Coordinator {
});
}
/// Emit `QuestionAdded` after an operator-targeted question is
/// inserted. Peer-to-peer questions (those with a non-null
/// `target` agent) never fire this — they don't surface on the
/// dashboard at all. Caller is responsible for the
/// `target.is_none()` guard.
/// Emit `QuestionAdded` after a question is inserted. Fires for
/// both operator-targeted (`target = None`) and peer-to-peer
/// (`target = Some(agent)`) threads — the dashboard surfaces
/// both, distinguishing visually + offering operator override.
pub fn emit_question_added(
&self,
id: i64,
@ -256,6 +255,7 @@ impl Coordinator {
options: &[String],
multi: bool,
deadline_at: Option<i64>,
target: Option<&str>,
) {
let asked_at = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
@ -271,20 +271,22 @@ impl Coordinator {
multi,
asked_at,
deadline_at,
target: target.map(str::to_owned),
});
}
/// Emit `QuestionResolved` when an operator-targeted question
/// transitions to answered (operator answer, peer override,
/// cancel, or ttl watchdog). Caller filters on the original
/// question's `target.is_none()` — peer questions are dashboard-
/// invisible.
/// Emit `QuestionResolved` when a question transitions to
/// answered (operator answer, peer answer, operator override on
/// a peer thread, operator cancel, or ttl watchdog). Both
/// operator-targeted and peer threads fire so the dashboard's
/// derived store can move the row from pending to history.
pub fn emit_question_resolved(
&self,
id: i64,
answer: &str,
answerer: &str,
cancelled: bool,
target: Option<&str>,
) {
let answered_at = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
@ -298,6 +300,7 @@ impl Coordinator {
answerer: answerer.to_owned(),
answered_at,
cancelled,
target: target.map(str::to_owned),
});
}