clippy: apply auto-fixable warnings across workspace (closes #265 partial)
This commit is contained in:
parent
56d0b02c2f
commit
30d82148e0
18 changed files with 83 additions and 102 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -589,6 +589,7 @@ dependencies = [
|
|||
"hive-fr0nt",
|
||||
"hive-sh4re",
|
||||
"libc",
|
||||
"reqwest",
|
||||
"rusqlite",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
|
|
|||
|
|
@ -312,24 +312,19 @@ pub fn context_window_tokens(model: &str) -> u64 {
|
|||
let m = model.to_ascii_lowercase();
|
||||
// Per-model env vars set by `hyperhive.contextWindowTokens` in Nix.
|
||||
for (key, val) in std::env::vars() {
|
||||
if let Some(suffix) = key.strip_prefix("HIVE_CONTEXT_WINDOW_TOKENS_") {
|
||||
if !suffix.is_empty() && m.contains(&suffix.to_ascii_lowercase()) {
|
||||
if let Ok(v) = val.trim().parse::<u64>() {
|
||||
if v > 0 {
|
||||
if let Some(suffix) = key.strip_prefix("HIVE_CONTEXT_WINDOW_TOKENS_")
|
||||
&& !suffix.is_empty() && m.contains(&suffix.to_ascii_lowercase())
|
||||
&& let Ok(v) = val.trim().parse::<u64>()
|
||||
&& v > 0 {
|
||||
return v;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Global override (single value, any model).
|
||||
if let Ok(s) = std::env::var("HIVE_CONTEXT_WINDOW_TOKENS") {
|
||||
if let Ok(v) = s.trim().parse::<u64>() {
|
||||
if v > 0 {
|
||||
if let Ok(s) = std::env::var("HIVE_CONTEXT_WINDOW_TOKENS")
|
||||
&& let Ok(v) = s.trim().parse::<u64>()
|
||||
&& v > 0 {
|
||||
return v;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Hard fallback for dev/test outside NixOS where env vars aren't set.
|
||||
200_000
|
||||
}
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ pub async fn run(socket: PathBuf, is_manager: bool) {
|
|||
let url = format!("{forge_url}/api/v1/user");
|
||||
fetch_json(&client, &url, &token)
|
||||
.await
|
||||
.and_then(|v| v["login"].as_str().map(|s| s.to_owned()))
|
||||
.and_then(|v| v["login"].as_str().map(std::borrow::ToOwned::to_owned))
|
||||
.unwrap_or_default()
|
||||
};
|
||||
if own_login.is_empty() {
|
||||
|
|
@ -184,7 +184,7 @@ fn truncate(s: &str, max: usize) -> String {
|
|||
/// Map a Forgejo review state to a readable action label.
|
||||
/// Returns `None` for non-review states (regular comments have no `state` field;
|
||||
/// `PENDING` means the review was saved but not submitted yet).
|
||||
/// Forgejo review states: "APPROVED", "REQUEST_CHANGES", "COMMENT", "PENDING".
|
||||
/// Forgejo review states: "APPROVED", "`REQUEST_CHANGES`", "COMMENT", "PENDING".
|
||||
fn review_state_label(state: &str) -> Option<&str> {
|
||||
match state {
|
||||
"APPROVED" => Some("approved"),
|
||||
|
|
@ -204,7 +204,7 @@ fn review_state_label(state: &str) -> Option<&str> {
|
|||
/// - New item: `[new issue #N repo] title\nurl: ...\nassignee: user`
|
||||
/// - State: `[PR merged #N repo] title\nurl: ...\nassignee: user`
|
||||
///
|
||||
/// Assignees (and, for PRs, requested_reviewers) are appended unconditionally
|
||||
/// Assignees (and, for PRs, `requested_reviewers`) are appended unconditionally
|
||||
/// on all issue/PR notifications (closes #256).
|
||||
///
|
||||
/// Number is extracted from `html_url` last path segment before any `#`.
|
||||
|
|
@ -246,10 +246,10 @@ async fn format_notification(
|
|||
|
||||
// Always fetch subject detail for assignee/reviewer metadata (#256).
|
||||
// Keeps agents informed of current ownership without a follow-up fetch.
|
||||
let subject = if !subject_api_url.is_empty() {
|
||||
fetch_json(client, subject_api_url, token).await
|
||||
} else {
|
||||
let subject = if subject_api_url.is_empty() {
|
||||
None
|
||||
} else {
|
||||
fetch_json(client, subject_api_url, token).await
|
||||
};
|
||||
|
||||
let is_pr = matches!(notif_type, "Pull Request" | "Pull");
|
||||
|
|
@ -328,10 +328,10 @@ async fn format_notification(
|
|||
// Review submission on a PR.
|
||||
let kind = format!("PR {review_label}{num}{repo}");
|
||||
let mut out = format!("[{kind}] {title}\nurl: {url}");
|
||||
if !body_text.is_empty() {
|
||||
out.push_str(&format!("\n\n{author}: {}", truncate(body_text, BODY_TRUNCATE)));
|
||||
} else {
|
||||
if body_text.is_empty() {
|
||||
out.push_str(&format!("\n\nreviewer: {author}"));
|
||||
} else {
|
||||
out.push_str(&format!("\n\n{author}: {}", truncate(body_text, BODY_TRUNCATE)));
|
||||
}
|
||||
out.push_str(&meta_suffix);
|
||||
Some(out)
|
||||
|
|
@ -457,12 +457,9 @@ async fn poll_once(
|
|||
let body_opt = format_notification(client, token, notif, own_login).await;
|
||||
|
||||
// None means self-echo — mark read silently, no delivery.
|
||||
let body = match body_opt {
|
||||
Some(b) => b,
|
||||
None => {
|
||||
mark_read(client, forge_url, token, id).await;
|
||||
continue;
|
||||
}
|
||||
let body = if let Some(b) = body_opt { b } else {
|
||||
mark_read(client, forge_url, token, id).await;
|
||||
continue;
|
||||
};
|
||||
|
||||
let delivered = if is_manager {
|
||||
|
|
@ -501,9 +498,9 @@ async fn poll_once(
|
|||
// when HIVE_FORGE_KEEP_SUBSCRIPTIONS=1 — triage and other firehose
|
||||
// consumers set this to retain broad repo visibility.
|
||||
let reason = notif["reason"].as_str().unwrap_or("");
|
||||
if !keep_subscriptions && reason == "subscribed" {
|
||||
if let Some(repo) = notif["repository"]["full_name"].as_str() {
|
||||
if !unsubbed_repos.contains(repo) {
|
||||
if !keep_subscriptions && reason == "subscribed"
|
||||
&& let Some(repo) = notif["repository"]["full_name"].as_str()
|
||||
&& !unsubbed_repos.contains(repo) {
|
||||
let unsub_url = format!("{forge_url}/api/v1/repos/{repo}/subscription");
|
||||
match client
|
||||
.delete(&unsub_url)
|
||||
|
|
@ -523,8 +520,6 @@ async fn poll_once(
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -681,7 +681,7 @@ pub async fn serve_manager_stdio(socket: PathBuf) -> Result<()> {
|
|||
|
||||
#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
|
||||
pub struct RequestInitConfigArgs {
|
||||
/// New sub-agent name (≤9 chars). Queues an InitConfig approval; on
|
||||
/// New sub-agent name (≤9 chars). Queues an `InitConfig` approval; on
|
||||
/// approval hive-c0re seeds the proposed config repo at
|
||||
/// `/agents/<name>/config/agent.nix` with the default template.
|
||||
/// After the approval the manager can edit and commit the config before
|
||||
|
|
|
|||
|
|
@ -272,11 +272,10 @@ pub async fn drive_turn(prompt: &str, files: &TurnFiles, bus: &Bus) -> TurnOutco
|
|||
// turn overflows into the reactive path. Best-effort — never changes
|
||||
// the outcome of the turn that already succeeded, but records it as
|
||||
// `Compacted` so turn stats can distinguish it from a plain `Ok`.
|
||||
if matches!(outcome, TurnOutcome::Ok) {
|
||||
if maybe_checkpoint_and_compact(files, bus).await {
|
||||
if matches!(outcome, TurnOutcome::Ok)
|
||||
&& maybe_checkpoint_and_compact(files, bus).await {
|
||||
return TurnOutcome::Compacted;
|
||||
}
|
||||
}
|
||||
outcome
|
||||
}
|
||||
|
||||
|
|
@ -528,42 +527,39 @@ async fn run_claude(prompt: &str, files: &TurnFiles, bus: &Bus) -> Result<(bool,
|
|||
if line.contains(PROMPT_TOO_LONG_MARKER) {
|
||||
flag_out.store(true, Ordering::Relaxed);
|
||||
}
|
||||
match serde_json::from_str::<serde_json::Value>(&line) {
|
||||
Ok(v) => {
|
||||
// Rate-limit detection: only fire on JSON `error` events,
|
||||
// not on arbitrary text content. An agent discussing a past
|
||||
// rate limit in its response would otherwise trigger a false
|
||||
// positive (the full conversation flows through stdout as
|
||||
// stream-json, so any text the model outputs is visible here).
|
||||
if v.get("type").and_then(|t| t.as_str()) == Some("error") {
|
||||
let raw = v.to_string();
|
||||
if RATE_LIMIT_MARKERS.iter().any(|m| raw.contains(m)) {
|
||||
rate_out.store(true, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
if let Some(u) = crate::events::TokenUsage::from_assistant_event(&v) {
|
||||
last_inference = Some(u);
|
||||
}
|
||||
if let Some(cost) = crate::events::TokenUsage::from_stream_event(&v) {
|
||||
// Fallback to `cost` if the turn somehow produced
|
||||
// a result without any assistant event — keeps the
|
||||
// ctx badge from going stale on a degenerate turn.
|
||||
let ctx = last_inference.unwrap_or(cost);
|
||||
bus_out.record_turn_usage(ctx, cost);
|
||||
}
|
||||
bus_out.observe_stream(&v);
|
||||
bus_out.emit(LiveEvent::Stream(v));
|
||||
}
|
||||
Err(_) => {
|
||||
// Non-JSON stdout: raw text check is fine here since these
|
||||
// are claude CLI messages, not conversation content.
|
||||
if RATE_LIMIT_MARKERS.iter().any(|m| line.contains(m)) {
|
||||
if let Ok(v) = serde_json::from_str::<serde_json::Value>(&line) {
|
||||
// Rate-limit detection: only fire on JSON `error` events,
|
||||
// not on arbitrary text content. An agent discussing a past
|
||||
// rate limit in its response would otherwise trigger a false
|
||||
// positive (the full conversation flows through stdout as
|
||||
// stream-json, so any text the model outputs is visible here).
|
||||
if v.get("type").and_then(|t| t.as_str()) == Some("error") {
|
||||
let raw = v.to_string();
|
||||
if RATE_LIMIT_MARKERS.iter().any(|m| raw.contains(m)) {
|
||||
rate_out.store(true, Ordering::Relaxed);
|
||||
}
|
||||
bus_out.emit(LiveEvent::Note {
|
||||
text: format!("(non-json) {line}"),
|
||||
});
|
||||
}
|
||||
if let Some(u) = crate::events::TokenUsage::from_assistant_event(&v) {
|
||||
last_inference = Some(u);
|
||||
}
|
||||
if let Some(cost) = crate::events::TokenUsage::from_stream_event(&v) {
|
||||
// Fallback to `cost` if the turn somehow produced
|
||||
// a result without any assistant event — keeps the
|
||||
// ctx badge from going stale on a degenerate turn.
|
||||
let ctx = last_inference.unwrap_or(cost);
|
||||
bus_out.record_turn_usage(ctx, cost);
|
||||
}
|
||||
bus_out.observe_stream(&v);
|
||||
bus_out.emit(LiveEvent::Stream(v));
|
||||
} else {
|
||||
// Non-JSON stdout: raw text check is fine here since these
|
||||
// are claude CLI messages, not conversation content.
|
||||
if RATE_LIMIT_MARKERS.iter().any(|m| line.contains(m)) {
|
||||
rate_out.store(true, Ordering::Relaxed);
|
||||
}
|
||||
bus_out.emit(LiveEvent::Note {
|
||||
text: format!("(non-json) {line}"),
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -265,7 +265,7 @@ async fn screen_ws(
|
|||
}
|
||||
|
||||
/// Pure byte pump: forwards raw bytes between the WebSocket client and
|
||||
/// the VNC TCP stream. Transparent to any RFB variant (plain, VeNCrypt).
|
||||
/// the VNC TCP stream. Transparent to any RFB variant (plain, `VeNCrypt`).
|
||||
async fn relay_ws_vnc(socket: axum::extract::ws::WebSocket, vnc_port: u16) {
|
||||
// Import futures traits locally so they don't conflict with
|
||||
// tokio_stream::StreamExt used at module scope.
|
||||
|
|
|
|||
|
|
@ -65,11 +65,10 @@ pub async fn approve(coord: Arc<Coordinator>, id: i64) -> Result<()> {
|
|||
if let Err(e) = crate::forge::ensure_config_repo(&approval.agent).await {
|
||||
tracing::warn!(agent = %approval.agent, error = ?e, "forge: ensure_config_repo after first spawn failed");
|
||||
}
|
||||
if let Some(core_token) = crate::forge::core_token() {
|
||||
if let Err(e) = crate::forge::meta_read_access(&approval.agent, &core_token).await {
|
||||
if let Some(core_token) = crate::forge::core_token()
|
||||
&& let Err(e) = crate::forge::meta_read_access(&approval.agent, &core_token).await {
|
||||
tracing::warn!(agent = %approval.agent, error = ?e, "forge: meta_read_access after first spawn failed");
|
||||
}
|
||||
}
|
||||
if let Err(e) = crate::forge::ensure_meta_remote(&approval.agent).await {
|
||||
tracing::warn!(agent = %approval.agent, error = ?e, "forge: ensure_meta_remote after first spawn failed");
|
||||
}
|
||||
|
|
@ -91,11 +90,10 @@ pub async fn approve(coord: Arc<Coordinator>, id: i64) -> Result<()> {
|
|||
}
|
||||
.await;
|
||||
// Wire the meta remote now that the proposed repo exists.
|
||||
if result.is_ok() {
|
||||
if let Err(e) = crate::forge::ensure_meta_remote(&approval.agent).await {
|
||||
if result.is_ok()
|
||||
&& let Err(e) = crate::forge::ensure_meta_remote(&approval.agent).await {
|
||||
tracing::warn!(agent = %approval.agent, error = ?e, "forge: ensure_meta_remote after init_config failed");
|
||||
}
|
||||
}
|
||||
finish_approval(&coord, &approval, result, None, false)
|
||||
}
|
||||
ApprovalKind::UpdateMetaInputs => {
|
||||
|
|
@ -145,11 +143,10 @@ pub async fn approve(coord: Arc<Coordinator>, id: i64) -> Result<()> {
|
|||
if let Err(e) = crate::forge::push_config(&agent_bg).await {
|
||||
tracing::warn!(agent = %agent_bg, error = ?e, "forge: push_config after spawn failed");
|
||||
}
|
||||
if let Some(core_token) = crate::forge::core_token() {
|
||||
if let Err(e) = crate::forge::meta_read_access(&agent_bg, &core_token).await {
|
||||
if let Some(core_token) = crate::forge::core_token()
|
||||
&& let Err(e) = crate::forge::meta_read_access(&agent_bg, &core_token).await {
|
||||
tracing::warn!(agent = %agent_bg, error = ?e, "forge: meta_read_access after spawn failed");
|
||||
}
|
||||
}
|
||||
if let Err(e) = crate::forge::ensure_meta_remote(&agent_bg).await {
|
||||
tracing::warn!(agent = %agent_bg, error = ?e, "forge: ensure_meta_remote after spawn failed");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -613,8 +613,7 @@ impl Broker {
|
|||
let now = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.ok()
|
||||
.map(|d| d.as_secs() as i64)
|
||||
.unwrap_or(0);
|
||||
.map_or(0, |d| d.as_secs() as i64);
|
||||
now - since_secs as i64
|
||||
} else {
|
||||
i64::MIN
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ pub struct ContainerView {
|
|||
#[serde(default)]
|
||||
pub pending_reminders: u64,
|
||||
/// Context-window size (prompt tokens) from the agent's most recent
|
||||
/// completed turn, read directly from the turn-stats SQLite.
|
||||
/// completed turn, read directly from the turn-stats `SQLite`.
|
||||
/// `None` when the file is absent or the agent has no turns yet.
|
||||
/// Stale by up to one crash-watch cycle (~10s); good enough for
|
||||
/// the "which agent is close to the window?" dashboard glance.
|
||||
|
|
@ -84,7 +84,7 @@ pub async fn build_all(coord: &Coordinator) -> Vec<ContainerView> {
|
|||
} else {
|
||||
continue;
|
||||
};
|
||||
let deployed_full = locked.get(&format!("agent-{logical}")).map(|s| s.as_str());
|
||||
let deployed_full = locked.get(&format!("agent-{logical}")).map(std::string::String::as_str);
|
||||
let needs_update = crate::auto_update::agent_config_pending(&logical, deployed_full);
|
||||
let needs_login =
|
||||
!is_manager && !claude_has_session(&Coordinator::agent_claude_dir(&logical));
|
||||
|
|
@ -159,7 +159,7 @@ fn is_rate_limited(name: &str) -> bool {
|
|||
}
|
||||
|
||||
/// Read the most recent completed turn's context-window size (prompt
|
||||
/// tokens) from the agent's turn-stats SQLite. Returns `None` when
|
||||
/// tokens) from the agent's turn-stats `SQLite`. Returns `None` when
|
||||
/// the file is absent or has no rows. Best-effort — any DB error
|
||||
/// silently yields `None` so a missing/corrupt file never blocks
|
||||
/// `build_all`.
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ pub fn spawn(coord: Arc<Coordinator>) {
|
|||
seeded = true;
|
||||
|
||||
tokio::select! {
|
||||
_ = tokio::time::sleep(POLL_INTERVAL) => {}
|
||||
() = tokio::time::sleep(POLL_INTERVAL) => {}
|
||||
_ = shutdown.changed() => {
|
||||
tracing::info!("crash watcher: shutdown signal received");
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ pub fn spawn(coord: Arc<Coordinator>) {
|
|||
loop {
|
||||
sweep_once();
|
||||
tokio::select! {
|
||||
_ = tokio::time::sleep(VACUUM_INTERVAL) => {}
|
||||
() = tokio::time::sleep(VACUUM_INTERVAL) => {}
|
||||
_ = shutdown.changed() => {
|
||||
tracing::info!("events vacuum: shutdown signal received");
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -54,9 +54,9 @@ const SEEDED_ORGS: &[&str] = &[CONFIG_ORG];
|
|||
/// a forge namespace).
|
||||
/// - `read:user` — token-owner endpoint clients call to introspect.
|
||||
/// - `write:misc` — hooks, attachments, the rest of the long tail.
|
||||
/// - `read:notification` — required by forge_notify to poll
|
||||
/// - `read:notification` — required by `forge_notify` to poll
|
||||
/// `GET /notifications` for unread PR/review events.
|
||||
/// - `write:notification` — required by forge_notify to mark
|
||||
/// - `write:notification` — required by `forge_notify` to mark
|
||||
/// notifications as read via `PATCH /notifications/threads/{id}`.
|
||||
const TOKEN_SCOPES: &str =
|
||||
"read:user,write:user,read:notification,write:notification,write:repository,write:issue,write:organization,write:misc";
|
||||
|
|
@ -583,11 +583,10 @@ pub async fn ensure_all() {
|
|||
}
|
||||
// Grant read-only access to core/meta and wire the `meta` remote
|
||||
// into the proposed repo so agents can fetch their deployment context.
|
||||
if let Some(token) = core_token.as_deref() {
|
||||
if let Err(e) = meta_read_access(&name, token).await {
|
||||
if let Some(token) = core_token.as_deref()
|
||||
&& let Err(e) = meta_read_access(&name, token).await {
|
||||
tracing::warn!(%name, error = ?e, "forge: ensure_meta_read_access failed");
|
||||
}
|
||||
}
|
||||
if let Err(e) = ensure_meta_remote(&name).await {
|
||||
tracing::warn!(%name, error = ?e, "forge: ensure_meta_remote failed");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -176,7 +176,7 @@ async fn main() -> Result<()> {
|
|||
Err(e) => tracing::warn!(error = ?e, "broker vacuum failed"),
|
||||
}
|
||||
tokio::select! {
|
||||
_ = tokio::time::sleep(interval) => {}
|
||||
() = tokio::time::sleep(interval) => {}
|
||||
_ = vacuum_shutdown.changed() => {
|
||||
tracing::info!("broker vacuum: shutdown signal received");
|
||||
break;
|
||||
|
|
@ -219,7 +219,7 @@ async fn main() -> Result<()> {
|
|||
tracing::info!("SIGINT received — requesting shutdown");
|
||||
coord_sig.request_shutdown();
|
||||
}
|
||||
_ = async {
|
||||
() = async {
|
||||
let mut sig = tokio::signal::unix::signal(
|
||||
tokio::signal::unix::SignalKind::terminate()
|
||||
).expect("failed to install SIGTERM handler");
|
||||
|
|
|
|||
|
|
@ -299,12 +299,11 @@ fn render_flake(
|
|||
// Forge URL — injected when hive-c0re itself has HIVE_FORGE_URL set
|
||||
// (the NixOS module derives it from hyperhive.forge.{domain,httpPort}).
|
||||
// Agents use it in forge_notify to poll Forgejo for PR/review events.
|
||||
if let Ok(forge_url) = std::env::var("HIVE_FORGE_URL") {
|
||||
if !forge_url.is_empty() {
|
||||
if let Ok(forge_url) = std::env::var("HIVE_FORGE_URL")
|
||||
&& !forge_url.is_empty() {
|
||||
let escaped = forge_url.replace('\\', "\\\\").replace('"', "\\\"");
|
||||
let _ = writeln!(out, " HIVE_FORGE_URL = \"{escaped}\";");
|
||||
}
|
||||
}
|
||||
out.push_str(
|
||||
r#" HYPERHIVE_STATE_DIR = "/agents/${name}/state";
|
||||
};
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ pub fn spawn(coord: Arc<Coordinator>) {
|
|||
loop {
|
||||
tick(&coord);
|
||||
tokio::select! {
|
||||
_ = tokio::time::sleep(POLL_INTERVAL) => {}
|
||||
() = tokio::time::sleep(POLL_INTERVAL) => {}
|
||||
_ = shutdown.changed() => {
|
||||
tracing::info!("reminder scheduler: shutdown signal received");
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ pub fn spawn(coord: Arc<Coordinator>) {
|
|||
loop {
|
||||
sweep_once();
|
||||
tokio::select! {
|
||||
_ = tokio::time::sleep(VACUUM_INTERVAL) => {}
|
||||
() = tokio::time::sleep(VACUUM_INTERVAL) => {}
|
||||
_ = shutdown.changed() => {
|
||||
tracing::info!("stats vacuum: shutdown signal received");
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ pub const TERMINAL_JS: &str = include_str!("../assets/terminal.js");
|
|||
/// without internet egress (the container itself never fetches it).
|
||||
///
|
||||
/// NB: must be the **UMD** build, not `marked.min.js` / `lib/marked.cjs` —
|
||||
/// those are CommonJS (`exports.parse = …`, no wrapper) and throw
|
||||
/// those are `CommonJS` (`exports.parse = …`, no wrapper) and throw
|
||||
/// `exports is not defined` in a `<script>` tag, leaving `window.marked`
|
||||
/// undefined and markdown silently falling back to raw text (issue #244).
|
||||
pub const MARKED_JS: &str = include_str!("../assets/marked.umd.js");
|
||||
|
|
|
|||
|
|
@ -124,11 +124,11 @@ pub enum ApprovalStatus {
|
|||
/// Reminder activity statistics for an agent over a time window.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ReminderStats {
|
||||
/// Total reminders scheduled in the window (created_at >= cutoff).
|
||||
/// Total reminders scheduled in the window (`created_at` >= cutoff).
|
||||
pub scheduled: u64,
|
||||
/// Reminders that have been delivered in the window (sent_at IS NOT NULL).
|
||||
/// Reminders that have been delivered in the window (`sent_at` IS NOT NULL).
|
||||
pub delivered: u64,
|
||||
/// Reminders still pending in the window (sent_at IS NULL).
|
||||
/// Reminders still pending in the window (`sent_at` IS NULL).
|
||||
pub pending: u64,
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue