use std::path::PathBuf;
use std::sync::Arc;
use async_trait::async_trait;
use serde_json::Value;
use theway_core::executor::ToolExecutor;
use theway_core::multiagent::graph::engine::DagEngine;
use theway_core::multiagent::graph::node_launcher;
use theway_core::multiagent::jobs::SubagentJobRegistry;
use theway_core::multiagent::types::ToolSetResolver;
use theway_core::{
AgentTool, AgentToolError, AgentToolResult, AgentToolUpdate, PermissionClassification,
};
use theway_llm_provider::Tool;
use tokio_util::sync::CancellationToken;
use crate::runtime_storage::SessionRepository;
use crate::tools::skill::SkillHarnessCell;
pub mod assembly;
pub mod dag_tools;
pub mod exec;
pub mod exec_shell;
pub mod install_skill;
pub mod mcp_adapter;
pub mod memory;
pub mod remove_skill;
pub mod session_graph;
pub mod set_skill_state;
pub mod skill;
pub mod skill_builder;
pub mod subagent;
pub mod bash;
pub mod edit;
pub mod find;
pub mod git;
pub mod grep;
pub mod ls;
pub mod outline;
pub mod read;
pub mod session_tool_result;
pub mod truncate;
pub mod web_fetch;
pub mod web_search;
pub mod write;
pub use crate::triggers::tool_assembly::{
list_cron_jobs_tool, list_triggers_tool, new_cron_job_tool, new_trigger_tool,
remove_cron_job_tool, remove_trigger_tool, set_cron_job_state_tool, set_trigger_state_tool,
};
pub const LOCAL_ONLY_TOOL_NAMES: &[&str] = &[
"bash",
"exec",
"get_output",
"kill_shell",
"write_to_process",
"ls",
"grep",
"find",
];
pub struct CwdScopedTool {
inner: Arc<dyn AgentTool>,
cwd: PathBuf,
}
impl CwdScopedTool {
pub fn new(inner: Arc<dyn AgentTool>, cwd: PathBuf) -> Self {
Self { inner, cwd }
}
fn scope_args(&self, mut args: Value) -> Value {
match self.inner.definition().name.as_str() {
"bash" | "exec" | "ls" | "grep" | "find" if args.get("cwd").is_none() => {
if let Some(obj) = args.as_object_mut() {
obj.insert("cwd".into(), self.cwd.to_string_lossy().into_owned().into());
}
}
_ => {}
}
args
}
}
#[async_trait]
impl AgentTool for CwdScopedTool {
fn definition(&self) -> &Tool {
self.inner.definition()
}
fn label(&self) -> &str {
self.inner.label()
}
fn execution_mode(&self) -> Option<theway_core::ToolExecutionMode> {
self.inner.execution_mode()
}
fn prepare_arguments(&self, args: Value) -> Value {
self.inner.prepare_arguments(self.scope_args(args))
}
fn permission_classification(&self, prepared_args: &Value) -> PermissionClassification {
self.inner.permission_classification(prepared_args)
}
async fn execute(
&self,
tool_call_id: &str,
params: Value,
cancel: CancellationToken,
on_update: Option<AgentToolUpdate>,
) -> Result<AgentToolResult, AgentToolError> {
self.inner
.execute(tool_call_id, self.scope_args(params), cancel, on_update)
.await
}
}
#[cfg(feature = "local")]
pub fn local_tools(executor: Arc<dyn ToolExecutor>) -> Vec<Arc<dyn AgentTool>> {
local_tools_for_cwd(executor, std::env::current_dir().unwrap_or_default())
}
#[cfg(feature = "local")]
pub fn local_tools_for_cwd(
executor: Arc<dyn ToolExecutor>,
cwd: PathBuf,
) -> Vec<Arc<dyn AgentTool>> {
vec![
Arc::new(read::ReadTool::new(executor.clone())),
Arc::new(write::WriteTool::new(executor.clone())),
Arc::new(edit::EditTool::new(executor.clone())),
Arc::new(CwdScopedTool::new(Arc::new(bash::BashTool), cwd.clone())),
Arc::new(CwdScopedTool::new(
Arc::new(exec_shell::ExecTool),
cwd.clone(),
)),
Arc::new(exec_shell::GetOutputTool),
Arc::new(exec_shell::KillShellTool),
Arc::new(exec_shell::WriteToProcessTool),
Arc::new(CwdScopedTool::new(Arc::new(ls::LsTool), cwd.clone())),
Arc::new(CwdScopedTool::new(Arc::new(grep::GrepTool), cwd.clone())),
Arc::new(CwdScopedTool::new(Arc::new(find::FindTool), cwd)),
Arc::new(outline::OutlineTool::new(executor.clone())),
Arc::new(git::GitTool::new(executor)),
Arc::new(web_fetch::WebFetchTool),
Arc::new(web_search::WebSearchTool::new()),
]
}
#[cfg(all(not(feature = "local"), feature = "sandbox"))]
pub fn local_tools(executor: Arc<dyn ToolExecutor>) -> Vec<Arc<dyn AgentTool>> {
local_tools_for_cwd(executor, std::env::current_dir().unwrap_or_default())
}
#[cfg(all(not(feature = "local"), feature = "sandbox"))]
pub fn local_tools_for_cwd(
executor: Arc<dyn ToolExecutor>,
_cwd: PathBuf,
) -> Vec<Arc<dyn AgentTool>> {
tracing::warn!(
omitted = ?LOCAL_ONLY_TOOL_NAMES,
"sandbox-only build: local-only tools bypass the ToolExecutor seam and touch \
the host FS/process table directly, so they are NOT registered (fail closed); \
executor-backed tools (read/write/edit/outline/git) and network-only tools \
(web_fetch/web_search) remain"
);
vec![
Arc::new(read::ReadTool::new(executor.clone())),
Arc::new(write::WriteTool::new(executor.clone())),
Arc::new(edit::EditTool::new(executor.clone())),
Arc::new(outline::OutlineTool::new(executor.clone())),
Arc::new(git::GitTool::new(executor)),
Arc::new(web_fetch::WebFetchTool),
Arc::new(web_search::WebSearchTool::new()),
]
}
#[cfg(not(any(feature = "local", feature = "sandbox")))]
pub fn local_tools(_executor: Arc<dyn ToolExecutor>) -> Vec<Arc<dyn AgentTool>> {
compile_error!("theway-daemon requires at least one of the `local` or `sandbox` features");
#[allow(unreachable_code)]
unreachable!()
}
pub fn subagent_tool(
model: impl Into<Option<theway_llm_provider::Model>>,
stream_fn: Option<theway_core::StreamFn>,
registry: SubagentJobRegistry,
memory_dir: PathBuf,
base_dir: PathBuf,
skill_harness_cell: SkillHarnessCell,
session_id: Option<String>,
executor: Arc<dyn ToolExecutor>,
) -> Arc<dyn AgentTool> {
Arc::new(
subagent::SubagentTool::new(
model,
stream_fn,
subagent_tool_sets(memory_dir, base_dir, skill_harness_cell, executor),
crate::agent_specs::launch_resolver(),
crate::agent_specs::spec_names(),
registry,
)
.with_session_id(session_id),
)
}
pub fn subagent_tool_sets(
memory_dir: PathBuf,
base_dir: PathBuf,
skill_harness_cell: SkillHarnessCell,
executor: Arc<dyn ToolExecutor>,
) -> ToolSetResolver {
subagent_tool_sets_for_cwd(
memory_dir,
base_dir,
skill_harness_cell,
executor,
std::env::current_dir().unwrap_or_default(),
)
}
pub fn subagent_tool_sets_for_cwd(
memory_dir: PathBuf,
base_dir: PathBuf,
skill_harness_cell: SkillHarnessCell,
executor: Arc<dyn ToolExecutor>,
cwd: PathBuf,
) -> ToolSetResolver {
assembly::subagent_tools(
&memory_dir,
&base_dir,
&skill_harness_cell,
Arc::new(move || local_tools_for_cwd(executor.clone(), cwd.clone())),
)
}
pub fn node_launcher(
engine: Arc<DagEngine>,
model: impl Into<Option<theway_llm_provider::Model>>,
stream_fn: Option<theway_core::StreamFn>,
cwd: PathBuf,
registry: SubagentJobRegistry,
memory_dir: PathBuf,
base_dir: PathBuf,
skill_harness_cell: SkillHarnessCell,
executor: Arc<dyn ToolExecutor>,
) -> Arc<node_launcher::NodeLauncherImpl> {
node_launcher::node_launcher(
engine,
model.into(),
stream_fn,
cwd.clone(),
registry,
subagent_tool_sets_for_cwd(memory_dir, base_dir, skill_harness_cell, executor, cwd),
crate::agent_specs::launch_resolver(),
)
}
pub fn session_tool_set<'a>(
memory_dir: &std::path::Path,
base_dir: &std::path::Path,
dag_engine: &Arc<DagEngine>,
subagent_registry: &SubagentJobRegistry,
model: impl Into<Option<&'a theway_llm_provider::Model>>,
stream_fn: Option<&theway_core::StreamFn>,
skill_harness_cell: &SkillHarnessCell,
session_id: &str,
executor: Arc<dyn ToolExecutor>,
services: &crate::DaemonServices,
repo: Arc<dyn SessionRepository>,
) -> Vec<Arc<dyn AgentTool>> {
session_tool_set_for_cwd(
memory_dir,
base_dir,
dag_engine,
subagent_registry,
model.into(),
stream_fn,
skill_harness_cell,
session_id,
executor,
services,
repo,
std::env::current_dir().unwrap_or_default(),
)
}
pub fn session_tool_set_for_cwd(
memory_dir: &std::path::Path,
base_dir: &std::path::Path,
dag_engine: &Arc<DagEngine>,
subagent_registry: &SubagentJobRegistry,
model: Option<&theway_llm_provider::Model>,
stream_fn: Option<&theway_core::StreamFn>,
skill_harness_cell: &SkillHarnessCell,
session_id: &str,
executor: Arc<dyn ToolExecutor>,
services: &crate::DaemonServices,
repo: Arc<dyn SessionRepository>,
cwd: PathBuf,
) -> Vec<Arc<dyn AgentTool>> {
let mut tools = local_tools_for_cwd(executor.clone(), cwd.clone());
tools.extend(assembly::engine_tools(
memory_dir,
base_dir,
dag_engine,
subagent_registry,
subagent_tool_sets_for_cwd(
memory_dir.to_path_buf(),
base_dir.to_path_buf(),
skill_harness_cell.clone(),
executor,
cwd.clone(),
),
crate::agent_specs::launch_resolver(),
crate::agent_specs::spec_names(),
model,
stream_fn,
skill_harness_cell,
session_id,
services.reload.clone(),
));
let graph_path = theway_contract::config::sessions_dir_for_cwd(&cwd)
.join(theway_storage::session_graph::SESSION_GRAPH_DB_FILE);
tools.extend(session_graph::SessionGraphTools::create(
repo.clone(),
graph_path,
cwd.clone(),
));
tools.extend(session_tool_result::SessionToolResultTools::create(
repo.clone(),
session_id.to_string(),
));
tools.push(new_cron_job_tool(
skill_harness_cell.clone(),
services.cron.clone(),
));
tools.push(list_cron_jobs_tool(services.cron.clone()));
tools.push(remove_cron_job_tool(
skill_harness_cell.clone(),
services.cron.clone(),
));
tools.push(set_cron_job_state_tool(
skill_harness_cell.clone(),
services.cron.clone(),
));
tools.push(new_trigger_tool(services.dynamic_triggers.clone()));
tools.push(list_triggers_tool(services.dynamic_triggers.clone()));
tools.push(remove_trigger_tool(services.dynamic_triggers.clone()));
tools.push(set_trigger_state_tool(services.dynamic_triggers.clone()));
tools
}
#[cfg(test)]
tests_bridge_macro::tests_bridge!("tools");