This repository has been archived on 2026-10-08. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
maestro/crates/maestro-daemon/src/activity.rs
T
naps62-yoloandClaude Opus 4.8 458c81094b feat(maestro): nest sessions by live working directory, drop labels
Replace the freeform `metadata.label` session grouping with nesting derived
from each session's working directory. The daemon's activity watcher already
polls each tmux pane every tick, so it now also captures
`#{pane_current_path}` and surfaces it as a new `Session.current_cwd`
(serde-default, back-compat); `cwd` stays the spawn dir for worktree
inference, spawn-similar, and recents.

Web catalog builds the tree from `current_cwd ?? cwd`, collapses the home
dir to `~`, and folds single-child folder chains so `~/git/maestro` reads as
one node. CLI swaps its LABEL column for an abbreviated DIR; the spawn forms
(web + TUI) and `--label` flag are removed.

The pane format stays space-delimited (parser splits the first three tokens
and keeps the remainder as the path) so a cwd containing spaces survives.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 21:11:23 +00:00

63 lines
1.9 KiB
Rust

//! Activity-state inference combine logic (NAP-340).
//!
//! Pure decision from three structured signals — PTY quiescence, foreground
//! process, and `/proc` block-state — so it's unit-testable without tmux or a
//! live session. The orchestration (gathering the signals each liveness tick
//! and emitting `SessionUpdated` on change) lives in [`crate::session`].
use std::time::Duration;
use maestro_protocol::ActivityState;
/// PTY output more recent than this keeps a session `Active`. It must exceed the
/// liveness tick (~2s, [`crate::session::DEFAULT_LIVENESS_INTERVAL`]) so a
/// session emitting a chunk every tick stays `Active` instead of flapping
/// Active↔Idle. This window *is* the debounce the issue calls for.
pub const ACTIVE_WINDOW: Duration = Duration::from_secs(3);
/// Combine the observed signals into an [`ActivityState`]:
/// recent output ⇒ `Active`; else blocked on a terminal read ⇒ `AwaitingInput`;
/// else `Idle`.
pub fn decide(idle_for: Duration, awaiting_input: bool) -> ActivityState {
if idle_for < ACTIVE_WINDOW {
ActivityState::Active
} else if awaiting_input {
ActivityState::AwaitingInput
} else {
ActivityState::Idle
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn recent_output_is_active_regardless_of_block_state() {
assert_eq!(
decide(Duration::from_millis(100), false),
ActivityState::Active
);
assert_eq!(
decide(Duration::from_millis(100), true),
ActivityState::Active
);
}
#[test]
fn quiescent_and_blocked_is_awaiting_input() {
assert_eq!(
decide(ACTIVE_WINDOW + Duration::from_secs(1), true),
ActivityState::AwaitingInput
);
}
#[test]
fn quiescent_and_not_blocked_is_idle() {
assert_eq!(
decide(ACTIVE_WINDOW + Duration::from_secs(1), false),
ActivityState::Idle
);
}
}