mirror of
https://github.com/outbackdingo/optimclaw.git
synced 2026-08-26 23:50:17 +00:00
* Rebase onto staging
* fix(routines): prevent autonomy-escalation in lightweight routines
- Add ROUTINE_TOOL_DENYLIST to block routine_create/update/delete/fire
and restart from being callable by lightweight routines
- Deduplicate sentinel logic by reusing handle_text_response() in the
no-tools path
- Filter tool definitions sent to LLM to only include callable tools,
avoiding wasted tokens on tools that would be rejected
This commit is contained in:
@@ -104,6 +104,14 @@ impl Tool for RoutineCreateTool {
|
||||
"enum": ["lightweight", "full_job"],
|
||||
"description": "Execution mode: 'lightweight' (single LLM call, default) or 'full_job' (multi-turn with tools)"
|
||||
},
|
||||
"use_tools": {
|
||||
"type": "boolean",
|
||||
"description": "Enable tool access in lightweight mode (default: false). Only safe tools (no approval required) are available. Ignored for full_job mode."
|
||||
},
|
||||
"max_tool_rounds": {
|
||||
"type": "integer",
|
||||
"description": "Max tool call rounds in lightweight mode (default: 3). Only used when use_tools is true."
|
||||
},
|
||||
"cooldown_secs": {
|
||||
"type": "integer",
|
||||
"description": "Minimum seconds between fires (default: 300)"
|
||||
@@ -262,11 +270,24 @@ impl Tool for RoutineCreateTool {
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
let use_tools = params
|
||||
.get("use_tools")
|
||||
.and_then(|v| v.as_bool())
|
||||
.unwrap_or(false);
|
||||
|
||||
let max_tool_rounds = params
|
||||
.get("max_tool_rounds")
|
||||
.and_then(|v| v.as_u64())
|
||||
.map(|v| v.clamp(1, crate::agent::routine::MAX_TOOL_ROUNDS_LIMIT as u64) as u32)
|
||||
.unwrap_or(3);
|
||||
|
||||
let action = match action_type {
|
||||
"lightweight" => RoutineAction::Lightweight {
|
||||
prompt: prompt.to_string(),
|
||||
context_paths,
|
||||
max_tokens: 4096,
|
||||
use_tools,
|
||||
max_tool_rounds,
|
||||
},
|
||||
"full_job" => {
|
||||
let tool_permissions = crate::agent::routine::parse_tool_permissions(¶ms);
|
||||
|
||||
+33
-1
@@ -23,7 +23,7 @@ use crate::tools::builtin::{
|
||||
ToolUpgradeTool, WriteFileTool,
|
||||
};
|
||||
use crate::tools::rate_limiter::RateLimiter;
|
||||
use crate::tools::tool::{Tool, ToolDomain};
|
||||
use crate::tools::tool::{ApprovalRequirement, Tool, ToolDomain};
|
||||
use crate::tools::wasm::{
|
||||
Capabilities, OAuthRefreshConfig, ResourceLimits, SharedCredentialRegistry, WasmError,
|
||||
WasmStorageError, WasmToolRuntime, WasmToolStore, WasmToolWrapper,
|
||||
@@ -278,6 +278,38 @@ impl ToolRegistry {
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Get tool definitions excluding specific tools by name.
|
||||
///
|
||||
/// Used by lightweight routines to filter out denylisted and approval-gated tools
|
||||
/// so the LLM only sees tools it is actually allowed to call.
|
||||
pub async fn tool_definitions_excluding(&self, deny: &[&str]) -> Vec<ToolDefinition> {
|
||||
let empty_params = serde_json::Value::Object(serde_json::Map::new());
|
||||
let mut defs: Vec<ToolDefinition> = self
|
||||
.tools
|
||||
.read()
|
||||
.await
|
||||
.values()
|
||||
.filter(|tool| {
|
||||
// Exclude denylisted tools
|
||||
if deny.contains(&tool.name()) {
|
||||
return false;
|
||||
}
|
||||
// Exclude tools that require approval
|
||||
matches!(
|
||||
tool.requires_approval(&empty_params),
|
||||
ApprovalRequirement::Never
|
||||
)
|
||||
})
|
||||
.map(|tool| ToolDefinition {
|
||||
name: tool.name().to_string(),
|
||||
description: tool.description().to_string(),
|
||||
parameters: tool.parameters_schema(),
|
||||
})
|
||||
.collect();
|
||||
defs.sort_unstable_by(|a, b| a.name.cmp(&b.name));
|
||||
defs
|
||||
}
|
||||
|
||||
/// Register development tools for building software.
|
||||
///
|
||||
/// These tools provide shell access, file operations, and code editing
|
||||
|
||||
Reference in New Issue
Block a user