Connectivity, Tools, Flows & Prompts

This commit is contained in:
Alex Emmet 2026-07-13 22:36:18 +02:00
commit ad42c0e51f
5 changed files with 57 additions and 37 deletions

View file

@ -1,7 +1,7 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
/* ExploreAgent is intentionally limited to read-only tools so it can be /* ExploreAgent is intentionally limited to read-only tools so it can be
dispatched without approval for safe, unrestricted codebase exploration. */ dispatched without approval for safe, unrestricted codebase exploration. */
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
pub enum AgentType { pub enum AgentType {
@ -101,17 +101,17 @@ impl AgentPolicy {
max_depth: 3, max_depth: 3,
require_preview: false, require_preview: false,
timeout_seconds: 300, timeout_seconds: 300,
max_tokens: 16384, max_tokens: 131072,
allowed_tools: vec![], allowed_tools: vec![],
forbidden_mutations: vec!["Cargo.toml".into(), "package.json".into()], forbidden_mutations: vec!["Cargo.toml".into(), "package.json".into()],
}, },
AgentType::Planner => Self { AgentType::Planner => Self {
agent_type: agent_type.clone(), agent_type: agent_type.clone(),
auto_approve: false, auto_approve: false,
max_depth: 3, max_depth: 5,
require_preview: true, require_preview: true,
timeout_seconds: 120, timeout_seconds: 120,
max_tokens: 16384, max_tokens: 131072,
allowed_tools: vec![], allowed_tools: vec![],
forbidden_mutations: vec![], forbidden_mutations: vec![],
}, },
@ -126,7 +126,7 @@ impl AgentPolicy {
forbidden_mutations: vec![], forbidden_mutations: vec![],
}, },
/* ExploreAgent: auto-approved, read-only tools only, short timeout. /* ExploreAgent: auto-approved, read-only tools only, short timeout.
Forbidden mutations set to wildcard to block all writes. */ Forbidden mutations set to wildcard to block all writes. */
AgentType::ExploreAgent => Self { AgentType::ExploreAgent => Self {
agent_type: agent_type.clone(), agent_type: agent_type.clone(),
auto_approve: true, auto_approve: true,
@ -149,8 +149,8 @@ impl AgentPolicy {
} }
/* Per-agent prompt configuration. Stored in the DB and resolved on task /* Per-agent prompt configuration. Stored in the DB and resolved on task
dispatch. Resolution order: task instructions > DB override > this config dispatch. Resolution order: task instructions > DB override > this config
> compiled-in default > tool protocol. */ > compiled-in default > tool protocol. */
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentPromptConfig { pub struct AgentPromptConfig {
pub agent_type: AgentType, pub agent_type: AgentType,
@ -208,7 +208,12 @@ mod tests {
for agent_type in &types { for agent_type in &types {
let s = agent_type.as_str(); let s = agent_type.as_str();
let parsed = AgentType::from_str(s); let parsed = AgentType::from_str(s);
assert_eq!(parsed.as_ref(), Some(agent_type), "roundtrip failed for {:?}", agent_type); assert_eq!(
parsed.as_ref(),
Some(agent_type),
"roundtrip failed for {:?}",
agent_type
);
} }
} }

View file

@ -18,7 +18,9 @@ pub mod task;
pub mod todo; pub mod todo;
pub mod tools; pub mod tools;
pub use agents::{AgentPolicy, AgentPromptConfig, AgentType, ApprovalMode, PlannedAction, SimulationResult}; pub use agents::{
AgentPolicy, AgentPromptConfig, AgentType, ApprovalMode, PlannedAction, SimulationResult,
};
pub use ai_response::{Conversation, Message}; pub use ai_response::{Conversation, Message};
pub use conclusion::{ pub use conclusion::{
Artifact, ArtifactType, ConclusionCard, ConclusionMetrics, ConclusionTrigger, Artifact, ArtifactType, ConclusionCard, ConclusionMetrics, ConclusionTrigger,
@ -32,11 +34,11 @@ pub use project::{Project, ProjectFile, ProjectId, ProjectSettings};
pub use task::{Task, TaskResult}; pub use task::{Task, TaskResult};
pub use todo::{Dependency, ToDo}; pub use todo::{Dependency, ToDo};
pub use tools::{ pub use tools::{
format_tool_error, format_tools_json, format_tool_result, parse_tool_call_blocks, format_tool_error, format_tool_result, format_tools_json, is_global_tool, is_reef_proxy_tool,
parse_tool_call_stream, tool_definitions, tool_ids_for_agent_type, ApprovalRequirement, parse_tool_call_blocks, parse_tool_call_stream, tool_definitions, tool_ids_for_agent_type,
CompositionStep, ContextVisibility, ExecutionContext, MarketplaceSearchResults, ParsedToolCall, ApprovalRequirement, CompositionStep, ContextVisibility, ExecutionContext,
REEF_PROXY_TOOLS, GLOBAL_TOOLS, is_reef_proxy_tool, is_global_tool, MarketplaceSearchResults, ParsedToolCall, TestAction, ToolComposition, ToolDefinition,
TestAction, ToolComposition, ToolDefinition, ToolDependency, ToolDocumentation, ToolExample, ToolDependency, ToolDocumentation, ToolExample, ToolExecutor, ToolListing, ToolParser,
ToolListing, ToolParser, ToolPlugin, ToolExecutor, ToolRegistry, ToolTest, ToolTestResult, ToolTestSuite, ToolPlugin, ToolRegistry, ToolTest, ToolTestResult, ToolTestSuite, ToolTestSuiteResult,
ToolTestSuiteResult, ToolVersion, ToolVersion, GLOBAL_TOOLS, REEF_PROXY_TOOLS,
}; };

View file

@ -1,4 +1,4 @@
use std::path::{Path, PathBuf, Component}; use std::path::{Component, Path, PathBuf};
/* Maximum allowed path depth as a safety measure */ /* Maximum allowed path depth as a safety measure */
const MAX_PATH_DEPTH: usize = 64; const MAX_PATH_DEPTH: usize = 64;
@ -54,15 +54,15 @@ pub fn resolve_sandboxed_path(validated: &Path, sandbox_root: &Path) -> Result<P
}; };
let canonical = if full_path.exists() { let canonical = if full_path.exists() {
full_path.canonicalize().map_err(|e| { full_path
format!("Failed to canonicalize path: {}", e) .canonicalize()
})? .map_err(|e| format!("Failed to canonicalize path: {}", e))?
} else { } else {
if let Some(parent) = full_path.parent() { if let Some(parent) = full_path.parent() {
if parent.exists() { if parent.exists() {
let canonical_parent = parent.canonicalize().map_err(|e| { let canonical_parent = parent
format!("Failed to canonicalize parent path: {}", e) .canonicalize()
})?; .map_err(|e| format!("Failed to canonicalize parent path: {}", e))?;
if !canonical_parent.starts_with(sandbox_root) { if !canonical_parent.starts_with(sandbox_root) {
return Err("Path escapes sandbox root boundaries".to_string()); return Err("Path escapes sandbox root boundaries".to_string());
} }
@ -81,7 +81,8 @@ pub fn resolve_sandboxed_path(validated: &Path, sandbox_root: &Path) -> Result<P
} }
fn has_directory_traversal(path: &Path) -> bool { fn has_directory_traversal(path: &Path) -> bool {
path.components().any(|comp| matches!(comp, Component::ParentDir)) path.components()
.any(|comp| matches!(comp, Component::ParentDir))
} }
fn contains_null_bytes(path: &Path) -> bool { fn contains_null_bytes(path: &Path) -> bool {
@ -90,10 +91,11 @@ fn contains_null_bytes(path: &Path) -> bool {
fn contains_shell_metacharacters(path: &Path) -> bool { fn contains_shell_metacharacters(path: &Path) -> bool {
const SHELL_METACHARACTERS: &[char] = &[ const SHELL_METACHARACTERS: &[char] = &[
'|', ';', '&', '$', '`', '>', '<', '(', ')', '{', '}', '|', ';', '&', '$', '`', '>', '<', '(', ')', '{', '}', '!', '#', '*', '?', '[', ']', '~',
'!', '#', '*', '?', '[', ']', '~', '\n', '\r', '\n', '\r',
]; ];
path.to_str().map_or(true, |s| s.contains(SHELL_METACHARACTERS)) path.to_str()
.map_or(true, |s| s.contains(SHELL_METACHARACTERS))
} }
#[cfg(test)] #[cfg(test)]
@ -152,7 +154,12 @@ mod tests {
#[test] #[test]
fn test_rejects_path_with_shell_metacharacters() { fn test_rejects_path_with_shell_metacharacters() {
let attacks = vec![ let attacks = vec![
"file|echo", "file;rm", "file$(id)", "file`id`", "file>out", "file<in", "file|echo",
"file;rm",
"file$(id)",
"file`id`",
"file>out",
"file<in",
]; ];
for path in attacks { for path in attacks {
let result = validate_path(path); let result = validate_path(path);

View file

@ -102,7 +102,10 @@ impl ToDo {
self self
} }
pub fn validate(&self, deps: &std::collections::HashMap<Uuid, Vec<Uuid>>) -> Result<(), ValidationError> { pub fn validate(
&self,
deps: &std::collections::HashMap<Uuid, Vec<Uuid>>,
) -> Result<(), ValidationError> {
if self.title.is_empty() { if self.title.is_empty() {
return Err(ValidationError::EmptyTitle); return Err(ValidationError::EmptyTitle);
} }
@ -127,8 +130,10 @@ impl ToDo {
} }
/* DFS cycle detection over a full dependency graph. Returns the first /* DFS cycle detection over a full dependency graph. Returns the first
todo_id found in a cycle, or None when the graph is acyclic. */ todo_id found in a cycle, or None when the graph is acyclic. */
pub fn detect_cycle_in_graph(deps: &std::collections::HashMap<Uuid, Vec<Uuid>>) -> Option<Uuid> { pub fn detect_cycle_in_graph(
deps: &std::collections::HashMap<Uuid, Vec<Uuid>>,
) -> Option<Uuid> {
let mut visited: HashSet<Uuid> = HashSet::new(); let mut visited: HashSet<Uuid> = HashSet::new();
let mut in_stack: HashSet<Uuid> = HashSet::new(); let mut in_stack: HashSet<Uuid> = HashSet::new();
@ -210,10 +215,7 @@ impl ToDo {
} }
/// Walk the parent_todo_id chain and return the depth (1 = root, 2 = child, etc.) /// Walk the parent_todo_id chain and return the depth (1 = root, 2 = child, etc.)
pub fn calculate_depth( pub fn calculate_depth(todo_id: Uuid, get_parent: impl Fn(Uuid) -> Option<Uuid>) -> usize {
todo_id: Uuid,
get_parent: impl Fn(Uuid) -> Option<Uuid>,
) -> usize {
let mut depth = 1usize; let mut depth = 1usize;
let mut current_id = get_parent(todo_id); let mut current_id = get_parent(todo_id);
let mut visited = HashSet::new(); let mut visited = HashSet::new();
@ -264,7 +266,6 @@ impl ToDo {
} }
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -287,7 +288,10 @@ mod tests {
fn test_todo_validate_empty_title() { fn test_todo_validate_empty_title() {
let todo = ToDo::default(); let todo = ToDo::default();
let deps = std::collections::HashMap::new(); let deps = std::collections::HashMap::new();
assert!(matches!(todo.validate(&deps), Err(ValidationError::EmptyTitle))); assert!(matches!(
todo.validate(&deps),
Err(ValidationError::EmptyTitle)
));
} }
#[test] #[test]

View file

@ -1867,6 +1867,8 @@ pub fn tool_ids_for_agent_type(agent_type: &crate::agents::AgentType) -> Option<
"get_documentation_context", "get_documentation_context",
"documentation_tree", "documentation_tree",
"store_file_doc", "store_file_doc",
"kanban_create_todo",
"kanban_update_todo",
]), ]),
crate::agents::AgentType::TestJudge => curated(&[ crate::agents::AgentType::TestJudge => curated(&[
"read_file", "read_file",