Synced from monorepo
Synced from monorepo Changes: - Shell: accept target response id on rewind execute - Shell: stamp response id on chat user message chunks - Worktree: optional rebuild and stale git registration cleanup in auto-GC - Worktree: kind-aware auto-GC TTLs and config knobs - Worktree: macOS process CWD scan and Unix PID liveness for GC guards - Worktree: automatic throttled GC on startup (Linux age-based; non-Linux dead-only) - Pager: add `[ui].combine_queued_prompts` to batch queued follow-ups - Shell: stop overwriting user skills - Tools: read markdown in `skills/` directories untruncated - `/usage` shows per-session token and dollar usage in the TUI - Security: prompt on environment-dumping `ps` variants - Security: always-safe `kubectl` no longer runs arbitrary kubeconfig credential plugins without permission - Tools: make scheduler deletion durable - Shell: add relocation storage primitives - Shell: give side model calls their own conversation ids - Fix five workflow-runtime bugs (budget, pause, cancel, reconnect) - Security: peel `env -S` / `--split-string` operands in the Bash permission gate (managed deny/ask) - Pager: expose doctor in the TUI - Security: block unauthorized RCE via abused safe commands - Pager idle watcher cue: "1 subagent still running" instead of "watching · 1 subagent" - Security: block `rg --pre` arbitrary code execution in auto-mode - Voice: diagnose silent-mic failures (macOS permission) and add doctor/terminal-setup Voice section - App builder deployer: `allow_forking` and `show_built_with_grok` - Pager: stop stacking duplicate "Worked for" markers on parked turns - Shell: support `max` as a distinct reasoning effort tier - Tools: serialize background `/loop` fires on the whole work unit - Shell: add working-directory relocation state primitives - Proto: `ClientToolResult` and `ChatConfig` client-side tools - Shell: model providers - Chat: select App Builder product on the Build path - Shell: attach author identity to feedback when the deployment opts in - Doctor: fix for SSH wrap setup - Workflow authoring skills: create-workflow and import-claude-workflow docs - Add read-only grok doctor - Sandbox: apply Landlock without a controlling TTY - Pager: recover image paste over grok wrap on headless remotes - Pager: make actions screen-mode aware - Shell: resume sessions when the working directory moves - Pager: centralize terminal diagnostics - Workspace: gate inline shell file access - Pager: centralize terminal probes - Pager: edit minimal prompts in an external editor - Pager: standardize backgrounding on Ctrl+B - Shell: recap rides the parent turn's prompt cache - Tools: add scheduler lifecycle version clock Source-Revision: 0f4d7c91b8b2b408333f6de1e8a76cb8eaa71899
This commit is contained in:
parent
a881e6703f
commit
3af4d5d398
556 changed files with 56609 additions and 21892 deletions
|
|
@ -196,7 +196,11 @@ fn build_local_rows(
|
|||
if !include_subagents {
|
||||
continue;
|
||||
}
|
||||
let mut subagents: Vec<&SubagentInfo> = agent.subagent_sessions.values().collect();
|
||||
let mut subagents: Vec<&SubagentInfo> = agent
|
||||
.subagent_sessions
|
||||
.values()
|
||||
.filter(|info| info.workflow_run_id.is_none())
|
||||
.collect();
|
||||
subagents.sort_by(|a, b| {
|
||||
let a_running = !a.finished;
|
||||
let b_running = !b.finished;
|
||||
|
|
@ -400,47 +404,22 @@ pub fn has_background_work(agent: &AgentView) -> bool {
|
|||
.any(|t| t.status == crate::app::agent::BgTaskStatus::Running)
|
||||
|| !agent.session.scheduled_tasks.is_empty()
|
||||
}
|
||||
/// Compact `"watching · …"` label summarising a turn-idle agent's live
|
||||
/// background work, listing only the non-zero kinds with singular/plural
|
||||
/// nouns — e.g. `"watching · 1 monitor · 2 loops"` or
|
||||
/// `"watching · 1 task"`. `None` when there's no background work (the
|
||||
/// caller then falls back to a bare `"Working"`). Mirrors the agent
|
||||
/// view's idle "watching" cue (`turn_status::watching_label`) so the
|
||||
/// dashboard and the agent view speak the same language; the counting
|
||||
/// matches [`has_background_work`].
|
||||
/// Compact `"… still running"` label summarising a turn-idle agent's live
|
||||
/// background work — e.g. `"1 monitor · 2 loops still running"` or
|
||||
/// `"1 task still running"`. `None` when there's no background work (the
|
||||
/// caller then falls back to a bare `"Working"`). Shares the format
|
||||
/// mechanics with the agent view's idle cue
|
||||
/// ([`crate::views::turn_status::format_still_running`]) but keeps the
|
||||
/// dashboard's own nouns ("task", not "command") and omits subagents —
|
||||
/// dashboard rows list those separately. Counts come from local state (not
|
||||
/// backend content), so no sanitise.
|
||||
fn background_work_label(agent: &AgentView) -> Option<String> {
|
||||
use std::fmt::Write as _;
|
||||
let mut monitors = 0usize;
|
||||
let mut tasks = 0usize;
|
||||
for t in agent.session.bg_tasks.values() {
|
||||
if t.status != crate::app::agent::BgTaskStatus::Running {
|
||||
continue;
|
||||
}
|
||||
if t.is_monitor {
|
||||
monitors += 1;
|
||||
} else {
|
||||
tasks += 1;
|
||||
}
|
||||
}
|
||||
let loops = agent.session.scheduled_tasks.len();
|
||||
if monitors + tasks + loops == 0 {
|
||||
return None;
|
||||
}
|
||||
let mut label = String::with_capacity(24);
|
||||
label.push_str("watching");
|
||||
if monitors > 0 {
|
||||
let noun = if monitors == 1 { "monitor" } else { "monitors" };
|
||||
let _ = write!(label, " \u{00b7} {monitors} {noun}");
|
||||
}
|
||||
if loops > 0 {
|
||||
let noun = if loops == 1 { "loop" } else { "loops" };
|
||||
let _ = write!(label, " \u{00b7} {loops} {noun}");
|
||||
}
|
||||
if tasks > 0 {
|
||||
let noun = if tasks == 1 { "task" } else { "tasks" };
|
||||
let _ = write!(label, " \u{00b7} {tasks} {noun}");
|
||||
}
|
||||
Some(label)
|
||||
let w = agent.watchers();
|
||||
crate::views::turn_status::format_still_running([
|
||||
(w.monitors, "monitor"),
|
||||
(w.loops, "loop"),
|
||||
(w.commands, "task"),
|
||||
])
|
||||
}
|
||||
/// Classify a subagent.
|
||||
///
|
||||
|
|
@ -1084,6 +1063,7 @@ mod tests {
|
|||
context_source: None,
|
||||
resumed_from: None,
|
||||
capability_mode: None,
|
||||
workflow_run_id: None,
|
||||
context_normalized: false,
|
||||
child_updates_replayed: false,
|
||||
parent_prompt_id: None,
|
||||
|
|
@ -1118,6 +1098,30 @@ mod tests {
|
|||
assert_eq!(classify_subagent(&info), RowState::Working);
|
||||
}
|
||||
#[test]
|
||||
fn full_tree_excludes_workflow_owned_subagent_rows() {
|
||||
let mut agents = IndexMap::new();
|
||||
let mut agent = crate::app::agent_view::test_fixtures::make_agent();
|
||||
let mut workflow_child = make_subagent("workflow-child", false, None);
|
||||
workflow_child.workflow_run_id = Some(Arc::from("wf_1"));
|
||||
agent
|
||||
.subagent_sessions
|
||||
.insert("workflow-child".into(), workflow_child);
|
||||
agents.insert(AgentId(0), agent);
|
||||
let rows = build_rows(
|
||||
&agents,
|
||||
&Default::default(),
|
||||
&[],
|
||||
Some(AgentId(0)),
|
||||
super::super::state::Grouping::State,
|
||||
&Filter::default(),
|
||||
None,
|
||||
);
|
||||
assert!(
|
||||
rows.iter()
|
||||
.all(|row| !matches!(row.id, DashboardRowId::Subagent { .. }))
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn classify_subagent_completed() {
|
||||
let info = make_subagent("a", true, Some("completed"));
|
||||
assert_eq!(classify_subagent(&info), RowState::Completed);
|
||||
|
|
@ -1921,7 +1925,7 @@ mod tests {
|
|||
.insert("m1".into(), running_bg_task("m1", true));
|
||||
assert_eq!(classify_top_level(&agent), RowState::Working);
|
||||
let row = top_level_row(AgentId(0), &agent, false, false, None);
|
||||
assert_eq!(row.activity.as_deref(), Some("watching · 1 monitor"));
|
||||
assert_eq!(row.activity.as_deref(), Some("1 monitor still running"));
|
||||
}
|
||||
/// An active scheduled `/loop` keeps the agent `Working` even with a
|
||||
/// fully idle turn, labelled as a loop.
|
||||
|
|
@ -1934,7 +1938,7 @@ mod tests {
|
|||
.insert("l1".into(), scheduled_loop("l1"));
|
||||
assert_eq!(classify_top_level(&agent), RowState::Working);
|
||||
let row = top_level_row(AgentId(0), &agent, false, false, None);
|
||||
assert_eq!(row.activity.as_deref(), Some("watching · 1 loop"));
|
||||
assert_eq!(row.activity.as_deref(), Some("1 loop still running"));
|
||||
}
|
||||
/// The background-work label lists every non-zero kind (monitors,
|
||||
/// then loops, then plain tasks) with correct singular/plural nouns.
|
||||
|
|
@ -1961,12 +1965,12 @@ mod tests {
|
|||
let row = top_level_row(AgentId(0), &agent, false, false, None);
|
||||
assert_eq!(
|
||||
row.activity.as_deref(),
|
||||
Some("watching · 1 monitor · 1 loop · 2 tasks"),
|
||||
Some("1 monitor · 1 loop · 2 tasks still running"),
|
||||
);
|
||||
}
|
||||
/// The background-work label is the LAST activity fallback: a more
|
||||
/// specific Working signal (here, replay loading) still wins over
|
||||
/// "watching · …", so a real turn is never masked by it.
|
||||
/// "… still running", so a real turn is never masked by it.
|
||||
#[test]
|
||||
fn specific_working_activity_wins_over_background_label() {
|
||||
let mut agent = make_idle_agent_with_model(None);
|
||||
|
|
|
|||
Loading…
Reference in a new issue