Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eba637b36c | ||
|
|
49db9d6e1c |
5 changed files with 55 additions and 3 deletions
|
|
@ -223,6 +223,18 @@ Per-variant fields:
|
||||||
- `Reminder { id, owner, message, due_at, age_seconds }` —
|
- `Reminder { id, owner, message, due_at, age_seconds }` —
|
||||||
`due_at` is the absolute unix timestamp the scheduler is
|
`due_at` is the absolute unix timestamp the scheduler is
|
||||||
targeting; clients compute time-until-fire as `due_at - now`.
|
targeting; clients compute time-until-fire as `due_at - now`.
|
||||||
|
- `PendingMessages { count }` — undelivered inbox messages the
|
||||||
|
agent still owes itself a `recv` for. Informational + not
|
||||||
|
cancellable (drain with `recv`); only emitted when `count > 0`,
|
||||||
|
and surfaced first in the agent-flavour list as the most
|
||||||
|
actionable signal. Counted host-side from the broker
|
||||||
|
(`count_pending`), so it reflects what's genuinely still queued —
|
||||||
|
the wake-message that drove the current turn is already delivered
|
||||||
|
and not counted.
|
||||||
|
- `UnreadMatrix { rooms, summary }` — unread matrix notifications.
|
||||||
|
Informational + not cancellable (clear with `mark_read`). Unlike
|
||||||
|
the others this is injected by the in-container harness, not
|
||||||
|
hive-c0re, because the matrix daemon lives inside the agent.
|
||||||
|
|
||||||
`age_seconds` saturates at zero on any clock anomaly (back-step,
|
`age_seconds` saturates at zero on any clock anomaly (back-step,
|
||||||
unsynchronised wall clock, etc.) so the bulleted list never
|
unsynchronised wall clock, etc.) so the bulleted list never
|
||||||
|
|
|
||||||
|
|
@ -891,6 +891,12 @@ window.marked = marked;
|
||||||
el('span', { class: 'inbox-ts' }, 'scheduled ' + fmtAge(t.age_seconds || 0) + ' ago'),
|
el('span', { class: 'inbox-ts' }, 'scheduled ' + fmtAge(t.age_seconds || 0) + ' ago'),
|
||||||
el('div', { class: 'inbox-body' }, t.message || ''),
|
el('div', { class: 'inbox-body' }, t.message || ''),
|
||||||
);
|
);
|
||||||
|
} else if (t.kind === 'pending_messages') {
|
||||||
|
li.append(
|
||||||
|
el('span', { class: 'inbox-from' }, '✉ inbox'), ' ',
|
||||||
|
el('span', { class: 'inbox-sep' }, (t.count || 0) + ' pending message(s)'), ' ',
|
||||||
|
el('span', { class: 'inbox-ts' }, 'drain with recv'),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
li.append(el('span', { class: 'inbox-body' }, JSON.stringify(t)));
|
li.append(el('span', { class: 'inbox-body' }, JSON.stringify(t)));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -298,6 +298,12 @@ fn render_loose_ends(loose_ends: &[hive_sh4re::LooseEnd]) -> String {
|
||||||
"- reminder #{id} ({owner}, scheduled {age_seconds}s ago, due_at={due_at}): {message}"
|
"- reminder #{id} ({owner}, scheduled {age_seconds}s ago, due_at={due_at}): {message}"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
hive_sh4re::LooseEnd::PendingMessages { count } => {
|
||||||
|
let _ = writeln!(
|
||||||
|
out,
|
||||||
|
"- {count} pending inbox message(s) — drain with recv (recv(max: {count}) to batch)"
|
||||||
|
);
|
||||||
|
}
|
||||||
hive_sh4re::LooseEnd::UnreadMatrix { rooms, summary } => {
|
hive_sh4re::LooseEnd::UnreadMatrix { rooms, summary } => {
|
||||||
let _ = write!(out, "- unread matrix messages in {rooms} room(s)");
|
let _ = write!(out, "- unread matrix messages in {rooms} room(s)");
|
||||||
if summary.is_empty() {
|
if summary.is_empty() {
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,8 @@ use hive_sh4re::{LooseEnd, MANAGER_AGENT};
|
||||||
use crate::coordinator::Coordinator;
|
use crate::coordinator::Coordinator;
|
||||||
|
|
||||||
/// Open threads pending against `agent`:
|
/// Open threads pending against `agent`:
|
||||||
|
/// - undelivered inbox messages this agent still owes itself a `recv`
|
||||||
|
/// for (only when the count is non-zero);
|
||||||
/// - pending approvals where this agent is the submitter (a parent
|
/// - pending approvals where this agent is the submitter (a parent
|
||||||
/// agent with the `approvals` group submits for its children; the
|
/// agent with the `approvals` group submits for its children; the
|
||||||
/// root submits for top-level agents). Legacy rows with no recorded
|
/// root submits for top-level agents). Legacy rows with no recorded
|
||||||
|
|
@ -29,12 +31,29 @@ use crate::coordinator::Coordinator;
|
||||||
/// someone) OR the target (owes a reply);
|
/// someone) OR the target (owes a reply);
|
||||||
/// - pending reminders this agent scheduled (`owner == self`).
|
/// - pending reminders this agent scheduled (`owner == self`).
|
||||||
///
|
///
|
||||||
/// Ordered approvals → questions → reminders within the returned
|
/// Ordered `pending_messages` (when non-zero) → approvals → questions →
|
||||||
/// vector. Within each kind, source-of-truth ordering (sqlite's
|
/// reminders within the returned vector. Within each kind,
|
||||||
/// `pending()` queries return newest-first within their indexes).
|
/// source-of-truth ordering (sqlite's `pending()` queries return
|
||||||
|
/// newest-first within their indexes).
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Propagates errors from `count_pending` and the pending-approval /
|
||||||
|
/// question / reminder sqlite queries.
|
||||||
pub fn for_agent(coord: &Coordinator, agent: &str) -> Result<Vec<LooseEnd>> {
|
pub fn for_agent(coord: &Coordinator, agent: &str) -> Result<Vec<LooseEnd>> {
|
||||||
let now = now_unix();
|
let now = now_unix();
|
||||||
let mut out = Vec::new();
|
let mut out = Vec::new();
|
||||||
|
// Undelivered inbox messages this agent still owes itself a `recv`
|
||||||
|
// for. Surfaced first (most actionable) and only when non-zero so a
|
||||||
|
// clean inbox doesn't add noise. The wake-message that drove the
|
||||||
|
// current turn is already delivered, so this counts only what's
|
||||||
|
// genuinely still queued.
|
||||||
|
let pending_messages = coord.broker.count_pending(agent)?;
|
||||||
|
if pending_messages > 0 {
|
||||||
|
out.push(LooseEnd::PendingMessages {
|
||||||
|
count: pending_messages,
|
||||||
|
});
|
||||||
|
}
|
||||||
// Show each pending approval to the agent that submitted it. The
|
// Show each pending approval to the agent that submitted it. The
|
||||||
// submitter column is NULL for rows predating it; those count as
|
// submitter column is NULL for rows predating it; those count as
|
||||||
// the root agent's.
|
// the root agent's.
|
||||||
|
|
|
||||||
|
|
@ -386,6 +386,15 @@ pub enum LooseEnd {
|
||||||
due_at: i64,
|
due_at: i64,
|
||||||
age_seconds: u64,
|
age_seconds: u64,
|
||||||
},
|
},
|
||||||
|
/// Undelivered inbox messages waiting to be `recv`'d by this agent.
|
||||||
|
/// Not cancellable — drain them with `recv`. Surfaced so an agent
|
||||||
|
/// doing a between-turns `get_loose_ends` sweep sees it still owes
|
||||||
|
/// itself a `recv` without having to poll the inbox separately. Only
|
||||||
|
/// emitted when `count > 0`.
|
||||||
|
PendingMessages {
|
||||||
|
/// Number of undelivered messages queued for this agent.
|
||||||
|
count: u64,
|
||||||
|
},
|
||||||
/// Unread matrix notifications in one or more rooms. Not cancellable —
|
/// Unread matrix notifications in one or more rooms. Not cancellable —
|
||||||
/// use `mark_read` via the matrix MCP to clear. Injected by the
|
/// use `mark_read` via the matrix MCP to clear. Injected by the
|
||||||
/// in-container harness (not hive-c0re) because the matrix daemon
|
/// in-container harness (not hive-c0re) because the matrix daemon
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue