MTP-0.2.0 migration
This commit is contained in:
parent
ad42c0e51f
commit
f1c7317fab
14 changed files with 1309 additions and 281 deletions
|
|
@ -1,4 +1,5 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use crate::task::SessionBudget;
|
||||
|
||||
/* ExploreAgent is intentionally limited to read-only tools so it can be
|
||||
dispatched without approval for safe, unrestricted codebase exploration. */
|
||||
|
|
@ -37,6 +38,35 @@ impl AgentType {
|
|||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn default_session_budget(&self) -> SessionBudget {
|
||||
match self {
|
||||
AgentType::BuildAgent => SessionBudget {
|
||||
max_tool_calls: 50,
|
||||
max_tokens: 100_000,
|
||||
},
|
||||
AgentType::Planner => SessionBudget {
|
||||
max_tool_calls: 30,
|
||||
max_tokens: 50_000,
|
||||
},
|
||||
AgentType::ExploreAgent => SessionBudget {
|
||||
max_tool_calls: 10,
|
||||
max_tokens: 5_000,
|
||||
},
|
||||
AgentType::DocAgent => SessionBudget {
|
||||
max_tool_calls: 20,
|
||||
max_tokens: 20_000,
|
||||
},
|
||||
AgentType::TestJudge => SessionBudget {
|
||||
max_tool_calls: 10,
|
||||
max_tokens: 10_000,
|
||||
},
|
||||
AgentType::ChatAgent => SessionBudget {
|
||||
max_tool_calls: 5,
|
||||
max_tokens: 5_000,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
|
|
@ -46,6 +76,24 @@ pub enum ApprovalMode {
|
|||
PerTool,
|
||||
}
|
||||
|
||||
/// Controls what happens when a tool call requires approval and the user defers it.
|
||||
/// Orthogonal to `ApprovalMode` (which controls *how* approvals are collected).
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ApprovalTurnMode {
|
||||
/// Block the agent turn; resume by injecting the approval result into the same turn.
|
||||
Wait,
|
||||
/// Return a placeholder result to the agent immediately ("Approval pending");
|
||||
/// the actual approval result is injected in a subsequent turn.
|
||||
Defer,
|
||||
}
|
||||
|
||||
impl Default for ApprovalTurnMode {
|
||||
fn default() -> Self {
|
||||
ApprovalTurnMode::Wait
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PlannedAction {
|
||||
pub tool_name: String,
|
||||
|
|
@ -70,6 +118,10 @@ pub struct AgentPolicy {
|
|||
pub max_tokens: u32,
|
||||
pub allowed_tools: Vec<String>,
|
||||
pub forbidden_mutations: Vec<String>,
|
||||
/// Controls what happens when a tool call is deferred by the user.
|
||||
/// `Wait` pauses the turn; `Defer` returns a placeholder immediately.
|
||||
#[serde(default)]
|
||||
pub turn_mode: ApprovalTurnMode,
|
||||
}
|
||||
|
||||
impl AgentPolicy {
|
||||
|
|
@ -84,6 +136,7 @@ impl AgentPolicy {
|
|||
max_tokens: 4096,
|
||||
allowed_tools: vec!["read_file".into(), "write_file".into(), "edit_file".into()],
|
||||
forbidden_mutations: vec!["Cargo.toml".into(), "package.json".into()],
|
||||
turn_mode: ApprovalTurnMode::Defer,
|
||||
},
|
||||
AgentType::TestJudge => Self {
|
||||
agent_type: agent_type.clone(),
|
||||
|
|
@ -94,6 +147,7 @@ impl AgentPolicy {
|
|||
max_tokens: 4096,
|
||||
allowed_tools: vec![],
|
||||
forbidden_mutations: vec![],
|
||||
turn_mode: ApprovalTurnMode::Defer,
|
||||
},
|
||||
AgentType::BuildAgent => Self {
|
||||
agent_type: agent_type.clone(),
|
||||
|
|
@ -104,6 +158,7 @@ impl AgentPolicy {
|
|||
max_tokens: 131072,
|
||||
allowed_tools: vec![],
|
||||
forbidden_mutations: vec!["Cargo.toml".into(), "package.json".into()],
|
||||
turn_mode: ApprovalTurnMode::Wait,
|
||||
},
|
||||
AgentType::Planner => Self {
|
||||
agent_type: agent_type.clone(),
|
||||
|
|
@ -114,6 +169,7 @@ impl AgentPolicy {
|
|||
max_tokens: 131072,
|
||||
allowed_tools: vec![],
|
||||
forbidden_mutations: vec![],
|
||||
turn_mode: ApprovalTurnMode::Defer,
|
||||
},
|
||||
AgentType::ChatAgent => Self {
|
||||
agent_type: agent_type.clone(),
|
||||
|
|
@ -124,6 +180,7 @@ impl AgentPolicy {
|
|||
max_tokens: 4096,
|
||||
allowed_tools: vec![],
|
||||
forbidden_mutations: vec![],
|
||||
turn_mode: ApprovalTurnMode::Defer,
|
||||
},
|
||||
/* ExploreAgent: auto-approved, read-only tools only, short timeout.
|
||||
Forbidden mutations set to wildcard to block all writes. */
|
||||
|
|
@ -143,6 +200,7 @@ impl AgentPolicy {
|
|||
"web_fetch".into(),
|
||||
],
|
||||
forbidden_mutations: vec!["*".into()],
|
||||
turn_mode: ApprovalTurnMode::Defer,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,53 +21,144 @@ pub struct AiConversation {
|
|||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct StreamEvent {
|
||||
#[serde(rename = "type")]
|
||||
pub event_type: String,
|
||||
pub content: Option<String>,
|
||||
pub thinking: Option<String>,
|
||||
pub error: Option<String>,
|
||||
pub message_id: Option<String>,
|
||||
#[serde(tag = "type")]
|
||||
pub enum StreamEvent {
|
||||
#[serde(rename = "content")]
|
||||
Content { content: String },
|
||||
|
||||
#[serde(rename = "thinking")]
|
||||
Thinking { content: String },
|
||||
|
||||
#[serde(rename = "error")]
|
||||
Error { error: String },
|
||||
|
||||
#[serde(rename = "done")]
|
||||
Done { message_id: String },
|
||||
|
||||
#[serde(rename = "tool_call")]
|
||||
ToolCall {
|
||||
tool_name: String,
|
||||
tool_id: String,
|
||||
arguments: String,
|
||||
},
|
||||
|
||||
#[serde(rename = "tool_executing")]
|
||||
ToolExecuting { tool_name: String, tool_id: String },
|
||||
|
||||
#[serde(rename = "tool_result")]
|
||||
ToolResult {
|
||||
tool_name: String,
|
||||
tool_id: String,
|
||||
status: String,
|
||||
result: String,
|
||||
},
|
||||
|
||||
#[serde(rename = "checkpoint")]
|
||||
Checkpoint {
|
||||
checkpoint_id: String,
|
||||
data: serde_json::Value,
|
||||
},
|
||||
|
||||
#[serde(rename = "simulation_result")]
|
||||
SimulationResult { simulation: serde_json::Value },
|
||||
|
||||
#[serde(rename = "debug")]
|
||||
Debug { content: String },
|
||||
|
||||
#[serde(rename = "handshake")]
|
||||
Handshake {
|
||||
protocol_version: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
warmup: Option<bool>,
|
||||
},
|
||||
|
||||
#[serde(rename = "task_complete")]
|
||||
TaskComplete,
|
||||
}
|
||||
|
||||
impl StreamEvent {
|
||||
pub fn content(content: String) -> Self {
|
||||
Self {
|
||||
event_type: "content".to_string(),
|
||||
content: Some(content),
|
||||
thinking: None,
|
||||
error: None,
|
||||
message_id: None,
|
||||
}
|
||||
Self::Content { content }
|
||||
}
|
||||
|
||||
pub fn thinking(thinking: String) -> Self {
|
||||
Self {
|
||||
event_type: "thinking".to_string(),
|
||||
content: None,
|
||||
thinking: Some(thinking),
|
||||
error: None,
|
||||
message_id: None,
|
||||
}
|
||||
pub fn thinking(content: String) -> Self {
|
||||
Self::Thinking { content }
|
||||
}
|
||||
|
||||
pub fn error(error: String) -> Self {
|
||||
Self {
|
||||
event_type: "error".to_string(),
|
||||
content: None,
|
||||
thinking: None,
|
||||
error: Some(error),
|
||||
message_id: None,
|
||||
}
|
||||
Self::Error { error }
|
||||
}
|
||||
|
||||
pub fn done(message_id: String) -> Self {
|
||||
Self {
|
||||
event_type: "done".to_string(),
|
||||
content: None,
|
||||
thinking: None,
|
||||
error: None,
|
||||
message_id: Some(message_id),
|
||||
Self::Done { message_id }
|
||||
}
|
||||
|
||||
pub fn tool_call(tool_name: String, tool_id: String, arguments: String) -> Self {
|
||||
Self::ToolCall {
|
||||
tool_name,
|
||||
tool_id,
|
||||
arguments,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tool_executing(tool_name: String, tool_id: String) -> Self {
|
||||
Self::ToolExecuting { tool_name, tool_id }
|
||||
}
|
||||
|
||||
pub fn tool_result(
|
||||
tool_name: String,
|
||||
tool_id: String,
|
||||
status: String,
|
||||
result: String,
|
||||
) -> Self {
|
||||
Self::ToolResult {
|
||||
tool_name,
|
||||
tool_id,
|
||||
status,
|
||||
result,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn checkpoint(checkpoint_id: String, data: serde_json::Value) -> Self {
|
||||
Self::Checkpoint {
|
||||
checkpoint_id,
|
||||
data,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn simulation_result(simulation: serde_json::Value) -> Self {
|
||||
Self::SimulationResult { simulation }
|
||||
}
|
||||
|
||||
pub fn debug(content: String) -> Self {
|
||||
Self::Debug { content }
|
||||
}
|
||||
|
||||
pub fn handshake(protocol_version: String, warmup: Option<bool>) -> Self {
|
||||
Self::Handshake {
|
||||
protocol_version,
|
||||
warmup,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn task_complete() -> Self {
|
||||
Self::TaskComplete
|
||||
}
|
||||
|
||||
pub fn event_type(&self) -> &str {
|
||||
match self {
|
||||
Self::Content { .. } => "content",
|
||||
Self::Thinking { .. } => "thinking",
|
||||
Self::Error { .. } => "error",
|
||||
Self::Done { .. } => "done",
|
||||
Self::ToolCall { .. } => "tool_call",
|
||||
Self::ToolExecuting { .. } => "tool_executing",
|
||||
Self::ToolResult { .. } => "tool_result",
|
||||
Self::Checkpoint { .. } => "checkpoint",
|
||||
Self::SimulationResult { .. } => "simulation_result",
|
||||
Self::Debug { .. } => "debug",
|
||||
Self::Handshake { .. } => "handshake",
|
||||
Self::TaskComplete => "task_complete",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
32
src/enums.rs
32
src/enums.rs
|
|
@ -1,6 +1,38 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum ToDoMode {
|
||||
Discussion,
|
||||
Finalized,
|
||||
}
|
||||
|
||||
impl Default for ToDoMode {
|
||||
fn default() -> Self {
|
||||
ToDoMode::Discussion
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ToDoMode {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
ToDoMode::Discussion => write!(f, "discussion"),
|
||||
ToDoMode::Finalized => write!(f, "finalized"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::str::FromStr for ToDoMode {
|
||||
type Err = String;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"discussion" => Ok(ToDoMode::Discussion),
|
||||
"finalized" => Ok(ToDoMode::Finalized),
|
||||
_ => Err(format!("unknown mode: {}", s)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum ToDoStatus {
|
||||
Pending,
|
||||
|
|
|
|||
|
|
@ -20,6 +20,21 @@ pub enum ValidationError {
|
|||
InvalidField(String),
|
||||
}
|
||||
|
||||
impl ValidationError {
|
||||
pub fn http_status(&self) -> u16 {
|
||||
match self {
|
||||
ValidationError::EmptyTitle
|
||||
| ValidationError::TitleTooLong
|
||||
| ValidationError::DescriptionTooLong
|
||||
| ValidationError::InvalidPriority
|
||||
| ValidationError::InvalidStatusTransition { .. }
|
||||
| ValidationError::InvalidField(_) => 400,
|
||||
ValidationError::CircularDependency(_)
|
||||
| ValidationError::DependencyNotFound(_) => 409,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Error)]
|
||||
pub enum ShoalError {
|
||||
#[error("storage error: {0}")]
|
||||
|
|
@ -38,6 +53,20 @@ pub enum ShoalError {
|
|||
InternalError(String),
|
||||
}
|
||||
|
||||
impl ShoalError {
|
||||
pub fn http_status(&self) -> u16 {
|
||||
match self {
|
||||
ShoalError::ValidationError(_) => 400,
|
||||
ShoalError::NotFound(_) => 404,
|
||||
ShoalError::Unauthorized(_) => 401,
|
||||
ShoalError::StorageError(_)
|
||||
| ShoalError::NetworkError(_)
|
||||
| ShoalError::DatabaseError(_)
|
||||
| ShoalError::InternalError(_) => 500,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ValidationError> for ShoalError {
|
||||
fn from(e: ValidationError) -> Self {
|
||||
ShoalError::ValidationError(e.to_string())
|
||||
|
|
|
|||
11
src/krill.rs
11
src/krill.rs
|
|
@ -3,6 +3,15 @@ use uuid::Uuid;
|
|||
|
||||
use crate::enums::ModelType;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
pub enum PodProtocolVersion {
|
||||
V1,
|
||||
}
|
||||
|
||||
impl Default for PodProtocolVersion {
|
||||
fn default() -> Self { Self::V1 }
|
||||
}
|
||||
|
||||
pub type KrillId = Uuid;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
@ -76,12 +85,14 @@ impl KrillDescriptor {
|
|||
#[derive(Debug, Clone, Default)]
|
||||
pub struct KrillConfig {
|
||||
pub settings: serde_json::Value,
|
||||
pub pod_protocol_version: PodProtocolVersion,
|
||||
}
|
||||
|
||||
impl KrillConfig {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
settings: serde_json::json!({}),
|
||||
pod_protocol_version: PodProtocolVersion::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
35
src/lib.rs
35
src/lib.rs
|
|
@ -28,17 +28,44 @@ pub use conclusion::{
|
|||
pub use coral::{Coral, CoralId};
|
||||
pub use enums::*;
|
||||
pub use errors::{ShoalError, ValidationError};
|
||||
pub use krill::{KrillConfig, KrillDescriptor, KrillId};
|
||||
pub use krill::{KrillConfig, KrillDescriptor, KrillId, PodProtocolVersion};
|
||||
pub use mutations::FileMutation;
|
||||
pub use project::{Project, ProjectFile, ProjectId, ProjectSettings};
|
||||
pub use task::{Task, TaskResult};
|
||||
pub use todo::{Dependency, ToDo};
|
||||
pub use tools::{
|
||||
format_tool_error, format_tool_result, format_tools_json, is_global_tool, is_reef_proxy_tool,
|
||||
compute_tools_hash, format_tool_error, format_tool_result, format_tools_json,
|
||||
is_global_tool, is_reef_proxy_tool,
|
||||
parse_tool_call_blocks, parse_tool_call_stream, tool_definitions, tool_ids_for_agent_type,
|
||||
ApprovalRequirement, CompositionStep, ContextVisibility, ExecutionContext,
|
||||
MarketplaceSearchResults, ParsedToolCall, TestAction, ToolComposition, ToolDefinition,
|
||||
ToolDependency, ToolDocumentation, ToolExample, ToolExecutor, ToolListing, ToolParser,
|
||||
ToolPlugin, ToolRegistry, ToolTest, ToolTestResult, ToolTestSuite, ToolTestSuiteResult,
|
||||
ToolVersion, GLOBAL_TOOLS, REEF_PROXY_TOOLS,
|
||||
ToolPlugin, ToolRegistry, ToolState, ToolTest, ToolTestResult, ToolTestSuite,
|
||||
ToolTestSuiteResult, ToolVersion, GLOBAL_TOOLS, REEF_PROXY_TOOLS,
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn typemap_contains_expected_types() {
|
||||
let tm = mtp::type_map::TypeMap::latest();
|
||||
assert!(
|
||||
tm.data_id_enum(DataType::ProjectId).is_some(),
|
||||
"TypeMap missing ProjectId"
|
||||
);
|
||||
assert!(
|
||||
tm.comm_id_enum(CommunicationType::AuthRequest).is_some(),
|
||||
"TypeMap missing AuthRequest"
|
||||
);
|
||||
assert!(
|
||||
tm.comm_id_enum(CommunicationType::PasswordAuthRequest).is_some(),
|
||||
"TypeMap missing PasswordAuthRequest"
|
||||
);
|
||||
assert!(
|
||||
tm.data_id_enum(DataType::DockId).is_some(),
|
||||
"TypeMap missing DockId"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -110,6 +110,8 @@ pub struct ProjectSettings {
|
|||
pub language: Option<String>,
|
||||
pub build_command: Option<String>,
|
||||
pub run_command: Option<String>,
|
||||
/// Conventional subject/body template used for generated git commits.
|
||||
pub git_commit_template: Option<String>,
|
||||
#[serde(flatten)]
|
||||
pub extra: serde_json::Value,
|
||||
}
|
||||
|
|
@ -120,6 +122,7 @@ impl ProjectSettings {
|
|||
language: None,
|
||||
build_command: None,
|
||||
run_command: None,
|
||||
git_commit_template: None,
|
||||
extra: serde_json::json!({}),
|
||||
}
|
||||
}
|
||||
|
|
@ -139,6 +142,11 @@ impl ProjectSettings {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn with_git_commit_template(mut self, template: String) -> Self {
|
||||
self.git_commit_template = Some(template);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_extra(&mut self, key: &str, value: serde_json::Value) {
|
||||
if let Some(obj) = self.extra.as_object_mut() {
|
||||
obj.insert(key.to_string(), value);
|
||||
|
|
|
|||
71
src/task.rs
71
src/task.rs
|
|
@ -183,6 +183,61 @@ pub struct ConnectorConfig {
|
|||
pub temperature: Option<f32>,
|
||||
}
|
||||
|
||||
impl ConnectorConfig {
|
||||
pub const MIN_API_KEY_LEN: usize = 20;
|
||||
|
||||
/// Validate the connector configuration fields. Returns a list of human-readable
|
||||
/// error messages; an empty vec means the config is valid.
|
||||
pub fn validate(&self) -> Vec<String> {
|
||||
let mut errors = Vec::new();
|
||||
|
||||
if self.api_key.trim().len() < Self::MIN_API_KEY_LEN {
|
||||
errors.push(format!(
|
||||
"api_key must be non-empty and at least {} characters",
|
||||
Self::MIN_API_KEY_LEN
|
||||
));
|
||||
}
|
||||
|
||||
match url::Url::parse(&self.base_url) {
|
||||
Ok(parsed) => {
|
||||
if parsed.scheme() != "https" && parsed.scheme() != "http" {
|
||||
errors.push(format!(
|
||||
"base_url must use https or http scheme, got '{}'",
|
||||
parsed.scheme()
|
||||
));
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
errors.push(format!("base_url is not a valid URL: {}", e));
|
||||
}
|
||||
}
|
||||
|
||||
if self.model.trim().is_empty() {
|
||||
errors.push("model must be non-empty".to_string());
|
||||
}
|
||||
|
||||
if let Some(tokens) = self.max_tokens {
|
||||
if tokens <= 0 {
|
||||
errors.push(format!(
|
||||
"max_tokens must be > 0, got {}",
|
||||
tokens
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(temp) = self.temperature {
|
||||
if !(0.0..=2.0).contains(&temp) {
|
||||
errors.push(format!(
|
||||
"temperature must be in [0.0, 2.0], got {}",
|
||||
temp
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
errors
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct AgentTaskSpec {
|
||||
pub task_id: Uuid,
|
||||
|
|
@ -200,6 +255,10 @@ pub struct AgentTaskSpec {
|
|||
pub pre_approved_plan_id: Option<Uuid>,
|
||||
pub execution_token: Option<Uuid>,
|
||||
pub session_budget: Option<SessionBudget>,
|
||||
/// Maximum number of checklist tasks this agent run may add. `None` leaves
|
||||
/// task creation unconstrained for backwards-compatible callers.
|
||||
#[serde(default)]
|
||||
pub task_budget: Option<u32>,
|
||||
pub agent_type: Option<AgentType>,
|
||||
#[serde(default)]
|
||||
pub suggested_test_files: Vec<String>,
|
||||
|
|
@ -209,10 +268,19 @@ pub struct AgentTaskSpec {
|
|||
pub connector_config: Option<ConnectorConfig>,
|
||||
#[serde(default)]
|
||||
pub tools_json: Option<String>,
|
||||
/// blake3-style hash of `tools_json` for deduplication across dispatches.
|
||||
/// When present, the receiver may skip the full payload if the hash is cached.
|
||||
#[serde(default)]
|
||||
pub tools_hash: Option<String>,
|
||||
/* Overrides the agent-type default system prompt when set. Takes highest
|
||||
precedence in the resolution chain (above DB overrides and defaults). */
|
||||
#[serde(default)]
|
||||
pub system_prompt: Option<String>,
|
||||
/// When set, the Pod reads the latest checkpoint from SQLite and injects the
|
||||
/// completed tool calls into the system message so the LLM resumes instead
|
||||
/// of restarting from scratch on retry.
|
||||
#[serde(default)]
|
||||
pub resume_from_checkpoint: Option<Uuid>,
|
||||
}
|
||||
|
||||
impl AgentTaskSpec {
|
||||
|
|
@ -238,12 +306,15 @@ impl AgentTaskSpec {
|
|||
pre_approved_plan_id: None,
|
||||
execution_token: None,
|
||||
session_budget: None,
|
||||
task_budget: None,
|
||||
agent_type: None,
|
||||
suggested_test_files: Vec::new(),
|
||||
project_id: None,
|
||||
connector_config: None,
|
||||
tools_json: None,
|
||||
tools_hash: None,
|
||||
system_prompt: None,
|
||||
resume_from_checkpoint: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use chrono::{DateTime, Utc};
|
|||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::enums::{ToDoSource, ToDoStatus};
|
||||
use crate::enums::{ToDoMode, ToDoSource, ToDoStatus};
|
||||
use crate::errors::ValidationError;
|
||||
use std::collections::HashSet;
|
||||
|
||||
|
|
@ -36,6 +36,7 @@ pub struct ToDo {
|
|||
pub subtask_ids: Vec<Uuid>,
|
||||
pub affected_files: Vec<String>,
|
||||
pub source: ToDoSource,
|
||||
pub mode: ToDoMode,
|
||||
pub automation_chain_id: Option<Uuid>,
|
||||
pub tags: Vec<String>,
|
||||
pub estimated_tokens: u32,
|
||||
|
|
@ -62,6 +63,7 @@ impl Default for ToDo {
|
|||
subtask_ids: Vec::new(),
|
||||
affected_files: Vec::new(),
|
||||
source: ToDoSource::User,
|
||||
mode: ToDoMode::default(),
|
||||
automation_chain_id: None,
|
||||
tags: Vec::new(),
|
||||
estimated_tokens: 0,
|
||||
|
|
@ -90,6 +92,7 @@ impl ToDo {
|
|||
subtask_ids: Vec::new(),
|
||||
affected_files: Vec::new(),
|
||||
source: ToDoSource::User,
|
||||
mode: ToDoMode::default(),
|
||||
automation_chain_id: None,
|
||||
tags: Vec::new(),
|
||||
estimated_tokens: 0,
|
||||
|
|
@ -199,6 +202,8 @@ impl ToDo {
|
|||
(ToDoStatus::Failed, ToDoStatus::ReadyForAgent) => true,
|
||||
(ToDoStatus::PendingApproval, ToDoStatus::InProgress) => true,
|
||||
(ToDoStatus::PendingApproval, ToDoStatus::Blocked) => true,
|
||||
(ToDoStatus::Draft, ToDoStatus::Pending) => true,
|
||||
(ToDoStatus::Pending, ToDoStatus::Draft) => true,
|
||||
_ => self.status == status,
|
||||
};
|
||||
|
||||
|
|
|
|||
399
src/tools.rs
399
src/tools.rs
|
|
@ -516,6 +516,48 @@ impl Default for ToolVersion {
|
|||
}
|
||||
}
|
||||
|
||||
/* Dynamic runtime state of a tool, managed per-project by `ToolStateManager`.
|
||||
This replaces the static `deprecated` bool on `ToolDefinition` with a richer
|
||||
taxonomy that operators can toggle without redeploying. */
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ToolState {
|
||||
Enabled,
|
||||
Disabled,
|
||||
Deprecated,
|
||||
Experimental,
|
||||
}
|
||||
|
||||
impl Default for ToolState {
|
||||
fn default() -> Self {
|
||||
Self::Enabled
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolState {
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Enabled => "enabled",
|
||||
Self::Disabled => "disabled",
|
||||
Self::Deprecated => "deprecated",
|
||||
Self::Experimental => "experimental",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_str(s: &str) -> Self {
|
||||
match s {
|
||||
"disabled" => Self::Disabled,
|
||||
"deprecated" => Self::Deprecated,
|
||||
"experimental" => Self::Experimental,
|
||||
_ => Self::Enabled,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_available(&self) -> bool {
|
||||
matches!(self, Self::Enabled | Self::Experimental)
|
||||
}
|
||||
}
|
||||
|
||||
/* Tool category classification for UI grouping and filtering. */
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
|
|
@ -849,7 +891,13 @@ pub const PLANNER_ONLY_TOOLS: &[&str] = &[
|
|||
Mutating tools (write_file, edit_file, bash, delete_workspace) are intentionally
|
||||
NOT here: they stay Everywhere so direct chat can call them under approval routing
|
||||
(Phase 4). Only genuinely agent-internal collaboration tools are agent-only. */
|
||||
pub const AGENT_ONLY_TOOLS: &[&str] = &["submit_batch_plan", "report_completion"];
|
||||
pub const AGENT_ONLY_TOOLS: &[&str] = &[
|
||||
"submit_batch_plan",
|
||||
"report_completion",
|
||||
"kanban_add_task",
|
||||
"kanban_complete_task",
|
||||
"kanban_remove_task",
|
||||
];
|
||||
|
||||
/* Tools that proxy to the Reef API when a `REEF_URL` / `REEF_API_URL` is
|
||||
configured. These tools have both a local implementation and a Reef-backed
|
||||
|
|
@ -899,7 +947,9 @@ pub fn tool_definitions() -> Vec<ToolDefinition> {
|
|||
"read_file",
|
||||
"Read the content of a file with optional pagination",
|
||||
"filesystem",
|
||||
false, false, false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -914,7 +964,9 @@ pub fn tool_definitions() -> Vec<ToolDefinition> {
|
|||
"write_file",
|
||||
"Create or overwrite a file with content",
|
||||
"filesystem",
|
||||
true, true, false,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -929,7 +981,9 @@ pub fn tool_definitions() -> Vec<ToolDefinition> {
|
|||
"list_directory",
|
||||
"List directory contents with optional filtering",
|
||||
"filesystem",
|
||||
false, false, false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -944,7 +998,9 @@ pub fn tool_definitions() -> Vec<ToolDefinition> {
|
|||
"edit_file",
|
||||
"Edit file contents with line-based operations (replace, insert, delete)",
|
||||
"filesystem",
|
||||
true, true, false,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -971,7 +1027,9 @@ pub fn tool_definitions() -> Vec<ToolDefinition> {
|
|||
"search_files",
|
||||
"Search for text patterns in files (grep-like)",
|
||||
"filesystem",
|
||||
false, false, false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -982,7 +1040,6 @@ pub fn tool_definitions() -> Vec<ToolDefinition> {
|
|||
"required": ["path", "pattern"]
|
||||
})
|
||||
),
|
||||
|
||||
// ========================
|
||||
// Execution Tools
|
||||
// ========================
|
||||
|
|
@ -990,7 +1047,9 @@ pub fn tool_definitions() -> Vec<ToolDefinition> {
|
|||
"bash",
|
||||
"Execute a shell command and capture output",
|
||||
"execution",
|
||||
true, true, false,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -1001,7 +1060,6 @@ pub fn tool_definitions() -> Vec<ToolDefinition> {
|
|||
"required": ["command"]
|
||||
})
|
||||
),
|
||||
|
||||
// ========================
|
||||
// Git Tools
|
||||
// ========================
|
||||
|
|
@ -1009,7 +1067,9 @@ pub fn tool_definitions() -> Vec<ToolDefinition> {
|
|||
"git_status",
|
||||
"Show working tree status in a git repository",
|
||||
"git",
|
||||
false, false, false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -1022,7 +1082,9 @@ pub fn tool_definitions() -> Vec<ToolDefinition> {
|
|||
"git_diff",
|
||||
"Show changes between commits, commit and working tree, etc.",
|
||||
"git",
|
||||
false, false, false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -1036,7 +1098,9 @@ pub fn tool_definitions() -> Vec<ToolDefinition> {
|
|||
"git_log",
|
||||
"Show commit history in a git repository",
|
||||
"git",
|
||||
false, false, false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -1049,7 +1113,9 @@ pub fn tool_definitions() -> Vec<ToolDefinition> {
|
|||
"git_branch",
|
||||
"List or manage git branches",
|
||||
"git",
|
||||
false, false, false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -1058,7 +1124,6 @@ pub fn tool_definitions() -> Vec<ToolDefinition> {
|
|||
}
|
||||
})
|
||||
),
|
||||
|
||||
// ========================
|
||||
// Build & Test Tools
|
||||
// ========================
|
||||
|
|
@ -1066,7 +1131,9 @@ pub fn tool_definitions() -> Vec<ToolDefinition> {
|
|||
"cargo_check",
|
||||
"Run cargo check on a Rust project to verify compilation",
|
||||
"build",
|
||||
false, false, false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -1079,7 +1146,9 @@ pub fn tool_definitions() -> Vec<ToolDefinition> {
|
|||
"npm_build",
|
||||
"Run npm build script in a JavaScript/TypeScript project",
|
||||
"build",
|
||||
false, false, false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -1092,7 +1161,9 @@ pub fn tool_definitions() -> Vec<ToolDefinition> {
|
|||
"python_check",
|
||||
"Check Python syntax or run linting on a Python file",
|
||||
"build",
|
||||
false, false, false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -1105,7 +1176,9 @@ pub fn tool_definitions() -> Vec<ToolDefinition> {
|
|||
"test_runner",
|
||||
"Run project tests with a test runner like pytest",
|
||||
"build",
|
||||
false, false, false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -1115,7 +1188,6 @@ pub fn tool_definitions() -> Vec<ToolDefinition> {
|
|||
}
|
||||
})
|
||||
),
|
||||
|
||||
// ========================
|
||||
// Network Tools
|
||||
// ========================
|
||||
|
|
@ -1123,7 +1195,9 @@ pub fn tool_definitions() -> Vec<ToolDefinition> {
|
|||
"web_search",
|
||||
"Search the web for information using a search provider",
|
||||
"network",
|
||||
false, false, false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -1138,7 +1212,9 @@ pub fn tool_definitions() -> Vec<ToolDefinition> {
|
|||
"web_fetch",
|
||||
"Fetch and parse web pages from URLs",
|
||||
"network",
|
||||
false, false, false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -1152,7 +1228,9 @@ pub fn tool_definitions() -> Vec<ToolDefinition> {
|
|||
"web_api",
|
||||
"Make HTTP API requests to external services",
|
||||
"network",
|
||||
false, false, false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -1163,7 +1241,6 @@ pub fn tool_definitions() -> Vec<ToolDefinition> {
|
|||
"required": ["url", "method"]
|
||||
})
|
||||
),
|
||||
|
||||
// ========================
|
||||
// Workspace Tools
|
||||
// ========================
|
||||
|
|
@ -1171,7 +1248,9 @@ pub fn tool_definitions() -> Vec<ToolDefinition> {
|
|||
"create_workspace",
|
||||
"Create an ephemeral workspace directory for isolated operations",
|
||||
"workspace",
|
||||
false, false, false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -1184,7 +1263,9 @@ pub fn tool_definitions() -> Vec<ToolDefinition> {
|
|||
"delete_workspace",
|
||||
"Delete an ephemeral workspace and all its contents",
|
||||
"workspace",
|
||||
true, true, false,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -1198,7 +1279,9 @@ pub fn tool_definitions() -> Vec<ToolDefinition> {
|
|||
"create_venv",
|
||||
"Create a Python virtual environment",
|
||||
"workspace",
|
||||
false, false, false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -1212,7 +1295,9 @@ pub fn tool_definitions() -> Vec<ToolDefinition> {
|
|||
"install_dependencies",
|
||||
"Install Python dependencies from requirements.txt or specified packages",
|
||||
"workspace",
|
||||
false, false, false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -1226,7 +1311,9 @@ pub fn tool_definitions() -> Vec<ToolDefinition> {
|
|||
"workspace_info",
|
||||
"Get information and statistics about a workspace directory",
|
||||
"workspace",
|
||||
false, false, false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -1235,7 +1322,6 @@ pub fn tool_definitions() -> Vec<ToolDefinition> {
|
|||
"required": ["path"]
|
||||
})
|
||||
),
|
||||
|
||||
// ========================
|
||||
// Kanban Tools
|
||||
// ========================
|
||||
|
|
@ -1243,7 +1329,9 @@ pub fn tool_definitions() -> Vec<ToolDefinition> {
|
|||
"kanban_list_board",
|
||||
"List all columns and todos in the Kanban board",
|
||||
"kanban",
|
||||
false, false, true,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -1255,10 +1343,12 @@ pub fn tool_definitions() -> Vec<ToolDefinition> {
|
|||
),
|
||||
tool!(
|
||||
"kanban_create_todo",
|
||||
"Create a new todo item in a Kanban board",
|
||||
"kanban",
|
||||
false, false, true,
|
||||
serde_json::json!({
|
||||
"Create a new todo item in a Kanban board",
|
||||
"kanban",
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"project_id": { "type": "string", "description": "Project ID" },
|
||||
|
|
@ -1280,7 +1370,9 @@ pub fn tool_definitions() -> Vec<ToolDefinition> {
|
|||
"kanban_update_todo",
|
||||
"Update a todo item's title, description, status, or priority",
|
||||
"kanban",
|
||||
false, false, true,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -1298,7 +1390,9 @@ pub fn tool_definitions() -> Vec<ToolDefinition> {
|
|||
"kanban_delete_todo",
|
||||
"Delete a todo item from the Kanban board",
|
||||
"kanban",
|
||||
false, false, true,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -1310,25 +1404,29 @@ pub fn tool_definitions() -> Vec<ToolDefinition> {
|
|||
),
|
||||
tool!(
|
||||
"kanban_move_todo",
|
||||
"Move a todo item to a different status",
|
||||
"kanban",
|
||||
false, false, true,
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"project_id": { "type": "string", "description": "Project ID" },
|
||||
"todo_id": { "type": "string", "description": "ID of the todo to move" },
|
||||
"status": { "type": "string", "enum": ["pending","in_progress","completed","blocked","ready_for_agent","delegated","failed","pending_approval"], "description": "Target status" },
|
||||
"task_order": { "type": "number", "description": "New order position" }
|
||||
},
|
||||
"required": ["project_id", "todo_id"]
|
||||
})
|
||||
"Move a todo item to a different status",
|
||||
"kanban",
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"project_id": { "type": "string", "description": "Project ID" },
|
||||
"todo_id": { "type": "string", "description": "ID of the todo to move" },
|
||||
"status": { "type": "string", "enum": ["pending","in_progress","completed","blocked","ready_for_agent","delegated","failed","pending_approval"], "description": "Target status" },
|
||||
"task_order": { "type": "number", "description": "New order position" }
|
||||
},
|
||||
"required": ["project_id", "todo_id"]
|
||||
})
|
||||
),
|
||||
tool!(
|
||||
"kanban_create_task",
|
||||
"Create a sub-task within a todo",
|
||||
"kanban",
|
||||
false, false, true,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -1344,7 +1442,9 @@ pub fn tool_definitions() -> Vec<ToolDefinition> {
|
|||
"kanban_update_task",
|
||||
"Update a sub-task's title, description, or status",
|
||||
"kanban",
|
||||
false, false, true,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -1357,11 +1457,64 @@ pub fn tool_definitions() -> Vec<ToolDefinition> {
|
|||
"required": ["project_id", "task_id"]
|
||||
})
|
||||
),
|
||||
tool!(
|
||||
"kanban_add_task",
|
||||
"Add a checklist task to the todo owned by this agent run",
|
||||
"kanban",
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"project_id": { "type": "string", "description": "Project ID" },
|
||||
"todo_id": { "type": "string", "description": "ID of the parent todo" },
|
||||
"title": { "type": "string", "description": "Checklist task title" },
|
||||
"description": { "type": "string", "description": "Optional description" }
|
||||
},
|
||||
"required": ["project_id", "todo_id", "title"]
|
||||
})
|
||||
),
|
||||
tool!(
|
||||
"kanban_complete_task",
|
||||
"Mark a checklist task complete or incomplete on the todo owned by this agent run",
|
||||
"kanban",
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"project_id": { "type": "string", "description": "Project ID" },
|
||||
"task_id": { "type": "string", "description": "Checklist task ID" },
|
||||
"status": { "type": "string", "enum": ["completed", "pending"], "description": "Target status (defaults to completed)" }
|
||||
},
|
||||
"required": ["project_id", "task_id"]
|
||||
})
|
||||
),
|
||||
tool!(
|
||||
"kanban_remove_task",
|
||||
"Remove a checklist task from the todo owned by this agent run",
|
||||
"kanban",
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"project_id": { "type": "string", "description": "Project ID" },
|
||||
"task_id": { "type": "string", "description": "Checklist task ID" }
|
||||
},
|
||||
"required": ["project_id", "task_id"]
|
||||
})
|
||||
),
|
||||
tool!(
|
||||
"kanban_add_tag",
|
||||
"Add a tag to a todo item",
|
||||
"kanban",
|
||||
false, false, true,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -1380,7 +1533,9 @@ pub fn tool_definitions() -> Vec<ToolDefinition> {
|
|||
"document_file",
|
||||
"Analyze and generate documentation for a file",
|
||||
"documentation",
|
||||
false, false, true,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -1394,7 +1549,9 @@ pub fn tool_definitions() -> Vec<ToolDefinition> {
|
|||
"document_project",
|
||||
"Generate a comprehensive summary of all project files",
|
||||
"documentation",
|
||||
false, false, true,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -1407,7 +1564,9 @@ pub fn tool_definitions() -> Vec<ToolDefinition> {
|
|||
"find_references",
|
||||
"Find all references to a symbol across the project",
|
||||
"documentation",
|
||||
false, false, true,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -1423,7 +1582,9 @@ pub fn tool_definitions() -> Vec<ToolDefinition> {
|
|||
"file_dependencies",
|
||||
"Get incoming or outgoing dependencies for a file",
|
||||
"documentation",
|
||||
false, false, true,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -1438,7 +1599,9 @@ pub fn tool_definitions() -> Vec<ToolDefinition> {
|
|||
"documentation_tree",
|
||||
"Build a directory tree showing documentation coverage",
|
||||
"documentation",
|
||||
false, false, true,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -1452,7 +1615,9 @@ pub fn tool_definitions() -> Vec<ToolDefinition> {
|
|||
"verify_documentation",
|
||||
"Verify documentation freshness and compute coverage stats",
|
||||
"documentation",
|
||||
false, false, true,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -1465,7 +1630,9 @@ pub fn tool_definitions() -> Vec<ToolDefinition> {
|
|||
"document_folder",
|
||||
"Document all files within a folder",
|
||||
"documentation",
|
||||
false, false, true,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -1479,7 +1646,9 @@ pub fn tool_definitions() -> Vec<ToolDefinition> {
|
|||
"store_file_doc",
|
||||
"Store documentation for a file",
|
||||
"documentation",
|
||||
false, false, true,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -1502,7 +1671,9 @@ pub fn tool_definitions() -> Vec<ToolDefinition> {
|
|||
"get_documentation_context",
|
||||
"Get the documentation context for a file",
|
||||
"documentation",
|
||||
false, false, true,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -1512,7 +1683,6 @@ pub fn tool_definitions() -> Vec<ToolDefinition> {
|
|||
"required": ["project_id", "file_path"]
|
||||
})
|
||||
),
|
||||
|
||||
// ========================
|
||||
// File Tools
|
||||
// ========================
|
||||
|
|
@ -1520,7 +1690,9 @@ pub fn tool_definitions() -> Vec<ToolDefinition> {
|
|||
"list_files",
|
||||
"List all files in a project",
|
||||
"files",
|
||||
false, false, true,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -1530,7 +1702,6 @@ pub fn tool_definitions() -> Vec<ToolDefinition> {
|
|||
"required": ["project_id"]
|
||||
})
|
||||
),
|
||||
|
||||
// ========================
|
||||
// Strategic Tools
|
||||
// ========================
|
||||
|
|
@ -1538,7 +1709,9 @@ pub fn tool_definitions() -> Vec<ToolDefinition> {
|
|||
"propose_strategic_item",
|
||||
"Propose a new high-level strategic goal or todo",
|
||||
"strategic",
|
||||
false, true, true,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -1555,7 +1728,9 @@ pub fn tool_definitions() -> Vec<ToolDefinition> {
|
|||
"split_task",
|
||||
"Split a Pending todo into multiple subtasks with proper dependency tracking, depth validation, and cycle detection. Each subtask should be independently deployable to an agent.",
|
||||
"strategic",
|
||||
false, false, true,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -1583,7 +1758,9 @@ pub fn tool_definitions() -> Vec<ToolDefinition> {
|
|||
"change_planner_mode",
|
||||
"Switch the Planner's working mode. Modes: cooperative (fast shipping), critical (audit), red_team (pre-mortem), socratic (discovery), execution (task decomposition).",
|
||||
"strategic",
|
||||
false, true, true,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -1598,7 +1775,9 @@ pub fn tool_definitions() -> Vec<ToolDefinition> {
|
|||
"audit_assumptions",
|
||||
"List the user's current assumptions about the project and identify the weakest ones",
|
||||
"strategic",
|
||||
false, false, true,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -1611,7 +1790,9 @@ pub fn tool_definitions() -> Vec<ToolDefinition> {
|
|||
"identify_blind_spots",
|
||||
"Query project data for missing dependency chains, undocumented risks, and recurring blockers",
|
||||
"strategic",
|
||||
false, false, true,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -1624,7 +1805,9 @@ pub fn tool_definitions() -> Vec<ToolDefinition> {
|
|||
"check_dependencies",
|
||||
"Analyze todo dependency chains for circular or missing dependencies",
|
||||
"strategic",
|
||||
false, false, true,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -1637,7 +1820,9 @@ pub fn tool_definitions() -> Vec<ToolDefinition> {
|
|||
"evaluate_plan_risk",
|
||||
"Score the current plan for feasibility, edge-case coverage, and alignment",
|
||||
"strategic",
|
||||
false, false, true,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -1650,7 +1835,9 @@ pub fn tool_definitions() -> Vec<ToolDefinition> {
|
|||
"compare_project_patterns",
|
||||
"Compare kanban structures across projects to detect inconsistencies",
|
||||
"strategic",
|
||||
false, false, false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -1662,7 +1849,9 @@ pub fn tool_definitions() -> Vec<ToolDefinition> {
|
|||
"find_similar_risks",
|
||||
"Search across projects for similar risk patterns",
|
||||
"strategic",
|
||||
false, false, false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -1671,7 +1860,6 @@ pub fn tool_definitions() -> Vec<ToolDefinition> {
|
|||
}
|
||||
})
|
||||
),
|
||||
|
||||
// ========================
|
||||
// Agent Tools
|
||||
// ========================
|
||||
|
|
@ -1679,7 +1867,9 @@ pub fn tool_definitions() -> Vec<ToolDefinition> {
|
|||
"report_completion",
|
||||
"Report completion status of a todo back to the Bridge",
|
||||
"agent",
|
||||
false, false, false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -1697,7 +1887,9 @@ pub fn tool_definitions() -> Vec<ToolDefinition> {
|
|||
"submit_batch_plan",
|
||||
"Submit a batch plan of mutations for pre-approval before execution",
|
||||
"agent",
|
||||
false, false, false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -1798,6 +1990,9 @@ pub fn tool_definitions() -> Vec<ToolDefinition> {
|
|||
("kanban_move_todo", 2),
|
||||
("kanban_create_task", 2),
|
||||
("kanban_update_task", 2),
|
||||
("kanban_add_task", 1),
|
||||
("kanban_complete_task", 1),
|
||||
("kanban_remove_task", 1),
|
||||
("create_workspace", 2),
|
||||
("split_task", 2),
|
||||
// Depth 3 — needs deep reasoning
|
||||
|
|
@ -1933,11 +2128,37 @@ pub fn format_tools_json(tools: &[ToolDefinition]) -> Option<String> {
|
|||
serde_json::to_string(&formatted).ok()
|
||||
}
|
||||
|
||||
/// Deterministic hash of a tools JSON string using std DefaultHasher.
|
||||
/// The hex-encoded result is used as a deduplication key across dispatch/pod.
|
||||
pub fn compute_tools_hash(tools_json: &str) -> String {
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::hash::{Hash, Hasher};
|
||||
let mut hasher = DefaultHasher::new();
|
||||
tools_json.hash(&mut hasher);
|
||||
format!("{:016x}", hasher.finish())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use serde_json::json;
|
||||
|
||||
#[test]
|
||||
fn test_compute_tools_hash_deterministic() {
|
||||
let input = r#"[{"type":"function","function":{"name":"foo","description":"bar","parameters":{}}}]"#;
|
||||
let h1 = compute_tools_hash(input);
|
||||
let h2 = compute_tools_hash(input);
|
||||
assert_eq!(h1, h2);
|
||||
assert_eq!(h1.len(), 16);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_compute_tools_hash_different_inputs() {
|
||||
let a = compute_tools_hash(r#"aaa"#);
|
||||
let b = compute_tools_hash(r#"bbb"#);
|
||||
assert_ne!(a, b);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_single_block() {
|
||||
let text = r#"
|
||||
|
|
@ -2213,10 +2434,12 @@ Done with tools.
|
|||
let results = parse_tool_call_blocks(text);
|
||||
assert_eq!(results.len(), 1);
|
||||
assert_eq!(results[0].call_id, "call_01");
|
||||
assert!(results[0].payload["content"]
|
||||
.as_str()
|
||||
.unwrap_or("")
|
||||
.contains("🎉"));
|
||||
assert!(
|
||||
results[0].payload["content"]
|
||||
.as_str()
|
||||
.unwrap_or("")
|
||||
.contains("🎉")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -2465,6 +2688,22 @@ Done with tools.
|
|||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn checklist_task_tools_are_agent_only_at_depth_one() {
|
||||
for id in [
|
||||
"kanban_add_task",
|
||||
"kanban_complete_task",
|
||||
"kanban_remove_task",
|
||||
] {
|
||||
let tool = tool_definitions()
|
||||
.into_iter()
|
||||
.find(|tool| tool.id == id)
|
||||
.expect("checklist task tool must be registered");
|
||||
assert_eq!(tool.context_visibility, ContextVisibility::AgentOnly);
|
||||
assert_eq!(tool.required_depth, 1);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_category_backstop_keeps_sensitive_tools_fail_closed() {
|
||||
/* Invariant enforced by the category backstop: no strategic tool is ever
|
||||
|
|
|
|||
Loading…
Reference in a new issue