Connectivity, Tools, Flows & Prompts
This commit is contained in:
parent
65d92ed22e
commit
ad42c0e51f
5 changed files with 57 additions and 37 deletions
|
|
@ -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
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
18
src/lib.rs
18
src/lib.rs
|
|
@ -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,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
22
src/todo.rs
22
src/todo.rs
|
|
@ -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]
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue