diff --git a/crates/ai/src/cli_agents/adapters.rs b/crates/ai/src/cli_agents/adapters.rs index e19f4cf8..aea5e012 100644 --- a/crates/ai/src/cli_agents/adapters.rs +++ b/crates/ai/src/cli_agents/adapters.rs @@ -290,6 +290,8 @@ impl AgentAdapter for ClaudeCode { if let Some(r) = cx.resume { a.extend(["--resume".into(), r.to_string()]); } + let tools = if cx.builder { "Bash,PowerShell,Read,Write,Edit,MultiEdit,Glob,Grep,TodoWrite,NotebookEdit" } else { "" }; + a.extend(["--tools".into(), tools.into()]); if cx.builder { // Questions go through our core_person_ask tool, which the app shows as buttons. a.extend(["--permission-prompt-tool".into(), format!("mcp__{MCP_NAME}__{APPROVAL_TOOL}"), "--disallowedTools".into(), "AskUserQuestion".into()]); diff --git a/crates/ai/src/cli_agents/mod.rs b/crates/ai/src/cli_agents/mod.rs index 9be49c42..fb189c05 100644 --- a/crates/ai/src/cli_agents/mod.rs +++ b/crates/ai/src/cli_agents/mod.rs @@ -1146,6 +1146,14 @@ mod tests { let pos = |f: &str| a.iter().position(|x| x == f).unwrap(); assert_eq!(a[pos("--output-format") + 1], "stream-json"); assert!(a.contains(&"--verbose".to_string())); + assert_eq!(a[pos("--tools") + 1], "", "built-in tools are opt-in"); + for access in [Access::Build, Access::CleanRoomBuild] { + let builder = build_args(adapter_for("claude-code").as_ref(), &BackendOptions::default(), "build it", "PRE", &mcp(), None, access); + let builder_pos = builder.iter().position(|x| x == "--tools").unwrap(); + let tools = &builder[builder_pos + 1]; + assert!(tools.split(',').any(|tool| tool == "Bash"), "{access:?}: {tools}"); + assert!(tools.split(',').any(|tool| tool == "Write"), "{access:?}: {tools}"); + } assert_eq!(a[pos("--resume") + 1], "sess-1"); assert_eq!(a[pos("--allowedTools") + 1], "mcp__craft-anything__*"); assert_eq!(a[pos("--model") + 1], "claude-opus-5-5", "Opus 5.5 unless configured otherwise"); diff --git a/crates/engine/src/tools/interaction_tools.rs b/crates/engine/src/tools/interaction_tools.rs index 796f651b..c60755ba 100644 --- a/crates/engine/src/tools/interaction_tools.rs +++ b/crates/engine/src/tools/interaction_tools.rs @@ -539,7 +539,7 @@ pub fn never_unasked(ws: Option<&Workspace>, tool: &str, input: &Value) -> Optio } None } - _ => None, + _ => refuse("uses a tool that has no automatic approval rule"), } } @@ -991,6 +991,14 @@ mod tests { assert_eq!((back["questions"].as_str(), back["chosen"].is_null(), back["app_default"].as_str()), (Some("ask"), true, Some("ask"))); } + #[test] + fn auto_mode_refuses_unknown_tools_that_may_publish_or_schedule() { + for tool in ["Artifact", "CronCreate", "ListAgents"] { + let result = never_unasked(None, tool, &json!({})); + assert!(result.is_some(), "{tool} must require the person's approval"); + } + } + #[test] fn auto_mode_keeps_going_but_never_does_the_dangerous_things() { let (_d, w) = ws();