Synced from monorepo
Synced from monorepo Changes: - Workspace task snapshots only list incomplete backgrounded tasks - Quiet auth, LSP, and config warnings in the shell - Fix observability attributes for warm store errors, restore setup, remote tools, and preview denials - Fail closed when soak metrics are missing - Run plan-mode exit last in mixed tool batches - Allow /loop to store prompts that can terminate the loop - Make subagent maximum nesting depth configurable - Security: apply sandbox profile to the leader process that executes tools Source-Revision: 1adcd1f477870e4a97bacbd6be78c8a3bfbac46d
This commit is contained in:
parent
b41c75a578
commit
02d9359435
96 changed files with 2346 additions and 351 deletions
|
|
@ -835,6 +835,7 @@ mod tests {
|
|||
explicitly_killed: false,
|
||||
owner_session_id: owner.map(|s| s.to_string()),
|
||||
description: None,
|
||||
is_backgrounded: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -444,6 +444,7 @@ impl ProcessState {
|
|||
kind: self.kind,
|
||||
owner_session_id: self.owner_session_id.clone(),
|
||||
description: self.description.clone(),
|
||||
is_backgrounded: self.bg_status.is_backgrounded(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1617,6 +1618,7 @@ impl LocalTerminalActor {
|
|||
explicitly_killed: p.explicitly_killed,
|
||||
owner_session_id: p.owner_session_id.clone(),
|
||||
description: p.description.clone(),
|
||||
is_backgrounded: true,
|
||||
};
|
||||
self.completed_task_snapshots.insert(id.clone(), snapshot);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -219,6 +219,9 @@ pub struct TaskSnapshot {
|
|||
/// Model-supplied label for task UI / snapshots.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub description: Option<String>,
|
||||
/// True after explicit/user/auto backgrounding; false for pure foreground runs.
|
||||
#[serde(default)]
|
||||
pub is_backgrounded: bool,
|
||||
}
|
||||
|
||||
impl TaskSnapshot {
|
||||
|
|
@ -238,6 +241,11 @@ impl TaskSnapshot {
|
|||
pub fn is_outstanding(&self) -> bool {
|
||||
!self.completed
|
||||
}
|
||||
|
||||
/// Incomplete and backgrounded — tray/`tasks_snapshot` predicate (not FG in-flight).
|
||||
pub fn is_outstanding_background(&self) -> bool {
|
||||
!self.completed && self.is_backgrounded
|
||||
}
|
||||
}
|
||||
|
||||
/// Result of killing a terminal task.
|
||||
|
|
|
|||
|
|
@ -50,7 +50,8 @@ pub use lsp::LspTool;
|
|||
pub use monitor::tool::MonitorTool;
|
||||
pub use read_file::ReadFileTool;
|
||||
pub use scheduler::create::{
|
||||
SCHEDULER_CREATE_TOOL_NAME, SchedulerCreateTool, loop_schedule_instruction, loop_usage_message,
|
||||
LoopFireMode, SCHEDULER_CREATE_TOOL_NAME, SchedulerCreateTool, loop_schedule_instruction,
|
||||
loop_usage_message,
|
||||
};
|
||||
pub use scheduler::delete::{SCHEDULER_DELETE_TOOL_NAME, SchedulerDeleteTool};
|
||||
pub use scheduler::list::SchedulerListTool;
|
||||
|
|
|
|||
|
|
@ -110,6 +110,53 @@ impl MonitorInput {
|
|||
}
|
||||
}
|
||||
|
||||
// Mid-turn monitor event buffer
|
||||
|
||||
/// A monitor event notification to be surfaced as a `<system-reminder>` mid-turn.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MonitorEventNotification {
|
||||
pub task_id: String,
|
||||
pub event_text: String,
|
||||
/// Session that owns the monitor which produced this event.
|
||||
///
|
||||
/// In leader mode every session shares one [`MonitorEventBuffer`], so the
|
||||
/// drain sites filter on this to avoid surfacing one session's monitor
|
||||
/// events inside another session's turn. `None` for legacy / non-grok-build
|
||||
/// backends, which any session drains for backwards compatibility.
|
||||
pub owner_session_id: Option<String>,
|
||||
}
|
||||
|
||||
impl MonitorEventNotification {
|
||||
/// Whether this buffered event should surface in the session whose owner id
|
||||
/// is `my_owner`. Mirrors `task_owned_by_session`: an event surfaces only
|
||||
/// when it has no recorded owner (legacy) or its owner matches the draining
|
||||
/// session. Foreign events stay buffered for their own session to drain.
|
||||
pub fn owned_by_session(&self, my_owner: Option<&str>) -> bool {
|
||||
match (my_owner, self.owner_session_id.as_deref()) {
|
||||
(Some(me), Some(owner)) => me == owner,
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Shared buffer for mid-turn monitor event notifications: an [`EventQueue`]
|
||||
/// of [`MonitorEventNotification`]. Producers `push_capped`; the turn loop
|
||||
/// drains its session's events via [`drain_owned`].
|
||||
///
|
||||
/// [`EventQueue`]: xai_interjection_core::EventQueue
|
||||
pub type MonitorEventBuffer = xai_interjection_core::EventQueue<MonitorEventNotification>;
|
||||
|
||||
crate::register_resource!("grok_build", "MonitorEventBuffer", MonitorEventBuffer);
|
||||
|
||||
/// Drain only `my_owner`'s events (the buffer is shared across sessions in
|
||||
/// leader mode); owner-less legacy events drain anywhere.
|
||||
pub fn drain_owned(
|
||||
buffer: &MonitorEventBuffer,
|
||||
my_owner: Option<&str>,
|
||||
) -> Vec<MonitorEventNotification> {
|
||||
buffer.drain_matching(|e| e.owned_by_session(my_owner))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ use super::types::{ScheduledTask, SchedulerCommand, SchedulerHandle, scheduler_t
|
|||
// Canonical /loop wording lives in the light API crate so other consumers can
|
||||
// link it without the tools implementation crate; re-exported to keep paths stable.
|
||||
pub use xai_grok_tools_api::slash_commands::{
|
||||
SCHEDULER_CREATE_TOOL_NAME, loop_schedule_instruction, loop_usage_message,
|
||||
LoopFireMode, SCHEDULER_CREATE_TOOL_NAME, loop_schedule_instruction, loop_usage_message,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
|
||||
|
|
@ -475,7 +475,7 @@ mod tests {
|
|||
#[test]
|
||||
fn loop_schedule_instruction_holds_invariants() {
|
||||
let args = "every 30 minutes do x";
|
||||
let instr = loop_schedule_instruction(args);
|
||||
let instr = loop_schedule_instruction(args, LoopFireMode::Detached);
|
||||
assert!(
|
||||
!instr.contains("10m"),
|
||||
"instruction must not default: {instr}"
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ use super::types::{
|
|||
};
|
||||
|
||||
/// Cap on retained completed-subagent entries before the oldest are evicted.
|
||||
/// Public so the subagent soak test can assert the coordinator stays bounded.
|
||||
pub const MAX_COMPLETED_ENTRIES: usize = 1024;
|
||||
pub(super) const OUTPUT_UNAVAILABLE_PLACEHOLDER: &str = "[subagent output no longer available]";
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
//!
|
||||
//! - `SubagentBackendResource` — backend for spawn/query/cancel (required)
|
||||
//! - `SubagentDepthCounter` — current nesting depth (optional, defaults to 0)
|
||||
//! - `MaxSubagentDepth` — max nesting (optional, defaults to [`MAX_SUBAGENT_DEPTH`])
|
||||
//! - `SessionIdResource` — current session ID for parent scoping (optional)
|
||||
//! - `SubagentForegroundWait` — host wait-window guard factory (optional)
|
||||
//! - `TaskModelValidator` — validates explicit model slugs before spawn
|
||||
|
|
@ -30,10 +31,16 @@ use crate::types::resources::SharedResources;
|
|||
use crate::types::tool::{ToolKind, ToolNamespace};
|
||||
use xai_tool_types::{SubagentCompletedOutput, SubagentIsolationMode, TaskToolInput};
|
||||
|
||||
/// Maximum nesting depth for subagents. A top-level session is depth 0;
|
||||
/// the first subagent is depth 1. Subagents cannot spawn further subagents.
|
||||
/// Default max nesting depth when [`MaxSubagentDepth`] is not injected.
|
||||
pub const MAX_SUBAGENT_DEPTH: u32 = 1;
|
||||
|
||||
pub fn effective_max_subagent_depth(resources: &crate::types::resources::Resources) -> u32 {
|
||||
resources
|
||||
.get::<MaxSubagentDepth>()
|
||||
.map(|d| d.0)
|
||||
.unwrap_or(MAX_SUBAGENT_DEPTH)
|
||||
}
|
||||
|
||||
// ───────────────────────────────────────────────────────────────────────────
|
||||
// Tool implementation
|
||||
// ───────────────────────────────────────────────────────────────────────────
|
||||
|
|
@ -125,10 +132,19 @@ impl xai_tool_runtime::Tool for TaskTool {
|
|||
.map(|cancellation| cancellation.0.clone());
|
||||
|
||||
// 1. Depth check
|
||||
let (depth, backend, model_validator, parent_session_id, parent_prompt_id, foreground_wait) = {
|
||||
let (
|
||||
depth,
|
||||
max_depth,
|
||||
backend,
|
||||
model_validator,
|
||||
parent_session_id,
|
||||
parent_prompt_id,
|
||||
foreground_wait,
|
||||
) = {
|
||||
let res = resources.lock().await;
|
||||
|
||||
let depth = res.get::<SubagentDepthCounter>().map(|d| d.0).unwrap_or(0);
|
||||
let max_depth = effective_max_subagent_depth(&res);
|
||||
|
||||
let backend = res
|
||||
.get::<SubagentBackendResource>()
|
||||
|
|
@ -155,6 +171,7 @@ impl xai_tool_runtime::Tool for TaskTool {
|
|||
|
||||
(
|
||||
depth,
|
||||
max_depth,
|
||||
backend,
|
||||
model_validator,
|
||||
parent_session_id,
|
||||
|
|
@ -163,9 +180,9 @@ impl xai_tool_runtime::Tool for TaskTool {
|
|||
)
|
||||
};
|
||||
|
||||
if depth >= MAX_SUBAGENT_DEPTH {
|
||||
if depth >= max_depth {
|
||||
return Err(xai_tool_runtime::ToolError::invalid_arguments(format!(
|
||||
"Subagent depth limit exceeded (current depth: {depth}, max: {MAX_SUBAGENT_DEPTH}). \
|
||||
"Subagent depth limit exceeded (current depth: {depth}, max: {max_depth}). \
|
||||
Cannot spawn further nested subagents."
|
||||
)));
|
||||
}
|
||||
|
|
@ -558,6 +575,41 @@ mod tests {
|
|||
assert!(err.contains("depth limit exceeded"), "error: {err}");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn raised_max_depth_allows_nested_spawn() {
|
||||
let (backend, mut rx) = make_backend();
|
||||
let mut resources = Resources::new();
|
||||
resources.insert(backend);
|
||||
resources.insert(SubagentDepthCounter(1));
|
||||
resources.insert(MaxSubagentDepth(2));
|
||||
resources.insert(SessionIdResource("child-session".to_string()));
|
||||
resources.insert(CurrentPromptIdResource("prompt-nested".to_string()));
|
||||
|
||||
let result = xai_tool_runtime::Tool::run(
|
||||
&TaskTool,
|
||||
test_ctx(resources.into_shared()),
|
||||
TaskToolInput {
|
||||
description: "nested ok".into(),
|
||||
prompt: "should be allowed at max_depth=2".into(),
|
||||
subagent_type: "explore".into(),
|
||||
run_in_background: true,
|
||||
capability_mode: None,
|
||||
isolation: None,
|
||||
resume_from: None,
|
||||
cwd: None,
|
||||
model: None,
|
||||
task_id: None,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
assert!(
|
||||
result.is_ok(),
|
||||
"expected Ok at depth 1 with max 2: {result:?}"
|
||||
);
|
||||
let _ = rx.try_recv();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn subagent_cannot_spawn_nested_subagent() {
|
||||
let (backend, _rx) = make_backend();
|
||||
|
|
|
|||
|
|
@ -10,7 +10,8 @@
|
|||
//!
|
||||
//! - `SubagentBackendResource` — wraps an `Arc<dyn SubagentBackend>` that
|
||||
//! abstracts spawn/query/cancel (see [`super::backend`])
|
||||
//! - `SubagentDepthCounter` — tracks nesting depth (max 1, no recursive spawning)
|
||||
//! - `SubagentDepthCounter` — current nesting depth
|
||||
//! - `MaxSubagentDepth` — configured max nesting depth
|
||||
//! - `SessionIdResource` — carries the current session ID for parent scoping
|
||||
//! - `TaskModelValidator` — validates explicit model slugs before background spawn
|
||||
//!
|
||||
|
|
@ -877,51 +878,6 @@ pub struct SubagentEventSender(#[educe(Debug(ignore))] pub mpsc::UnboundedSender
|
|||
|
||||
register_resource!("grok_build", "SubagentEventSender", SubagentEventSender);
|
||||
|
||||
// Mid-turn monitor event buffer
|
||||
|
||||
/// A monitor event notification to be surfaced as a `<system-reminder>` mid-turn.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MonitorEventNotification {
|
||||
pub task_id: String,
|
||||
pub event_text: String,
|
||||
/// Session that owns the monitor which produced this event.
|
||||
///
|
||||
/// In leader mode every session shares one [`MonitorEventBuffer`], so the
|
||||
/// drain sites filter on this to avoid surfacing one session's monitor
|
||||
/// events inside another session's turn. `None` for legacy / non-grok-build
|
||||
/// backends, which any session drains for backwards compatibility.
|
||||
pub owner_session_id: Option<String>,
|
||||
}
|
||||
|
||||
impl MonitorEventNotification {
|
||||
/// Whether this buffered event should surface in the session whose owner id
|
||||
/// is `my_owner`. Mirrors `task_owned_by_session`: an event surfaces only
|
||||
/// when it has no recorded owner (legacy) or its owner matches the draining
|
||||
/// session. Foreign events stay buffered for their own session to drain.
|
||||
pub fn owned_by_session(&self, my_owner: Option<&str>) -> bool {
|
||||
match (my_owner, self.owner_session_id.as_deref()) {
|
||||
(Some(me), Some(owner)) => me == owner,
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Shared buffer for mid-turn monitor event notifications: an [`EventQueue`]
|
||||
/// of [`MonitorEventNotification`]. Producers `push_capped`; the turn loop
|
||||
/// drains its session's events via [`drain_owned`].
|
||||
pub type MonitorEventBuffer = xai_interjection_core::EventQueue<MonitorEventNotification>;
|
||||
|
||||
register_resource!("grok_build", "MonitorEventBuffer", MonitorEventBuffer);
|
||||
|
||||
/// Drain only `my_owner`'s events (the buffer is shared across sessions in
|
||||
/// leader mode); owner-less legacy events drain anywhere.
|
||||
pub fn drain_owned(
|
||||
buffer: &MonitorEventBuffer,
|
||||
my_owner: Option<&str>,
|
||||
) -> Vec<MonitorEventNotification> {
|
||||
buffer.drain_matching(|e| e.owned_by_session(my_owner))
|
||||
}
|
||||
|
||||
// Active subagent listing (compaction)
|
||||
|
||||
/// Lightweight summary of a running subagent.
|
||||
|
|
@ -952,15 +908,18 @@ pub struct SubagentListActiveRequest {
|
|||
pub respond_to: oneshot::Sender<Vec<ActiveSubagentSummary>>,
|
||||
}
|
||||
|
||||
/// Tracks nesting depth. Injected into child's Resources with depth+1.
|
||||
///
|
||||
/// Top-level sessions start at depth 0. Each child increments by 1.
|
||||
/// `TaskTool` rejects spawns when `depth >= MAX_SUBAGENT_DEPTH`.
|
||||
/// Current nesting depth (top-level = 0; child = parent + 1).
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SubagentDepthCounter(pub u32);
|
||||
|
||||
register_resource!("grok_build", "SubagentDepthCounter", SubagentDepthCounter);
|
||||
|
||||
/// Host-injected max nesting depth; absent → [`super::MAX_SUBAGENT_DEPTH`].
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct MaxSubagentDepth(pub u32);
|
||||
|
||||
register_resource!("grok_build", "MaxSubagentDepth", MaxSubagentDepth);
|
||||
|
||||
/// Session-scoped validator for model-facing `Task.model` arguments.
|
||||
///
|
||||
/// Returns an error message for an invalid slug and `None` for a valid slug.
|
||||
|
|
|
|||
|
|
@ -917,6 +917,7 @@ pub(crate) mod test_helpers {
|
|||
explicitly_killed: false,
|
||||
owner_session_id: None,
|
||||
description: None,
|
||||
is_backgrounded: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
use crate::types::requirements::{Expr, ToolRequirement};
|
||||
use crate::types::tool::{ToolKind, ToolNamespace};
|
||||
|
||||
use super::task::MAX_SUBAGENT_DEPTH;
|
||||
use super::task::types::SubagentDepthCounter;
|
||||
|
||||
pub use xai_grok_tools_api::slash_commands::WORKFLOW_TOOL_NAME;
|
||||
|
|
@ -237,7 +236,8 @@ impl xai_tool_runtime::Tool for WorkflowTool {
|
|||
(depth, sender)
|
||||
};
|
||||
|
||||
if depth >= MAX_SUBAGENT_DEPTH {
|
||||
// Workflows stay top-level-only regardless of configurable subagent depth.
|
||||
if depth > 0 {
|
||||
return Err(xai_tool_runtime::ToolError::custom(
|
||||
"workflow_depth_exceeded",
|
||||
"Workflows can only be launched from a top-level session (subagents and \
|
||||
|
|
|
|||
|
|
@ -108,6 +108,7 @@ mod tests {
|
|||
explicitly_killed: false,
|
||||
owner_session_id: None,
|
||||
description: None,
|
||||
is_backgrounded: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -255,7 +255,7 @@ fn split_wrapped_monitor_event(event_text: &str) -> Option<(&str, &str)> {
|
|||
/// Buffered `event_text` arrives pre-wrapped (`wrap_monitor_event`); it is
|
||||
/// unwrapped via [`split_wrapped_monitor_event`] with verbatim fallback.
|
||||
pub fn format_monitor_events(
|
||||
events: &[crate::implementations::grok_build::task::types::MonitorEventNotification],
|
||||
events: &[crate::implementations::grok_build::monitor::types::MonitorEventNotification],
|
||||
task_output_name: Option<&str>,
|
||||
) -> Option<String> {
|
||||
use std::fmt::Write as _;
|
||||
|
|
@ -278,7 +278,8 @@ pub fn format_monitor_events(
|
|||
))
|
||||
}
|
||||
_ => {
|
||||
type Event = crate::implementations::grok_build::task::types::MonitorEventNotification;
|
||||
type Event =
|
||||
crate::implementations::grok_build::monitor::types::MonitorEventNotification;
|
||||
let mut groups: Vec<(&str, Vec<&Event>)> = Vec::new();
|
||||
for event in events {
|
||||
match groups.iter_mut().find(|(id, _)| *id == event.task_id) {
|
||||
|
|
@ -802,6 +803,7 @@ mod tests {
|
|||
explicitly_killed: false,
|
||||
owner_session_id: None,
|
||||
description: None,
|
||||
is_backgrounded: false,
|
||||
};
|
||||
let msg = format_bash_completion(&task, Some("get_command_or_subagent_output"), None);
|
||||
assert!(msg.contains("abc-123"));
|
||||
|
|
@ -829,6 +831,7 @@ mod tests {
|
|||
explicitly_killed: false,
|
||||
owner_session_id: None,
|
||||
description: None,
|
||||
is_backgrounded: false,
|
||||
};
|
||||
let msg = format_monitor_completion(&task, Some("get_command_or_subagent_output"));
|
||||
assert!(
|
||||
|
|
@ -862,6 +865,7 @@ mod tests {
|
|||
explicitly_killed: false,
|
||||
owner_session_id: None,
|
||||
description: None,
|
||||
is_backgrounded: false,
|
||||
};
|
||||
let msg = format_monitor_completion(&task, None);
|
||||
assert!(
|
||||
|
|
@ -890,6 +894,7 @@ mod tests {
|
|||
explicitly_killed: false,
|
||||
owner_session_id: None,
|
||||
description: None,
|
||||
is_backgrounded: false,
|
||||
};
|
||||
let msg = format_bash_completion(&task, Some("get_command_or_subagent_output"), None);
|
||||
assert!(msg.contains("cargo test"));
|
||||
|
|
@ -915,6 +920,7 @@ mod tests {
|
|||
explicitly_killed: false,
|
||||
owner_session_id: None,
|
||||
description: None,
|
||||
is_backgrounded: false,
|
||||
};
|
||||
let msg = format_bash_completion(&task, Some("get_command_or_subagent_output"), None);
|
||||
assert!(msg.contains("exit code: unknown"));
|
||||
|
|
@ -943,6 +949,7 @@ mod tests {
|
|||
explicitly_killed: false,
|
||||
owner_session_id: None,
|
||||
description: None,
|
||||
is_backgrounded: false,
|
||||
};
|
||||
let msg = format_bash_completion(&task, Some("get_command_or_subagent_output"), None);
|
||||
assert!(
|
||||
|
|
@ -982,6 +989,7 @@ mod tests {
|
|||
explicitly_killed: false,
|
||||
owner_session_id: None,
|
||||
description: None,
|
||||
is_backgrounded: false,
|
||||
};
|
||||
let msg = format_bash_completion(&task, Some("get_command_or_subagent_output"), None);
|
||||
assert!(
|
||||
|
|
@ -1020,6 +1028,7 @@ mod tests {
|
|||
explicitly_killed: false,
|
||||
owner_session_id: None,
|
||||
description: None,
|
||||
is_backgrounded: false,
|
||||
};
|
||||
let msg = format_bash_completion(&task, Some("get_command_or_subagent_output"), None);
|
||||
assert!(msg.contains("exit code: 0"));
|
||||
|
|
@ -1181,6 +1190,7 @@ mod tests {
|
|||
explicitly_killed: false,
|
||||
owner_session_id: None,
|
||||
description: None,
|
||||
is_backgrounded: false,
|
||||
}
|
||||
}
|
||||
fn make_running(id: &str) -> TaskSnapshot {
|
||||
|
|
@ -1202,6 +1212,7 @@ mod tests {
|
|||
explicitly_killed: false,
|
||||
owner_session_id: None,
|
||||
description: None,
|
||||
is_backgrounded: false,
|
||||
}
|
||||
}
|
||||
fn make_bg_started(id: &str) -> crate::types::output::BackgroundTaskStarted {
|
||||
|
|
@ -1811,7 +1822,7 @@ mod tests {
|
|||
/// reintroduced.
|
||||
#[tokio::test]
|
||||
async fn reminder_pipeline_ignores_monitor_event_buffer() {
|
||||
use crate::implementations::grok_build::task::types::{
|
||||
use crate::implementations::grok_build::monitor::types::{
|
||||
MonitorEventBuffer, MonitorEventNotification,
|
||||
};
|
||||
use crate::types::resources::Resources;
|
||||
|
|
@ -1845,7 +1856,7 @@ mod tests {
|
|||
/// own + owner-less legacy events; foreign events stay buffered.
|
||||
#[test]
|
||||
fn drain_owned_partitions_by_session_owner() {
|
||||
use crate::implementations::grok_build::task::types::{
|
||||
use crate::implementations::grok_build::monitor::types::{
|
||||
MonitorEventBuffer, MonitorEventNotification, drain_owned,
|
||||
};
|
||||
let shared_buffer = MonitorEventBuffer::default();
|
||||
|
|
@ -1882,7 +1893,7 @@ mod tests {
|
|||
/// empty => `None`.
|
||||
#[test]
|
||||
fn format_monitor_events_single_vs_batched() {
|
||||
use crate::implementations::grok_build::task::types::MonitorEventNotification;
|
||||
use crate::implementations::grok_build::monitor::types::MonitorEventNotification;
|
||||
let event = |task: &str, desc: &str, text: &str| MonitorEventNotification {
|
||||
task_id: task.to_string(),
|
||||
event_text: format!(
|
||||
|
|
@ -1900,7 +1911,7 @@ mod tests {
|
|||
single, "<monitor-event task_id=\"task-0\">\n[alpha] line 0\n</monitor-event>",
|
||||
"single event must use the lean monitor-event form"
|
||||
);
|
||||
let bare = crate::implementations::grok_build::task::types::MonitorEventNotification {
|
||||
let bare = crate::implementations::grok_build::monitor::types::MonitorEventNotification {
|
||||
task_id: "task-9".into(),
|
||||
event_text: "bare text, no wrapper".into(),
|
||||
owner_session_id: None,
|
||||
|
|
@ -1988,7 +1999,7 @@ mod tests {
|
|||
/// End-to-end multibyte safety through the formatter (single + batch).
|
||||
#[test]
|
||||
fn format_monitor_events_handles_multibyte_content() {
|
||||
use crate::implementations::grok_build::task::types::MonitorEventNotification;
|
||||
use crate::implementations::grok_build::monitor::types::MonitorEventNotification;
|
||||
let event = |task: &str, desc: &str, text: &str| MonitorEventNotification {
|
||||
task_id: task.to_string(),
|
||||
event_text: format!(
|
||||
|
|
|
|||
Loading…
Reference in a new issue