fix(#108): expose les tools MCP autorisées dès le démarrage de session Codex
This commit is contained in:
@ -31,82 +31,11 @@
|
|||||||
"idea_template_create",
|
"idea_template_create",
|
||||||
"idea_template_update",
|
"idea_template_update",
|
||||||
"idea_template_delete",
|
"idea_template_delete",
|
||||||
"idea_create_skill"
|
"idea_create_skill",
|
||||||
|
"idea_ask_agents"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"agents": [
|
"agents": [
|
||||||
{
|
|
||||||
"agentId": "a6ced819-b893-4213-b003-9e9dc79b9641",
|
|
||||||
"policy": {
|
|
||||||
"allowedTools": [
|
|
||||||
"idea_list_agents",
|
|
||||||
"idea_context_read",
|
|
||||||
"idea_memory_read",
|
|
||||||
"idea_skill_read",
|
|
||||||
"idea_workstate_read",
|
|
||||||
"idea_ticket_read",
|
|
||||||
"idea_ticket_list",
|
|
||||||
"idea_ticket_read_carnet",
|
|
||||||
"idea_sprint_list",
|
|
||||||
"idea_template_list",
|
|
||||||
"idea_template_read",
|
|
||||||
"idea_ask_agent",
|
|
||||||
"idea_launch_agent",
|
|
||||||
"idea_stop_agent",
|
|
||||||
"idea_update_context",
|
|
||||||
"idea_context_propose",
|
|
||||||
"idea_memory_write",
|
|
||||||
"idea_workstate_set",
|
|
||||||
"idea_create_skill",
|
|
||||||
"idea_ticket_create",
|
|
||||||
"idea_ticket_update",
|
|
||||||
"idea_ticket_update_status",
|
|
||||||
"idea_ticket_update_priority",
|
|
||||||
"idea_ticket_update_carnet",
|
|
||||||
"idea_ticket_link",
|
|
||||||
"idea_ticket_unlink",
|
|
||||||
"idea_template_create",
|
|
||||||
"idea_template_update",
|
|
||||||
"idea_template_delete"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"agentId": "dce19c75-9669-4e45-b8de-9950025157da",
|
|
||||||
"policy": {
|
|
||||||
"allowedTools": [
|
|
||||||
"idea_list_agents",
|
|
||||||
"idea_context_read",
|
|
||||||
"idea_memory_read",
|
|
||||||
"idea_skill_read",
|
|
||||||
"idea_workstate_read",
|
|
||||||
"idea_ticket_read",
|
|
||||||
"idea_ticket_list",
|
|
||||||
"idea_ticket_read_carnet",
|
|
||||||
"idea_sprint_list",
|
|
||||||
"idea_template_list",
|
|
||||||
"idea_template_read",
|
|
||||||
"idea_ask_agent",
|
|
||||||
"idea_launch_agent",
|
|
||||||
"idea_stop_agent",
|
|
||||||
"idea_update_context",
|
|
||||||
"idea_context_propose",
|
|
||||||
"idea_memory_write",
|
|
||||||
"idea_workstate_set",
|
|
||||||
"idea_create_skill",
|
|
||||||
"idea_ticket_create",
|
|
||||||
"idea_ticket_update",
|
|
||||||
"idea_ticket_update_status",
|
|
||||||
"idea_ticket_update_priority",
|
|
||||||
"idea_ticket_update_carnet",
|
|
||||||
"idea_ticket_link",
|
|
||||||
"idea_ticket_unlink",
|
|
||||||
"idea_template_create",
|
|
||||||
"idea_template_update",
|
|
||||||
"idea_template_delete"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"agentId": "73c853d1-c0fd-463b-ad17-1d24fefa371f",
|
"agentId": "73c853d1-c0fd-463b-ad17-1d24fefa371f",
|
||||||
"policy": {
|
"policy": {
|
||||||
@ -358,6 +287,80 @@
|
|||||||
"idea_template_delete"
|
"idea_template_delete"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"agentId": "a6ced819-b893-4213-b003-9e9dc79b9641",
|
||||||
|
"policy": {
|
||||||
|
"allowedTools": [
|
||||||
|
"idea_list_agents",
|
||||||
|
"idea_context_read",
|
||||||
|
"idea_memory_read",
|
||||||
|
"idea_skill_read",
|
||||||
|
"idea_workstate_read",
|
||||||
|
"idea_ticket_read",
|
||||||
|
"idea_ticket_list",
|
||||||
|
"idea_ticket_read_carnet",
|
||||||
|
"idea_sprint_list",
|
||||||
|
"idea_template_list",
|
||||||
|
"idea_template_read",
|
||||||
|
"idea_ask_agent",
|
||||||
|
"idea_launch_agent",
|
||||||
|
"idea_stop_agent",
|
||||||
|
"idea_update_context",
|
||||||
|
"idea_context_propose",
|
||||||
|
"idea_memory_write",
|
||||||
|
"idea_workstate_set",
|
||||||
|
"idea_create_skill",
|
||||||
|
"idea_ticket_create",
|
||||||
|
"idea_ticket_update",
|
||||||
|
"idea_ticket_update_status",
|
||||||
|
"idea_ticket_update_priority",
|
||||||
|
"idea_ticket_update_carnet",
|
||||||
|
"idea_ticket_link",
|
||||||
|
"idea_ticket_unlink",
|
||||||
|
"idea_template_create",
|
||||||
|
"idea_template_update",
|
||||||
|
"idea_template_delete",
|
||||||
|
"idea_run_in_background"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"agentId": "dce19c75-9669-4e45-b8de-9950025157da",
|
||||||
|
"policy": {
|
||||||
|
"allowedTools": [
|
||||||
|
"idea_list_agents",
|
||||||
|
"idea_context_read",
|
||||||
|
"idea_memory_read",
|
||||||
|
"idea_skill_read",
|
||||||
|
"idea_workstate_read",
|
||||||
|
"idea_ticket_read",
|
||||||
|
"idea_ticket_list",
|
||||||
|
"idea_ticket_read_carnet",
|
||||||
|
"idea_sprint_list",
|
||||||
|
"idea_template_list",
|
||||||
|
"idea_template_read",
|
||||||
|
"idea_ask_agent",
|
||||||
|
"idea_launch_agent",
|
||||||
|
"idea_stop_agent",
|
||||||
|
"idea_update_context",
|
||||||
|
"idea_context_propose",
|
||||||
|
"idea_memory_write",
|
||||||
|
"idea_workstate_set",
|
||||||
|
"idea_create_skill",
|
||||||
|
"idea_ticket_create",
|
||||||
|
"idea_ticket_update",
|
||||||
|
"idea_ticket_update_status",
|
||||||
|
"idea_ticket_update_priority",
|
||||||
|
"idea_ticket_update_carnet",
|
||||||
|
"idea_ticket_link",
|
||||||
|
"idea_ticket_unlink",
|
||||||
|
"idea_template_create",
|
||||||
|
"idea_template_update",
|
||||||
|
"idea_template_delete",
|
||||||
|
"idea_run_in_background"
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
issueRef: "#112"
|
issueRef: "#112"
|
||||||
version: 3
|
version: 4
|
||||||
updatedBy: {"kind":"user"}
|
updatedBy: {"kind":"user"}
|
||||||
updatedAt: 1785332227989
|
updatedAt: 1785341341049
|
||||||
---
|
---
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
id: "ff8e11d1-98f8-4c6c-b5d0-a8a087c1dbbc"
|
id: "ff8e11d1-98f8-4c6c-b5d0-a8a087c1dbbc"
|
||||||
number: 112
|
number: 112
|
||||||
title: "Pouvoir ajouter plusieurs tickets a la fois a un sprint"
|
title: "Pouvoir ajouter plusieurs tickets a la fois a un sprint"
|
||||||
status: "open"
|
status: "closed"
|
||||||
priority: "medium"
|
priority: "medium"
|
||||||
sprint: null
|
sprint: null
|
||||||
links: []
|
links: []
|
||||||
@ -11,7 +11,7 @@ attachments: []
|
|||||||
createdBy: {"kind":"user"}
|
createdBy: {"kind":"user"}
|
||||||
updatedBy: {"kind":"user"}
|
updatedBy: {"kind":"user"}
|
||||||
createdAt: 1785332192429
|
createdAt: 1785332192429
|
||||||
updatedAt: 1785332227989
|
updatedAt: 1785341341049
|
||||||
version: 3
|
version: 4
|
||||||
---
|
---
|
||||||
Je veux qu'on ajoute la possibilité de set le sprint des tickets selectionnés grace a la selection multiple de ticket dans la liste des tickets.
|
Je veux qu'on ajoute la possibilité de set le sprint des tickets selectionnés grace a la selection multiple de ticket dans la liste des tickets.
|
||||||
@ -1467,7 +1467,7 @@
|
|||||||
"issueRef": "#112",
|
"issueRef": "#112",
|
||||||
"path": "112",
|
"path": "112",
|
||||||
"title": "Pouvoir ajouter plusieurs tickets a la fois a un sprint",
|
"title": "Pouvoir ajouter plusieurs tickets a la fois a un sprint",
|
||||||
"status": "open",
|
"status": "closed",
|
||||||
"priority": "medium",
|
"priority": "medium",
|
||||||
"sprint": null,
|
"sprint": null,
|
||||||
"assignedAgentIds": [
|
"assignedAgentIds": [
|
||||||
@ -1476,7 +1476,7 @@
|
|||||||
"createdBy": {
|
"createdBy": {
|
||||||
"kind": "user"
|
"kind": "user"
|
||||||
},
|
},
|
||||||
"updatedAt": 1785332227989
|
"updatedAt": 1785341341049
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -597,6 +597,39 @@ mod tests {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn default_and_override_doc(
|
||||||
|
agent: AgentId,
|
||||||
|
project_default_tools: &[&str],
|
||||||
|
override_tools: &[&str],
|
||||||
|
) -> ProjectMcpToolPermissions {
|
||||||
|
let known_tools = infrastructure::orchestrator::mcp::tools::classified_tool_names();
|
||||||
|
ProjectMcpToolPermissions::new(
|
||||||
|
Some(
|
||||||
|
McpToolPolicy::new(
|
||||||
|
project_default_tools
|
||||||
|
.iter()
|
||||||
|
.map(|tool| (*tool).to_owned())
|
||||||
|
.collect(),
|
||||||
|
&known_tools,
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
vec![AgentMcpToolPolicyOverride::new(
|
||||||
|
agent,
|
||||||
|
McpToolPolicy::new(
|
||||||
|
override_tools
|
||||||
|
.iter()
|
||||||
|
.map(|tool| (*tool).to_owned())
|
||||||
|
.collect(),
|
||||||
|
&known_tools,
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
)],
|
||||||
|
&known_tools,
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn openai_tool_policy_rejects_tool_outside_allowlist() {
|
fn openai_tool_policy_rejects_tool_outside_allowlist() {
|
||||||
let policies = FakePolicies::default();
|
let policies = FakePolicies::default();
|
||||||
@ -864,6 +897,41 @@ mod tests {
|
|||||||
let _ = std::fs::remove_dir_all(temp);
|
let _ = std::fs::remove_dir_all(temp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn openai_tools_list_keeps_project_default_tools_with_agent_override() {
|
||||||
|
let temp = std::env::temp_dir().join(format!(
|
||||||
|
"idea-openai-mcp-tools-list-override-{}",
|
||||||
|
Uuid::new_v4()
|
||||||
|
));
|
||||||
|
let core = crate::BackendCore::build(temp.clone());
|
||||||
|
let agent = AgentId::from_uuid(Uuid::from_u128(183));
|
||||||
|
let invoker = AppOpenAiToolInvoker::new(
|
||||||
|
Arc::clone(&core.orchestrator_service),
|
||||||
|
Arc::new(FakeProjects::with(project())) as Arc<dyn ProjectStore>,
|
||||||
|
Arc::new(FakePolicies::default()),
|
||||||
|
mcp_permissions(default_and_override_doc(
|
||||||
|
agent,
|
||||||
|
&["idea_memory_read", "idea_run_in_background"],
|
||||||
|
&["idea_memory_read"],
|
||||||
|
)),
|
||||||
|
Arc::new(FakeTicketTools::default()),
|
||||||
|
Arc::new(FakeTemplateTools::default()),
|
||||||
|
);
|
||||||
|
|
||||||
|
let tools = invoker
|
||||||
|
.tools_for_context("/tmp/project", &agent.to_string())
|
||||||
|
.await
|
||||||
|
.expect("tools list should resolve durable policy");
|
||||||
|
let names = tools
|
||||||
|
.iter()
|
||||||
|
.map(|tool| tool.name.as_str())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
assert_eq!(names, vec!["idea_run_in_background", "idea_memory_read"]);
|
||||||
|
|
||||||
|
let _ = std::fs::remove_dir_all(temp);
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn openai_durable_agent_override_allows_explicit_write_tool() {
|
async fn openai_durable_agent_override_allows_explicit_write_tool() {
|
||||||
let temp =
|
let temp =
|
||||||
|
|||||||
@ -185,8 +185,8 @@ impl ProjectMcpToolPermissions {
|
|||||||
|
|
||||||
/// Resolves the effective policy for `agent_id`.
|
/// Resolves the effective policy for `agent_id`.
|
||||||
///
|
///
|
||||||
/// Resolution order: agent override > project default > canonical read-only
|
/// Resolution order: project default enriched by agent override > agent
|
||||||
/// fallback.
|
/// override > project default > canonical read-only fallback.
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
/// [`McpToolPermissionError`] if the document or fallback set is invalid.
|
/// [`McpToolPermissionError`] if the document or fallback set is invalid.
|
||||||
@ -197,12 +197,24 @@ impl ProjectMcpToolPermissions {
|
|||||||
known_tools: &[&str],
|
known_tools: &[&str],
|
||||||
) -> Result<McpToolPolicy, McpToolPermissionError> {
|
) -> Result<McpToolPolicy, McpToolPermissionError> {
|
||||||
self.validate(known_tools)?;
|
self.validate(known_tools)?;
|
||||||
if let Some(override_) = self
|
let override_policy = self
|
||||||
.agents
|
.agents
|
||||||
.iter()
|
.iter()
|
||||||
.find(|override_| override_.agent_id == agent_id)
|
.find(|override_| override_.agent_id == agent_id)
|
||||||
|
.map(|override_| &override_.policy);
|
||||||
|
if let (Some(project_default), Some(override_policy)) =
|
||||||
|
(&self.project_default, override_policy)
|
||||||
{
|
{
|
||||||
return Ok(override_.policy.clone());
|
let mut allowed_tools = project_default.allowed_tools.clone();
|
||||||
|
for tool in &override_policy.allowed_tools {
|
||||||
|
if !allowed_tools.iter().any(|allowed| allowed == tool) {
|
||||||
|
allowed_tools.push(tool.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return McpToolPolicy::new(allowed_tools, known_tools);
|
||||||
|
}
|
||||||
|
if let Some(override_policy) = override_policy {
|
||||||
|
return Ok(override_policy.clone());
|
||||||
}
|
}
|
||||||
if let Some(policy) = &self.project_default {
|
if let Some(policy) = &self.project_default {
|
||||||
return Ok(policy.clone());
|
return Ok(policy.clone());
|
||||||
@ -236,7 +248,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn agent_override_replaces_project_default() {
|
fn agent_override_extends_project_default() {
|
||||||
let project_default =
|
let project_default =
|
||||||
McpToolPolicy::new(vec!["idea_memory_read".to_owned()], KNOWN).unwrap();
|
McpToolPolicy::new(vec!["idea_memory_read".to_owned()], KNOWN).unwrap();
|
||||||
let override_policy =
|
let override_policy =
|
||||||
@ -250,8 +262,8 @@ mod tests {
|
|||||||
|
|
||||||
let effective = doc.effective_policy(agent(7), READ_ONLY, KNOWN).unwrap();
|
let effective = doc.effective_policy(agent(7), READ_ONLY, KNOWN).unwrap();
|
||||||
|
|
||||||
|
assert!(effective.permits("idea_memory_read"));
|
||||||
assert!(effective.permits("idea_memory_write"));
|
assert!(effective.permits("idea_memory_write"));
|
||||||
assert!(!effective.permits("idea_memory_read"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@ -700,11 +700,27 @@ mod tests {
|
|||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct FakeInvoker {
|
struct FakeInvoker {
|
||||||
calls: AtomicUsize,
|
calls: AtomicUsize,
|
||||||
|
tools: Mutex<Vec<ToolSpec>>,
|
||||||
|
bound_tools: Mutex<Option<Vec<ToolSpec>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FakeInvoker {
|
||||||
|
fn with_bound_tools(tools: Vec<ToolSpec>) -> Self {
|
||||||
|
Self {
|
||||||
|
calls: AtomicUsize::new(0),
|
||||||
|
tools: Mutex::new(Vec::new()),
|
||||||
|
bound_tools: Mutex::new(Some(tools)),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl ToolInvoker for FakeInvoker {
|
impl ToolInvoker for FakeInvoker {
|
||||||
fn tools(&self) -> Vec<ToolSpec> {
|
fn tools(&self) -> Vec<ToolSpec> {
|
||||||
|
let tools = self.tools.lock().expect("mutex sain");
|
||||||
|
if !tools.is_empty() {
|
||||||
|
return tools.clone();
|
||||||
|
}
|
||||||
vec![ToolSpec {
|
vec![ToolSpec {
|
||||||
name: "idea_echo".to_owned(),
|
name: "idea_echo".to_owned(),
|
||||||
description: "Echo".to_owned(),
|
description: "Echo".to_owned(),
|
||||||
@ -712,6 +728,15 @@ mod tests {
|
|||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn tools_for_bound_context(&self) -> Result<Vec<ToolSpec>, ToolInvocationError> {
|
||||||
|
Ok(self
|
||||||
|
.bound_tools
|
||||||
|
.lock()
|
||||||
|
.expect("mutex sain")
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_else(|| self.tools()))
|
||||||
|
}
|
||||||
|
|
||||||
async fn call(&self, name: &str, args_json: &str) -> Result<String, ToolInvocationError> {
|
async fn call(&self, name: &str, args_json: &str) -> Result<String, ToolInvocationError> {
|
||||||
self.calls.fetch_add(1, Ordering::SeqCst);
|
self.calls.fetch_add(1, Ordering::SeqCst);
|
||||||
Ok(format!("{name}:{args_json}"))
|
Ok(format!("{name}:{args_json}"))
|
||||||
@ -829,6 +854,18 @@ mod tests {
|
|||||||
path
|
path
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn tool(name: &str) -> ToolSpec {
|
||||||
|
ToolSpec {
|
||||||
|
name: name.to_owned(),
|
||||||
|
description: format!("{name} description"),
|
||||||
|
input_schema: json!({
|
||||||
|
"type": "object",
|
||||||
|
"properties": {},
|
||||||
|
"additionalProperties": false
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn config(endpoint: String, max_tool_iterations: Option<u16>) -> HttpChatConfig {
|
fn config(endpoint: String, max_tool_iterations: Option<u16>) -> HttpChatConfig {
|
||||||
config_with_timeouts(endpoint, max_tool_iterations, 10_000, 1_000)
|
config_with_timeouts(endpoint, max_tool_iterations, 10_000, 1_000)
|
||||||
}
|
}
|
||||||
@ -1055,6 +1092,44 @@ mod tests {
|
|||||||
handle.abort();
|
handle.abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn first_openai_request_uses_bound_tool_surface() {
|
||||||
|
let (endpoint, bodies, handle) = http_server(vec![TestHttpResponse::ok(
|
||||||
|
r#"{"choices":[{"message":{"role":"assistant","content":"ok"}}]}"#,
|
||||||
|
)])
|
||||||
|
.await;
|
||||||
|
let invoker = Arc::new(FakeInvoker::with_bound_tools(vec![
|
||||||
|
tool("idea_memory_read"),
|
||||||
|
tool("idea_run_in_background"),
|
||||||
|
]));
|
||||||
|
let session = OpenAiCompatibleSession::new(
|
||||||
|
SessionId::new_random(),
|
||||||
|
config(endpoint, Some(1)),
|
||||||
|
temp_run_dir("initial-bound-tools"),
|
||||||
|
"# system",
|
||||||
|
Some(invoker),
|
||||||
|
)
|
||||||
|
.expect("session");
|
||||||
|
|
||||||
|
let _ = drain(&session, "hello").await;
|
||||||
|
|
||||||
|
let bodies = bodies.lock().expect("mutex sain");
|
||||||
|
let body: Value = serde_json::from_str(&bodies[0]).expect("request body JSON");
|
||||||
|
let names = body["tools"]
|
||||||
|
.as_array()
|
||||||
|
.expect("initial request carries tools")
|
||||||
|
.iter()
|
||||||
|
.map(|tool| {
|
||||||
|
tool["function"]["name"]
|
||||||
|
.as_str()
|
||||||
|
.expect("tool function name")
|
||||||
|
.to_owned()
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
assert_eq!(names, vec!["idea_memory_read", "idea_run_in_background"]);
|
||||||
|
handle.abort();
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn max_tool_iterations_returns_single_degraded_final() {
|
async fn max_tool_iterations_returns_single_degraded_final() {
|
||||||
let tool = r#"{"choices":[{"message":{"role":"assistant","content":null,"tool_calls":[{"id":"call_1","type":"function","function":{"name":"idea_echo","arguments":"{}"}}]}}]}"#;
|
let tool = r#"{"choices":[{"message":{"role":"assistant","content":null,"tool_calls":[{"id":"call_1","type":"function","function":{"name":"idea_echo","arguments":"{}"}}]}}]}"#;
|
||||||
|
|||||||
@ -754,6 +754,9 @@ async fn tools_list_advertises_the_idea_tools_with_schemas() {
|
|||||||
"idea_ticket_bulk_delete",
|
"idea_ticket_bulk_delete",
|
||||||
"idea_ticket_read_carnet",
|
"idea_ticket_read_carnet",
|
||||||
"idea_ticket_update_carnet",
|
"idea_ticket_update_carnet",
|
||||||
|
"idea_ticket_attachment_add",
|
||||||
|
"idea_ticket_attachment_read",
|
||||||
|
"idea_ticket_attachment_mark_summarized",
|
||||||
"idea_ticket_link",
|
"idea_ticket_link",
|
||||||
"idea_ticket_unlink",
|
"idea_ticket_unlink",
|
||||||
"idea_sprint_list",
|
"idea_sprint_list",
|
||||||
@ -772,8 +775,8 @@ async fn tools_list_advertises_the_idea_tools_with_schemas() {
|
|||||||
assert!(!names.contains(&"idea_reply"));
|
assert!(!names.contains(&"idea_reply"));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
tools.len(),
|
tools.len(),
|
||||||
34,
|
37,
|
||||||
"exactly the thirty-four exposed idea_* tools; got {names:?}"
|
"exactly the thirty-seven exposed idea_* tools; got {names:?}"
|
||||||
);
|
);
|
||||||
|
|
||||||
// Every tool advertises an object input schema.
|
// Every tool advertises an object input schema.
|
||||||
|
|||||||
@ -110,7 +110,8 @@ async fn save_then_load_roundtrips_project_default_and_agent_override() {
|
|||||||
.effective_policy(agent, READ_ONLY_TOOLS, &classified_tool_names())
|
.effective_policy(agent, READ_ONLY_TOOLS, &classified_tool_names())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(effective.permits("idea_ticket_update"));
|
assert!(effective.permits("idea_ticket_update"));
|
||||||
assert!(!effective.permits("idea_context_read"));
|
assert!(effective.permits("idea_context_read"));
|
||||||
|
assert!(effective.permits("idea_memory_read"));
|
||||||
|
|
||||||
let path = tmp.0.join(".ideai").join("mcp-tool-permissions.json");
|
let path = tmp.0.join(".ideai").join("mcp-tool-permissions.json");
|
||||||
assert!(
|
assert!(
|
||||||
|
|||||||
Reference in New Issue
Block a user