thought: wire up agent and subconscious to use shared tools
- agent/tools/mod.rs: remove duplicated tool implementations, delegate
to thought::dispatch for shared tools, keep only agent-specific
tools (control, vision, working_stack)
- subconscious/api.rs: replace duplicated memory/tool dispatch with
thought::dispatch, use thought::all_definitions() for tool schemas
- Delete agent/tools/{bash,read,write,edit,grep,glob_tool,journal,memory}.rs
(now live in thought/)
Both poc-agent and subconscious agents now use the same tool
implementations through the thought layer. Agent-specific behavior
(node tracking in runner.rs, control tools) stays in agent/.
2026-03-27 15:27:33 -04:00
|
|
|
// tools/mod.rs — Agent-specific tool dispatch
|
2026-03-25 00:52:41 -04:00
|
|
|
//
|
thought: wire up agent and subconscious to use shared tools
- agent/tools/mod.rs: remove duplicated tool implementations, delegate
to thought::dispatch for shared tools, keep only agent-specific
tools (control, vision, working_stack)
- subconscious/api.rs: replace duplicated memory/tool dispatch with
thought::dispatch, use thought::all_definitions() for tool schemas
- Delete agent/tools/{bash,read,write,edit,grep,glob_tool,journal,memory}.rs
(now live in thought/)
Both poc-agent and subconscious agents now use the same tool
implementations through the thought layer. Agent-specific behavior
(node tracking in runner.rs, control tools) stays in agent/.
2026-03-27 15:27:33 -04:00
|
|
|
// Shared tools (memory, files, bash, journal) live in thought/.
|
|
|
|
|
// This module handles agent-specific tools (control, vision,
|
|
|
|
|
// working_stack) and delegates everything else to thought::dispatch.
|
2026-03-25 00:52:41 -04:00
|
|
|
|
2026-04-03 21:59:14 -04:00
|
|
|
// Core tools
|
2026-04-04 00:29:11 -04:00
|
|
|
mod bash;
|
2026-04-04 14:45:22 -04:00
|
|
|
pub mod channels;
|
2026-04-04 00:29:11 -04:00
|
|
|
mod edit;
|
|
|
|
|
mod glob;
|
|
|
|
|
mod grep;
|
2026-04-04 14:45:22 -04:00
|
|
|
pub mod memory;
|
2026-04-04 00:29:11 -04:00
|
|
|
mod read;
|
2026-04-04 12:18:11 -04:00
|
|
|
mod web;
|
2026-04-04 00:29:11 -04:00
|
|
|
mod write;
|
2026-04-03 21:59:14 -04:00
|
|
|
|
|
|
|
|
// Agent-specific tools
|
2026-03-25 00:52:41 -04:00
|
|
|
mod control;
|
|
|
|
|
mod vision;
|
|
|
|
|
|
tools/memory: one function per tool
Split the monolithic dispatch(name, args) into individual public
functions (render, write, search, links, link_set, link_add, used,
weight_set, rename, supersede, query, output, journal_tail,
journal_new, journal_update) each with a matching _def() function.
The old dispatch() remains as a thin match for backward compat
until the Tool registry replaces it.
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-04 15:03:04 -04:00
|
|
|
use std::future::Future;
|
|
|
|
|
use std::pin::Pin;
|
2026-04-08 20:37:19 -04:00
|
|
|
use std::sync::Arc;
|
2026-04-03 23:21:16 -04:00
|
|
|
use std::time::Instant;
|
|
|
|
|
|
|
|
|
|
fn default_timeout() -> u64 { 120 }
|
|
|
|
|
|
2026-04-08 20:37:19 -04:00
|
|
|
pub type ToolHandler = Arc<dyn Fn(
|
WIP: Agent/AgentState split — core methods migrated
turn(), push_node(), assemble_prompt_tokens(), compact(),
restore_from_log(), load_startup_journal(), apply_tool_result()
all use separate context/state locks. ToolHandler signature
updated to Arc<Agent>.
Remaining: tool handlers, control.rs, memory.rs, digest.rs,
and all outer callers (mind, user, learn, oneshot, dmn).
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-08 15:39:03 -04:00
|
|
|
Option<std::sync::Arc<super::Agent>>,
|
tools/memory: one function per tool
Split the monolithic dispatch(name, args) into individual public
functions (render, write, search, links, link_set, link_add, used,
weight_set, rename, supersede, query, output, journal_tail,
journal_new, journal_update) each with a matching _def() function.
The old dispatch() remains as a thin match for backward compat
until the Tool registry replaces it.
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-04 15:03:04 -04:00
|
|
|
serde_json::Value,
|
2026-04-08 20:37:19 -04:00
|
|
|
) -> Pin<Box<dyn Future<Output = anyhow::Result<String>> + Send>>
|
|
|
|
|
+ Send + Sync>;
|
tools/memory: one function per tool
Split the monolithic dispatch(name, args) into individual public
functions (render, write, search, links, link_set, link_add, used,
weight_set, rename, supersede, query, output, journal_tail,
journal_new, journal_update) each with a matching _def() function.
The old dispatch() remains as a thin match for backward compat
until the Tool registry replaces it.
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-04 15:03:04 -04:00
|
|
|
|
2026-04-08 20:37:19 -04:00
|
|
|
#[derive(Clone)]
|
tools/memory: one function per tool
Split the monolithic dispatch(name, args) into individual public
functions (render, write, search, links, link_set, link_add, used,
weight_set, rename, supersede, query, output, journal_tail,
journal_new, journal_update) each with a matching _def() function.
The old dispatch() remains as a thin match for backward compat
until the Tool registry replaces it.
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-04 15:03:04 -04:00
|
|
|
pub struct Tool {
|
2026-04-04 15:50:14 -04:00
|
|
|
pub name: &'static str,
|
|
|
|
|
pub description: &'static str,
|
|
|
|
|
pub parameters_json: &'static str,
|
tools/memory: one function per tool
Split the monolithic dispatch(name, args) into individual public
functions (render, write, search, links, link_set, link_add, used,
weight_set, rename, supersede, query, output, journal_tail,
journal_new, journal_update) each with a matching _def() function.
The old dispatch() remains as a thin match for backward compat
until the Tool registry replaces it.
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-04 15:03:04 -04:00
|
|
|
pub handler: ToolHandler,
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-04 15:50:14 -04:00
|
|
|
impl Tool {
|
|
|
|
|
/// Build the JSON for this tool's definition (for the API tools array).
|
|
|
|
|
pub fn to_json(&self) -> String {
|
|
|
|
|
format!(
|
|
|
|
|
r#"{{"type":"function","function":{{"name":"{}","description":"{}","parameters":{}}}}}"#,
|
|
|
|
|
self.name,
|
|
|
|
|
self.description.replace('"', r#"\""#),
|
|
|
|
|
self.parameters_json,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-03 23:42:27 -04:00
|
|
|
pub struct ActiveToolCall {
|
|
|
|
|
pub id: String,
|
|
|
|
|
pub name: String,
|
|
|
|
|
pub detail: String,
|
|
|
|
|
pub started: Instant,
|
|
|
|
|
pub background: bool,
|
WIP: Rename context_new → context, delete old files, fix UI layer
Renamed context_new.rs to context.rs, deleted context_old.rs,
types.rs, openai.rs, parsing.rs. Updated all imports. Rewrote
user/context.rs and user/widgets.rs for new types. Stubbed
working_stack tool. Killed tokenize_conv_entry.
Remaining: mind/mod.rs, mind/dmn.rs, learn.rs, chat.rs,
subconscious.rs, oneshot.rs.
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-08 15:20:26 -04:00
|
|
|
pub handle: tokio::task::JoinHandle<(super::context::PendingToolCall, String)>,
|
2026-04-03 23:42:27 -04:00
|
|
|
}
|
|
|
|
|
|
2026-04-08 16:41:14 -04:00
|
|
|
pub struct ActiveTools(Vec<ActiveToolCall>);
|
|
|
|
|
|
|
|
|
|
impl ActiveTools {
|
|
|
|
|
pub fn new() -> Self { Self(Vec::new()) }
|
|
|
|
|
|
|
|
|
|
pub fn push(&mut self, call: ActiveToolCall) {
|
|
|
|
|
self.0.push(call);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn remove(&mut self, id: &str) {
|
|
|
|
|
self.0.retain(|t| t.id != id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn take_finished(&mut self) -> Vec<ActiveToolCall> {
|
|
|
|
|
let mut finished = Vec::new();
|
|
|
|
|
let mut i = 0;
|
|
|
|
|
while i < self.0.len() {
|
|
|
|
|
if self.0[i].handle.is_finished() {
|
|
|
|
|
finished.push(self.0.remove(i));
|
|
|
|
|
} else {
|
|
|
|
|
i += 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
finished
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn take_foreground(&mut self) -> Vec<ActiveToolCall> {
|
|
|
|
|
let mut fg = Vec::new();
|
|
|
|
|
let mut i = 0;
|
|
|
|
|
while i < self.0.len() {
|
|
|
|
|
if !self.0[i].background {
|
|
|
|
|
fg.push(self.0.remove(i));
|
|
|
|
|
} else {
|
|
|
|
|
i += 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
fg
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn iter(&self) -> impl Iterator<Item = &ActiveToolCall> {
|
|
|
|
|
self.0.iter()
|
|
|
|
|
}
|
2026-04-05 22:34:48 -04:00
|
|
|
|
2026-04-08 16:45:56 -04:00
|
|
|
pub fn abort_all(&mut self) {
|
|
|
|
|
for entry in self.0.drain(..) {
|
|
|
|
|
entry.handle.abort();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-08 16:41:14 -04:00
|
|
|
pub fn len(&self) -> usize { self.0.len() }
|
|
|
|
|
pub fn is_empty(&self) -> bool { self.0.is_empty() }
|
2026-04-05 22:34:48 -04:00
|
|
|
}
|
|
|
|
|
|
2026-04-03 23:21:16 -04:00
|
|
|
/// Truncate output if it exceeds max length, appending a truncation notice.
|
|
|
|
|
pub fn truncate_output(mut s: String, max: usize) -> String {
|
|
|
|
|
if s.len() > max {
|
|
|
|
|
s.truncate(max);
|
|
|
|
|
s.push_str("\n... (output truncated)");
|
|
|
|
|
}
|
|
|
|
|
s
|
|
|
|
|
}
|
2026-03-25 00:52:41 -04:00
|
|
|
|
2026-04-04 15:10:13 -04:00
|
|
|
/// Dispatch a tool call by name through the registry.
|
2026-04-04 16:08:59 -04:00
|
|
|
/// Dispatch a tool call by name. Returns the result text,
|
|
|
|
|
/// or an error string prefixed with "Error: ".
|
2026-03-25 00:52:41 -04:00
|
|
|
pub async fn dispatch(
|
|
|
|
|
name: &str,
|
|
|
|
|
args: &serde_json::Value,
|
2026-04-04 16:08:59 -04:00
|
|
|
) -> String {
|
2026-04-04 15:10:13 -04:00
|
|
|
dispatch_with_agent(name, args, None).await
|
|
|
|
|
}
|
2026-03-25 00:52:41 -04:00
|
|
|
|
2026-04-04 15:10:13 -04:00
|
|
|
/// Dispatch a tool call with optional agent context.
|
2026-04-04 16:23:04 -04:00
|
|
|
/// If agent is provided, uses the agent's tool list.
|
2026-04-04 15:10:13 -04:00
|
|
|
pub async fn dispatch_with_agent(
|
|
|
|
|
name: &str,
|
|
|
|
|
args: &serde_json::Value,
|
WIP: Agent/AgentState split — core methods migrated
turn(), push_node(), assemble_prompt_tokens(), compact(),
restore_from_log(), load_startup_journal(), apply_tool_result()
all use separate context/state locks. ToolHandler signature
updated to Arc<Agent>.
Remaining: tool handlers, control.rs, memory.rs, digest.rs,
and all outer callers (mind, user, learn, oneshot, dmn).
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-08 15:39:03 -04:00
|
|
|
agent: Option<std::sync::Arc<super::Agent>>,
|
2026-04-04 16:08:59 -04:00
|
|
|
) -> String {
|
2026-04-04 16:25:42 -04:00
|
|
|
let tool = if let Some(ref a) = agent {
|
2026-04-08 19:19:05 -04:00
|
|
|
// Only dispatch tools the agent is allowed to use
|
WIP: Agent/AgentState split — core methods migrated
turn(), push_node(), assemble_prompt_tokens(), compact(),
restore_from_log(), load_startup_journal(), apply_tool_result()
all use separate context/state locks. ToolHandler signature
updated to Arc<Agent>.
Remaining: tool handlers, control.rs, memory.rs, digest.rs,
and all outer callers (mind, user, learn, oneshot, dmn).
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-08 15:39:03 -04:00
|
|
|
let guard = a.state.lock().await;
|
2026-04-04 16:25:42 -04:00
|
|
|
guard.tools.iter().find(|t| t.name == name).copied()
|
|
|
|
|
} else {
|
2026-04-08 19:19:05 -04:00
|
|
|
// No agent context — allow all tools (CLI/MCP path)
|
|
|
|
|
tools().into_iter().find(|t| t.name == name)
|
2026-04-04 16:25:42 -04:00
|
|
|
};
|
|
|
|
|
match tool {
|
|
|
|
|
Some(t) => (t.handler)(agent, args.clone()).await
|
|
|
|
|
.unwrap_or_else(|e| format!("Error: {}", e)),
|
|
|
|
|
None => format!("Error: Unknown tool: {}", name),
|
2026-03-25 00:52:41 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-04 15:07:55 -04:00
|
|
|
/// Return all registered tools with definitions + handlers.
|
|
|
|
|
pub fn tools() -> Vec<Tool> {
|
2026-04-04 15:22:03 -04:00
|
|
|
let mut all = vec![
|
2026-04-04 15:34:07 -04:00
|
|
|
read::tool(), write::tool(), edit::tool(),
|
|
|
|
|
grep::tool(), glob::tool(), bash::tool(),
|
|
|
|
|
vision::tool(),
|
2026-04-04 15:22:03 -04:00
|
|
|
];
|
2026-04-04 15:34:07 -04:00
|
|
|
all.extend(web::tools());
|
2026-04-04 15:22:03 -04:00
|
|
|
all.extend(memory::memory_tools());
|
2026-04-04 16:48:33 -04:00
|
|
|
all.extend(memory::journal_tools());
|
2026-04-04 15:22:03 -04:00
|
|
|
all.extend(channels::tools());
|
|
|
|
|
all.extend(control::tools());
|
|
|
|
|
all
|
2026-04-04 15:07:55 -04:00
|
|
|
}
|
|
|
|
|
|
2026-04-04 16:39:04 -04:00
|
|
|
/// Memory + journal tools only — for subconscious agents.
|
|
|
|
|
pub fn memory_and_journal_tools() -> Vec<Tool> {
|
|
|
|
|
let mut all = memory::memory_tools().to_vec();
|
|
|
|
|
all.extend(memory::journal_tools());
|
|
|
|
|
all
|
2026-04-03 23:21:16 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Create a short summary of tool args for the tools pane header.
|
|
|
|
|
pub fn summarize_args(tool_name: &str, args: &serde_json::Value) -> String {
|
|
|
|
|
match tool_name {
|
|
|
|
|
"read_file" | "write_file" | "edit_file" => args["file_path"]
|
|
|
|
|
.as_str()
|
|
|
|
|
.unwrap_or("")
|
|
|
|
|
.to_string(),
|
|
|
|
|
"bash" => {
|
|
|
|
|
let cmd = args["command"].as_str().unwrap_or("");
|
|
|
|
|
if cmd.len() > 60 {
|
|
|
|
|
let end = cmd.char_indices()
|
|
|
|
|
.map(|(i, _)| i)
|
|
|
|
|
.take_while(|&i| i <= 60)
|
|
|
|
|
.last()
|
|
|
|
|
.unwrap_or(0);
|
|
|
|
|
format!("{}...", &cmd[..end])
|
|
|
|
|
} else {
|
|
|
|
|
cmd.to_string()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
"grep" => {
|
|
|
|
|
let pattern = args["pattern"].as_str().unwrap_or("");
|
|
|
|
|
let path = args["path"].as_str().unwrap_or(".");
|
|
|
|
|
format!("{} in {}", pattern, path)
|
|
|
|
|
}
|
|
|
|
|
"glob" => args["pattern"]
|
|
|
|
|
.as_str()
|
|
|
|
|
.unwrap_or("")
|
|
|
|
|
.to_string(),
|
|
|
|
|
"view_image" => {
|
|
|
|
|
if let Some(pane) = args["pane_id"].as_str() {
|
|
|
|
|
format!("pane {}", pane)
|
|
|
|
|
} else {
|
|
|
|
|
args["file_path"].as_str().unwrap_or("").to_string()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
"journal" => {
|
|
|
|
|
let entry = args["entry"].as_str().unwrap_or("");
|
|
|
|
|
if entry.len() > 60 {
|
|
|
|
|
format!("{}...", &entry[..60])
|
|
|
|
|
} else {
|
|
|
|
|
entry.to_string()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
"yield_to_user" => args["message"]
|
|
|
|
|
.as_str()
|
|
|
|
|
.unwrap_or("")
|
|
|
|
|
.to_string(),
|
|
|
|
|
"switch_model" => args["model"]
|
|
|
|
|
.as_str()
|
|
|
|
|
.unwrap_or("")
|
|
|
|
|
.to_string(),
|
|
|
|
|
"pause" => String::new(),
|
|
|
|
|
_ => String::new(),
|
|
|
|
|
}
|
|
|
|
|
}
|