WIP: Output tool via Arc<Mutex<Subconscious>>, ToolHandler to Arc<dyn Fn>
- ToolHandler changed to Arc<dyn Fn(...)> (supports closures) - Subconscious wrapped in Arc<Mutex<>> on Mind - init_output_tool() pushes output tool closure capturing the Arc - Output removed from static memory_tools() - Most tool handlers wrapped in Arc::new() but some have paren issues Co-Authored-By: Proof of Concept <poc@bcachefs.org>
This commit is contained in:
parent
d167b11283
commit
12798eeae2
15 changed files with 74 additions and 51 deletions
|
|
@ -37,7 +37,7 @@ pub fn tool() -> super::Tool {
|
|||
name: "bash",
|
||||
description: "Execute a bash command and return its output. Use for git operations, building, running tests, and other terminal tasks.",
|
||||
parameters_json: r#"{"type":"object","properties":{"command":{"type":"string","description":"The bash command to execute"},"timeout_secs":{"type":"integer","description":"Timeout in seconds (default 120)"}},"required":["command"]}"#,
|
||||
handler: |_a, v| Box::pin(async move { run_bash(&v).await }),
|
||||
handler: Arc::new(|_a, v| Box::pin(async move { run_bash(&v).await })),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,27 +15,27 @@ pub fn tools() -> [Tool; 6] {
|
|||
Tool { name: "channel_list",
|
||||
description: "List all available channels and their status (connected, unread count).",
|
||||
parameters_json: r#"{"type":"object","properties":{}}"#,
|
||||
handler: |_a, _v| Box::pin(async { channel_list().await }) },
|
||||
handler: Arc::new(|_a, _v| Box::pin(async { channel_list().await })) },
|
||||
Tool { name: "channel_recv",
|
||||
description: "Read messages from a channel.",
|
||||
parameters_json: r#"{"type":"object","properties":{"channel":{"type":"string","description":"Channel path (e.g. irc.#bcachefs, telegram.kent)"},"all_new":{"type":"boolean","description":"If true, return all unconsumed messages","default":true},"min_count":{"type":"integer","description":"Minimum number of lines to return","default":20}},"required":["channel"]}"#,
|
||||
handler: |_a, v| Box::pin(async move { channel_recv(&v).await }) },
|
||||
handler: Arc::new(|_a, v| Box::pin(async move { channel_recv(&v).await })) },
|
||||
Tool { name: "channel_send",
|
||||
description: "Send a message to a channel.",
|
||||
parameters_json: r#"{"type":"object","properties":{"channel":{"type":"string","description":"Channel path (e.g. irc.#bcachefs, irc.pm.nick, telegram.kent)"},"message":{"type":"string","description":"Message to send"}},"required":["channel","message"]}"#,
|
||||
handler: |_a, v| Box::pin(async move { channel_send(&v).await }) },
|
||||
handler: Arc::new(|_a, v| Box::pin(async move { channel_send(&v).await })) },
|
||||
Tool { name: "channel_notifications",
|
||||
description: "Get pending channel notifications (unread signals). Does not consume messages — use channel_recv for that.",
|
||||
parameters_json: r#"{"type":"object","properties":{}}"#,
|
||||
handler: |_a, _v| Box::pin(async { channel_notifications().await }) },
|
||||
handler: Arc::new(|_a, _v| Box::pin(async { channel_notifications().await })) },
|
||||
Tool { name: "channel_open",
|
||||
description: "Open a channel — start monitoring. For tmux: finds the pane by name and attaches pipe-pane.",
|
||||
parameters_json: r#"{"type":"object","properties":{"label":{"type":"string","description":"Channel label / tmux pane name"}},"required":["label"]}"#,
|
||||
handler: |_a, v| Box::pin(async move { channel_open(&v).await }) },
|
||||
handler: Arc::new(|_a, v| Box::pin(async move { channel_open(&v).await })) },
|
||||
Tool { name: "channel_close",
|
||||
description: "Close a channel — stop monitoring and clean up.",
|
||||
parameters_json: r#"{"type":"object","properties":{"channel":{"type":"string","description":"Channel path (e.g. tmux.ktest)"}},"required":["channel"]}"#,
|
||||
handler: |_a, v| Box::pin(async move { channel_close(&v).await }) },
|
||||
handler: Arc::new(|_a, v| Box::pin(async move { channel_close(&v).await })) },
|
||||
]
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ pub(super) fn tools() -> [super::Tool; 3] {
|
|||
Tool { name: "switch_model",
|
||||
description: "Switch to a different LLM model mid-conversation. Memories and history carry over.",
|
||||
parameters_json: r#"{"type":"object","properties":{"model":{"type":"string","description":"Name of the model to switch to"}},"required":["model"]}"#,
|
||||
handler: |agent, v| Box::pin(async move {
|
||||
handler: Arc::new(|agent, v| Box::pin(async move {
|
||||
let model = v.get("model").and_then(|v| v.as_str())
|
||||
.ok_or_else(|| anyhow::anyhow!("'model' parameter is required"))?;
|
||||
if model.is_empty() { anyhow::bail!("'model' parameter cannot be empty"); }
|
||||
|
|
@ -22,7 +22,7 @@ pub(super) fn tools() -> [super::Tool; 3] {
|
|||
Tool { name: "pause",
|
||||
description: "Pause all autonomous behavior. Only the user can unpause (Ctrl+P or /wake).",
|
||||
parameters_json: r#"{"type":"object","properties":{}}"#,
|
||||
handler: |agent, _v| Box::pin(async move {
|
||||
handler: Arc::new(|agent, _v| Box::pin(async move {
|
||||
if let Some(agent) = agent {
|
||||
let mut a = agent.state.lock().await;
|
||||
a.pending_yield = true;
|
||||
|
|
@ -33,7 +33,7 @@ pub(super) fn tools() -> [super::Tool; 3] {
|
|||
Tool { name: "yield_to_user",
|
||||
description: "Wait for user input before continuing. The only way to enter a waiting state.",
|
||||
parameters_json: r#"{"type":"object","properties":{"message":{"type":"string","description":"Optional status message"}}}"#,
|
||||
handler: |agent, v| Box::pin(async move {
|
||||
handler: Arc::new(|agent, v| Box::pin(async move {
|
||||
let msg = v.get("message").and_then(|v| v.as_str()).unwrap_or("Waiting for input.");
|
||||
if let Some(agent) = agent {
|
||||
let mut a = agent.state.lock().await;
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ pub fn tool() -> super::Tool {
|
|||
name: "edit_file",
|
||||
description: "Perform exact string replacement in a file. The old_string must appear exactly once (unless replace_all is true). Use read_file first to see current contents.",
|
||||
parameters_json: r#"{"type":"object","properties":{"file_path":{"type":"string","description":"Absolute path to the file to edit"},"old_string":{"type":"string","description":"The exact text to find and replace"},"new_string":{"type":"string","description":"The replacement text"},"replace_all":{"type":"boolean","description":"Replace all occurrences (default false)"}},"required":["file_path","old_string","new_string"]}"#,
|
||||
handler: |_a, v| Box::pin(async move { edit_file(&v) }),
|
||||
handler: Arc::new(|_a, v| Box::pin(async move { edit_file(&v)) }),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ pub fn tool() -> super::Tool {
|
|||
name: "glob",
|
||||
description: "Find files matching a glob pattern. Returns file paths sorted by modification time (newest first).",
|
||||
parameters_json: r#"{"type":"object","properties":{"pattern":{"type":"string","description":"Glob pattern to match files (e.g. '**/*.rs')"},"path":{"type":"string","description":"Directory to search in (default: current directory)"}},"required":["pattern"]}"#,
|
||||
handler: |_a, v| Box::pin(async move { glob_search(&v) }),
|
||||
handler: Arc::new(|_a, v| Box::pin(async move { glob_search(&v)) }),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ pub fn tool() -> super::Tool {
|
|||
name: "grep",
|
||||
description: "Search for a pattern in files. Returns matching file paths by default, or matching lines with context.",
|
||||
parameters_json: r#"{"type":"object","properties":{"pattern":{"type":"string","description":"Regex pattern to search for"},"path":{"type":"string","description":"Directory or file to search in (default: current directory)"},"glob":{"type":"string","description":"Glob pattern to filter files (e.g. '*.rs')"},"show_content":{"type":"boolean","description":"Show matching lines instead of just file paths"},"context_lines":{"type":"integer","description":"Lines of context around matches"}},"required":["pattern"]}"#,
|
||||
handler: |_a, v| Box::pin(async move { grep(&v) }),
|
||||
handler: Arc::new(|_a, v| Box::pin(async move { grep(&v)) }),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -37,40 +37,37 @@ pub fn memory_tools() -> [super::Tool; 12] {
|
|||
[
|
||||
Tool { name: "memory_render", description: "Read a memory node's content and links.",
|
||||
parameters_json: r#"{"type":"object","properties":{"key":{"type":"string","description":"Node key"}},"required":["key"]}"#,
|
||||
handler: |_a, v| Box::pin(async move { render(&v) }) },
|
||||
handler: Arc::new(|_a, v| Box::pin(async move { render(&v) })) },
|
||||
Tool { name: "memory_write", description: "Create or update a memory node.",
|
||||
parameters_json: r#"{"type":"object","properties":{"key":{"type":"string","description":"Node key"},"content":{"type":"string","description":"Full content (markdown)"}},"required":["key","content"]}"#,
|
||||
handler: |a, v| Box::pin(async move { write(&a, &v).await }) },
|
||||
handler: Arc::new(|a, v| Box::pin(async move { write(&a, &v).await })) },
|
||||
Tool { name: "memory_search", description: "Search the memory graph via spreading activation. Give 2-4 seed node keys.",
|
||||
parameters_json: r#"{"type":"object","properties":{"keys":{"type":"array","items":{"type":"string"},"description":"Seed node keys to activate from"}},"required":["keys"]}"#,
|
||||
handler: |_a, v| Box::pin(async move { search(&v).await }) },
|
||||
handler: Arc::new(|_a, v| Box::pin(async move { search(&v).await })) },
|
||||
Tool { name: "memory_links", description: "Show a node's neighbors with link strengths.",
|
||||
parameters_json: r#"{"type":"object","properties":{"key":{"type":"string","description":"Node key"}},"required":["key"]}"#,
|
||||
handler: |_a, v| Box::pin(async move { links(&v) }) },
|
||||
handler: Arc::new(|_a, v| Box::pin(async move { links(&v) })) },
|
||||
Tool { name: "memory_link_set", description: "Set link strength between two nodes.",
|
||||
parameters_json: r#"{"type":"object","properties":{"source":{"type":"string"},"target":{"type":"string"},"strength":{"type":"number","description":"0.01 to 1.0"}},"required":["source","target","strength"]}"#,
|
||||
handler: |_a, v| Box::pin(async move { link_set(&v).await }) },
|
||||
handler: Arc::new(|_a, v| Box::pin(async move { link_set(&v).await })) },
|
||||
Tool { name: "memory_link_add", description: "Add a new link between two nodes.",
|
||||
parameters_json: r#"{"type":"object","properties":{"source":{"type":"string"},"target":{"type":"string"}},"required":["source","target"]}"#,
|
||||
handler: |a, v| Box::pin(async move { link_add(&a, &v).await }) },
|
||||
handler: Arc::new(|a, v| Box::pin(async move { link_add(&a, &v).await })) },
|
||||
Tool { name: "memory_used", description: "Mark a node as useful (boosts weight).",
|
||||
parameters_json: r#"{"type":"object","properties":{"key":{"type":"string","description":"Node key"}},"required":["key"]}"#,
|
||||
handler: |_a, v| Box::pin(async move { used(&v).await }) },
|
||||
handler: Arc::new(|_a, v| Box::pin(async move { used(&v).await })) },
|
||||
Tool { name: "memory_weight_set", description: "Set a node's weight directly (0.01 to 1.0).",
|
||||
parameters_json: r#"{"type":"object","properties":{"key":{"type":"string"},"weight":{"type":"number","description":"0.01 to 1.0"}},"required":["key","weight"]}"#,
|
||||
handler: |_a, v| Box::pin(async move { weight_set(&v).await }) },
|
||||
handler: Arc::new(|_a, v| Box::pin(async move { weight_set(&v).await })) },
|
||||
Tool { name: "memory_rename", description: "Rename a node key in place.",
|
||||
parameters_json: r#"{"type":"object","properties":{"old_key":{"type":"string"},"new_key":{"type":"string"}},"required":["old_key","new_key"]}"#,
|
||||
handler: |_a, v| Box::pin(async move { rename(&v).await }) },
|
||||
handler: Arc::new(|_a, v| Box::pin(async move { rename(&v).await })) },
|
||||
Tool { name: "memory_supersede", description: "Mark a node as superseded by another (sets weight to 0.01).",
|
||||
parameters_json: r#"{"type":"object","properties":{"old_key":{"type":"string"},"new_key":{"type":"string"},"reason":{"type":"string"}},"required":["old_key","new_key"]}"#,
|
||||
handler: |a, v| Box::pin(async move { supersede(&a, &v).await }) },
|
||||
handler: Arc::new(|a, v| Box::pin(async move { supersede(&a, &v).await })) },
|
||||
Tool { name: "memory_query", description: "Run a structured query against the memory graph.",
|
||||
parameters_json: r#"{"type":"object","properties":{"query":{"type":"string","description":"Query expression"}},"required":["query"]}"#,
|
||||
handler: |_a, v| Box::pin(async move { query(&v).await }) },
|
||||
Tool { name: "output", description: "Produce a named output value for passing between steps.",
|
||||
parameters_json: r#"{"type":"object","properties":{"key":{"type":"string","description":"Output name"},"value":{"type":"string","description":"Output value"}},"required":["key","value"]}"#,
|
||||
handler: |_a, v| Box::pin(async move { output(&v) }) },
|
||||
handler: Arc::new(|_a, v| Box::pin(async move { query(&v).await })) },
|
||||
]
|
||||
}
|
||||
|
||||
|
|
@ -79,13 +76,13 @@ pub fn journal_tools() -> [super::Tool; 3] {
|
|||
[
|
||||
Tool { name: "journal_tail", description: "Read the last N journal entries (default 1).",
|
||||
parameters_json: r#"{"type":"object","properties":{"count":{"type":"integer","description":"Number of entries (default 1)"}}}"#,
|
||||
handler: |_a, v| Box::pin(async move { journal_tail(&v).await }) },
|
||||
handler: Arc::new(|_a, v| Box::pin(async move { journal_tail(&v).await })) },
|
||||
Tool { name: "journal_new", description: "Start a new journal entry.",
|
||||
parameters_json: r#"{"type":"object","properties":{"name":{"type":"string","description":"Short node name (becomes the key)"},"title":{"type":"string","description":"Descriptive title"},"body":{"type":"string","description":"Entry body"}},"required":["name","title","body"]}"#,
|
||||
handler: |a, v| Box::pin(async move { journal_new(&a, &v).await }) },
|
||||
handler: Arc::new(|a, v| Box::pin(async move { journal_new(&a, &v).await })) },
|
||||
Tool { name: "journal_update", description: "Append text to the most recent journal entry.",
|
||||
parameters_json: r#"{"type":"object","properties":{"body":{"type":"string","description":"Text to append"}},"required":["body"]}"#,
|
||||
handler: |a, v| Box::pin(async move { journal_update(&a, &v).await }) },
|
||||
handler: Arc::new(|a, v| Box::pin(async move { journal_update(&a, &v).await })) },
|
||||
]
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,21 +21,18 @@ mod vision;
|
|||
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
use std::time::Instant;
|
||||
|
||||
fn default_timeout() -> u64 { 120 }
|
||||
|
||||
/// Async tool handler function.
|
||||
/// Agent is None when called from contexts without an agent (MCP server, subconscious).
|
||||
pub type ToolHandler = fn(
|
||||
pub type ToolHandler = Arc<dyn Fn(
|
||||
Option<std::sync::Arc<super::Agent>>,
|
||||
serde_json::Value,
|
||||
) -> Pin<Box<dyn Future<Output = anyhow::Result<String>> + Send>>;
|
||||
) -> Pin<Box<dyn Future<Output = anyhow::Result<String>> + Send>>
|
||||
+ Send + Sync>;
|
||||
|
||||
/// A tool with its definition and handler — single source of truth.
|
||||
/// Strings are static — the tool list JSON can be built without
|
||||
/// serialization by interpolating these directly.
|
||||
#[derive(Clone, Copy)]
|
||||
#[derive(Clone)]
|
||||
pub struct Tool {
|
||||
pub name: &'static str,
|
||||
pub description: &'static str,
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ pub fn tool() -> super::Tool {
|
|||
name: "read_file",
|
||||
description: "Read the contents of a file. Returns the file contents with line numbers.",
|
||||
parameters_json: r#"{"type":"object","properties":{"file_path":{"type":"string","description":"Absolute path to the file to read"},"offset":{"type":"integer","description":"Line number to start reading from (1-based)"},"limit":{"type":"integer","description":"Maximum number of lines to read"}},"required":["file_path"]}"#,
|
||||
handler: |_a, v| Box::pin(async move { read_file(&v) }),
|
||||
handler: Arc::new(|_a, v| Box::pin(async move { read_file(&v)) }),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ pub fn tool() -> super::Tool {
|
|||
name: "view_image",
|
||||
description: "View an image file or capture a tmux pane screenshot. Supports PNG, JPEG, GIF, WebP. Use pane_id to capture a tmux pane instead.",
|
||||
parameters_json: r#"{"type":"object","properties":{"file_path":{"type":"string","description":"Path to an image file"},"pane_id":{"type":"string","description":"Tmux pane ID to capture (e.g. '0:1.0')"},"lines":{"type":"integer","description":"Lines to capture from tmux pane (default 50)"}}}"#,
|
||||
handler: |_a, v| Box::pin(async move { view_image_text(&v) }),
|
||||
handler: Arc::new(|_a, v| Box::pin(async move { view_image_text(&v)) }),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,13 +9,13 @@ pub fn tools() -> [super::Tool; 2] {
|
|||
name: "web_fetch",
|
||||
description: "Fetch content from a URL and return it as text. Use for reading web pages, API responses, documentation.",
|
||||
parameters_json: r#"{"type":"object","properties":{"url":{"type":"string","description":"The URL to fetch"}},"required":["url"]}"#,
|
||||
handler: |_a, v| Box::pin(async move { web_fetch(&v).await }),
|
||||
handler: Arc::new(|_a, v| Box::pin(async move { web_fetch(&v).await }),
|
||||
},
|
||||
super::Tool {
|
||||
name: "web_search",
|
||||
description: "Search the web and return results. Use for finding documentation, looking up APIs, researching topics.",
|
||||
parameters_json: r#"{"type":"object","properties":{"query":{"type":"string","description":"The search query"},"num_results":{"type":"integer","description":"Number of results to return (default 5)"}},"required":["query"]}"#,
|
||||
handler: |_a, v| Box::pin(async move { web_search(&v).await }),
|
||||
handler: Arc::new(|_a, v| Box::pin(async move { web_search(&v).await }),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ pub fn tool() -> super::Tool {
|
|||
name: "write_file",
|
||||
description: "Create or overwrite a file with the given content.",
|
||||
parameters_json: r#"{"type":"object","properties":{"file_path":{"type":"string","description":"Absolute path to write"},"content":{"type":"string","description":"File content"}},"required":["file_path","content"]}"#,
|
||||
handler: |_a, v| Box::pin(async move { write_file(&v) }),
|
||||
handler: Arc::new(|_a, v| Box::pin(async move { write_file(&v)) }),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue