Synced from monorepo
Synced from monorepo Changes: - Workspace server: report `/ready` as failed with dwell on hub connect failure - Refresh OIDC token for the Grok agent in the shell - ACP terminal output recorder - Cross-platform provider auth commands in the shell - Default `/resume` to Grok sessions with a hint for hidden external sessions - Resume sessions by title with `--resume` - Limit app-builder archive size - Data-driven tag labels for slash commands - Doctor fixes for tmux - Custom provider gateways and subprocess environment policy in the shell - `/tutorial` — opt-in onboarding tour of Grok Build - Soft and required CLI version checks in the shell - Privacy banner env overrides survive live settings updates - Add remote flag to override the image-edit model - Return profile fields from auth info even when the access token is expired - Add edit control on queued prompt rows - Keep fail-closed policy when clearing orphans with no team - Setting to disable the Ctrl+Space/F8 voice shortcut - Pass `--raw` to pw-record so Linux dictation works on older PipeWire - Validate git URLs when adding marketplace entries - Stop shipping stale tool-doc parameter and tool names - Re-point dashboard attach after `/fork` only when the parent was attached - Surface Grok Computer media-generation results as file-path chunks - Clear web background-task tray on kill and keep the task description - Show privacy upsell banner in agent view until acted on - Add tools-server client callback surface - Protect persistent global hook sources Source-Revision: 95d84f443eddcbed6cbfd6eed22e2eafe6b3939d
This commit is contained in:
parent
a5727c5960
commit
69f0ba880a
286 changed files with 22939 additions and 9624 deletions
|
|
@ -3,22 +3,25 @@ license = "Apache-2.0"
|
|||
name = "xai-grok-subagent-resolution"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
description = "Subagent configuration resolution: merges persona, role, and spawn-time overrides into a resolved spec"
|
||||
description = "Shared subagent definition, runtime, prompt, and resume resolution"
|
||||
|
||||
[dependencies]
|
||||
chrono = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
xai-grok-agent = { path = "../xai-grok-agent" }
|
||||
xai-grok-sampling-types = { path = "../xai-grok-sampling-types" }
|
||||
xai-grok-tools = { path = "../xai-grok-tools" }
|
||||
xai-tool-types.workspace = true
|
||||
# TODO(phase2): add these when resolve_subagent_spec() composition is implemented:
|
||||
# xai-grok-agent = { path = "../xai-grok-agent" } # AgentDefinition lookup
|
||||
# xai-fast-worktree = { path = "../xai-fast-worktree" } # worktree creation
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = { workspace = true }
|
||||
tokio = { workspace = true, features = ["macros", "rt"] }
|
||||
toml = { workspace = true }
|
||||
|
||||
[lints]
|
||||
|
|
|
|||
408
crates/codegen/xai-grok-subagent-resolution/src/definition.rs
Normal file
408
crates/codegen/xai-grok-subagent-resolution/src/definition.rs
Normal file
|
|
@ -0,0 +1,408 @@
|
|||
//! Production subagent definition discovery and tool-policy resolution.
|
||||
use crate::config::{SubagentPersona, SubagentRole};
|
||||
use crate::types::{EffectiveRuntimeConfig, ResolutionError};
|
||||
use std::collections::HashMap;
|
||||
use std::path::Path;
|
||||
use xai_grok_agent::config::{AgentDefinition, IsolationMode};
|
||||
use xai_grok_agent::plugins::PluginRegistry;
|
||||
use xai_grok_agent::prompt::context::{PromptAudience, PromptContext};
|
||||
use xai_grok_tools::implementations::grok_build::task::types::{
|
||||
SubagentCapabilityModeExt, SubagentRuntimeOverrides, prune_orphaned_background_task_tools,
|
||||
};
|
||||
use xai_grok_tools::registry::types::ToolConfig;
|
||||
use xai_grok_tools::types::compat::CompatConfig;
|
||||
use xai_grok_tools::types::template_renderer::TemplateRenderer;
|
||||
use xai_grok_tools::types::tool::ToolKind;
|
||||
use xai_tool_types::{SubagentCapabilityMode, SubagentIsolationMode};
|
||||
/// Inputs that affect definition discovery and spawn permission.
|
||||
pub struct DefinitionResolutionContext<'a> {
|
||||
pub cwd: &'a Path,
|
||||
pub plugins: Option<&'a PluginRegistry>,
|
||||
pub cli_agents: &'a [AgentDefinition],
|
||||
pub toggles: &'a HashMap<String, bool>,
|
||||
pub allowed_types: Option<&'a [String]>,
|
||||
}
|
||||
/// Inputs for validating a type when only session CLI names are available.
|
||||
pub struct DefinitionValidationContext<'a> {
|
||||
pub cwd: &'a Path,
|
||||
pub plugins: Option<&'a PluginRegistry>,
|
||||
pub cli_agent_names: &'a [String],
|
||||
pub toggles: &'a HashMap<String, bool>,
|
||||
pub allowed_types: Option<&'a [String]>,
|
||||
}
|
||||
/// Parent/runtime inputs that choose the production child harness flavor.
|
||||
pub struct HarnessToolsetContext<'a> {
|
||||
pub harness_override: Option<&'a str>,
|
||||
pub parent_agent_name: Option<&'a str>,
|
||||
pub parent_model_agent_type: Option<&'a str>,
|
||||
pub file_tool_overrides: Option<&'a [ToolConfig]>,
|
||||
}
|
||||
/// `false` twin: the alternate flavors re-select toolset presets and
|
||||
/// templates, so none is representable when the optional harness is compiled
|
||||
/// out. Keeps ungated call sites compiling.
|
||||
pub fn subagent_harness_flavor_is_representable(_agent_type: &str) -> bool {
|
||||
false
|
||||
}
|
||||
/// Apply the production parent/harness-dependent child toolset selection.
|
||||
pub fn apply_harness_toolset(
|
||||
#[allow(unused_variables)] subagent_type: &str,
|
||||
context: &HarnessToolsetContext<'_>,
|
||||
definition: &mut AgentDefinition,
|
||||
) {
|
||||
let flavor_agent = context.harness_override.or_else(|| {
|
||||
context
|
||||
.parent_agent_name
|
||||
.filter(|name| subagent_harness_flavor_is_representable(name))
|
||||
.or(context.parent_model_agent_type)
|
||||
});
|
||||
if flavor_agent.is_some_and(subagent_harness_flavor_is_representable) {
|
||||
} else if let Some(file_tools) = context.file_tool_overrides {
|
||||
definition.override_file_tools(file_tools.to_vec());
|
||||
}
|
||||
}
|
||||
/// Discover the same project/builtin/user/plugin definition used by production,
|
||||
/// with session CLI definitions as the final fallback.
|
||||
pub fn discover_agent_definition(
|
||||
subagent_type: &str,
|
||||
context: &DefinitionResolutionContext<'_>,
|
||||
) -> Option<AgentDefinition> {
|
||||
xai_grok_agent::discovery::by_name_in_cwd_with_plugins(
|
||||
subagent_type,
|
||||
context.cwd,
|
||||
context.plugins,
|
||||
)
|
||||
.or_else(|| {
|
||||
context
|
||||
.cli_agents
|
||||
.iter()
|
||||
.find(|definition| definition.name == subagent_type)
|
||||
.cloned()
|
||||
})
|
||||
}
|
||||
/// Sorted model-facing names available under the current discovery context.
|
||||
pub fn available_agent_names(context: &DefinitionResolutionContext<'_>) -> Vec<String> {
|
||||
let mut available: Vec<String> = xai_grok_agent::discovery::all_subagents_with_plugins(
|
||||
context.cwd,
|
||||
context.toggles,
|
||||
context.plugins,
|
||||
)
|
||||
.into_iter()
|
||||
.map(|entry| entry.name)
|
||||
.collect();
|
||||
for definition in context.cli_agents {
|
||||
if context
|
||||
.toggles
|
||||
.get(&definition.name)
|
||||
.copied()
|
||||
.unwrap_or(true)
|
||||
&& !available.contains(&definition.name)
|
||||
{
|
||||
available.push(definition.name.clone());
|
||||
}
|
||||
}
|
||||
available.sort();
|
||||
available
|
||||
}
|
||||
/// Apply the production toggle and parent allow-list gates.
|
||||
pub fn gate_agent_definition(
|
||||
subagent_type: &str,
|
||||
context: &DefinitionResolutionContext<'_>,
|
||||
) -> Result<(), ResolutionError> {
|
||||
if !context.toggles.get(subagent_type).copied().unwrap_or(true) {
|
||||
return Err(ResolutionError::Disabled {
|
||||
subagent_type: subagent_type.to_string(),
|
||||
});
|
||||
}
|
||||
if let Some(allowed) = context.allowed_types
|
||||
&& !allowed
|
||||
.iter()
|
||||
.any(|candidate| candidate.eq_ignore_ascii_case(subagent_type))
|
||||
{
|
||||
return Err(ResolutionError::NotAllowed {
|
||||
subagent_type: subagent_type.to_string(),
|
||||
allowed: allowed.to_vec(),
|
||||
});
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
/// Validate discovery, toggle, and allow-list gates without cloning definitions.
|
||||
pub fn validate_agent_name(
|
||||
subagent_type: &str,
|
||||
context: &DefinitionValidationContext<'_>,
|
||||
) -> Result<(), ResolutionError> {
|
||||
let resolves = context
|
||||
.cli_agent_names
|
||||
.iter()
|
||||
.any(|name| name == subagent_type)
|
||||
|| xai_grok_agent::discovery::by_name_in_cwd_with_plugins(
|
||||
subagent_type,
|
||||
context.cwd,
|
||||
context.plugins,
|
||||
)
|
||||
.is_some();
|
||||
if !resolves {
|
||||
let mut available: Vec<String> = xai_grok_agent::discovery::all_subagents_with_plugins(
|
||||
context.cwd,
|
||||
context.toggles,
|
||||
context.plugins,
|
||||
)
|
||||
.into_iter()
|
||||
.map(|entry| entry.name)
|
||||
.collect();
|
||||
for name in context.cli_agent_names {
|
||||
if context.toggles.get(name).copied().unwrap_or(true) && !available.contains(name) {
|
||||
available.push(name.clone());
|
||||
}
|
||||
}
|
||||
available.sort();
|
||||
return Err(ResolutionError::Unknown {
|
||||
subagent_type: subagent_type.to_owned(),
|
||||
available,
|
||||
});
|
||||
}
|
||||
let gate_context = DefinitionResolutionContext {
|
||||
cwd: context.cwd,
|
||||
plugins: context.plugins,
|
||||
cli_agents: &[],
|
||||
toggles: context.toggles,
|
||||
allowed_types: context.allowed_types,
|
||||
};
|
||||
gate_agent_definition(subagent_type, &gate_context)
|
||||
}
|
||||
/// Discover and gate one production agent definition.
|
||||
pub fn resolve_agent_definition(
|
||||
subagent_type: &str,
|
||||
context: &DefinitionResolutionContext<'_>,
|
||||
) -> Result<AgentDefinition, ResolutionError> {
|
||||
let definition = discover_agent_definition(subagent_type, context).ok_or_else(|| {
|
||||
ResolutionError::Unknown {
|
||||
subagent_type: subagent_type.to_string(),
|
||||
available: available_agent_names(context),
|
||||
}
|
||||
})?;
|
||||
gate_agent_definition(subagent_type, context)?;
|
||||
Ok(definition)
|
||||
}
|
||||
/// Resolve the role selected by production: type-specific first, then persona.
|
||||
pub fn select_role<'a>(
|
||||
subagent_type: &str,
|
||||
overrides: &SubagentRuntimeOverrides,
|
||||
roles: &'a HashMap<String, SubagentRole>,
|
||||
) -> (Option<&'a SubagentRole>, Option<String>) {
|
||||
if let Some(role) = roles.get(subagent_type) {
|
||||
return (Some(role), Some(subagent_type.to_string()));
|
||||
}
|
||||
let Some(persona) = overrides.persona.as_deref() else {
|
||||
return (None, None);
|
||||
};
|
||||
match roles.get(persona) {
|
||||
Some(role) => (Some(role), Some(persona.to_string())),
|
||||
None => (None, None),
|
||||
}
|
||||
}
|
||||
/// Fill runtime values whose defaults live on the resolved agent definition.
|
||||
pub fn apply_definition_runtime_defaults(
|
||||
runtime: &mut EffectiveRuntimeConfig,
|
||||
definition: &AgentDefinition,
|
||||
) {
|
||||
if runtime.capability_mode.is_none() {
|
||||
runtime.capability_mode = definition.capability_mode;
|
||||
}
|
||||
if runtime.reasoning_effort.is_none() {
|
||||
runtime.reasoning_effort = definition
|
||||
.effort
|
||||
.map(|effort| <&str>::from(effort).to_string());
|
||||
}
|
||||
if runtime.isolation == SubagentIsolationMode::None
|
||||
&& definition.isolation == Some(IsolationMode::Worktree)
|
||||
{
|
||||
runtime.isolation = SubagentIsolationMode::Worktree;
|
||||
}
|
||||
}
|
||||
/// Apply capability filtering and recursion depth to the exact production
|
||||
/// definition toolset.
|
||||
pub fn apply_child_tool_policy(
|
||||
definition: &mut AgentDefinition,
|
||||
capability_mode: Option<SubagentCapabilityMode>,
|
||||
allow_nested_subagents: bool,
|
||||
) {
|
||||
if let Some(mode) = capability_mode {
|
||||
mode.filter_tool_config(&mut definition.tool_config);
|
||||
}
|
||||
if !allow_nested_subagents {
|
||||
definition
|
||||
.tool_config
|
||||
.tools
|
||||
.retain(|tool| tool.kind != Some(ToolKind::Task));
|
||||
prune_orphaned_background_task_tools(&mut definition.tool_config);
|
||||
}
|
||||
}
|
||||
/// Resolve runtime overrides and definition defaults in the production order.
|
||||
pub fn resolve_runtime_config(
|
||||
subagent_type: &str,
|
||||
overrides: &SubagentRuntimeOverrides,
|
||||
roles: &HashMap<String, SubagentRole>,
|
||||
personas: &HashMap<String, SubagentPersona>,
|
||||
cwd: Option<&Path>,
|
||||
definition: &AgentDefinition,
|
||||
) -> EffectiveRuntimeConfig {
|
||||
let (role, role_name) = select_role(subagent_type, overrides, roles);
|
||||
let mut runtime = crate::resolve_effective_overrides(overrides, role, personas, cwd, role_name);
|
||||
apply_definition_runtime_defaults(&mut runtime, definition);
|
||||
runtime
|
||||
}
|
||||
/// Render the same full subagent base template + definition body used by the
|
||||
/// production `AgentBuilder`, for runtimes that expose only finalized tool
|
||||
/// names rather than a complete `ToolBridge`.
|
||||
pub fn render_subagent_system_prompt(
|
||||
definition: &AgentDefinition,
|
||||
runtime: &EffectiveRuntimeConfig,
|
||||
renderer: &TemplateRenderer,
|
||||
working_directory: &Path,
|
||||
) -> Option<String> {
|
||||
let context = PromptContext {
|
||||
prompt_mode: definition.prompt_mode.clone(),
|
||||
audience: PromptAudience::Subagent,
|
||||
prompt_body: definition.prompt_body.clone(),
|
||||
system_prompt: definition.system_prompt.clone(),
|
||||
role_instructions: runtime.role_prompt.clone(),
|
||||
persona_instructions: runtime.persona_instructions.clone(),
|
||||
os_name: Some(format!(
|
||||
"{} {}",
|
||||
std::env::consts::OS,
|
||||
std::env::consts::ARCH
|
||||
)),
|
||||
shell_path: std::env::var("SHELL").ok(),
|
||||
working_directory: Some(working_directory.to_string_lossy().into_owned()),
|
||||
current_date: Some(chrono::Local::now().format("%Y-%m-%d").to_string()),
|
||||
is_non_interactive: true,
|
||||
..Default::default()
|
||||
};
|
||||
context.render_with_renderer(renderer)
|
||||
}
|
||||
/// Render project instructions as the child's prepended user message.
|
||||
pub async fn render_subagent_initial_user_message(
|
||||
definition: &AgentDefinition,
|
||||
working_directory: &Path,
|
||||
compat: CompatConfig,
|
||||
) -> Option<String> {
|
||||
if !definition.agents_md {
|
||||
return None;
|
||||
}
|
||||
let agents_md_files = xai_grok_agent::prompt::agents_md::read_agents_config_with_paths(
|
||||
&working_directory.to_string_lossy(),
|
||||
compat,
|
||||
)
|
||||
.await;
|
||||
PromptContext {
|
||||
audience: PromptAudience::Subagent,
|
||||
system_prompt: definition.system_prompt.clone(),
|
||||
agents_md_files,
|
||||
..Default::default()
|
||||
}
|
||||
.agents_md_user_reminder()
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
fn context<'a>(
|
||||
cwd: &'a Path,
|
||||
toggles: &'a HashMap<String, bool>,
|
||||
) -> DefinitionResolutionContext<'a> {
|
||||
DefinitionResolutionContext {
|
||||
cwd,
|
||||
plugins: None,
|
||||
cli_agents: &[],
|
||||
toggles,
|
||||
allowed_types: None,
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn builtin_explore_uses_production_read_only_toolset() {
|
||||
let cwd = tempfile::tempdir().unwrap();
|
||||
let toggles = HashMap::new();
|
||||
let mut definition =
|
||||
resolve_agent_definition("explore", &context(cwd.path(), &toggles)).unwrap();
|
||||
apply_child_tool_policy(&mut definition, None, false);
|
||||
let kinds: Vec<Option<ToolKind>> = definition
|
||||
.tool_config
|
||||
.tools
|
||||
.iter()
|
||||
.map(|tool| tool.kind)
|
||||
.collect();
|
||||
assert!(kinds.contains(&Some(ToolKind::Read)));
|
||||
assert!(kinds.contains(&Some(ToolKind::Search)));
|
||||
assert!(!kinds.contains(&Some(ToolKind::Execute)));
|
||||
assert!(!kinds.contains(&Some(ToolKind::Task)));
|
||||
}
|
||||
#[test]
|
||||
fn gates_disabled_and_not_allowed_definitions() {
|
||||
let cwd = tempfile::tempdir().unwrap();
|
||||
let toggles = HashMap::from([("explore".to_string(), false)]);
|
||||
let disabled = context(cwd.path(), &toggles);
|
||||
assert!(matches!(
|
||||
resolve_agent_definition("explore", &disabled),
|
||||
Err(ResolutionError::Disabled { .. })
|
||||
));
|
||||
let allowed = ["plan".to_string()];
|
||||
let toggles = HashMap::new();
|
||||
let restricted = DefinitionResolutionContext {
|
||||
allowed_types: Some(&allowed),
|
||||
..context(cwd.path(), &toggles)
|
||||
};
|
||||
assert!(matches!(
|
||||
resolve_agent_definition("explore", &restricted),
|
||||
Err(ResolutionError::NotAllowed { .. })
|
||||
));
|
||||
}
|
||||
#[test]
|
||||
fn definition_defaults_fill_runtime_without_overwriting_explicit_values() {
|
||||
let cwd = tempfile::tempdir().unwrap();
|
||||
let toggles = HashMap::new();
|
||||
let mut definition =
|
||||
resolve_agent_definition("explore", &context(cwd.path(), &toggles)).unwrap();
|
||||
definition.isolation = Some(IsolationMode::Worktree);
|
||||
let mut runtime = EffectiveRuntimeConfig::default();
|
||||
apply_definition_runtime_defaults(&mut runtime, &definition);
|
||||
assert_eq!(runtime.isolation, SubagentIsolationMode::Worktree);
|
||||
}
|
||||
#[test]
|
||||
fn full_prompt_uses_production_subagent_template_and_body() {
|
||||
let cwd = tempfile::tempdir().unwrap();
|
||||
let toggles = HashMap::new();
|
||||
let definition =
|
||||
resolve_agent_definition("explore", &context(cwd.path(), &toggles)).unwrap();
|
||||
let renderer = TemplateRenderer::new(
|
||||
HashMap::from([
|
||||
(ToolKind::Read, "read_x".to_string()),
|
||||
(ToolKind::List, "list_x".to_string()),
|
||||
(ToolKind::Search, "search_x".to_string()),
|
||||
]),
|
||||
HashMap::new(),
|
||||
);
|
||||
let prompt = render_subagent_system_prompt(
|
||||
&definition,
|
||||
&EffectiveRuntimeConfig::default(),
|
||||
&renderer,
|
||||
cwd.path(),
|
||||
)
|
||||
.unwrap();
|
||||
assert!(prompt.contains("<project_instructions_spec>"));
|
||||
assert!(prompt.contains("read-only codebase exploration agent"));
|
||||
assert!(prompt.contains(&format!("Workspace Path: {}", cwd.path().display())));
|
||||
assert!(!prompt.contains("${{"));
|
||||
}
|
||||
#[tokio::test]
|
||||
async fn initial_user_message_contains_project_instructions() {
|
||||
let cwd = tempfile::tempdir().unwrap();
|
||||
std::fs::write(cwd.path().join("AGENTS.md"), "Use the project contract.").unwrap();
|
||||
let toggles = HashMap::new();
|
||||
let definition =
|
||||
resolve_agent_definition("explore", &context(cwd.path(), &toggles)).unwrap();
|
||||
let message =
|
||||
render_subagent_initial_user_message(&definition, cwd.path(), CompatConfig::default())
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(message.contains("Use the project contract."));
|
||||
}
|
||||
}
|
||||
|
|
@ -14,24 +14,27 @@
|
|||
//! Designed to be consumed by local hosts (e.g. `xai-grok-shell`) and any
|
||||
//! future remote spawn path that only needs pure resolution logic.
|
||||
//!
|
||||
//! ## Planned composition API
|
||||
//!
|
||||
//! Future work may add a higher-level composition helper once shell call sites
|
||||
//! are refactored onto this crate:
|
||||
//!
|
||||
//! - `resolve_subagent_spec()` composition function
|
||||
//! - `SubagentSpec`, `ResolveSubagentRequest`, `ResolutionContext` boundary types
|
||||
//! - Optional deps for `AgentDefinition` lookup and worktree creation
|
||||
//! - Model override resolution chain (global > per-type > role > parent)
|
||||
//! - Capability mode filtering (delegates to `SubagentCapabilityMode::filter_tool_config()`)
|
||||
//! Definition discovery, gating, prompt context, runtime defaults, and
|
||||
//! capability/depth tool policy are shared here. Model catalog selection and
|
||||
//! workspace materialization remain host adapters.
|
||||
|
||||
pub mod config;
|
||||
pub mod context;
|
||||
pub mod definition;
|
||||
pub mod overrides;
|
||||
pub mod resume;
|
||||
pub mod types;
|
||||
|
||||
pub use config::{PersonaIOField, SubagentPersona, SubagentRole};
|
||||
pub use definition::{
|
||||
DefinitionResolutionContext, DefinitionValidationContext, HarnessToolsetContext,
|
||||
apply_child_tool_policy, apply_definition_runtime_defaults, apply_harness_toolset,
|
||||
available_agent_names, discover_agent_definition, gate_agent_definition,
|
||||
render_subagent_initial_user_message, render_subagent_system_prompt, resolve_agent_definition,
|
||||
resolve_runtime_config, select_role, subagent_harness_flavor_is_representable,
|
||||
validate_agent_name,
|
||||
};
|
||||
pub use overrides::{intersect_capability_modes, resolve_effective_overrides};
|
||||
pub use resume::{ResumeValidationError, validate_resume_identity};
|
||||
pub use types::{ContextSource, EffectiveRuntimeConfig, ResolutionError, ResumeSourceData};
|
||||
pub use xai_grok_agent::config::AgentDefinition;
|
||||
|
|
|
|||
|
|
@ -82,6 +82,24 @@ pub struct ResumeSourceData {
|
|||
/// Errors that can occur during subagent resolution.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ResolutionError {
|
||||
/// No production or session CLI definition has this name.
|
||||
#[error("unknown subagent type \"{subagent_type}\"; available: {available:?}")]
|
||||
Unknown {
|
||||
subagent_type: String,
|
||||
available: Vec<String>,
|
||||
},
|
||||
|
||||
/// The definition exists but is disabled by the session toggle.
|
||||
#[error("subagent \"{subagent_type}\" is disabled")]
|
||||
Disabled { subagent_type: String },
|
||||
|
||||
/// The parent session restricts which child types may run.
|
||||
#[error("subagent \"{subagent_type}\" is not allowed; allowed: {allowed:?}")]
|
||||
NotAllowed {
|
||||
subagent_type: String,
|
||||
allowed: Vec<String>,
|
||||
},
|
||||
|
||||
/// Persona was explicitly requested but could not be resolved.
|
||||
#[error("persona resolution failed: {0}")]
|
||||
PersonaResolution(String),
|
||||
|
|
|
|||
Loading…
Reference in a new issue